И снова здравствуйте. Сегодня я расскажу вам про таймер-счетчик, который является одним из самых ходовых ресурсов AVR микроконтроллера. Основное его назначение – отчитывать заданные интервалы. С его помощью будем переключать разряды индикатора. Что позволить организовать динамическую индикацию.
Динамическая индикация – это метод отображения целостной картины через быстрое последовательное отображение отдельных элементов этой картины. Причем, «целостность» восприятия получается благодаря инерционности человеческого зрения.
Таймеры-счётчики — это такие устройства или модули в микроконтроллере, которые, как видно из названия, постоянно что-то считают. Считают они либо до определённой величины, либо до такой величины, сколько они битности. Считают они постоянно с одной скоростью, со скоростью тактовой частоты микроконтроллера, поправленной на делители частоты, которые мы будем конфигурировать в определённых регистрах.
В микроконтроллере ATmega328 есть три таймера. 2 из них по 8 бит (T0, T2) и один 16 бит (T1). Для них всех есть одинаковые режимы работы:
- Обычный режим работы. Самый распространенный режим, когда таймер просто считает приходящие импульсы и при переполнении счетного регистра устанавливает флаг прерывания по переполнению. При этом счетный регистр сбрасывается в 0 и подсчет импульсов начинается сначала. 
- Режим подсчета импульсов (Сброс при совпадении). Также называется CTC. В этом режиме при совпадении счетного регистра с одним из регистров сравнения выставляется флаг прерывания по совпадению, счетный регистр обнуляется и подсчет начинается сначала. При этом по совпадению может меняться сигнал на выходе таймера, соответствующего используемому регистру совпадения. 
- Режим ШИМ. В данном режиме изменяется ширина импульса в зависимости от значения, записанного в регистр совпадения. Для 8-битного таймера, к примеру, если записать в регистр совпадения число 10, состояние логической единицы на выходе совпадения будет 10 тактов из 256, а логический 0 246 тактов из 256 (для не инверсного режима). В инверсном же режиме 10 тактов будет состояние логического 0 и 246 тактов логической единицы. 
- Режим коррекции фазы ШИМ. В обычном режиме ШИМ мы получаем плавающую фазу выходного сигнала. Для того, чтобы этого избежать, в режиме коррекции фазы ШИМ при достижении максимального значения, счетный регистр таймера/счетчика начинает уменьшатся. Это происходит циклически. 
 Это все основные режимы работы таймеров/счетчиков. К примеру, у таймера/счетчика T0 присутствуют только они.
Дополнительные режимы работы таймера/счетчика T2:
- Асинхронный режим работы. В асинхронном режиме работы к микроконтроллеру подключается внешний кварцевый резонатор 32 кГц. Чаще всего этот режим используется для работы таймера/счетчика T2 в качестве часов реального времени.
Дополнительные режимы работы таймера/счетчика T1:
- Режим коррекции фазы и частоты ШИМ. Отличается от режима коррекции фазы ШИМ лишь моментом обновления регистра сравнения. Регистр сравнения обновляется, когда значение счетного регистра достигает минимума. 
- Режим захвата. При поступлении сигнала от аналогового компаратора, а также на вывод ICP1 (14 ножка) значение счетного регистра сохраняется в регистре захвата, устанавливая при этом флаг прерывания по захвату. 
Прерывания (Interrupts) — это такие механизмы, которые прерывают код в зависимости от определённых условий или определённой обстановки, которые будут диктовать некоторые устройства, модули и шины, находящиеся в микроконтроллере.
| Вектор | Прерывание | Обработчик | Описание | 
|---|---|---|---|
| 1 | RESET | RESET_vect | External Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset | 
| 2 | INT0 | INT0_vect | Внешнее прерывание 0 | 
| 3 | INT1 | INT1_vect | Внешнее прерывание 1 | 
| 4 | PCINT0 | PCINT0_vec | Прерывание 0 по изменению состояния вывода | 
| 5 | PCINT1 | PCINT1_vect | Прерывание 1 по изменению состояния вывода | 
| 6 | PCINT2 | PCINT2_vect | Прерывание 2 по изменению состояния вывода | 
| 7 | WDT | WDT_vect | Сторожевой таймер (если используется в качестве источника прерывания) | 
| 8 | TIMER2_COMPA | TIMER2_COMPA_vect | Прерывание по сравнению, канал A таймера/счетчика 2 | 
| 9 | TIMER2_COMPB | TIMER2_COMPB_vect | Прерывание по сравнению, канал B таймера/счетчика 2 | 
| 10 | TIMER2_OVF | TIMER2_OVF_vect | Прерывание по переполнению таймера/счетчика 2 | 
| 11 | TIMER1_CAPT | TIMER1_CAPT_vect | Прерывание таймера/счетчика 1 по захвату | 
| 12 | TIMER1_COMPA | TIMER1_COMPA_vect | Прерывание по сравнению, канал A таймера/счетчика 1 | 
| 13 | TIMER1_COMPB | TIMER1_COMPB_vect | Прерывание по сравнению, канал B таймера/счетчика 1 | 
| 14 | TIMER1_OVF | TIMER1_OVF_vect | Прерывание по переполнению таймера/счетчика 1 | 
| 15 | TIMER0_COMPA | TIMER0_COMPA_vect | Прерывание по сравнению, канал A таймера/счетчика 0 | 
| 16 | TIMER0_COMPB | TIMER0_COMPB_vect | Прерывание по сравнению, канал B таймера/счетчика 0 | 
| 17 | TIMER0_OVF | TIMER0_OVF_vect | Прерывание по переполнению таймера/счетчика 0 | 
| 18 | SPI | SPI_STC_vect | Завершение передачи по последовательному каналу SPI | 
| 19 | USART_RX | USART_RX_vect | Завершение приема по каналу USART | 
| 20 | USART_UDRE | USART_UDRE_vect | Регистр данных USART пуст | 
| 21 | USART_TX | USART_TX_vect | Завершение передачи по каналу USART | 
| 22 | ADC | ADC_vect | Преобразование АЦП завершено | 
| 23 | EE_READY | EE_READY_vect | EEPROM готова | 
| 24 | ANALOG_COMP | ANALOG_COMP_vect | Аналоговый компаратор переключился | 
| 25 | TWI | TWI_vect | Событие двухпроводного интерфейса (I2C) | 
| 26 | SPM_READY | SPM_READY_vect | Готовность SPM | 
К сожалению программа Proteus не всегда корректно отображает динамическую индикацию на многоразрядных индикаторах. Поэтому я один четырехразрядный индикатор заменил на четыре одноразрядных с общим катодом, что в свою очередь никак не повлияет на код программы.
Схема подключения индикаторов:
Настроим таймер. Для этого в конец файла port_ini.c нужно добавить строки:
void timer_ini(void)
{
    TCCR1B |= (1<<WGM12); // устанавливаем режим СТС (сброс по совпадению)
    TIMSK1 |= (1<<OCIE1A);  //устанавливаем бит разрешения прерывания 1-ого счетчика по совпадению с OCR1A(H и L)
    OCR1AH = 0b00000000; //записываем в регистр число для сравнения
    OCR1AL = 0b10000000;
    TCCR1B |= (0<<CS12)|(1<<CS11)|(1<<CS10);//установим делитель.
}
В первой строке устанавливаем режим работы таймера. В последней строке устанавливается делитель, если его не установить, то счетчик будет очень быстро считать. Когда счетчик досчитает до числа, записанного в регистр OCR1A произойдет прерывание и будет выполнен код обработчика прерывания:
ISR (TIMER1_COMPA_vect) //процедура обработки прерывания по совпадению таймера Т1
{
    if(n_count==0) {PORTC&=~(1<<PORTC0);PORTC|=(1<<PORTC1)|(1<<PORTC2)|(1<<PORTC3);seg_char(R4);}
    if(n_count==1) {PORTC&=~(1<<PORTC1);PORTC|=(1<<PORTC0)|(1<<PORTC2)|(1<<PORTC3);seg_char(R3);}
    if(n_count==2) {PORTC&=~(1<<PORTC2);PORTC|=(1<<PORTC0)|(1<<PORTC1)|(1<<PORTC3);seg_char(R2);}
    if(n_count==3) {PORTC&=~(1<<PORTC3);PORTC|=(1<<PORTC0)|(1<<PORTC1)|(1<<PORTC2);seg_char(R1);}
    n_count++;
    if (n_count>3) n_count=0;
}
Этот код нужно добавить в файл main.c. Здесь включаем нужный разряд индикатора, а остальные гасим.
Добавим в файл main.c еще одну функцию:
void led_print(unsigned int number) //разделяем число на разряды
{
    R1 = number%10;       //единицы
    R2 = number%100/10;   //десятки
    R3 = number%1000/100; //сотни
    R4 = number/1000;     //тысячи
}
В данной функции мы распределим цифру по разрядам.
Для этого применяется математическая операция, которая вычисляет остаток от деления. Обозначается она знаком %. Через данную операцию мы вычислим единицы. Аналогично поступаем и с остальными значениями числа.
Функция вывода числа на индикатор:
void seg_char (unsigned char seg) //выводим число на индикатор
{
    PORTB = codes[seg]; 
}
Для корректной компиляции без ошибок в начало файла main.c необходимо добавить строки:
unsigned int i;
unsigned char n_count=0;
unsigned short int R1=0, R2=0, R3=0, R4=0; //переменные под цифры каждого разряда
Здесь инициализируем необходимые переменные.
Весь код файла main.c:
#include "main.h"
//                              0    1    2    3    4    5    6    7    8    9    .
unsigned char codes[11]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x80};
unsigned int i;
unsigned char n_count=0;
unsigned short int R1=0, R2=0, R3=0, R4=0; //переменные под цифры каждого разряда
void seg_char (unsigned char seg) //выводим число на индикатор
{
    PORTB = codes[seg]; 
}
ISR (TIMER1_COMPA_vect) //процедура обработки прерывания по совпадению таймера Т1
{
    if(n_count==0) {PORTC&=~(1<<PORTC0);PORTC|=(1<<PORTC1)|(1<<PORTC2)|(1<<PORTC3);seg_char(R4);}
    if(n_count==1) {PORTC&=~(1<<PORTC1);PORTC|=(1<<PORTC0)|(1<<PORTC2)|(1<<PORTC3);seg_char(R3);}
    if(n_count==2) {PORTC&=~(1<<PORTC2);PORTC|=(1<<PORTC0)|(1<<PORTC1)|(1<<PORTC3);seg_char(R2);}
    if(n_count==3) {PORTC&=~(1<<PORTC3);PORTC|=(1<<PORTC0)|(1<<PORTC1)|(1<<PORTC2);seg_char(R1);}
    n_count++;
    if (n_count>3) n_count=0;
}
void led_print(unsigned int number) //разделяем число на разряды
{
    R1 = number%10;       //единицы
    R2 = number%100/10;   //десятки
    R3 = number%1000/100; //сотни
    R4 = number/1000;     //тысячи
}
int main(void){
    port_ini(); //Инициализируем порты
    timer_ini(); //Инициализируем таймер
    
    sei(); // Глобально разрешаем прерывания
    
    i=0;
    
    //ledprint(3475); //выводим на индикаторы произвольное число
    while(1)
    {
        
        for (i=0;i<10000;i++){
        led_print(i);
        _delay_ms(10); //скорость счета. 
        }
    }
}
Код файла main.h:
#ifndef MAIN_H_
#define MAIN_H_
#define F_CPU 8000000UL //Рабочая частота МК (8МГц)
#include <avr/io.h>
#include <util/delay.h> //подключение библиотеки для генерации задержек
#include <avr/interrupt.h>
void port_ini();
void timer_ini(void);
#endif /* MAIN_H_ */
Код файла port_ini.c:
#include "main.h"
void port_ini(){
DDRB = 0xff; //Переключаем порт B на выход
PORTB = 0x00; //устанавливаем все выходы порта в логический 0
DDRC = 0xff; //Переключаем порт C на выход
PORTC = 0x00; //устанавливаем все выходы порта в логический 0
}
void timer_ini(void)
{
    TCCR1B |= (1<<WGM12); // устанавливаем режим СТС (сброс по совпадению)
    TIMSK1 |= (1<<OCIE1A);  //устанавливаем бит разрешения прерывания 1ого счетчика по совпадению с OCR1A(H и L)
    OCR1AH = 0b00000000; //записываем в регистр число для сравнения
    OCR1AL = 0b10000000;
    TCCR1B |= (0<<CS12)|(1<<CS11)|(1<<CS10);//установим делитель.
}
Результат симуляции работы в программе Proteus:
Если есть вопросы и предложения по этому уроку, пишите комментарии буду рад ответить на ваши вопросы.
Часть 1
Часть 2-1 Часть 2-2
Часть 3-1 Часть 3-2
Часть 4-1 Часть 4-2
Часть 5
Часть 6-1






Привет!
Этот пост был выбран Академией Голоса и попал в список программы поддержки качественных образовательных постов.
Ссылка на твой пост будет опубликована в отчете Академии.
Спасибо за полезный контент (ノ◕ヮ◕)ノ*:・゚✧