1 本节介绍
📝本节您将学习如何通过将K230开发板的GPIO引脚复用为PWM功能并输出PWM信号;实现输出PWM信号及控制板载无源蜂鸣器发出声音。
🏆学习目标
1️⃣如何将GPIO引脚配置为PWM模式,通过40Pin排针中的部分引脚来输出PWM信号。
2️⃣如何驱动板载的无源蜂鸣器,实现让蜂鸣器鸣叫及播放简单的音调。
2 名词解释
名词 | 说明 |
---|---|
PWM | Pulse width modulation (脉宽调制) |
无源蜂鸣器 | 是一种需要外部信号源提供音频频率的电子器件,用于产生声音。 |
INFO
- 脉冲宽度调制(PWM)是一种通过改变脉冲的宽度来控制电信号平均功率的技术。在PWM中,脉冲的频率一般保持恒定,但脉冲的宽度(有效电平的时间)根据需要的模拟信号变化,从而实现对电机速度、LED调光和温度控制等的精确控制。
- 无源蜂鸣器是一种电子发声器件,直接供电并不会鸣叫,只有当输入信号的频率变化时,其发出的声音的频率和音调才会发生相应变化。主要用来产生简单的音调来警示用户。
3 什么是PWM?
PWM(Pulse Width Modulation,脉宽调制)是一种在嵌入式系统中常用的技术,它可以用来模拟信号,控制设备的功率输出或者实现对设备的精确控制。PWM信号是一种类似于方波的信号,具有固定的频率,但脉冲宽度(占空比)可以调整。在一定频率下,我们可以通过调整这个占空比来改变他的有效电压,在一定程度上可以实现D/A转换(数字量转模拟量,不过一般来说都是用DAC,本开发板K230的DAC已经被连接到了3.5mm耳机孔上面了,可以用来播放音频)。
- 频率(Frequency):指PWM信号在一秒内循环的次数。频率是周期的倒数,单位是赫兹(Hz)。
- 周期(Period):指一个完整的PWM信号的时间长度,与频率成反比。单位是秒(s)。
- 脉宽(Pulse Width):指PWM信号中高电平(通常为1)的时间长度。单位是秒(s)或毫秒(ms)。
- 占空比(Duty Ratio):表示在一个完整的PWM信号周期内,高电平(通常为1)所占的时间比例。占空比 = (脉宽 / 周期)x 100%。
- 上升沿(Rising Edge):PWM信号从低电平跳变到高电平的瞬间,通常用来作为触发事件。
- 下降沿(Falling Edge):PWM信号从高电平跳变到低电平的瞬间,也常被用作触发事件。
- 正脉冲宽度(Positive Pulse Width):PWM信号中高电平的持续时间,一般情况下的脉宽指的就是这个。
- 负脉冲宽度(Negative Pulse Width):PWM信号中低电平的持续时间。
在嵌入式系统中,PWM的应用场景非常广泛,例如:
- 电机控制:通过调整PWM的占空比,可以精确控制直流电机的转速。占空比越高,电机转速越快;占空比越低,电机转速越慢。
- LED亮度调节:通过调整PWM的占空比,可以实现对LED灯的亮度调节。占空比越高,LED灯越亮;占空比越低,LED灯越暗。
举个例子:假设我们有一个LED灯,想实现亮度调节。我们可以生成一个PWM信号,频率为1kHz(周期为1ms),然后通过调整占空比来实现LED灯的亮度调节。如果占空比为100%,那么LED灯将一直处于亮的状态;如果占空比为50%,那么LED灯将以1ms为周期,半亮半暗;如果占空比为0%,那么LED灯将一直处于熄灭状态。通过不断调整占空比,就可以实现LED灯的亮度调节。如果PWM信号的频率过低,我们可能会感觉到它在闪烁。所以,在设计PWM驱动的LED灯时,一般会选择较高的频率来避免可见的闪烁。通常我们是看不到它闪烁的,这主要是因为两个原因:第一个是PWM信号的频率够高,第二个是人眼的视觉暂留效应。即当光源瞬间消失时,我们还能在极短的时间内感知到光源。这种效应导致人眼对快速闪烁的光源产生平滑的感觉。当PWM频率足够高时,视觉暂留效应会使我们感觉到LED灯的亮度是连续的。
4 手机屏幕的亮度是怎么调节的?
我们平时一直摸的手机屏幕有一部分就是用PWM来调节的。在智能手机屏幕中,PWM调光和DC调光是两种常见的屏幕亮度调节技术。它们的主要区别在于亮度调节的实现方式。
PWM调光:如之前所说,PWM(脉宽调制)调光是通过调整占空比来控制屏幕亮度的。在这种方法中,屏幕背光源会周期性地开启和关闭。占空比越高,背光源亮的时间越长,屏幕亮度越高;占空比越低,背光源亮的时间越短,屏幕亮度越低。
DC调光:DC(直流)调光是通过调整屏幕背光源的电流来实现亮度调节的。在这种方法中,背光源会一直保持开启状态,但电流的大小会改变,从而调整屏幕亮度。
这两种调光技术各有优缺点:
PWM调光优缺点:
- 优点:能实现较高的亮度范围和对比度,通常在高亮度下表现更好。
- 缺点:在低频率下,PWM调光可能导致屏幕闪烁,对部分人来说可能引起眼睛疲劳和不适。此外,对于快速拍照或录像时,PWM调光可能导致出现条纹或闪烁的现象。
DC调光优缺点:
- 优点:在低亮度下表现更好,因为屏幕背光源一直保持开启状态,不会出现闪烁现象,对眼睛更友好。
- 缺点:在高亮度下,对比度和亮度范围可能不如PWM调光好。此外,DC调光可能导致背光源的寿命降低和能耗略高。
各种技术的选择取决于手机制造商的考虑和市场需求。有一些高端手机是用的混合调光,在高亮度的模式下用DC调光,亮度低于一定值后用PWM调光。
5 开发板上可用的PWM信号
40PIN排针引出脚:
从上图可以看到,我们排针处有以下PWM可供用户使用。
排针引脚号 | 芯片引脚号 | PWM通道号 |
---|---|---|
12 | GPIO 47 | PWM3 |
26 | GPIO 61 | PWM1 |
32 | GPIO 46 | PWM2 |
33 | GPIO 52 | PWM4 |
35 | GPIO 42 | PWM0 |
板载无源蜂鸣器电路:
这个就是我们的板载无源蜂鸣器了,蜂鸣器可以将电信号转化为声音信号,可以向用户提供声音反馈或者警报信号。 蜂鸣器从构造类型上有电磁式和电压式两种,从驱动方式上来说有无源(由外部方波驱动)和有源(由内部驱动,外部给电就行)两种,这里选择的是无源电磁式贴片蜂鸣器,工作电压2.5-4.5v,频率4000Hz,这里的频率是指他在这个频率下的声音最响。大家可以用这个蜂鸣器来做人机交互的提示,也可以用不同的PWM来驱动这个蜂鸣器来播放简单的纯音调音乐。
D15在这里的主要作用是保护这个驱动的MOS管,因为蜂鸣器和电机一样是一个感性元件,也就是说它的电流是不能瞬变的。必须有一个续流二极管提供续流。如果没有这个续流二极管,停止给蜂鸣器供电的时候在蜂鸣器两端会有反向感应电动势,产生高达几十V的尖峰电压,很有可能损坏驱动电路。 R89:限流电阻,防止电流太大损坏芯片的PWM输出引脚。R90就是一个简单的下拉电阻了。
驱动蜂鸣器的芯片引脚号 | PWM通道号 |
---|---|
GPIO 43 | PWM1 |
板载屏幕背光驱动电路:
这个屏幕背光驱动电路,可以简单把他当成一个恒流源。
LCD_EN
可以是一个使能电平,也可以是PWM信号。使能电平只能控制屏幕的亮灭,使用PWM信号时可以调节屏幕背光的平均亮度,通过改变PWM信号的占空比(就是高电平时间和整个周期时间的比值)。
芯片FB脚连接了两个电阻,FB脚的阻值决定了驱动芯片的最大输出电流,这里采用两个0603封装的电阻主要是为了 方便大家焊接 和 通过两个电阻组合出不常用的阻值。FB脚通过这部分电阻来检测通过屏幕背光LED的电流,并将此电流值反馈给驱动芯片。驱动芯片根据反馈电流与设定值进行比较,从而调整输出,使之维持恒流状态。这里两个电阻默认是贴两个3.6欧姆的,按照他的计算公式I(led)=0.2V/R(cs)
,这里设定的最大电流是111mA,一般驱动大尺寸(10寸以上)的屏幕就需要设置为100MA以上的驱动电流,这里是为了兼容泰山派,和泰山派共用一款3.1寸屏幕扩展板,我们默认适配的屏幕扩展板上面是自带一个适配小屏幕的驱动电流的。这样处理可以让我们的板子既能驱动大屏幕,也能驱动小屏幕。
同时,我们目前选用的这个31p屏幕接口的MIPI线序,是可以直接接入市面上常见的大屏幕的(小屏幕一般没有这个接口,需要转换),也就是说理论上大屏幕不需要额外的扩展板就可以直接接入我们的DSI接口进行驱动,当然还需要软件再适配一下。
驱动板载背光驱动的芯片引脚号 | PWM通道号 |
---|---|
GPIO 25 | PWM5 |
⚠️注意!
驱动蜂鸣器的PWM通道是PWM1,而排针处的26号引脚(GPIO 61)的通道也是PWM1,因此它们无法同时独立使用PWM功能。
K230内部包含两个PWM硬件模块,每个模块有三个输出通道。每个模块的输出频率可调,但同一模块内的三个通道共享同一频率,而占空比可独立调整。因此,通道 0、1和2 输出频率相同,通道 3、4和5 输出频率也相同。如果同时使用蜂鸣器(PWM1)和排针上的PWM通道(例如PWM0、PWM1、PWM2),需要注意它们的频率是耦合的,无法单独调整。
这里优先考虑的是板载功能的互斥,所以驱动蜂鸣器用的是PWM1
,背光驱动用的是PWM5
,这两个PWM通道之间没有耦合,可以随意调整。如果大家同时用到蜂鸣器和背光驱动的时候就要注意了,如果此时使用排针的PWM信号,那么他的频率也就是周期其实你已经不能改了,否则就会互相影响。
6 PWM使用指南
6.1 将引脚的复用功能配置为PWM模式
使用一个GPIO的复用功能,首先要做的就是要把他配置为相应的模式,按我们之前在GPIO和FPIOA章节中所说。这里不再赘述。先用FPIOA
的set_function
方法把对应GPIO配置为PWM模式。如下所示:
from machine import FPIOA
# 创建FPIOA对象,用于初始化引脚功能配置
fpioa = FPIOA()
# 将排针处有PWM功能的引脚配置为PWM功能
fpioa.set_function(47, FPIOA.PWM3)
fpioa.set_function(61, FPIOA.PWM1)
fpioa.set_function(46, FPIOA.PWM2)
fpioa.set_function(52, FPIOA.PWM4)
fpioa.set_function(42, FPIOA.PWM0)
# 将蜂鸣器驱动脚GPIO43配置为PWM功能
fpioa.set_function(43, FPIOA.PWM1)
# 将屏幕背光驱动驱动脚GPIO25配置为PWM功能
fpioa.set_function(25, FPIOA.PWM5)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
6.2 配置PWM模块
⚠️注意!
K230 内部包含两个 PWM 硬件模块,每个模块具有三个输出通道。每个模块的输出频率可调,但三个通道共享同一时钟,而占空比则可独立调整。因此,通道 0、1 和 2 输出频率相同,通道 3、4 和 5 输出频率也相同。这里优先考虑的是板载功能的互斥,所以驱动蜂鸣器用的是PWM1
,背光驱动用的是PWM5
,这两个PWM通道之间没有耦合,可以随意调整。如果大家同时用到蜂鸣器和背光驱动的时候就要注意了,如果此时使用排针的PWM信号,那么他的频率也就是周期其实你已经不能改了,否则就会互相影响。
要使用 machine.PWM
,首先需要导入该模块:
from machine import PWM
6.2.1 构造函数
用于构造pin对象,可同时对引脚进行初始化。
pwm = PWM(channel, freq, duty=50, enable=False)
参数
channel
: PWM 通道号,取值范围为 [0, 5]。具体能用哪些引脚请看上面的 开发板上可用的PWM信号 介绍。freq
: PWM 通道输出频率。单位为Hz。duty
: PWM 通道输出占空比,表示高电平在整个周期中的百分比,取值范围为 [0, 100],支持小数点。可选参数,默认值为 50。enable
: PWM 通道输出是否立即使能,可选参数,默认值为 False。
6.2.2 freq
方法
pwm.freq([freq])
获取或设置 PWM 通道的输出频率。
参数
freq
: PWM 通道输出频率,可选参数。如果不传入参数,则返回当前频率。
返回值
返回当前 PWM 通道的输出频率或空。
示例
# 获取当前频率
current_freq = pwm.freq()
print("当前频率为:", current_freq, "Hz")
# 设置新的频率
pwm.freq(4000) # 将频率设置为4000Hz
2
3
4
5
6
6.2.3 duty
方法
pwm.duty([duty])
获取或设置 PWM 通道的输出占空比。
参数
duty
: PWM 通道输出占空比,可选参数。支持小数点。如果不传入参数,则返回当前占空比。
返回值
返回当前 PWM 通道的输出占空比或空。
示例
【TODO】:当前有BUG,duty设置的占空比好像不太对劲,是 100%-当前的设定值。
# 获取当前占空比
current_duty = pwm.duty()
print("当前占空比为:", current_duty, "%")
# 设置新的占空比
pwm.duty(75) # 将占空比设置为75%
2
3
4
5
6
6.2.4 enable
方法
pwm.enable(enable)
使能或禁用 PWM 通道的输出。
参数
enable
: 是否使能 PWM 通道输出。True
||1
False
||0
返回值
无
6.2.5 deinit
方法
pwm.deinit()
释放 PWM 通道的资源。
7 控制排针引脚输出PWM信号
import time
from machine import PWM, FPIOA
# 配置排针引脚号12,芯片引脚号为47的排针复用为PWM通道3输出
pwm_io = FPIOA()
pwm_io.set_function(47, FPIOA.PWM3)
# 初始化PWM参数
pwm = PWM(3, 2000, 50, enable=True) # 默认频率2kHz,占空比50%
2
3
4
5
6
7
8
9
import time
from machine import PWM, FPIOA
# 配置排针引脚号26,芯片引脚号为61的排针复用为PWM通道1输出
pwm_io = FPIOA()
pwm_io.set_function(61, FPIOA.PWM1)
# 初始化PWM参数
pwm = PWM(1, 2000, 50, enable=True) # 默认频率2kHz,占空比50%
2
3
4
5
6
7
8
9
import time
from machine import PWM, FPIOA
# 配置排针引脚号32,芯片引脚号为46的排针复用为PWM通道2输出
pwm_io = FPIOA()
pwm_io.set_function(46, FPIOA.PWM2)
# 初始化PWM参数
pwm = PWM(2, 2000, 50, enable=True) # 默认频率2kHz,占空比50%
2
3
4
5
6
7
8
9
import time
from machine import PWM, FPIOA
# 配置排针引脚号33,芯片引脚号为52的排针复用为PWM通道4输出
pwm_io = FPIOA()
pwm_io.set_function(52, FPIOA.PWM4)
# 初始化PWM参数
pwm = PWM(4, 2000, 50, enable=True) # 默认频率2kHz,占空比50%
2
3
4
5
6
7
8
9
import time
from machine import PWM, FPIOA
# 配置排针引脚号35,芯片引脚号为42的排针复用为PWM通道0输出
pwm_io = FPIOA()
pwm_io.set_function(42, FPIOA.PWM0)
# 初始化PWM参数
pwm = PWM(0, 2000, 50, enable=True) # 默认频率2kHz,占空比50%
2
3
4
5
6
7
8
9
有条件的话可以把对应排针接入示波器,调整好时基,坐标等就能看到输出2KHz,占空比为50%的PWM信号了,如下图所示: 横坐标为时间,每一个格子为250微秒,也就是0.25ms,正好对应2kHz的频率。从图中可以看到,信号的高电平持续时间是250微秒,占整个周期的一半,也就是占空比为50%。 建议大家在代码中调整PWM参数,然后去观察信号的变化。
8 控制板载无源蜂鸣器
知道了啥是PWM,以及如何控制PWM后,我们就可以驱动板载蜂鸣器来播放声音了。
8.1 什么是蜂鸣器
大家可以先去立创商城看看蜂鸣器都有哪些,点这个链接。
它是一种常见的声音输出设备,可以发出各种声音或音调。它们广泛应用于家用电器、电子设备、汽车、安全系统等领域。以下是蜂鸣器的主要种类及其使用场景。
蜂鸣器从构造类型上有电磁式和电压式两种:
电磁式蜂鸣器:
它是利用电磁原理来产生声音,内部由线圈、电磁铁和振动膜片组成。电流通过线圈时,产生的磁场驱动振动膜片振动,从而发出声音。声音较大,但能耗高,容易受到磁场干扰,且对工作电压要求较为严格。适用于低频应用、需要较大音量的场合,适合工业环境或报警器等。
压电式蜂鸣器:
它是基于压电效应原理,利用压电陶瓷材料在外加电场作用下发生形变,驱动振动膜片发出声音。其功耗低,体积小,工作电压范围很广。但低频性能较差,声音是比较小的。适用于对体积和功耗有要求的电子设备。
从驱动方式来说蜂鸣器主要分为两大类:有源蜂鸣器和无源蜂鸣器。
有源蜂鸣器(Active Buzzer):
有源蜂鸣器内部集成了一个振荡电路,当直接接入电源时就可以发出声音。由于内部已经集成了振荡电路,有源蜂鸣器的控制相对简单,只需要提供一个恒定的电源电压即可。但是,这种蜂鸣器的音调和音量调节较为有限。
无源蜂鸣器(Passive Buzzer):
与有源蜂鸣器不同,无源蜂鸣器没有内置振荡电路。要使无源蜂鸣器发声,需要提供一个外部的交流信号(PWM信号)。这种蜂鸣器的优点是可以通过调整外部信号的频率和占空比来实现更丰富的音调和音量控制。
我这里选用的无源蜂鸣器的C编号是C95297,是一个贴片无源电磁式蜂鸣器,他的驱动频率是4000Hz,也就是说我们的PWM频率在4KHz时声音是最响的。
8.2 简单鸣叫一声
下面代码的主要功能就是让蜂鸣器发出一个短暂的4kHz(50%占空比)声音,然后在50毫秒后关闭。
import time
from machine import PWM, FPIOA
# 配置蜂鸣器IO口功能
beep_io = FPIOA()
beep_io.set_function(43, FPIOA.PWM1)
# 初始化蜂鸣器PWM通道
beep_pwm = PWM(1, 4000, 50, enable=False) # 默认频率4kHz,占空比50%
# 使能PWM通道输出
beep_pwm.enable(1)
# 延时50ms
time.sleep_ms(50)
# 关闭PWM输出 防止蜂鸣器吵闹
beep_pwm.enable(0)
# 叫完了就释放PWM
beep_pwm.deinit()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
开头就是导入time
库用来延时,PWM用来控制引脚输出PWM信号,FPIOA用来将引脚复用为PWM功能。
接下来实例化FPIOA,设置蜂鸣器的驱动脚GPIO43
为PWM通道1输出模式。设置频率为4KHz,占空比为50%,enable=False
表示初始化时关闭PWM输出,即默认状态下蜂鸣器不发声。接下来调用beep_pwm.enable(1)
让蜂鸣器开始发出4Khz的声音,延时50ms后先关闭PWM输出,来停止蜂鸣器的发声,最后释放一下PWM通道资源,防止在不断电的情况下继续运行其他程序造成的资源占用。
8.3 播放【一闪一闪亮晶晶】
下面代码的主要功能就是通过定义音符频率来控制蜂鸣器来发出《一闪一闪亮晶晶》旋律的声音。
下述程序的流程图如下:
graph TD A[程序开始] --> B[配置蜂鸣器IO] B --> C[初始化蜂鸣器PWM] C --> D{遍历旋律} D -- 还有音符未播放--> E[获取音符频率] E --> F{频率 > 0?} F -- 是 --> G[设置频率并播放音符] G --> H[音符持续时间] H --> I[停止蜂鸣器并暂停50ms] F -- 否 --> I I --> D D -- 遍历结束--> J[释放PWM资源] J --> K[程序结束]
import time
from machine import PWM, FPIOA
# 配置蜂鸣器IO口功能
beep_io = FPIOA()
beep_io.set_function(43, FPIOA.PWM1)
# 初始化蜂鸣器
beep = PWM(1, 1000, 50, enable=False) # 默认频率1kHz,占空比50%
# 定义音符频率(以Hz为单位)
notes = {
'C4': 261,
'D4': 293,
'E4': 329,
'F4': 349,
'G4': 392,
'A4': 440,
'B4': 493,
'C5': 523
}
# 定义《一闪一闪亮晶晶》旋律和节奏 (音符, 时长ms)
melody = [
('C4', 500), ('C4', 500), ('G4', 500), ('G4', 500),
('A4', 500), ('A4', 500), ('G4', 1000),
('F4', 500), ('F4', 500), ('E4', 500), ('E4', 500),
('D4', 500), ('D4', 500), ('C4', 1000)
]
def play_tone(note, duration):
"""播放指定音符"""
frequency = notes.get(note, 0) # 获取音符对应的频率
if frequency > 0:
beep.freq(frequency) # 设置频率
beep.enable(True) # 启用蜂鸣器
time.sleep_ms(duration) # 持续播放指定时间
beep.enable(False) # 停止蜂鸣器
time.sleep_ms(50) # 音符之间的短暂停顿
# 播放旋律
for note, duration in melody:
play_tone(note, duration)
# 释放PWM资源
beep.deinit()
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
上面这个程序的开头和上一个程序是一样的,都是导入需要的模块及配置蜂鸣器的PWM输出,这里就不再赘述。
不同的是定义了一个音符频率表notes
,单位是Hz,对应了标准的音符。然后定义了旋律和节奏melody
,包含具体的音符和持续时间的列表,每个元组表示一个音符和它的时长(单位是毫秒)。例如,('C4', 500)
表示音符C4
将持续500毫秒。
接下来定义了一个播放单个音符的函数play_tone
,接受两个参数:音符note
和持续时间duration
。在这个函数中,根据音符名称从音符表中查找对应的频率。如果找不到对应的音符,默认返回0;然后设置蜂鸣器的频率为指定的音符频率;开启PWM信号并持续duration
毫秒,最后停止发声在两个音符之间短暂暂停50ms,避免连续播放时音符之间没有间隔造成的失真。
之后就到了播放旋律了,使用for
循环遍历melody
列表,依次播放每个音符。每次循环会调用play_tone
函数,传入音符和它的持续时间。
最后等播放完毕后,释放PWM资源。
参考资料: