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 був зовсым не популярним :)
Tags
bme280 bmp280 gps mpu-6050 options stm32 ssd1331 ssd1306 eb-500 3d-printer soldering tim mpu-9250 dma watchdog piezo exti web raspberry-pi docker ngnix solar bluetooth foc html css brushless flask dc-dc capture gpio avr rs-232 mpx4115a atmega mongodb st-link barometer pwm nvic git java-script programmator dht11 hih-4000 pmsm encoder max1674 smd sensors rtc adc lcd motor timer meteo examples i2c usb flash sms rfid python esp8266 servo books bldc remap eeprom bkp battery ethernet uart usart displays led websocket nodemcu wifi
Архіви




