12. STM32. Програмування STM32F103. TIMER. PWM
У попередніх статтях ми познайомились з тим, як таймери можуть захоплювати вхідний сигнал. Таймери мікроконтролера STM32 також можуть формувати вихідні сигнали. Сьогодні ми познайомимося з PWM або ШІМ сигналом на прикладах.
Ініціалізація PWM виконується наступним чином:
- налаштовується вихід порту відповідного каналу таймера, який буде задіяний для формування PWM сигналу
- виконуються базові налаштування таймера
- виконується налаштування OC каналу таймера (налаштування параметрів PWM)
- вмикається таймер
Налаштування параметрів PWM виконується через структуру TIM_OCInitTypeDef:
typedef struct
{
  uint16_t TIM_OCMode;        /*!< Specifies the TIM mode.
                                   This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
  uint16_t TIM_OutputState;   /*!< Specifies the TIM Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_state */
  uint16_t TIM_OutputNState;  /*!< Specifies the TIM complementary Output Compare state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_state
                                   @note This parameter is valid only for TIM1 and TIM8. */
  uint16_t TIM_Pulse;         /*!< Specifies the pulse value to be loaded into the Capture Compare Register. 
                                   This parameter can be a number between 0x0000 and 0xFFFF */
  uint16_t TIM_OCPolarity;    /*!< Specifies the output polarity.
                                   This parameter can be a value of @ref TIM_Output_Compare_Polarity */
  uint16_t TIM_OCNPolarity;   /*!< Specifies the complementary output polarity.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
                                   @note This parameter is valid only for TIM1 and TIM8. */
  uint16_t TIM_OCIdleState;   /*!< Specifies the TIM Output Compare pin state during Idle state.
                                   This parameter can be a value of @ref TIM_Output_Compare_Idle_State
                                   @note This parameter is valid only for TIM1 and TIM8. */
  uint16_t TIM_OCNIdleState;  /*!< Specifies the TIM Output Compare pin state during Idle state.
                                   This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
                                   @note This parameter is valid only for TIM1 and TIM8. */
} TIM_OCInitTypeDef;
Нас цікавлять наступні параметри:
- TIM_OCMode - режим виходу (TIM_OCMode_Timing | TIM_OCMode_Active | TIM_OCMode_Inactive | TIM_OCMode_Toggle | TIM_OCMode_PWM1 | TIM_OCMode_PWM2). Нас цікавить TIM_OCMode_PWM1 або TIM_OCMode_PWM2.
- TIM_OutputState - стан виходу (TIM_OutputState_Disable | TIM_OutputState_Enable)
- TIM_Pulse - шпаруватість ШІМ (від 0x0000 до 0xFFFF)
- TIM_OCPolarity - (TIM_OCPolarity_High | TIM_OCPolarity_Low) TIM_OCPolarity_High - прямий ШІМ, TIM_OCPolarity_Low - інвертований.
PWM. Яскравість світлодіода
Перший приклад: ми будемо змінювати яскравість світлодіода. Схема підключення:
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"
#define PERIOD 1000
int main(void)
{
	int TIM_Pulse = 0;
	int i;
	GPIO_InitTypeDef port;
	TIM_TimeBaseInitTypeDef timer;
	TIM_OCInitTypeDef timerPWM;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	GPIO_StructInit(&port);
	port.GPIO_Mode = GPIO_Mode_IPU;
	port.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &port);
	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);
	TIM_TimeBaseStructInit(&timer);
	timer.TIM_Prescaler = 720;
	timer.TIM_Period = PERIOD;
	timer.TIM_ClockDivision = 0;
	timer.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &timer);
	TIM_OCStructInit(&timerPWM);
	timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
	timerPWM.TIM_OutputState = TIM_OutputState_Enable;
	timerPWM.TIM_Pulse = 10;
	timerPWM.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM4, &timerPWM);
    TIM_Cmd(TIM4, ENABLE);
    while(1)
    {
    	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) {
    		if (TIM_Pulse < PERIOD)
    			TIM_Pulse++;
    		TIM4->CCR1 = TIM_Pulse;
    	}
    	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0) {
    		if (TIM_Pulse > 0)
    			TIM_Pulse--;
    		TIM4->CCR1 = TIM_Pulse;
    	}
    	/* delay */
    	for(i=0;i<0x10000;i++);
    }
}
Яскравість змінюється кнопками. Частота ШІМ вираховується, як частота тактування таймера, поділена на дільник таймера. Наприклад, якщо таймер тактується частотою 8МГц, а поділювач TIM_Prescaler = 800, частота ШІМ буде 8000000/800 = 10КГц. Доречі, якщо частоту ШІМ знизити до 1 герца, а шрабуватість встановити 50%, світлодіод буде просто блимати один раз на секунду.
PWM. RGB-LED
Один таймер може генерувати окремі ШІМ сигнали на кожному зі своїх каналів. У наступному прикладі використовується три канали (з чотирьох доступних) одного таймера для формування трьох PWM сигналів для керування кольором RGB світлодіода. Схема:
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"
#define PERIOD 1000
int main(void)
{
	int TIM_Pulse_R = 0;
	int TIM_Pulse_G = 0;
	int TIM_Pulse_B = 0;
	int i;
	GPIO_InitTypeDef port;
	TIM_TimeBaseInitTypeDef timer;
	TIM_OCInitTypeDef timerPWM;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	GPIO_StructInit(&port);
	port.GPIO_Mode = GPIO_Mode_AF_PP;
	port.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOB, &port);
	TIM_TimeBaseStructInit(&timer);
	timer.TIM_Prescaler = 720;
	timer.TIM_Period = PERIOD;
	timer.TIM_ClockDivision = 0;
	timer.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &timer);
	TIM_OCStructInit(&timerPWM);
	timerPWM.TIM_Pulse = 0;
	timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
	timerPWM.TIM_OutputState = TIM_OutputState_Enable;
	timerPWM.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM4, &timerPWM);
	TIM_OC2Init(TIM4, &timerPWM);
	TIM_OC3Init(TIM4, &timerPWM);
    TIM_Cmd(TIM4, ENABLE);
    while(1)
    {
    	TIM_Pulse_R++;
    	if (TIM_Pulse_R > PERIOD)
    		TIM_Pulse_R = 0;
    	TIM_Pulse_G +=2;
    	if (TIM_Pulse_G > PERIOD)
    		TIM_Pulse_G = 0;
    	TIM_Pulse_B +=4;
    	if (TIM_Pulse_B > PERIOD)
    		TIM_Pulse_B = 0;
    	TIM4->CCR1 = TIM_Pulse_R;
    	TIM4->CCR2 = TIM_Pulse_G;
    	TIM4->CCR3 = TIM_Pulse_B;
    	/* delay */
   	    for(i=0;i<0x1000;i++);
    }
}
PWM. Servo
Деякі пристрої керуються PWM сигналом специфічної форми. Одним з таких пристроїв є сервоприводи або сервомашинки, які досить часто використовують у роботах та радіокерованих моделях. Про сервоприводи я писав у статті "Управление сервоприводом (сервомашинкой) с помощью микроконтроллера ATMega". У цій статті також наведено параметри сигналу для керування сервою. Тому ми не будемо заглиблюватися у деталі керування сервою. Ціль цієї статті: продемонструвати різноманіття пристроїв, якими можна керувати, використовуючи можливості таймерів мікроконтролера. Тому я просто наведу приклад керування сервою. Схема підключення:
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"
#define SYSCLK 72000000
#define PRESCALER 72
GPIO_InitTypeDef port;
TIM_TimeBaseInitTypeDef timer;
TIM_OCInitTypeDef timerPWM;
void servo_init(void) {
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, 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);
	TIM_TimeBaseStructInit(&timer);
	timer.TIM_Prescaler = PRESCALER;
	timer.TIM_Period = SYSCLK / PRESCALER / 50;
	timer.TIM_ClockDivision = 0;
	timer.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &timer);
	TIM_OCStructInit(&timerPWM);
	timerPWM.TIM_Pulse = 1000;
	timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
	timerPWM.TIM_OutputState = TIM_OutputState_Enable;
	timerPWM.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM4, &timerPWM);
    TIM_Cmd(TIM4, ENABLE);
}
int main(void)
{
	int TIM_Pulse;
	int i;
	//Init buttons
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_StructInit(&port);
	port.GPIO_Mode = GPIO_Mode_IPU;
	port.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	port.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOA, &port);
	servo_init();
	TIM_Pulse = timerPWM.TIM_Pulse;
    while(1)
    {
    	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) {
    		if (TIM_Pulse < 2000)
    			TIM_Pulse++;
    		TIM4->CCR1 = TIM_Pulse;
    	}
    	if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0) {
    		if (TIM_Pulse > 1000)
    			TIM_Pulse--;
    		TIM4->CCR1 = TIM_Pulse;
    	}
   	    // delay
   	    for(i=0;i<0x1000;i++);
    }
}
PWM. Звук
Крім того, використовуючи ШІМ, можна генерувати звук. Для цього частота ШІМ має бути у межах звукового діапазону, який може сприймати людина. При цьому шпаруватість ШІМ має бути 50%. У даному прикладі до тестової плати підключений п`єзо-електричний бузер (без внутрішнього генератора!). Це звичайна пищавка на кшталт таких, тільки в корпусі:Будь-ласка, не плутайте п`єзо-електричний бузер з магніто-динамічним. Звичайні динаміки не можна напряму підключати до мікроконтролера.
Схема підключення п`єзо-електричного бузера:
#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"
#define SYSCLK 72000000
#define PRESCALER 72
#define C	261	//Do
#define C_	277 //Do#
#define D	293 //Re
#define D_	311 //Re#
#define E	239 //Mi
#define F	349 //Fa
#define F_	370 //Fa#
#define G 	392 //Sol
#define G_	415 //Sol#
#define A	440 //La
#define A_	466 //La#
#define H	494 //Si
#define t1		2000
#define t2		1000
#define t4		500
#define t8		250
#define t16		125
typedef struct
{
	uint16_t freq;
	uint16_t time;
}SoundTypeDef;
#define MUSICSIZE 48
const SoundTypeDef Music[MUSICSIZE] ={
	{C*2, t4},
	{G, t4},
	{A_, t8},
	{F, t8},
	{D_, t8},
	{F, t8},
	{G, t4},
	{C, t2},
	{C*2, t4},
	{G, t4},
	{A_, t8},
	{F, t8},
	{D_, t8},
	{F, t8},
	{G, t4},
	{C*2, t4},
	{0, t8},
	{D_, t8},
	{D_, t8},
	{D_, t8},
	{G, t8},
	{A_, t4},
	{D_*2, t8},
	{C_*2, t8},
	{C*2, t8},
	{C*2, t8},
	{C*2, t8},
	{C*2, t8},
	{A_, t8},
	{F, t8},
	{D_, t8},
	{F, t8},
	{G, t4},
	{C*2, t2},
	{C*2, t2},
	{A_, t8},
	{G_, t8},
	{G, t8},
	{G_, t8},
	{A_, t2},
	{A_, t4},
	{C*2, t4},
	{A_, t8},
	{F, t8},
	{D_, t8},
	{F, t8},
	{G, t4},
	{C*2, t2}
};
int MusicStep = 0;
char PlayMusic = 0;
void StartMusic(void) {
	MusicStep = 0;
	PlayMusic = 1;
	sound(Music[MusicStep].freq, Music[MusicStep].time);
}
void SetSysClockTo72(void)
{
	ErrorStatus HSEStartUpStatus;
    /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration -----------------------------*/
    /* RCC system reset(for debug purpose) */
    RCC_DeInit();
    /* Enable HSE */
    RCC_HSEConfig( RCC_HSE_ON);
    /* Wait till HSE is ready */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();
    if (HSEStartUpStatus == SUCCESS)
    {
        /* Enable Prefetch Buffer */
    	//FLASH_PrefetchBufferCmd( FLASH_PrefetchBuffer_Enable);
        /* Flash 2 wait state */
        //FLASH_SetLatency( FLASH_Latency_2);
        /* HCLK = SYSCLK */
        RCC_HCLKConfig( RCC_SYSCLK_Div1);
        /* PCLK2 = HCLK */
        RCC_PCLK2Config( RCC_HCLK_Div1);
        /* PCLK1 = HCLK/2 */
        RCC_PCLK1Config( RCC_HCLK_Div2);
        /* PLLCLK = 8MHz * 9 = 72 MHz */
        RCC_PLLConfig(0x00010000, RCC_PLLMul_9);
        /* Enable PLL */
        RCC_PLLCmd( ENABLE);
        /* Wait till PLL is ready */
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }
        /* Select PLL as system clock source */
        RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK);
        /* Wait till PLL is used as system clock source */
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    { /* If HSE fails to start-up, the application will have wrong clock configuration.
     User can add here some code to deal with this error */
        /* Go to infinite loop */
        while (1)
        {
        }
    }
}
GPIO_InitTypeDef port;
TIM_TimeBaseInitTypeDef timer;
TIM_OCInitTypeDef timerPWM;
int sound_time;
int sound_counter;
void sound_init(void) {
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, 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);
	TIM_TimeBaseStructInit(&timer);
	timer.TIM_Prescaler = PRESCALER;
	timer.TIM_Period = 0xFFFF;
	timer.TIM_ClockDivision = 0;
	timer.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &timer);
	TIM_OCStructInit(&timerPWM);
	timerPWM.TIM_Pulse = 0;
	timerPWM.TIM_OCMode = TIM_OCMode_PWM1;
	timerPWM.TIM_OutputState = TIM_OutputState_Enable;
	timerPWM.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM4, &timerPWM);
    /* Enable Interrupt by overflow */
    TIM_ITConfig(TIM4, TIM_IT_CC4, ENABLE);
	//TIM_Cmd(TIM4, ENABLE);
    /* Enable Interrupt of Timer TIM2 */
    NVIC_EnableIRQ(TIM4_IRQn);
}
void TIM4_IRQHandler(void){
	if (TIM_GetITStatus(TIM4, TIM_IT_CC4) != RESET)
	  {
	    /* Reset flag */
	    TIM_ClearITPendingBit(TIM4, TIM_IT_CC4);
	    sound_counter++;
	    if (sound_counter > sound_time) {
	    	if (PlayMusic == 0) {
	    		TIM_Cmd(TIM4, DISABLE);
	    	}
	    	else {
	    		if (MusicStep < MUSICSIZE-1) {
	    			if (TIM4->CCR1 == 0){
	    				MusicStep++;
	    				sound(Music[MusicStep].freq, Music[MusicStep].time);
	    			}
	    			else{
	    				sound(0, 30);
	    			}
	    		}
	    		else {
		    		PlayMusic = 0;
		    		TIM_Cmd(TIM4, DISABLE);
	    		}
	    	}
	    }
	    /* over-capture */
	    if (TIM_GetFlagStatus(TIM4, TIM_FLAG_CC4OF) != RESET)
	    {
	      TIM_ClearFlag(TIM4, TIM_FLAG_CC4OF);
	      // ...
	    }
	  }
}
void sound (int freq, int time_ms) {
	if (freq > 0) {
		TIM4->ARR = SYSCLK / timer.TIM_Prescaler / freq;
		TIM4->CCR1 = TIM4->ARR / 2;
	}
	else {
		TIM4->ARR = 1000;
		TIM4->CCR1 = 0;
	}
	TIM_SetCounter(TIM4, 0);
	sound_time = ((SYSCLK / timer.TIM_Prescaler / TIM4->ARR) * time_ms ) / 1000;
	sound_counter = 0;
	TIM_Cmd(TIM4, ENABLE);
}
int main(void)
{
	SetSysClockTo72();
	sound_init();
	//sound (440, 1000);
	StartMusic();
	while(1)
    {
    }
}
Я не зупинився на генеруванні простого монотонного звука, і написав функцію sound(), якій можна задати частоту і тривалість звучання. Але і це ще не все. Я написав ще одну функцію StartMusic(), яка запускає програвання мелодії. І все це вимагає ресурсів лише одного таймера. При цьому основний цикл програми залишається вільний. Тобто, музика грає у фоні і не заважає роботі.
У цій та попередніх статтях ми розглянули основні можливості таймерів, але це далеко не все, на що вони здатні.
Бажаю успіхів!
Дивись також:
- 1. STM32. Програмування STM32F103. Тестова плата. Прошивка через UART та через ST-Link
- 2. STM32. Програмування. IDE для STM32
- 3. STM32. Програмування STM32F103. GPIO
- 4. STM32. Програмування STM32F103. Тактування
- 5. STM32. Програмування STM32F103. USART
- 6. STM32. Програмування STM32F103. NVIC
- 7. STM32. Програмування STM32F103. ADC
- 8. STM32. Програмування STM32F103. DMA
- 9. STM32. Програмування STM32F103. TIMER
- 10. STM32. Програмування STM32F103. TIMER. Захоплення сигналу
- 11. STM32. Програмування STM32F103. TIMER. Encoder
- 12. STM32. Програмування STM32F103. TIMER. PWM
- 13. STM32. Програмування STM32F103. EXTI
- 14. STM32. Програмування STM32F103. RTC
- 15. STM32. Програмування STM32F103. BKP
- 16. STM32. Програмування STM32F103. Flash
- 17. STM32. Програмування STM32F103. Watchdog
- 18. STM32. Програмування STM32F103. Remap
- 19. STM32. Програмування STM32F103. I2C Master
- 20. STM32. Програмування STM32F103. I2C Slave
- 21. STM32. Програмування STM32F103. USB
- 22. STM32. Програмування STM32F103. PWR
- 23. STM32. Програмування STM32F103. Option bytes
- 24. STM32. Програмування STM32F103. Bootloader
- STM32. Скачати приклади
- System Workbench for STM32 Інсталяція на Ubuntu
- Keil uVision5 – IDE для STM32
- IAR Workbench – IDE для STM32
- Керування безколекторним двигуном постійного струму (BLDC) за допомогою STM32
- Керування PMSM за допомогою STM32
Дуже погано, що ти не показуєш як налаштувати конфігурацію через STM32CubeMX.
Цій статті 9 років. Тоді CubeMX був зовсым не популярним :)
Недавні записи
- DShot receiver on STM32
- CRSF to PWM
- U-FOC PC Monitor для Chrome browser
- Фільтрація Back-EMF. Безсенсорні BLDC мотори
- Text to speech. Українська мова
- LCD Display ST7567S (IIC)
- Розпізнавання мови (Speech recognition)
- Selenium
- Комп'ютерний зір (Computer Vision)
- Деякі думки про точність вимірювань в електроприводі
Tags
git wifi encoder solar atmega ssd1331 sensors nvic piezo rfid smd python usb rtc motor timer bmp280 mpu-9250 barometer ethernet html books sms flask ssd1306 adc battery max1674 lcd soldering avr bldc stm32 mongodb 3d-printer remap eeprom dc-dc displays java-script nodemcu programmator gps watchdog gpio raspberry-pi websocket flash rs-232 css hih-4000 foc brushless esp8266 uart meteo bme280 mpu-6050 examples i2c servo capture bluetooth led web options bkp pwm usart exti st-link tim docker ngnix dma pmsm dht11 eb-500 mpx4115a
Архіви




