Логотип

Если Вы хотите обучаться программированию микроконтроллеров, но попали, сюда не прочитав предыдущих уроков, то советую начать изучение материала с самого начала.

Программирование микроконтроллеров. Урок 16
Прерывания, таймер/счетчик Т0 и предделители.

Урок 16 (Прерывания, таймер/счетчик Т0 и предделители.)

На прошлом занятии мы с вами разогнали наш МК и скорректировали частоту генератора. А сегодня мы познакомимся с прерываниями, которые вызывают разные устройства, находящиеся на "борту" микроконтроллера, и рассмотрим их работу на примере таймера/счетчика Т0.

Прерывание, это сигнал от внутреннего устройства, который приостанавливает работу основной программы и дает команду на выполнение подпрограммы (обработчика) согласно установленному вектору прерывания. После исполнения обработчика прерывания МК продолжает выполнение прерванной программы с места где она была остановлена. По сути обработчик прерывания является подпрограммой, которая выполняется не по вызову из основной программы, а по внешнему требованию. От этого обработчики прерывания имеют ряд особенностей, о которых надо знать. Во-первых, прерывания могут работать только с глобальными переменными или переменными созданными внутри обработчика прерывания. Из этого вытекает то, что если нам надо передать данные в обработчик или из него, то это можно сделать только с помощью глобальных переменных. Во-вторых, и это самое важное, из-за того, что прерывание может быть вызвано в любой момент, а сам обработчик может изменить состояние регистра SREG, то в самом начале обработки прерывания необходимо запомнить регистр состояния, а в конце, непосредственно перед выходом из обработчика, восстановить его. В противном случае ошибок в работе программы не избежать. И, в-третьих. При написании основной программы, если у вас используются прерывания, необходимо следить за тем, чтобы в местах где вызов прерывания испортит результат проводимой операции. Например, при записи в EEPROM. Необходимо запрещать прерывания, а после исполнения последовательности команд, которую нельзя прерывать, снова его разрешать.

У ATmega8 имеется всего восемнадцать векторов прерываний:
1. INT0 – Внешнее прерывание 0;
2. INT1 – Внешнее прерывание 1;
3. TIMER2 COMP – Совпадение таймера/счетчика Т2;
4. TIMER2 OVF – Переполнение таймера/счетчика Т2;
5. TIMER1 CAPT – Захват таймера/счетчика Т1;
6. TIMER1 COMPA – Совпадение «А» таймера/счетчика Т1;
7. TIMER1 COMPB – Совпадение «В» таймера/счетчика Т1;
8. TIMER1 OVF – Переполнение таймера/счетчика Т1;
9. TIMER0 OVF – Переполнение таймера/счетчика Т0;
10. SPI, STC – Передача по SPI завершена;
11. USART, RXC – USART, прием завершен;
12. USART, UDRE – Регистр данных USART пуст;
13. USART, TXC – USART, передача завершена;
14. ADC – Преобразование АЦП завершено;
15. EE_RDY – EEPROM, готово;
16. ANA_COMP – Аналоговый компаратор;
17. TWI – Прерывание от модуля TWI;
18. SPM_RDY – Готовность SPM.

Половина этих прерываний в простой жизни радиолюбителя не пригодится. Но теми, которые относятся к таймерам/счетчикам вы будете пользоваться часто.

Так как обработчики прерываний являются подпрограммами, то их необходимо объявить соответствующим образом. Но если свои подпрограммы вы вольны называть как вам захочется, то обработчики прерывания оформляются особым образом.

    interrupt [EXT_INT0] void ext_int_interrapt (void);
    interrupt [EXT_INT1] void ext_int1_interrapt (void);
    interrupt [TIM2_COMP] void tim2_comp_interrapt (void);
    interrupt [TIM2_OVF] void tim2_ovf_interrapt (void);
    interrupt [TIM1_CAPT] void tim1_capt_interrapt (void);
    interrupt [TIM1_COMPA] void tim1_compa_interrapt (void);
    interrupt [TIM1_COMPB] void tim1_compb_interrapt (void);
    interrupt [TIM1_OVF] void tim1_ovf_interrapt (void);
    interrupt [TIM0_OVF] void tim0_ovf_interrapt (void);
    interrupt [SPI_STC] void spi_stc_interrapt (void);
    interrupt [USART_RXC] void usart_rxc_interrapt (void);
    interrupt [USART_DRE] void usart_dre_interrapt (void);
    interrupt [USART_TXC] void usart_txc_interrapt (void);
    interrupt [ADC_INT] void adc_int_interrapt (void);
    interrupt [EE_RDY] void ee_rdy_interrapt (void);
    interrupt [ANA_COMP] void ana_comp_interrapt (void);
    interrupt [TWI] void twi_interrapt (void);
    interrupt [SPM_READY] void spm_ready_interrapt (void);

Объявлять обработчик лучше всего после всех подпрограмм, непосредственно перед функцией «main». Это конечно не обязательно, но если вдруг вы в обработчике прерывания используете подпрограмму, то по понятным причинам она должна быть объявлена заранее.

Вот так будет выглядеть полное оформление обработчика прерывания по переполнению таймера/счетчика Т0.

/*Уроки по изучению основ программирования микроконтроллеров*/

#include 

//Объявления обработчика прерывания
interrupt [TIM0_OVF] void tim0_ovf_interrapt (void);

void main(void)
{
while (1)
	{ 
	}
}

//Обработчик прерывания от таймера/счетчика Т0
interrupt [TIM0_OVF] void tim0_ovf_interrapt (void)
{
	unsigned chr temp = SREG;	//Сохранение SREG
	
	//Код обработчика прерывания Т0
	
	SREG = temp; 			//восстановление SREG
}

Что еще надо знать о прерываниях? Ну конечно, как их включить и выключить на глобальном уровне. Здесь все просто. Для того чтобы разрешить прерывания необходимо установить единицу в седьмой бит (I) регистра SREG, а, чтобы запретить необходимо сбросить этот бит в ноль. Управлять прерываниями можно как с применением кода Си:

SREG |= (1<<I); //Разрешить прерывания установить 1 в седьмой бит SREG
SREG &= ~(1<<I); //Запретить прерывания установить 0 в седьмой бит SREG

Так и с использованием команд ассемблера (sei) и (cli). В тексте программы на «Си» ассемблерные вставки оформляются вот так:

#asm("sei"); //разрешить прерывание
#asm("cli"); //запретить прерывание

Для лучшей удобочитаемости обычно эти вставки записываются с помощью директивы #define:

#define SEI #asm("sei")     //разрешить прерывание
#define CLI #asm("cli")     //запретить прерывание 

Ну вот с оформлением прерываний мы разобрались. Теперь давайте рассмотрим таймер/счетчик Т0 ATmega8. Это самый простой восьмиразрядный суммирующий таймер/счетчик из находящихся в этом микроконтроллере. Его структурную схему нужно изучить в описании на МК. Он имеет всего одно прерывание по переполнению счетного регистра TCNT0. То есть в момент, когда счетный регистр TCNT0 переходит через значение $FF генерируется сигнал на прерывание, при этом счет продолжается со значения $00. Необходимо отметить что счетный регистр TCNT0 доступен как для чтения, так и для записи в любой момент. Приращение счетного регистра может происходить в двух случаях. Либо в результате появления некого внешнего события, которое поступает в счетчик через вывод микроконтроллера PD4, либо по импульсам, идущим на таймер от предделителя. Помимо счетного регистра TCNT0, таймер/счетчик Т0 имеет еще и регистр управления TCCR0. С помощью которого мы можем осуществить выбор источника тактового сигнала. Таблицу выбора источника тактового сигнала таймера/счетчика Т0 смотрим в описании на МК.

Теперь давайте немного отвлечёмся от таймера/счетчика Т0 и обратим наше внимание на предделитель тактовых сигналов. Предделители предназначены для формирования внутренних тактовых импульсов для каждого таймера/счетчика. Их в ATmega8 два. Один работает со счетчиком Т2, а другой одновременно работает со счетчиками Т0 и Т1. Еще раз. Для счетчика Т0 и Т1 тактовые импульсы могут быть разные, но предделитель один! Структурную схему предделителя без асинхронного режима нужно изучить в описании на МК. Предделитель для Т0 и Т1 представляет из себя десятиразрядный счетчик, который принимая тактовые сигналы микроконтроллера выдает сигналы таймерам/счетчикам деля их на 8, 64, 256 и 1024. Например, если у нас тактовая частота 8МГц, то установив предделитель таймера Т0 на 64 (то есть записав в регистр TCCR0 = 0x03) мы будем иметь частоту работы таймера равную 125кГц. При такой частоте прерывание по переполнению Т0 будет вызвано около 488 раз за секунду. Предделители в ATmega8 начинают работать вместе с запуском МК и в этом контроллере остановить их невозможно. Но для каждого предделителя имеется функция сброса. Для этого надо записать единицу в разряд PSR2 – для Т2 или PSR10 – для Т1, Т0 регистра SFIOR. При этом произойдет сброс счетчиков предделителя в ноль, а записанные единицы в разряды PSR2 и PSR10 сбросятся автоматически. Т.к. таймеры/счетчика Т0 и Т1 работают от одного предделителя при сбросе необходимо учитывать тот факт, что, сбросив предделитель для одного счетчика мы практически всегда увеличим период одного такта для другого т.к. счетчику предделителя придется начинать счет с нуля и время подачи сигнала на таймер/счетчик будет увеличено. Отсюда вытекает и то, что при запуске таймеров/счетчиков с предделителем первый такт практически всегда меньше остальных из-за того, что предделитель уже отчитал n-ное количество тактов. Об этих особенностях предделителей надо знать, но заморачиваться не стоит потому как задач, в которых надо будет учитывать эти мелочи крайне мало.


Но вернемся к таймеру/счетчику Т0. Необходимо отметить то, что если вам потребовалось сосчитать какие-то внешние сигналы, поступающие на вывод PD4, то их частота должна быть в 2,5, а то и в 3 раза меньше чем частота работы микроконтроллера. В противном случае некоторые сигналы будут потеряны.

Говоря о таймерах/счетчиках в целом, нельзя не упомянуть еще о двух регистрах которые управляют прерываниями. TIFR – регистр флагов для индикации наступления прерываний от таймеров/счетчиков. Если появляется сигнал на прерывание от какого-то таймера/счетчика, то флаг соответствующей этому сигналу, устанавливается в единицу. Когда же запускается обработка возникшего прерывания, то он сбрасывается аппаратно в ноль. Другой регистр TIMSK – регистр разрешения/запрещения прерываний от таймеров/счетчиков. Если нам необходимо разрешить прерывание по какому-то событию от таймеров/счетчиков, то помимо разрешения глобального прерывания мы обязаны установить флаг, разрешающий это прерывание. Сегодня нас будет интересовать флаг TOIE0. Он разрешает прерывания по переполнению таймера/счетчика Т0.

И так. Для работы с таймером/счетчиком Т0 нам понадобятся настроить всего два регистра: регистр управления Т0 - TCCR0 и регистр разрешения прерываний – TIMSK. В качестве проверки работы таймера, а заодно и прерываний предлагаю написать небольшую программку, которая бы моргала светодиодом, но не из основной программы, как мы делали это раньше, а из нашего прерывания. В собранной на прошлом уроке схеме ничего менять не надо. Если сразу непонятно как это сделать, то архив проекта в приложенных файлах. Изучаем, компилируем, прошиваем, наслаждаемся. Для закрепления материала необходимо будет поиграться, изменяя подаваемый с предделителя сигнал. По морганию светодиода будет наглядно видно изменение частоты работы таймера/счетчика Т0.


На сон, грядущий необходимо повторить о прерываниях и о том, как вставлять в Си ассемблерный код:
   М.Б. Лебедев «CodeVisionAVR пособие для начинающих» 2008г. Страницы 241…243; 248…250


А также про последовательный периферийный интерфейс SPI
   Евстифеев А.В. «Микроконтроллеры AVR семейства Tiny и Mega фирмы Atmel» 2008г. Страницы 347…354.


И конечно же обязательно ознакомиться с описанием на LCD дисплей Nokia 5110. Как я уже писал для следующего занятия нам понадобится этот дисплей. Его надо обязательно достать. Т.к. дальнейшее обучение без него будет затруднительным.


А на сегодня всё. Удачи.


18.02.19



Если вдруг найдете в статье неточности или заблуждения. Напишите мне об этом. Я подправлю.



Приложение:
Проект CV_AVR.