Найти тему
Евгений Бывшев

Программирование микроконтроллеров. Часть 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) — это такие механизмы, которые прерывают код в зависимости от определённых условий или определённой обстановки, которые будут диктовать некоторые устройства, модули и шины, находящиеся в микроконтроллере.

К сожалению программа 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-2 Часть 2-2 Часть 3-1 Часть 3-2 Часть 4-1 Часть 4-2 Часть 5 Часть 6-1