LPCXpresso Урок 14. PWM. Синхронный ШИМ на таймере.
Прежде чем завершить курс для новичков рассмотрим такую всеми любимую и всем надоевшую тему как ШИМ.
Схема
Для данного урока нам надо подключить пару светодиодов к выводам P1.9 и P1.10 платы, через ток ограничительные резисторы (номиналом 100 Ом – 1 кОм):
Если вдруг под рукой у вас не завалялось ни одного светодиода, то можно поступить и так:
Немного теории
Синхронный ШИМ будем формировать аппаратно. Для этого задействуем 16-ти битный таймер 1 CT16B1. Выбран он просто, потому что все его выводы занимают, на мой взгляд, менее ценные выводы. Остальные же делят шину отладки, SPI, АЦП. В общем, поделить их не проблема, но не станем морочиться.
По работе таймера в режиме ШИМ в UM10375 есть несколько правил (раздел 14.8.13), вот они:
- Все выводы ШИМ устанавливаются в низкий уровень в начале каждого ШИМ цикла (счётчик таймера в 0), если их значения сравнения не равны нулю.
- Каждуй вывод ШИМ устанавливается в высокий уровень когда достигнуто его значение сравнения. Если значение сравнения не достигается (например, когда оно больше длины ШИМ), то вывод остаётся с низком уровне.
- Если в регистр совпадения записывается значение сравнения больше длины ШИМ и вывод ШИМ уже имеет высокий уровень, то ШИМ сигнал будет установлен в низкий уровень в начале следующего цикла.
- Если регистр сравнения имеет то же значение что и значение сброса таймера (длина ШИМ), то вывод ШИМ будет сброшен в низкий уровень в следующий такт. Таким образом, ШИМ сигнал всегда будет содержать один импульс высокого уровня длительностью в один такт с периодом равным длине ШИМ (значением сброса таймера).
- Если регистр сравнения установлен в 0, то ШИМ вывод будет установлен в высокий уровень (и останется в нем в дальнейшем) когда таймер сбросится в нулевое значение. Вывод ШИМ не будет устанавливаться в низкий уровень в начале цикла.
Пишем программу
Это будет самая простая программа без применения библиотек, кроме CMSIS. Функцию для ШИМ я не буду помещать в библиотеку LPC13xx_Lib, можете сделать это самостоятельно, если посчитаете нужным. Код как всегда прокомментирован, и сомневаюсь, что я смогу добавить к ним что-то ещё.
Начнём как обычно с инициализации. Передаётся функции один параметр – период ШИМ в микросекундах:
void PWM16_init(uint16_t period)
{
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<8); // Разрешаем тактирование для таймера Timer1_16
LPC_TMR16B1->PR = SystemCoreClock / 1000000; // Предделитель для получения частоты 1МГц
LPC_IOCON->PIO1_9 &= ~0x07;
LPC_IOCON->PIO1_9 |= 0x01; // Вывод на Timer1_16 MAT0
LPC_IOCON->PIO1_10 &= ~0x07;
LPC_IOCON->PIO1_10 |= 0x02; // Вывод на Timer1_16 MAT1
LPC_TMR16B1->PWMC = (1<<3) // Разрешаем Match3 - по нему будет сброс
| (1<<0) // Разрешаем PWM0
| (1<<1) // Разрешаем PWM1
;
LPC_TMR16B1->MR3 = period - 1; // В регистр сравнения MR3 период ШИМ (-1 т.к. сброс идет «после» установленного значения)
LPC_TMR16B1->MR0 = period / 2; // Значение сравнения ШИМ0
LPC_TMR16B1->MR1 = period / 2; // Значение сравнения ШИМ1
LPC_TMR16B1->MCR = (2<<9); // Режим Сброс счётчика TC при его совпадении с MR3
LPC_TMR16B1->TCR = (1<<0); // Разрешить счет Timer1_16
}
Примеры временных диаграмм есть в UM10375 раздел 14.9.
Код из архива содержит ещё несколько закомментированных строк. В режиме ШИМ они нам безразличны, но имеют значение при работе выводов в режиме сравнения. Желающие могут изучить их самостоятельно, я же не придумал для них подходящей демонстрации.
Функция установки периода ШИМ просто заносит в соответствующий регистр сравнения переданное ей значение:
void PWM16_set(uint8_t num, uint16_t value)
{
if(num) {
LPC_TMR16B1->MR1 = value;
} else {
LPC_TMR16B1->MR0 = value;
}
}
Надо отметить что никакой «двойной буферизации» для данных регистров нет, и значение применяется сразу, как заносится в регистр. По этой причине могут возникнуть артефакты при записи нового значения. Например, TC=50, MR1=100 и вы записываете в него, скажем 20. В итоге значение сравнения не будет достигнуто в текущей итерации цикла ШИМ и вывод так и останется в низком уровне до достижения счетчиком значения 20 в следующей итерации цикла ШИМ.
Ну и наконец функция main
#define PWM_RERIOD 2000 /* Период ШИМ в микросекундах */
int main(void) {
uint16_t pwm = 0;
SysTick_Config(SystemCoreClock / 1000); // настройка таймера на период 1мс
PWM16_init(PWM_RERIOD);
while(1) {
PWM16_set(0, pwm);
PWM16_set(1, PWM_RERIOD - pwm);
delay_ms(100);
pwm = (pwm < PWM_RERIOD) ? pwm + PWM_RERIOD/20 : 0;
}
return 0 ;
}
Алгоритм прост. Каждые 100мс значение ШИМ0 увеличивается и значение ШИМ1 уменьшается за 20 шагов.
Запуск
Запускаем на исполнение полученные 3176 байт кода и наблюдаем довольно «ступенчатое» изменение яркости светодиодов. Поскольку светодиоды у нас подключены к земле, то светодиод ШИМ0 будет уменьшать свою яркость, а светодиод ШИМ1 увеличивать. Такие заметные «ступеньки» из-за малого выбранного количества шагов (20 шагов у нас). Это некий компромисс, что бы и в отладчике не долго бегать для того, чтобы увидеть изменения, и при этом не сильно резко менять яркость.
Вместо заключения
ШИМ на всех таймерах (два 16-ти битных таймера и два 32-х битных) запускается одинаково. Разница только в используемых выводах, их количестве и в разрядности таймеров. ШИМ прост, оттого и урок такой короткий.
Файлы: blinky_pwm.zip