Top.Mail.Ru

STM32 с нуля. Timer. Использование таймеров для генерации ШИМ.

Чуть ранее (в этой статье) мы рассмотрели в общих чертах таймеры в STM32 и написали простенькую программку. Теперь, как и обещал, поподробнее покопаем генерацию ШИМ при помощи все того же таймера TIM4. Итак, начинаем!

Время традиционной вставки: поскольку компания STMicroelectronics прекратила поддержку библиотеки SPL, которая использовалась в этом курсе, я создал новый, посвященный работе уже с новыми инструментами, так что буду рад видеть вас там - STM32CubeMx. Кроме того, вот глобальная рубрика по STM32, а также небольшая подборка статей с ШИМ и таймерами:

Честно говоря, писать-то особо нечего. Думаю многие знают что такое ШИМ и с чем его едят, а если нет то об этом можно прочитать, например, в Википедии, так что нет, наверное, смысла отдельно описывать то, что уже многократно и хорошо описано...

Давайте создадим пример для генерации ШИМ. Просто сгенерировать такой сигнал не так интересно, так что давайте хоть немного усложним себе задачу. Будем генерировать ШИМ с разной длительностью импульса в зависимости от состояния кнопки. Если кнопка нажата – генерируем сигнал с периодом 2.5 мс и длительностью импульса 1.5 мс, а если кнопка не нажата – то 2.5 мс и 0.5 мс соответственно.

Так что создаем новый проект и пишем код. Для начала набор includ’ов:

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"

Добавили все файлы, необходимые нам для работы. Объявим переменные:

#define TIM_CCER_CC3NE                                           ((uint16_t)0x0400)
#define TIMER_PRESCALER                                          720
#define EXT_TIM_PULSE                                            150
#define TIM_PULSE                                                50
  • TIMER_PRESCALER мы уже упоминали - это предделитель частоты.
  • EXT_TIM_PULSE – увеличенная длительность импульса (то есть при нажатой кнопке), аналогично TIM_PULSE – обычная длительность (кнопка не нажата).

Продолжаем:

uint16_t previousState;
GPIO_InitTypeDef port;
TIM_TimeBaseInitTypeDef timer;
TIM_OCInitTypeDef timerPWM;
uint16_t buttonPreviousState;

Просто переменные, которые нам понадобятся в проекте. Пока все просто! И вот наконец-то кое-что поинтереснее, а именно наша разросшаяся функция инициализации:

void initAll()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

	GPIO_StructInit(&port);
	port.GPIO_Mode = GPIO_Mode_AF_PP;
	port.GPIO_Pin = GPIO_Pin_6;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &port);

	port.GPIO_Mode = GPIO_Mode_IPD;
	port.GPIO_Pin = GPIO_Pin_3;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &port);

	TIM_TimeBaseStructInit(&timer);
	timer.TIM_Prescaler = TIMER_PRESCALER;
	timer.TIM_Period = 250;
	TIM_TimeBaseInit(TIM4, &timer);

	TIM_OCStructInit(&timerPWM);
	timerPWM.TIM_Pulse = 50;
	timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
	timerPWM.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OC1Init(TIM4, &timerPWM);
}

Давайте прямо по строчкам смотреть, что тут происходит.

Вначале уже привычное включение тактирования необходимой периферии. Шестую ножку порта GPIOB настраиваем на работу в режиме альтернативной функции(!). Лезем в даташит на контроллер и видим, что альтернативной функцией у этого вывода является первый канал таймера TIM4. То что надо:

Функции выводов STM32.

Вывод PA3 настраиваем на вход – там будет наша воображаемая кнопка. Итак, с инициализацией портов закончили, идем настраивать таймер. Поначалу все конфигурируем, как и в предыдущем проекте. А вот дальше кое-что новенькое:

TIM_OCStructInit(&timerPWM);
timerPWM.TIM_Pulse = 50;
timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
timerPWM.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init(TIM4, &timerPWM);

Для использования режима генерации ШИМ нам понадобилась структура TIM_OCInitTypeDef. Поле TIM_Pulse – длительность заполнения, пусть будет сначала 50 тиков (0.5 мс). Далее задаем режим - TIM_OCMode_PWM1. Помимо PWM1 есть еще PWM2. Это всего лишь разные режимы ШИМ – с выравниванием по границе и по центру. В поле TIM_OutputState забиваем – Enable и инициализируем таймер. Готово!

Осталось совсем немного... Функция main() – как же без нее:

int main()
{
	__enable_irq();
	initAll();
	buttonPreviousState = 0;
	TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
	TIM_Cmd(TIM4, ENABLE);
	NVIC_EnableIRQ(TIM4_IRQn);
	while(1)
	{
		__NOP();
	}
}

Все как и раньше – включаем прерывание по переполнению. Оно нам нужно, чтобы изменять параметры генерируемого ШИМ-сигнала именно в момент окончания периода. Вот и сам код обработчика:

void TIM4_IRQHandler()
{ 
	uint16_t button = 0;
	button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_3);
	TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
	
	if ((button == 1) && (buttonPreviousState == 0))
	{
		TIM4->CCR1 = EXT_TIM_PULSE;
		buttonPreviousState = 1;
	}
	if ((button == 0) && (buttonPreviousState == 1))
	{
		TIM4->CCR1 = TIM_PULSE;
		buttonPreviousState = 0;
	}
}

Опрашиваем кнопку, ничего нового, знакомая функция 🙂 Возможно возникнет вопрос – зачем так усложнять:

(button == 1) && (buttonPreviousState == 0)

А чтобы работа с регистром таймера шла не на каждое прерывание, а только когда состояние кнопки изменяется. Упомянутая работа с регистром заключается в прямой записи длительности заполнения в регистр таймера TIM_CCR.

Компилируем, идем в отладчик, запускаем программу и эмулируем нажатие кнопки на PA3. Если кто забыл, открываем окно General Purpose I/O A (GPIOA) и вручную выставляем бит на входе PA3. В окошке логического анализатора видим:

Программа для работы с таймерами в STM32.

При изменении состояния кнопки меняется и скважность импульсов, как и задумывалось. И на сегодня это все, до скорого 🤝

Подписаться
Уведомить о
guest

43 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
Денис
Денис
11 лет назад

А можно поподробней строчку:
timerPWM.TIM_Pulse = 50;
если туда вбить ноль?
или 5000?

giperboloid
giperboloid
10 лет назад

извиняюсь за бестолковый вопрос, но где в главной функции вызывается обработчик TIM4_IRQHandler?

giperboloid
giperboloid
Ответ на комментарий  Aveal
10 лет назад

а какие еще функции, связанные с таймерами, могут вызываться аппаратно и где про них можно прочитать?

giperboloid
giperboloid
Ответ на комментарий  Aveal
10 лет назад

просто в stm32f4xx_tim.h про него упоминания нет, в reference manual тоже

giperboloid
giperboloid
10 лет назад

разобрался - в stm32f4xx.h

Set2013
Set2013
10 лет назад

А на установленных на плате светодиодах ШИМ не пробовали? На зарубежных форумах ппадалось, но не компилируется выдает ошибки.

#include "stm32F10x.h"
#include "STM32vldiscovery.h"

int main(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;

volatile int i;
int n = 1;
int brightness = 0;

RCC_APB2PeriphClockCmd(
RCC_APB2Periph_GPIOC |
RCC_APB2Periph_AFIO, ENABLE );

RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM3, ENABLE );

GPIO_StructInit(&GPIO_InitStructure); // Reset init structure

// Setup Blue & Green LED on STM32-Discovery Board to use PWM.
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // Alt Function - Push Pull
GPIO_Init( GPIOC, &GPIO_InitStructure );
GPIO_PinRemapConfig( GPIO_FullRemap_TIM3, ENABLE ); // Map TIM3_CH3 to GPIOC.Pin8, TIM3_CH4 to GPIOC.Pin9

// Let PWM frequency equal 100Hz.
// Let period equal 1000. Therefore, timer runs from zero to 1000. Gives 0.1Hz resolution.
// Solving for prescaler gives 240.
TIM_TimeBaseStructInit( &TIM_TimeBaseInitStruct );
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV4;
TIM_TimeBaseInitStruct.TIM_Period = 1000 - 1; // 0..999
TIM_TimeBaseInitStruct.TIM_Prescaler = 240 - 1; // Div 240
TIM_TimeBaseInit( TIM3, &TIM_TimeBaseInitStruct );

TIM_OCStructInit( &TIM_OCInitStruct );
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
// Initial duty cycle equals 0%. Value can range from zero to 1000.
TIM_OCInitStruct.TIM_Pulse = 0; // 0 .. 1000 (0=Always Off, 1000=Always On)

TIM_OC3Init( TIM3, &TIM_OCInitStruct ); // Channel 3 Blue LED
TIM_OC4Init( TIM3, &TIM_OCInitStruct ); // Channel 4 Green LED

TIM_Cmd( TIM3, ENABLE );

while(1) // Do not exit
{
if (((brightness + n) >= 1000) || ((brightness + n) CCR3 = brightness; // set brightness
TIM3->CCR4 = 1000 - brightness; // set brightness

for(i=0;i<10000;i++); // delay
}

return(0); // System will implode
}

Set2013
Set2013
10 лет назад

Если есть на плате светодиоды, чего изголяться... уже получилось, код ниже.
#include "stm32f10x.h" /* "stm32f10x.h"
int main(void)
{
volatile int i; // Переменная отсчета паузы
int n = 1; // Шаг увеличения яркости
int brightness = 0; // Яркость

RCC->APB2ENR |= (RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN); //Тактирование порта C и альтернативных функций
RCC->APB1ENR | =RCC_APB1ENR_TIM3EN;//Тактирование таймера TIM3

GPIOC->CRH &= ~(GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF8 | GPIO_CRH_CNF9); //очистить разряды MODE, CNF (поскольку после сброса мк задан режим "Input Floating")
GPIOC->CRH |= GPIO_CRH_MODE8 | GPIO_CRH_MODE9 | GPIO_CRH_CNF8_1 | GPIO_CRH_CNF9_1;//Максимальная скорость порта PC8,9 = 50 MHz - выход Push-Pull в режиме альтернативной функции
AFIO->MAPR |=AFIO_MAPR_TIM3_REMAP_FULLREMAP; //Full remap (CH1/PC6, CH2/PC7, CH3/PC8, CH4/PC9) TIM3->PSC = 240-1; // Настройка предделителя таймера (24МГц/240Кгц=100 Кгц)
TIM3->ARR = 1000-1; // задаем период ШИМ 100000/1000=100 Гц
TIM3->CCMR2 |= (TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1); // OCxM = 110 - ШИМ режим 1(прямой ШИМ)на каналы 3 & 4
TIM3->CCR3 = 0; // Начальная длительность импульса.
/* разрешаем выходной сигнал */
TIM3->CCER |= (TIM_CCER_CC3E | TIM_CCER_CC4E);
TIM3->CR1 |= TIM_CR1_CEN; //Запускаем таймер!
while(1)
{
if (((brightness + n) >= 1000) || ((brightness + n) CCR3 = brightness; // установим яркость
TIM3->CCR4 = 1000 - brightness; // установим яркость

for(i=0;i<10000;i++); // пауза
}
}

Astrgan
Astrgan
10 лет назад

1) Если в прерывании я буду менять CCR1 по синусоидальному закону (брать значения из таблицы синуса), а выходной сигнал пропущу через фнч, то получится синусоидальное напряжение?

2) Если использовать режим ШИМ выравнивание по центру, то как настроить прерывание?

Никита
Никита
10 лет назад

Всем привет. Не пойму как считать значения prescaler , TIM_Period и TIM_Pulse. Хочу попробовать управлять сервоприводом. Ему нужно 50 Гц. Как мне расчитать параметры, чтобы получилась нужная частота ШИМа?

vaniaz
vaniaz
10 лет назад

Здравствуйте, коллеги!

Очень простой вопрос. Я хочу, чтобы мой процессор работал на частоте 168 МГц (очень надо). Еще я хочу, чтобы один из таймеров делал ШИМ на одной из ног с частотой порядка 2 МГц с предельно высокой точностью. Но получается, что точность ШИМа моего таймера будет определяться не точностью и стабильностью кварца, а тем, как работает модуль PLL???

Михаил
Михаил
9 лет назад

Может это конечно и тупой вопрос, но подскажите как высчитывать PRESCALER? Точнее от какой частоты? Вот не могу найти эту информацию в гугле, везде попадается только такие же примеры, как и тут. Спасибо.

Паша
Паша
9 лет назад

Я предполагал что с 8 битных AVRок будет тяжеловато переходить на 32 битные но все же.
Переделал чуть код для Stm32F3discovery:
#include "stm32f30x_gpio.h"
#include "stm32f30x_rcc.h"
#include "stm32f30x_tim.h"
#include "stm32f30x.h"

#define TIMER_PRESCALER 720
#define EXT_TIM_PULSE 150 //
#define TIM_PULSE 50

uint16_t previousState;
GPIO_InitTypeDef port;
TIM_TimeBaseInitTypeDef timer;
TIM_OCInitTypeDef timerPWM;
uint16_t buttonPreviousState;

void initAll()
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // для PA0 ( User кнопка ) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // тактирую TIM2 на шине APB1

GPIO_StructInit(&port);
port.GPIO_Mode = GPIO_Mode_AF; // альтерн. ф-ция
port.GPIO_Pin = GPIO_Pin_2; // PA2 I/O TTa -
USART2_TX, TIM2_CH3, ( из даташит )
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &port);

port.GPIO_Mode = GPIO_Mode_IN;
port.GPIO_Pin = GPIO_Pin_0; // вход PA0 User кнопка
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &port);

TIM_TimeBaseStructInit(&timer);
timer.TIM_Prescaler = TIMER_PRESCALER;
timer.TIM_Period = 250;
TIM_TimeBaseInit(TIM2, &timer);

TIM_OCStructInit(&timerPWM);
timerPWM.TIM_Pulse = 50; // длительность заполнения
timerPWM.TIM_OCMode = TIM_OCMode_PWM1; // режим
timerPWM.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init(TIM2, &timerPWM);
}

int main()
{
__enable_irq ();
initAll();
//buttonPreviousState = 0;
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
NVIC_EnableIRQ(TIM2_IRQn);
while(1)
{
__NOP();
}
}

void TIM2_IRQHandler()
{
uint16_t button = 0;
button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
if ((button == 1) && (buttonPreviousState == 0))
{
TIM2->CCR1 = EXT_TIM_PULSE;
buttonPreviousState = 1;
}
if ((button == 0) && (buttonPreviousState == 1))
{
TIM2->CCR1 = TIM_PULSE;
buttonPreviousState = 0;
}
}

Все вроде перепроверил нету на выходе ШИМа (((
Aveal что я не так делаю? Заранее спасибо.
P.S. канал для таймера указывать не надо? ( TIM2->CCER | = TIM_CCER_CC3E; )

Паша
Паша
9 лет назад

Разобрался! Необходимо было добавить
GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1); это для контроллеров серии STM32f3xx - f4xx.

Теперь пытаюсь разобраться дальше с частотой тактирования и надеюсь на вашу помощь.
Шина от которой тактирую таймер 2 имеет максимальную частоту 36 мгц ( по даташиту ).
здесь gpio.GPIO_Speed = GPIO_Speed_50MHz; выставляется 50 мгц так какая частота будет по факту?

Паша
Паша
9 лет назад

Спасибо за быстрый ответ !

don_slavone
don_slavone
9 лет назад

Добрый день! я тоже разбираюсь с stm32 на плате F3Discovery. Подскажите, GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_1); где инициализировать эту функцию?
Че то у меня нету шима по-прежнему

don_slavone
Ответ на комментарий  Aveal
9 лет назад

Вот мой код, и всеравно не работает, Кнопка на РА0, таймер 4, альтернативный пин PORTD12, по даташиту канал 1 таймера. Че еще не так?

#include "stm32f30x.h"
#include "stm32f30x_rcc.h"
#include "stm32f30x_gpio.h"
#include "stm32f30x_tim.h"
#define TIMER_PRESCALER 711
#define EXT_TIM_PULSE 150
#define TIM_PULSE 50
uint16_t previousState;
GPIO_InitTypeDef port;
TIM_TimeBaseInitTypeDef timer;
TIM_OCInitTypeDef timerPWM;
uint16_t buttonPreviousState;
void initAll()
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
GPIO_StructInit(&port);
port.GPIO_Mode = GPIO_Mode_OUT;
port.GPIO_OType = GPIO_OType_PP;
port.GPIO_Pin = GPIO_Pin_12;
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOD, &port);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource15,GPIO_AF_2);

port.GPIO_Mode = GPIO_Mode_IN;
port.GPIO_Pin = GPIO_Pin_0;
port.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &port);

TIM_TimeBaseStructInit(&timer);
timer.TIM_Prescaler = TIMER_PRESCALER;
timer.TIM_Period = 250;
TIM_TimeBaseInit(TIM4, &timer);

TIM_OCStructInit(&timerPWM);
timerPWM.TIM_Pulse = 50;
timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
timerPWM.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC1Init(TIM4, &timerPWM);
}

int main()
{
__enable_irq ();
initAll();
buttonPreviousState = 0;
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM4, ENABLE);
NVIC_EnableIRQ(TIM4_IRQn);
while(1)
{
__NOP();
}
}
void TIM4_IRQHandler()
{
uint16_t button = 0;
button = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
if ((button == 1) && (buttonPreviousState == 0))
{
TIM4->CCR1 = EXT_TIM_PULSE;
buttonPreviousState = 1;
}
if ((button == 0) && (buttonPreviousState == 1))
{
TIM4->CCR1 = TIM_PULSE;
buttonPreviousState = 0;
}
}

don_slavone
Ответ на комментарий  don_slavone
9 лет назад

Там ошибка, вместо GPIO_PinSource15 должно быть GPIO_PinSource12, но всеравно не работает

don_slavone
Ответ на комментарий  Aveal
9 лет назад

GPIO_Mode_AF точно. проглядел. Спасибо. теперь вроде работает. А частота импульсов теоретически должна быть около 333Гц, я правильно понимаю?

don_slavone
Ответ на комментарий  Aveal
9 лет назад

Я даже не знаю.. А где можно посмотреть или как выставить?

Антон
Антон
9 лет назад

В проекте есть файл типа такого:
startup_stm32f10x_md.s

В нём все названия и описаны. По сути - это просто указатель на функцию, записаный в нужный адрес. Так же, как и прерывания в MS-DOS для PC.

Слава Панас
Слава Панас
7 лет назад

Здравствуйте. Вы не подскажите, как пользоваться логическим анализатором? Хотя бы, как увидеть такую же картинку, как в примере.

Dimaster
Dimaster
6 лет назад

День добрый. Использую F407 проц и Cube. Прошу подсказать, как правильно запустить трехфазый ШИМ (то есть нужно три канала), с использованием DMA. Пытался вызывать функцию HAL_TIM_PWM_Start_DMA для всех трех каналов, но работает в итоге только один, который первым вызвали. Как это делается правильно?

Dimaster
Dimaster
6 лет назад

Все три канала работают по своим данным. Ладно, ну его DMA. Выяснилось что у меня и обычная функция HAL_TIM_PWM_Start_IT не хочет работать. Как на ней запустить три канала одновременно и для каждого канала иметь возможность задавать свои данные?

Dimaster
Dimaster
6 лет назад

Да, спасибо. Уже сообразил. Не мог долго понять, что у TIM1 прерывание надо вручную врубать, а активное прерывание, запускаемое от ШИМ, не подходит для подпихивания данных

1
1
5 лет назад

GPIO_StructInit(&port);

в предыдущих статьях в функции StructInit не было &amp.
Для чего здесь в примере добавлено?

Эдуард
Эдуард
2 лет назад

Здравствуйте! А через define-ны мы получается можем настраивать интервалы и длительность импульсов?

43
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x