- 项目介绍
本项目是基于【硬禾RP2350B核心板搭配电子琴扩展板】的电子琴项目,可以实现电子琴演奏和回访功能。其音色悠扬(近似小提琴),可玩性强,插上充电宝就是妥妥的玩具一枚,我家娃见了都爱不释手。
-使用到的硬件介绍
主要的2个硬件是:
1. 硬禾RP2350B核心板,采用了树莓派RP2350B主控CPU,双核Arm处理器 + 双核RISC-V处理器
– 两个 150MHz Arm Cortex-M33 内核,支持浮点和 DSP
– 内置 520KB SRAM
– 围绕 Arm TrustZone for Cortex-M 构建的综合安全架构
– 内置开关模式电源和低静态电流 LDO
– 12 个升级的 PIO 状态机
– 用于高速数据传输的新型 HSTX 外设
– 支持外部 QSPI PSRAM
核心板上还有:
1) 8颗单色LED,需要注意LED采用了查理复用的方式,仅用4个IO口组合的方式驱动(高电平、低电平、高阻态)
2) 2个三色LED(WS2812可编程的LED灯)
3) 2个7段数码管(两颗74HC595组合驱动)
4) 4个拨码开关
5) 4个轻触按键
2. 硬禾电子琴扩展板

电子琴扩展板核心是13颗(钢琴琴键)+2颗微动开关,以及2路音频播放器(1路蜂鸣器、1路喇叭),再有就是切换开关1个,以及喇叭的音量调节旋钮。
整个电子琴扩展板做工精致,拿来即用,按键清脆,可玩性比较好,需要主要他与硬禾RP2350B核心板的连接方式如图所示,USB朝左,否则容易发生短路。
- 方案框图和项目设计思路介绍
方案框图如下,先进行引脚定义和参数初始化,
紧接着进入while True:大循环
1. 音源切换判断(SW16=左,播放蜂鸣器SW=右,播放喇叭)
2. 记忆旋律回放及清零判断(SW15 清零存储旋律SW14 播放记忆旋律)
3. 琴键判断、发音及记忆旋律
4. 若无琴键按下则静音
- 软件流程图和关键代码介绍
软件流程图如上图,关键代码有2部分,第1部分就是播放对应旋律yinfu(tone[1~13])
核心原理是利用PWM输出。
我们知道:要播放对应的旋律,只需要输出对应频率的波形就好了。

如图音符与频率对照表:
中音1对应的频率是523Hz
这里涉及2个要素:①波形,②占空比
就波形来说:如下3种波形:矩形波、三角波、正弦波都能表达对应频率,那究竟采用哪种波形呢?

实际上就单片机来说,最容易实现的就是矩形波,因为电压只涉及高电平、低电平,不涉及中间状态,最容易实现。
然后听过矩形波输出声音的你估计会只摇头:太难听了。完全是一种机械性的、尖锐的、刺耳的声音,毫无音乐美感,那咱们换成正弦波输出如何,声音怎么样呢?
于是SPWM就应运而生。
SPWM全称是正弦脉冲宽度调制,其主要原理是面积等效原理 :冲量相等而形状不同的窄脉冲施加在惯性环节上时,其效果基本相同
在SPWM中,通过生成正弦波的周期性脉冲,并且根据正弦波的幅值来调整每个脉冲的宽度,从而生成逼近正弦波形的交流输出电压。SPWM技术主要用于实现电压调节、频率控制和电动机驱动等功能。

上述曲线清晰得表达了如何通过正弦波的幅值来调整每个脉冲的宽度,从而生成逼近正弦波形的交流输出电压。
简单说来,就是仍然输出矩形波,但矩形波的占空比根据正弦波的幅值而实时变化,幅值高的地方,矩形波占空比高(最高100%),幅值低的地方,矩形波占空比低(最低0%)
最后就能模拟实现正弦波的效果,实测声音是十分悦耳的。
实际上经过笔者多次测试,三角波的声音效果跟正弦波十分接近,而且实现效果更简单,因此本篇采用三角波来播放旋律。
下图就是我在测试三角波PWM绘制的曲线图。

播放旋律的关键代码如下:
def yinfu(note):
duty = 0 占空比:初始值为0
direction = 1 曲线增长方向:初始值为1,正向增长斜率为正
for _ in range(2 * 256): 水平轴x为512个基本单位
duty += direction 斜率为正,占空比逐步+1;为负则-1
if duty > 256: 占空比y达到256(半个周期)
duty = 256 占空比y达到最大值256
direction = -1 斜率变成-1
elif duty < 0: 占空比y降到最小值0
duty = 0
direction = 1 斜率变成+1
if sound ==1 : 蜂鸣器发声
beeper.freq(note) 设定频率
beeper.duty_u16(duty*256) #65536/256=257设占空比
else: 喇叭发声
buzzer.freq(note) 设定频率
buzzer.duty_u16(duty*256) #65536/256=257设占空比
time.sleep(0.001)
另外还有通过SW14、SW15升高8度和降低8度扩展音域的功能。
首先把扩展区的频率增加在tone中。
tone=[196,220,245,262,294,330,349,392,440,494,523,587,659,698,784,880,988,1046,1175,1318,1397,1588,1760,1975,2093,2351,2640,2797,3140,3525]
初始状态n=0
程序主循环里。
if SW15.value()== 0: #升高8度
n=7
if SW14.value()== 0: #降低8度
n=-7
if SW1.value()== 0:
yinfu(tone[7+n]) 这是升高8度/降低8度的关键
print((utime.ticks_ms()-start_time)//100, "SW1")
旋律记忆及回放功能则比较简单,主要通过数组存放已按下的按键对应的音符,当按下回放按钮,则播放该数组(若为0则不播放),当按下清零按钮,则将数组中的所有值清零。
if SW2.value()== 0:
yinfu(tone[1])
yinyue[n]=tone[1] 将按下的音符记忆进数组yinyue[n]
n=n+1 数组序号+1,待存入下一个音符
print((utime.ticks_ms()-start_time)//100, "SW2",n)
if SW15.value()== 0: #清除
i=0
for i in range(0,len(yinyue)):
yinyue[i]=0 将记忆的音符全部清零
n=0
if SW14.value()== 0: #回放
i=0
for i in range(0,len(yinyue)):
if(yinyue[i]!=0): 音符不为0才播放
yinfu(yinyue[i])
- 实物功能展示图及说明硬件如图
P/ A开关(SW16),拨至左侧则播放蜂鸣器,拨至右侧则播放喇叭
清零开关(SW15),位于图中右上处按钮,按下则记忆旋律都清零
记忆旋律回放(SW14),位于图中右下处按钮,按下则回放记忆旋律
- 项目中遇到的难题和解决方法
项目中遇到的难题主要是记忆回放主要实现了旋律的记忆,如1、2、3、5……
但旋律与旋律间的停顿,则无法很好地计算、存储,并在回放时插入,也就是目前地旋律回放是不考虑旋律之间地停顿的,默认无停顿。
解决方法:目前还没找到比较好的解决方法。
尝试过micropython的计时器函数 utime.ticks_ms()-start_time
但其数值不稳定,每次运行会存在较大差异,目前还不清楚其运行逻辑和处理机制,理论上可以每一次按键按下时的计时值,减去前一次的计时值,再减去旋律的持续时间,就可以计算旋律与旋律间的停顿。

- 对本次活动的心得体会(包括意见或建议)
本次活动挺好的,锻炼了项目实战经验,技能体验到作为开发板的基本功能,还能带着任务去实现对应功能,在实际功能开发中巩固了知识。
建议后续多多举办类似活动,另外提点建议是,建议活动完成后会有官方讲解(标准答案),以便开发者更好掌握。