找回密码
 立即注册
查看: 125|回复: 1

新手学STC单片机 实验八 蜂鸣器应用

[复制链接]
  • 打卡等级:常住居民I
  • 打卡总天数:68
  • 最近打卡:2026-02-10 09:53:37

21

主题

30

回帖

164

积分

注册会员

积分
164
发表于 2026-1-26 14:50:21 | 显示全部楼层 |阅读模式
实验目的
掌握有源蜂鸣器的使用方法。
掌握无源蜂鸣器的使用方法。
掌握 STC15 系列单片机 PCA 定时器高速输出模式的使用。

实验内容
掌握 STC15 单片机 PCA 定时器高速输出模式;
使用单片机开发板实现蜂鸣器播放音乐的编写;
结合模电知识分析完成蜂鸣器简单驱动电路的设计;

硬件电路

截图202601261436301561.jpg

    如图 8-1 所示为无源蜂鸣器的电路原理图,编写程序在 JP69 送入不同频率的
脉冲波,输出经三极管放大驱动蜂鸣器发声。电阻 R40、R41 起到限流保护作用,
电阻 R1 的值应在 5~10Ω之间,太大会降低 BZ 发声功率。蜂鸣器一般是电感元件,
当电感元件突然断电会产生很大感应电动势,造成对电子元件的损伤,所以需要并
联二极管 D12,目的是旁路掉此感应电动势,起到保护的作用。
    有源蜂鸣器与无源蜂鸣器的硬件电路类似。

有源蜂鸣器和无源蜂鸣器
    这里的“源”不是指电源。而是指震荡源。也就是说,有源蜂鸣器内部带震荡
源,所以只要一通电就会叫。而无源内部不带震荡源,所以如果用直流信号无法令
其鸣叫。必须用 2K~5K 的方波去驱动它。
    有源蜂鸣器和无源蜂鸣器的差别主要为:它们对输入信号的要求不一样,有源
蜂鸣器工作的理想信号是直流电,通常标示为 VDC、VDD 等。因为蜂鸣器内部有一
简单的振荡电路,能将恒定的直流电转化成一定频率的脉冲信号,从面实出磁场交
变,带动钼片振动发音。而无源蜂鸣器没有内部驱动电路,有些公司和工厂称为讯
响器,国标中称为声响器。无源蜂鸣器工作的理想信号方波。如果给预直流信号蜂
鸣器是不响应的,因为磁路恒定,钼片不能振动发音。
    无源蜂鸣器的优点是:1、便宜;2、声音频率可控,可以做出“多来米发索拉
西”的效果。3、在一些特例中,可以和 LED 复用一个控制口。有源蜂鸣器的优点
是:程序控制方便。所以为了产生不用频率的音符我们选用无源蜂鸣器,报警则只
需要有源蜂鸣器,方便控制。

音乐播放原理

    在编写程序前,通常要确定各音符所对应的脉冲输出频率值,还要知道该音符
发生的长度(节拍)值。接下来是根据所要产生曲谱的音调和节拍,建立相应的发
声数据组。
    如表 8-1 所示是一个简单的 8 位音符的频率(周期)对应表。在表中,中音音
符“1”的频率为 523,即 1s 脉冲为 523 个,周期为 1/523=1912us,半周期为 956us。

假定把音乐的 1 个节拍的时间长度定位 0.4s,那么 1/4 节拍的时间长度为 0.1s。
截图202601261438429635.jpg

    因此,如果要发出 1/4 节拍长度的中音“1”,需要 0.1/956us=105 次半周期,
也就是定时器中断 105 次即可发出 1/4 节拍长度的中音“1”。如果以 1/4 节拍为 1
个基本节拍单位,则 2 个基本节拍单位为 1/2 拍,4 个基本节拍单位为 1 拍,依次
类推。
    根据表 8-1,再根据儿童歌曲“我爱北京天安门”的乐谱编制出发音数据组,
每一个音符的发生用一组数据来表示。其中第一个数据表示发音的音符;第二个数
据是节拍基本单位的赔率值,它表示音符的发音长度。例如,乐谱中第一个音符“5”
为 1/2 拍,则其对应的数据表示为(5,2)。

PCA 模块相关寄存器

    PCA 模块的几个相关寄存器 CMOD(工作模式寄存器),CCON(控制寄存器),

CCAPM0/CCAPM1/CCAPM2(比较/捕获寄存器)
    CMOD:PCA 工作模式寄存器

截图202601261441301126.jpg

    CPS2、CPS1、CPS0:PCA 计数脉冲源选择控制位。PCA 计数脉冲选择如下所示
截图202601261441475267.jpg

    ECF:PCA 计数溢出中断使能位。
    CCON:PCA 控制寄存器
截图202601261442099282.jpg

    CCAPM0:PCA 比较/捕获寄存器
截图202601261442309909.jpg

    当采用高速输出模式时,需要置位 CCAPMn 的 ECOMn、MATn、TOGn 相应位,具
体的宏实现如下所示:
/********************************************************
* 功能:设置PCA为高速输出模式
* 参数:num模块号(0-2) ; on 使能控制,1:允许,0禁止
*********************************************************/

#define SET_PCA_HIGHSPEED_MODE(num) \
    st( \
        if (num == 0) \
            CCAPM0 |= (BIT6 + BIT3 + BIT2); \
        else if (num == 1) \
            CCAPM1 |= (BIT6 + BIT3 + BIT2); \
        else if (num == 2) \
            CCAPM2 |= (BIT6 + BIT3 + BIT2); \
    )

软件设计
    本实验的软件设计,使用一个定时器产生不同频率并控制节拍,结合外部中断
功能,使用按键控制音乐的播放,音乐播放一遍后报警。
    实验代码可参看具体程序。这里主要讲解采用 PCA 模块的高速输出模式产生不
同频率的方法以及外部中断。首先是 PCA 模块的几个相关寄存器 CMOD(工作模式寄
存器),CCON(控制寄存器),CCAPM0/CCAPM1/CCAPM2(比较/捕获寄存器),当采用高
速输出模式时,需要置位 CCAPMn 的 ECOMn、MATn、TOGn 相应位。 PCA 定时器的计
数源为 1MHz,CCAP0H、CCAP0L 为匹配值,2 次匹配中断的匹配比较输出在 P1.1 上
输出一个完整的方波。变量 int_n 记录了中断次数,用于控制该音符脉冲输出的个
数,实际上就是音符输出的时间,代表了节拍的长度。在比较匹配中断服务程序中,
会自动判别整个音乐是否全部播放完成,如果音乐没有全部播完,将取出下一个音
符的音调和节拍,继续播放。数组 fre[9]、beat_fourth[9]为音符对应的半周期的
微妙数和基本 1/4 节拍应产生的半周期数。note_beat[Max_note]为整个音乐的发
生数据。

实验测试
    连线:用 8P 杜邦线将核心板 JP2 P32 连接 JP74 DK3
    核心板 JP1 P10 连接 JP69 任意插针
    核心板 JP1 P11 连接 JP70 任意插针
    实验现象:按下 DK3 键 播放音乐,音乐播放完成后,有急促的报警声。

代码分析

    PCA 模块 0 具体的初始化以及中断处理程序如下所示:

/********************************************************
* 函数功能:PCA模块0初始化函数 @12MHz
* 入口参数:无
* 返回参数:无
********************************************************/
void PCA_Init(void)
{
    PCA_CLK_DIV(0);                          // 系统时钟/12为定时器的时钟源
    PCA_MATCHINT_EN(0, OFF);                 // PCA模块0不允许匹配中断
    SET_PCA_HIGHSPEED_MODE(0);               // 设置PCA为高速输出模式
    CL = 0;
    CH = 0;
    CLR_PCA_MATCHINT_FLAG(0);                // 清除PCA模块0匹配中断标志
    PCA_RUN(OFF);
}


/*******************************************************
* 函数名称:PCA_inter
* 函数功能:PCA模块0的比较匹配中断服务程序
* 入口参数:无
* 返回参数:无
********************************************************/
void PCA_inter(void) interrupt 7 using 1
{
    CLR_PCA_MATCHINT_FLAG(0);                // 清除PCA模块0匹配中断标志

    if (play_on)
    {
        if (--int_n == 0)                     // 一个音符播放完成
        {
            PCA_RUN(OFF);                     // 关闭PCA定时器

            if (note_n < Max_note)            // 音乐未播放完,播放下一个音符
            {
                value = fre[note_beat[note_n]];          // 获取当前音符频率值
                temp = fre[note_beat[note_n]];           // 保存当前频率值(用于后续计算)
                int_n = beat_fourth[note_beat[note_n]];   // 获取当前节拍时长
                note_n++;                                  // 移动到下一个节拍索引
                int_n = int_n * note_beat[note_n];         // 更新节拍时长(可能原逻辑需确认此处合理性)
                note_n++;                                  // 再次移动索引(注意:连续两次note_n++可能导致跳音)
                CCAP0L = value;                            // 设置比较值低8位
                CCAP0H = value >> 8;                       // 设置比较值高8位(取value高8位)
                value += temp;                             // 更新下一次比较值(累加频率值?需结合硬件逻辑确认)
            }
            else
            {
                play_on = 0;                  // 音乐播放完成
            }

            PCA_RUN(ON);                      // 启动PCA定时器
        }
        else
        {
            CCAP0L = value;                  // 一个音符未播完,赋值下一次比较值
            CCAP0H = value >> 8;             // 将value的高8位赋给CCAP0H
            value += temp;                   // 更新下一次比较值
        }
    }
    else
    {
        alarm_flag = 1;                       // 触发报警标志
        PCA_RUN(OFF);                         // 关闭PCA定时器
    }
}

    针对外部中断,本次实验采用外部中断 0,独立按键与 P3.2 连接,下降沿触发,
当按键按下时产生外部中断,外部中断允许,则进入中断服务程序执行相应的处理,
即初始化音乐的播放条件并启动 PCA 定时器。外部中断 0 的中断处理程序如下:

/*******************************************************
* 函数名称:Int0_ISR
* 函数功能:外部中断0中断服务程序
* 入口参数:无
* 返回参数:无
********************************************************/
void Int0_ISR(void) interrupt INT0_VECTOR using 0
{
    if (!play_on)  // 音乐不在播放
    {
        TIMER_RUN(0, OFF);               // 停止定时器0
        war_buzzer = 0;                  // 关闭蜂鸣器(假设war_buzzer控制蜂鸣器)

        // 初始化PCA定时器,准备播放第一个音符
        note_n = 1;                      // 初始化音符索引(从1开始?需结合数组定义确认)
        int_n = beat_fourth[note_beat[note_n]];  // 获取初始节拍时长
        value = fre[note_beat[note_n]];          // 获取初始音符频率值
        temp = fre[note_beat[note_n]];           // 保存初始频率值(用于后续比较值累加)

        note_n++;                        // 移动到下一个音符索引(连续自增需注意数组越界风险)
        int_n = int_n * note_beat[note_n];       // 更新节拍时长(基于新索引的note_beat值)
        note_n++;                        // 再次移动索引(连续两次自增可能导致跳音,需确认逻辑)

        PCA_MATCHINT_EN(0, ON);          // PCA模块0允许匹配中断
        CCAP0L = value;                  // 设置比较值低8位
        CCAP0H = value >> 8;             // 将value的高8位赋给CCAP0H(修正"付给"为"赋给")
        value += temp;                   // 更新下一次比较值(累加频率值,需结合硬件逻辑确认)

        play_on = 1;                     // 标记开始从头播放
        PCA_RUN(ON);                     // 启动PCA定时器
    }
}
     针对报警,本实验采用定时器0控制,中断间隔50ms,中断服务程序中取反P1.0
(连接有源蜂鸣器),以此产生嘟嘟嘟嘟的报警声。具体初始化程序和中断服务程
序如下:
/*******************************************************
* 函数名称:Timer0Init
* 函数功能:定时器0初始化函数 50毫秒@12MHz
* 入口参数:无
* 返回参数:无
********************************************************/
void Timer0Init(void)
{
    TIMER_CLK_DIV(0, 12);          // 设置定时器0时钟12T模式(12分频)
    TIMER_TIME(0);                 // 设置定时器0为定时模式
    TIMER_MODE(0, 0);              // 设置定时器0工作模式0(13位自动重装?需结合硬件手册确认)

    // 计算50ms定时初值(@12MHz,12T模式下机器周期=12/12MHz=1us,50ms=50000us)
    TH0 = (65536 - 50000) / 256;   // 定时器高8位初值(65536-50000=15536 → 15536/256=60 → 0x3C)
    TL0 = (65536 - 50000) % 256;   // 定时器低8位初值(15536%256=176 → 0xB0)

    TIMER_RUN(0, OFF);             // 停止定时器0(初始化时默认关闭)
    TIMER_INT_EN(0, ON);           // 使能定时器0中断
    CLR_TIMER_FLAG(0);             // 清除定时器0溢出标志(TF0)
}

/*******************************************************
* 函数名称:Timer0_ISR
* 函数功能:定时器0中断服务程序
* 入口参数:无
* 返回参数:无
********************************************************/
void Timer0_ISR(void) interrupt T0_VECTOR using 1
{
    war_buzzer = !war_buzzer;      // 有源蜂鸣器每隔50ms翻转电平,实现报警鸣响(50ms周期 → 10Hz频率)
}
    主程序只是判断报警标志位是否置位,并没有做其他工作。

程序相关宏文件:

/********************************************************
* 功能:开全局中断
* 参数:on - 使能控制,非0允许,0禁止(!!on将任意非零值转为1,0保持0)
*********************************************************/
#define INT_GLOBAL_ENABLE(on)  EA = (!!on)


/********************************************************
* 功能:定时器运行操作控制
* 参数:num - 计数器号(0-4);on - 运行控制,1:启动,0停止
*********************************************************/
#define TIMER_RUN(num, on) \
    st( \
        if (num == 0)       TR0 = on; \
        else if (num == 1)  TR1 = on; \
        else if (num == 2)  (on) ? (AUXR |= BIT4) : (AUXR &= ~BIT4); \
        else if (num == 3)  (on) ? (T4T3M |= BIT3) : (T4T3M &= ~BIT3); \
        else if (num == 4)  (on) ? (T4T3M |= BIT4) : (T4T3M &= ~BIT4); \
    )


/********************************************************
* 功能:定时器定时操作控制(配置为定时模式)
* 参数:num - 计数器号(0-4)
* 说明:通过清除模式位最高位(通常为C/T位,0=定时,1=计数)
*********************************************************/
#define TIMER_TIME(num) \
    st( \
        if (num == 0)       TMOD &= ~BIT2;  /* 清除T0 C/T位(BIT2) */ \
        else if (num == 1)  TMOD &= ~BIT6;  /* 清除T1 C/T位(BIT6) */ \
        else if (num == 2)  AUXR &= ~BIT3;  /* 清除T2 C/T位(BIT3) */ \
        else if (num == 3)  T4T3M &= ~BIT2; /* 清除T3 C/T位(BIT2) */ \
        else if (num == 4)  T4T3M &= ~BIT6; /* 清除T4 C/T位(BIT6) */ \
    )


/********************************************************
* 功能:定时器中断使能操作控制
* 参数:num - 计数器号(0-4);on - 使能控制,1:允许,0禁止(1=溢出中断允许)
*********************************************************/
#define TIMER_INT_EN(num, on) \
    do { \
        if (num == 0)       ET0 = on; \
        else if (num == 1)  ET1 = on; \
        else if (num == 2)  (on) ? (IE2 |= BIT3) : (IE2 &= ~BIT3);  /* T2中断使能 */ \
        else if (num == 3)  (on) ? (IE2 |= BIT5) : (IE2 &= ~BIT5);  /* T3中断使能 */ \
        else if (num == 4)  (on) ? (IE2 |= BIT6) : (IE2 &= ~BIT6);  /* T4中断使能 */ \
    } while (0)


/********************************************************
* 功能:定时器工作模式选择
* 参数:num - 计数器号(0-1);sel - 模式选择(0-3)
*       0:16位重载  1:16位计数器  2:8位重载  3:计数器无效(具体以手册为准)
*********************************************************/
#define TIMER_MODE(num, sel) \
    do { \
        if (sel < 4) { \
            if (num == 0) { \
                TMOD &= ~0x03;  /* 清除T0模式位(M1/M0) */ \
                TMOD |= sel;    /* 设置T0模式 */ \
            } else if (num == 1) { \
                TMOD &= ~0x30;  /* 清除T1模式位(M1/M0) */ \
                TMOD |= sel << 3; /* 设置T1模式(左移3位对齐) */ \
            } \
        } \
    } while (0)


/********************************************************
* 功能:定时器定时时间分频选择
* 参数:num - 计数器号(0-4);sel - 分频选择(1=1分频,0=12分频)
*********************************************************/
#define TIMER_CLK_DIV(num, sel) \
    st( \
        if (num == 0)       (sel == 1) ? (AUXR |= BIT7) : (AUXR &= ~BIT7);  /* T0分频 */ \
        else if (num == 1)  (sel == 1) ? (AUXR |= BIT6) : (AUXR &= ~BIT6);  /* T1分频 */ \
        else if (num == 2)  (sel == 1) ? (AUXR |= BIT2) : (AUXR &= ~BIT2);  /* T2分频 */ \
        else if (num == 3)  (sel == 1) ? (T4T3M |= BIT1) : (T4T3M &= ~BIT1); /* T3分频 */ \
        else if (num == 4)  (sel == 1) ? (T4T3M |= BIT5) : (T4T3M &= ~BIT5); /* T4分频 */ \
    )


/********************************************************
* 功能:清除定时器中断标志(溢出中断)
* 参数:num - 计数器号(0-1)
* 修正:原代码中num=0和num=1均清除TF0,疑似笔误,若为不同定时器需区分TF0/TF1
*********************************************************/
#define CLR_TIMER_FLAG(num) \
    st( \
        if (num == 0)       TF0 = 0; \
        else if (num == 1)  TF1 = 0;  /* 修正:原代码误写为TF0,应为TF1(T1溢出标志) */ \
    )


/********************************************************
* 功能:设置外部中断触发模式
* 参数:inum - 中断号(INUM_EX0/INUM_EX1);mode - 触发模式(RISING_FALLING=双边沿,FALLING=下降沿)
* 示例:INT_TRIGGER(INUM_EX0, RISING_FALLING);
*********************************************************/
#define INT_TRIGGER(inum, mode) \
    do { \
        if (inum == INUM_EX0)  IT0 = mode; \
        else if (inum == INUM_EX1)  IT1 = mode; \
    } while (0)


/********************************************************
* I/O口四种工作模式定义
* IO_ST   - 准双向口(标准8051模式)
* IO_OUT  - 推挽输出
* IO_IN   - 高阻输入
* IO_OPEN - 开漏输出
*********************************************************/
#define IO_ST    0
#define IO_OUT   1
#define IO_IN    2
#define IO_OPEN  3


/********************************************************
* 功能:设置I/O口工作模式(支持Pn端口,n=0~4)
* 参数:port - 端口号(如1表示P1);pin - 引脚掩码(如BIT0表示P1.0);mode - 模式(IO_ST/IO_OUT/IO_IN/IO_OPEN)
* 说明:通过PnM1和PnM0寄存器组合配置(具体逻辑以芯片手册为准)
*********************************************************/
#define IO_PORT_SET(port, pin, mode) \
    do { \
        if (mode == IO_ST) { \
            P##port##M1 &= ~(pin); \
            P##port##M0 &= ~(pin); \
        } else if (mode == IO_OUT) { \
            P##port##M1 &= ~(pin); \
            P##port##M0 |= (pin); \
        } else if (mode == IO_IN) { \
            P##port##M1 |= (pin); \
            P##port##M0 &= ~(pin); \
        } else { /* mode == IO_OPEN */ \
            P##port##M1 |= (pin); \
            P##port##M0 |= (pin); \
        } \
    } while (0)


/********************************************************
* 功能:PCA模块计数运行操作控制
* 参数:on - 运行控制,1:启动,0停止(CR为PCA控制寄存器,1=启动)
*********************************************************/
#define PCA_RUN(on)  CR = on


/********************************************************
* 功能:PCA计数脉冲源选择
* 参数:sel - 脉冲源选择(0-7),具体对应关系:
*       000: fosc/12  001: fosc/2  010: Timer0溢出  011: ECI/P3.4外部时钟(≤fosc/2)
*       100: fosc/1   101: fosc/4  110: fosc/6     111: fosc/8
*********************************************************/
#define PCA_CLK_DIV(sel) \
    st( \
        if (sel == 0)       CMOD &= ~(BIT1 | BIT2 | BIT3); \
        else if (sel == 1)  CMOD = (CMOD & ~(BIT2 | BIT3)) | BIT1; \
        else if (sel == 2)  CMOD = (CMOD & ~(BIT1 | BIT3)) | BIT2; \
        else if (sel == 3)  CMOD = (CMOD & ~BIT3) | (BIT1 | BIT2); \
        else if (sel == 4)  CMOD = (CMOD & ~(BIT1 | BIT2)) | BIT3; \
        else if (sel == 5)  CMOD = (CMOD & ~BIT2) | (BIT3 | BIT1); \
        else if (sel == 6)  CMOD = (CMOD & ~BIT1) | (BIT3 | BIT2); \
        else if (sel == 7)  CMOD |= (BIT3 | BIT1 | BIT2); \
    )


/********************************************************
* 功能:清除PCA捕获或匹配中断标志
* 参数:num - 模块号(0-2)(CCF0/CCF1/CCF2为模块0-2匹配中断标志)
*********************************************************/
#define CLR_PCA_MATCHINT_FLAG(num) \
    st( \
        if (num == 0)       CCF0 = 0; \
        else if (num == 1)  CCF1 = 0; \
        else if (num == 2)  CCF2 = 0; \
    )


/********************************************************
* 功能:清除PCA溢出中断标志(CF为PCA溢出标志)
* 参数:无
*********************************************************/
#define CLR_PCA_OVERFLOWINT_FLAG()  CF = 0


/********************************************************
* 功能:PCA匹配中断使能操作控制
* 参数:num - 模块号(0-2);on - 使能控制,1:允许,0禁止(BIT0为CCAPMx的ECF位,1=允许匹配中断)
*********************************************************/
#define PCA_MATCHINT_EN(num, on) \
    do { \
        if (num == 0)       (on) ? (CCAPM0 |= BIT0) : (CCAPM0 &= ~BIT0); \
        else if (num == 1)  (on) ? (CCAPM1 |= BIT0) : (CCAPM1 &= ~BIT0); \
        else if (num == 2)  (on) ? (CCAPM2 |= BIT0) : (CCAPM2 &= ~BIT0); \
    } while (0)


/********************************************************
* 功能:设置PCA为高速输出模式
* 参数:num - 模块号(0-2)
* 说明:BIT6(CAPPn)=1捕获使能,BIT3(MATn)=1匹配使能,BIT2(TOGGLEn)=1高速输出模式
*********************************************************/
#define SET_PCA_HIGHSPEED_MODE(num) \
    do { \
        if (num == 0)       CCAPM0 |= (BIT6 | BIT3 | BIT2); \
        else if (num == 1)  CCAPM1 |= (BIT6 | BIT3 | BIT2); \
        else if (num == 2)  CCAPM2 |= (BIT6 | BIT3 | BIT2); \
    } while (0)


回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:324
  • 最近打卡:2026-02-12 09:05:32

835

主题

1万

回帖

2万

积分

管理员

积分
22170
发表于 2026-1-27 08:47:38 | 显示全部楼层
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2026-2-12 18:02 , Processed in 0.101656 second(s), 49 queries .

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表