Синхронизация ШИМ на STM32
Не буду особо вдаваться в теорию, в сети много ресурсов где все очень подробно описано. Но когда дело доходит до практики понимаешь, что все намного сложнее. Используется микроконтроллер stm32l152c-discovery. В статье будет описан процесс запуска ШИМ двух таймеров в одно и то же время (полная синхронизация):
А так же запуск с отставанием (на фото отставание в пол периода):
Создадим функцию для инициализации initPWM, объявим в ней переменные для удобства
void initPWM()
{ const uint32_t Period = 20 - 1;//переменная периода const uint32_t prescaler = 1 - 1;//Предделитель //-1 потому что счет начинается с 0 const uint32_t pulse = 15;//Длина импульса
}
Для начала необходимо провести инициализацию структур для конфигурирования таймеров:
GPIO_InitTypeDef port; TIM_TimeBaseInitTypeDef timer; TIM_OCInitTypeDef timerPWM;
Далее активируем необходимую нам периферию. Будем использовать TIM3 и TIM4, просто потому что так захотелось:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIOB активируем, т.к. выходы таймеров сидят на этой шине PB5 и PB9. Эту информацию можно найти в юзер мануале к микроконтроллеру в таблице MCU pin description versus board function.
Настраиваем выходы в режим альтернативной фунции:
GPIO_StructInit(&port); port.GPIO_Mode = GPIO_Mode_AF;//Режим альтернативной функции, нужен для ШИМ port.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_9; port.GPIO_Speed = GPIO_Speed_2MHz;//Частота до 2Мгц port.GPIO_OType = GPIO_OType_PP;//режим работы "push-pull" "двухтактный выход" GPIO_Init(GPIOB, &port);
//активация альтернативной функции выхода PB5 GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_TIM3);
//активация альтернативной функции выхода PB9 GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_TIM4);
Настраиваем таймер в режим работы ШИМ:
TIM_TimeBaseStructInit(&timer); timer.TIM_ClockDivision = TIM_CKD_DIV1; timer.TIM_CounterMode = TIM_CounterMode_Up;//Счет вверх timer.TIM_Prescaler = prescaler;//Предделитель timer.TIM_Period = Period;//Период TIM_TimeBaseInit(TIM4, &timer);//Записываем настройки в оба таймера TIM_TimeBaseInit(TIM3, &timer); TIM_OCStructInit(&timerPWM); timerPWM.TIM_Pulse = pulse;//Длина импульса timerPWM.TIM_OCMode = TIM_OCMode_PWM1;//Выравнивание по границе timerPWM.TIM_OutputState = TIM_OutputState_Enable;//активируем выход timerPWM.TIM_OCPolarity = TIM_OCPolarity_High;//Импульс это 3.3В
Инициализируем нужные каналы для таймеров, каналы ищем в юзер мануале:
TIM_OC2Init(TIM3, &timerPWM); TIM_OC4Init(TIM4, &timerPWM);
А теперь самое важное, настройка синхронизации. Необходимо сделать один таймер мастером, другой слейвом:
//Настройка синхронизации TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_Enable);//Конфигурируем мастера TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable);//Задаем режим TIM_SelectInputTrigger(TIM3, TIM_TS_ITR3);//Конфигурируем подчиненного,
//используем триггер TIM4 канал 4, но нумерация с нуля, поэтому TIM_TS_ITR3 TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Gated);//режим стробирования
Если нам нужно сделать отставание, допустим на пол периода, то необходимо задать начальное значение счета подчиненного таймера равное половине периода. Для полной синхронизации счет начинаем с 0:
TIM3->CNT = 10;//Период 20, поэтому чтоб начать с половины пишем 10
//отставание в четверть это 5, в три четверти это 15
Активируем таймеры:
TIM_Cmd(TIM3, ENABLE);//Обязательно сначала слейв TIM_Cmd(TIM4, ENABLE);//А потом уже мастер
Выкладываю весь код полностью:
#include "stm32l1xx.h" //Для ШИМ
const uint32_t Period = 20 - 1;//переменная периода
const uint32_t prescaler = 1 - 1;
const uint32_t pulse = 15; void initPWM()
{ GPIO_InitTypeDef port; TIM_TimeBaseInitTypeDef timer; TIM_OCInitTypeDef timerPWM; //активация переферии RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // настройка выхода PB5, PB9 GPIO_StructInit(&port); port.GPIO_Mode = GPIO_Mode_AF; port.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_9; port.GPIO_Speed = GPIO_Speed_2MHz; port.GPIO_OType = GPIO_OType_PP; GPIO_Init(GPIOB, &port); //активация альтернативной функции выхода PB5 GPIO_PinAFConfig(GPIOB,GPIO_PinSource5,GPIO_AF_TIM3);
//активация альтернативной функции выхода PB9 GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_TIM4); //настройка таймера TIM_TimeBaseStructInit(&timer); timer.TIM_ClockDivision = TIM_CKD_DIV1; timer.TIM_CounterMode = TIM_CounterMode_Up; timer.TIM_Prescaler = prescaler; timer.TIM_Period = Period; TIM_TimeBaseInit(TIM4, &timer); TIM_TimeBaseInit(TIM3, &timer); TIM_OCStructInit(&timerPWM); timerPWM.TIM_Pulse = pulse; timerPWM.TIM_OCMode = TIM_OCMode_PWM1; timerPWM.TIM_OutputState = TIM_OutputState_Enable; timerPWM.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC2Init(TIM3, &timerPWM); TIM_OC4Init(TIM4, &timerPWM);
//Настройка синхронизации TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_Enable);//Конфигурируем мастера TIM_SelectMasterSlaveMode(TIM4, TIM_MasterSlaveMode_Enable); TIM_SelectInputTrigger(TIM3, TIM_TS_ITR3);//Конфигурируем подчиненного TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Gated); TIM3->CNT = 5;
// TIM_Cmd(TIM4, ENABLE); TIM_Cmd(TIM3, ENABLE); TIM_Cmd(TIM4, ENABLE);
} int main()
}
Вот собственно и все. В моей задаче нужно было управлять двумя шаговыми двигателями. Дополнительно еще писался код для плавного разгона, начинал работу с более длинным периодом и постепенно его уменьшал функцией TIM_SetAutoreload.