Режим ШИМ (Широтно-Импульсная Модуляция).
Попробуем последовательно разобраться с таймерами, так как всё сразу понять проблематично. Начнём с вопроса - что такое ШИМ? ШИМ характерен тем, что частота сигнала постоянна, а коэффициент заполнения меняется. Не правда ли страшно звучит?
Итак, что такое частота? Это характеристика, которая показывает, как часто сигнал переходит из состояния "0", в состояние "1" за секунду (это для цифры, аналоговики молчать). Берём сферического коня в вакууме (так как сигнал мы ещё не получили, и осциллограф некуда подключать) и представляем, как предыдущая фраза у нас нарисуется на осциллографе:
Мы договорились, что период - это время от одной "1" до следующей, ещё их называют передним фронтом, а переход из "1"->"0" задним фронтом или спадом. Период обозначают буквой T, и равен он T = 1 / f, где f - частота сигнала. Зададимся получить частоту 1000 Герц. Тогда по формуле получаем период T = 1 / f = 1 / 1000 = 0.001 сек или 1 миллисекунду.
Теперь второе страшное слово - заполнение:
На рисунке хорошо видно координатную сетку. И мы видим, что высокий уровень занимает две клетки, низкий уровень тоже две клетки - получается 50% времени у нас состояние "1" и 50% времени состояние "0". Таким образом заполнение у нас 50%.
В этом случае период у нас не изменился, а заполнение стало 1 / 4, т. е. 25%. Нууу, в общем, ничего страшного, теперь посмотрим структуру базового таймера, нас интересует только эта часть:
Что бы не смущала куча стрелок, нарисуем упрощённо:
Основные узлы:
- PSC - предварительный делитель тактовой частоты контроллера. Подаваемая частота зависит от того, к какой шине тактового генератора МК подключен данный таймер;
- ARR - регистр в который пишется, до какого числа будет считать счётчик CNT;
- CNT - счётчик;
- CCRx - регистр, с которым сравнивается значение счётчика CNT. Таких регистров столько, сколько каналов у таймера.
Сигналы:
- CK_PSC - тактовая частота, подаваемая с тактового генератора;
- CK_CNT - тактовая частота, которая вываливается из предделителя. Предназначена для тактирования счётчика CNT;
- OCx_REF - выход канала, на котором будет "1" или "0" в зависимости от того, что нам там насравнивал регистр CCRx.
Как всегда начнём с конца, с регистра CCRx.
Примечание: не забываем, что все отсчёты в МК идут с нуля, поэтому во все регистры пишутся значения на единицу меньше.
Вспомним, что коэффициент заполнения ШИМ мы считали в процентах. Допустим, зададимся диапазоном от 0 до 30, тогда 50% будут соответствовать значению CNT равному 14. Если диапазон от 0 до 999, тогда значение CNT при 25% будет равно 254, что увеличит плавность регулировки ШИМ. Мы же сейчас зададимся значениями 0-99 (удобно когда числа целые). Допустим, хотим получить коэффициент заполнения 25%, значит в регистр CCRx записываем цифру 24 (не забываем 25-1, вычитаем единицу).
Дальше, при запуске нашего таймера, происходит игра в разведчика. Причём эта игра зависит от нескольких параметров. Счётчик CNT у нас умеет считать "вверх", "вниз" и "вверх потом вниз" (последний режим разделяется на несколько подрежимов). Каждый режим задаётся в его настройках. Мы возьмём режим счёта вверх, т. е. счётчик CNT считает от 0 до значения в регистре ARR, потом сбрасывается в ноль.
Во время счёта CCRx внимательно следит за CNT (разведчик). В начале слежки, в зависимости от того, какой тип ШИМ выбран, на выходе OCx_REF выставляется определенный уровень (1-й тип - на выходе OCx_REF единица, в случае ШИМ режима №2 - на выходе ноль). CCRx ждёт, когда значение в счётчике CNT достигнет того числа, что записан в нём самом (в блокнотик заглядывает). При совпадении этих чисел он в режиме ШИМ1 сбрасывает выход OCx_REF в ноль, или выставляет в 1 в режиме ШИМ2.
Исходя из сказанного, если мы в CNT запишем 99, а в CCRx 24, то при достижении счётчиком CNT значения 24, CCRx изменит состояние выхода OCx_REF на противоположное, и оставит его таким, пока CNT не досчитает до 99. Далее весь цикл повторяется и мы получаем на выходе OCx_REF ШИМ сигнал с заданной скважностью:
Теперь нам осталось разобраться с тактированием самого счётчика CNT. Вот с этим всё просто - перед ним стоит предделитель PSC, какое число в него запишешь, на столько он и поделит частоту на входе, а результат подаст на CNT. Для расчёта принимаем, что мы должны получить ШИМ с частотой 1000 Гц с возможностью регулировки заполнения от 0 до 99.
Так как CNT у нас и так считает до 100, значит, чтобы получить частоту 1000 Гц, нам нужно подать на CNT частоту 1000 * 100 = 100 000 Гц. Значит значение для предделителя, например при входной тактовой частоте 168 МГц, будет 168000000 / 100000 = 1680. Не забываем вычесть единицу 1680 - 1 = 1679 - это то число, которое мы должны записать в предделитель PSC.
Примечание: Не забываем смотреть, какую частоту можно подать на таймер, который вы используете в данный момент.
Для этого загружаем CubeMX (если используем STM32), выставляем тактовую на максимум и смотрим что идёт на таймеры:
Для STM32F103C8T все таймеры тактируются от 72 МГц. Значит делитель для PSC будет равен 72000000 / 100000 - 1 = 720 - 1 = 719. Полностью код выкладывать не буду, сгенерить проект можно с помощью CubeMX, а потом добавить следующий код в main.c, но только перед циклом.
Исходим из данных для этого МК:
- Частота ШИМ = 1000 Гц
- Частота, подаваемая на таймер = 72 000 000 Гц
- Коэффициент заполнения равен 25, при диапазоне 1 - 100
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // Врубаем тактирование таймера TIM1->PSC = 720 - 1; // Снижаем частоту перед таймером до 100 000 Гц TIM1->ARR = 100 - 1; // Длительность цикла 1 мс или 100 тактов счётчика CNT TIM1->CCR1 = 25 - 1; // Заполнение 25% TIM1->CCMR1 |= (0b110 << TIM_CCMR1_OC1M_Pos); // ШИМ 1-го типа TIM1->CCER |= TIM_CCER_CC1E; // Включаем выход TIM1->BDTR |= TIM_BDTR_MOE // Разрешаем всем каналам плеваться на выход // (такое есть только у таймеров с синфазным выходом) TIM1->CR1 |= TIM_CR1_CEN; // Запускаем таймер
Мы должны получить на выходе то же, что нарисовано на предыдущем рисунке для режима ШИМ1. Чтобы запустить все каналы, можно сделать так:
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // Врубаем тактирование таймера TIM1->PSC = 720 - 1; // Снижаем частоту перед таймером до 100 000 Гц TIM1->ARR = 100 - 1; // Длительность цикла 1мс или 100 тактов счётчика CNT TIM1->CCR1 = 25 - 1; // Заполнение 25% TIM1->CCR2 = 40 - 1; // Заполнение 40% TIM1->CCR3 = 50 - 1; // Заполнение 50% TIM1->CCR4 = 75 - 1; // Заполнение 75% TIM1->CCMR1 |= (0b110 << TIM_CCMR1_OC1M_Pos); // ШИМ 1-го типа TIM1->CCMR1 |= (0b110 << TIM_CCMR1_OC2M_Pos); // ШИМ 1-го типа TIM1->CCMR2 |= (0b110 << TIM_CCMR2_OC3M_Pos); // ШИМ 1-го типа TIM1->CCMR2 |= (0b110 << TIM_CCMR2_OC4M_Pos); // ШИМ 1-го типа TIM1->CCER |= TIM_CCER_CC1E; // Включаем выход 1 TIM1->CCER |= TIM_CCER_CC2E; // Включаем выход 2 TIM1->CCER |= TIM_CCER_CC3E; // Включаем выход 3 TIM1->CCER |= TIM_CCER_CC4E; // Включаем выход 4 TIM1->BDTR |= TIM_BDTR_MOE // Разрешаем всем каналам плеваться на выход // (такое есть только у таймеров с синфазным выходом) TIM1->CR1 |= TIM_CR1_CEN; // Запускаем таймер
Получим такую осциллограмму (масштаб не соблюдён):
Рис. 8
Самым неожиданным для многих будет то, что все каналы начинают работу одновременно (красная черта). И получить таким способом сигнал для управления трёхфазным двигателем, т. е. сигналы, сдвинутые на 120 градусов, возможно только с помощью хитрых манипуляций. Однако не всё потеряно - есть STM32Gxxx, у которых это возможно. Есть правда один фокус, заменить строку:
TIM1->CCMR2 |= (0b110 << TIM_CCMR2_OC4M_Pos); // ШИМ 1-го типа
на
TIM1->CCMR2 |= (0b111 << TIM_CCMR2_OC4M_Pos); // ШИМ 2-го типа
Тогда получим сигнал такого типа:
Правда его можно получить ещё некоторыми способами, но это уже другой узел и в этой статье не рассматривается. И как всегда вишенку на торте: если посмотреть рисунок со структурной схемой таймера, то там нарисовано не до конца. В действительности выглядит так:
У регистров ARR и CCRx оказывается есть тень. Что это значит?
Всё просто. При записи значений в регистры они сразу попадают по месту назначения. Понятно, что при записи в начале инициализации таймера, это ничего не значит. Но мы же не хотим вечно давать на выходе ШИМ с заполнением 25%, мы же его запускали чтобы, допустим, двигателем управлять или сервоприводом. Значит мы должны менять значение в CCRx, а иногда и CNT в процессе работы.
Если во время работы таймера он будет где-то на полпути, допустим, отыграет только 50% того, что в него заложено, то, записав новое значение, мы добьёмся только сбоя в работе. Он бросит всё и начнёт работать опять от красной черты, как на рисунке, но уже с новыми значениями. И у нас будет срыв сигнала, что может сказаться на нагрузке или управляющей схеме, если они большой мощности, например, при управлении трёхфазным двигателем на 3 КВт. У вас какие-то транзисторы будут открыты, а перезапись счётчика вызовет одновременное открытие транзисторов в другом плече - фейерверк будет ещё тот.
Чтобы этого не происходило, есть специальные биты разрешения предзагрузки в регистрах CCMRx->OCxPE для каждого канала и CR1->ARPE для регистра ARR. Если их поставить в "1", то запись значений в ARR или CCRx не вызовет моментальной перезаписи регистров. Они как бы уйдут в тень и только после того, как цикл закончится, они выйдут из тени и займут законное место в соответствующих регистрах. Правда тут опять есть одно но - при подаче питания всё записанное в теневые регистры будет там и находиться, а в самих регистрах может оказаться мусор, который также может привести к непоправимым последствиям (в случае управления мощной нагрузкой). Чтобы этого не произошло, нужно подать команду "обновления" этих регистров через регистр и бит EGR->UG. После этого желательно почистить флаги в регистре SR и только после этого можно разрешать таймеру работать. Про ШИМ пока на сегодня на этом всё.
А что в этом неожиданного? Счетчик-то у таймера один общий на все каналы, значит все каналы всегда обновляются синхронно.
Я частенько сталкивался с людьми, которые думают, что каналы работают по мере записи. Нзависимо. Поэтому так и написал.
Да и честно говоря, я и сам так с начала считал. Пока не понял. Я на STM с PIC16 перешёл, а там такого нет.