求助:用两个单独的p通道驱动喇叭声音大,换成两个n通道后声音变小,是什么问题
STC8H4K32TL单片机,原控制板用的蜂鸣器,现需要加一个简单的语音提示功能,改用ad116H桥芯片推喇叭,在测试版上用的两个单独p通道测试正常后移到项目控制板上,因为项目控制板上没有p通道了,就用了两个独立的n通道修改模式后代替p通道。现在的问题是一样的参数蜂鸣器模式和音乐模式声音都变小了。试了很多方法都没有找到原因,难道n通道不能输出全量程幅度吗。
以下是两个p通道版本。
#include <intrins.h> // 包含_nop_()函数
#include "music-8bit-8K-wave.h"
#include <STC8H.H> // 包含STC8H系列单片机的寄存器定义,如P4、AUXR、TMOD等
typedef bit u2; //给逻辑值起个好听的名字,
typedef unsigned char u8; //给短整数起个好听的名字,
typedef unsigned int u16; //给中整数起个好听的名字,
typedef unsigned long u32; //给长整数起个好听的名字,
//喇叭pwma控制,上电状态在蜂鸣器模式,每次需要播放语音时切换到音乐模式,播放音频文件,结束后切换到蜂鸣器模式。
//蜂鸣器响或播放音乐时不能切换状态,且蜂鸣器优先。
// 全局变量
u32 Music_i = 0; //音频播放位置
u2 BeepMode = 0; // 0:蜂鸣器模式,1:音乐模式
u2 LastKeyState_P03 = 1;// P0.3上一次状态
u2 LastKeyState_P02 = 1;// P0.2上一次状态
u8 d1;// 上个值
u8 d2;// 下个值
u2 czzt = 0;// 插值一次状态,0当前位置正常采集点,1当前位置插值点,
u8 KeyDebounceCnt_P03 = 0;// P0.3消抖计数器
u8 KeyDebounceCnt_P02 = 0;// P0.2消抖计数器
// 声明外部数组
extern const unsigned char code Music1[];
extern const unsigned char code Music2[];
extern const unsigned char code Music3[];
extern const unsigned char code Music4[];
u8 *Music; // 当前播放哪首歌的指针
u16MUSIC_LEN; // 当前歌曲长度
#define MUSIC_LEN1 sizeof(Music1);//取数组长度备用
#define MUSIC_LEN2 sizeof(Music2);//取数组长度备用
#define MUSIC_LEN3 sizeof(Music3);//取数组长度备用
#define MUSIC_LEN4 sizeof(Music4);//取数组长度备用
// ================= 延时函数(用于消抖) =================
void DelayMs(unsigned int ms)
{
unsigned int i, j;
for(i = 0; i < ms; i++) {
for(j = 0; j < 1200; j++) {
_nop_();
}
}
}
// ================= PWM 初始化(音乐模式) =================
void PWMA_Music(void)
{
P_SW2 |= 0x80;
// 1. 边沿对齐模式(向上计数)
PWMA_CR1 = 0x00; // 默认就是边沿对齐
// 2. 8位PWM(0~255)
PWMA_PSCR = 2; // 3分频,33MHz/3=11MHz
PWMA_ARR = 255; // 8位分辨率,PWM频率 = 11MHz / 151 ≈ 72.8kHz
// 3. 通道3 (P2.4) - 上桥
PWMA_CCMR3 = 0x68; // PWM模式1
PWMA_CCR3 = 0; // 占空比0
PWMA_CCER2 = 0x01; // 开启通道3,正极性
// 4. 通道1 (P1.0) - 下桥(用P通道,不用N通道)
PWMA_CCMR1 = 0x68; // PWM模式1
PWMA_CCR1 = 0; // 占空比0
PWMA_CCER1 = 0x01; // 开启通道1,正极性(不用N通道)
// 5. 关闭死区(因为不用互补,不需要死区)
PWMA_DTR = 0;
// 6. 引脚映射
PWMA_PS = (0x01 << 4) | (0x00 << 0);// PWM3P->P2.4, PWM1P->P1.0
// 7. 输出使能
PWMA_ENO = (1 << 4) | (1 << 0); // 开启 PWM3P (P2.4) 和 PWM1P (P1.0)
// 8. 启动
PWMA_BKR = 0x80;
PWMA_CR1 |= 0x01;
BeepMode = 1; //修改喇叭模式为音乐状态
}
// ================= PWM 初始化(蜂鸣器模式) =================
void PWMA_Beep(void)
{
P_SW2 |= 0x80;
// 1. 边沿对齐模式(向上计数)
PWMA_CR1 = 0x00; // 默认就是边沿对齐
// 2. 8位PWM(0~255)
PWMA_PSCR = 48; // // 48分频,33MHz/128=687kHz
PWMA_ARR = 255; // 2.69kHz PWM频率(687kHz/255≈2.69kHz)
PWMA_DTR = 0; //死区(因为下桥取反互补)
// 3. 通道3 (P2.4) - 上桥
PWMA_CCMR3 = 0x68; // PWM模式1
PWMA_CCR3 = 0; // 10%占空比(92×40%=36.8≈37,但我们用40),占空比大喇叭超电流,而且播放声音和凤鸣时音量不一样,
PWMA_CCER2 = 0x01; // 开启通道3,正极性
// 4. 通道1 (P1.0) - 下桥(取反输出,带死区时间)
PWMA_CCMR1 = 0x68; // PWM模式1
PWMA_CCR1 = 0; // 下桥占空比 =0
PWMA_CCER1 = 0x01; // 开启通道1,正极性(不用N通道)
// 5. 引脚映射
PWMA_PS = (0x01 << 4) | (0x00 << 0);// PWM3P->P2.4, PWM1P->P1.0
// 6. 输出使能
PWMA_ENO = (1 << 4) | (1 << 0); // 开启 PWM3P (P2.4) 和 PWM1P (P1.0)
// 7. 启动
PWMA_BKR = 0x80;
PWMA_CR1 |= 0x01;
BeepMode = 0; //修改喇叭模式为蜂鸣器状态
}
// ================= 定时器0 初始化 =================
void Timer0_Init(void)
{
AUXR |= 0x80; // 1T模式
TMOD &= 0xF0; // 16位自动重装
TL0 = 0x24; // 22kHz初值低8位 (64036 & 0xFF)
TH0 = 0xFA; // 22kHz初值高8位 (64036 >> 8)
TF0 = 0; // 清除定时器0溢出标志
ET0 = 0; // 打开定时器0中断,需要播放音乐才打开。
TR0 = 1; // 启动定时器0开始计数
}
// ================= 定时器0 中断服务函数 =================
void Timer0_ISR(void) interrupt 1
{
u8 pwm_val; // 8位PWM值
u16 sum; // 插值
u2 bridge_select; // 上下桥选择位
if (Music_i < MUSIC_LEN)// 使用宏定义 MUSIC_LEN
{
if (czzt == 1) //插值位置
{
czzt = 0; //插值状态
sum=d1+d2;
if(sum>=256) //加法后面跟随大于等于256时编译器优化时有99%可能用加法的溢出位判断,这样可以加速程序。
{
bridge_select = 1;// 提取上下桥标识位
} else {
bridge_select = 0;// 提取上下桥标识位
}
pwm_val = sum; //16位转8位,51单片机直接等于只取低8位,其他单片机可能要通过位与运算才能获得低8位。
}else { //正常播放
czzt = 1; //插值状态
bridge_select = (d1 >> 7) & 0x01; // 提取上下桥标识位(d1的bit3)
pwm_val = d1 <<1; // 提取音频数据,并放大2倍,右移放大并挤出标志位。
d2=d1; //存储本次数据
d1 = Music; //准备下个数据
}
//修改通道占空比,输出数据
if (bridge_select) {
// 上半周:P通道工作,N通道静音
PWMA_CCR3 = pwm_val; // P通道输出PWM占空比
PWMA_CCR1 = 0; // N通道静音:全高
} else {
// 下半周:N通道工作,P通道静音
PWMA_CCR3 = 0; // P通道静音:全低
PWMA_CCR1 = 255 - pwm_val; // N通道输出PWM占空比
}
} else {
// 播放完毕,
Music_i=0; //播放位置置零,1.切换到蜂鸣器模式,在主循环里表示蜂鸣器模式时检测这个值,如果为零则播放完毕,切换回蜂鸣器模式,播放时要插值需提前读取一个值,起始位就是1了,
PWMA_CCR3 = 0; // P通道静音:全低
PWMA_CCR1 = 0; // p通道静音:全高
ET0 = 0; // 关闭中断,关闭定时器中断节省cpu开支。
}
}
// ================= 按钮初始化 =================
void Key_Init(void)
{
// P0.3和P0.2设置为上拉输入
P0M0 &= ~0x0C;// P0.3和P0.2准双向模式
P0M1 &= ~0x0C;
// 启用上拉电阻
P0PU |= 0x0C; // 启用P0.3和P0.2的上拉电阻
}
// ================= 读取按钮状态 =================
bit ReadKey_P03(void)
{
return (P0 & 0x08) ? 1 : 0;// P0.3为高电平返回1,低电平返回0
}
bit ReadKey_P02(void)
{
return (P0 & 0x04) ? 1 : 0;// P0.2为高电平返回1,低电平返回0
}
// ================= 播放哪一个声音 排水、脱水、洗涤、换洗=================
void StartMusic(unsigned char i)
{
if(i == 1)
{
Music = Music1;
MUSIC_LEN = MUSIC_LEN1
}
// else if(i == 2)
// {
// Music = Music2;
// MUSIC_LEN= MUSIC_LEN2
// }
// else if(i == 3)
// {
// Music = Music3;
// MUSIC_LEN= MUSIC_LEN3
// }
// else if(i == 4)
// {
// Music = Music4;
// MUSIC_LEN= MUSIC_LEN4
// }
Music_i=1; //置第一个采样点位置
d1 = Music;//插值要预读第一个值。
czzt = 0; //初始化插值状态
PWMA_Music(); //直接切换到音乐模式
ET0 = 1; // 打开定时器0中断
}
// ================= 主函数 =================
void main(void)
{
EA = 1;
// 初始化
Key_Init();
PWMA_Beep();// 默认蜂鸣器模式
Timer0_Init();
while(1) {
bit current_key_p03, current_key_p02;
if (1==BeepMode) //在音乐模式,检查状态播放完了立马切换回蜂鸣器模式。
{
if (0==Music_i)
{
PWMA_Beep();// 切换回蜂鸣器模式
}
}
// 读取当前按钮状态
current_key_p03 = ReadKey_P03();
current_key_p02 = ReadKey_P02();
// ========== P0.3按钮处理(蜂鸣器) ==========
if (current_key_p03 != LastKeyState_P03) {
// 状态变化,开始消抖
KeyDebounceCnt_P03++;
if (KeyDebounceCnt_P03 >= 5) {// 消抖5次
KeyDebounceCnt_P03 = 0;
if (current_key_p03 == 0) {// 按下(低电平)
// 在蜂鸣器模式打开蜂鸣器,不在则忽略,
if (0==BeepMode) {
PWMA_CCR1 = 20; // 启动PWM蜂鸣器。
}
DelayMs(10);// 消抖延时
} else {// 松开(高电平)
// 在蜂鸣器模式打开蜂鸣器,不在则忽略,
if (0==BeepMode) {
PWMA_CCR1 = 0; // 停止PWM,// 关闭蜂鸣器
}
DelayMs(10);// 消抖延时
}
LastKeyState_P03 = current_key_p03;
}
} else {
KeyDebounceCnt_P03 = 0;// 状态稳定,重置计数器
}
// ========== P0.2按钮处理(播放音乐) ==========
if (current_key_p02 != LastKeyState_P02) {
// 状态变化,开始消抖
KeyDebounceCnt_P02++;
if (KeyDebounceCnt_P02 >= 5) {// 消抖5次
KeyDebounceCnt_P02 = 0;
if (current_key_p02 == 0) {// 按下(低电平)
// 切换到音乐模式并播放
StartMusic(1);
DelayMs(10);// 消抖延时
}
LastKeyState_P02 = current_key_p02;
}
} else {
KeyDebounceCnt_P02 = 0;// 状态稳定,重置计数器
}
// 短暂延时,避免CPU占用过高
_nop_();
_nop_();
_nop_();
}
}
这是两个n通道版本
//==================== PWMB初始化 pwm驱动H桥喇叭 ====================
void PWMA_Init(void)
{
P_SW2 |= 0x80; // 允许访问扩展寄存器(XDATA)
PWMA_CCER1 = 0x00; // 关闭通道1/2 比较输出
PWMA_CCER2 = 0x00; // 关闭通道3/4 比较输出
PWMA_ENO = 0x00; // 关闭所有 PWM 输出引脚
// 1. 边沿对齐模式(向上计数)
PWMA_CR1 = 0x00; // 默认就是边沿对齐
// 2. ? 关键:设置 AOE=1,允许 N 通道全幅度输出
PWMA_BKR = 0xC0; // 0xC0 = 1100 0000 → MOE=1, AOE=1
PWMA_ARR= 255; // 计数器占空比量程,自动重装值:0.蜂鸣器模式约 2.7kHz(33M/预分配48/255=2.7K) 1.音乐模式64khz(33M/预分配2/255=64K)
PWMA_DTR = 0; // 死区时间,防止上下桥直通
// 引脚映射:PWM2N->P1.3, PWM4N->P1.7
PWMA_PS &= ~((0x03 << 6) | (0x03 << 2)); // 清除通道2/4 映射位
PWMA_PS |=((0x00 << 6) | (0x00 << 2)); // 映射到第0组(P1口)
PWMA_CCMR2 = 0x68; // 通道2:PWM 模式1,预装载使能
PWMA_CCMR4 = 0x68; // 通道4:PWM 模式1,预装载使能
// 6. ? N 通道独立输出(正极性)
PWMA_CCER1 = 0x40; // CC2NE=1, CC2NP=0(只开 N 通道)
PWMA_CCER2 = 0x40; // CC4NE=1, CC4NP=0(只开 N 通道)
PWMA_ENO |= (1 << 7) | (1 << 3); // 使能 PWM4N(P1.7) 和 PWM2N(P1.3) 输出
PWMA_BKR = 0x80; // 主输出使能(MOE=1)
PWMA_CR1 |= 0x01; // 启动计数器
}
// ================= PWMA 切换为蜂鸣器模式=================
void PWMA_Beep(void)
{
PWMA_PSCR = 48; // 预分频:48 分频
PWMA_CCR2 = 0; // 通道2 占空比20,总数255,只用上桥,开启音量值:PWMA_SATA
PWMA_CCR4 = 0; // 通道4 占空比0
BeepMode = 0; // 标记当前为蜂鸣器模式
}
// ================= PWM 切换为音乐模式=================
void PWMA_Music(void)
{
PWMA_PSCR = 2; // 预分频:2 分频(高频载波)
PWMA_CCR2 = 0; // 初始静音
PWMA_CCR4 = 0; // 初始静音
BeepMode = 1; // 标记当前为音乐模式
}
// ================= 播放哪一个声音 换洗、洗涤、脱水、排水、=================
void StartMusic(unsigned char i)
{
if(i == 1)
{
Music = Music1;
MUSIC_LEN = MUSIC_LEN1;
}
else if(i == 2)
{
Music = Music2;
MUSIC_LEN= MUSIC_LEN2;
}
else if(i == 3)
{
Music = Music3;
MUSIC_LEN= MUSIC_LEN3;
}
else if(i == 4)
{
Music = Music4;
MUSIC_LEN= MUSIC_LEN4;
}
Music_i=1; //置第一个采样点位置
d1 = Music;//插值要预读第一个值。
czzt = 0; //初始化插值状态
PWMA_Music(); //直接切换到音乐模式
IE2 |= 0x04; // 打开定时器2中断,刷新音频数据。
}
// ================= 定时器2 初始化 =================
void Timer2_Init(void)
{
AUXR |= 0x04; // 定时器2为1T模式 (T2x12=1)
T2L = 0x24; // 22kHz初值低8位 (64036 & 0xFF)
T2H = 0xFA; // 22kHz初值高8位 (64036 >> 8)
AUXR |= 0x10; // 启动定时器2 (T2R=1)
// IE2 |= 0x04; // 打开定时器2中断 (ET2=1)
IE2 &= ~0x04;// 关闭定时器2中断,只有播放音频时才打开。
}
// ================= 定时器2 中断服务函数 =================
void Timer2_ISR(void) interrupt 12
{
u8 pwm_val; // 8位PWM值
u16 sum; // 插值
u2 bridge_select; // 上下桥选择位
if (Music_i < MUSIC_LEN)// 使用宏定义 MUSIC_LEN
{
if (czzt == 1) //插值位置
{
czzt = 0; //插值状态
sum=d1+d2;
if(sum>=256) //加法后面跟随大于等于256时编译器优化时有99%可能用加法的溢出位判断,这样可以加速程序。
{
bridge_select = 1;// 提取上下桥标识位
} else {
bridge_select = 0;// 提取上下桥标识位
}
pwm_val = sum; //16位转8位,51单片机直接等于只取低8位,其他单片机可能要通过位与运算才能获得低8位。
}else { //正常播放
czzt = 1; //插值状态
bridge_select = (d1 >> 7) & 0x01; // 提取上下桥标识位(d1的bit3)
pwm_val = d1 <<1; // 提取音频数据,并放大2倍,右移放大并挤出标志位。
d2=d1; //存储本次数据
d1 = Music; //准备下个数据
}
//修改通道占空比,输出数据
if (bridge_select) {
// 上半周:P通道工作,N通道静音
PWMA_CCR2 = pwm_val; // 上桥输出PWM占空比
PWMA_CCR4 = 0; // 下桥静音
} else {
// 下半周:N通道工作,P通道静音
PWMA_CCR2 = 0; // 上桥静音
PWMA_CCR4 = 255 - pwm_val; // 下桥输出PWM占空比
}
} else {
// 播放完毕,
Music_i=0; //播放位置置零,1.切换到蜂鸣器模式,在主循环里表示蜂鸣器模式时检测这个值,如果为零则播放完毕,切换回蜂鸣器模式,播放时要插值需提前读取一个值,起始位就是1了,
PWMA_CCR2 = 0; // 上桥道静音
PWMA_CCR4 = 0; // 下桥道静音
IE2 &= ~0x04;// 关闭定时器2中断,节省cpu开支。
}
}
H桥是高电平触发的,蜂鸣器模式只用到半桥,播放音乐时才用上下桥驱动,电平没有反,电平反了,静音状态h桥应该有电流,碰喇叭线应该喇叭会刺啦刺啦响,蜂鸣器占空比只有20,arr全量程设的255,占比很低,反了应该声音更大才对,现在就是出现用两个p通道驱动声音大,两个n通道声音小,他们两个应该声音一样大才对。 大神在不在 PWM肯定是可以输出100%占空比的,这个无须质疑。
请贴出你的驱动电路我看看。 梁工 发表于 2026-5-19 14:28
PWM肯定是可以输出100%占空比的,这个无须质疑。
请贴出你的驱动电路我看看。 ...
不是单片机问题,是我自己的电路问题,我单片机直接驱动h桥,h桥输出到喇叭,h桥内阻不一样导致音量不一样。测试中还有个不确认的地方,pwma控制的通道,p通道关闭,只开n通道,它就成p通道了吗,后面的取反就没用了吗,我发现只开启n通道(ne=1),极性(np=0,1)设置那种都一样不起效果。我不用负极性,只要不影响我的功能就行 彭彭 发表于 2026-5-20 14:25
不是单片机问题,是我自己的电路问题,我单片机直接驱动h桥,h桥输出到喇叭,h桥内阻不一样导致音量不一 ...
如果只开P或N,则都是P,输出占空比0~100%,音量就看你的驱动了。看了AD116手册,没看到速度参数,看其内部三极管的电路,估计速度不高,你用个10欧姆电阻做负载,输出稳定的50%占空的PWM,示波器观察AD116输出波形,并贴出来看看。
没有波形看,我是没法判断你的问题所在的。如果驱动速度足够高,时没有问题的,我用HSPWM做D类功放,效果还不错的,70~90KHz的PWM频率,32V供电,4欧姆负载,一个声道可以输出125W功率(RMS)。 梁工 发表于 2026-5-20 16:54
如果只开P或N,则都是P,输出占空比0~100%,音量就看你的驱动了。看了AD116手册,没看到速度参数,看其内 ...
没有示波器,靠万用表,喇叭LED灯检测,现在可以确认单片机输出正常,是h桥体质问题,每个内阻不一样,我想再问个其他的,ne是单独开启n通道,n通道就成p通道了,不是互补通道np控制电平极性的参数还起作用吗,如果有用这个参数是控制电平上下反转,还是左右反转的。 彭彭 发表于 2026-5-20 22:04
没有示波器,靠万用表,喇叭LED灯检测,现在可以确认单片机输出正常,是h桥体质问题,每个内阻不一样,我 ...
只用一个输出,都是P,遵循P输出的设置。 梁工 发表于 2026-5-20 22:09
只用一个输出,都是P,遵循P输出的设置。
好的,谢谢,我试的也是只开启n通道,就和p通道一样了,极性标志位与结果无关了
页:
[1]