Программирование микроконтроллеров. Часть 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-ого счетчика по совпадению с OCR1A(H и L)
    OCR1AH  //записываем в регистр число для сравнения
    OCR1AL   делитель.
}

В первой строке устанавливаем режим работы таймера. В последней строке устанавливается делитель, если его не установить, то счетчик будет очень быстро считать. Когда счетчик досчитает до числа, записанного в регистр OCR1A произойдет прерывание и будет выполнен код обработчика прерывания:

ISR (TIMER1_COMPA_vect) //процедура обработки прерывания по совпадению таймера      (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<    (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ого счетчика по совпадению с OCR1A(H и L)
    OCR1AH  //записываем в регистр число для сравнения
    OCR1AL   делитель.
}

Результат симуляции работы в программе Proteus:


Если есть вопросы и предложения по этому уроку, пишите комментарии буду рад ответить на ваши вопросы.


Часть 1
Часть 2-1 Часть 2-2
Часть 3-1 Часть 3-2
Часть 4-1 Часть 4-2
Часть 5
Часть 6-1


Comments 0


Привет!

Этот пост был выбран Академией Голоса и попал в список программы поддержки качественных образовательных постов.
Ссылка на твой пост будет опубликована в отчете Академии.

Спасибо за полезный контент (ノ◕ヮ◕)ノ*:・゚✧

13.03.2017 15:38
0