Тема 1.2 Генерация сложных последовательностей импульсов
Как правило, при решении задач программирования систем на основе микроконтроллеров необходимость в формировании простых серий импульсных сигналов возникает не так часто, как необходимость в сложных последовательностях, формируемых по определенным правилам. Характеристики таких сигналов могут отличаться от одного периода к другому, как собственно и количество их повторений.
Как и в предыдущем подразделе воспользуемся светодиодами, установленными на плате AVRmegaboardX8. Компиляция исходного текста программ приведенных примеров также будет выполнена при помощи интегрированной среды разработки CodeVisionAVR.
В качестве первого примера генерации относительно сложной последовательности управляющих сигналов рассмотрим листинг 1.4, в котором реализовано некоторое подобие работы полицейской сирены. В результате выполнения программы производится поочередная манипуляция красным и желтым светодиодами, чередующаяся с их синхронным переключением.
Чередующееся включение красного и желтого светодиодов происходит в строках 20 – 25. Количество повторений считает счетчик i, ограниченный значением m. Цикличность переключений организованна оператором цикла со счетчиком for. Формирование синхронных переключений в строках 26- -31 производится аналогично, но ограничением числа повторов является значение n.
Особое внимание стоит уделить командным строкам 21, 23 и 27, где производятся манипуляции с разрядами регистра PORTD. Последовательно разберем эти команды.
Листинг 1.4 – “Полицейская сирена”
//-----подключение библиотек--------------------------- //----------------------------------------------------- #include <mega8.h> //описание памяти микроконтроллера #include <delay.h> //временные задержки //***************************************************** //=====ГЛАВНАЯ ФУНКЦИЯ================================= //===================================================== void main (void){ //-----локальные объекты------------------------------- const unsigned char n=5, //кол-во поочередных и m=10; //синхронных включениях const unsigned int Tp=500,//задержка при поочередном Ts=200;//и синхронном включениях unsigned char i; //счетчик повторений //-----настройка портов ввода/вывода------------------- DDRD=0xC0; //7,6 разряды - на вывод //-----фоновая программа------------------------------- while(1){ //бесконечный цикл for(i=0;i<n;i++){ //поочередное переключение PORTD=1<<7; //вкл. красного, выкл. желтого delay_ms(Tp); //задержка между переключениями PORTD=1<<6; //вкл. желтого, выкл. красного delay_ms(Tp); //задержка между переключениями } for(i=0;i<m;i++){ //синхронное переключение PORTD=1<<7|1<<6;//включение красного и желтого delay_ms(Ts); //задержка между переключениями PORTD=0; //выключение красного и желтого delay_ms(Ts); //задержка между переключениями } |
Продолжение листинга 1.4
}//while(1) }//END MAIN //===================================================== |
В строке 21 происходит присвоение регистру PORTD значения, полученного в результате операции сдвига влево десятичной единицы на семь двоичных разрядов. Вычисление данного выражения произойдет на стадии компиляции текста программы, в результате чего, в регистр PORTD будет записано вычисленное значение, содержащее единицу в седьмом двоичном разряде, все остальные разряды двоичного числа будут хранить нулевые значения, при этом красный светодиод будет включен, а желтый – выключен. Аналогичным образом происходит включение желтого светодиода и выключение красного в строке 23.
Запись вида “PORTD=1<<7” аналогична записи “PORTD=0x80”, но результат ее более очевиден. Прочесть эту команду можно было бы как: “установить седьмой разряд регистра PORTD в единицу, а всем остальным разрядам присвоить нулевые значения”.
В строке 27 происходит одновременное включение красного и желтого светодиодов. Значение, присваиваемое регистру PORTD, в команде “PORTD=1<<7|1<<6” как и в рассмотренных ранее командах, вычисляется на стадии компиляции. В начале будут вычислены операции сдвига влево десятичных единиц на 6 и 7 двоичных разрядов соответственно. Далее между результатами выполнения этих операций будет произведено логическое сложение (“ИЛИ”). В итоге будет получена бинарная маска с единицами в шестом и седьмом разрядах, все остальные разряды будут хранить нулевой результат. Прочесть данную команду можно как: “установить седьмой и шестой разряды регистра PORTD в единицу, а всем остальным разрядам присвоить нулевые значения”.
В следующем примере разберем текст программы, формирующей последовательности импульсных сигналов на выводах микроконтроллера для управления подключенными к ним светодиодами в соответствии с алгоритмом работы светофора.
Для упрощения определения граничных значений времени переключения сигналов “светофора”, последовательность управляющих сигналов, которую необходимо сформировать, представим в виде графика на рисунке 1.4. Чтобы абстрагироваться от конкретных значений, всем интервалам времени на графике дадим имена, которыми будем пользоваться в программе.
Рисунок 1.4 – График переключения сигналов “светофора”
Рассмотрим значение интервалов времени приведенных на рисунке:
1. Tr – длительность работы красного сигнала светофора;
2. Tg – длительность работы зеленого сигнала светофора;
3. Ty – длительность работы желтого сигнала светофора;
4. Tp – длительность интервалов при переключении зеленого сигнала светофора. Так как длительность переключений для всех шести интервалов имеет равное значение, то на графике время Tp умножено на 6.
На графике переключений видно, что некоторые интервалы времени накладываются друг на друга по шкале времени. Так в завершении цикла работы красного сигнала светофора на некоторое время включается желтый сигнал. А во время работы зеленый сигнал светофора не все время включен непрерывно, цикл его работы завершается тремя переключениями. Следовательно, для организации правильной работы “светофора” ко времени, необходимо вычислить значения интервалов между переключениями его сигналов.
В результате получим следующий алгоритм работы “светофора”:
1. Включение красного сигнала светофора на время Tr-Ty;
2. Включение желтого сигнала светофора на время Ty;
3. Выключение красного и желтого сигналов светофора;
4. Включение зеленого сигнала светофора на время Tg-6*Tp;
5. Формирование одной серии, состоящей их трех импульсов длительность импульса и паузы которых равны Tp, для управлением переключений зеленого сигнала светофора;
6. Выключение зеленого сигнала светофора;
7. Переход к выполнению пункта 1.
Данный алгоритм обработки реализован в листинге 1.5.
В этом примере также стоит уделить внимание командам манипуляции разрядами регистров использованных в строках 20, 22, 27 и 29. Команды, подобные приведенным в строках 24 и 32, уже были рассмотрены в предыдущих примерах.
Рассмотрим команду “PORTD|=1<<6”, расположенную в строке 22. Операция сдвига влево “1<<6” в результате дает бинарную маску, в которой шестой разряд установлен в единицу, а остальные – сброшены. Операция “|=” обозначает, что производится логическое сложение значений в правой и левой частях выражения, после чего результат передается объекту с лева. Таким образом, с помощью данной команды можно установить один разряд регистра или переменной, не влияя на содержимое остальных разрядов.
Команда “PORTD&=~(1<<5)”, расположенная в строке 27, позволяет сбросить один заданный разряд регистра или переменной, не влияя при этом на прочее содержимое. Инверсия значения, полученного в результате операции сдвига влево десятичной единицы, дает бинарную маску, в которой все двоичные разряды установлены, а разряд, позиция которого совпадает с количеством сдвигов влево, сброшен. Операция ‘&=’, аналогично ‘|=’, объекту слева передает результат операции логического умножения над значениями левой и правой частей, в результате чего происходит сброс заданного разряда.
Листинг 1.5 – “Светофор”
//-----подключение библиотек--------------------------- //----------------------------------------------------- #include <mega8.h> //описание памяти микроконтроллера #include <delay.h> //временные задержки //***************************************************** //=====ГЛАВНАЯ ФУНКЦИЯ================================= //===================================================== void main (void){ //-----локальные объекты------------------------------- const unsigned int Tr=10000,//время вкл. красного Tg=12000,//время вкл. зеленого Ty=2000, //время вкл. желтого Tp=1000; //период перекл. зеленого unsigned char i; //счетчик переключений зеленого //-----настройка портов ввода/вывода------------------- DDRD=0xE0; //7,6 и 5 разряды - на вывод //-----фоновая программа------------------------------- while(1){ //бесконечный цикл PORTD|=1<<7; //вкл. красный delay_ms(Tr-Ty); //пауза вкл. красного PORTD|=1<<6; //вкл. желтый delay_ms(Ty); //пауза вкл. желтого+красного |
Продолжение листинга 1.5
PORTD=1<<5; //выкл. желтый+красный, вкл. зеленый delay_ms(Tg-6*Tg); //пауза вкл. зеленого for(i=0;i<3;i++){ //цикл переключения зеленого сигнала PORTD&=~(1<<5);//выкл. зеленый delay_ms(Tp); //задержка выключения PORTD|=1<<5; //вкл. зеленый delay_ms(Tp); //задержка включения } PORTD=1<<6; //выкл. зеленый, вкл. желтый delay_ms(Ty); //пауза вкл. желтого PORTD=0; //выключение "светофора" }//while(1) }//END MAIN //===================================================== |
В некоторых случаях рассмотренное решение может быть не приемлемым, т.к. выполнение программы занимает все свободное процессорное время.
Алгоритм, описанный в листинге 1.5, представляет собой некоторое подобие сценария, на основе которого производится заданная последовательность действий. При этом большую часть времени микроконтроллер занимается формированием интервалов времени, а управление сигналами светофора выполняется сравнительно не долго.
Конечно, можно было бы заменить команды вызова функций, реализующих задержки времени, на эквивалентные по времени обработчики некоторых параллельных задач, но это было бы крайне не удобно с точки зрения реализации. При этом возможность дальнейшего корректирования графика управления светофором значительно затруднится.
Применив парадигму автоматного программирования для решения данной задачи, получим программу, описанную в листинге 1.6, которая моделирует конечный автомата, описывающий состояния сигналов светофора.
Для удобства написания текста программы, график на рисунке 1.4 был дополнен метками времени, характеризующими состояния сигналов светофора, т.е. фазы конечного автомата. В результате получен рисунок 1.5, на котором отмечены интервалы времени между включением сигналов и началом цикла работы светофора.
Рисунок 1.5 – График переключения сигналов “светофора”
Листинг 1.6 – “Светофор”
//-----подключение библиотек--------------------------- //----------------------------------------------------- #include <mega8.h> //описание памяти микроконтроллера #include <delay.h> //временные задержки //***************************************************** |
Продолжение листинга 1.6
//-----замена имен типов------------------------------- //----------------------------------------------------- typedef unsigned char byte; //1 байт typedef unsigned int uWord; //2 байта //***************************************************** //=====ГЛАВНАЯ ФУНКЦИЯ================================= //===================================================== void main (void){ //-----локальные объекты------------------------------- byte counter=0; //счетчик базовых интервалов const uWord Tbas=1000;//базовый интервал const byte Tr=10, //время вкл. красного Tg=12, //время вкл. зеленого Ty=2, //время вкл. желтого Tp=1; //период перекл. зеленого //-----график переключения сигналов светофора---------- const byte t1=Tr-Ty, //вкл. желтый t2=Tr, //выкл. кр. и ж.,вкл. зел. t3=Tr+Tg-6*Tp, //выкл. зеленый t4=Tr+Tg-5*Tp, //вкл. зеленый t5=Tr+Tg-4*Tp, //выкл. зеленый t6=Tr+Tg-3*Tp, //вкл. зеленый t7=Tr+Tg-2*Tp, //выкл. зеленый t8=Tr+Tg-Tp, //вкл. зеленый t9=Tr+Tg, //выкл. зел., вкл. желтый t10=Tr+Tg+Ty; //вык. желтый, вкл. кр. //-----настройка портов ввода/вывода------------------- DDRD=0xE0; //7,6 и 5 разряды - на вывод PORTD.7=1; //вкл. красный сигнал светофора |
Продолжение листинга 1.6
//-----фоновая программа------------------------------- while(1){ //бесконечный цикл delay_ms(Tbas); //формирование базового интервала if(++counter==t1) //увеличение счетчика и проверка PORTD.6=1; //вкл. желтый else if(counter==t2) PORTD=1<<5; //выкл. кр. и желтый, вкл. зел. else if(counter==t3) PORTD.5=0; //выкл. зеленый else if(counter==t4) PORTD.5=1; //вкл. зеленый else if(counter==t5) PORTD.5=0; //выкл. зеленый else if(counter==t6) PORTD.5=1; //вкл. зеленый else if(counter==t7) PORTD.5=0; //выкл. зеленый else if(counter==t8) PORTD.5=1; //вкл. зеленый else if(counter==t9) PORTD=1<<6; //выкл. зеленый, вкл. желтый else if(counter==t10){ PORTD=1<<7; //выкл. желтый, вкл. красный counter=0; //возобновление цикла работы } }//while(1) }//END MAIN //===================================================== |
В данном случае, процесс формирование сигналов управления разделяется на десять фаз, названных t1 – t10. Каждая фаза характеризует конечное состояние сигналов светофора ко времени, а шаг автомата определяется значением переменной – счетчика counter, который увеличивается на единицу, после каждого сформированного базового временного интервала. Определение конкретного состояния конечного автомата производится конструкцией множественного выбора, состоящей из последовательности операторов if – else.
Длительность базового временного интервала выбирается как минимальная кратная величина для каждого значения длительности сигнала светофора. Следовательно, значение промежутков времени между переключениями сигналов светофора указывают количество базовых временных интервалов, а не время.
Также в представленном листинге вызывает интерес способ определения типов. В строке 8 при помощи оператора typedef для типа unsigned char объявляется новое имя byte. В строке 9 аналогично для типа unsigned int объявлено имя uWord. Подобное решение не только позволяет сократить количество символов при определении типов объектов, но и позволяет легко корректировать объемы памяти, выделяемые для объектов. Это может быть важно, например, при переносе фрагмента кода между различными компиляторами, т.к. в зависимости от реализации для одних и тех же типов возможно выделение разных объемов памяти. Так для объектов типа int может быть выделено 2, 4 или более байт. Подобный прием позволит внести корректировку путем изменения всего одной команды.
Не смотря на значительное увеличение количества командных строк листинга 1.6 в сравнении с листингом 1.5, последний пример имеет явное преимущество: вызов функции формирования интервалов времени за один цикл обработки состояния светофора выполняется однократно и остается неизменным по продолжительности. Все это в значительной мере упрощает возможность реализации дополнительных параллельно выполняемых задач.
Если внимательно рассмотреть обработчики, выделенные элементами конструкции множественного выбора, то можно выделить некоторые условия, при истинности которых состояния автомата будут совпадать.
Например, можно выделить состояния установившегося зеленого сигнала светофора, приведенные в строках 46 – 47, 50 – 51, 54 – 55, и состояния его отключения, приведенные в строках 44 – 45, 48 – 49, 52 – 53. Получается, что эти состояния могут складываться по совокупности нескольких условий.
Состояния, описываемые в строках 42 – 43 и 56 – 57 нельзя отнести к перечисленным общим случаям, так как в них кроме манипуляций с зеленым.
Состояния, описываемые в строках 42 – 43 и 56 – 57 нельзя отнести к перечисленным общим случаям, так как в них кроме манипуляций с зеленым сигналом светофора производятся дополнительные действия. И не смотря на то, что в результате выполнения данных обработчиков устанавливаются аналогичные перечисленным случаям состояния светофора (включен зеленый сигнал, желтый и красный – выключены), их объединение было бы идеологически не верным, так как при необходимости расширения функциональности программного кода эти состояния могут быть дополнены различными командами.
Таким образом, можно группировать условия выполнения однотипных состояний при помощи операций логического сложения, что поможет сэкономить рассмотренный код. В листинге 1.7 приводится описание операции множественного выбора с произведенными на основе изложенных мыслей корректировками.
Листинг 1.7 – Обработчик отдельных состояний “светофора”
if(++counter==t1) //увеличение счетчика и проверка PORTD.6=1; //вкл. желтый else if(counter==t2) PORTD=1<<5; //выкл. кр. и желтый, вкл. зел. |
Продолжение листинга 1.7
else if((counter==t3)|| (counter==t5)|| (counter==t7)) PORTD.5=0; //выкл. зеленый else if((counter==t4)|| (counter==t6)|| (counter==t8)) PORTD.5=1; //вкл. Зеленый else if(counter==t9) PORTD=1<<6; //выкл. зеленый, вкл. желтый else if(counter==t10){ PORTD=1<<7; //выкл. желтый, вкл. красный counter=0; //возобновление цикла работы } |
Вопросы для сома контроля
1. Как в листинге 1.4 производится поочередное переключение красного и желтого светодиодов?
2. Как в этом примере производится подсчет переключений сигналов?
3. Какие действия выполняет программа, представленная в листинге 1.5?
4. Как в листинге 1.6 определяется состояние управляющих светодиодами сигналов?
5. Как определяется длительность базового временного интервала?
6. Какими недостатками обладают программы листингов 1.5 и 1.6?
7. При помощи каких команд можно осуществлять манипуляции с отдельными разрядами регистров управления портами микроконтроллера?