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

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

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

Давайте создадим пример для генерации ШИМ. Просто сгенерировать такой сигнал не так интересно, так что давайте хоть немного усложним себе задачу. Будем генерировать ШИМ с разным заполнением в зависимости от состояния кнопки. Если кнопка нажата – генерируем сигнал с периодом 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
41 Комментарий
старее
новее большинство голосов
Inline Feedbacks
View all comments
Денис
Денис
7 лет назад

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

giperboloid
giperboloid
6 лет назад

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

giperboloid
giperboloid
Reply to  Aveal
6 лет назад

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

giperboloid
giperboloid
Reply to  Aveal
6 лет назад

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

giperboloid
giperboloid
6 лет назад

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

Set2013
Set2013
6 лет назад

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

#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
6 лет назад

Если есть на плате светодиоды, чего изголяться… уже получилось, код ниже.
#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
6 лет назад

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

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

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

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

vaniaz
vaniaz
6 лет назад

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

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

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

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

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

Я предполагал что с 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; )

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

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

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

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

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

don_slavone
don_slavone
5 лет назад

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

don_slavone
Reply to  Aveal
5 лет назад

Вот мой код, и всеравно не работает, Кнопка на РА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
Reply to  don_slavone
5 лет назад

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

don_slavone
Reply to  Aveal
5 лет назад

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

don_slavone
Reply to  Aveal
5 лет назад

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

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

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

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

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

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

Dimaster
Dimaster
2 лет назад

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

Dimaster
Dimaster
2 лет назад

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

Dimaster
Dimaster
2 лет назад

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

1
1
1 год назад

GPIO_StructInit(&port);

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

Присоединяйтесь!

Profile Profile Profile Profile Profile
Vkontakte
Twitter

Язык сайта

Июнь 2020
Пн Вт Ср Чт Пт Сб Вс
« Май    
1234567
891011121314
15161718192021
22232425262728
2930  

© 2013-2020 MicroTechnics.ru