1 项目需求
- 设计音乐键盘,让不同按键发出不同音调;
- 电位计用来控制声音大小;
- 流水灯用来显示声音档位;
2 完成的功能及达到的性能
2.1 音调
使用PWM驱动无源蜂鸣器发声,通过修改PWM频率,实现不同音调。
根据开发套件设计,在蜂鸣器驱动电路有PA16连接,PA16是单片机一路PWM输出,因此通过杜邦线连接。
2.2 音量
根据开发套件设计,通过ADC检测电位器分压,换算出固定8个挡位的音量大小,通过编程修改PWM占空比实现音量变化。
2.3 按键输入
根据开发套件设计,板载4x4矩阵键盘,通过GPIO不断扫描获取键值。
2.4 指示灯
根据开发套件设计,使用8个LED显示音量水平,根据前面采集ADC换算的音量挡位通过LED指示。GPIO控制。
3 实现思路
- 反复轮询按键获取键值;
- 反复触发ADC对模拟输入进行采样,采样结果由DMA搬运;
- 将采样得到的ADC量化值求10次均值,然后换算电压,并求出音量挡位;
- 每当键值不为0xFF,代表有按键按下,获取对应的音调的频率,结合音量挡位换算占空比,修改PWM参数并开始PWM输出,结合音量挡位打开对应的LED灯;
- 当键值恢复0xFF,停止输出PWM,熄灭LED灯。
4 实现过程
4.1 上电预生成音调表、初始化外设,然后播放音调序列来提示开机。
static void Peripheral_Set()
{
// printf("Software version is V%.1f\r\n",SoftWare_Version);
Buzzer.Note_generate();
myADC.Init();
Buzzer.OFF();
Public.Delay_ms(200);
Buzzer.ON(note_table[0], 1);
Public.Delay_ms(200);
Buzzer.ON(note_table[1], 2);
Public.Delay_ms(200);
Buzzer.ON(note_table[2], 3);
Public.Delay_ms(200);
Buzzer.ON(note_table[3], 4);
Public.Delay_ms(200);
Buzzer.ON(note_table[4], 5);
Public.Delay_ms(800);
Buzzer.OFF();
// printf("Initialization completed, system startup!\r\n\r\n");
}
4.2 执行主循环
按键扫描,反复更新KEY_PAD.Key_value键值参数。
ADC换算,ADC采样由DMA在后台反复更新,这里取10个数求均值作为结果,并换算电压值。
当按键键值不为0xFF,则表示有按键按下,开始执行计算音量,更新myADC.ucVolume参数,音量LED灯显示,更新PWM参数,驱动蜂鸣器响,
当按键键值恢复0xFF,则表示按键松开,熄灯静音。
static void Run()
{
KEY_PAD.KEY_Detect();
myADC.Get_ADC_Voltage();
if (KEY_PAD.Key_value != 0xFF)
{
myADC.Disp_Volume();
uint16_t frequency = note_table[KEY_PAD.Key_value];
Buzzer.ON(frequency, myADC.ucVolume);
}
else
{
Buzzer.OFF();
// 熄灭所有LED
DL_GPIO_setPins(GPIO_LED_PORT,
GPIO_LED_PIN_0_PIN |
GPIO_LED_PIN_1_PIN |
GPIO_LED_PIN_2_PIN |
GPIO_LED_PIN_3_PIN |
GPIO_LED_PIN_4_PIN |
GPIO_LED_PIN_5_PIN |
GPIO_LED_PIN_6_PIN |
GPIO_LED_PIN_7_PIN);
}
Public.Delay_ms(50);
}
5 遇到的主要难题
5.1 按键扫描出现的问题
矩阵按键的行列扫描,采用行信号分别主动拉低电平,然后检测列信号是否有低电平,以此确认是否有按键按下。
先发现行信号第二行与其他行低电平波形不一致:
1、3、4行拉低信号:
2行拉低信号,存在缓起:
由于第二行信号出现缓起,导致扫描周期不能太短,否则第二行键盘键值完全错误!处理方案为行扫描信号间主动增加延时,实现正常识别按键。
static void KEY_Detect()
{
uint8_t i = (uint8_t)0;
const int row_pins[] = {KEY_PAD_H1_PIN, KEY_PAD_H2_PIN, KEY_PAD_H3_PIN, KEY_PAD_H4_PIN};
const int col_pins[] = {KEY_PAD_V1_PIN, KEY_PAD_V2_PIN, KEY_PAD_V3_PIN, KEY_PAD_V4_PIN};
// 初始化行引脚为高电平
for (uint8_t row = 0; row < 4; row++) {
DL_GPIO_setPins(KEY_PAD_PORT, row_pins[row]);
}
for (uint8_t row = 0; row < 4; row++) {
DL_GPIO_clearPins(KEY_PAD_PORT ,row_pins[row]);
// 确保引脚状态已经改变
for (volatile uint32_t delay = 0; delay < 1000; delay++);
//***************************************
//此处存疑,H2行(PA1)的行扫恢复信号波形存在缓起,原因暂时未知。主动增加延时后,可以正常使用。
//***************************************
for (uint8_t col = 0; col < 4; col++) {
if (DL_GPIO_readPins(KEY_PAD_PORT, col_pins[col]) == 0) {
KEY_PAD.Key_value = row * 4 + col;
KEY_PAD.KEY_Flag = TRUE;
// printf("Key pressed: %d \r\n", KEY_PAD.Key_value);
// 恢复行引脚为高电平
DL_GPIO_setPins(KEY_PAD_PORT, row_pins[row]);
return;
}
}
// 恢复行引脚为高电平
DL_GPIO_setPins(KEY_PAD_PORT, row_pins[row]);
}
KEY_PAD.Key_value = 0xFF;
KEY_PAD.KEY_Flag = FALSE;
// printf("Key released. \r\n");
}
5.2 音量控制问题
之前想当然的处理音量,以占空比范围20%~80%范围进行,结果发现人耳听起来对音量没有明显的区别。后来修改占空比范围在很小的范围修改,以达成能够分辨的音量变化。
static void Buzzer_ON(uint16_t Freq, uint16_t Duty)
{
uint16_t duty_adj_reg = 0;
duty_adj_reg = Freq - (Freq*((Duty*2)+1)/100); //((Duty*2)+1)/100是根据传入的音量等级换算的占空比,实际范围1%~15%
Buzzer.Status = Buzzer_Status_ON;
DL_TimerG_setLoadValue(PWM_BEEP_INST, Freq);
DL_TimerG_setCaptureCompareValue(PWM_BEEP_INST, duty_adj_reg, DL_TIMER_CC_0_INDEX);
DL_TimerG_startCounter(TIMG0);
}
5.3 Keil报错
音乐键盘差不多做完时,突然出现烧录报错,此时只有进入仿真器设置界面随便改一改参数,再烧录就可以了。反复如此不堪其扰。遂搜了一下,发现可能是RAM剩余不足,下载算法导入失败。经过排除法,可能是与延时有关。手动修改堆栈空间,开始是有效果的,但是到后面将延时改为滴答定时器之后,反而没有效果了,每次下载都会报错,暂未根本解决。
编辑堆栈空间。
6 实物展示
项目基于EVM_MSPM0开发套件直接编写代码,实物即为EVM_MSPM0开发板原貌,通过一个杜邦线连接PWM引脚与蜂鸣器驱动信号:
使用的各个部分入下图所示:
项目详情、以及演奏演示请移步视频观看,欢迎点赞投币🙂🙂
7 未来的计划建议
该项目已经成功实现了简易音乐键盘的功能,并达到了预期指标。然而通过更换硬件,还有许多可以提升与扩展的地方。
一阶段:
- 增加检测按钮实现调性改变,目前是1=C。
- 增加音乐播放功能,在单片机预留音乐序列,可以直接播放。
二阶段:
- 增加OLED屏幕展示更多信息。
- 可通过外部电路增加低通滤波器,达到DAC效果,去驱动喇叭代替蜂鸣器,实现更好的听觉体验。
- 主控芯片MSPM0L1306外设不支持iis。如果想实现更专业的音频应用,可以更换为带iis外设的单片机。