Аппаратная ШИМ в микроконтроллере ATmega8

В этой заметке я постараюсь немного затронуть тему аппаратной ШИМ (широтно-импульсной модуляции, англ. PWM - Pulse-width modulation) в микроконтроллерах семейства AVR на примере микроконтроллера ATmega8. Классический ШИМ сигнал представляет собой цифровой сигнал, определенной постоянной частоты.

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

Для демонстрации работы с ШИМ напишем программу, которая будет плавно менять яркость светодиода от нуля до максимума, а потом плавно ее снижать. То есть нам нужно сгенерировать примерно такой ШИМ сигнал (масштаб не соблюден):
ШИМ сигнал для светодиода
Генерировать сигнал будем аппаратными средствами микроконтроллера ATmega8. Можно конечно сгенерировать все и программно, но это не экономично и неудобно, если микроконтроллер должен выполнять что-то еще, кроме генерации ШИМ сигнала. Принципиальная схема макета:

На схеме показана стандартная обвязка ATmega8. Q1 - любой pnp транзистор, способный выдержать ток 500мА, я использовал биполярный pnp транзистор ZXTN19020, так как у него очень малое сопротивление открытого канала коллектор-эмиттер, всего 18 миллиом и при токах 500ма он не будет ощутимо греться. Транзисторный ключ открывается при высоком уровне на PB1 и пропускает через цепочку резистор R4-коллектор-эмиттер-светодиод ток, который по закону Ома равен I=5B/R4. (сопротивлением светодиода в рабочем состоянии и канала коллектор-эмиттер на транзисторе Q1 мы пренебрежем).

Зададимся, что наш светодиод во включенном состоянии будет питаться током 500мА (максимальный ток для светодиода Cree MC-E, используемого мною в этом макете, при последовательном соединении 4-х кристаллов составляет 2.8А), для этого вычислим номинал ограничительного резистора: R4=5В/0,5А=10 Ом. R3 номиналом 1 кОм. Следует не забывать про охлаждение светодиода, иначе очень быстро сгорит. Для небольших мощностей достаточно использовать радиоатор и термопасту. Теперь перейдем к написанию прошивки:

  1. #include <avr/io.h>//библиотека ввода/вывода
  2.  
  3. //Програма задержки
  4. void pause (unsigned int a)
  5. {
  6. unsigned int i;
  7. for (i=a;i>0;i--);
  8. }
  9.  
  10. //Програма инициализации ШИМ
  11. void init_pwm (void)
  12. {
  13. TCCR1A=(1<<COM1A1)|(1<<WGM10); //На выводе OC1A единица, когда OCR1A==TCNT1, восьмибитный ШИМ
  14. TCCR1B=(1<<CS10); //Делитель= /1
  15. OCR1A=0x00; //Начальная яркость нулевая
  16. }
  17.  
  18. //Основная програма
  19. int main(void)
  20. { unsigned char i;
  21.  
  22. DDRB=0x02; //Инициализация PB1 (OC1A) как выход
  23. init_pwm();
  24.  
  25. while (1)
  26. {
  27. for (i=0;i<255;i++) //Плавно повышаем яркость
  28. {OCR1A++; pause(1000);}
  29. for (i=0;i<255;i++) //Плавно понижаем яркость
  30. {OCR1A--; pause(1000);}
  31. } return 1;
  32. }

Рассмотрим приведенный выше код.
Сначала мы инициализируем ШИМ, после чего в вечном цикле постепенно увеличиваем-уменьшаем яркость светодиода.

Рассмотрим подробнее инициализацию ШИМ.
Будем использовать так называемый Phase correct PWM на таймере 1. Счетчик TCNT1 постепенно увеличивается (согласно установленному делителю), когда его содержимое становиться равным содержимому OCR1A, то на выводе OC1A в зависимости от битов COM1A0, COM1A1 (в регистре TCCR1A), устанавливается нолик или единица. После счетчик достигает 0xFF (в зависимости от битности ШИМ) и начинает уменьшатся. Как только TCNT1 снова сравняется с OCR1A, на пине OC1A уровень меняется на противоположный. После чего счетчик достигает 0х00 и все повторяется снова.
В нашем случае COM1A1=1,COM1A0=0. И это означает, что при начале счета, на выводе OC1A устанавливается высокий уровень. При достижении счетчиком значения OCR1A при возрастании уровень на OC1A становится нулевым. А при достижении счетчиком значения OCR1A при убывании, уровень на OC1A становиться высоким, и т.д. Проще говоря: «чем больше значение OCR1A – тем больше заполнение сигнала».
COM1A биты
Битность и режим ШИМ задается с помощью битов WGM13-WGM10 (биты WGM13,WGM12 находятся в регистре TCCR1B, а биты WGM10, WGM11 в регистре TCCR1A).

Для нашего 8-ми битного Phase correct PWM требуется, чтобы WGM10=1.
WGM биты

фото макета
Видео работы программы:

Скачать прошивку для ATmega8, как проект для AVR Studio 4

Пример реализации аппаратной ШИМ в несколько каналов на микроконтроллере ATmega8

Вывод OC1A (на котором должен

Вывод OC1A (на котором должен генерится сигнал ШИМ) в mega128 - это PB5, да и таймер1 не совпадает с тем, который имеется в mega8 (но это не так критично).

Вот код для mega128. Зайдействуется таймер0 и соответственно сигнал генерится на OC0=PB4.

  1. #include <avr/io.h>
  2.  
  3. void pause (unsigned int a)
  4. { unsigned int i;
  5. for (i=a;i>0;i--);
  6. }
  7.  
  8. void init_pwm (void)
  9. {
  10. TCCR0=(1<<COM01)|(1<<WGM00); //Turn 1, if OCR0==TCNT0, 8-bit PWM, phase correct
  11. TCCR0|=(1<<CS00); //Psescallers= /1
  12. OCR0=0x00;
  13. }
  14.  
  15. int main(void)
  16. { unsigned char i;
  17. DDRB=_BV(4); //init PB4 as output
  18. init_pwm();
  19.  
  20. while (1)
  21. {
  22. for (i=0;i<255;i++) //More brightness
  23. {OCR0++;
  24. pause(1000);
  25. }
  26. for (i=0;i<255;i++) //Less brightness
  27. {OCR0--;
  28. pause(1000);
  29. }
  30. }
  31. return 1;
  32. }

В свойствах проекта project->Configuration Options не забудь поставить оптимизацию O0, иначе не будет корректно работать.

Как что-то получиться с сервами - дай знать, мне интересно посмотреть ;)

задать нужную частоту

не как не могу понять как правильно задать частоту шим. и как задать время импульса.
для управления сервой частота должна быть 50 герц. а длительность импульса 1-2 мс.
11,12 строка кода я вообще не чего не понимаю.
сам справиться не смог, объясните пожалуйста по подробнее.

Смотри, давай для простоты

Смотри, давай для простоты понимания выберем Fast PWM Mode для ШИМ. Т.е. таймер наростает от 0 до 255, при совпадении регистров TCNT0 и OCR0 сигнал на выводе OC0 сбрасывается в ноль. Как только таймер TCNT0 переполнился - значение TCNT0 становится равно нулю, а на выходе OC0 появляется логическая единица. Вот картинка (наш случай - нижний график):
fast pwm mode
Подробнее читай в даташите mega128 на странице 100.
Частота ШИМ при таком режиме составляет Fpwm=Fclk/(256*N), N - делитель таймера. Если использовать внешний кварц на 3.2768МГц, и делитель на таймере0 256, то получаем Fpwm=50Hz ровно.

Теперь про длительность импульса, длительность задается в регистре OCR0. Для длительности 1мс нужно OCR0=0x0d; 2мс - OCR0=0х19. Формула для длительности в секундах OCR0=time*256*Fpwm. Но не забывай, что регистр OCR0 - 8-ми разрядный, т.е. его максимальное значение 0xff, так что длительность импульса более 20мс задать не получится :)

Вот инициализация ШИМ для частоты 50Гц, Fast PWM Mode:

  1. void init_pwm (void)
  2. {
  3. TCCR0=(1<<COM01)|(1<<WGM00)|(1<<WGM01); //Turn 1, if OCR0==TCNT0, 8-bit fastPWM
  4. TCCR0|=(1<<CS02)|(1<<CS01); //Psescallers= /256
  5.  
  6. OCR0=0x00;
  7. }

В строчке 3 происходит включение fast-pwm mode
В строчке 4 задаем делитель.

PS поправил комментарии к прошлому коду.

спасибо за подробные ответы,

спасибо за подробные ответы, но самостоятельно рассчитать делитель пока не как не могу.
у меня стоит кварц 7.3728 мгц, есть еще кварц на 8 мгц.
можно ли

  1. void init_pwm (void)

разложить на отдельные команды?, типа -
"
  1. TCCR1A=0xA0;
  2. TCCR1B=0x12;
  3. TCNT1H=0x00;
  4. TCNT1L=0x00;
  5. ICR1H=(char)TOP>>8;
  6. ICR1L=(char)TOP
  7. OCR1B=0x00;
  8. OCR1A=0x00;

"
что бы было понятнее. (только командами для mega128)
читал мануал, но почти не чего не понял :(
напишите пожалуйста инициализацию pwm для первого и второго кварца, буду разбираться по примеру.

ШИМ

  1. TCCR0=(1<<COM01);
  2. //При совпадении TNCT0 c OCR0 сбрасывать OC0
  3. // (стр. 158, таблица 66) даташита mega128
  4. TCCR0|=(1<<WGM00)|(1<<WGN01);
  5. //Форма генерации сигнала Fast PWM mode, (стр 157, таблица 64)
  6. TCCR0|=(1<<CS02)|(1<<CS01);
  7. //Устанавливаем делитель как /256, (стр. 158, таблица 68)
  8.  
  9. OCR0=0x00;
  10. //Устанавливаем первоначальное заполнение ШИМ

настройки шим

с шим частично разобрался, а именно как сформировать шим. если что поправьте.
регистр TCCR0 - это 8 бит, каждый из которых задает параметры шим.
биты WGM - задают тип шим.
есть 4 типа-
Fast PWM
Phase Correct PWM
Clear Timer On Compare (CTC)
normal (что за тип -описания не нашел)
подробно нашел здесь-
easyelectronics.ru/avr-uchebnyj-kurs-ispolzovanie-shim.html
биты COM "ри совпадении TNCT0 c OCR0 сбрасывать OC0"
биты CS - устанавливается делитель 8,64,256,1024

я так понимаю можно и так написать TCCR0 &= 0b01101110; ?

так вот. надо искать кварц 3.2768МГц. т.к. с любым другим не получить 50гц ?

в принципе, сервомашинка нормально работает и при 40-60гц. но это наверно не правильно. как хорошо если есть друг у которого можно взять хороший осциллограф :)
хотел еще спросить, выход шима будет только на ноге PB4 или его можно на др ногу назначить ?

"регистр TCCR0 - это 8 бит,

"регистр TCCR0 - это 8 бит, каждый из которых задает параметры шим."

Нет, регистр TCCR0 задает параметры ТАЙМЕРА0, а ТАЙМЕР0 уже можно настроить так, чтобы он генерил ШИМ.

"я так понимаю можно и так написать TCCR0 &= 0b01101110; ? "

Можно, но "нечитабельно".

надо искать кварц 3.2768МГц. т.к. с любым другим не получить 50гц ?

Можно получить, для расчета частоты кварца и делителя юзай выше приведенную формулу Fpwm=Fclk/(256*N) (справедлива для Fast PWM mode) и калькулятор.

"выход шима будет только на ноге PB4 или его можно на др ногу назначить ?"
Нет. Если использовать ТАЙМЕР0 для генерации ШИМа, то выход только на PB4 в mega128

Вечером гляну в чем трабла с

Вечером гляну в чем трабла с мегой128, и напишу решение. Там наверное что-то с таймерами, или OC1A висит не на PB1

шим

Всем здравствуйте
очень интересная статья и сайт спасибо
и у меня есть вопрос по поводу этой оптимизации "В свойствах проекта project->Configuration Options не забудь поставить оптимизацию O0, иначе не будет корректно работать".
А почему она без нее не корректно работает? и за что она отвечает? и для чего она вообще?
Просто я все вроде прошил как бы должно работать а светодиод тупо горит и все а когда поставил O0 то все заработало

Компилятор при транслировании

Компилятор при транслировании кода и бинарники может применить такую фишку как оптимизация для ускорения выполнения кода и уменьшения его объема. При этом задержки вида pause(1000); не отрабатываются корректно и у тебя светодиод тупо горел.

На днях набросаю небольшой гайд по оптимизациям, там будет подробней.

Спасибо за разъяснения теперь

Спасибо за разъяснения теперь понятно

Это я так понимаю от нажатия кнопки происходит регулирование?

Это я так понимаю от нажатия кнопки происходит регулирование? Оте 10-80% от 5В. Где именно указано какова доля\часть импульсов?
А как сделать чтобы яркость сама изменялась от какого-то параметра? например есть диапазон параметра Н, который изменяется от 1 до 10. И в зависимости от значения Н яркость должна варьироваться сама, без нажатий кнопок? Спасибо.

Нет, кнопка не

Нет, кнопка не задействована.
Изменение происходит от заданных параметров ШИМ, параметры смотрив коде.

А как сделать чтобы яркость

А как сделать чтобы яркость сама изменялась от какого-то параметра? например есть диапазон параметра Н, который изменяется от 1 до 10. И в зависимости от значения Н яркость должна варьироваться сама, без нажатий кнопок? Спасибо.

Ну так тут так и сделано,

Ну так тут так и сделано, параметр яркости то переменная i, и диапазон значений от 0 до 255
Если будешь делать что-то, учти что включение самого светодиода произойдет не с значения 1 или 10 а я думаю эдак с 100 а то и больше. Связано с тем что для светодиода надо чтобы ток пошел достаточный для его открытия.

"параметр яркости то

"параметр яркости то переменная i,"

Нифига, за скважность отвечает регистр OCR1A. У меня загорались диоды от значений порядка 0х10.