====== Contadores/Temporizadores====== Os contadores, que em certo sentido, também podem ser chamados temporizadores, são uma das mais importantes sub-funções de um microcontrolador. Estes permitem precisamente processos temporais, gerar sinais e contar os eventos. Um contador converte o número de ciclos de entrada para um valor binário usando um array de triggers. O número máximo de ciclos contados depende do comprimento deste array, e este facto é assinalado pelo comprimento do código binário. Um AVR tem contadores de 8 e 16 bits. Se um temporizador atingiu o seu valor máximo (255 em 8-bits ou 65.535 em 16 bits), o ciclo seguinte irá gerar um overflow e o contador dá a volta para 0. O sinal de relógio de um contador pode vir do sinal do relógio do microcontrolador, e nesse caso é possível diminuir o seu valor usando um divisor. Alguns AVRs tem um gerador de sinal de relógio interno independente, que pode ser modificado para funcionar mais rapidamente utilizando um multiplicador de frequência. Os contadores também podem ser diferentes segundo os casos de aplicação e modos de trabalho. ===== Modo padrão de um Contador ===== No modo padrão, um contador não faz nada mais do que contar continuamente números sequenciais. O seu valor pode, claro, ser lido e alterado a partir do programa a qualquer momento. A única função adicional no modo padrão é causar uma interrupção no overflow do contador. O modo padrão é normalmente usado para executar uma seção do programa a determinados intervalos. Task: Make an 8 MHz ATmega128 fire an interrupt every 10 ms (frequency 100 Hz). For this task, the 8-bit counter 0 is suitable. #include ISR(TIMER0_OVF_vect) { // Give the counter such a value // that the next overflow occurs in 10 ms. // Formula: 256 - 8 MHz / 1024 / 100 Hz = 177,785 = ~178 TCNT0 = 178; } int main() { // To make the first overflow interrupt fire in 10 ms as well, // the counter needs to be initialized here. TCNT0 = 178; // Prescaler value 1024 TCCR0 = 0x07; // Allow overflow interrupts TIMSK |= (1 << TOIE0); // Allow interrupts globally sei(); // Endless loop while (1) continue; } O contador neste exemplo não gerará a interrupção a exactamente cada 10 ms. Para fazer isso seria necessário dar ao contador um valor decimal, e isto não é possível. Para conseguir um intervalo preciso entre as interrupções, tanto o valor do divisor como o valor inicial do contador têm de ser escolhidos de modo a que o resultado da sua divisão seja um número exacto. Isto nem sempre é possível, em especial, com os contadores de 8 bits pois a sua gama de valores é bastante pequena. Para conseguir um intervalo mais preciso ou maior, um contador de 16 bits pode ser utilizado. ==== Contador de Relógio Externo ==== É também possível a utilização de uma fonte de relógio externo como sinal de contador de relógio. Um AVR tem um pino chamado Tn para esta finalidade, sendo n o número associado ao contador. O sinal de relógio externo e a polaridade podem ser selecionados usando o registro prescaler. ==== Eventos Temporizados ==== Uma vez que os contadores permitem operações de temporização, os microcontroladores AVR mais complexos têm uma opção para eventos específicos de tempo ao nível do hardware. Esta parte do contador é designada por unidade de captura de entrada. Há uma escolha entre dois eventos: a mudança de valor num pino especial de entrada ou no valor do resultado do camparador analógico. Se ocorrer o evento selecionado, o valor do contador é gravado num registo especial, de onde pode ser lido a qualquer momento. Se o evento demorar mais do que o tempo de overflow do contador, o programa tem para contar os overflows e levá-los em consideração no cálculo do resultado final. Task: Measure the frequency of an external 122 Hz - 100 kHz logical square signal using an 8 MHz ATmega128. The measurement has to be at 1 Hz precision. The program uses a 16-bit counter with 1 input capture unit. #include unsigned long frequency; // Interrupt for the event ISR(TIMER1_CAPT_vect) { // Counter to 0 TCNT1 = 0; // The result is valid only if the counter // has not overflowed yet if (!(TIFR & (1 << TOV1))) { // Calculating the frequency from the period frequency = (unsigned long)8000000 / (unsigned long)ICR1; } else { // Frequency is less than 122 Hz frequency = 0; // Set the counter's overflow flag to 0 TIFR &= ~(1 << TOV1); } } int main() { // Register a rising front, prescaler value 1 TCCR1B = (1 << ICES1) | (1 << CS10); // Allow event interrupts TIMSK = (1 << TICIE1); // Allow interrupts globally sei(); // Endless loop while (1) continue; } O programa dispara uma interrupção de cada vez que uma frente ascendente ocorre no sinal externo. Durante a interrupção, o overflow do contador é verificado - isto pode acontecer se a frequência do sinal estiver abaixo de 122 Hz (8 MHz / 216) e, neste caso, o valor do contador não reflecte um período real. A frequência é calculada usando números de 32 bits para obter o inverso do período. A primeira coisa é definir o contador para 0, pois o temporizador funciona no mesmo sinal de relógio que o processador e cada execução da instrução que ocorra após o evento externo encurta o período medido corrompendo o resultado. A frequência máxima medida é limitada pelo tempo gasto no programa de interrupção. Apanhar eventos e registar o tempo que levou para que eles ocorram também pode ser resolvido no nível do software. É possível utilizar interrupções externas ou outras e ler o valor do contador durante estes eventos. O apanhar de eventos ao nível de hardware deverá ser executado de forma independente do programa principal e em eventos de tempo relativamente curtos (ou frequentes). ===== Geração de Sinal ===== Os contadores mais complexos podem gerar um sinal, além de cronometrarem a duração dos mesmos. Para este efeito, o contador tem uma unidade de comparação à saída e uma unidade de comparação de correspondência. A unidade de saída de comparação tem registros com a mesma largura de bits que o contador e os valores desses registos são comparados com o valor do contador enquanto ele estiver em execução. Uma interrupção pode ser gerada e os valores dos pinos especiais podem ser alterados de cada vez que o valor do contador for igual ao valor no registo na unidade de comparação. Neste momento, um pino pode ser definido como alto, baixo ou invertido. O sinal é gerado por mudanças no valor do pino de saída. Em alguns modos de geração de sinal, o valor máximo do contador pode ser alterado. O tamanho físico do contador permanecerá o mesmo, mas um registo de comparação é usado para repor o contador numa contagem específica. Os exemplos anteriores também podem ser resolvidos usando este método, mas a função serve principalmente para mudar o período do sinal. Além disso, um contador pode ser configurado de modo a funcionar com incrementação ou decrementação. Os contadores e os modos de geração de sinal que os usam são um dos módulos periféricos mais complexos num AVR. Escrever sobre todos eles aqui vai além do âmbito deste texto, e, normalmente, não há também necessidade de saber todos os aspectos, com vista a usá-los. O que se segue descreve um dos sinais PWM mais comuns em robótica. O resto pode ser lido a partir da documentação do AVR. ==== Pulse Width Modulation ==== A modulação de largura de impulso (PWM) é um tipo de sinal, em que a frequência e período são (tipicamente) ambos constantes, mas o comprimento do meio período varia. Os sinais PWM são usados para controlar dispositivos electro-mecânicos, ópticos e outras. Por exemplo, os servomotores usados em modelação usam um sinal PWM de 50 Hz e têm um elevada meio-período de 1 a 2 ms. Task: Using an 8MHz ATmega128, generate two speed regulating servo motor signals. Use pin PB5 (OC1A) to generate a pulse width of 1 ms and pin PB6 (OC1B) to generate pulse width of 2 ms. #include int main() { // Set pins as outputs DDRB |= (1 << PIN5) | (1 << PIN6); // Set outputs A and B low for comparison, // "Fast PWM" mode, prescaler value 8 TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // Maximum value of the counter. Formula: // TOP = 8 MHz / 8 / 50 Hz ICR1 = 20000; // Half-period of the first motor is 1 ms, and second 2 ms OCR1A = 1000; OCR1B = 2000; // Endless loop while (1) continue; }