Вы находитесь на странице: 1из 38

Раздел 2

Программирование
микроконтроллеров AVR во
встраиваемых системах
Тема 2. 1 Управление включением
светодиодов
ПОДРОБНОЕ ОПИСАНИЕ ПОРЯДКА СОСТАВЛЕНИЯ
ИСХОДНОГО КОДА ПРОГРАММЫ ДЛЯ ЛАБОРАТОРНОЙ
РАБОТЫ № 1

 Макетирование и исследование встраиваемой


системы на базе микроконтроллера семейства
AVR ATtiny2313
 Цель работы
 ознакомление с техническими данными
микроконтроллера ATtiny2313;
 создание программы на языке C в соответствии с
техническим заданием;
 компиляция и отладка программы в среде
программирования Code Vision AVR;
 загрузка в микроконтроллер загрузочного файла и
проверка работы запрограммированного
микроконтроллера в рабочей станции NI ELVIS II.
Краткие технические данные
микроконтроллера ATtiny2313
 ATtiny2313 – малопотребляющий КМОП (CMOS) -8-
битный микроконтроллер, основанный на AVR-
усовершенствованной RISC-архитектуре.
Выполняет команды за один такт центрального
процессора (ЦП или ЦПУ).
 120 команд - большинство выполняется за 1 так ЦПУ;
 32 x 8 регистра общего назначения (32 регистра 8-ми
разрядных);
 полностью статическая операция;
 производительность до 20 миллионов команд в секунду
на 20 МГЦ ЦПУ;
 энергонезависимая память данных и программ;
 2 КБ внутрисистемной энергонезависимой ФЛЭШ-памяти
программ;
 выносливость ФЛЭШ-памяти 10 000 циклов
записи/стирания;
 128-байтовая встроенная программируемая EEPROM
память;
 выносливость EEPROM: 100 000 циклов записи/стирания;
 128-байтовая внутренняя SRAM память;
 программирование защитной блокировки для ФЛЭШ-
прграмм и EEPROM-данных;
Периферийные особенности
 один 8-битный Таймер/Счетчик с отдельным
предделителем частоты и Режимом сравнения;
 один 16-битный Таймер/Счетчик с отдельным
предделителем частоты и Режимом сравнения;
 четыре ШИМ канала;
 встроенный в чип Аналоговый Компаратор;
 программируемый Сторожевой Таймер со
встроенным Генератором;
 USI - универсальный последовательный интерфейс;
 полно-дуплексный интерфейс – USART;
Конфигурация выводов
2. Задание на выполнение лабораторной работы.

 разработать устройство на микроконтроллере


ATtiny2313, которое будет отображать в двоичном
виде светящимися светодиодами 8-ми битное число,
начиная с 0 и с постоянным увеличением на 1; 
 устройство должно питаться постоянным
стабилизированным напряжением  +5 вольт; 
 тактирование МК осуществляется от внутреннего
генератора с частотой 1 Мгц; 
 всего должно быть подключено 8 светодиодов от
выводов порта  через токо-ограничительные
резисторы к «земле»; 
 переключение светодиодов должно производиться с
паузами в 262 мс.
Анализ технического задания
 При анализе ТЗ становится очевидным, что в качестве
выходного порта для сигналов переключения 8 светодиодов
можно использовать только 8-ми битный порт В в
микроконтроллере ATtiny2313. Другие порты А и С имеют
меньшую разрядность.
 В качестве источника питания можно использовать встроенный
источник +5 В рабочей станции NI ELVIS II.
 В заводских установках микроконтроллера ATtiny2313 заложена
программа по умолчанию для работы с внутренним
генератором на частоте 8 МГц с делителем частоты на 8, что
дает частоту тактирования 1 МГц. Оставляем фьюзы, т.е.
первоначальные установки, неизменными.
 В качестве светодиодов используем штатные светодиоды LED0
– LED7 рабочей станции NI ELVIS II. Они уже подключены
катодами к «земле». Сопротивление токо-ограничительных
резисторов выбираем 510-560 Ом.
 Пауза переключения будет обеспечиваться программно.
 Принципиальная схема устройства содержит
микроконтроллер, резисторы и светодиоды и
представлена на рис 1. Она реализуется на
монтажной плате NI ELVIS II. К выводам 1, 17 – 19
присоединяются также провода, идущие к
программатору и использующиеся для прошивки
микроконтроллера. Для того, чтобы светодиод
начал светиться, на его анод нужно подать
положительное напряжение 4…5 В, т.е. обеспечить
появление на нужном выводе порта B
микроконтроллера логической единицы «1». Учтем,
что вывод 12 соответствует младшему биту порта и
светодиоду LED0, а вывод 19 соответствует
старшему, восьмому биту порта и светодиоду LED7.
Рис.1. Принципиальная схема устройства
Создание программы микроконтроллера
 Для создания программы микроконтроллера
ATtiny2313 будем пользоваться языком высокого
уровня C/C++. Необходимо ориентироваться в
основных понятиях языка программирования,
знаниями об операторах, функциях и приемах их
использования.
 Составление алгоритма программы
 Создание программы начинается с составления
алгоритма, т.е. последовательности этапов,
которые необходимо выполнять. Алгоритмы
должны быть формализованы в виде схемы,
однако в простом случае их можно выражать с
помощью текстового описания, так называемого
псевдокода. Например, для нашего ТЗ:
 а) старт программы; 
 б) сделать выводы МК, к которым подключены
светодиоды, выходами. 
 в) запустить таймер МК со скоростью счета,
обеспечивающей его переполнение каждые 262 мс,
это сформирует паузу;
 г) добавить 1 к числу в регистре PORTВ;
 д) ждать переполнения таймера; 
 е) очистить признак переполнения таймера; 
 ж) перейти к пункту г) – обеспечить непрерывный
цикл.
 Интервал времени, который занимает цикл счета
от 0 до 255, будет 0.262*28 = 67,072 с, т.е. примерно
минуту. 
 Начинаем составление программы в
соответствии с созданным алгоритмом.
 а) старт программы.
 В МК программа стартует автоматически после
подачи питания и наличии "1" на ножке RESET. 
Т.е. на Си ничего специального писать не надо. Но
мы напишем то, с чего обычно начинается
программа на С -  с директив препроцессора (это
раздел компилятора, который готовит текст
программы к компиляции). Например,: 
  #include <tiny2313.h>
 #define PB_OUT  DDRB = 0xFF
 #include < >  -  означает, что
вместо <....>  препроцессор должен подставить
текст заголовочного файла – tiny2313.h - в нем
описаны регистры МК, и именно ATtiny2313, чтобы
компилятор знал их физические адреса в МК и
можно было ставить в тексте мнемонические
обозначения регистров и битов.
 #define AAA BBB – означает введение макроса
AAA, и препроцессор компилятора перед
компиляцией программы заменит в ее тексте все
найденные AAA на то, что стоит правее пробела
после AAA. В этом примере на BBB, но это могут
быть и более сложные выражения. В нашей
программе перед компиляцией  PВ_OUT   будет
заменено на   DDRВ = 0xFF .
 б) сделать выводы МК к которым подключены светодиоды выходами. 
 Каждому порту в МК AVR соответствуют минимум три регистра:
 DDRx - регистр направления работы - вход или выход: х - означает
букву A, B, C, D, E... порта, по числу портов в конкретном МК. Если
установлен (т.е. «1»), значит бит работает на вывод данных, если не
установлен («0»), то работает на ввод данных. Выражение DDRВ =
0xFF (или 11111111) означает, что все 8 битов порта В настроены на
вывод данных.  
 PINх - регистр содержит значения физических (т.е. реальных, тех
которые мы получим измерив вольтметром и преобразовав в 1 или 0)
уровней сигнала на соответствующих выводах МК. 
 PORTх – регистр, в биты которого записываем значения "1" или "0" - 
то, что хотим получить на соответствующих выводах МК при
назначении их выходом, когда соответствующий бит в регистре DDRx
установлен (т.е. равен "1"). Это выражение может использоваться как
8-ми битовая переменная.
 запустить таймер МК со скоростью счета,
обеспечивающей 
          его переполнение каждые 262 мС. 
 В МК ATtiny2313 есть несколько таймеров. Самый простой
из них  8-ми битный таймер-счетчик Timer_0 . (см. Datasheet
Complete - раздел "8-bit Timer/Counter0").  Именно его мы
будем использовать. 8-ми битный означает, что он в
простейшем режиме работы Normal Mode считает так:  n,
n+1, n+2, ... 253, 254, 255, 0, 1, 2, ...Так меняются числа в
регистре TCNT0 этого таймера. Причем при появлении в
нем  0 после 255 возникает "переполнение" таймера_0 и в
регистре флагов прерываний от таймеров - TIFR  -
устанавливается (т.е. становится "1") бит_0  - он называется
"флаг возникновения события - переполнение таймера 0". 
 Из ДШ узнаем, что: Для запуска Таймера_0  - т.е. для
того чтобы происходило периодическое увеличение
значения в регистре Таймера_0 TCNT0 - нужно лишь
установить коэффициент деления. На этот
коэффициент будет делиться  тактовая частота МК
(clock  -  частота кварца, например, или частота работы
внутреннего RC генератора МК, причем с учетом
деления на 8, если используется делитель DIV8)  перед
тем как подавать на таймер_0 импульсы счета.
 Для этого нужно изменить в соответствии с
таблицей Table 42. Clock Select Bit Description биты 2…
0 – CS02 CS01 CS00 "Clock Select" в
регистре TCCR0B управления Таймером_0 - он
называется в ДШ "Timer/Counter Control Register". Если
все биты  CS02 CS01 CS00 - нули - то таймер
остановлен! 
Тактовая частота задана заводскими
установками 1000000 колебаний в секунду,
поделим их на 256=28 (столько насчитывает 8-
битный таймер_0 между переполнениями) и на
максимально возможный коэффициент деления 
1024=210 по таблице Table41. Получим частоту
F= 106/218Гц .
Из этого выражения сразу получаем период
повторения цикла gtht
Т= 1/F = 218/ 106 = 0,262144 с, что
приблизительно равно заданному 262 мс
TABLE 41
CS02 CS01 CS00 Description

0 0 0 No clock source (Timer/Counter stopped)

0 0 1 clkI/O /(No prescaling)

0 1 0 clkI/O /8 (From prescaler)

0 1 1 clkI/O /64 (From prescaler)

1 0 0 clkI/O /256 (From prescaler)

1 0 1 clkI/O /1024 (From prescaler)

1 1 0 External clock source on TO pin. Clock on falling edge.

1 1 1 External clock source on TO pin. Clock on rising edge.


 Теперь у нас есть теоретические знания для того,
чтобы написать код на Си, выполняющий
пункты б) и в). Эти два пункта нашего алгоритма
настраивают задействованную в устройстве
периферию МК (это PORTB и Timer0) так, как нам
нужно. Удобно вынести такую настройку в
отдельную функцию, которая будет выполняться в
начале программы. Обычно такую функцию
называют инициализацией.
 void initialization(void)
{
PB_OUT; // макрос для того, чтобы установить
PORTB выходом
TCCR0B = 0x05; // таймер включить на счет с
частотой в 1024 раз меньшей, чем тактовая
частота
 PORTB = 0; // определяем первоначальное значение
переменной PORTB, т.е. записываем во все биты
регистра PORTB нули
}
 После выполнения этой функции выводы порта B станут
выходами, и таймер будет запущен с паузой
переполнения 262 мс. Дополнительно обнуляем все
выходы, чтобы счет начинался с 0. Значит пункты б) и в)
нашего  алгоритма выполнены.
 Из анализа оставшейся части алгоритма видно, что
программа должна выполняться  циклически.
Переходим к созданию главной для программы на Си
функции:
 main(){
}
 В ней вначале необходимо вызвать функцию инициализации МК и
затем вставить безусловный цикл, который часто называют
бесконечным:
 void main (void){
initialization(); // Вызвать (== выполнить) функцию инициализации МК
 //Бесконечный цикл
while (1){ //для микроконтроллеров такую запись рекомендуется
делать всегда
 PORTВ++; //добавить 1 к значению в PORTВ
 while (!(TIFR&0x02)); // ждем установки флага переполнения timer0
 TIFR = 0x02; // очистить флаг переполнения timer0, пауза закончена
         }; // закончено тело цикла, управление переходит к начальной
команде в теле цикла
} // закрывает скобку для main
Проанализируем программу с места, где начинается
бесконечный цикл. 

 while (1){}; 
 "бесконечный" потому, что в скобках, где стоит
условие его выполнения, написана константа -
число 1 - это "не ноль" значит "истина" в Си.
Поэтому цикл будет выполняться, пока есть
питание в МК и нет "сброса" (RESET).  Можно
написать любое число отличное от  0 - не ноль это
"истина" .
 Строчка программы :
 PORTB++; //добавить 1 к значению в PORTB после паузы,
вызванной переполнением.
 Строчка программы :
 while (!(TIFR&0x02)); // ждем установки флага переполнения
timer0
 это условный цикл, где while (условие){}; , но без тела цикла,
поэтому скобки {} опущены. Без тела - т.е. он ничего не
выполняет, а лишь проверяет условие в скобках на
"истинность" так часто насколько возможно по скорости
МК и до тех пор, пока не обнаружит, что условие  в скобках
"ложно" - тогда программа закончит этот цикл и пойдет
ниже,  на следующую строчку кода.
 Условие   в скобках !(TIFR&0x02) включает знак   ! 
что означает логическое отрицание - меняет  "ложь"
на "истину" и наоборот.
 Значит цикл будет продолжаться, пока
таймер/счетчик 0 будет накапливать счетные
импульсы до переполнения и будет проверяться
содержимое регистра TIFR – регистра флагов
прерывания таймеров. Результат вычисления
выражения 
 TIFR&0x02 (0х02 = 0b00000010)
 будет равен 0 что значит в Си "ложно" до тех пор,
пока не произойдет переполнение и бит_1 в регистре
TIFR установится в 1. При том же (!(TIFR&0x02))
будет «истина».
 & - это побитное (поразрядное) логическое "И"  -
означает, что только 1  и  1 в каждом бите дают  1.
 В числе 0x02  все биты равны 0 кроме бит_1  -
значит, когда бит_1 в регистре TIFR станет "1" то
результат TIFR&0x02 станет 1 или "истина", тогда (!
(TIFR&0x02)) станет «ложь», и программа покинет
цикл.
 Из ДШ известно, что бит_1 регистра TIFR имеет
обозначение TOV0 - это "флаг"  события:
"переполнение таймера_0". Значит мы выполнили
пункт д) алгоритма. Рассмотрим подробнее
информацию об этом бите, начнем с цитирования
ДШ на английском:
 • Bit 1 – TOV0: Timer/Counter0 Overflow Flag
 The bit TOVO is set when an overflow occurs in Timer/Counter0. TOVO
is cleared by hardware when executing the corresponding interrupt
handling vector. Alternatively, TOVO is cleared by writing a logic one to
the flag. When the SREG l-bit, TOIE0 (Timer/CounterO Overflow
Interrupt Enable), and TOVO are set, the Timer/CounterO Overflow
interrupt is executed.
 Перевод:
 • Бит 1 - TOV0: флаг переполнения таймера/счетчика 0
 Бит TOV0 устанавливается ( т.е. становится «1»), когда происходит
переполнение в таймере/счетчике 0. TOV0 очищается ( т.е.
становится «0») аппаратно при выполнении соответствующего
вектора обработки прерываний т.е. TOV0 сам очищается
(становится "0") если происходит вызов обработчика прерывания
связанного с событием, устанавливающим данный флаг.
Альтернативно, TOV0 очищается путем записи логической единицы
во флаг.
 Обработчик прерывания вызывается программно,
если ранее в программе мы 
 - разрешили прерывания глобально ("установив"
бит 1 в регистре SREG ) и 
 - разрешили данное прерывание "установив"
бит TOIE0.
 Заметим, что эти условия мы не обеспечивали.
 Следующий пункт алгоритма :
 е) очистить признак переполнения таймера. 
 нужно выполнить для того чтобы и в следующий раз
определить момент переполнение таймера_0. Для
этого нужно очистить бит TOV0. Мы отметили выше,
что не планировали использовать прерывание от
таймера_0 и не разрешали его и не разрешили
прерывания вообще. 
 Поэтому мы используем альтернативный метод
очистки флага, и строка программы:
 TIFR = 0x02; // очистить флаг переполнения timer0
 помещает число 2 в регистр  TIFR т.е. именно
вписывает логическую  "1" в бит_1  - тем самым
очищает флаг и выполняет пункт алгоритма   е).
Именно такой метод используется для очистки
флага только в микроконтроллерах AVR. В других
типах МК используется запись логического «0» во
флаг. Причем даже в микроконтроллерах AVR флаг
переполнения может располагаться в разных битах
регистра TIFR. Необходимо внимательно изучать
полную ДШ для каждого конкретного МК.
  Внимание !  Строка: TIFR = 0x02; вписывает в другие
биты регистра (кроме бит_1)  нули ! В нашем случае
это допустимо. Если вам нужно записать "1" только в
один бит_n регистра, не трогая другие биты,
используйте манипулирование отдельными битами,
будет такой код:
 REGISTER |= 1<<n; // здесь 1 сдвинута на n разрядов
влево, при побитном сравнении с любым числом в
разряде n даст 1.
 Если вам нужно записать "0" только в один
бит_n регистра, не трогая другие биты, используйте
такой код:
 REGISTER &= ~(1<<n);
 В бит_n появится нужное вам число не зависимо от
того  что там было!
 Последний пункт нашего алгоритма 
 ж) перейти к пункту г)
 не требует написания специального кода - он
выполняется автоматически, 
так как программа выполнила все "тело" цикла и
сама перейдет опять в начало цикла, т.е. к пункту
алгоритма -  г)
Полный текст программы:
 #include <io.h> //включает файл с объявлениями
всех типов
 // микроконтроллеров Atmel
 #include <tiny2313.h> // включает файл с
объявлениями битов
 //и регистров используемого
микроконтроллера
  
 #define PB_OUT DDRB = 0xFF // определяет макрос
PB_OUT,
 //который объявляет порт B выходом
  
 void initialization (void) // функция инициализации
 {
 PB_OUT; //вызываем макрос PB_OUT
 TCCR0B = 0x05; //запускаем таймер 0 с
коэффициентом деления 1024
 PORTB = 0; // обнуляем биты порта B
 }
 void main(void) //главная программа
 {
 initialization ();//начинаем с инициализации
 while (1) // непрерывный цикл
 {
 PORTB++; //прибавляем к значению переменной
PORTB единицу
 while (!(TIFR&0x02)); // ждем появления флага
переполнения таймера 0
 TIFR = 0x02; // очищаем флаг переполнения
таймера
 } // закрывающая скобка непрерывного цикла,
возврат к началу цикла
 } // закрывающая скобка главной программы
Использованы материалы сайта:
http://www.123avr.com/z1.htm
Посещено 25.12.2015 14-35