Этот урок — продолжение серии переводов на русский язык учебных уроков по работе с микроконтроллерами от Texas Instruments серии msp430. Начало можно найти в сообществе we.easyelectronics.ru.

Так как я ранее сделал довольно объёмный курс из многих частей, я думал, что напишу быстро и в этот раз. На этом уроке будем иметь дело с чрезвычайно универсальным режимом работы модуля таймера — режимом широтно-импульсной модуляции.

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

Основы сигнала ШИМ

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

ШИМ-сигнал является естественным продолжением сигнала прямоугольной формы, — так же, как сигналы, которые мы уже использовали в тактировании. Для образования меандра, сигнал включается и выключается через определённые промежутки времени. Когда вы смотрите на график меандра, вы видите, что сигнал имеет высокий уровень и через определённый промежуток времени — низкий.

Соотношение времени, когда установлен высокий уровень — длительность импульса — к периоду, или интервалу между импульсами, называется рабочим циклом. Для сигнала прямоугольной формы длительность импульса равна половине длины одного цикла, поэтому он имеет 50% рабочий цикл. В ШИМ-сигнале просто регулируется рабочий цикл при сохранении той же частоты импульсов.

Мы организуем ШИМ-сигнал с использованием двух прерываний по таймеру. При необходимости, можно использовать одно и то же прерывание и отрегулировать момент возникновения прерывания в программе, но в модуле таймера в MSP430 имеется по два прерывания, которые мы и будем использовать! Одно прерывание будет устанавливать общую частоту нашего сигнала, в то время как второе будет регулировать рабочий цикл. Идея проста: в режиме сравнения таймера, при непрерывном режиме счёта, модуль Timer_A имеет три прерывания. Всякий раз, когда таймер переполняется, а это — после 65 536 (или 2 16) тактов, — происходит прерывание, которое мы будем использовать, чтобы установить выход нашей ШИМ. Используя регистр CCR0, мы можем установить рабочий цикл путем сброса выхода после определенного количества тактов. Если регистр CCR0 сбрасывается, когда регистр счетчика TAR достигает 32 768 (половина из 65 536), получится как раз 50-процентное заполнение рабочего цикла; выход ШИМ устанавливается в первую половину периода, и сбрасывается в следующую половину. Если записать в регистр CCR0 6554, у нас будет около 10% рабочего цикла. По минимуму,  модуль Timer_A  имеет два регистра захвата-сравнения, используя этот метод, мы можем получить по крайней мере два ШИМ-сигнала от любого устройства под управлением MSP430. К сожалению, часто на практике мы теряем однин из них.

В качестве первого примера рассмотрим следующий:

LEDPWM_G2231.c


#include

#define LED1 BIT5 // LED1 на P1.5
#define LED2 BIT6 // LED2 на P1.6

/* определение функций*/
void TA_init(void);

void main(void) {
int dir0=1, dir1=1; // Направление счёта для изменения цикла ШИМ.
// +1 - возрастание,-1 убывание

WDTCTL = WDTPW + WDTHOLD;

P1OUT = 0;
P1DIR = LED1 + LED2;
P1SEL = LED1 + LED2; // установка режима выходов таймера TA0.x
TA_init(); // Установка Timer_A в качестве ШИМ

// BCSCTL1 = 0x8d; // RSELx = 13 is 7.8 MHz.
// DCOCTL = 0x60; //из документации стандартное значение DCOx = 3,

__enable_interrupt();

while(1) {
__delay_cycles(327680); // Даём поработать ШИМ 5 циклов перед началом изменения

/* Значения 771 и 1285 выбраны не случайно: они являются общими
* множителями 2^16 - 1. эти значения позволяют регистру TACCRx
* достигать как 0, так и 65535, ШИМ при этом работает во всём диапазоне счёта таймера - 2^16 бит
* Если вы хотите изменить эти значения, то используйте только 3, 5, 17,
* и 257. Так же нужно поменять начальные значения в функции TA_init().
*/

TACCR0 += dir0*771; // изменение цикла с 0 по 1.18%
TACCR1 += dir1*1285; // изменение цикла с 1 по 1.96%
if (TACCR0 == 65535)
dir0=-1; // уменьшение
else if (TACCR0 == 771)
dir0=1; // увеличение
if (TACCR1 == 65535)
dir1=-1; // уменьшение
else if (TACCR1 == 1285)
dir1=1; // увеличение
}
} // main

void TA_init(void) {
TACCR0 = 771; // LED1 начинается с 771/2^16 = 1.18%
TACCR1 = 1285; // LED2 начинается с1285/2^16 = 1.96%
TACTL = TASSEL_2 + ID_0 + MC_2 + TAIE; // SMCLK, делитель 1, непрерывный режим счёта
// Eразрешаем прерывания
TACCTL0 = OUTMOD_5; // сброс при TACCR0
TACCTL1 = OUTMOD_5; // сброс при TACCR1
}

#pragma vector = TIMERA1_VECTOR
__interrupt void TA0_Rollover_ISR(void) {
switch(TAIV) {
case(0xA):
TACCTL0 = OUTMOD_0 + OUT; // установка высокого уровня на выходе TA0.0
TACCTL1 = OUTMOD_0 + OUT; // установка высокого уровня на выходе TA0.1
TACCTL0 = OUTMOD_5; // возврат к сбросу при TACCR0
TACCTL1 = OUTMOD_5; // возврат к сбросу при TACCR1
break;
default:
break;
}
}

Если запустить эту программу на LaunchPad, вы увидите как светодиоды загораются, начиная с тусклого свечения, постепенно яркость нарастает, потом наоборот, начинает снижаться от максимальной яркости, затем цикл повторяется. ШИМ-сигнал отображается с помощью светодиодов, как увеличивается рабочий цикл от 0 до 100%, а затем уменьшается обратно до 0, и повторяется. Возможно, вы также заметите не очень желательную особенность: переходы происходят не особенно плавно. В самом деле, вы можете обнаружить, что светодиоды мигают почти всё время. Как только вы запустите код как есть, попробуйте раскомментировать строки,  устанавливающие внутренний тактовый генератор DCO на более высокую частоту и отметьте, какое  влияние она оказывает на ваше восприятие свечения диодов.

Читателю на заметку: Почему нельзя в коде сделать так, чтобы уровень ШИМ-сигнала уменьшался до 0? Попробуйте поменять для сравнения направление счёта вверх, на  else if (TACCR0 == 0) и посмотреть, что происходит. Можете ли вы объяснить это?

Улучшаем работу ШИМ

Теперь, когда вы видели ШИМ в действии, рассмотрим выходную частоту этой схемы. Если мы позволим работу счётчика таймера до 2 16, выход ШИМ-сигнала будет установлен на частоте F / 2 16, где F — частота счёта Timer_A. По умолчанию частота тактового генератора DCO около 1,1 МГц, частота между импульсами составляет всего около 17 Гц — это легко увидеть человеческим глазом! Если использовать максимальную частоту 16 МГц, то максимальная частота ШИМ-сигнала при использовании этого метода будет 244 Гц. Для некоторых приложений, это может быть достаточно быстро, но для многих других, вы можете обнаружить импульсы частотой около 1 кГц или выше! Так как у нас нет никакого способа, чтобы увеличить частоту Timer_A , нам нужно иметь счетчик таймера в нижних значениях; мы можем сделать это с помощью режима счёта ВВЕРХ, а не в непрерывном режиме, за счёт регистра CCR0, работающего в этом качестве. Хотя это означает, что мы потеряем один выход, мы получим более гибкое и быстрое ШИМ.

Счёт к меньшему значению также означает, что мы теряем в разрешении. Если установить регистр CCR0 в 10, например, есть только 11 возможных рабочих циклов: регистр CCR1 может быть установлен только в целое значение, меньшее (или равное) 10. Это дает 11 рабочих циклов от 0% до 100%, разбитых на 10% интервалы. Установив регистр CCR0 на 100, можно выбирать 1-процентные промежутки времени; значение 1000 позволит уже 0,1-процентные интервалы, и так далее. Хотя возможно, было бы хорошо иметь такое высокое разрешение, используя все 16 битов регистра Timer_A, в большинство приложений это не используется. Там, где действительно требуется высокое выходное разрешение, приходится идти на компромисс при работе на более низкой частоте.

Попробуйте код из второго примера: LEDPWM2_G2231.c,и обратите внимание на различия в настройках таймера.

#include

#define LED2 BIT6 // LED2 is on P1.6
#define PWMTop 1000 // Установка частоты ШИМ: DCO/PWMTop

/* Function Definitions */
void TA_init(void);

void main(void) {
int dir1=1; // Направление счёта для изменения цикла ШИМ.
// +1 - возрастание,-1 убывание

WDTCTL = WDTPW + WDTHOLD;

P1OUT = 0;
P1DIR = LED2;
P1SEL = LED2; // установка режима выходов таймера TA0.x
TA_init(); // Установка Timer_A в качестве ШИМ

while(1) {
__delay_cycles(PWMTop*5); // Даём поработать ШИМ 5 циклов
TACCR1 += dir1; // Меняем цикл ШИМ  с 1 на 1%
if (TACCR1 == PWMTop)
dir1=-1; // уменьшение
else if (TACCR1 == 0)
dir1=1; // увеличение
}
} // main

void TA_init(void) {
TACCR0 = PWMTop; // регистр TACCR0 управляет частотой ШИМ

TACCR1 = 1; // LED2 начинает с 1% заполнения для PWMTop = 1000
TACTL = TASSEL_2 + ID_0 + MC_1; // SMCLK, делитель 1, режим счёта вверх
// нет нужды в прерываниях!
TACCTL1 = OUTMOD_7; // сброс/установка: установка при TACCR0, сброс при TACCR1
}

Раньше мы должны были вручную установить ШИМ-выходы на прерывание при переполнении. MSP430 управляет выходами таймера, которые соответствуют регистрам TACCRx и TACCR0, поэтому установка выходов ШИМ теперь происходит автоматически. Прерывания не нужны! Эта особенность намного уменьшает код программы, а также экономит много памяти. Конечно, если необходимы другие функции в сочетании с ШИМ-сигналом, то прерывание все ещё может  быть вызвано для обработки. В общем, в этом нет необходимости.

Можно также заметить, что одной из особенностей схемы было то, что все импульсы были выровнены по левому краю, так как каждый выход ШИМ устанавливался в тот же момент (когда таймер переполнялся до 0). В некоторых случаях бывает желательно иметь импульсы, выровненные по центру, так чтобы центр импульсов происходил на одно и тот же время. Этот режим осуществляется с помощью таймера, работающего в режиме счёта вверх-вниз, и установки выхода таймера для срабатывания в соответствии с прерыванием CCRx. Если выход ШИМ начинается с низкого уровня (который может быть обеспечен при сбросе регистра TAR в 0, при необходимости), то высокий уровень устанавливается, когда счётчик достигает значения CCRx, потом снова низкого, когда это же значение достигается при счёте вниз. Этот метод центрирует импульс в высшей точке, позволяя синхронизировать несколько сигналов ШИМ, работающих одновременно. Поскольку этот режим требует установки значения регистра TACCR0, до которого будет считать таймер, использование выхода TA0.0 в качестве ШИМ становится невозможно.

На заметку: Обратили ли вы внимание, что на этот раз ШИМ проходит весь путь до 0%? Если вы не смогли найти ответ на первом упражнении, подумайте, в чём отличие этого кода. Какой порядок действий происходит в методе с прерываниями? В каком порядке они происходят в методе работы сброс/установка?

Почему ШИМ?

Во втором примере код имеет такую ​​же простую функцию как и прежде: увеличивается рабочий цикл ШИМ-сигнала от 0% до 100%, затем уменьшается до 0%, и повторяется. Когда длительность импульса мала, в среднем светодиод излучает меньшее количество света. 10% рабочего цикла означает, что светодиод горит только 10% времени, чем обычно, и поэтому яркость воспринимается как примерно 10% от полной яркости свечения. Увеличивая затем уменьшая рабочий цикл, вы создаете классный, эффект-мерцание от светодиода, который сегодня используется во многих популярных электронных устройствах. При более практическом применении, метод может быть использован в качестве «регулятора» для светодиодного источника света, при необходимости.

Поскольку поведение светодиодов почти цифровое (они либо включены, либо выключены, без вариантов), то почему же мы воспринимаем свет как регулируемый, а не наблюдаем мигание, как они на самом деле работают? Глаз человека имеет относительно высокую инерционность, и медленно реагирует на изменения света — пока диод мигает быстрее, чем человеческий глаз может воспринимать мигание, мы не обнаружим быстрые переходы, и только воспринимаем суммарную интенсивность света в наших глазах. Это тот же самый трюк, который делает возможным фильмы — видео с частотой кадров 25 кадров в секунду или более, кажется гладким и переключение незаметно для человеческого глаза. Но это не значит, что в идеале вы должны сделать мерцание светодиодов как можно быстрее, а так как светодиоды не являются полностью цифровыми, вероятно, можно ожидать, что, имея частоту ШИМ установленную слишком высоко, вы можете искусственно уменьшить яркость даже больше, так как Светодиод сам по себе (или по крайней мере с сопротивлением и емкостью схемы подключения сигнала к светодиоду) не может реагировать достаточно быстро. На практике, 1-10 кГц обычно достаточно для ШИМ, хотя в некоторых приложения выгоднее использовать более высокие частоты. Эти ситуации требуют тщательного проектирования, чтобы компенсировать сопротивление и емкость цепи, не замедляя переходы слишком сильно.

Готовим ШИМ к работе

На самом деле, это дополнительное сопротивление и ёмкость иногда дают преимущество. Например, намеренно установив достаточную RC-цепь на выходе ШИМ, можно предотвратить значительное снижение уровня сигнала. Каждый импульс ШИМ заряжает конденсатор лишь частично, и в каждый мертвый промежуток времени конденсатор разряжается медленно. Эффект сохраняет относительно постоянное напряжение между импульсами. Точно подобрав параметры RC-цепочки, можно успешно поддерживать напряжение, пропорциональное рабочему циклу: Вы успешно создали цифро-аналоговый преобразователь!

Это всё для урока. Надеемся, что это дало ответы на некоторые ваши вопросы о ШИМ, и некоторые идеи о том, как его использовать.

Упражнение для самостоятельной работы: Попробуйте сделать 10-разрядный ЦАП. Если хотите, задайте такую комбинацию R и C, которая давала бы постоянную времени больше периода между импульсами; так, если вы используете ШИМ частотой 1 кГц (например, по умолчанию внутренний тактовый генератор, или калиброванный на 1 МГц, раз уж пошла такая пьянка, и установив TACCR0 до 1000) сделайте значение RC > 0,001. Напишите свой ​​код таким образом, чтобы позволить погасить несколько различных рабочих циклов, в цикле или путем нажатия кнопки, по команде через UART, или автоматически с длинной задержкой между изменениями. Измерьте напряжение на выходе при помощи мультиметра; вывод должен быть близким к рабочему циклу умноженному на напряжение питания вашего Launchpad. (Если вы работаете от USB, то это 3,3 V.) Если у вас есть осциллограф, посмотрите на чистоту напряжения постоянного тока. Какие пульсации у вас получились? Попробуйте увеличить или уменьшить значение RC-постоянной. Какое влияние это оказывает на выход?

файлы для статьи:
LEDPWM_G2231.c
LEDPWM2_G2231.c

источник