AVR. Аппаратный ШИМ микроконтроллера
Широтно-импульсная модуляция (pulse width modulation, PWM) часто используется в цифровой технике. AVR-микроконтроллеры имеют встроенную аппаратную возможность генерировать ШИМ-сигнал.
Особенность этого способа модуляции заключается в том, что он позволяет регулировать постоянную составляющую выходного сигнала средствами логических элементов. Другими словами можно изменять яркость светодиодов, скорость вращения коллекторных двигателей, температуру нагревателей и т.д.
Временная диаграмма ШИМ-сигнала:
Подготовка к работе
На нашей плате TutorShield есть трехцветный светодиод. Отдельные цвета подключены к выводам PB1, PB2 и PB3 на которые можно вывести ШИМ-сигнал. Для возможности управления им, установите перемычку "color" так, как показано на рисунке. Всех остальных перемычек быть не должно.
Голубой светодиод подключен к выводу PB1, оранжевый к PB2, зеленый к PB3.
Первый пример
У микроконтроллера есть встроенные таймеры, которые и управляют формированием ШИМ-сигнала. Для использования достаточно правильно настроить таймер и затем для формирования сигнала потребуется только изменять состояние одного регистра.
Вообще, таймеры в микроконтроллерах — очень полезная вещь. Они позволяют точно отмерять время, выполнять "фоновые" программы и т.д. Их полный функционал описан в datasheet от производителя микроконтроллера. В этой статье мы не будем описывать весь функционал таймера, а остановимся только на том, что понадобится нам для генерирования ШИМ-сигнала.
Прежде чем начать объяснения, давайте посмотрим как это будет выглядеть. Скомпилируйте и загрузите в память микроконтроллера следующую программу:
#define F_CPU 16000000UL //16MHz #include <avr/io.h> #include <util/delay.h> #define BLUE_PB 1 #define BLUE_PWM OCR1A #define ORANGE_PB 2 #define ORANGE_PWM OCR1B #define GREEN_PB 3 #define GREEN_PWM OCR2 void pin_init(void) { DDRB |= (1<<BLUE_PB) | (1<<ORANGE_PB) | (1<<GREEN_PB); PORTB &= ~((1<<BLUE_PB) | (1<<ORANGE_PB) | (1<<GREEN_PB)); } void timer1_init(void) { TCCR1A |= (1 << COM1A1) | (1 << WGM11); TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS10); TCNT1 = 0x00; ICR1 = 0xFF; OCR1A = 0x00; } int main(void) { pin_init(); timer1_init(); while(1) { BLUE_PWM = 250; _delay_ms(1000); BLUE_PWM = 50; _delay_ms(1000); } }
То что программа делает хорошо видно в функции main(). С периодичностью в 1000мс она изменяет яркость светодиода:
Теперь давайте подробно разберемся с настройкой таймера.
Настройка таймера
Вся программа сводится к тому, что необходимо правильно настроить таймер, а затем остается просто в правильный регистр записывать требуемое значение скважности сигнала. После этого вам уже не надо будет думать о том, как работать с выходом — управление им берет на себя модуль таймера.
Таймеры в микроконтроллерах — очень мощный инструмент. Сейчас мы остановимся только на том, как сконфигурировать их на генерирование ШИМ-сигнала. Более подробно о работе с таймерами мы расскажем в следующих статьях. Исчерпывающую информацию о работе таймеров можно получить из даташита.
Вернемся к нашей программе. В ней первым делом настраиваются сразу все нужные выводы на выход, а затем происходит инициализация таймера. По порядку рассмотрим в каких регистрах и какие функции включены в нашей программе:
- Timer/Counter 1 Control Register A – TCCR1A. Бит COM1A1 это регистра отвечает за сброс таймера при совпадении. Бит WGM11 связан с битами WGM12 и WGM13 следующего регистра.
- Timer/Counter 1 Control Register B – TCCR1B. Биты WGM11, WGM12, WGM13, выставленные одновременно, переводят таймер в режим генерирования ШИМ-сигнала. Бит CS10 задает скорость счета таймера одновременно запускает таймер и задает делитель частоты тактового сигнала. Если не задать никакой делитель — таймер будет остановлен. С битом CS10 делитель будет равен единице, то есть таймер будет тактироваться от кварца.
- Timer/Counter 1 – TCNT1H и TCNT1L. В этих двух регистрах хранится состояние таймера. Именно в них с каждым "тиком" кварца увеличивается хранимое значение. Обнулим его, на всякий случай
- Input Capture Register 1 – ICR1H и ICR1L. Это период следования импульсов. То есть, когда содержимое регистра TCNT1 достигнет значения 0xFF начнется следующий период сигнала
- Output Compare Register 1A — OCR1AH и OCR1AL. Задает скважность. То есть пока в регистре TCNT1 значение меньше OCR1A, то на выводе "1", а после этого "0"
Все это гораздо проще понять по следующей картинке:
Мы предположим, в регистр OCR1A записано значение "100".
Тактовый сигнал у нас задан без делителя. Период следования тактовых импульсов 1/16МГц = 62,5нс. С каждым новым тактом, состояние регистра TCNT1 увеличивается на единицу. При это постоянно происходит его сравнение с регистрами OCR1A и ICR1. Как только он досчитывает до OCR1A происходит переключение состояния вывода из "1" в "0". При достижении значения ICR1 состояние TCNT1 сбрасывается в ноль, на выводе снова выставляется "1" и начинается следующий период сигнала.
Несложно посчитать период следования импульсов в нашем проекте: 255*62,5 = 15937,5нс. Примерно 16мкс. Частота при этом 62,5кГц. Это очень большая частота и снизить ее можно увеличив делитель или/и значение ICR1.
В итоге, чтобы изменить скважность сигнала остается только записать новое значение в регистр OCR1. Мы переопределили его директивой
#define BLUE_PWM OCR1A
В бесконечном цикле программа именно это и происходит: устанавливаем относительную скважность 200, ждем 1с, скважность 50, ждем 1с. Осталось только самое простое — понять на каком выводе будет этот сигнал. Изменить это нельзя и формироваться он будет на выводе OC1A, который в микроконтроллере Atmega8 подключен к 13му пину PB1 (или D9 выводу Arduino).
Второй пример
Теперь сделаем циклическое изменение яркости. Для потребуется только добавить цикл, перебирающий возможные значения скважности от 0 до 255. Сделаем его при помощи простой функции for().
#define F_CPU 16000000UL //16MHz #include <avr/io.h> #include <util/delay.h> #define BLUE_PB 1 #define BLUE_PWM OCR1A #define ORANGE_PB 2 #define ORANGE_PWM OCR1B #define GREEN_PB 3 #define GREEN_PWM OCR2 void pin_init(void) { DDRB |= (1<<BLUE_PB) | (1<<ORANGE_PB) | (1<<GREEN_PB); PORTB &= ~((1<<BLUE_PB) | (1<<ORANGE_PB) | (1<<GREEN_PB)); } void timer1_init(void) { TCCR1A |= (1 << COM1A1) | (1 << WGM11); TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS10); TCNT1 = 0x00; ICR1 = 0xFF; OCR1A = 0x00; } int main(void) { pin_init(); timer1_init(); while(1) { for (int i=0; i<256; i++) { BLUE_PWM = i; _delay_ms(10); } } }
Если все сделано правильно — то работа будет выглядеть вот так:
Третий пример
Всего в микроконтроллере Atmega8 два аппаратных таймера — 16ти-битный и 8ми-битный. Причем в первом есть сразу два модуля сравнения и он может формировать одновременно два разных ШИМ-сигнала. Третий ШИМ-сигнал может быть сформирован при помощи второго таймера. То есть всего на трех выводах может быть сгенерирован аппаратный ШИМ. Это выводы МК PB1, PB2 и PB3. Именно этим обусловлены ограничения функции Arduino analogWrite().
В этом разделе настроим еще два канала и будем управлять остальными светодиодами.
Для этого потребуется только дополнительно выставить бит COM1B1 и начать управлять регистром OCR1B. На эти действия будет реагировать оранжевый светодиод, подключенный к выводу PB2.
Для использования третьего канала придется вставить дополнительную функцию timer2_init() для запуска второго таймера.
#define F_CPU 16000000UL //16MHz #include <avr/io.h> #include <util/delay.h> #define BLUE_PB 1 #define BLUE_PWM OCR1A #define ORANGE_PB 2 #define ORANGE_PWM OCR1B #define GREEN_PB 3 #define GREEN_PWM OCR2 void pin_init(void) { DDRB |= (1<<BLUE_PB) | (1<<ORANGE_PB) | (1<<GREEN_PB); PORTB &= ~((1<<BLUE_PB) | (1<<ORANGE_PB) | (1<<GREEN_PB)); } void timer1_init(void) { TCCR1A |= (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS10); TCNT1 = 0x00; ICR1 = 0xFF; OCR1A = 0x00; OCR1B = 0x00; } void timer2_init(void) { TCCR2 |= (1 << COM21) | (1 << WGM21) | (1 << WGM20) | (1 << CS20); TCNT2 = 0x00; OCR2 = 0x00; } int main(void) { pin_init(); timer1_init(); timer2_init(); while(1) { for (int i=0; i<256; i++) { BLUE_PWM = i; _delay_ms(10); } BLUE_PWM = 0; for (int i=0; i<256; i++) { ORANGE_PWM = i; _delay_ms(10); } ORANGE_PWM = 0; for (int i=0; i<256; i++) { GREEN_PWM = i; _delay_ms(10); } GREEN_PWM = 0; } }
Второй таймер имеет гораздо меньше функций и настраивать его проще. Бит COM21 выводит сигнал на ногу. Биты WGM21 и WGM20 переводят таймер в режим формирования ШИМ. CS20 задает делатель тактового сигнала, равный нулю. Как видите, тут изменять частоту получится только при помощи делителя.
Вот видео работы этого примера:
Индивидуальные задания
- Измените третий пример так, чтобы после плавного увеличения яркости, светодиоды гасли плавно.
- Подберите соотношение яркости светодиодов так, чтобы добиться белого свечения светодиода.
Остальные статьи цикла можно найти здесь.
Мы будем очень рады, если вы поддержите наш ресурс и посетите магазин наших товаров shop.customelectronics.ru.
Метки: AVR, PWM, настройка таймера, обучение, программирование, ШИМ Просмотров: 37807