mikroPascal for AVR. Урок 7. Аппаратный ШИМ

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

Итак, что же такое ШИМ? На английском это сокращение звучит иначе — PWM, что расшифровывается как pulse-width modulation (широтно-импульсная модуляция). Вся суть ШИМа в изменении скважности импульсов, что позволяет изменять среднее напряжение на нагрузке. Всего различают три вида ШИМ: аналоговый, двоичный (наш вариант) и троичный.


Рис 1.1. Пример ШИМ.

В микроконтроллерах AVR ШИМ организован аппаратно. Забегая вперед, скажу, что при желании его можно сделать и программно, использовав прерывания. Но это тема следующего урока.

Подведем промежуточные итоги: мы знаем что в МК фирмы Atmel есть аппаратный ШИМ (зачастую не один, а несколько), а так же знаем, что собственно он собой представляет. Но как им управлять?

Оказывается все довольно просто. Для управления ШИМом в mikroPascal есть специальные процедуры, с помощью которых можно инициализировать ШИМ, запустить/остановить и задать длительность импульсов. Далее, предлагаю вашему вниманию простой пример, в котором мы просто запустим ШИМ с заданной длительностью импульса и частотой.

 program PWM_mega8_1_2_3ch_def;                                                                          //Самый простой метод работы с ШИМом в mikroPascal.                                                                                                         //Достаточно просто инициализировать модуль ШИМ и просто                                                                                                         //забыть про него.  procedure Init();                                                                                       //Для простоты вынес инициализацию в отдельную процедуру. begin                                                                                                   //В этом примере это не существенно, но далее тк будет проще.      DDRB := 0xFF;                                                                                      //Для правильной работы ШИМа необходимо настроить порт "на выход".      PWM16bit_Init(_PWM16_FAST_MODE_8BIT, _PWM16_PRESCALER_16bit_8, _PWM16_NON_INVERTED, 200, _TIMER1); //Инициализация 16 битного ШИМа (OCR1A, OCR1B).      PWM2_Init(_PWM2_FAST_MODE, _PWM2_PRESCALER_8, _PWM2_NON_INVERTED, 200);                            //Тоже самое, но для 8-ми битного модуля (OCR2). end;  begin      Init();      while TRUE do begin      end; end.

Думаю вас заинтересовали функции «PWM16bit_Init» и «PWM2_Init». Это функции из стандартной библиотеки mikroPascal, которая так и называется — PWM Library. Советую вам все-таки почитать даташит для ATmega8 (для демонстрации примеров будет использоваться именно этот доступный МК). И как из последнего следует, в ATmega8 есть три канала ШИМ: один 8-ми битный и два 16-ти битных. Первый работает за счет таймера/счетчика 2, а второй — таймера/счетчика 1. К сожалению, для T0 такой опции нет.

Теперь немного о самих функциях инициализации.

 procedure PWM16bit_Init(тип генерации : byte; предделитель : byte; инверсия : byte; длительность импульса : word; используемый таймер : byte);

Для PWM2 отличий мало:

 procedure PWM2_Init(тип генерации : byte; предделитель : byte; инверсия : byte; длительность импульса : word);

Отсутствует только выбор таймера. Что касается выбора параметров, то для этого нужно открыть help. Там все неплохо расписано, хоть и на английском (в статье я это приводить не буду, уж больно много места это займет).

Теперь можно посмотреть, что получилось в итоге. Для этого запускаем Proteus, выбираем МК.. ну и так далее. Вот результат:

Но согласитесь, довольно редко требуется чтобы скважность ШИМ была неизменной. Ведь практически всегда нужно изменять яркость светодиода и т.д. во время выполнения программы. Так что немного дополним первый пример.

 program PWM_mega8_1_2_3ch_def_adv;  procedure Init();                                                                                       //Инициализация ШИМа в этом примере ничем не отличается от прошлого примера. begin      DDRB := 0xFF;      PWM16bit_Init(_PWM16_FAST_MODE_8BIT, _PWM16_PRESCALER_16bit_8, _PWM16_NON_INVERTED, 200, _TIMER1);      PWM2_Init(_PWM2_FAST_MODE, _PWM2_PRESCALER_8, _PWM2_NON_INVERTED, 200); end;  begin      Init();      delay_ms(1000);                                                                               //Выставляем задержку 1с      PWM2_Set_Duty(128);                                                                           //Изменяем скважность импульсов для второго модуля ШИМ.      delay_ms(1000);                                                                               //Еще 1с задержки. Далее изменяем скважность уже для 1-го модуля ШИМ.      PWM16bit_Change_Duty(128, _TIMER1_CH_A);                                                      //Очень интересный момент! Подробнее смотрите в видео!      while TRUE do begin       end; end.

Теперь, как вы видите, через 1с после запуска программы изменится длительность импульсов на выходе OC2, а еще через 1с — на выходе OC1A.

Но можно еще немного усложнить программу. Например, добавим выключение ШИМа через 3с после старта.

 delay_ms(1000);                                                                               //Допишем еще три строчки, которые будут отключать ШИМ (оба канала), PWM2_Stop;                                                                                    //через 1с. PWM16bit_Stop(_TIMER1_CH_A);

Этот кусочек кода вставьте перед бесконечным циклом. И вот результат:

Использование библиотек, конечно удобно. Но в любом случае, необходимо уметь работать с регистрами напрямую, что бы не зависеть от других разработчиков (которые как раз и пишут эти библиотеки).

Так как мы используем в качестве «наглядного пособия» микроконтроллер ATmega8, то у ШИМ тут относятся следующие регистры: OCR1AL, OCR1AH, OCR1BL, OCR1BH, OCR2, TCCR1A, TCCR1B, TCCR2. Регистры OCRx служат для записи значения, до которого будет считать таймер, и соответственно это определяют длительность импульса на выходе. Регистры TCCRx служат для настройки режимов ШИМ, настройки таймеров и их предделителей.

В прикрепленном файле TCCR.rar (скачать в конце статьи) привожу «расшифровку» вышеупомянутых регистров

Зададим такие же параметры ШИМ как и в первом примере.

Начнем с TCCR2. Для этого нам нужно:

  • Установить режим Fast PWM. Это можно сделать, выставив биты 3 и 6 (WGM21 и WGM20).
  • Кроме того, не забываем про бит 5 (COM21), для не инвертированного режима.
  • И устанавливаем предделитель на 8 — это бит 1 (CS21).

Должно получиться так: 01101010 (нулевой бит справа).

Далее конфигурируем регистры TCCR1A и TCCR1B.

  • В TCCR1A нужно записать такую последовательность: 10100001;
  • А в TCCR1B нужно записать: 00001010.
  • Что я предлагаю вам туда записать, посмотрите в даташите или картинках выше.

Ниже представлен полный код:

 program PWM_1_2_3ch_reg_adv;                                      //В этом примере рассмотрим                                                                   //"ручную" настройку ШИМа для                                                                   //популярного микроконтроллера ATmega8. var i: byte;  procedure Init(); begin      DDRB := 0xFF;                                                //Привычно настраиваем порт на выход.      OCR2 := 200;                                                 //Но теперь, уже простого заглядывания в "магический" help не будет.      OCR1AH := 0x0;                                               //Нужно брать в руки даташит на тот контроллер, под который вы пишите программу и      OCR1AL := 200;                                               //выискивать раздел с описанием регистров для таймеров.      OCR1BH := 0x0;                                               //В данном случае, нужно было найти информацию по регистрам TCCR2, TCCR1A и TCCR1B.      OCR1BL := 200;                                               //В регистры OCRx записывается число, с которым потом будет сравниваться значение      TCCR2 := %01101010;                                          //таймера, и которое, соответственно будет определять длительность (заполнение) импульсов.      TCCR1A := %10100001;      TCCR1B := %00001010; end;  begin      Init();      while TRUE do begin       end; end.

Как уже упоминалось, для изменения длительности импульсов на выходе достаточно просто записать другое число в регистры OCRx. Не забывайте, регистры OCR1AH и OCR1AL представляют собой «половинки» одного 16-ти битного регистра. Можно сразу «закрепить» знания, дописав немного кода:

 var i: byte; .... for i := 0 to 255 do begin                             //Просто сидеть и наблюдать за неизменной длительностью импульсов     OCR2 := i;                                         //довольно скучно, по этому мы немного внесем разнообразия в этот проектик.     OCR1AL := i;     OCR1BL := i;                                       //Просто возьмем и сделаем так, чтобы сначала длительность импульсов     delay_ms(10);                                      //увеличивалась, а потом уменьшалась. end;                                                   //Это крайне легко сделать с помощью цикла! for i := 255 downto 0 do begin                         //Такую конструкцию не очень часто встретишь, но слово "downto" обозначает декремент.     OCR2 := i;     OCR1AL := i;     OCR1BL := i; delay_ms(10); end;

Объявите переменную i (byte) и вставьте код в бесконечный цикл. После запуска симуляции в Proteus, можно наблюдать такую картину:

На этой ноте я заканчиваю. Надеюсь, что вы нашли в этой статье что-то полезное для себя. Спасибо за внимание!