Уважаемые пользователи Голос!
Сайт доступен в режиме «чтение» до сентября 2020 года. Операции с токенами Golos, Cyber можно проводить, используя альтернативные клиенты или через эксплорер Cyberway. Подробности здесь: https://golos.io/@goloscore/operacii-s-tokenami-golos-cyber-1594822432061
С уважением, команда “Голос”
GOLOS
RU
EN
UA
evgenij-byvshev
7 лет назад

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

ВекторПрерываниеОбработчикОписание
1RESETRESET_vectExternal Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset
2INT0INT0_vectВнешнее прерывание 0
3INT1INT1_vectВнешнее прерывание 1
4PCINT0PCINT0_vecПрерывание 0 по изменению состояния вывода
5PCINT1PCINT1_vectПрерывание 1 по изменению состояния вывода
6PCINT2PCINT2_vectПрерывание 2 по изменению состояния вывода
7WDTWDT_vectСторожевой таймер (если используется в качестве источника прерывания)
8TIMER2_COMPATIMER2_COMPA_vectПрерывание по сравнению, канал A таймера/счетчика 2
9TIMER2_COMPBTIMER2_COMPB_vectПрерывание по сравнению, канал B таймера/счетчика 2
10TIMER2_OVFTIMER2_OVF_vectПрерывание по переполнению таймера/счетчика 2
11TIMER1_CAPTTIMER1_CAPT_vectПрерывание таймера/счетчика 1 по захвату
12TIMER1_COMPATIMER1_COMPA_vectПрерывание по сравнению, канал A таймера/счетчика 1
13TIMER1_COMPBTIMER1_COMPB_vectПрерывание по сравнению, канал B таймера/счетчика 1
14TIMER1_OVFTIMER1_OVF_vectПрерывание по переполнению таймера/счетчика 1
15TIMER0_COMPATIMER0_COMPA_vectПрерывание по сравнению, канал A таймера/счетчика 0
16TIMER0_COMPBTIMER0_COMPB_vectПрерывание по сравнению, канал B таймера/счетчика 0
17TIMER0_OVFTIMER0_OVF_vectПрерывание по переполнению таймера/счетчика 0
18SPISPI_STC_vectЗавершение передачи по последовательному каналу SPI
19USART_RXUSART_RX_vectЗавершение приема по каналу USART
20USART_UDREUSART_UDRE_vectРегистр данных USART пуст
21USART_TXUSART_TX_vectЗавершение передачи по каналу USART
22ADCADC_vectПреобразование АЦП завершено
23EE_READYEE_READY_vectEEPROM готова
24ANALOG_COMPANALOG_COMP_vectАналоговый компаратор переключился
25TWITWI_vectСобытие двухпроводного интерфейса (I2C)
26SPM_READYSPM_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

25
645.376 GOLOS
На Golos с February 2017
Комментарии (1)
Сортировать по:
Сначала старые