作为一个电子爱好者,我一直想做一个既能弹奏又能播放音乐的小系统。这次设计的核心思路是:用单片机作为控制中枢,通过矩阵键盘实现输入,配合液晶显示屏提供实时反馈。整个系统最巧妙的地方在于两种模式的切换——弹奏模式和播放模式,这相当于把电子琴和音乐播放器合二为一了。
选择STC89C52这款经典51单片机作为主控,主要考虑三点:首先它价格亲民,一片不到10块钱;其次IO口资源足够,正好能驱动4×4矩阵键盘;最重要的是开发环境成熟,网上资料丰富,特别适合DIY项目。系统框图可以简单分为输入模块(键盘)、控制模块(单片机)、输出模块(蜂鸣器+LCD)三大部分。
提示:初学者常犯的错误是直接照搬开发板电路,实际上DIY项目应该根据需求精简电路。比如这个项目就不需要外接RAM、ADC等外围器件,能省下不少成本和PCB空间。
采用4×4矩阵键盘布局,16个按键中:
键盘扫描使用经典的"行扫描+列检测"方法。具体接线如下:
这种设计有三大优势:
测试了三种发声方案:
最终选择方案1,因为:
注意:蜂鸣器要选择电磁式而非压电式,后者虽然音量更大但高频失真严重。实测推荐用KY-012模块,3V供电时音质最佳。
原始的行扫描代码存在"长按重复触发"问题,改进后加入状态机机制:
c复制#define KEY_DEBOUNCE 20 // 消抖时间(ms)
#define KEY_HOLD 500 // 长按判定时间(ms)
typedef struct {
uint8_t last_state;
uint32_t press_time;
} KeyStatus;
KeyStatus keys[16];
uint8_t get_key_event(uint8_t key_id) {
uint8_t current = (key_scan() == key_id);
if(current && !keys[key_id].last_state) {
keys[key_id].press_time = sys_tick;
keys[key_id].last_state = 1;
return KEY_PRESS;
}
else if(!current && keys[key_id].last_state) {
keys[key_id].last_state = 0;
return KEY_RELEASE;
}
else if(current && (sys_tick - keys[key_id].press_time > KEY_HOLD)) {
return KEY_HOLD;
}
return KEY_NONE;
}
这种实现带来三个提升:
采用定时器中断产生方波信号,各音阶频率计算公式:
code复制定时器重载值 = 65536 - (Fosc / (频率 × 2 × 分频系数))
以中音C4(261.63Hz)为例,使用12MHz晶振、定时器12分频:
code复制重载值 = 65536 - 12000000/(261.63×2×12) ≈ 63777
对应代码实现:
c复制void play_note(uint8_t note) {
uint16_t reload = note_table[note]; // 预计算的频率表
TMOD &= 0xF0; // 定时器0模式1
TL0 = reload; // 低字节
TH0 = reload>>8; // 高字节
TR0 = 1; // 启动定时器
}
实测发现,用12MHz晶振时,高音区(>2kHz)失真明显。后来改用11.0592MHz晶振,虽然计算稍复杂,但各音阶准确度提升约30%。
系统有三种状态:
用枚举清晰定义状态:
c复制typedef enum {
MODE_PLAY,
MODE_SELECT,
MODE_PLAYING
} SystemMode;
SystemMode current_mode = MODE_PLAY;
状态转换逻辑:
内置音乐采用简谱编码,每个音符用3字节存储:
code复制[音高][时长][间隔]
例如《欢乐颂》前两节:
c复制const uint8_t music[] = {
0x34, 0x20, 0x10, // 3拍半音,时长32,间隔16
0x34, 0x20, 0x10,
0x35, 0x20, 0x10,
0x37, 0x20, 0x10,
// ...其他音符
};
这种编码方式的优势:
1602液晶只有32字节显示RAM,需要精心设计显示内容:
采用"脏矩形"刷新算法,仅更新变化部分:
c复制void update_display() {
static char last_line1[16], last_line2[16];
if(strcmp(current_line1, last_line1) != 0) {
lcd_set_pos(0,0);
lcd_print(current_line1);
strcpy(last_line1, current_line1);
}
// 同理处理第二行...
}
这种方法使刷新效率提升约60%,避免了屏幕闪烁。
通过自定义字符实现音乐符号显示:
自定义字符生成工具推荐使用LCD Character Creator,可以直观设计5×8点阵图案。
现象:高音区音量小,低音区破音
解决方法:
现象:快速弹奏时丢音
优化方案:
实测优化后支持每秒15个音符的快速输入,完全满足业余演奏需求。
原因分析:简谱解码消耗过多CPU时间
改进措施:
经过优化后,即使在播放最复杂的《野蜂飞舞》段落时,CPU占用率也从78%降至42%。
这个基础版本完成后,还可以进一步扩展:
最近我在尝试加入SD卡支持,这样就能直接播放SD卡里的音乐文件了。不过发现FAT32文件系统解析比较占用资源,可能需要升级到STC12系列带硬件乘除法的单片机。