STM32F103、LCD1602实现蜂鸣器音乐项目

发布时间:2026/7/19 1:40:13
STM32F103、LCD1602实现蜂鸣器音乐项目 一、项目概述1.1 项目功能本项目基于 STM32F103 单片机实现双曲目蜂鸣器音乐播放器硬件搭载 LCD1602 液晶、4 路独立按键、TIM2 PWM 有源蜂鸣器支持功能LCD1602 实时显示播放器标题与当前播放曲目名称KEY1上一曲切换生日快乐 ↔ 茉莉花KEY2下一曲切换KEY3播放 / 暂停切换KEY4重启当前曲目播放过程实时扫描按键切歌立即生效无卡顿阻塞PWM 输出不同频率驱动蜂鸣器播放简谱休止符静音1.2 硬件平台主控STM32F103 系列标准库 V3.5 外设LCD16028 位并行PB0 (E)、PB1 (RS)、PB2 (RW)PB8~PB15 数据口蜂鸣器PA0 复用 TIM2_CH1 PWM 输出独立按键上拉输入4 路 KEY1~KEY4系统延时SysTick 提供毫秒级延时二、硬件资源分配2.1 LCD1602 引脚分配表格功能引脚单片机引脚功能说明RSPB1寄存器选择RWPB2读写控制EPB0使能时钟D0~D7PB8~PB158 位并行数据2.2 蜂鸣器引脚PA0 复用 TIM2_CH172MHz 系统时钟PWM 调频驱动有源蜂鸣器。2.3 按键资源4 路独立按键统一端口上拉输入按下电平为低 KEY1、KEY2、KEY3、KEY4 共用 KEY_PORT头文件宏定义。三、软件架构说明项目采用模块化分层设计分为 5 大驱动模块lcd1602.c/hLCD1602 液晶底层驱动8 位并行读写、初始化、字符串显示、清屏key.c/h独立按键初始化、消抖扫描函数beep_music.c/hTIM2 PWM 初始化、蜂鸣器频率设置、简谱数据、音乐播放逻辑public.h全局公共宏、类型、延时函数声明main.c主业务逻辑循环按键检测 曲目播放调度3.1 全局核心变量beep_music.h 全局导出表格变量名类型作用musicIndexuint8_t当前曲目索引0 生日快乐1 茉莉花musicPauseuint8_t暂停标志1 暂停0 播放musicName[]uint8_t*曲目名称字符串数组LCD 显示用3.2 乐谱结构体定义c运行typedef struct { uint16_t tone; // 音符频率REST0代表休止符 uint16_t time; // 音符持续时长(ms) }MusicNote;预定义两套乐谱数组happyBirthday[]、jasmine[]末尾以{REST,0}作为结束标记。3.3 简谱频率宏定义分为低音 L、中音 M、高音 H 三级简谱频率REST0 为静音。四、各模块详细技术说明4.1 LCD1602 驱动模块4.1.1 工作模式配置宏LCD1602_4OR8_DATA_INTERFACE0使用8 位并行驱动单次读写 8bit 数据速度更快。4.1.2 核心函数lcd1602_gpio_init()初始化控制脚、数据脚推挽输出默认 RS/RW/E 拉低lcd1602_write_cmd()写指令函数RS 置低触发 E 引脚时序写入控制指令lcd1602_write_data()写显示数据RS 置高输出字符至 LCD 显存lcd1602_init()LCD 上电初始化配置显示模式、光标、清屏lcd1602_clear()发送 0x01 指令全屏清屏lcd1602_show_string(x,y,str)指定坐标显示字符串x 范围 0~15y0 第一行 /y1 第二行自动处理字符串换行溢出4.1.3 底层输出逻辑lcd_set_dataport()将 8 位数据左移 8 位输出至 PB8~PB15一次性操作 ODR 寄存器效率高。4.2 按键驱动模块4.2.1 硬件配置GPIO 配置为上拉输入模式 IPU按键一端接地按下引脚电平变低。4.2.2 消抖扫描逻辑key_scan()检测引脚低电平触发按键20ms 延时消抖再次确认电平等待按键松开再次消抖 20ms返回对应按键值无按键按下返回KEY_UNPRESS4.2.3 优势阻塞式单次扫描消抖稳定无长按误触发适配音乐播放内实时按键检测。4.3 蜂鸣器与音乐播放模块4.3.1 TIM2 PWM 初始化beep_tim2_init()开启 GPIOA、TIM2 外设时钟PA0 配置复用推挽输出 AF_PP时基配置预分频 7172M/721MHz 定时器计数时钟PWM1 模式输出初始占空比 50%开启预装载使能 TIM24.3.2 频率控制beep_set_freq(uint16_t freq)freq0关闭 PWM 比较值蜂鸣器静音freq≠0根据目标频率自动计算 ARR 自动重装值动态更新周期与比较值实现音调切换4.3.3 播放核心函数play_music(MusicNote *music)核心设计非阻塞分段延时全程实时检测按键循环遍历乐谱数组直到休止结束符每段音符前扫描按键支持切歌、暂停、重启音符时长拆分为 10ms 分段延时每 10ms 扫描一次按键操作实时响应暂停标志置 1 时蜂鸣器静音持续等待播放恢复单音符播放完成后静音 20ms 区分音符间隔避免音调粘连切歌操作直接 return 退出当前播放回到 main 循环加载新曲目4.4 主程序逻辑 main.c外设初始化LCD、TIM2 蜂鸣器、按键LCD 开机显示标题与默认曲目主大循环扫描按键处理切歌、暂停、重启逻辑同步刷新 LCD 曲目名称根据musicIndex调用对应乐谱播放函数按键业务逻辑KEY1上一曲边界判断暂停播放 300ms 刷新屏幕后恢复播放KEY2下一曲边界判断刷新 LCD 曲目KEY3翻转暂停标志即时生效KEY4短暂暂停后重启当前歌曲五、核心业务流程上电初始化所有外设LCD 显示默认曲目《Happy Birthday》进入 while (1) 主循环持续扫描按键检测到切歌按键修改 musicIndex清屏刷新曲目名短暂暂停后恢复播放调用play_music播放对应乐谱播放全程每 10ms 扫描一次按键按下 KEY3 切换暂停状态蜂鸣器立刻静音 / 恢复发声按下 KEY4 重启当前曲目乐谱播放完毕自动循环回到主循环重新播放六、关键技术亮点播放过程实时按键响应音符延时分段处理不会出现长音符阻塞按键检测动态 PWM 调频自动计算定时器 ARR任意音符频率精准输出模块化高内聚液晶、按键、蜂鸣器、音乐数据完全解耦可单独移植复用LCD 坐标自适应字符串显示超出 16 字符自动换行适配长曲名全局状态统一管理musicIndex、musicPause 全局变量主函数与播放函数共享状态硬件资源宏封装所有引脚、端口、时钟使用宏定义修改硬件仅需改头文件移植性强七、移植与修改说明修改硬件引脚仅修改对应.h头文件引脚宏底层驱动无需改动切换 4/8 位 LCD 模式修改LCD1602_4OR8_DATA_INTERFACE宏新增曲目在beep_music.c新建 MusicNote 数组添加曲名至 musicNamemain 中增加索引判断修改蜂鸣器定时器更换 TIM 外设后同步修改时钟、通道、PWM 初始化代码调整播放速度修改乐谱内 time 字段数值数值越大音符播放越久八、缺陷与优化建议现有缺陷按键扫描为阻塞式消抖长按无连续切歌功能无音量调节功能PWM 固定 50% 占空比LCD 刷新存在 300ms 延时切歌时短暂停顿无掉电记忆曲目功能上电默认第一首优化方案增加长按识别逻辑实现长按连续切歌增加音量全局变量动态修改 PWM 比较值调节占空比取消切歌延时使用状态标记异步刷新 LCD消除卡顿增加 24C02 存储芯片保存上次播放曲目索引九、文件依赖关系main.c依赖 lcd1602.h、beep_music.h、key.h、public.hbeep_music.c依赖 key.h、lcd1602.h、stm32f10x.hkey.c依赖 stm32f10x.h、public.hlcd1602.c依赖 lcd1602.h、SysTick.h、stm32f10x.h所有模块统一依赖标准库内核头文件stm32f10x.h十、接口调用规范初始化顺序LCD1602 → 蜂鸣器 TIM2 → 按键播放调用play_music(乐谱数组名)函数内部自带按键检测无需外部循环检测曲目切换直接修改全局变量musicIndex置musicPause1可中断当前播放屏幕刷新调用lcd1602_clear()清屏后重新调用lcd1602_show_string()刷新文字十一、核心代码//main.c #include lcd1602.h #include beep_music.h #include key.h #include public.h int main(void) { uint8_t key_val; lcd1602_init(); beep_tim2_init(); KEY_Init(); lcd1602_show_string(0,0,(uint8_t*)Music Player); lcd1602_show_string(0,1,musicName[musicIndex]); while(1) { key_val key_scan(); switch(key_val) { case KEY1_PRESS: musicIndex (musicIndex 0) ? 1 : musicIndex - 1; musicPause 1; lcd1602_clear(); lcd1602_show_string(0,0,(uint8_t*)Music Player); lcd1602_show_string(0,1,musicName[musicIndex]); delay_ms(300); musicPause 0; break; case KEY2_PRESS: musicIndex (musicIndex 1) ? 0 : musicIndex 1; musicPause 1; lcd1602_clear(); lcd1602_show_string(0,0,(uint8_t*)Music Player); lcd1602_show_string(0,1,musicName[musicIndex]); delay_ms(300); musicPause 0; break; case KEY3_PRESS: musicPause !musicPause; break; case KEY4_PRESS: musicPause 1; delay_ms(200); musicPause 0; break; default: break; } if(musicIndex 0) play_music(happyBirthday); else play_music(jasmine); } }//key.c #include key.h void KEY_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(KEY_RCC, ENABLE); GPIO_InitStruct.GPIO_Pin KEY1_PIN | KEY2_PIN | KEY3_PIN | KEY4_PIN; GPIO_InitStruct.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(KEY_PORT, GPIO_InitStruct); } uint8_t key_scan(void) { if(KEY1 0) { delay_ms(20); while(KEY1 0); delay_ms(20); return KEY1_PRESS; } if(KEY2 0) { delay_ms(20); while(KEY2 0); delay_ms(20); return KEY2_PRESS; } if(KEY3 0) { delay_ms(20); while(KEY3 0); delay_ms(20); return KEY3_PRESS; } if(KEY4 0) { delay_ms(20); while(KEY4 0); delay_ms(20); return KEY4_PRESS; } return KEY_UNPRESS; }//beep_music.h #ifndef _BEEP_MUSIC_H #define _BEEP_MUSIC_H #include stm32f10x.h #include public.h // 蜂鸣器 PA0 TIM2_CH1 #define BEEP_PIN GPIO_Pin_0 #define BEEP_PORT GPIOA #define BEEP_RCC RCC_APB2Periph_GPIOA|RCC_APB1Periph_TIM2 // 简谱频率 #define L1 262 #define L2 294 #define L3 330 #define L4 349 #define L5 392 #define L6 440 #define L7 494 #define M1 523 #define M2 587 #define M3 659 #define M4 698 #define M5 784 #define M6 880 #define M7 988 #define H1 1047 #define H2 1175 #define H3 1319 #define H4 1397 #define H5 1568 #define H6 1760 #define H7 1976 #define REST 0 typedef struct { uint16_t tone; uint16_t time; }MusicNote; extern MusicNote happyBirthday[]; extern MusicNote jasmine[]; extern uint8_t musicIndex; extern uint8_t musicPause; extern uint8_t *musicName[]; void beep_tim2_init(void); void beep_set_freq(uint16_t freq); void play_music(MusicNote *music); #endif//beep_music #include beep_music.h #include key.h #include lcd1602.h uint8_t musicIndex 0; uint8_t musicPause 0; uint8_t *musicName[] { (uint8_t*)Happy Birthday, (uint8_t*)Jasmine Flower }; // 生日快乐 MusicNote happyBirthday[] { {L5,300},{L5,100},{L6,400},{L5,400},{H1,400},{M7,800}, {L5,300},{L5,100},{L6,400},{L5,400},{H2,400},{H1,800}, {L5,300},{L5,100},{H5,400},{H3,400},{H1,400},{M7,400},{M6,800}, {H4,300},{H4,100},{H3,400},{H1,400},{H2,400},{H1,800}, {REST,0} }; // 茉莉花 MusicNote jasmine[] { {M3,200},{M5,200},{M6,400},{M5,200},{M3,200},{M2,400}, {M3,200},{M5,200},{M6,400},{M5,200},{M3,200},{M2,800}, {M6,200},{M6,200},{H1,400},{M6,200},{M5,200},{M3,800}, {M5,200},{M5,200},{M3,400},{M2,200},{M1,800}, {REST,0} }; // TIM2 PWM初始化 PA0 复用推挽 void beep_tim2_init(void) { GPIO_InitTypeDef gpio_cfg; TIM_TimeBaseInitTypeDef tim_base; TIM_OCInitTypeDef tim_oc; // 开启GPIOA和TIM2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // PA0 复用推挽输出 gpio_cfg.GPIO_Pin GPIO_Pin_0; gpio_cfg.GPIO_Mode GPIO_Mode_AF_PP; gpio_cfg.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, gpio_cfg); // 预分频7172M/721MHz计数频率 tim_base.TIM_Prescaler 71; tim_base.TIM_CounterMode TIM_CounterMode_Up; tim_base.TIM_Period 999; tim_base.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, tim_base); // PWM1模式 tim_oc.TIM_OCMode TIM_OCMode_PWM1; tim_oc.TIM_OutputState TIM_OutputState_Enable; tim_oc.TIM_Pulse 500; // 50%占空比 tim_oc.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, tim_oc); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_Cmd(TIM2, ENABLE); } // 设置输出频率0静音 void beep_set_freq(uint16_t freq) { uint16_t arr; if(freq 0) { TIM_SetCompare1(TIM2, 0); return; } arr 1000000 / freq - 1; TIM_SetAutoreload(TIM2, arr); TIM_SetCompare1(TIM2, arr / 2); } // 分段播放每10ms扫描一次按键支持切歌 void play_music(MusicNote *music) { uint16_t i 0; uint16_t t; uint8_t key_val; while(music[i].tone ! REST) { key_val key_scan(); switch(key_val) { case KEY1_PRESS: musicIndex (musicIndex 0) ? 1 : musicIndex - 1; musicPause 1; lcd1602_clear(); lcd1602_show_string(0,0,(uint8_t*)Music Player); lcd1602_show_string(0,1,musicName[musicIndex]); delay_ms(300); musicPause 0; return; case KEY2_PRESS: musicIndex (musicIndex 1) ? 0 : musicIndex 1; musicPause 1; lcd1602_clear(); lcd1602_show_string(0,0,(uint8_t*)Music Player); lcd1602_show_string(0,1,musicName[musicIndex]); delay_ms(300); musicPause 0; return; case KEY3_PRESS: musicPause !musicPause; break; case KEY4_PRESS: musicPause 1; delay_ms(200); musicPause 0; break; default: break; } if(musicPause 1) { beep_set_freq(0); delay_ms(100); continue; } beep_set_freq(music[i].tone); // 拆分长延时实时检测按键 t music[i].time; while(t 10) { delay_ms(10); key_val key_scan(); if(key_val ! KEY_UNPRESS) { switch(key_val) { case KEY1_PRESS: musicIndex (musicIndex 0) ? 1 : musicIndex - 1; musicPause 1; lcd1602_clear(); lcd1602_show_string(0,0,(uint8_t*)Music Player); lcd1602_show_string(0,1,musicName[musicIndex]); delay_ms(300); musicPause 0; return; case KEY2_PRESS: musicIndex (musicIndex 1) ? 0 : musicIndex 1; musicPause 1; lcd1602_clear(); lcd1602_show_string(0,0,(uint8_t*)Music Player); lcd1602_show_string(0,1,musicName[musicIndex]); delay_ms(300); musicPause 0; return; case KEY3_PRESS: musicPause !musicPause; break; case KEY4_PRESS: musicPause 1; delay_ms(200); musicPause 0; break; default: break; } } t - 10; } if(t 0) delay_ms(t); beep_set_freq(0); delay_ms(20); i; } }//lcd1602.c #include lcd1602.h #include SysTick.h // IO初始化 void lcd1602_gpio_init(void) { GPIO_InitTypeDef gpio_cfg; RCC_APB2PeriphClockCmd(LCD_CTRL_RCC, ENABLE); // 控制脚 PB0 PB1 PB2 推挽输出 gpio_cfg.GPIO_Pin LCD_E_PIN | LCD_RS_PIN | LCD_RW_PIN; gpio_cfg.GPIO_Mode GPIO_Mode_Out_PP; gpio_cfg.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(LCD_CTRL_PORT, gpio_cfg); // 数据脚 PB8~PB15 推挽输出 gpio_cfg.GPIO_Pin LCD_DATA_MASK; gpio_cfg.GPIO_Mode GPIO_Mode_Out_PP; gpio_cfg.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(LCD_DATA_PORT, gpio_cfg); LCD_RS_L(); LCD_RW_L(); LCD_E_L(); } // 将8位数据输出到PB8~PB15D0→PB8D7→PB15 static void lcd_set_dataport(uint8_t val) { // 先清除PB8~PB15 LCD_DATA_PORT-ODR ~(0xFF 8); // val低8位移到PB8~PB15输出 LCD_DATA_PORT-ODR | ((uint16_t)val 8); } // 写命令 #if (LCD1602_4OR8_DATA_INTERFACE 0) void lcd1602_write_cmd(uint8_t cmd) { LCD_RS_L(); LCD_RW_L(); LCD_E_L(); lcd_set_dataport(cmd); delay_ms(1); LCD_E_H(); delay_ms(1); LCD_E_L(); } #else void lcd1602_write_cmd(uint8_t cmd) { LCD_RS_L(); LCD_RW_L(); LCD_E_L(); lcd_set_dataport(cmd 4); delay_ms(1); LCD_E_H(); delay_ms(1); LCD_E_L(); lcd_set_dataport(cmd 0x0F); delay_ms(1); LCD_E_H(); delay_ms(1); LCD_E_L(); } #endif // 写数据 #if (LCD1602_4OR8_DATA_INTERFACE 0) void lcd1602_write_data(uint8_t dat) { LCD_RS_H(); LCD_RW_L(); LCD_E_L(); lcd_set_dataport(dat); delay_ms(1); LCD_E_H(); delay_ms(1); LCD_E_L(); } #else void lcd1602_write_data(uint8_t dat) { LCD_RS_H(); LCD_RW_L(); LCD_E_L(); lcd_set_dataport(dat 4); delay_ms(1); LCD_E_H(); delay_ms(1); LCD_E_L(); lcd_set_dataport(dat 0x0F); delay_ms(1); LCD_E_H(); delay_ms(1); LCD_E_L(); } #endif // LCD初始化 #if (LCD1602_4OR8_DATA_INTERFACE 0) void lcd1602_init(void) { lcd1602_gpio_init(); delay_ms(20); lcd1602_write_cmd(0x38); lcd1602_write_cmd(0x0C); lcd1602_write_cmd(0x06); lcd1602_write_cmd(0x01); } #else void lcd1602_init(void) { lcd1602_gpio_init(); delay_ms(20); lcd1602_write_cmd(0x28); lcd1602_write_cmd(0x0C); lcd1602_write_cmd(0x06); lcd1602_write_cmd(0x01); } #endif // 清屏 void lcd1602_clear(void) { lcd1602_write_cmd(0x01); } // 显示字符串 x:0~15 y:0/1 void lcd1602_show_string(uint8_t x, uint8_t y, uint8_t *str) { uint8_t i 0; if(y 1 || x 15) return; if(y 0) { while(*str ! \0) { if(i 16 - x) lcd1602_write_cmd(0x80 i x); else lcd1602_write_cmd(0x80 0x40 i x - 16); lcd1602_write_data(*str); str; i; } } else { while(*str ! \0) { if(i 16 - x) lcd1602_write_cmd(0x80 0x40 i x); else lcd1602_write_cmd(0x80 i x - 16); lcd1602_write_data(*str); str; i; } } }//lcd1602.h #ifndef _LCD1602_H #define _LCD1602_H #include stm32f10x.h // 08位并口 14位并口 #define LCD1602_4OR8_DATA_INTERFACE 0 // 控制引脚映射 #define LCD_RS_PIN GPIO_Pin_1 #define LCD_RW_PIN GPIO_Pin_2 #define LCD_E_PIN GPIO_Pin_0 #define LCD_CTRL_PORT GPIOB #define LCD_CTRL_RCC RCC_APB2Periph_GPIOB // 数据引脚 PB8~PB15 对应D0~D7 #define LCD_DATA_MASK GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15 #define LCD_DATA_PORT GPIOB #define LCD_DATA_RCC RCC_APB2Periph_GPIOB // 控制电平宏 #define LCD_RS_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_RS_PIN) #define LCD_RS_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_RS_PIN) #define LCD_RW_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_RW_PIN) #define LCD_RW_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_RW_PIN) #define LCD_E_H() GPIO_SetBits(LCD_CTRL_PORT, LCD_E_PIN) #define LCD_E_L() GPIO_ResetBits(LCD_CTRL_PORT, LCD_E_PIN) // 函数声明 void lcd1602_gpio_init(void); void lcd1602_write_cmd(uint8_t cmd); void lcd1602_write_data(uint8_t dat); void lcd1602_init(void); void lcd1602_clear(void); void lcd1602_show_string(uint8_t x, uint8_t y, uint8_t *str); #endif