PWM蜂鸣器基础知识参考:
51单片机外设篇:蜂鸣器_路溪非溪的博客-CSDN博客
查看原理图
根据原理图可知,只要在PA8端口,即TIM2和输入某种频率的PWM波即可发出声响。
用51的老办法,手动构造一个方波信号,也能驱动蜂鸣器。
那么,在32中,是怎么实现的呢?
PWM实现
在32中,可以通过通用定时器或者高级定时器来实现PWM,在这里的硬件电路上选择的是高级定时器TIM1(拥有通用寄存器的所有功能)来实现的。
查阅数据手册。
脉冲宽度调制(PWM)模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。
此功能无需产生中断,只需要控制定时器不断输出一个PWM波形即可。
其中,计数器确定频率,比较器确定占空比。
举例说明:
假如分频后的频率是1MHz,那么单个时钟周期就是1us。
此时,我如何构造一个1KHz的PWM波形?
1KHz对应的周期是1ms,那么,就需要计数1000个1us才可以。
按照占空比为50%,那么就需要在前500个计数周期内高电平(或者低电平),后500个周期内低电平(或者高电平);如此往复循环。
配置MX
配置时钟:
高级定时器的时钟来自于APB2
将PA8配置成TIM1_CH1(不止一个端口可以被配置成TIM1_CH1,比如PE9也可以被配置成TIM1_CH1,配置时要注意下)其他和PWM波无关的暂时不用管。
具体参数设置:
至此,完成MX配置。
比较简单,就是频率的设置,以及占空比的设置。
代码实现
实现功能:通过按键KEY1实现蜂鸣器的开启和关闭,通过基本定时器TIM6改变蜂鸣器的频率。
PWM波相关函数,在文件stm32f1xx_hal_tim.h中:
/** @addtogroup TIM_Exported_Functions_Group3 TIM PWM functions
* @brief TIM PWM functions
* @{
*/
/* Timer PWM functions ********************************************************/
HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_PWM_DeInit(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);
void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef *htim);
/* Blocking mode: Polling */
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_StatusTypeDef HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel);
/* Non-Blocking mode: Interrupt */
HAL_StatusTypeDef HAL_TIM_PWM_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
HAL_StatusTypeDef HAL_TIM_PWM_Stop_IT(TIM_HandleTypeDef *htim, uint32_t Channel);
/* Non-Blocking mode: DMA */
HAL_StatusTypeDef HAL_TIM_PWM_Start_DMA(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t *pData, uint16_t Length);
HAL_StatusTypeDef HAL_TIM_PWM_Stop_DMA(TIM_HandleTypeDef *htim, uint32_t Channel);
这里,我们重点关注这个函数:
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
创建文件,buzzer.c和buzzer.h
buzzer.h
#ifndef _BUZZER_H_
#define _BUZZER_H_
typedef enum
{
CLOSE,OPEN
} buzzer_state;
typedef struct
{
buzzer_state buzzerState;
void (*buzzerOpen)(void);
void (*buzzerClose)(void);
} buzzer_handler;
extern buzzer_state buzzerState;
extern buzzer_handler buzzerHandler;
#endif
buzzer.c
#include "myapplication.h"
static void BuzzerOpen(void);
static void BuzzerClose(void);
buzzer_handler buzzerHandler =
{
CLOSE,
BuzzerOpen,
BuzzerClose
};
static void BuzzerOpen(void)
{
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);//这里的通道别搞错了,我一开始写成了HAL_TIM_ACTIVE_CHANNEL_1
}
static void BuzzerClose(void)
{
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_1);
}
再新增key.c和key.h
key.h
#ifndef _KEY_H_
#define _KEY_H_
typedef struct
{
void (*key1BuzzerSORP)(void);
} key_handler;
extern key_handler keyHandler;
#endif
key.c
#include "myapplication.h"
static void Key1BuzzerSORP(void);
key_handler keyHandler =
{
Key1BuzzerSORP
};
static void Key1BuzzerSORP(void)
{
if(buzzerHandler.buzzerState == CLOSE)
{
buzzerHandler.buzzerOpen();
buzzerHandler.buzzerState = OPEN;
}
else
{
buzzerHandler.buzzerClose();
buzzerHandler.buzzerState = CLOSE;
}
}
之后,在按键1的外部中断函数中调用按键1处理函数
//重写外部中断函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
case KEY0_Pin :
keyHandler.key1BuzzerSORP();
led_operater_middle.ledMiddle(LED1, LedExtinguish);
break;
case KEY1_Pin :
printf("second key is running.\n\r");
led_operater_middle.ledMiddle(LED2, LedExtinguish);
break;
case KEY2_Pin :
printf("third key is running.\n\r");
led_operater_middle.ledMiddle(LED3, LedExtinguish);
break;
case KEY3_Pin :
printf("forth key is running.\n\r");
relayObj.relayOpen();
break;
default:
printf("key fault!please click right key.\n\r");
}
}
到了这里,就完成了按键一控制蜂鸣器开启或者关闭的功能。
接下来,需要实现改变蜂鸣器频率的功能。
蜂鸣器频率的改变取决于装载值和比较值。
当前的装载值是999,比较值是498,也就是说,周期是1ms,占空比50%。
为了改变这两个值,就需要去改变相应的寄存器的值。
当前设置,一旦开启,每1秒改变一次频率。让频率往更大的方向去变化(越来越尖锐),频率越大,周期越短,装载值也就越小。
那么,就每隔1秒让装载值减少100,直到减少到499,就再循环回去从999开始。
相对应的,比较值都是一半,保证占空比都是50%。
那么,怎么去改变这两个寄存器的值呢?
通过寄存器的结构体指针去访问对应的寄存器。
TIM1 -> ARR来改变装载值;
TIM1 -> CCR1来改变通道1的比较值。
TIM1是个结构体指针的宏定义。
那么,我们就再TIM6的回调函数中实现这个功能。
//重写TIM6中断调用函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if((htim->Instance) == (htim6.Instance) & buzzerHandler.buzzerState == OPEN)
{
if(++circleCount == TIME_COUNT_1S)
{
//改变频率
TIM1->ARR -= 100;
if(TIM1->ARR < 100)
{
TIM1->ARR = 999;
}
TIM1->CCR1 = (TIM1->ARR - 1)/2;
printf("current arr is %d\n\r", TIM1->ARR);
circleCount = 0;
}
}
}
注意:别忘了开启定时器6。
尝试发出do、re、mi、fa、sol、la、si
以国际标准音A-la-440HZ为准:
do的频率为261.6HZ,相应的计数值为3822
re的频率为293.6HZ,相应的计数值为3406
mi的频率为329.6HZ,相应的计数值为3034
fa的频率为349.2HZ,相应的计数值为2864
sol的频率为392HZ,相应的计数值为2551
la的频率为440HZ,相应的计数值为2272
si的频率为493.8HZ,相应的计数值为2025
//重写TIM6中断调用函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if((htim->Instance) == (htim6.Instance) & buzzerHandler.buzzerState == OPEN)
{
uint16_t fluency[] = {3822, 3406, 3034, 2864, 2552, 2272, 2026};
static uint8_t fluencyCount = 0;
if(++circleCount == TIME_COUNT_1S)
{
//改变频率
TIM1->ARR = fluency[fluencyCount];
if(fluencyCount++ == 6)
{
fluencyCount = 0;
}
TIM1->CCR1 = TIM1->ARR / 2;
printf("current arr is %d\n\r", TIM1->ARR);
circleCount = 0;
}
}
}
|