Conhecimento necessário: [HW] Módulo de Controlo, [AVR] Contadores/Temporizadores, [LIB] Pinos, [LIB] Espera, [LIB] Temporizadores
Muitas vezes existe a necessidade de criar programas de espera em microcontroladores, é necessário tempo para que ocorram as acções ou esperar até ao fim das mesmas. Segundo este conceito um dos métodos mais fáceis para a criação de uma espera no trabalho de um microcontrolador é sobrecarregar seu processador com uma ação alternativa - por exemplo, fazê-lo contar grandes números. A partir da frequência do processador pode ser calculado o número de contagens de forma a obter um determinado intervalo de tempo. Contando um número de zero até ao valor da frequência do processador em hertz-s, teoricamente deveria criar uma espera de um segundo. Por várias razões isso não é tão simples na prática.
Se o processador do microcontrolador calcula usando números na forma binária é tão grande como seu barramento interno (o AVR tem 8 bits), então é preciso um disparo do processador para executar uma operação aritmética, por exemplo, adicionando 1 a qualquer valor. Para operar com milhares ou milhões o número tem de ser de 16 ou 32 bits e para um processador de 8 bits é preciso mais do que um disparo para calculá-los. Então, ao lidar com grandes números, é preciso estar familiarizado com o interior do processador - e conhecer exatamente seus comandos.
Ao programar em linguagens avançadas (C, por exemplo), os programas não são escritos diretamente na base do comando, a fim de criar uma espera de software, é preciso conhecer também o seu compilador que converte o programa para código de máquina. A partir deste, depende de quantas instruções (e fases portanto) que leva para realizar uma operação aritmética. A complexidade é adicionada pela capacidade dos compiladores de converter o programa em código de máquina de várias formas - por exemplo, fazendo com que o código de máquina, poupe o mais possível a memória ou seja facilmente executável. Estas acções do compilador são as chamadas otimizações. Com diferentes modos de otimização o softaware de espera e as suas durações podem ser diferentes do esperado.
O que se segue é um exemplo de software para gerar atraso com o microcontrolador AVR. Uma parte de um programa em linguagem C é escrito, e conta a variável x num ciclo de 0 a 100. Dentro de cada ciclo de uma instrução vazia explícita é concluída. Ela é necessária, porque, se o conteúdo do ciclo está vazio, o compilador otimiza-o fora do programa, uma vez que considera que este é inútil.
unsigned char x; // Cycle until x is 100 for (x = 0; x < 100; x++) { // With empty instruction nop asm volatile ("nop"); }
Esta é a mesma parte do programa depois de compilar. Os 2 números hexadecimais à esquerda são código de máquina e à direita está o comando com operando(s) em linguagem assembler. O código de máquina e a linguagem assembler são conformais; o assembler é apenas para apresentar o código de máquina de uma forma legível para os seres humanos .Na compilação, a otimização da duração do programa é utilizado (parâmetro do compilador -Os).
80 e0 ldi r24, 0x00 ; r24 loading number 0 to the index 00 00 nop ; Empty operation 8f 5f subi r24, 0xFF ; subtracting 255 form the r24 index, that means adding +1 84 36 cpi r24, 0x64 ; comparing r24 index with number 100 e1 f7 brne .-8 ; If the comparison was wrong, then transfer 8-baits back
Na forma compilada pode ser visto o que realmente acontece com o ciclo na linguagem C e este pode ser usado para calcular o número de ciclos de relógio são necessários para completar um ciclo de um período. A informação sobre o efeito das instruções e tempo de operação pode ser encontrada a partir das instruções da datasheet do AVR. No exemplo dado, são necessários 4 ciclos de relógio para completar 4 instruções num período de ciclo, porque todas as instruções exigem uma taxa de relógio. Além disso, uma taxa de relógio é usado antes do ciclo para a instrução de carregamento e depois uma taxa de relógio extra para sair do ciclo. Assumindo que a taxa de relógio em funcionamento do controlador é 14,7456 MHz, todo a espera produzida pelo programa pode ser calculada.
(1 + 100 ⋅ 4 + 1) / 14745600 = 27,26 μs
A espera produzida no exemplo, é em microssegundos e a variável usada é de 8 bits pelo que o código de máquina é bastante simples. Para produzirmos uma espera na ordem dos milissegundos, precisamos contar números muito maiores e, portanto, o código de máquina fica maior. Ciclos de trabalho dentro do outro também podem ser usados, mas com este método o atraso não está em relação linear com o número de ciclos porque com cada nível de ciclo de um pequeno atraso adicional ocorre.
O objetivo deste exercício não é a criação de uma espera de software precisa ao nível de código de máquina, porque isto constitui um trabalho muito preciso e temos já as funções necessárias para produzir esperas na avr-libc e na biblioteca da HomeLab. Estes são utilizados também nos exemplos a seguir.
Ao lidar com o software de espera é importante saber, que, independentemente de sua simplicidade de base, é um método extremamente ineficiente do ponto de vista do consumo de energia. Durante todos os ciclos de relógio quando o microcontrolador está a contar o inútil, energia é consumida. Portanto, quando no uso de aplicações que operam com baterias, não é sábio escrever software com esperas muito grandes. Mais sábio é usar temporizadores de hardware, que trabalham de forma independente e despertam o processador da hibernação quando chega o momento de continuar o trabalho.
As esperas por software não são o único método para a criação de intervalos. O mesmo pode ser feito usando timer. Timer é um hardware que conta a uma certa frequência. A freqüência de relógio do timer pode ser gerada a partir da frequência do microcontrolador ou de algum outro tipo de ritmo proveniente de fora. Em geral, a frequência de relógio pode ser dividida com um multiplicador de frequência para alcançar uma menor - isto é feito com um prescaler. Facto importante é que o valor do temporizador de freqüência de relógio fixo é linearmente proporcional ao tempo. O tempo pode ser calculado multiplicando o período da frequência de relógio do temporizador pelo valor do temporizador.
Os contadores AVR podem informar sobre o overflow do contador ou quando ocorre um match. O overflow ocorre quando o contador tem o valor máximo possível e o ciclo começa tudo de novo a partir de 0. O alcançar de um valor pré-definido durante o momento de crescimento do valor do contador é comparado com o valor fornecido pelo utilizador. Na ocorrência do evento, os bits nos índices de status da AVR são automaticamente definidos como high.
Para gerar uma espera utilizando um temporizador, é necessário apenas definir o temporizador e esperar que o bit de status vá para high. Diferentemente do software de espera, o trabalho do timer não está de dependente do compilador, o que o torna mais confiável. Ao mesmo tempo, a diversidade (ou complexidade) da configuração do contador AVR pode ser considerado bastante problemático. Dependendo do sinal de temporização do microcontrolador, pode acontecer que não ser possível dividir exatamente pelo período de espera desejado e a espera não ser precisa.
O código seguinte de um programa é acerca da função de espera de software sw_delay_ms, que determina uma determinada espera em milissegundos usando o parâmetro count. A função usa a função da biblioteca avr-libc _delay_ms que é parcialmente escrita em linguagem assembler. A razão pela qual _delay_ms não é utilizada neste exercício imediatamente é que ao usar _delay_ms problemas podem ocorrer quando as esperas são longas. A função sw_delay_ms permite a criação de esperas de 65535 ms, sem quaisquer complicações.
// Software delay in milliseconds void sw_delay_ms(unsigned short count) { // Counting the variable of the delay to 0 while (count-- > 0) { // 1ms delay with a special function _delay_ms(1); } }
O programa seguinte usa a função dada, criando duas esperas no loop infinito: 100 ms e 900 ms. Durante a espera mais curta o LED é iluminado e durante a mais longa este é desligado. Resultado: o LED pisca periodicamente.
// The demonstration program of the software delay of the HomeLab // The program is blinking a LED for a moment after ~1 second #include <homelab/pin.h> #include <homelab/delay.h> // Main program int main(void) { // Setting the pin of the LED as output pin_setup_output(led_debug); // Endless loop while (true) { // Lighting the LED pin_clear(led_debug); // Software delay for 100 ms sw_delay_ms(100); // Switching off the LED pin_set(led_debug); // Software delay for 900 milliseconds sw_delay_ms(900); } }
Embora pareça que o indicador pisca a cada segundo, o tempo é na verdade um pouco mais, porque as chamadas das funções de espera e do LED tomam um par de ciclos de relógio do microcontrolador.
O código que se segue é uma função de espera com base num temporizador um pouco simplificado. O princípio de contagem é o mesmo da função de espera de software - uma quantidade desejada de esperas de 1 ms são produzidas. O atraso é produzido com um contador 0 de 8 bits. É calculado anteriormente que à frequência do relógio de 14,7456 MHz, o sinal de sincronismo tem de ser dividido, pelo menos, 64 vezes, de modo a que o contador não chegue ao overflow em 1 ms. O valor que o contador deve ter para que o overflow ocorra após 1 ms é apresentado sob a forma de uma expressão sendo a variável timer_start. F_CPU que é uma constante na macro-linguagem, mostra a freqüência de relógio em Hz. A frequência de relógio deve ser 25,6 no momento, mas uma vez que fracções não podem ser usadas, o valor inicial será estabelecido em 26. Infelizmente aqui surge um erro no tempo de espera, sendo no entanto, bastante pequeno (-1,7 μs).
No ciclo ocorre a inicialização do contador e a da flag de overflow (que será 1 quando ocorre). De seguida, espera-se até que o contador atinja 256 a partir do valor inicial, ou seja, o overflow. No momento do overflow a flag de overflow passa a high e a espera de 1 ms teve já lugar. No final da função do temporizador é parado.
// Hardware delay in milliseconds void hw_delay_ms(unsigned short count) { // Calculating the initial value of the timer register unsigned char timer_start = 256 - F_CPU / 1000 / 64; // Starting the timer timer0_init_normal(TIMER0_PRESCALE_64); // Counting the variable of the delay to the 0 while (count-- > 0) { // Initializing the timer timer0_set_value(timer_start); // Zeroing the overflow flag timer0_overflow_flag_clear(); // Waiting for overflow while (!timer0_overflow_flag_is_set()) { asm volatile ("nop"); } } // Zeroing the overflow flag timer0_overflow_flag_clear(); // Stoping the timer timer0_stop(); }
A função de espera referenciada usa uma biblioteca de temporizador cujo código-fonte para o controlador ATmega é parecida com o seguinte:
// Timer 0 prescaler selection type typedef enum { TIMER0_NO_PRESCALE = 0x01, TIMER0_PRESCALE_8 = 0x02, TIMER0_PRESCALE_32 = 0x03, TIMER0_PRESCALE_64 = 0x04, TIMER0_PRESCALE_128 = 0x05, TIMER0_PRESCALE_256 = 0x06, TIMER0_PRESCALE_1024 = 0x07 } timer0_prescale; // Setting Timer 0 to a normal mode inline void timer0_init_normal(timer0_prescale prescale) { TCCR0 = prescale & 0x07; } // Stopping the Taimer 0 inline void timer0_stop() { TCCR0 = 0x00; } // Taimer 0 value set inline void timer0_set_value(unsigned char value) { TCNT0 = value; } // Timer 0 overflow flag clear inline void timer0_overflow_flag_clear(void) { bit_set(TIFR, TOV0); } // Timer 0 overflow flag state reading inline bool timer0_overflow_flag_is_set(void) { return (bit_is_set(TIFR, TOV0) ? true : false); }
O que se segue é um programa semelhante ao exemplo de espera de software. Nos meios-períodos mais curtos de 100 ms, o LED é iluminado e nos meios-períodos mais longos é desligado. Como resultado o LED pisca a cada segundo. Infelizmente, neste exemplo, o período não é precisamente um segundo também, porque a execução de outras funções do programa leva tempo também. Para uma temporização exata, um temporizador de 16 bits com interrupções deve ser utilizado.
// Demonstration program of hardware delay of the HomeLab // The Program blinks LED for a moment after every ~1 second #include <homelab/pin.h> #include <homelab/delay.h> // Main program int main(void) { // Setting the pin of the LED as output pin_setup_output(led_debug); // Endless loop while (true) { // Lighting the LED pin_clear(led_debug); // Hardware delay for 100 milliseconds hw_delay_ms(100); // Switch off of the LED pin_set(led_debug); // Hardware delay for 900 milliseconds hw_delay_ms(900); } }