Программирование микроконтроллеров. Часть 6-2
И снова здравствуйте. Сегодня я расскажу вам про таймер-счетчик, который является одним из самых ходовых ресурсов 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