сайт для палких паяльників

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. Це два режими PWM. 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 – інвертований.

Решта параметрів для advanced таймерів. Їх будемо розглядати пізніше.

PWM. Яскравість світлодіода

Перший приклад: ми будемо змінювати яскравість світлодіода. Схема підключення:

STM32F103C8_LED

#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 світлодіода. Схема:

STM32F103C8_RGB_LED

#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“. У цій статті також наведено параметри сигналу для керування сервою. Тому ми не будемо заглиблюватися у деталі керування сервою. Ціль цієї статті: продемонструвати різноманіття пристроїв, якими можна керувати, використовуючи можливості таймерів мікроконтролера. Тому я просто наведу приклад керування сервою. Схема підключення:

STM32F103C8_SERVO

#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%. У даному прикладі до тестової плати підключений п’єзо-електричний бузер (без внутрішнього генератора!). Це звичайна пищавка на кшталт таких, тільки в корпусі:

piezo buzzer

Будь-ласка, не плутайте п’єзо-електричний бузер з магніто-динамічним. Звичайні динаміки не можна напряму підключати до мікроконтролера.

Схема підключення п’єзо-електричного бузера:

STM32F103C8_SOUND

#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(), яка запускає програвання мелодії. І все це вимагає ресурсів лише одного таймера. При цьому основний цикл програми залишається вільний. Тобто, музика грає у фоні і не заважає роботі.

У цій та попередніх статтях ми розглянули основні можливості таймерів, але це далеко не все, на що вони здатні.

Бажаю успіхів!

Дивись також:

Translate
Архіви

© 2011-2018 Андрій Корягін, Кременчук - Київ, Україна