Você está na página 1de 8

Programao em C em micro-controladores.

Neste conjunto de tutoriais, tentamos ensinar ao leitor como programar um micro-controlador AVR em C low-level. Para fazer isso, especialmente necessrio compreender como controlar as vrias funes do micro-controlador. Os restantes tutoriais concentram-se nisso. No entanto, para compreender os exemplos dados, e poder aplicar o que ensinado, o leitor necessita de compreender algumas coisas bsicas primeiro, respectivamente: Controlo da funcionalidade do micro-controlador os registers. Pseudo-cdigo/cdigo esqueleto MACROS Variveis volatile Operaes bit-wise em C. assumido que o leitor sabe programar em C e que domina os seguintes conceitos: comentrios, bibliotecas, variveis, funes, ponteiros, ciclos, condies, lgica e bases numricas.

Controlo da funcionalidade do micro-controlador os registers


Os AVR tm vrias funes: podem ser usados para comparar e ler diferenas de potencial, comunicar por serial, Todas estas funes so controladas por registers mas o que so registers? Todos os CPUs tm uma certa memria interna. Esta funciona quase como a memria ram, excepto no uso de ponteiros. O CPU tem acesso directo a esta memria, o que significa que em termos de performance muito mais eficiente usar registers para armazenamento do que memria ram (o compilador em C optimiza automaticamente os programas, dando uso deste boost na performance sempre que possvel da a importncia de usar variveis volatile quando se usam interrupes, estudadas mais frente). No entanto, estes no so s usados para armazenamento, mas tambm para controlar vrias funes dos micro-controladores. Certos bits em certos registers podem controlar o estado de um pino, ligar e desligar o ADC, Nos AVR todos os registers tm o tamanho de 8 bits. Logo, quando necessrio armazenar valores maiores que 255, usam-se mais do que um register. No entanto, este pormenor abstrado pelo compilador, visto que podemos muitas vezes aceder a um conjunto de registers como se fosse um s (como por exemplo, o register TCNT1 do timer1 que corresponde a dois registers, visto que pode conter um valor de 16 bits). Agora que sabemos o que um register, vamos aprender como us-los. As bibliotecas do avr do-nos um header muito til que nos permite aceder directamente aos

registers e bits dos mesmos atravs dos seus nomes: avr/io.h Um exemplo: Para alterar o estado de um pino, alteramos o bit correspondente no register DDRx (em que x corresponde porta. Por exemplo, o pino PB1 est na porta B, logo para alterar o seu estado, alteramos o bit PB1 no register DDRB). Logo, utilizamos o cdigo seguinte: #include <avr/io.h> int main(void) { DDRB |= (1<<PB1); } (quando alteramos o bit para 1, estamos a colocar o pino em output) Se no compreende exactamente como altermos um bit no register, no se preocupe, pois as operaes bit-wise sero explicadas de seguida.

Pseudo-cdigo/cdigo esqueleto
O pseudo-cdigo basicamente uma representao abstracta do cdigo, em linguagem natural. Muitas vezes comea-se por escrever o pseudo-cdigo, e depois vai-se substituindo por linhas de cdigo (muitas vezes o pseudo-cdigo transforma-se nos comentrios). Irei usar isto nos meus tutoriais para ir construindo os programas passo-a-passo. Por exemplo, o famoso programa Hello World, feito passo-a-passo: // Iniciar o programa // Escrever Hello World no Ecr // Terminar o programa Primeiro, fazemos o mais simples: iniciar e terminar o programa. Como vamos precisar de funes Input/Output, parte da inicializao incluir o header stdio.h, o resto comear a funo main(), e terminamos com return 0 (sair do programa com sucesso visto que nos AVRs no existe sistema operativo, a funo main nunca far um return, apenas acabar num loop infinito): // Iniciar o programa: #include <stdio.h> int main(void) { // Escrever Hello World no Ecr return 0; } // Terminar o programa Agora falta a parte funcional do programa: escrever o Hello World no ecr:

// Iniciar o programa: #include <stdio.h> int main(void) { printf(Hello World); // Escrever Hello World no Ecr return 0; } // Terminar o programa

MACROS
Em quase todos os programas de C, temos instrues comeadas por '#'. Estas no so instrues em C, mas sim instrues interpretadas apenas pelo pr-processador, antes da compilao. Por exemplo, quando fazemos #include <qualquercoisa.h>, estamos a indicar ao pr-processador para incluir o contedo do ficheiro qualquercoisa.h no nosso programa. Uma MACRO uma instruo deste tipo, que se comporta como uma funo. So teis quando queremos realizar certas tarefas repetidamente, mas no se justifica o custo em performance de chamar uma funo (para quem programa em C++, isto equivalente ao inline). Por exemplo, duas macros que costumo usar so as seguintes: #define max(I,J) ((I)>(J)?(I):(J)) #define min(I,J) ((I)<(J)?(I):(J)) Antes da compilao, o pr-processador substitui todas as declaraes de max(x,y) e min(x,y) pelo cdigo correspondente, sem ser assim necessrio chamar uma funo (as macros so teis para substituir principalmente funes com s uma linha de cdigo). H vrios pormenores envolvidos na criao de macro (como por exemplo, abusar das parntesis para proteger o cdigo), mas no interessam para este tutorial. No entanto, visto que so muito teis, aconselho os interessados a pesquisar sobre elas.

Variveis volatile
Quando declaramos variveis, podemos controlar certos aspectos de como o cdigo deve acedlas. Uma declarao importante quando se programa AVRs, devido existncia de interrupes, a volatile. Mais frente explicarei a importncia disto, por agora apenas importante reter que quando se declara uma varivel como volatile, estamos a informar que o seu valor pode ser alterado de formas inesperadas, logo deve sempre ir buscar o seu valor actualizado.

Operaes bit-wise em C
Muita da programao em micro-controladores consiste principalmente em manipular bits de certos registers. Para fazer isso, usamos as operaes bit-wise que manipulam valores ao nvel dos bits.

Para quem no compreende bases numricas, e no sabe o que significa manipular bits, aconselho a lerem algum livro/tutorial que trate deste assunto. No entanto, explicado de uma forma breve, o seguinte: Normalmente usamos a base decimal. Isto significa que usamos 10 dgitos diferentes (do 0 ao 9). Com combinaes deles, fazemos diferentes nmeros. Quando queremos um valor acima do dgito maior, transportamos mais um para a posio seguinte (se contarmos desde a direita). Assim podemos dar valores a cada posio no nmero. Por exemplo, o nmero 29 tem um 9 na posio 0 e um 2 na posio 1. A posio 0 corresponde ao valor 10 (1), e a 1 ao valor 10. Assim, podemos chegar ao nmero atravs da conta: 2*10 + 9*10. Nmeros de base binria funcionam da mesma forma que os de base decimal, com a particularidade de apenas utilizarmos dois algarismos: o 0 e o 1. Assim, cada posio tem um valor diferente. Por conveno, chamam-se s posies de um nmero em base binria de bit. Assim, quando falamos em manipular bits, estamos a falar em manipular o valor (0 ou 1) de certas posies. Por exemplo, o nmero 1001 (para facilitar a leitura, costumam-se ler os dgitos separados. Assim, em vez de se ler mil e um, l-se um zero zero um) corresponde ao nmero em decimal 9. Isto porque o bit 0 tem o valor de 1 (2) e o bit 3 tem o valor de 8 (2). Logo, como esses so os nicos bits com dgitos l, chegamos ao 9 atravs da conta: 1*2+1*2. Agora que j conhecemos a base binria, e o que significa manipular bits, vamos ver como podemos manipul-los. Isto feito atravs de operaes bit-wise. Em C, existem cinco operaes bit-wise: | or & and ~ not ^ xor << shift left >> shift right As duas primeiras operaes funcionam como as operaes lgicas ||, &&. No entanto, em vez de testarem a varivel como um todo lgico, testam bit a bit, e o resultado corresponde a essa comparao bit a bit. Em termos de valor lgico, os pares de operaes ||/| e &&/& do exactamente

o mesmo resultado. No entanto, enquanto temos resultados bem definidos com as operaes | e &, as operaes || e && podem dar um valor aleatrio para verdadeiro. Assim, quando se necessitam de valores lgicos, devem-se usar as operaes || e &&, e para manipulao bit a bit, devem-se usar as operaes | e &. Vamos ento comear por estudar essas duas operaes: O or retorna 0 quando ambos os bits so 0, e 1 quando pelo menos um dos bits 1. Olhemos para um exemplo: 111000 | 001110 = 111110 Vamos analisar isto bit a bit. Em ambos os nmeros, o bit 0 tem o valor 0. 0 ou 0 = 0. Logo, o bit 0 do resultado ser um 0. No bit 1, o primeiro nmero tem um 0, mas o segundo tem um 1. 0 ou 1 = 1. Logo, o resultado ter um 1 no bit 1. A mesma coisa ocorre com o bit 2. No bit 3, ambos os nmeros tm um 1. 1 ou 1 = 1. Logo, o resultado ter um 1 no bit 3. Nos restantes bits, o primeiro nmero tem um 1, e o segundo tem um 0. 1 ou 0 = 1. Logo os restantes bits (bits 4 e 5) tero um 1 no resultado. Assim, chegamos ao nmero 111110. Podemos usar isto para colocar o valor 1 numa certa posio num nmero. Por exemplo, temos o nmero 1101 (em decimal o nmero 13), e queremos preencher aquele 0 com um 1. Se fizermos um ou com o nmero 0010 (em decimal o nmero 2), preenchemo-lo. Vejamos um exemplo:

#include <stdio.h> int main(void) { int i = 13; i = i|2; // Equivalente a fazer i |= 2 printf(%d\n, i); // Imprime o nmero 15 em binrio 1111. return 0; }

Vamos agora observar a operao &. O and retorna 0 quando pelo menos um dos bits 0, e 1 quando os dois bits so 1. Por exemplo: 1101 & 0111 = 0101

A anlise deste exemplo ser deixada como um desafio ao leitor. O & muitas vezes usado para colocar a 0 um certo bit. Por exemplo: se tivermos o nmero 10111 (em decimal 23), e quisermos a partir dele obter o nmero 10101 (em decimal 21), podemos fazer a seguinte operao: 10111 & 1101 = 10101:

#include <stdio.h> int main(void) { int i = 23; i = i&13; // 13 em binrio 1101; equivalente a i &= 13; printf(%d\n, i); // Imprime 21 return 0; }

A terceira operao, ~ (not), tambm tem um comportamento semelhante ao seu equivalente lgico, o !. No entanto, foi separado das outras duas operaes, pois esta no pode ser usada como uma operao lgica, visto que ~(true) pode dar um valor verdadeiro mesma (interessantemente, devido forma como a aritmtica dos CPUs funcionam, fazer o ~ de qualquer nmero positivo d um nmero negativo e vice-versa, sendo a nica excepo o -1, j que ~(-1) = 0. No aprofundaremos mais isto, visto que no interessa muito para programar micro-controladores). Vejamos como funciona: ~1101 = 0010 Cada bit do nmero original invertido, logo a partir de um nmero positivo (true), podemos no obter 0 (false), que exactamente o que o ! lgico faz. O ~ muitas vezes utilizado em conjuno com o & para pr um valor 0 num bit. Vejamos porqu: 11101 & 10111 = 10101 Sabendo a posio do bit que queremos pr a 0 (neste caso a posio 4), como chegamos ao seu inverso, de forma a manter o resto do nmero intacto. Usando o ~, claro! Neste caso, fazer: 11101 & 10111 = 10101

igual a fazer: 11101 & (~01000) = 11101 & 10111 = 10101 (mais frente iremos estudar como criar um nmero com apenas um 1 na posio pretendida, sabendo apenas essa posio). Por exemplo, com cdigo agora (reformulao do exemplo do &):

#include <stdio.h> int main(void) { int i = 23; i &= ~(8); // 8 01000 printf(%d\n, i); // Imprime 21. return 0; }

O exclusive or, ou como melhor conhecido, o xor, no tem um equivalente lgico bvio. o mesmo que !=. O seu comportamento o seguinte: retorna 0 quando ambos os nmeros so iguais, e 1 quando so diferentes. Como o & e o |, quando se procura um resultado lgico, equivalente usar o ^ e o !=. Vejamos ento um exemplo 1101 ^ 0101 = 1001 O xor muitas vezes usado para fazer toggle (alterar o valor de 0 para 1 e vice-versa) de um certo bit. Por exemplo, se tivermos um nmero 11x1, e quisermos alterar o estado do bit 1, sem conhecermos o seu valor, basta fazer a seguinte operao: 11x1 ^ 0010 Isto porque quando fazermos um xor com 0, o resultado sempre igual ao do outro nmero (1^ 0 = 1; 0^0 = 0), e quando fazemos um xor com 1, altera sempre (1^1 = 0; 1^0 = 1). (visto que o cdigo de exemplo seria semelhante aos anteriores, iremos passar frente desse passo). Agora s nos falta estudar os operadores de shift. Estes so muito teis porque nos permitem pr um valor em qualquer posio do nmero, ou seja, fazer shift para cima ou para baixo desse mesmo valor.

Vamos utilizar o exemplo do ~ e do &. Sabendo apenas a posio em que queremos pr o 0, e o nmero que tem essa posio a 1, e as restantes a 0, j sabemos que operao utilizar. Mas ainda nos falta uma coisa: como chegamos ao nmero que tem a posio desejada a 1? Para isso usam-se os operadores de shift. Por exemplo, se quisermos colocar o 1 na posio 3, fazemos o seguinte: 1<<3 = 1000

#include <stdio.h> int main(void) { int i = 23; i &= ~(1<<3); // 1<<3 = 8 01000 printf(%d\n, i); // Imprime 21. return 0; }

Esta tcnica tambm utilizada para chegar aos valores utilizador com o or e o xor, sabendo apenas os bits que queremos, respectivamente, colocar a 1, ou alterar o valor. Tambm existe o operador de shift >>, que faz o contrrio do <<. Por exemplo: 111>>2 = 1 Mas menos usado quando se programa micro-controladores. de notar que qualquer overflow completamente esquecido. Por exemplo, se considerarmos um limite de 5 bits: 10111<<3 = 11000 10111>>3 = 00010 Uma pequena curiosidade: dadas as caractersticas das bases numricas, fazer <<x, equivalente a multiplicar por 2^x, e fazer >>x equivalente a dividir por 2^x. E assim terminamos o nosso tutorial acerca das bases de programao necessrias para programar micro-controladores. Esperamos que o leitor esteja agora preparado para se aventurar no mundo da programao low-level dos mesmos!