In the beginner's mind there are many possibilities; in the expert's mind there are few. - Shunryu Suzuki
A vericação de sistemas embarcados apresenta algumas restrições, de modo geral não é possível inferir sobre a operação do sistema sem paralisá-lo. Como este tipo de sistema possui vários dispositivos agregados, que funcionam independentemente do processador, é necessário utilizar abordagens diferentes para realizar o debug.
7Mais informações sobre debug de sistemas embarcados referir ao artigo The ten secrets of embedded debug-
Tabela 2.5: Operação bit clear com dene
Operação Bit clear
Passo a Passo
char bit = 2 ;
char mascara ;
mascara = 1 << bit ; arg = arg & ~mascara ;
Uma linha arg = arg & ~(1<<bit )
Com dene #define BitSet ( arg , b i t ) ( ( arg ) &= ~(1<< b i t ) )
Tabela 2.6: Operação bit ip com dene
Operação Bit ip
Passo a Passo
char bit = 2 ;
char mascara ;
mascara = 1 << bit ; arg = arg ^ mascara ;
Uma linha arg = arg ^ (1<<bit )
Tabela 2.7: Operação bit test com dene
Operação Bit clear
Passo a Passo
char bit = 2 ;
char mascara ;
mascara = 1 << bit ; arg = arg & mascara ;
Uma linha arg = arg & (1<<bit )
Devemos lembrar que além do software devemos levar em conta possíveis problemas advindos do hardware. Debounce, tempo de chaveamento, limite do barramento de comunicação são exemplos de pontos a serem considerados no momento de depuração.
Externalizar as informações.
A primeira necessidade é conhecer o que está acontecendo em teu sistema. Na programação tradicional para desktop é comum utilizarmos de mensagens no console avisando o estado do programa.
#include "stdio.h"
#include "serial.h"
// i n i c i o do programa
int main (int argc , char* argv [ ] ) {
printf (" Inicializando sistema") ; i f ( CheckForData ( ) )
{
printf ("Chegou informação") ; }
else {
printf ("Problemas na comunicação") ; }
return 0 ; }
Devemos ter em mente onde é necessário colocar estes alertas e lembrar de retirá-los do código nal.
Para a placa em questão utilizaremos o barramento de leds que está ligado à porta D. A ope- ração deste dispositivo será estudada posteriormente em detalhes. Por enquanto basta sabermos que cada bit da variável PORTD está ligada à um led diferente. Por causa da construção física da placa, o led é aceso com valor 0 (zero) e desligado com o valor 1 (um). Além disso temos que congurar a porta D. Isto é feito iniciando a variável TRISD com o valor 0x008.
// d e f i n e s para p o r t a s de entrada e saíd a
#define PORTD ( * (volatile near unsigned char*) 0xF83 ) #define TRISD ( * (volatile near unsigned char*) 0xF95 )
// i n i c i o do programa
void main (void) interrupt 0
{
// configurando todos os pinos como s a í d a s
TRISD = 0x00 ; PORTD = 0xFF ; // d e s l i g a todos os l e d s // l i g a apenas o b i t 1 . BitClr ( PORTD , 1 ) ; //mantém o sistema l i g a d o i nd e f i ni d a me n t e for( ; ; ) ; }
Devemos utilizar os leds como sinais de aviso para entendermos o funcionamento do programa. Isto pode ser feito através das seguintes ideias: Se passar desta parte liga o led X, Se entrar no IF liga o led Y, se não entrar liga o led Z, Assim que sair do loop liga o led W.
Programação incremental
Ao invés de escrever todo o código e tentar compilar, é interessante realizar testes incrementais. A cada alteração no código realizar um novo teste. Evitar alterar o código em muitos lugares simultaneamente, no caso de aparecer um erro ca mais difícil saber onde ele está.
Checar possíveis pontos de Memory-leak
Se for necessário realizar alocação dinâmica garantir que todas as alocações são liberadas em algum ponto do programa.
Cuidado com a fragmentação da memória
Sistemas com grande frequência na alocação/liberação de memória podem fragmentar a memória até o ponto de inviabilizar os espaços livres disponíveis, eventualmente travando o sistema. Quando trabalhar com rotinas de nível mais baixo, mais próximo ao hardware, tente utilizar apenas mapeamento estático de memória.
Otimização de código
Apenas se preocupe com otimização se estiver tendo problemas com o cumprimento de tarefas. Mesmo assim considere em migrar para uma plataforma mais poderosa. Sistemas embarcados preconizam segurança e não velocidade.
Caso seja necessário otimizar o código analise antes o local de realizar a otimização. Não adianta otimizar uma função grande se ela é chamada apenas uma vez. Utilize-se de ferramentas do tipo proler sempre que possível. Isto evita a perda de tempo e auxilia o programador a visualizar a real necessidade de otimização de código.
Reproduzir e isolar o erro
Quando houver algum erro deve-se primeiro entender como reproduzi-lo. Não é possível tentar corrigir o erro se não houver maneira de vericar se ele foi eliminado.
No momento em que se consegue um procedimento de como reproduzir o erro podemos começar a visualizar onde ele pode estar. A partir deste momento devemos isolar onde o erro está acontecendo. Uma maneira de se fazer isto em sistemas embarcados é colocar um loop innito dentro de um teste, que visa vericar alguma condição de anomalia. Se o sistema entrar neste teste devemos sinalizar através dos meios disponíveis, ligar/desligar algum led por exemplo.
// aqui tem um monte de código . . .
i f ( PORTB >= 5) //PORTB não d e v e r i a s e r um v a l o r maior que 5 .
{
BitClr ( PORTD , 3 ) ; // l i g a o l e d 3
for( ; ; ) ; // t r a v a o programa
}
// aqui continua com um monte de código . . .
2.11 Ponteiros e endereços de memória
Writing in C or C++ is like running a chain saw with all the safety guards removed. - Bob Gray
Toda variável criada é armazenada em algum lugar da memória. Este lugar é denido de maneira única através de um endereço.
Para conhecermos o endereço de uma variável podemos utilizar o operador &. Cuidado! Este operador também é utilizado para realização da operação bitwise AND. Exemplo:
// c r i a a v a r i á v e l a num endereço de memória a s e r // d e c i d i d o p e l o compilador
int a = 0 ; a = a + 1 ;
printf ( a ) ; // imprime o v a l o r 1
Conhecer o endereço de uma variável é muito útil quando queremos criar um ponteiro para ela.
Ponteiro é uma variável que, ao invés de armazenar valores, armazena endereços de memória. Através do ponteiro é possível manipular o que está dentro do lugar apontado por ele.
Para denir um ponteiro também precisamos indicar ao compilador um tipo. A diferença é que o tipo indica quanto cabe no local apontado pelo ponteiro e não o próprio ponteiro.
Sintaxe:
tipo * nome da variavel ; Exemplo:
int *apint ; float * apfloat ;
Deve-se tomar cuidado, pois nos exemplos acima, apint e apoat são variáveis que armazenam endereços de memória e não valores tipo int ou oat. O lugar APONTADO pela variável apint é que armazena um inteiro, do mesmo modo que o lugar apontado por oat armazena um valor fracionário.
Se quisermos manipular o valor do endereço utilizaremos apint e apoat mas se quisermos manipular o valor que esta dentro deste endereço devemos usar um asterisco antes do nome da variável. Exemplo:
apfloat = 3 . 2 ; * apfloat = 3 . 2 ;
A primeira instrução indica ao compilador que queremos que o ponteiro apoat aponte para o endereço de memória número 3.2, que não existe, gerando um erro. Se quisermos guardar o valor 3.2 no endereço de memória apontado por apoat devemos utilizar a segunda expressão.
Para trabalhar com ponteiros é preciso muito cuidado. Ao ser denido, um ponteiro tem como conteúdo não um endereço, mas algo indenido. Se tentamos usar o ponteiro assim mesmo, corremos o risco de que o conteúdo do ponteiro seja interpretado como o endereço de algum local da memória vital para outro programa ou até mesmo para o funcionamento da máquina. Neste caso podemos provocar danos no programa, nos dados, ou mesmo travar a máquina.
É necessário tomar cuidado ao inicializar os ponteiros. O valor atribuído a eles deve ser realmente um endereço disponível na memória.
Por exemplo, podemos criar um ponteiro que aponta para o endereço de uma variável já denida:
// d e f i n i n d o a v a r i á v e l i v a r
int ivar ;
// d e f i n i n d o o p o n t e i r o i p t r
int *iptr ;
//o p o n t e i r o i p t r recebe o v a l o r do endereço da v a r i á v e l i v a r
iptr = &ivar ;
// as próximas l i n h a s são e q u i v a l e n t e s
ivar = 421; *iptr = 421;
Com sistemas embarcados existem alguns endereços de memória que possuem características especiais. Estes endereços possuem registros de conguração, interfaces com o meio externo e variáveis importantes para o projetista. É pelo meio da utilização de ponteiros que é possível acessar tais endereços de maneira simples, através da linguagem C.
Arquitetura de microcontroladores
Any suciently advanced technology is indistinguishable from magic. - Arthur C. Clarke
Os microcontroladores são formados basicamente por um processador, memória e periféricos interligados através de um barramento conforme Figura 3.1.
Em geral, os periféricos são tratados do mesmo modo que a memória, ou seja, para o pro- cessador não existe diferença se estamos tratando com um valor guardado na memória RAM ou com o valor da leitura de um conversor analógico digital. Isto acontece porque existem circuitos eletrônicos que criam um nível de abstração em hardware. Deste modo todos os dispositivos aparecem como endereços de memória.