View
30
Download
0
Embed Size (px)
Citation preview
5/17/2018 bitwise microcontroladores - slidepdf.com
http://slidepdf.com/reader/full/bitwise-microcontroladores 1/8
Programação 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 necessário compreender como controlar as
várias funções 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 básicas primeiro, respectivamente:
Controlo da funcionalidade do micro-controlador – os registers.
Pseudo-código/código esqueleto
MACROS
Variáveis volatile
Operações bit-wise em C.
É assumido que o leitor sabe programar em C e que domina os seguintes conceitos: comentários,
bibliotecas, variáveis, funções, ponteiros, ciclos, condições, lógica e bases numéricas.
Controlo da funcionalidade do micro-controlador – os registers
Os AVR têm várias funções: podem ser usados para comparar e ler diferenças de potencial,
comunicar por serial, …Todas estas funções são controladas por registers … mas o que são registers?
Todos os CPUs têm uma certa memória interna. Esta funciona quase como a memória ram,
excepto no uso de ponteiros.
O CPU tem acesso directo a esta memória, o que significa que em termos de performance é
muito mais eficiente usar registers para armazenamento do que memória ram (o compilador em C
optimiza automaticamente os programas, dando uso deste “boost” na performance sempre que
possível – daí a importância de usar variáveis volatile quando se usam interrupções, estudadas maisà frente). No entanto, estes não são só usados para armazenamento, mas também para controlar
várias funções 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 têm o tamanho de 8 bits. Logo,
quando é necessário armazenar valores maiores que 255, usam-se mais do que um register. No
entanto, este pormenor é abstraído 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 dão-nos um header muito útil que nos permite aceder directamente aos
5/17/2018 bitwise microcontroladores - slidepdf.com
http://slidepdf.com/reader/full/bitwise-microcontroladores 2/8
registers e bits dos mesmos através 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 código 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 não compreende exactamente como alterámos um bit no register, não se preocupe, pois as
operações bit-wise serão explicadas de seguida.
Pseudo-código/código esqueleto
O pseudo-código é basicamente uma representação abstracta do código, em linguagem natural.
Muitas vezes começa-se por escrever o pseudo-código, e depois vai-se substituindo por linhas de
código (muitas vezes o pseudo-código transforma-se nos comentários). 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
funções Input/Output, parte da inicialização é incluir o header stdio.h, o resto é começar a função
main(), e terminamos com return 0 (sair do programa com sucesso – visto que nos AVRs não existesistema operativo, a função 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ã:
5/17/2018 bitwise microcontroladores - slidepdf.com
http://slidepdf.com/reader/full/bitwise-microcontroladores 3/8
// 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 instruções começadas por '#'. Estas não são instruções
em C, mas sim instruções interpretadas apenas pelo pré-processador, antes da compilação. Por
exemplo, quando fazemos “#include <qualquercoisa.h>”, estamos a indicar ao pré-processador para
incluir o conteúdo do ficheiro qualquercoisa.h no nosso programa.
Uma MACRO é uma instrução deste tipo, que se comporta como uma função. São úteis quando
queremos realizar certas tarefas repetidamente, mas não se justifica o custo em performance de
chamar uma função (para quem programa em C++, isto é equivalente ao inline).
Por exemplo, duas macros que costumo usar são as seguintes:
#define max(I,J) ((I)>(J)?(I):(J))
#define min(I,J) ((I)<(J)?(I):(J))
Antes da compilação, o pré-processador substitui todas as declarações de max(x,y) e min(x,y)
pelo código correspondente, sem ser assim necessário chamar uma função (as macros são úteis para
substituir principalmente funções com só uma linha de código). Há vários pormenores envolvidos
na criação de macro (como por exemplo, abusar das parêntesis para proteger o código), mas não
interessam para este tutorial. No entanto, visto que são muito úteis, aconselho os interessados a
pesquisar sobre elas.
Variáveis volatile
Quando declaramos variáveis, podemos controlar certos aspectos de como o código deve acedê-las. Uma declaração importante quando se programa AVRs, devido à existência de interrupções, é a
volatile. Mais à frente explicarei a importância disto, por agora é apenas importante reter que
quando se declara uma variável como volatile, estamos a informar que o seu valor pode ser alterado
de formas inesperadas, logo deve sempre ir buscar o seu valor actualizado.
Operações bit-wise em C
Muita da programação em micro-controladores consiste principalmente em manipular bits de
certos registers. Para fazer isso, usamos as operações bit-wise que manipulam valores ao nível dos
bits.
5/17/2018 bitwise microcontroladores - slidepdf.com
http://slidepdf.com/reader/full/bitwise-microcontroladores 4/8
Para quem não compreende bases numéricas, e não 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 dígitos diferentes (do 0 ao 9).
Com combinações deles, fazemos diferentes números. Quando queremos um valor acima do dígito
maior, transportamos mais um para a posição seguinte (se contarmos desde a direita). Assim
podemos dar valores a cada posição no número.
Por exemplo, o número 29 tem um 9 na posição 0 e um 2 na posição 1. A posição 0 corresponde
ao valor 10 (1), e a 1 ao valor 10¹. Assim, podemos chegar ao número através da conta: 2*10¹ +⁰
9*10 .⁰
Números de base binária funcionam da mesma forma que os de base decimal, com a
particularidade de apenas utilizarmos dois algarismos: o 0 e o 1. Assim, cada posição tem um valor
diferente. Por convenção, chamam-se às posições de um número em base binária de bit. Assim,
quando falamos em manipular bits, estamos a falar em manipular o valor (0 ou 1) de certas
posições. Por exemplo, o número 1001 (para facilitar a leitura, costumam-se ler os dígitos
separados. Assim, em vez de se ler “mil e um”, lê-se “um zero zero um”) corresponde ao número
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 são os únicos bits com dígitos lá, chegamos ao 9 através da conta: 1*2 +1*2³.⁰
Agora que já conhecemos a base binária, e o que significa manipular bits, vamos ver como
podemos manipulá-los.
Isto é feito através de operações bit-wise.
Em C, existem cinco operações bit-wise:
| – or
& – and
~ – not
^ – xor
<< – shift left
>> – shift right
As duas primeiras operações funcionam como as operações lógicas ||, &&. No entanto, em vez
de testarem a variável como um todo lógico, testam bit a bit, e o resultado corresponde a essacomparação bit a bit. Em termos de valor lógico, os pares de operações ||/| e &&/& dão exactamente
5/17/2018 bitwise microcontroladores - slidepdf.com
http://slidepdf.com/reader/full/bitwise-microcontroladores 5/8
o mesmo resultado. No entanto, enquanto temos resultados bem definidos com as operações | e &,
as operações || e && podem dar um valor aleatório para verdadeiro. Assim, quando se necessitam
de valores lógicos, devem-se usar as operações || e &&, e para manipulação bit a bit, devem-se usar
as operações | e &.
Vamos então começar por estudar essas duas operações:
O or retorna 0 quando ambos os bits são 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 números, 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 número 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 osnúmeros têm um 1. 1 ou 1 = 1. Logo, o resultado terá um 1 no bit 3. Nos restantes bits, o primeiro
número tem um 1, e o segundo tem um 0. 1 ou 0 = 1. Logo os restantes bits (bits 4 e 5) terão um 1
no resultado. Assim, chegamos ao número 111110.
Podemos usar isto para colocar o valor 1 numa certa posição num número.
Por exemplo, temos o número 1101 (em decimal é o número 13), e queremos preencher aquele 0
com um 1. Se fizermos um ou com o número 0010 (em decimal é o número 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 número 15 – em binário
1111.
return 0; }
Vamos agora observar a operação &.
O and retorna 0 quando pelo menos um dos bits é 0, e 1 quando os dois bits são 1.
Por exemplo:1101 & 0111 = 0101
5/17/2018 bitwise microcontroladores - slidepdf.com
http://slidepdf.com/reader/full/bitwise-microcontroladores 6/8
A análise 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 número 10111 (em decimal 23), e quisermos a partir dele obter o
número 10101 (em decimal 21), podemos fazer a seguinte operação: 10111 & 1101 = 10101:
#include <stdio.h>
int main(void) {
int i = 23;
i = i&13; // 13 – em binário 1101; equivalente a i &= 13;
printf(“%d\n”, i); // Imprime 21
return 0; }
A terceira operação, ~ (not), também tem um comportamento semelhante ao seu equivalente
lógico, o !. No entanto, foi separado das outras duas operações, pois esta não pode ser usada como
uma operação lógica, visto que ~(true) pode dar um valor verdadeiro à mesma (interessantemente,
devido à forma como a aritmética dos CPUs funcionam, fazer o ~ de qualquer número positivo dá
um número negativo e vice-versa, sendo a única excepção o -1, já que ~(-1) = 0. Nãoaprofundaremos mais isto, visto que não interessa muito para programar micro-controladores).
Vejamos como funciona:
~1101 = 0010
Cada bit do número original é invertido, logo a partir de um número positivo (true), podemos
não obter 0 (false), que é exactamente o que o ! lógico faz.
O ~ é muitas vezes utilizado em conjunção com o & para pôr um valor 0 num bit. Vejamos porquê:
11101 & 10111 = 10101
Sabendo a posição do bit que queremos pôr a 0 (neste caso a posição 4), como chegamos ao seu
inverso, de forma a manter o resto do número intacto.
Usando o ~, claro!
Neste caso, fazer:11101 & 10111 = 10101
5/17/2018 bitwise microcontroladores - slidepdf.com
http://slidepdf.com/reader/full/bitwise-microcontroladores 7/8
é igual a fazer:
11101 & (~01000) = 11101 & 10111 = 10101
(mais à frente iremos estudar como criar um número com apenas um 1 na posição pretendida,
sabendo apenas essa posição).
Por exemplo, com código agora (reformulação 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, não tem um equivalente lógico óbvio. É o
mesmo que !=. O seu comportamento é o seguinte: retorna 0 quando ambos os números são iguais,
e 1 quando são diferentes. Como o & e o |, quando se procura um resultado lógico, é equivalente
usar o ^ e o !=.
Vejamos então 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 número 11x1, e quisermos alterar o estado do bit 1, sem
conhecermos o seu valor, basta fazer a seguinte operação:
11x1 ^ 0010
Isto porque quando fazermos um xor com 0, o resultado é sempre igual ao do outro número (1^0
= 1; 0^0 = 0), e quando fazemos um xor com 1, altera sempre (1^1 = 0; 1^0 = 1).
(visto que o código de exemplo seria semelhante aos anteriores, iremos passar à frente desse
passo).
Agora só nos falta estudar os operadores de shift.
Estes são muito úteis porque nos permitem pôr um valor em qualquer posição do número, ouseja, fazer shift para cima ou para baixo desse mesmo valor.
5/17/2018 bitwise microcontroladores - slidepdf.com
http://slidepdf.com/reader/full/bitwise-microcontroladores 8/8
Vamos utilizar o exemplo do ~ e do &. Sabendo apenas a posição em que queremos pôr o 0, e o
número que tem essa posição a 1, e as restantes a 0, já sabemos que operação utilizar.
Mas ainda nos falta uma coisa: como chegamos ao número que tem a posição desejada a 1?
Para isso usam-se os operadores de shift.
Por exemplo, se quisermos colocar o 1 na posição 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 técnica também é 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.
Também existe o operador de shift >>, que faz o contrário 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 características das bases numéricas, 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 programação necessárias para
programar micro-controladores. Esperamos que o leitor esteja agora preparado para se aventurar no
mundo da programação “low-level” dos mesmos!