Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Микропроцессорные системы. Применение микроконтроллеров семейства AVR для управления внешними устройствами
.pdf
21
На языке ассемблера AVR запись в регистр DDRD будет выглядеть
следующим образом:
LDI R16, 0xF0
OUT DDRD,R16
Обратите внимание, что язык ассемблера AVR не позволяет запи-
сать константу в порт, поэтому приходится использовать промежуточ-
ный РОН – в нашем примере R16.
На языках С и С++ запись в регистр DDRD будет выглядеть следующим образом:
DDRD=0xF0;
В дальнейшем все примеры будут приводиться на языках С и С++,
если не оговорено иное. Все примеры опробованы на реализации языков
C и С++ в версии avr-gcc. С пакетом компиляторов GCC можно ознако-
миться в [4–6]. Также советуем ознакомиться с утилитами автоматиза-
ции сборки программных проектов, такими как GNU Make [7].
4.2. Пример работы с портом ввода-вывода в режиме ввода
Очень многие внешние устройства, состояние которых опрашива-
ется с помощью порта ввода, можно представить как «сухой контакт».
«Сухой контакт» – это контакт, имеющий два состояния – замкнутое и
разомкнутое: кнопка, выключатель, геркон и датчик открывания дверей
и многие другие датчики.
Подключение такого «сухого контакта», например кнопки, показано на рис. 11.
В нашем примере датчик-кнопка подключен к линии 3 порта D.
В разомкнутом состоянии линия PD3 через резистор подключена к
напряжению питания, что соответствует логической 1 на входе PD3.
Если кнопка замыкается, то линия PD3 оказывается подключенной
непосредственно к общему проводу, что соответствует логическому 0.
Для того чтобы программно определить состояние линии PD3,
необходимо проконтролировать состояние бита номер 3 в регистре
PIND.
Рис. 11. Подключение датчика типа «сухой контакт» к микроконтроллеру

22
Для контроля состояния одного или нескольких бит используется
битовая маска. Битовая маска – это двоичное число, в котором контролируемые биты равны 1, а остальные биты равны 0. Операция побитного логического умножения битовой маски на проверяемое значение дает
нули во всех разрядах, кроме контролируемых. Это позволяет контроли-
ровать значения как отдельных битов, так и групп битов.
Рассмотрим, как осуществляется контроль бита в нашем примере
(рис. 12).
Рис. 12. Выделение бита с помощью битовой маски
Битовая маска для нашего примера содержит 1 в третьем бите –
остальные нули, т. е. 0b00001000, или, в десятичном виде – это число 8.
После логического умножения битовой маски на значение, считанное из порта PIND, все биты, кроме 3-го, обнуляются, а значение 3-го
бита будет равно нулю (если контакты замкнуты) или единице (если
контакты разомкнуты).
Для контроля единственного бита, маска всегда равна единице,
сдвинутой на номер бита, начиная отсчет с нуля. Действительно: 1<<3 = 8.
На языках С и С++ подобная конструкция записывается очень просто.
Для нашего примера проверяющее нажатие кнопки будет выглядеть так:
if (PIND & (1<<3)){
// Бит 3 = 1
// Кнопка НЕ нажата
}
else{
// Бит 3 = 0
// Кнопка нажата
}

23
4.3. Пример работы с портом ввода-вывода в режиме вывода
Из-за особенностей схемотехнического решения порта ввода-
вывода микроконтроллеров семейства AVR целесообразно подключать к
нему нагрузку таким образом, чтобы ток через нее протекал при нулевом логическом уровне. Значит, нагрузка включена, когда на линию по-
дан логический нуль.
В нашем примере (рис. 13) нагрузка-светодиод подключена к линии 4 порта D. Резистор нужен для ограничения тока через светодиод.
Для того чтобы линия была переведена в состояние вывода, необхо-
димо установить бит 4 регистра DDRD в состояние лог. 1. Для установки
одного или группы битов используется команда побитового логического
сложения с битовой маской, в которой установлены те биты, которые тре-
буется установить в состояние лог. 1, например так, как сказано ниже:
DDRD= DDRD | (1<<4);
Данная команда устанавливает только бит 4, не изменяя значения
других битов регистра DDRD. Выражение (1<<4), т. е. единица, сдвинутая
на 4 бита влево, является битовой маской. Точно так же можно записать:
// шестнадцатиричная запись маски
DDRD= DDRD | 0x10;
или
// десятичная запись маски
DDRD= DDRD | 16;
Все эти выражения являются полностью идентичными, и какое из
них использовать – определяет программист.
Рис. 13. Пример подключения нагрузки (светодиода) к порту ввода-вывода

24
Заметим, что если необходимо установить не один, а несколько
битов, то для этого достаточно всего лишь изменить битовую маску.
Например, если нам необходимо перевести в состояние вывода две ли-
нии порта D, номер 4 и 7, то это можно сделать следующим образом:
DDRD= DDRD | ( (1<<4) | (1<<7) );
Выражение (1<<4) – битовая маска для линии 4; выражение (1<<7) –
битовая маска для линии 7. Их логическая сумма дает число, в котором
установлены биты 4 и 7 (т. е. число 0x90), – битовую маску для линий 4
и 7. И, наконец, логическое суммирование значения регистра DDRD с
битовой маской для линий 4 и 7 устанавливает в состояние вывода ли-
нии порта 4 и 7.
Чтобы включить нагрузку (зажечь светодиод в нашем примере),
необходимо сбросить 4-й бит порта в состояние лог. 0.
Для сброса одного из битов регистра в состояние лог. 0 применяется операция логического умножения на инверсию битовой маски:
PORTD= PORTD & ~(1<<4);
Рассмотрим подробнее, как работает данная конструкция. Выра-
жение (1<<4) – битовая маска бита 4. Другими словами, такое число, в
котором все биты равны 0, а 4-й бит равен 1.
Побитовая инверсия этого числа, обозначаемая символом тильда
(~) на языках С и С++, дает в результате число, в котором все биты рав-
ны 1, кроме 4-го, который становится равным 0 (рис. 14).
Рис. 14. Битовая маска – прямая и инверсная

25
Логическое умножение инверсной битовой маски на значение ре-
гистра PORD изменит значение единственного бита – бита номер 4,
сбросив его в ноль. Остальные биты не изменят своего значения.
Для сброса нескольких битов одновременно необходимо использовать
битовую маску, с указанными в ней необходимыми битами.
Например, сброс одновременно бита номер 4 и бита номер 7 делается так:
PORTD= PORTD & ~( (1<<4) | (1<<7) );
Для выключения нагрузки (гашения светодиода в нашем примере),
необходимо установить бит 4-го регистра PORTD в состояние лог. 1:
PORTD= PORTD | (1<<4);
Вкратце можно определить несколько правил работы с портами
ввода-вывода общего назначения в микроконтроллерах семейства AVR.
Правило 1
Состояние всех линий порта нужно контролировать с помощью
считывания значения регистра PINх.
Правило 2
Управлять состоянием линий, которые запрограммированы на вы-
вод, нужно с помощью записи в регистр PORTx.
Правило 3
Если нам необходимо работать с линями порта, настроенными на
вывод, то сначала необходимо установить их состояние с помощью регистра PORTx, а только потом программировать эти линии на вывод с
помощью регистра DDRx. Это необходимо, чтобы не было ложного сра-
батывания подключенных внешних устройств из-за неопределенного
состояния регистра PORTx в момент старта микроконтроллера.
4.4. Прерывания портов ввода-вывода
В микроконтроллерах ATMega169 имеется возможность генерации прерываний по изменению состояния некоторых линий порта ввода-
вывода. Например, если к линии ввода микроконтроллера подключены
кнопки, нет необходимости их периодически опрашивать, нужно лишь
запрограммировать генерацию прерываний от этих линий.
Микроконтроллер ATMega169 имеет возможность генерировать
прерывания при изменении уровня сигнала на линях портов B и E.

26
Каждый из портов – B и E – имеет свой вектор прерывания по из-
менению состояния линии. Управление генерацией прерываний портов
B и E осуществляется несколькими регистрами:
DRRB – регистр направления порта B. Должен содержать нули в
битах, соответствующих линиям, генерирующим прерывания.
PORTB – регистр вывода порта B. Должен содержать единицы в
битах, соответствующих линиям, генерирующим прерывания.
PCMSK1 – регистр маски прерываний порта B. Должен содержать
единицы в битах, соответствующих линиям, генерирующим прерыва-
ния.
PCIF1 – регистр флагов прерываний порта B. При возникновении
события-прерывания от порта B содержит единицы в тех разрядах, ко-
торые соответствуют линиям, генерирующим прерывание. Биты можно
очистить принудительно, записав в них единицы.
DRRE – регистр направления порта E. Должен содержать нули в
битах, соответствующих линиям, генерирующим прерывания.
PORTE – регистр вывода порта E. Должен содержать единицы в
битах, соответствующих линиям, генерирующим прерывания.
PCMSK0 – регистр маски прерываний порта B. Должен содержать
единицы в битах, соответствующих линиям, генерирующим прерыва-
ния.
PCIF0 – регистр флагов прерываний порта E. При возникновении
события-прерывания от порта E содержит единицы в тех разрядах, кото-
рые соответствуют линиям, генерирующим прерывание. Биты можно
очистить принудительно, записав в них единицы.
EIMSK – регистр разрешения внешних прерываний. Прерывани-
ями от порта B управляет бит PCIE1 этого регистра. Прерываниями от
порта E управляет бит PCIE0 этого регистра.
EIFR – регистр флагов прерываний портов B и E. При возникно-
вении события-прерывания от порта B содержит единицу в разряде
PCIF1. При возникновении события-прерывания от порта E содержит
единицу в разряде PCIF0. Флаги PCIF1 и PCIF0 не очищаются автоматически при входе в процедуру обработки прерывания, поэтому необходимо очищать их программно, записывая в единицы в разряды PCIF1 и
PCIF0 в процессе выполнения обработчика прерывания.
Заметим, что прерывания генерируются как при изменении состо-
яния линии из 0 в 1, так и при изменении из 1 в 0. Поэтому, чтобы знать
состояние линий, в обработчике прерывания необходимо считать это со-
стояние из регистров PINB или PINE, в зависимости от прерывания.
Чтобы разрешить прерывания от порта B, необходимо описать обработчик прерывания по изменению линий порта B:

27
ISR(PCINT1_vect){
// Флаг PCIF1 не очищается автоматически.
// Очищаем его принудительно
EIFR = (1<<PCIF1);
// Считываем состояние линий порта B
uint8_t lines=PINB;
// Обрабатываем линии порта B, считанные в
// переменную lines
// ……………………..
}
В процедуре инициализации программы необходимо сформиро-
вать маску линий порта, назовем ее BMASK. Маска содержит единицы в
тех линиях, прерывания по изменению которых мы хотим сгенериро-
вать.
Программируем биты регистров PORTB и DDRB:
DDRB &= ~BMASK;
PORTB |= BMASK;
После этого линии, генерирующие прерывания, работают как
входные, с подтяжкой к напряжению питания.
Разрешаем прерывания от конкретных линий порта B:
PCMSK1 = BMASK;
Разрешаем прерывания порта B:
EIFR |= (1<<PCIF1);
EIMSK |= (1<<PCIE1);
Если прерывания разрешены глобально (флаг I регистра SREG
установлен), то с этого момента прерывания по изменению состояния
линий порта B, определенных маской BMASK, разрешены.
Запись единицы в разряд PCIF1 регистра EIFR необходима для
предотвращения возникновения ложного прерывания.
Чтобы разрешить прерывания от порта E, необходимо описать обработчик прерывания по изменению линий порта E:
ISR(PCINT0_vect){
// Флаг PCIF0 не очищается автоматически.
// Очищаем его принудительно
EIFR = (1<<PCIF0);

28
// Считываем состояние линий порта E
uint8_t lines=PINE;
// Обрабатываем линии порта E, считанные в
// переменную lines
// ……………………..
}
В процедуре инициализации программы необходимо сформиро-
вать маску линий порта, назовем ее EMASK. Маска содержит единицы в
тех линиях, прерывания по изменению которых мы хотим сгенериро-
вать.
Программируем биты регистров PORTE и DDRE:
DDRE &= ~EMASK;
PORTE |= EMASK;
После этого линии, генерирующие прерывания, работают как
входные, с подтяжкой к напряжению питания.
Разрешаем прерывания от конкретных линий порта E:
PCMSK0 = BMASK;
Разрешаем прерывания порта E:
EIFR |= (1<<PCIF0);
EIMSK |= (1<<PCIE0);
Если прерывания разрешены глобально (флаг I регистра SREG
установлен), то с этого момента прерывания по изменению состояния
линий порта E, определенных маской EMASK, разрешены.
Запись единицы в разряд PCIF0 регистра EIFR необходима для
предотвращения возникновения ложного прерывания.
В прил. Е приведен исходный текст драйвера джойстика модуля
AVR Butterfly, использующего обработку прерываний по изменению ли-
ний портов B и E.

29
5. Таймеры-счетчики
Таймеры-счетчики – это устройства, предназначенные для отсчета
импульсов, поступающих на их счетный вход.
Общая классификация по назначению таймеров-счетчиков микроконтроллеров семейства AVR приведена на рис. 15.
Рис. 15. Общая классификация таймеров-счетчиков по назначению
Основой таймера-счетчика микроконтроллера семейства AVR яв-
ляется двоичный счетчик. Значение счетного регистра этого счетчика в
микроконтроллерах семейства AVR в любой момент времени может
быть прочитано и записано программным путем.
Если на счетный вход таймера-счетчика подаются импульсы опре-
деленной частоты (например, с точного кварцевого резонатора), то количество отсчитываемых импульсов пропорционально времени. Такой
режим называется режимом таймера.
Если на счетный вход подаются случайные импульсы, период
между которыми непостоянен, то такой режим называется режимом
счетчика импульсов.
Некоторые таймеры-счетчики микроконтроллеров семейства AVR
имеют аппаратное расширение, позволяющее им управлять внешней
линией, генерируя сигналы с широтно-импульсной модуляцией (ШИМ).
Такие сигналы можно использовать для цифроаналогового преобразования. Например, с помощью ШИМ можно управлять яркостью свечения
светодиода; генерировать звуковые сигналы; управлять мощностью
нагревателя и так далее.
Ниже кратко рассмотрены различные режимы работы таймеровсчетчиков микроконтроллеров семейства AVR на примере таймерасчетчика 0 микроконтроллера ATMega169. Таймер-счетчик 0
(Timer/Counter0) имеет три основных режима работы – счет внешних
импульсов (счетчик), счет импульсов тактовой частоты (таймер) и
ШИМ-режим.
Кроме того, таймер-счетчик 0 может генерировать два прерывания – по событию переполнения счетного регистра и по совпадению
счетного регистра с регистром сравнения.

30
Таймер-счетчик 0 является 8-битным, т. е. его счетный регистр
имеет размер 8 бит. Максимальное количество импульсов, которое мо-
жет отсчитать этот таймер-счетчик, равно 28, или 256.
Для управления таймером-счетчиком 0 используются несколько
регистров:
TCCR0A – регистр управления таймером-счетчиком 0;
TCNT0 – счетный регистр таймера-счетчика 0;
OCR0A – регистр сравнения (совпадения);
TIMSK0 – регистр маски прерываний таймера-счетчика 0;
TIFR0 – регистр флагов прерываний от таймера-счетчика 0.
Мы не будем рассматривать все возможные режимы работы тай-
мера-счетчика, ограничимся основными – режимом таймера, режимом
счетчика импульсов и режимом ШИМ.
Таймер-счетчик 0 имеет возможность генерировать два прерывания –
по переполнению (on overflow) и по совпадению (on compare match).
Прерывание по переполнению генерируется в случае, когда значение
счетного регистра TCNT0 равно максимальному (в нашем случае 28–1, или
255) и приходит очередной (256-й) импульс на счетный вход. При этом зна-
чение счетного регистра становится равным 0, и выставляется флаг прерывания по переполнению TOV0 в регистре TIFR0. Если прерывания по пе-
реполнению разрешены установкой флага TOV0 в регистре TIMSK0, а
также прерывания разрешены глобально (флаг I регистра SREG установлен), то происходит вызов процедуры обработки прерывания с вектором
номер 11, расположенным по адресу 0x002С памяти программ.
Если необходимо, чтобы таймер-счетчик 0 генерировал прерыва-
ния по переполнению счетного регистра, то необходимо в программе
описать обработчик прерывания следующим образом:
ISR ( TIMER0_OVF_vect ) {
// Обработчик прерывания по переполнению
// от таймера-счетчика 0
}
Затем необходимо разрешить прерывания по переполнению от
таймера-счетчика 0 во время инициализации программы:
TIMSK0 |= (1<<TOV0);
Запретить прерывания по переполнению от таймера-счетчика 0
можно следующей командой:
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
