CH585 PCM 播放测试以及双 PWM 原理

CH585 PCM 播放测试以及双 PWM 原理 简单聊一下双 PWM 音频播放原理及实现以及 PCM 播放测试 ...... 矜辰所致前言上一篇文章我们测试了一下 SBC 音频解码然后通过 双 PWM 播放音频的效果当时更多的是想看看效果只做了一些必要的解释说明。考虑到后面自己应用上需要有一些修改有些必要的原理还是要了解一下这样后期的做一些应用也知道去改什么地方。所以本文我们简单学习一下双 PWM 的原理 以及测试一下 PCM 音频播放。相关文章嵌入式语音开发应用基础说明CH585 SBC 音频解码播放测试说明.我是矜辰所致全网同名尽量用心写好每一系列文章不浮夸不将就认真对待学知识的我们矜辰所致金石为开目录前言一、 双 PWM 原理1.1 基础说明1.2 程序中双 PWM 的体现1.3 PWM 的频率1.4 音量调节二、 PCM 播放测试2.1 PCM 数据说明2.2 音频文件生成2.3 APP修改测试结语一、 双 PWM 原理现在有了 AI 很多通用技术的理论了解并不是什么难事我们简单说明一下主要还是结合一下程序中如何实现做一些分析。1.1 基础说明双 PWMDual PWM / Differential PWM音频驱动的本质是用两路互补的 PWM 信号来驱动扬声器通过差分放大还原出模拟音频波形。双 PWM 音频:两路互补的数字开关信号 → 差分驱动扬声器 → 利用感性负载滤除载波 → 还原模拟声音相对于单路 PWM 双路 PWM 方案有以下优点抗干扰强外界电磁干扰同时耦合到两根线差分相互抵消信噪比更高双路 PWM 喇叭线圈等效 LC 低通自动滤除高频载波 。输出功率翻倍差分峰峰值 2×VDD同等供电下音量更大无直流偏置静态时两路 50% 占空比扬声器两端电压差为 0音圈不会静态偏移不烧喇叭、低音更扎实外围电路无需输出电容单端需要隔直电容差分直接耦合常见波形与 PCM 映射逻辑如下音频采样值 → 调制为两路互补 PWMPWM_A 占空比 50% ΔPWM_B 占空比 50% - Δ举个例子 设 PWM 周期固定满幅占空比 0~100%.静音PCM0PWM 占空比 50%PWM - 占空比 50%两路电压平均值相等喇叭两端压差 0不发声。.正半周音频PCM0PWM 占空比升高PWM - 占空比同步降低喇叭左端平均电压 右端电流正向流过音圈。.负半周音频PCM0PWM 占空比降低PWM - 占空比同步升高喇叭右端平均电压 左端电流反向流过音圈。还有一种单极性交替 PWM (本文测试示例就使用的这种)pcm0PWM输出幅值PWM20pcm0PWM-输出幅值PWM10等价 H 桥单向导通正半周左 PWM 输出方波右侧恒低电流正向流过喇叭负半周右 PWM 输出方波左侧恒低电流反向流过喇叭过零点pcm0两路都是 0无静态压差1.2 程序中双 PWM 的体现在我们的测试程序中有这么一段数据的处理for(uint32_ti0;i(out_len1);i){if(pcm_buf[i]0){*p_datapcm_buf[i]/8;// 正值 → PWM1 有输出*p_data20;// PWM2 0}else{*p_data20-pcm_buf[i]/8;// 负值 → PWM2 有输出*p_data0;// PWM1 0}p_data;p_data2;}当音频信号为正0时p_dataPWM 通道 1输出对应的占空比p_data2PWM 通道 2静止在 0 电平。当音频信号为负0时p_data2PWM 通道 2输出对应的占空比取绝对值p_dataPWM 通道 1静止在 0 电平。代码中的数据事直接决定 PWM 的占空比的为什么上面的数据需要除 8 *p_data pcm_sample / 8;因为 这个数值是直接决定 PWM 的占空比的在程序中 PWM 的占空比 也和总周期计数有关这个值肯定不能大于总计数这个我们需要先往下看先了解一下 PWM 的频率有关的内容 。1.3 PWM 的频率PWM 音频播放频率要调整到音频的采样率 , 在使用 MCU 的时候一般的步骤如下确定/设置 定时器时钟来源确定/设置 有没有分频根据目标 PWM 载波频率推算自动重载值。对于本次的示例自动重载值变量为max_cnt// 根据采样率选择固定计数值if(rateCH5XX_AUDIO_SAMPLE_RATE_16K){max_cnt4875;}这个值是怎么算出来的16K 采样配套max_cnt 4875单次完整 PWM 周期计数总长 max_cnt 1 487616K 场景 PWM 载波频率F(pwm) 78000000 / 4876 ≈ 15996.72 Hz​8K 采样率是 16K 的一半所以 PWM 周期总计数必须翻倍总计数4876*2 9752max_cnt 9752-1 97518K 下载波频率F(pwm) 78000000 / 9752 ≈ 7998.36 Hz好了现在知道了在 16K 模式下面最大重装载值为 4875 也就可以解释为什么上面需要*p_data pcm_sample / 8;了示例中pcm_sample是一个int16_t的变量数值范围-32768 ~ 32767 。如果不缩放直接赋值大于 4875会溢出失效。除以 8最大输出 32767/8 ≈ 4095小于 4875留有安全余量这个值决定了占空比的大小示例中是为了把他设置到合理范围那么这里有一个比较重要的问题需要说明就是改变这个单个的占空比并不会改变声音内容它改变的是声音大小这里很多初学者会不太理解但是要记住 。❤️ 瞬时占空比数值只决定当前音量大小❤️❤️ 占空比随时间变化的节奏 / 快慢决定是什么声音、音调、语音内容。❤️1.4 音量调节上面我们已经说到了改变瞬时占空比就可以改变音量大小我们在示例基础上直接做了一个修改实际应用中就可以根据数值调整百分比直接上一下有关部分的代码#defineVOL_MAX256// 改变 audio_vol 的值改变声音大小 0 静音- 256 最大uint16_taudio_vol64;// 0静音256最大for(uint32_ti0;i(out_len1);i){int16_trawpcm_buf[i];// 音量缩放核心行int32_tvol_pcm(int32_t)raw*audio_vol/VOL_MAX;if(vol_pcm0){*p_datavol_pcm/8;*p_data20;}else{*p_data2(0-vol_pcm)/8;*p_data0;}p_data;p_data2;}二、 PCM 播放测试OK上面把一些需要了解的关键点理了一下我们本文还要测试一下 PCM 播放 实际应用中直接处理 PCM 的情况应该不会太多因为不压缩数据量实在太大了但是既然测试了那就记录一下而且可以方便的用来验证上面我们提到的原理部分的一些内容。2.1 PCM 数据说明我们知道 PCM 就是原始的音频数据我们嵌入式语音采样就是采样的原始的音频数据。 但是呢因为原始的音频数据很大一般应用中都不太可能本地存储下来除非是明确知道语音数据不大Flash 空间足够往往都是采集到了传输出去而且很多时候都会压缩传输。这是采集相关的话题后期我们应用可能需要分析说 。PCM 音频理论上来说的占用空间 16k1 秒32000 Byte ≈ 31.25 KB5 秒160000 Byte ≈ 156.25 KB10 秒320000 Byte ≈ 312.5 KB15 秒480000 Byte ≈ 468.75 KB8K 比 16K 小一半2.2 音频文件生成PCM 文件使用 ffmpeg 工具可以直接生成而且电脑端通过 ffplay 可以直接验证这个我们可以先准备好PC 端可以直接验证 。生成 PCM 指令:# 把mp3 转成 8K采样率 的 单声道 PCMffmpeg-i004.mp3-ar8000-ac1-fs16le 004p8k.pcm# 把 mp3 转成 16K采样率 的 单声道 PCMffmpeg-i004.mp3-ar16000-ac1-fs16le 004p16k.pcm# 把 mp3 的前 5S 转成 16K采样率 的 单声道 PCMffmpeg-t5-i004.mp3-ar16000-ac1-fs16le 004p16k5s.pcm查看 PCM 大小#windows 下dir004p8k.pcm示例如下C:\Users\OWNER\Desktop\Demo抽烟报警dir 004p8k.pcm 驱动器 C 中的卷是 系统 卷的序列号是 6E0E-5AEB C:\Users\OWNER\Desktop\Demo抽烟报警 的目录2026/06/2617:1381,502004p8k.pcm1个文件81,502字节0个目录50,758,230,016 可用字节 C:\Users\OWNER\Desktop\Demo抽烟报警dir 004p16k.pcm 驱动器 C 中的卷是 系统 卷的序列号是 6E0E-5AEB C:\Users\OWNER\Desktop\Demo抽烟报警 的目录2026/06/2617:13163,004004p16k.pcm1个文件163,004字节0个目录50,757,672,960 可用字节PCM 文件这里 dir 查出来的大小是真实的大小。使用如下指令可以在 PC 端直接播放 PCM 进行测试 #8K 单声道 s16le#-ch_layout mono单声道替代旧版 -ac 1ffplay-fs16le-ar8000-ch_layoutmono 004p8k.pcm#16K 单声道 s16leffplay-fs16le-ar16000-ch_layoutmono 004p16k.pcm最后把 PCM 转成 Hex 文件srec_cat.exe 004p8k.pcm-binary-offset32768-o004p8k_h.hex-intelsrec_cat.exe 004p16k.pcm-binary-offset32768-o004p16k_h.hex-intel或者 bin2hex.exe--offset32768004p8k.pcm 004p8k_h.hex2.3 APP修改测试现在存放的就是原始 PCM 数据解压代码也不需要从 Flash 读出来数据直接交给 PWM 输出就行了简单修改了一下直接上一下有关代码caseCH5XX_PWM_DMA_GET:{uint32_t*p_datap_event-p_data;uint32_t*p_data2p_event-p_data2;uint32_tlengthp_event-len;// 需要填充的采样点数switch(read_data_sta){case0:{// 计算还剩多少字节PCM数据uint32_tremain_bytessample.length-read_index;uint32_tneed_byteslength*2;// 每个采样16bit2字节if(remain_bytes0){read_data_sta1;break;}// 实际能读的字节数uint32_tcopy_bytes(remain_bytesneed_bytes)?remain_bytes:need_bytes;uint32_tcopy_samplescopy_bytes/2;// 直接从Flash读PCM数据// 关键修复Flash数据拷贝到RAM再读取// 使用__wrap_memcpy高速拷贝Flash数据到RAM缓存__wrap_memcpy(pcm_buf,(uint8_t*)(sample.start_addrread_index),copy_bytes);for(uint32_ti0;icopy_samples;i){int16_tpcm_samplepcm_buf[i];if(pcm_sample0){*p_datapcm_sample/8;*p_data20;}else{*p_data2(0-pcm_sample)/8;*p_data0;}p_data;p_data2;}read_indexcopy_bytes;// 如果数据不够补零静音for(uint32_ticopy_samples;ilength;i){*p_data0;*p_data20;}if(read_indexsample.length){PRINT(PCM play end\r\n);ch5xx_audio_stop();}}break;case1:ch5xx_audio_stop();break;default:break;}}break;因为音频效果文章不好放我就把我测试过程和结果描述一下。先测试 16K 播放就测试一个音频audio_record_tmusic_record[]{{0x000000,163004},// 大小按照原始大小};ch5xx_audio_init(CH5XX_AUDIO_SAMPLE_RATE_16K,audio_handler);可以正常播放只是声音感觉没有直接 SBC 解码好但是语音完全够用。再测试一下 8K audio_record_tmusic_record[]{{0x000000,163004},// 大小按照原始大小};ch5xx_audio_init(CH5XX_AUDIO_SAMPLE_RATE_8K,audio_handler);我开始以为会多给了后半段会破音但是测试下来后半段没有尖锐的声音 这个在代码中处理好了声音差了很多语音也能听清楚但是杂音比较明显。因为改到了 8K 我们的 PWM 最大重装载值翻倍了我们可以改成如下方式把声音放大if(pcm_sample0){*p_datapcm_sample/4;*p_data20;}else{*p_data2(0-pcm_sample)/4;*p_data0;}结语本文我们简单说明了一下双 PWM 音频播放的基本原理虽然并没有对着完全的源码分析但是对于我们应用需要修改到的地方都有提到不仅是在 CH585 上面所有的 MCU 使用此方式驱动音频对应部分都是类似的处理。之后我们还测试了 PCM 的播放总的来说作为语音播放来说 16K 完全可以满足需求8K 的声音就没有那么清晰会有杂音 (个人测试仅供参考与自己应用层代码处理也有关系) 。对于语音播放博主还计划要测试一下 ADPCM 解码播放说明毕竟 ADPCM 也是嵌入式常用编解码方式。好了本文就到这里。谢谢大家