实验六 无源蜂鸣器控制—PWM

一.实验目标

1.通过编程使用STM32的timer,产生PWM波;

2.通过按键外部中断的方式控制蜂鸣器发出声响。

3.熟悉timer用作pwm输出时基本操作。。

二.知识储备及设计思路

蜂鸣器简介:

蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、打印机、复印机、报警器、电子玩具、汽车电子设备、电话机、定时器等电子产品中作发声器件。蜂鸣器分为有源蜂鸣器和无源蜂鸣器。这里的有源是指有没有自带震荡电路,有源蜂鸣器自带了震荡电路,一通电就会发声;无源蜂鸣器则没有自带震荡电路,必须外部提供1~5Khz左右的方波驱动,才能发声。

开发板板载的蜂鸣器是无源蜂鸣器,需要外部提供方波才能够响。如图所示是板载无源蜂鸣器:

前面我们已经对STM32F4的IO做了简单介绍,前面章节,我们就是利用STM32的IO口直接驱动LED的,本章的蜂鸣器,我们能否直接用STM32的IO口驱动呢?让我们来分析下:STM32F4的单个IO最大可以提供25mA电流(来自数据手册),而蜂鸣器的驱动电流是30mA左右,两者十分相近,但是全盘考虑,STM32F4整个芯片的电流,最大也就150mA,如果用IO口直接驱动蜂鸣器,其他地方用电就得省着点了…所以,我们不用STM32F4的IO直接驱动蜂鸣器,而是通过三极管扩流后再驱动蜂鸣器,这样STM32F4的IO只需要提供不到1mA的电流就足够了。

TIMER简介

定时器(Timer)最基本的功能就是定时了,比如定时发送USART
数据、定时采集AD数据等等。如果把定时器与GPIO
结合起来使用的话可以实现非常丰富的功能,可以测量输入信号的脉冲宽度,可以生产输出波形。定时器生成PWM控制电机状态是工业控制普遍方法。

STM32F4xxx 系列控制器有2 个高级控制定时器、10 个通用定时器和2
个基本定时器,还有2
个看门狗定时器。看门狗定时器不在本章讨论范围,有专门讲解的章节。控制器上所有定时器都是彼此独立的,不共享任何资源。

定时器类 型 Timer 计数器 分辨率 计数器类 型 预分频系数 DMA请 求生成 捕获/ 比较 通道 互补 输出 最大接口时 钟(MHz) 最大定时器 时钟(MHz)
高级控制 TIM1 和 TIM8 16 位 递增、递 减、递增/ 递减 1~65536(整数) 4 84 (APB2) 168
通用 TIM2, TIM5 递增、递 减、递增/ 递减 1~65536(整数) 4 42 (APB1) 84/168
TIM3, TIM4 16 位 递增、递 减、递增/ 递减 1~65536(整数) 4 42 (APB1) 84/168
TIM9 16 位 递增 1~65536(整数) 2 84 (APB2) 168
TIM10, TIM11 16 位 递增 1~65536(整数) 1 84 (APB2) 168
TIM12 16 位 递增 1~65536(整数) 2 42 (APB1) 84/168
TIM13, TIM14 16 位 递增 1~65536(整数) 1 42 (APB1) 84/168
基本 TIM6 和 TIM7 16 位 递增 1~65536(整数) 0 42 (APB1) 84/168

本节中我们使用的是TIM13,我们就介绍TIM13定时器。

通用定时器主要特性:

● 16 位自动重载递增计数器

● 16 位可编程预分频器,用于对计数器时钟频率进行分频(即运行时修改),分频系数
介于 1 和 65536 之间

● 独立通道,可用于:

— 输入捕获

— 输出比较

— PWM 生成(边沿对齐模式)

— 单脉冲模式输出

● 发生如下事件时生成中断:

— 更新:计数器上溢、计数器初始化(通过软件)

— 输入捕获

— 输出比较

通用定时器框图如图:

基本定时器功能框图

本章节我们用定时器实现PWM功能,需要用到timer模块再框图中标注出。

1.时钟源

定时器要实现计数必须有个时钟源,基本定时器时钟只能来自内部时钟,高级控制定时器和通用定时器还可以选择外部时钟源或者直接来自其他定时器等待模式。我们可以通过RCC
专用时钟配置寄存器(RCC_DCKCFGR)的TIMPRE
位设置所有定时器的时钟频率,我们一般设置该位为默认值0,使得定时器(Timer)最基本的功能就是定时了,比如定时发送USART
数据、定时采集AD 数据等等。

STM32F4xxx 系列控制器有2 个高级控制定时器、10 个通用定时器和2
个基本定时器,还有2
个看门狗定时器。看门狗定时器不在本章讨论范围,有专门讲解的章节。控制器上所有定时器都是彼此独立的,不共享任何资源。

基本定时器只能使用内部时钟,当TIM13控制寄存器1(TIMx_CR1)的CEN 位置1
时,启动基本定时器,并且预分频器的时钟来源就是CK_INT。对于高级控制定时器和通用定时器的时钟源可以来找控制器外部时钟、其他定时器等等模式,较为复杂,我们在相关章节会详细介绍。

表中timer13可选的最大定时器时钟为42MHz,即基本定时器的内部时钟(CK_INT)频率为42MHz。

2.控制器

定时器控制器控制实现定时器功能,控制定时器复位、使能、计数是其基础功能。

3.计数器

基本定时器计数过程主要涉及到三个寄存器内容,分别是计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR),这三个寄存器都是16
位有效数字,即可设置值为0 至65535。

预分频器PSC,它有一个输入时钟CK_PSC 和一个输出时钟CK_CNT。输入时钟CK_PSC
来源于控制器部分,基本定时器只有内部时钟源所以CK_PSC
实际等于CK_INT,即90MHz。在不同应用场所,经常需要不同的定时频率,通过设置预分频器PSC
的值可以非常方便得到不同的CK_CNT,实际计算为:fCK_CNT
等于fCK_PSC/(PSC[15:0]+1)。

下图是将预分频器PSC 的值从1 改为4 时计数器时钟变化过程。原来是1 分频,CK_PSC
和CK_CNT 频率相同。向TIMx_PSC 寄存器写入新值时,并不会马上更新CK_CNT
输出频率,而是等到更新事件发生时,把TIMx_PSC
寄存器值更新到影子寄存器中,使其真正产生效果。更新为4 分频后,在CK_PSC 连续出现4
个脉冲后CK_CNT 才产生一个脉冲。

在定时器使能(CEN 置1)时,计数器COUNTER 根据CK_CNT 频率向上计数,即每来一个CK_CNT
脉冲,TIMx_CNT 值就加1。当TIMx_CNT 值与TIMx_ARR
的设定值相等时就自动生成事件并TIMx_CNT
自动清零,然后自动重新开始计数,如此重复以上过程。为此可见,我们只要设置CK_PSC
和TIMx_ARR
这两个寄存器的值就可以控制事件生成的时间,而我们一般的应用程序就是在事件生成的回调函数中运行的。在TIMx_CNT
递增至与TIMx_ARR 值相等,我们叫做为定时器上溢。

自动重载寄存器TIMx_ARR
用来存放于计数器值比较的数值,如果两个数值相等就生成事件,将相关事件标志位置位,生成DMA
和中断输出。TIMx_ARR 有影子寄存器,可以通过TIMx_CR1 寄存器的ARPE
位控制影子寄存器功能,如果ARPE
位置1,影子寄存器有效,只有在事件更新时才把TIMx_ARR 值赋给影子寄存器。如果ARPE
位为0,修改TIMx_ARR 值马上有效。

经过上面分析,我们知道定时事件生成时间主要由TIMx_PSC 和TIMx_ARR
两个寄存器值决定,这个也就是定时器的周期。比如我们需要一个1ms
周期的定时器,具体这两个寄存器值该如何设置内。假设,我们先设置TIMx_ARR
寄存器值为999,即当TIMx_CNT从0 开始计算,刚好等于999 时生成事件,总共计数1000
次,那么如果此时时钟源周期为1us 即可得到刚好1ms 的定时周期。

接下来问题就是设置TIMx_PSC 寄存器值使得CK_CNT 输出为1us
周期(1000000Hz)的时钟。预分频器的输入时钟CK_PSC
为42MHz,所以设置预分频器值为(42-1)即可满足。

4.捕获/比较

本章节使用定时器的PWM输出功能,下图就是一个简单的PWM原理示意图。

图中,我们假定定时器工作在向上计数PWM模式,且当CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。

STM32F4的定时器除了TIM6和7。其他的定时器都可以用来产生PWM输出。其中高级定时器TIM1和TIM8可以同时产生多达7路的PWM输出。而通用定时器也能同时产生多达4路的PWM输出!这里我们仅使用TIM13的CH1产生一路PWM输出。

要使STM32F4的通用定时器TIMx产生PWM输出,除了通用介绍的寄存器外,我们还会用到3个寄存器,来控制PWM的。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。接下来我们简单介绍一下这三个寄存器。

STM32 寄存器简介

首先是捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有2个:TIMx _CCMR1和TIMx
_CCMR2,描述如下:

这里我们需要说明的是模式设置位OC1M,此部分由3位组成。总共可以配置成7种模式,我们使用的是PWM模式,所以这3位必须设置为110/111。这两种PWM模式的区别就是输出电平的极性相反。另外CC1S用于设置通道的方向(输入/输出)默认设置为0,就是设置通道作为输出使用。

TIMx_CCER捕获/比较使能寄存器:

该寄存器比较简单,我们这里只用到了CC1E位,该位是输入/捕获1输出使能位,要想PWM从IO口输出,这个位必须设置为1,所以我们需要设置该位为1。

捕获/比较寄存器(TIMx_CCR1~4),该寄存器总共有4个,对应4个通道CH1~4:

在输出模式下,该寄存器的值与CNT的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制PWM的输出脉宽了。

HAL库函数和cube生成函数介绍

通过以上一些寄存器的操作外加一下IO口的配置,我们就可以达到pwm最基本的配置了,关于timer13更详细的介绍,请参考《STM32F4xx中文参考手册》第445页至483页。接下来我们将着重讲解使用HAL库实现pwm配置和使用的方法。在HAL库中,timer相关的函数和定义主要在文件stm32f4xx_hal_tim.c和stm32f4xx_hal_tim.h中。接下来我们看看HAL库提供的串口相关操作函数。

  1. 通用timer参数初始化(预分频/从装值等)。

串口作为STM32的一个外设,HAL库为其配置了串口初始化函数。接下来我们看看使用CUBE生成的timer初始化函数MX_TIM13_Init

1
void MX_TIM13_Init (void) 

其中用到了初始化函数HAL_SPI_Init相关知识,定义如下:

1
HAL_StatusTypeDef HAL_TIM_Base\_Init(TIM_HandleTypeDef \*htim); 

该函数只有一个入口参数htim,为TIM_HandleTypeDef结构体指针类型,我们俗称其为timer句柄,它的使用会贯穿整个timer程序。一般情况下,我们会定义一个TIM_HandleTypeDef结构体类型全局变量,然后初始化各个成员变量。接下来我们看看结构体TIM_HandleTypeDef的定义:

1
2
3
4
5
6
7
8
9
10
typedef struct
{
TIM_TypeDef *Instance; /*!< Register base address */
TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters */
HAL_TIM_ActiveChannel Channel; /*!< Active channel */
DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_TIM_StateTypeDef State; /*!< TIM operation state */
} TIM_HandleTypeDef;

该结构体成员变量非常多,一般情况下载调用函数HAL_SPI_Init对timer进行初始化的时候,我们只需要先设置Instance和Init两个成员变量的值。接下来我们依次解释一下各个成员变量的含义。

Instance是TIM_TypeDef结构体指针类型变量,它是执行寄存器基地址,实际上这个基地址HAL库已经定义好了,如果是TIM13,取值为TIM13即可。

Init是TIM_Base_InitTypeDef结构体类型变量,它是用来设置TIMER的各个参数,包括预分频系数,自动从装值等,它的使用方法非常简单。

TIM_TypeDef结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct
{
__IO uint32_t CR1;
__IO uint32_t CR2;
__IO uint32_t SMCR;
__IO uint32_t DIER;
__IO uint32_t SR;
__IO uint32_t EGR;
__IO uint32_t CCMR1;
__IO uint32_t CCMR2;
__IO uint32_t CCER;
__IO uint32_t CNT;
__IO uint32_t PSC;
__IO uint32_t ARR;
__IO uint32_t RCR;
__IO uint32_t CCR1;
__IO uint32_t CCR2;
__IO uint32_t CCR3;
__IO uint32_t CCR4;
__IO uint32_t BDTR;
__IO uint32_t DCR;
__IO uint32_t DMAR;
__IO uint32_t OR;
} TIM_TypeDef;

详细的TIM_TypeDef可以查看寄存器描述和数据手册。

2)timer中PWM输出初始化

我们要使输出PWM,前面已经对通用寄存器初始话,还需要输出pwm相关寄存器初始化,

其中用到了初始化函数HAL_TIM_PWM_ConfigChannel相关知识,定义如下:

1
HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef *sConfig, uint32_t Channel);

该函数第一个入口参数选择timer,第二个是pwm配置,第三个是pwm输出通道接下来我们看看结构体TIM_OC_InitTypeDef的定义:

1
2
3
4
5
6
7
8
9
10
typedef struct
{
uint32_t OCMode;
uint32_t Pulse;
uint32_t OCPolarity;
uint32_t OCNPolarity;
uint32_t OCFastMode;
uint32_t OCIdleState;
uint32_t OCNIdleState;
} TIM_OC_InitTypeDef;

该结构体一般情况下载调用函数HAL_TIM_PWM_ConfigChannel对timer进行初始化的时候,我们一般只需要先设置OCMode和Pulse两个成员变量的值。Pulse就是比较器的值,范围0x0000
- 0xFFFF。

详细的TIM_OC_InitTypeDef可以查看寄存器描述和数据手册。

3)timer13初始化

Timer初始化,调用MX_TIM13_Init,具体方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
TIM_HandleTypeDef htim13;

void MX_TIM13_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0};

htim13.Instance = TIM13;
htim13.Init.Prescaler = 167;
htim13.Init.CounterMode = TIM_COUNTERMODE_UP;
htim13.Init.Period = 1000;
htim13.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim13.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim13) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim13) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim13, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_MspPostInit(&htim13);

}

其中MX_TIM13_Init函数会调用初始化函数HAL_TIM_Base_MspInit,配置使用:

1
2
3
4
5
6
7
8
9
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM13)
{
__HAL_RCC_TIM13_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 3, 3);
HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn);
}
}

其中MX_TIM13_Init函数会调用初始化函数HAL_TIM_MspPostInit,使能gpio配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* timHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(timHandle->Instance==TIM13)
{
__HAL_RCC_GPIOF_CLK_ENABLE();
/**TIM13 GPIO Configuration PF8 ------> TIM13_CH1 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF9_TIM13;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
}
}

4) timer开启关闭

STM32F4的开启/关闭timer时,调用如下函数就能够实现开启/关闭timer:

1
2
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim);
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *htim);

三.引脚说明与硬件连接

图1.3.8为无源蜂鸣器的硬件连接图。通过STM32F4向其对应引脚输出低频的PWM波,可以使其发声。表1.3.7为其引脚说明。

1.3.8 蜂鸣器硬件连接图

1.3.7 蜂鸣器引脚说明

设备名 引脚号
BEEP PF8

在单片机管脚输出1khz的方波,无源蜂鸣器就能够发出“滴滴”响声。

四.程序设计

本实验,在main函数中添加了少部分代码,主要实现开启pwm1,和按键控制蜂鸣器开启/关闭:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(void)
{
uint8_t key1_value=0,key2_value=0; //按键状态值
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM13_Init();
HAL_TIM_PWM_Start(&htim13,TIM_CHANNEL_1);//开启PWM通道1
while (1)
{
//按键控制LED灯点亮
key1_value=HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_6);
key2_value=HAL_GPIO_ReadPin(GPIOG,GPIO_PIN_7);
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_6|GPIO_PIN_7, key2_value);
HAL_GPIO_WritePin(GPIOI, GPIO_PIN_10|GPIO_PIN_11, key1_value);
//按键1控制蜂鸣器
if(key1_value == GPIO_PIN_RESET)
{
HAL_TIM_Base_MspInit(&htim13);
}
else
{
HAL_TIM_Base_MspDeInit(&htim13);
}
}
}

五.实验结果

按下按键KEY18,蜂鸣器会发出“滴滴”声,松开按键,蜂鸣器不发声。

六.STM32CubeMX配置硬件timer13

经过前面多个章节的学习,大家对STM32CubeMX配置已经非常熟悉.
,出于篇幅考虑,我们将不再像之前章节一样讲解那么详细,我们将只会列出配置的关键点,然后生成工程,大家自行与提供的实验代码对照学习.

Tim13配置配置如下: