51单片机播放音乐

一、文档概述

1.1 项目简介

本程序基于标准 51 内核单片机(11.0592MHz 晶振),通过 IO 口无源蜂鸣器输出方波实现《小星星》乐曲循环播放,全程无定时器中断,采用纯软件延时翻转 IO 生成不同频率音符,结构简单、易移植,适合单片机入门音乐播放实验。

1.2 硬件环境

  • 主控:STC89C52/AT89C52 51 单片机
  • 晶振:11.0592MHz
  • 发声器件:无源蜂鸣器(高电平关闭,低电平导通发声)
  • IO 分配:P1^0 驱动蜂鸣器
  • 编译环境:Keil C51

1.3 功能特性

  1. 支持低 / 中 / 高三个八度音阶,包含休止符静音功能
  2. 乐谱采用二维数组存储,音符 + 节拍分离,修改曲目便捷
  3. 纯软件方波生成,不依赖定时器中断,无中断冲突问题
  4. 乐曲播放完成自动循环,间隔 1 秒重复播放
  5. 分层函数设计:延时、单音符播放、整曲播放、硬件初始化解耦

二、头文件与宏、类型定义说明

c

运行

复制代码
#include <reg52.h>
typedef unsigned char uchar;
typedef unsigned int uint;
sbit BEEP = P1^0;
#define REST 0
  1. reg52.h:51 单片机寄存器标准头文件,定义 P0~P3、TCON、TMOD 等特殊功能寄存器
  2. 类型别名:uchar 8 位无符号字符、uint 16 位无符号整型,简化代码书写
  3. sbit BEEP = P1^0:位定义,将 P1.0 引脚绑定蜂鸣器控制位;无源蜂鸣器默认高电平静音
  4. REST:休止符标记,索引 0,代表无音符、静音延时

三、音符频率对照表 NOTE_TABLE

3.1 数组说明

NOTE_TABLE 为常量数组,存储各音阶定时器初值,用于换算方波半周期延时,索引对应音符:

表格

索引 音符 音区 索引 音符 音区 索引 音符 音区
0 REST 休止 8 1 中音 15 1 高音
1 1 低音 9 2 中音 16 2 高音
2 2 低音 10 3 中音 17 3 高音
3 3 低音 11 4 中音 18 4 高音
4 4 低音 12 5 中音 19 5 高音
5 5 低音 13 6 中音 20 6 高音
6 6 低音 14 7 中音 21 7 高音
7 7 低音 ------ ------ ------ ------ ------ ------

3.2 原理说明

数组存储定时器 1 计数初值,程序仅提取低 8 位计算半周期延时 half_period = 256 - (timer_val & 0xFF);数值越小,延时越短,输出频率越高,音调越高。

四、乐谱数组 music_star

4.1 存储格式

c

运行

复制代码
uint code music_star[][2] = {{音符索引, 节拍时长ms}, ... , {REST,0}};
  • 二维数组第 1 列:音符索引,取值0~21,0 为休止符
  • 二维数组第 2 列:当前音符持续时间(单位 ms)
  • 结束标记:{REST,0},循环读取乐谱时以此判定乐曲结束
  • code关键字:数组存储在程序 ROM 区,不占用 RAM 内存

4.2 乐曲逻辑

完整《小星星》简谱转译,短音 200ms,长音 400ms,播放完毕延时 1s 后循环重放。

五、全局变量说明

表格

变量名 类型 作用
beat_delay uint 存储当前音符节拍时长,预留扩展参数
timer_val uint 临时存储当前音符对应的定时器初值

六、底层延时函数

6.1 void DelayMs(uint ms)

  • 功能:毫秒级软件延时,适配 11.0592MHz 晶振
  • 参数:ms 延时毫秒数
  • 实现:双层 for 循环空跑,固定循环参数j=110保证 1ms 基准延时

6.2 void DelayUs(uint us)

  • 功能:微秒级短延时,用于生成方波高低电平保持时间
  • 参数:us 微秒延时值
  • 使用场景:蜂鸣器 IO 翻转后延时,控制方波频率,决定音调高低

七、硬件初始化函数 Timer1_Init()

c

运行

复制代码
void Timer1_Init(void)
{
    TMOD |= 0x20;
    ET1 = 0;
    TR1 = 0;
}
  1. TMOD |= 0x20:配置定时器 1 为 8 位自动重装模式(仅预留,本程序未使用定时器中断)
  2. ET1 = 0:关闭定时器 1 中断允许位,彻底屏蔽定时器中断
  3. TR1 = 0:关闭定时器 1 计数,定时器全程不启动
  4. 设计目的:统一定时器寄存器初始化,避免寄存器乱序导致的潜在干扰

八、核心播放函数

8.1 单音符播放 PlayNote(uint note, uint beat)

入参
  • note:音符索引(0~21),0 为休止符
  • beat:音符持续节拍(ms)
执行逻辑
  1. 休止符判断:音符为 REST 时,蜂鸣器置高静音,直接延时对应节拍,函数返回
  2. 音调计算:读取音符对应初值,计算方波半周期微秒延时
  3. 方波循环输出 :在总节拍时长内循环翻转蜂鸣器 IO:
    • BEEP=0 发声 → 微秒延时
    • BEEP=1 静音 → 微秒延时
    • 总时长自减,直到节拍耗尽
  4. 音符播放完成后蜂鸣器置高,停止发声

8.2 整曲播放 PlayMusic(uint music[][2])

  1. 遍历乐谱二维数组,循环读取每组音符+节拍
  2. 判断结束标记music[i][0] == REST && music[i][1] == 0,满足则退出循环
  3. 调用PlayNote()逐音符播放,播放完成索引自增读取下一个音符

九、主函数 main()

  1. 调用Timer1_Init()完成定时器寄存器初始化
  2. BEEP = 1:上电默认关闭蜂鸣器,防止上电杂音
  3. 无限大循环:
    • 完整播放《小星星》乐曲
    • 延时 1000ms,乐曲间隔静音 1 秒
    • 重复播放

十、程序工作原理

  1. 无源蜂鸣器发声原理:无源蜂鸣器必须输入固定频率方波才能发声,高低电平交替翻转产生振动;翻转频率决定音调。
  2. 音调控制:不同音符对应不同半周期延时,延时越短,翻转频率越高,音调越高。
  3. 节拍控制:外层总时间循环控制音符持续时长,实现长短音区分。
  4. 静音处理:休止符直接拉高蜂鸣器引脚,停止方波输出,仅做延时。
  5. 无中断设计优势:代码逻辑直观,不会与按键、串口等外设中断产生冲突,适合初学者调试。

十一、修改扩展指南

11.1 修改乐曲

  1. 新增二维code乐谱数组,格式{``{音符索引,节拍},...,{REST,0}}
  2. main函数循环内调用PlayMusic(新数组名)即可切换曲目

11.2 调整播放速度

  • 统一修改乐谱内所有节拍数值:数值变大播放变慢,数值变小播放变快
  • 或修改主函数循环间隔DelayMs(1000)调整歌曲间隔

11.3 更换蜂鸣器 IO

修改 sbit BEEP = P1^0; 引脚定义,如P2^0P0^1等,硬件同步改接线

11.4 适配其他晶振

需重新校准DelayMs内层循环值、DelayUs延时基准、NOTE_TABLE初值表。

十二、注意事项

  1. 硬件必须使用无源蜂鸣器;有源蜂鸣器无法通过方波调节音调,本程序不兼容。
  2. 蜂鸣器驱动建议增加三极管放大,51 单片机 IO 驱动电流不足会导致声音微弱。
  3. 程序全程阻塞延时,播放乐曲时无法响应按键、串口等其他任务,如需同步交互需改用定时器中断播放。
  4. 乐谱数组加code关键字,存放在程序存储器,减少 RAM 占用,去掉后大容量乐谱会导致内存溢出。
  5. 休止符判断条件严格匹配{REST,0},乐谱末尾必须添加结束标记,否则程序数组越界死机。

十三、程序优缺点

优点

  1. 逻辑简单,无中断、无复杂寄存器操作,新手易理解
  2. 乐谱与程序分离,换曲仅需修改数组,维护方便
  3. 资源占用低,不占用定时器运行资源
  4. 移植简单,仅需修改 IO 定义与延时参数

缺点

  1. 阻塞式延时,播放期间 CPU 无法处理其他任务

  2. 软件延时精度受晶振、编译器优化影响,音准存在微小误差

  3. 长节拍播放时 CPU 空转,资源利用率低

    cpp 复制代码
    #include <reg52.h>
    typedef unsigned char uchar;
    typedef unsigned int uint;
    
    // 蜂鸣器IO定义
    sbit BEEP = P1^0;
    
    // 休止符常量
    #define REST 0
    
    // 定时器1初值表(11.0592MHz,仅用来换算半周期延时)
    uint code NOTE_TABLE[] = {
        0,      // 0:休止符REST
        0xF895, // 低音1
        0xF960, // 低音2
        0xFA09, // 低音3
        0xFA90, // 低音4
        0xFB00, // 低音5
        0xFB68, // 低音6
        0xFBC7, // 低音7
        0xFC0B, // 中音1
        0xFC58, // 中音2
        0xFC9C, // 中音3
        0xFCCF, // 中音4
        0xFCFF, // 中音5
        0xFD2E, // 中音6
        0xFD59, // 中音7
        0xFD7F, // 高音1
        0xFDA3, // 高音2
        0xFDC4, // 高音3
        0xFDDD, // 高音4
        0xFDF4, // 高音5
        0xFE08, // 高音6
        0xFE1A  // 高音7
    };
    
    // 小星星乐谱 格式{音符索引, 节拍ms}
    uint code music_star[][2] = {
        {8,200}, {8,200}, {15,200}, {15,200},
        {17,200}, {17,200}, {15,400},
        {13,200}, {13,200}, {12,200}, {12,200},
        {10,200}, {10,200}, {8,400},
        {15,200}, {15,200}, {13,200}, {13,200},
        {12,200}, {12,200}, {10,400},
        {15,200}, {15,200}, {13,200}, {13,200},
        {12,200}, {12,200}, {10,400},
        {8,200}, {8,200}, {15,200}, {15,200},
        {17,200}, {17,200}, {15,400},
        {13,200}, {13,200}, {12,200}, {12,200},
        {10,200}, {10,200}, {8,400},
        {REST,0} // 结束标志
    };
    
    uint beat_delay;
    uint timer_val;
    
    // 毫秒延时 11.0592MHz
    void DelayMs(uint ms)
    {
        uint i,j;
        for(i=ms; i>0; i--)
            for(j=110; j>0; j--);
    }
    
    // 微秒级短延时,用于生成音符方波
    void DelayUs(uint us)
    {
        uint i;
        for(i=us; i>0; i--);
    }
    
    // 定时器初始化(仅配置,关闭中断,不使用溢出中断)
    void Timer1_Init(void)
    {
        TMOD |= 0x20;
        ET1 = 0; // 彻底关闭定时器1中断
        TR1 = 0;
    }
    
    // 播放单音符 - 纯软件翻转,无中断
    void PlayNote(uint note, uint beat)
    {
        uint total_time = beat;
        uint half_period;
        beat_delay = beat;
    
        // 休止符直接静音延时
        if(note == REST)
        {
            BEEP = 1;
            DelayMs(total_time);
            return;
        }
    
        // 根据定时初值换算半周期延时
        timer_val = NOTE_TABLE[note];
        half_period = 256 - (timer_val & 0xFF);
    
        // 在节拍总时长内循环翻转蜂鸣器输出方波
        while(total_time > 0)
        {
            BEEP = 0;
            DelayUs(half_period);
            BEEP = 1;
            DelayUs(half_period);
            total_time--;
        }
        BEEP = 1;
    }
    
    // 播放整首乐曲
    void PlayMusic(uint music[][2])
    {
        uchar i = 0;
        while(music[i][0] != REST || music[i][1] != 0)
        {
            PlayNote(music[i][0], music[i][1]);
            i++;
        }
    }
    
    void main(void)
    {
        Timer1_Init();
        BEEP = 1; // 蜂鸣器默认关闭
        while(1)
        {
            PlayMusic(music_star);
            DelayMs(1000); // 歌曲循环间隔1秒
        }
    }
相关推荐
执迷不悟8231 小时前
51单片机
单片机·嵌入式硬件·51单片机
zjxtxdy2 小时前
TIM输入捕获
单片机·嵌入式硬件
西城微科方案开发2 小时前
西城微科蓝牙电子秤PCBA方案设计
单片机·嵌入式硬件
hongmai66688811 小时前
FH8856V310芯片详解:6M高清+0.5TOPS算力,赋能智能安防新方案
人工智能·单片机·嵌入式硬件·物联网·智能家居
三易串口屏14 小时前
串口屏是什么
单片机·嵌入式硬件
冰茶丿17 小时前
嵌入式安全第一关:Secure Boot是怎么保护你的设备的?
嵌入式硬件·安全
卖萌的斜阳17 小时前
W55MH32L-EVB 上手测评:硬件 TCP/IP 加持的以太网单片机,MicroPython 零门槛开发
单片机·网络协议·tcp/ip
Kuakewei88817 小时前
4056充电芯片:28V高耐压+电池反接保护+热调节功能
嵌入式硬件
三品吉他手会点灯17 小时前
嵌入式机器学习 - 学习笔记1.1.1 - 什么是机器学习?
c语言·人工智能·笔记·嵌入式硬件·学习·机器学习