LPCXpresso Урок 14. PWM. Синхронный ШИМ на таймере.

Прежде чем завершить курс для новичков рассмотрим такую всеми любимую и всем надоевшую тему как ШИМ.

Схема

Для данного урока нам надо подключить пару светодиодов к выводам P1.9 и P1.10 платы, через ток ограничительные резисторы (номиналом 100 Ом – 1 кОм):
Схема подключения светодиодов

Если вдруг под рукой у вас не завалялось ни одного светодиода, то можно поступить и так:
Схема использования установленного на плате светодиода

Немного теории

Синхронный ШИМ будем формировать аппаратно. Для этого задействуем 16-ти битный таймер 1 CT16B1. Выбран он просто, потому что все его выводы занимают, на мой взгляд, менее ценные выводы. Остальные же делят шину отладки, SPI, АЦП. В общем, поделить их не проблема, но не станем морочиться.

По работе таймера в режиме ШИМ в UM10375 есть несколько правил (раздел 14.8.13), вот они:

  1. Все выводы ШИМ устанавливаются в низкий уровень в начале каждого ШИМ цикла (счётчик таймера в 0), если их значения сравнения не равны нулю.
  2. Каждуй вывод ШИМ устанавливается в высокий уровень когда достигнуто его значение сравнения. Если значение сравнения не достигается (например, когда оно больше длины ШИМ), то вывод остаётся с низком уровне.
  3. Если в регистр совпадения записывается значение сравнения больше длины ШИМ и вывод ШИМ уже имеет высокий уровень, то ШИМ сигнал будет установлен в низкий уровень в начале следующего цикла.
  4. Если регистр сравнения имеет то же значение что и значение сброса таймера (длина ШИМ), то вывод ШИМ будет сброшен в низкий уровень в следующий такт. Таким образом, ШИМ сигнал всегда будет содержать один импульс высокого уровня длительностью в один такт с периодом равным длине ШИМ (значением сброса таймера).
  5. Если регистр сравнения установлен в 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

Hosted by uCoz