Você está na página 1de 86

SISTEMAS EMBUTIDOS

BCC 425

Prof: Gabriel Garcia

Baseado nos materiais de Aline Fidêncio e Eduardo Luz


Programação
em
Linguagem C
Linguagem C

Largamente utilizada no desenvolvimento de sistemas


embutidos.

Relativamente simples.

Forma a base de diversas outras linguagens (C++, C#,


Java, Python, etc).
Organização dos Programas em C
Padrão de Escrita

A linguagem C é bastante flexı́vel com relação ao modo de


escrita. Ex.: abre e fecha chaves.

O conjunto de escolhas é denominado padrão de escrita.

Obedecer a um padrão de escrita é uma boa prática de


programação: facilita a visualização do código e pode
ajudar a corrigir erros simples.

O primeiro ponto importante que deve ser adotado como


padrão de escrita é a indentação.
Padrão de Escrita
Padrão de Escrita
Nomes de funções e de variáveis

Podem ter qualquer nome, desde que comecem com uma


letra e contenham letras, números ou o underscore ( ).

C é case sensitive!

contaPosicao, contaValorTotal

LeTeclado(), ImprimeLCD(); leConversor()

Definição de constantes e nomes ou labels são sempre


escritos em maiúscula:
Diretivas de Pré-compilação

#include: inclusão de arquivos, chamados de bibliotecas.


Diretivas de Pré-compilação

#define: permite a troca de sı́mbolos, valores ou textos


antes de compilar o arquivo.

Essa diretiva NÃO cria constantes.

Ela permite criar identificadores ou nomes que serão


substituı́dos pela lista de comandos.

Na prática, isso permite inserir valores constantes no


código de modo simplificado.

Mas, o processo de otimização do compilador pode


computar os valores de modo que o ”valor
constante”sequer apareça no código final.
Diretivas de Pré-compilação
Diretivas de Pré-compilação
Diretivas de Pré-compilação
Diretivas de Pré-compilação
Diretivas de Pré-compilação
#pragma: fornece instruções diretamente para o
compilador.

Assim, o seu funcionamento e as opções possı́veis


dependem do fabricante do compilador e dor processador
para o qual o código está sendo gerado.

É necessário consultar o manual do fabricante para saber


quais os comandos possı́veis.

Geralmente usada para controlar as configurações


básicas do processador, tais como frequência de
operação, capacidade debug, etc.
Função Main
Entrada e Saı́da de dados
Variáveis

< tipo > nomeDaVariavel = valorInicial;

Tipo: é o tipo da variável. Indica seu tamanho e modo de


interpretação.

nomeDaVariavel: é o identificador para a variável criada e


deve ser único.

valorInicial: é o valor que o programa dará inicialmente


para a variável.
Variáveis
Variáveis

Tabela: Tipos de dado e faixa de valores

Tipo Bits Bytes Faixa de valores


char 8 1 -128 a 127
int 16 2 -32.768 a 32.767
float 32 4 −3, 402x10−38 a 3, 402x10−38
double 64 8 1, 797x10−308 a 1, 797x10308
Variáveis

1 unsigned int dec = 200; // 200 em decimal


2 unsigned int hex = 0xC8; // 200 em hexadecimal
3 unsigned int bin = 0b11001001; // 200 em binário
4 // todas as três variáveis possuem o mesmo valor
Variáveis

1 float a = 3.05; // 3.05 e convertido e armazenado


como float.
2 double b = 3.05;// 3.05 e armazenado como double.
3
4 if (a == 3.05){
5 // a comparação e feita entre um float e um double
6 }
7
8 if (a == 3.05f){
9 // a comparação e feita entre um float e outro
float
10 }
11
12 if (b == 3.05){
13 // a comparação e feita entre um double e outro
double
14 }
Conversão de Tipos

A linguagem C permite a conversão de um valor de um


tipo para um valor de outro tipo.

Este processo é chamado de type casting.

Sintaxe: (< novo tipo >) variável/valor

1 float a = (float) 3.05; // 3.05 e convertido e


armazenado como float.

Ao realizar type casting, tenha cuidado para não perder


informação!
Modificadores

São palavras reservadas que, quando são adicionadas


aos tipos básicos de dados, tem a capacidade de alterar
seu comportamento, tamanho ou modo de
armazenamento.

Estes modificadores são inseridos na variável no momento


de sua declaração e acompanham a variável por todo
tempo, não sendo possı́vel alterá-la.
Modificadores de Tamanho

Existem dois modificadores de tamanho: long, short

long: pode ter tamanho maior ou igual ao tipo original.

short: pode ter tamanho menor ou igual ao tipo original.

A decisão de como os modificadores se comportam, cabe


ao compilador.
Modificadores de Tamanho

Tipo Bytes Faixa de valores


int 2 -32.768 a 32.767
long int 4 -2.147.483.648 a 2.147.483.647
short int 2 -32.768 a 32.767
Modificadores de Sinal

Existem dois modificadores de sinal: signed, unsigned

Os tipos declarados como signed possuem um bit


reservado para o sinal. Assim, armazenam menos valores.

Os tipos declarados como unsigned não podem assumir


valores negativos. Em compensação, podem representar
valores duas vezes maior que um tipo signed.
Modificadores de Sinal

Tipo Bytes Faixa de valores


unsigned char 1 0 a 255
signed char 1 -128 a 127
unsigned int 2 0 a 65.535
signed int 2 -32.768 a 32.767
Modificadores de Sinal

Sem os modificadores de sinal:


O tipo inteiro é sinalizado. Logo não precisa escrever
signed int, pois já está implı́cito.

O tipo char não possui comportamento definido, podendo


ou não possuir sinal, dependendo do compilador.

Os tipos float e double sempre possuem sinalização, logo,


unsigned double, por exemplo, não existe.
Modificadores de Acesso

Altera a maneira como o código acessa as variáveis.


volatile: utilizado para indicar que a variável pode ser
alterada a qualquer momento, mesmo fora do código
(memória compartilhada).

const: utilizado para representar que a variável só pode


ser lida, e não modificada.

extern: indica que a variável em questão já foi declarada


em outro lugar, geralmente outro arquivo.
Modificadores de Armazenamento

static: força uma variável a possuir espaço reservado.

register: indica ao compilador que desejamos que a


variável seja armazenada, se possı́vel, nos registros do
processador.
Modificadores de Acesso

1 int resposta;
2 void espera(){
3 resposta = 0;
4 while (resposta != 255); // laço infinito
5 }
6
7 // após o processo de otimização do compilador
8
9 int reposta;
10 void espera(){
11 resposta = 0;
12 while (1); // laço infinito
13 }
Modificadores de Acesso

1 volatile int resposta;


2 void espera(){
3 resposta = 0;
4 while (resposta != 255); // laço pode não ser
infinito
5 }
Modificadores de Acesso

1 volatile const int resposta;


2 void espera(){
3 resposta = 0;
4 while (resposta != 255); // laço pode não ser
infinito
5 }
Ponteiros

Variáveis que armazenam endereços de memória.

Através de ponteiros é possı́vel manipular o conteúdo de


outra variável.

É necessário, na declaração do ponteiro, especificar para


qual tipo de variável ele irá apontar.

A diferença entre declarar um ponteiro e uma variável


comum é a presença do * antes do nome da variável.

Sintaxe: < tipo > *nomeDoPonteiro;


Ponteiros

Os operadores & e * são utilizados em conjunto para a


manipulação de ponteiros.

Operador &: utilizado para obter o endereço de uma


variável.

Operador *: utilizado para acessar o conteúdo da posição


de memória apontada pelo ponteiro.

Como os ponteiros também são variáveis, eles ocupam


memória. Logo, pode-se obter o endereço do ponteiro e
criar ponteiros para ponteiros.
Ponteiros

1 //definindo a variável ivar


2 int ivar;
3 //definindo o ponteiro iptr
4 int *iptr;
5 //o ponteiro iptr recebe o valor do endereço do
variável ivar
6 iptr = &ivar;
7 //as próximas linas são equivalente
8 ivar = 421;
9 *iptr = 421;
Estruturas Homogêneas

Um vetor é uma sequência de objetos do mesmo tipo.

Um vetor pode ser de qualquer um dos tipos da linguagem


C, incluindo estruturas definidas pelo usuário.

A única restrição é que todas as posições possuam o


mesmo tipo.

Os objetos de um vetor são chamados elementos do


vetor e são numerados consecutivamente a partir de zero
(0, 1 , 2, ...). Esses valores são chamados de ı́ndices.
Estruturas Homogêneas
A declaração de um vetor é similar à declaração de
qualquer variável, com a diferença de que é necessário
informar a quantidade de elementos do vetor.

Sintaxe: < tipo > nomeDoVetor[qtdDeElementos];

Essa declaração indica ao compilador que ele deve


reservar espaço suficiente na memória para armazenar
qtdDeElementos do tipo < tipo >.

Além do espaço de cada um dos elementos, também é


reservado espaço para o ponteiro que aponta para o
primeiro elemento do vetor.

Os elementos de um mesmo vetor serão sempre


armazenados em blocos contı́nuos na memória.
Estruturas Homogêneas
O vetor de char é um tipo muito comum para armazenar
um texto.

Neste caso, cada letra do texto é armazenada em uma


posição do vetor.

Um problema com esse tipo de vetor é saber quando o


texto termina. A convenção é utilizar o delimitador ‘\0’.

Neste caso, o tamanho do vetor deve ser uma unidade


maior que a quantidade de letras que se deseja
armazenar.

1 char msg[] = "Bom dia"; // msg possui tamanho 8


Estruturas Homogêneas

As matrizes são variáveis semelhantes ao vetores, com a


diferença de poderem possuir mais de um ı́ndice.

Cada ı́ndice controla uma dimensão da matriz.

Os vetores podem então ser vistos como matrizes


unidimensionais.

Sintaxe para uma matriz de duas dimensões: < tipo >


nomeDaMatriz [linhas][colunas];
Estruturas Heterogêneas

Alternativa para armazenar um conjunto de informações


com elementos de tipos diferentes de dados.

Utilização de um tipo de dado chamado de estrutura.

Os elementos individuais de uma estrutura são chamados


de membros.

Cada membro de uma estrutura pode conter dados de um


tipo diferente dos outros membros.
Estruturas Heterogêneas

Para declarar o formato da estrutura a palavra reservada


struct é utilizada:

1 struct nomeDaEstrutura{
2 tipoDadoMembro_1 nomeMembro_1;
3 tipoDadoMembro_2 nomeMembro_2;
4 //...
5 tipoDadoMembro_n nomeMembro_n;
6 };
Estruturas Heterogêneas

Após definir o formato da estrutura, ela pode ser utilizada


para criar variáveis.

1 struct nomeDaEstrutura nomeDaVariavel;


Estruturas Heterogêneas

1 struct Aluno{
2 char nome[50];
3 int matricula;
4 char conceito;
5 };
6
7 int main (){
8 struct Aluno aluno;
9 aluno.nome = "Joao";
10 aluno.matricula = 201711234;
11 aluno.conceito = ’A’;
12 // ...
13 }
Enumeradores

São definições da linguagem C que visam facilitar a


criação de referências textuais.

1 enum{
2 LABEL1 = VALOR1,
3 LABEL2 = VALOR2,
4 //...
5 LABELN = VALORN
6 };

O valores numéricos são opcionais. Caso sejam omitidos,


a estrutura automaticamente incrementa uma unidade
para cada elemento de enum.
Enumeradores

1 enum{
2 AZUL = 1,
3 VERDE, // VERDE = AZUL + 1
4 VERMELHO = 4
5 };
6
7 // Alternativa com defines
8 #define AZUL 1
9 #define VERDE 2
10 #define VERMELHO 4
Definições de tipo

Utilizadas quando os tipos básicos da linguagem C não


são suficientes para representar as informações.

Neste caso, novos tipos podem ser definidos através da


palavra reservada typedef, seguida do tipo base e do
novo nome.

Sintaxe: typedef < tipo > novoTipo;


Definições de tipo

1 typedef struct {
2 unsigned char bit0:1;
3 unsigned char bit1:1;
4 unsigned char bit2:1;
5 unsigned char bit3:1;
6 unsigned char bit4:1;
7 unsigned char bit5:1;
8 unsigned char bit6:1;
9 unsigned char bit7:1;
10 unsigned char bit0:1;
11 }port;
12
13 // ...
14 port PORTA;
15 PORTA.bit0 = 1;
16 PORTA.bit1 = 0;
Álgebra Booleana

Parte da matemática que permite descrever circuitos


lógicos.

Circuitos lógicos = sistemas que processam sinais cujos


valores possuem apenas dois estados.

Estes sinais não representam necessariamente números,


mas sim estados: falso/verdadeiro, baixo/alto, zero/um.

Para a linguagem C, toda variável que possui valor zero é


considerada falsa. Toda variável que possui valor
diferente de zero é verdadeira.
Álgebra Booleana

Para a álgebra comum temos as seguintes operações


básicas: soma, subtração, multiplicação e divisão.

Para a álgebra booleana são definidas três operações


básicas: NÃO, E e OU.
Operação NÃO

A operação de negação é a operação mais simples da


lógica digital.

Funciona negando a informação de entrada.

Se na entrada o estado for verdadeiro, na saı́da o estado


passa a ser falso.

Utilizando termos numéricos, a operação de negação


inverte os valores em uma variável binária. Os bits que
valem um (1) passam a valer zero (0) e os bits que valem
zero (0) passam a valer um (1).
Operação NÃO
Na linguagem C, a operação lógica de negação é feita
pelo operador exclamação !.

Lembrando que em C uma variável com valor 0 representa


falso e uma variável com valor diferente de zero
representa verdadeiro.

1 char ini = 12;


2 char result;
3 // ini = 0b00001100
4 result = !ini;
5 // result = 0
6 result = !(!ini);
7 // result = 1
Operação E
Toma a decisão baseada em duas informações de
entrada.

Retorna um resultado verdadeiro apenas quando ambas


as entradas são verdadeiras. Se alguma das entradas for
falsa, a saı́da é falsa.

Na linguagem C utiliza-se o operador &&.

1 char A = 8;
2 // A = 0b00001000
3 char B = 5;
4 // B = 0b00000101
5 result = A && B;
6 // result = 1
Operação OU
Toma a decisão baseada em duas informações de
entrada.

Retorna um resultado falso apenas quando ambas as


entradas são falsas. Se pelo menos uma das entradas for
verdadeira, a saı́da é verdadeira.

Na linguagem C utiliza-se o operador ||.

1 char A = 8;
2 // A = 0b00001000
3 char B = 5;
4 // B = 0b00000101
5 result = A || B;
6 // result = 1
Outras Operações

Na álgebra booleana, as portas lógicas podem ser


encadeadas para formar outros circuitos.

A mesma ideia pode ser implementada na linguagem C,


bastando utilizar a definição de cada um dos novos
circuitos.

1 char A = 8;
2 // A = 0b00001000
3 char B = 5;
4 // B = 0b00000101
5 result = (A && !B) || (!A && B);
6 // result = 1
7
8 // exemplo da porta OU-EXCLUSIVO
Bitwise NÃO

Operador ẽxecuta uma operação de negação lógica.

Por ser um operador bitwise, a operação é realizada para


cada um dos bits da variável e não para a variável como
um todo.

1 char A = 12; // 0b00001100


2 char r;
3 r = ˜A;
4 // r = 0b11110011; r = 243
Bitwise E

Operador & executa uma operação de negação lógica.

Executa um E lógico para cada par de bits nas posições


correspondentes das variáveis de entrada e armazena o
resultado de cada uma das operações na variável de
saı́da.

1 char A = 8; // 0b00001000
2 char B = 5; // 0b00000101
3 char r;
4 r = A & B;
5 // r = 0b00000000; r = 0
Bitwise OU

Operador | executa uma operação de negação lógica.

Executa um OU lógico para cada par de bits nas posições


correspondentes das variáveis de entrada e armazena o
resultado de cada uma das operações na variável de
saı́da.

1 char A = 8; // 0b00001000
2 char B = 5; // 0b00000101
3 char r;
4 r = A | B;
5 // r = 0b00001101;
Operadores de Deslocamento

Na linguagem C é representada pelos sı́mbolos: << ou


>>.

Permite que o programador controle o fluxo dos bits numa


variável, sendo esse o procedimento utilizado para a
criação de um sistema de comunicação serial.

As operações de shift em linguagem C podem deslocar os


bits para a esquerda: operador <<, ou direita: operador
>>.
Operadores de Deslocamento

Importante: esta operação apena desloca os bits, ela não


altera o valor da variável original.

Para que o valor seja alterado e armazenado, é necessário


atribuir o resultado a uma variável, que pode ser a própria
variável original.

Além do operando (valor/variável original) e do operador


(maior ou menor), é necessário indicar a quantidade de
deslocamentos a realizar.

Sintaxe:
resultado = variavel >> vezes;
resultado = variavel << vezes;
Operadores de Deslocamento

1 unsigned char antes;


2 unsigned char depois;
3 antes = 0b10111001;
4 depois = antes << 1;
5 // depois = 0b01110010;
Operadores de Deslocamento

1 unsigned char antes;


2 unsigned char depois;
3 antes = 0b10111001;
4 depois = antes >> 1;
5 // depois = 0b01011100;
6 // deslocamento de bits para a direita (lógico)
Operadores de Deslocamento

1 char antes;
2 char depois;
3 antes = 0b10111001; //em complemento de 2
4 depois = antes >> 1;
5 // depois = 0b11011100;
6 // deslocamento de bits para a direita
(aritmético)
Manipulando apenas 1 bit de cada vez

Para manipular um bit por vez, devemos utilizar algumas


regras da álgebra booleana.

Para ligar apenas um bit, utilizamos uma operação OU.

Utilizando uma operação OU bitwise, podemos então


controlar quais bits serão ligados na variável desejada.

Além disso, é necessário criar uma variável de controle,


onde apenas o bit da posição que desejamos ligar possua
valor um e as demais posições possuam valor 0.
Manipulando apenas 1 bit de cada vez

Essa variável de controle é mais conhecida como


máscara.

Posição N ... X+1 X X-1 ... 0


Valor 0 ... 0 1 0 ... 0
Manipulando apenas 1 bit de cada vez

Para ligar apenas o bit 2 de uma variável PORTD


podemos utilizar o seguinte código:

1 void main (void){


2 char PORTD;
3 char mascara;
4 PORTD = 0x00; // todos os leds desligados
5 mascara = 1; // mascara = 0b00000001
6 // deslocando a mascara 2 posições
7 mascara = mascara << 2;
8 // ligando o bit 2
9 PORTD = PORTD | mascara;
10 }
Manipulando apenas 1 bit de cada vez

Para desligar um bit de uma variável, a operação bitwise E


pode ser utilizada.

Posição N ... X+1 X X-1 ... 0


Valor 1 ... 1 0 1 ... 1
Manipulando apenas 1 bit de cada vez

Para desligar apenas o bit 2 de uma variável PORTD


podemos utilizar o seguinte código:

1 void main (void){


2 char PORTD;
3 char mascara;
4 PORTD = 0xFF; // todos os leds ligados
5 mascara = 1; // mascara = 0b00000001
6 // deslocando a mascara 2 posições
7 mascara = mascara << 2;
8 mascara = ˜mascara; // invertendo todos os bits
da máscara
9 // desligando o bit 2
10 PORTD = PORTD & mascara;
11 }
Manipulando apenas 1 bit de cada vez

Para verificar se um determinado bit está ligado ou não,


uma das opções é zerar todos os demais bits de modo que
apenas o bit de interesse continue com seu valor original.

Posição N ... X+1 X X-1 ... 0


Valor 0 ... 0 1 0 ... 0
Manipulando apenas 1 bit de cada vez
Para testar apenas o bit 2 de uma variável PORTD
podemos utilizar o seguinte código:

1 void main (void){


2 char PORTD;
3 char mascara;
4 PORTD = 0x00; // todos os leds ligados (lógica
negativa)
5 mascara = 1; // mascara = 0b00000001
6 // deslocando a mascara 2 posições
7 mascara = mascara << 2;
8 if (PORTD & mascara){
9 // o bit 2 está ligado
10 } else {
11 // o bit 2 está desligado
12 }
13 }
Definição de Macros

Os exemplos acima (ligar, desligar e testar um bit) podem


ser reescritos como macros:

1 #define BitSet(variavel, bit) (variavel |= (1 <<


bit))
2 #define BitClr(variavel, bit) (variavel &= ˜(1 <<
bit))
3 #define BitTst(variavel, bit) (variavel & (1 <<
bit))
Estruturas Condicionais

Utilizadas quando queremos que algumas ações sejam


executadas apenas quando alguma(s) condição(ões)
é(são) verdadeira(s).

As estruturas condicionais são utilizadas para escolha


entre dois blocos de comando distintos para serem
executados.

A estrutura condicional mais simples é a if.

1 if (expressao){
2 // comandos..
3 }
Estruturas Condicionais

A estrutura if pode ainda ser acompanhada de um


segundo bloco de comandos que será executado apenas
quando a expressão for falsa.

Este segundo bloco é indicado pela palavra reservada


else.

1 if (expressao){
2 // comandos se expressão verdadeira
3 } else{
4 // comandos se expressão falsa
5 }
Estruturas Condicionais

Quando apenas uma expressão não é suficiente, as


estruturas condicionais podem ser encadeadas, formando
comandos condicionais aninhados.

1 if (expressao){
2 // lista de comandos se expressão for verdadeira
3 if (expressao2){
4 // lista de comandos se expressão 2 for
verdadeira
5 } else {
6 // lista de comandos se expressão 2 for falsa
7 }
8 } else{
9 // comandos se expressão for falsa
10 }
Estruturas Condicionais
switch ... case: para verificar diferentes valores de uma
mesma variável.

1 switch (variavel){
2 case 1:
3 //se variavel == 1
4 // lista de comandos
5 break;
6 case 2:
7 //se variavel == 2
8 // lista de comandos
9 break;
10 // ...
11 default:
12 //se variavel for diferente de todos os valores
anteriores
13 // lista de comandos default
14 break;
Estruturas de Repetição

Estruturas que permitem a repetição de um trecho de


código várias vezes.

Também chamadas de loops.

Todo loop deve conter uma condição que indique quando


ele deve terminar.

Uma condição de parada mal definida causa o loop


infinito (programa fica preso indeterminadamente).
Estruturas de Repetição

1 for (inicialização; condição; alteração){


2 // comandos...
3 }

1 while (condição){
2 // comandos...
3 }

1 do{
2 // comandos...
3 }while(condição);
Estruturas de Repetição - Rotinas de Tempo

1 unsigned char i;
2 for (i=0; i < 100; i++);
Funções

A modularização é um recurso muito importante das


linguagens de programação.

A modularização permite que um programa seja divido em


sub-rotinas especı́ficas.

A linguagem C possibilita a modularização por meio de


funções.
Funções

Funções permitem que um determinado código possa ser


reutilizado em diversos locais sem a necessidade de se
copiar todo o algoritmo diversas vezes.

Funções com mesma funcionalidade são agrupadas em


bibliotecas.
Funções
Funções

1 tipoDeRetorno nomeDaFunçao (listaDeParametros){


2 // corpo da função
3 // lista de comandos
4 return expressão
5 }

O tipo de retorno de dado deve ser um dos tipo de C, char,


int, float, double ou um tipo definido pelo usuário.

O tipo void serve para indicar que a função não retorna


nenhum valor.
Funções

1 int soma (int num1, int num2)


2 int resp;
3 resp = num1 + num2;
4 return resp;
5 }
6
7 int max (int x, int y);
8 double media (double x1, double x2);
9 float soma (int numElem);
10 void tela(void);
Funções

Para que uma função seja executada, ela precisa ser


chamada.

Qualquer expressão pode conter uma chamada à função,


a qual redirecionará o controle do programa para a função
chamada.

Sintaxe da chamada de uma função:


nomeDaFunção (parâmetrosDaFunção)

1 void main(void){
2 int soma = soma(2,2);
3 }
Referência

Programação de Sistemas Embarcados - Almeida, Moraes,


Seraphim
FIM

Você também pode gostar