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

Программирование микроконтроллеров. Урок 23
(Таймер/счетчик Т1.)

Урок 23 (Таймер/счетчик Т1.)

На прошлом уроке мы с вами посмотрели, как работает аналоговый компаратор, а сегодня разберемся с таймером/счетчиком Т1 и познакомимся с одним из вариантов его работы. Ну а попутно сделаем ночник с плавной регулировкой яркости.


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

В ATmege_8 каждый таймер/счетчик имеет свою особенность и предназначен для выполнения определенных задач. Безусловно отмерять интервалы по переполнению счетного регистра может каждый из них, но Т2 – заточен для работы в асинхронном режиме, Т0 – счетчик внешних событий, а специализация Т1 – формирование ШИМ сигналов переменной разрядности. Также у этого таймера/счетчика есть еще одна эксклюзивная фишка. Он может сохранять свое текущее состояние в отдельном регистре по команде из вне или по сигналу от аналогового компаратора. Ну и конечно не стоит забывать о том, что этот счетчик не 8-ми, а 16-ти битный. И с этим связана еще одна особенность этого модуля. Дело в том, что восьми разрядный контроллер не может за один такт передать данные в два регистра стразу. Поэтому передача данных происходит за два такта. Но для того чтобы данные небыли изменены при последовательной загрузке регистров (особенно это касается счетного регистра TCNT1) то в МК был добавлен еще один буферный восьми битный регистр TEMP (программно он не доступен) в который загружается старший байт при работе с двухбайтовыми регистрами. Итак, чтобы правильно передать данные в двух байтный регистр сначала загружается старший байт, который автоматически помещается в регистр TEMP, а затем загружается младший байт. Во время передачи младшего байта происходит синхронная загрузка старшего байта из регистра TEMP. Таким образом достигается передача данных в двухбайтовый регистр за один такт. Сама процедура на «Си» выглядит таким образом.

unsigned int d_data = DATA;

TCNT1H = (unsigned char) (d_data >> ONE_BYTE);	//загрузка старшего байта
TCNT1L = (unsigned char) d_data;		//загрузка младшего байта

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

unsigned int d_data = CLEAN;   

d_data = TCNT1L;			//загрузка младшего байта 
d_data += (TCNT1H << ONE_BYTE);		//загрузка старшего байта

Строго говоря не все двухбайтовые регистры Т1 требует к себе такое обращение, но, чтобы не забивать себе голову, лучше эти правила применять ко всем рабочим регистрам таймера/счетчика. И проблем с правильной передачей/приемом данных не будет. И еще. Если в каком-то из прерываний у вас происходит обращение к рабочим регистрам Т1, то при работе с таймером/счётчиком Т1 в основной программе, это прерывание необходимо запретить.

Флаги прерываний находятся в регистре флагов TIFR, а управление разрешением/запрещением прерывания Т1, как и для других таймеров/счетчиков находятся в регистре TIMSK. И если регистр TIFR по большому счету не предоставляет из себя ничего интересного, то вот регистр настроек прерываний надо рассмотреть по подробнее.

Для управления прерываниями всех счетчиков используется один регистр TIMSK. Формат этого регистра во многих МК отличается, но в ATmege_8 переключатели разрешения прерываний располагаются таким образом:
7 бит OCIE2 - прерывание по событию «Совпадение» Т2;
6 бит TOIE2 – прерывание по переполнению Т2;
5 бит TICIE1 – прерывание по событию «Захват» Т1;
4 бит OCIE1A – прерывание по событию «Совпадение А» Т1;
3 бит OCIE1B - прерывание по событию «Совпадение B» Т1;
2 бит TOIE1 – прерывание по переполнению Т1;
0 бит TOIE0 – прерывание по переполнению Т0.
Для разрешения прерывания по требуемому событию необходимо установить (1) в нужный бит и разрешить глобальное прерывание установив (1) в седьмой бит регистра SREG. К слову сказать, флаги прерывания в регистре TIFR располагаются в той же последовательности что и переключатели в регистре TIMSK.

Для работы и управления Т1 использует два восьми и четыре шестнадцати битных регистра:
TCCR1A и TCCR1B (8 бит) - регистры управления;
TCNT1 (16 бит) – счетный регистр (TCNT1H и TCNT1L);
OCR1A (16 бит) – регистр сравнения А (OCR1AH и OCR1AL);
OCR1B (16 бит) – регистр сравнения В (OCR1BH и OCR1BL);
ICR1 (16 бит) - регистр захвата (ICR1H и ICR1L).

Разберем управление этим счетчиком и рассмотрим сначала регистр TCCR1A:
7, 6 биты COM1A1 COM1A0 – определяют работу выводов OC1A при совпадении счетного регистра TCNT1 с данными в регистре OCR1A;
5, 4 биты COM1B1 COM1B0 – определяют работу выводов OC1B при совпадении счетного регистра TCNT1 с данными в регистре OCR1В;
3, 2 биты FOC1A FOC1B – принудительное изменение состояния выводов OC1A и OC1B. Запись (1) в эти биты меняет состояние выводов в зависимости от настроек, указанных в битах COM1A(1, 0) и COM1В(1, 0) и режима работы таймера/счетчика. Прерывание при этом не генерируется;
1, 0 биты WGM11 WGM10 – определяют режим работы Т1 совместно с WGM13 WGM12. Всего имеется 15 режимов работы Т1. Подробно о каждом режиме нужно прочитать в даташите. Сегодня нас будет интересовать режим номер 3 (10 разрядный Phase correct PWM). В этом режиме регистр TCNT1 функционирует как реверсивный счетчик. Т.е. его состояние изменяется сначала от нуля до максимума (в данном случае до 03FF), а затем от максимума до нуля. А так как мы установим биты COM1A1 - (1) COM1A0 - (0), то этот счетчик будет сбрасывать OC1A в (0) при прямом счете и устанавливать в (1) при обратном. В следствии чего появляется возможность регулировать скважность во всем диапазоне работы счетного регистра (2047 отсчетов счетчика). Графически это можно изобразить так.

Теперь рассмотрим регистр TCCR1B:
7 бит ICNC1 - Управляет работой схемы подавления помех при захвате. (0) схема выключена, захват происходит по первому активному фронту. (1) схема включена. Захват происходит только в случае четырех одинаковых выборок, которые соответствуют активному фронту;
6 бит ICEC1 – Выбор активного фронта сигнала захвата. (0) сохранение по спадающему фронту (1) сохранение по нарастающему фронту;
4, 3 биты WGM13 WGM12 – определяют режим работы Т1 совместно с WGM11 WGM10;
2, 1, 0 биты CS12 CS11 CS10 - Управление тактовым сигналом. Необходимо иметь в виду, что тактирование Т1 может осуществляться не только тактовым сигналом МК, но и внешним сигналом, поступающим на вывод Т1 (PD5). Внутренний тактовый сигнал, также, как и в других счетчиках, может быть масштабирован предделителем, описанным в 16 уроке. Таблицу выбора источника и настройку предделителя для Т1 нужно смотреть в даташите. Но т.к. нам, по большому счету, совершенно не нужно уменьшать частоту работы счетчика то мы смело ставим (1) в бит CS10 и запускаем счетчик в работу с тактовой частотой контроллера. Лучше, чтобы она была 8 МГц. О том, как это сделать можно вспомнить здесь.

Ну вот, с теорией вроде разобрались, перейдем к практике. Схема нашего светильника будет простой как лом. Если вы нашли светодиодную ленту питанием 5V, то схема ее подключения находится в приложенных файлах. Единственное отличие будет в том, что на стабилизатор придется нацепить небольшой радиатор, а светодиодную ленту подключить к линии 5V.

Новым для вас в этой схеме будет стабилизатор на 5 вольт. Он состоит из микросхемы LM7805 и двух электролитических конденсаторов. Необходимо помнить, что электролитические конденсаторы рассчитаны на определенное напряжение и поэтому их необходимо ставить не менее чем на 25% больше рабочего напряжения. Т.е. для напряжения питания 12 воль, конденсаторы должны быть рассчитаны минимум на 16, а лучше на 25 вольт. Хочу лишний раз напомнить о том, что при несоблюдении полярности электролитических конденсаторов или при превышении расчетного напряжения они феерически взрываются, разбрызгивая по окрестностям свои потроха и маслянистую, неприятную субстанцию.

Так вот. Стабилизатор понадобится в любом случае даже если вы нашли 5-ти вольтовою светодиодную ленту и надумали подключить ее к USB порту. Если рассматривать такое подключение в "крупную клетку", то порт USB2.0 может выдавать не более 0.5А, а в совокупности с пятью вольтами это всего 2,5 ватта. При том, что метр светодиодной ленты может потреблять около 7-ми ватт. Посему, если питать устройство от USB порта, как мы это делали ранее, то, как минимум, можно потерять порт. Следовательно, ищем блок питания на 12 вольт, благо таких много, мощностью не менее 6…8 ватт и собираем схему.

При сборке будьте предельно внимательны и перепроверьте полярность подсоединяемого блока питания, а также, перед подключением стабилизатора к микросхеме, проверьте тестером правильность его работы. Для проверки правильного подключения транзистора VT1 и диодной ленты можно аккуратно отсоединив от МК вывод резистора R2 замкнуть его на шину +5V при этом светодиоды должны загореться. Если этого не произошло, смотри правильность подключения транзистора или работу стабилизатора.

Далее все просто. Заливаем в МК программу и наслаждаемся работой устройства.

	//Настройка и запуск таймера/счетчика Т1
	TCCR1A = (1<<COM1A1)|(0<<COM1A0)|(0<<COM1B1)|(0<<COM1B0)|(0<<FOC1A)|(0<<FOC1B)|(1<<WGM11)|(1<<WGM10);
	TCCR1B = (0<<ICNC1)|(0<<ICES1)|(0<<WGM13)|(0<<WGM12)|(0<<CS12)|(0<<CS11)|(1<<CS10);         

while (1) {
	d_encoder = encoder();						//опрос энкодера
	if (d_encoder){							//если энкодер повернули... 
		if(d_encoder == LEFT){					//в лево
			if(d_work_a < (FUL_10_BIT-STEP)) d_work_a += STEP;		//увеличение яркости
			if((FUL_10_BIT - d_work_a) < STEP)d_work_a = FUL_10_BIT;	//полная яркость
		}
		else{							//в право
			if(d_work_a >= (ZERO+STEP)) d_work_a -= STEP;	//уменьшение яркости
			if(d_work_a < STEP)d_work_a = ZERO;		//полное затухание
		};
		//Загрузка новых данных в Т1
		OCR1AH = (unsigned char) (d_work_a >> ONE_BYTE);	//загрузка старшего регистра блока сравнения
		OCR1AL = (unsigned char) d_work_a;			//загрузка младшего регистра блока сравнения 
	};
}
} //END void main(void)

Это часть базовой программы для тестирования собранной схемы, которую мы немного разберем. Т.к. переключение вывода OC1A микроконтроллер будет производить аппаратно, то нужды организовывать прерывания у нас нет. Поэтому после настройки МК и Т1 в частности мы входим в основной цикл программы и постоянно «мониторим» работу энкодера. Как только энкодер подает сигнал мы его обрабатываем, увеличивая или уменьшая значение для сравнения (обязательно учитываем крайние случаи), которое в последствии передается в регистр OCR1A. И как только это произошло таймер/счетчик изменяет скважность подаваемых импульсов и наш ночник изменит интенсивность свечения.

Для проверки работоспособности нашей схемы этого достаточно. У меня работа светильника с этой программой выглядит так.

На видео видно, что изменения яркости происходят ступенчато согласно шагу, который я указал в программе. На осциллографе хорошо видна работа ШИМ. Можно подобрать приемлемый шаг и сказать, что дело сделано.

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

Вот так получилось у меня. Для плавного изменения яркости я использовал кривую, описанную полиномом 3-й степени, но можно так уж не заморачиваться и просто сделать небольшую линейную или вообще постоянную задержку для каждого шага Т1. Полином представлен в приложенных файлах в формате таблицы "Excel". Если будет интерес и желание, то там можно поиграться с коэффициентами и посмотреть, как будет меняться график. Самый кайф от этого светильника получаешь в моменты, когда огоньки медленно разгораются или также медленно затухают. Это завораживает. А если немного напрячься и подключить генератор случайных чисел, то можно попробовать сделать имитацию всполохов пламени. Но это уже другая история.


И так. Вот мы и подошли к концу обучения основам программирования МК в том виде как я это себе представляю. Не охваченными остались только темы о работе сторожевого таймера, универсального асинхронного приемопередатчика (модуля USART/UART) и последовательного двухпроводного интерфейса TWI. Я эти темы не освещаю потому как со сторожевым таймером вы можете легко разобраться сами почитав Евстифеева А.В. «Микроконтроллеры AVR семейства Tiny и Mega фирмы Atmel» 2008г. стр. 300…305, а остальными модулями я сам пока ни разу не пользовался. Ничего сложного в работе этих устройств нет и если у вас будет необходимость в их использовании, то уверен, что проблем с организацией работы этих интерфейсов не возникнет. Возможно, если мне когда-нибудь придется иметь с ними дело, я запилю еще пару уроков и опишу их работу.

Также не до конца раскрыта тема интерфейсов программирования микроконтроллеров и тема самопрограммирования МК. Но эти вопросы я не могу отнести к «основам». Они требуют достаточно глубокого погружения в работу «железа».

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

#define WDR 		#asm("wdr")	     	//кормим собаку

Вот как-то так. Если вы прошли со мной путь от начала до конца, то уже вполне способны не только писать на Си простенький код для МК, но и собирать несложные схемы электронных устройств оживляя их своими программами.


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

30.09.20

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

Приложение:
Файлы к уроку 23