- 打卡等级:常住居民I
- 打卡总天数:68
- 最近打卡:2026-02-10 09:53:37
注册会员
- 积分
- 164
|
实验目的
掌握有源蜂鸣器的使用方法。
掌握无源蜂鸣器的使用方法。
掌握 STC15 系列单片机 PCA 定时器高速输出模式的使用。
实验内容
掌握 STC15 单片机 PCA 定时器高速输出模式;
使用单片机开发板实现蜂鸣器播放音乐的编写;
结合模电知识分析完成蜂鸣器简单驱动电路的设计;
硬件电路
如图 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。
因此,如果要发出 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 工作模式寄存器
CPS2、CPS1、CPS0:PCA 计数脉冲源选择控制位。PCA 计数脉冲选择如下所示
ECF:PCA 计数溢出中断使能位。
CCON:PCA 控制寄存器
CCAPM0:PCA 比较/捕获寄存器
当采用高速输出模式时,需要置位 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)
|
|