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
Недавні записи
- Text to speech. Українська мова
- LCD Display ST7567S (IIC)
- Розпізнавання мови (Speech recognition)
- Selenium
- Комп'ютерний зір (Computer Vision)
- Деякі думки про точність вимірювань в електроприводі
- Датчики Холла 120/60 градусів
- Модуль драйверів напівмосту IGBT транзисторів
- Драйвер IGBT транзисторів на A316J
- AS5600. Варіант встановлення на BLDC мотор
Tags
docker sensors rtc led timer wifi remap sms css websocket mongodb bme280 rfid bluetooth esp8266 nodemcu bkp piezo bmp280 i2c mpu-6050 encoder examples avr brushless ngnix nvic displays bldc java-script pmsm barometer pwm lcd ethernet stm32 raspberry-pi capture usart gpio exti atmega meteo solar smd dma adc eeprom eb-500 flask python ssd1306 uart options flash mpx4115a html gps st-link 3d-printer servo dc-dc foc git books battery hih-4000 watchdog dht11 web tim programmator ssd1331 mpu-9250 usb rs-232 motor max1674 soldering
Архіви