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

В предыдущих статьях мы познакомились с тем, как таймеры могут захватывать входной сигнал. Таймеры микроконтроллера 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. Сервопривод

Некоторые устройства управляются 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 (), которая запускает проигрывание мелодии. И все это требует ресурсов только одного таймера. При этом основной цикл программы остается свободный. То есть, музыка играет в фоне и не мешает работе контроллера.

В этой и предыдущих статьях мы рассмотрели основные возможности таймеров, но это далеко не все, на что они способны.

Желаю успехов!

Смотри также:

4 комментария: 12. STM32. Программирование STM32F103. TIMER. PWM

  • Mark говорить:

    Спасибо большое.
    А с библиотекой HAL, Вы можете повторить.

    • andre говорить:

      Я планировал и HAL. Но это будет позже. Возможно, ближе к осени.

  • Алексей Молотов говорить:

    Замечательный цикл статей низкий вам поклон! Очень приятно видеть развитие электроники на территории стран СНГ не взирая на то что творит власть. Ещё раз спасибо!

Translate
Архіви

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