ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

51单片机软解I2S驱动TDA1311:定时器中断模拟时序全解析

2026/9/16 4:15:20 拓冰建站 浏览量
51单片机软解I2S驱动TDA1311:定时器中断模拟时序全解析 简介面向51单片机音频外设开发的实战代码包围绕IIS总线驱动TDA1311音频DAC并在此基础上通过软件方式模拟I2S协议完成DAC升级。适合嵌入式工程师、电子设计爱好者以及对音频数据链路底层机制感兴趣的学习者也可作为单片机课程设计与毕业设计的参考资料。压缩包共1个文件为C语言源代码大小仅983字节短小精悍却涵盖接口初始化、TDA1311配置、I2S时序模拟与同步时钟/帧同步信号处理等核心逻辑。阅读该代码可清晰理解51单片机如何通过GPIO模拟IIS/I2S时序掌握TDA1311寄存器配置与数模转换参数调整方法同时体会在资源受限单片机上实现音频数据同步发送的优化思路。目前已有207人学习下载尤其适合需要参考最小实现来快速验证音频DAC驱动方案、或希望深入学习单片机音频接口编程的读者可直接将代码移植到自己的工程中调试扩展有效缩短开发周期。1. 51单片机驱动TDA1311为什么软解I2S还有存在的必要51单片机配合TDA1311这颗老牌DAC做数字音频输出是IIS软件模拟里性价比最高的玩法之一。TDA1311虽然便宜但它的输入不像PCM51xx那样随便一接就能出声而是严格走BCK、WS、DATA三根线。没有硬件I2S外设的51只能靠定时器中断在普通IO上精确翻转电平来模拟I2S时序。这听起来是拿软件硬扛但把采样率压到16kHz甚至32kHz时完全可行而且能复用到任何引脚足够的51上不必换STM32。适合两类人一是手头有多余51开发板、想低成本验证DAC时序的硬件爱好者二是想彻底搞懂位时钟、帧时钟和声道选择之间关系而不是对着芯片手册空对空的嵌入式开发者。解开这份IIS.zip里面的IIS.c正好用C语言把这三根线在GPIO上重现。2. IIS与I2S协议差异及TDA1311的接口时序2.1 IIS与I2S同一总线协议的两种拼写在Philips早期的音频DAC手册里这套总线写作IISInter-IC Sound后来半导体部门并入NXP多数资料和Linux驱动统一改称I2SInter-IC Sound或Integrated Interchip Sound但寄存器命名里经常还留着IIS字样。所以标题里“IIS.zip”和“模拟i2s”说的是同一件事用BCK位时钟驱动按WS声道切换逐位把数字音频送到DAC。标准I2S格式的核心约束有三条数据在BCK下降沿更新接收端在BCK上升沿采样WS在BCK下降沿变化低电平对应左声道、高电平对应右声道一帧里每个声道16bit时WS的高低电平各占16个BCK周期。最容易做错的是第三条I2S的MSB并不是和WS切换同时出现而是比WS变化晚一个BCK周期。很多软件模拟的I2S信号频率和占空比都对就是不出声原因往往就是这里差了一拍。另外要注意I2S并不像UART那样能从波形里自动解析字长16bit还是24bit完全靠发送和接收双方预先约定。TDA1311按固定位数解串所以发送端一旦中途改变数据位宽DAC不会报错只会把声道错位后的噪声直接送到输出级。2.2 TDA1311作为I2S从设备的接口约束TDA1311内部包含解串、插值滤波、噪声整形和开关电容DAC输出级对外只需要三根信号线并不像后来的DAC那样要写配置寄存器。先把接口约束理清再写代码才不会白调。信号方向电平关键约束BCK输入TTL兼容频率不宜超出手册最大值偏离fs整数倍会导致噪声整形频率点偏移WS输入TTL兼容高低电平各持续16个BCK要求与BCK严格同步DATA输入TTL兼容在BCK上升沿被采样发送端应在BCK下降沿更新MSB比WS变化晚一个BCK实际接线时BCK、WS、DATA分别接51的任意三个IO。51的准双向口高电平驱动能力偏弱但TDA1311是TTL电平输入只要信号线控制在10cm以内一般不会出现误采样。我习惯在三条线上各串一个100Ω电阻并在TDA1311的VDD引脚旁边并联0.1uF和10uF退耦电容IO翻转产生的开关噪声就不容易串进DAC模拟供电。手头有74HC04或74HC541的话在51和DAC之间加一级缓冲整形更稳妥尤其当你准备把信号线拉长到机内飞线的时候。注意三条信号线的返回电流必须走独立的数字地再与TDA1311模拟地单点连接否则BCK翻转时会在DAC输出地线上感应出尖峰听感就是沙沙的底噪。2.3 从CD机升级场景理解这套时序“升级TDA1311”在老DIY圈子里通常指把原CD机DSP输出的I2S信号截断改成由独立MCU或外部数字源直接驱动DAC绕开原机DSP和输出级。这样做的收益是数字通路变短时钟抖动来源减少代价是原DSP承担的音量控制、去加重、静音等功能一并失效需要由播放链路另外处理。拿到IIS.zip这种工程做复刻时先用逻辑分析仪或示波器看一遍正常播放时的I2S波形确认当前测试源的BCK频率、WS极性和数据格式再对照TDA1311手册确认自己模拟的时序极性。很多“升级后无声”其实是WS极性反了或者DATA延迟了一个BCK而不是DAC被烧掉。到了这一步代码层面的模拟才有对比基准。3. 51单片机软件模拟I2S定时器初值、位序与左右声道切换3.1 为什么不用SPI或PCA模块硬改增强型51普遍带SPI和PCA/PWM但这两种模块都很难直接复现I2S。SPI有SCLK和片选可以按连续字节输出但SPI没有独立的帧同步概念用NSS当WS只能做成左对齐格式而且SPI按字节发送16bit数据要拆成两次传输片选翻转间隙会造成BCK不连续。PCA更不行它只是捕捉/比较单元能产生PWM但输出不了带数据位的串行流。与其在模块上绕来绕去不如回到定时器GPIO这条通用路径51上任何一个引脚都能映射换芯片时也只改三个sbit定义。另外想用延时函数做位翻转的误区也要避开把BCK拉低、延时、输出一位、拉高、延时在8kHz采样率下勉强能跑通但主循环一旦被按键、串口等打断BCK的相位就乱了DAC会瞬间失锁并爆出刺耳噪声。定时器中断虽然也会被打断但中断响应时间是确定的BCK周期稳定音质和可靠性都高一档。3.2 定时器初值计算从目标采样率反推BCKI2S标准帧按每个声道16bit计算一个采样周期里总共有32个bit所以BCK频率和采样率的关系是BCK fs × 32目标fs16kHz时BCK512kHzfs32kHz时BCK1.024MHz。对于12T模式的传统51、晶振11.0592MHz机器周期约1.085us而512kHz的位周期只有1.953us一个半位周期不到1us中断处理完全来不及。所以这个软件方案的实际运行平台是1T增强型51比如STC15系列外接22.1184MHz晶振时一个机器周期约45.2ns半位周期约0.976us对应约21个机器周期代码优化后还能塞进一次中断处理。定时器初值的计算可以直接写成宏#define FOSC 22118400UL // 主频22.1184MHz1T模式 #define FS 16000UL // 目标采样率16kHz #define BCK (FS * 32UL) // 位时钟512kHz #define HALF_BIT_CNT (FOSC / BCK / 2) // 半位周期计数约21.6逻辑说明FOSC/BCK得到每个位周期对应的机器周期数再除以2就是半位周期翻转一次定时器需要的计数个数。这里算出来约21.6实际定时器重装值只能取整数把小数丢掉后BCK会有约3%的周期抖动听感上会表现为底噪轻微升高。要让抖动最小应当选用能让FOSC/BCK除尽的组合例如22.1184MHz下fs8kHz时BCK256kHz分频系数正好是整数43波形最干净。如果坚持fs16kHz就用两个不同的定时初值交替重装例如一个周期装21、下一个周期装22平均下来频率更准代价是代码里多一个累加余数的分支。下表是同一颗1T芯片在22.1184MHz下的实际余量目标fsBCK频率半位周期机器周期数实际可行性8kHz256kHz43宽松还能做缓冲搬运16kHz512kHz21偏紧中断耗时接近极限32kHz1.024MHz10基本不可行中断处理不完44.1kHz1.4112MHz7不可行波形直接畸变这里的“机器周期数”是按最精简中断程序估算的实际会多几条压栈出栈指令所以fs16kHz已经是这套方案的实用上限。3.3 位输出与WS翻转的中断实现在1T增强型51上用定时器0方式1做半位周期翻转每次中断完成BCK翻转、数据移出、声道切换三件事。核心代码结构如下// IIS.c 核心片段软件I2S位输出 // 硬件连接STC15系列 // P1.0 - TDA1311 BCK // P1.1 - TDA1311 WS // P1.2 - TDA1311 DATA sbit BCK_PIN P1^0; sbit WS_PIN P1^1; sbit DATA_PIN P1^2; unsigned int i2s_buf_l; // 左声道16bit样本 unsigned int i2s_buf_r; // 右声道16bit样本 unsigned char bit_cnt; // 当前位计数15..0 bit cur_ws; // 当前声道0左1右 void timer0_isr(void) interrupt 1 { BCK_PIN !BCK_PIN; // 先翻转位时钟 if (BCK_PIN 0) // BCK下降沿更新数据与声道 { if (bit_cnt 0) { // 一帧开始切换WS输出MSB WS_PIN cur_ws; if (cur_ws 0) DATA_PIN (i2s_buf_l 15) 0x01; else DATA_PIN (i2s_buf_r 15) 0x01; bit_cnt 15; cur_ws !cur_ws; // 16bit结束后切换声道 } else { // 继续输出低位 bit_cnt--; if (cur_ws 0) DATA_PIN (i2s_buf_l bit_cnt) 0x01; else DATA_PIN (i2s_buf_r bit_cnt) 0x01; } } // 定时器重装保持半个BCK周期一次中断 TH0 (unsigned char)(HALF_BIT_CNT 8); TL0 (unsigned char)(HALF_BIT_CNT 0xFF); }逻辑说明标准I2S里接收端在BCK上升沿采样所以发送端必须在BCK下降沿更新数据。代码里先翻转BCK再判断当前是下降沿还是上升沿只有在下降沿才改DATA和WS保证数据在下一个上升沿到来前已经稳定。bit_cnt从15倒计数到0逐位移出16bit数据。WS在每16位结束后翻转恰好形成左右声道各16bit的一帧。标准I2S要求MSB比WS变化晚一个BCK。这里WS在下降沿先改变MSB要等下一个BCK下降沿、也就是bit_cnt0的分支再次触发时才送出天然满足“晚一拍”的时序要求。如果你把TDA1311的SCL脚配成左对齐格式就需要把MSB输出和WS切换放在同一个下降沿内代码里把两段合并即可。参数说明(i2s_buf_l 15)在Keil C51下会生成循环移位和判断指令大约18个机器周期加上中断进出约8个周期总耗时已经逼近半位周期的21个周期。因此不建议在中断里做音量调节或滤波计算这些一律放到主循环。TH0、TL0直接使用宏计算出的整数。如果你在代码里试验除不尽的组合把HALF_BIT_CNT替换为两个交替常量但必须保证两个常量对应的时间合成后仍是完整的BCK周期。主函数只需要初始化定时器并不断更新两个声道缓冲void i2s_init(void) { TMOD 0xF0; // 定时器0方式1 TMOD | 0x01; TH0 (unsigned char)(HALF_BIT_CNT 8); TL0 (unsigned char)(HALF_BIT_CNT 0xFF); ET0 1; // 允许定时器0中断 EA 1; // 打开总中断 TR0 1; // 启动定时器 } void main(void) { i2s_buf_l 0x8000; // 静音中心点附近的测试值 i2s_buf_r 0x8000; i2s_init(); while (1) { // 从WAV数组或串口取得新的左右声道样本 // 写缓冲前先关闭总中断防止中断读到半个新数据 EA 0; i2s_buf_l next_sample_l; i2s_buf_r next_sample_r; EA 1; } }逻辑说明先禁用总中断再更新双声道缓冲是为了避免中断在赋值中途进入把旧高字节和新低字节拼成一次输出产生明显的“咔哒”爆音。但EA关掉的时间要尽量短超过半位周期会导致BCK瞬时停振DAC失锁后恢复需要几十毫秒。更好的做法是4.2节的双缓冲方案。参数说明0x8000对应16bit中间零点TDA1311输出的是以电源电压一半为参考的模拟信号这个值的含义在4.3节还会再讨论。4. TDA1311驱动参数与音频数据流设计4.1 上电顺序与SNS、SCL引脚的配置TDA1311没有I2C寄存器所谓驱动初始化主要是确认外围引脚状态并等待DAC内部电路建立。常见手册里SNS用于设置内部噪声整形器的工作采样率档位SCL用于选择输入数据格式是标准I2S还是左对齐MSB-justified。这两个引脚一般接VDD或GND固定电平代码里不需要动态改但在PCB上必须按目标采样率范围接好否则DAC会进入错误的整形模式输出底噪明显增大。上电顺序也常被忽视。我给TDA1311供电后会至少等50ms再启动51的定时器输出BCK。原因是噪声整形器和开关电容网络的偏置建立需要时间送电后立刻进数据流DAC可能把初始毛刺锁进解码状态机表现为播放前几百毫秒有短暂“嗯”的冲击声。用这段延时把静音态稳定下来听感会干净很多。// TDA1311驱动初始化IO默认拉低延时等待内部建立 void tda1311_init(void) { BCK_PIN 0; WS_PIN 0; DATA_PIN 0; delay_ms(50); // 等待DAC内部偏置建立 }逻辑说明这段代码本身不包含任何寄存器写入它的目的是让三根输入线在DAC供电期间保持确定的低电平避免DAC在偏置未稳定时收到随机抖动而误锁状态。delay_ms的时长取决于电源和退耦电容大小实际做到50ms以上即可。参数说明如果把delay_ms里塞进真实数据发送函数相当于在DAC建立期间就启动I2STDA1311虽然不会损坏但首帧可能被丢弃或错位。较稳妥的做法是让三根线低电平保持到延时结束再进入数据流。4.2 双缓冲更新策略避免数据撕裂与爆音前面的主循环版本在每次更新前关总中断只能算临时方案。更工程化的做法是双缓冲中断始终从当前活动缓冲读样本主循环往非活动缓冲写新样本写完后再做一次乒乓切换。音频数据本身是连续流双缓冲能让主循环有整整一帧、也就是约62.5us的时间去准备下一个样本对16kHz采样率来说足够做查表和串口收发。#define BUF_NUM 2 unsigned int dac_l[BUF_NUM]; unsigned int dac_r[BUF_NUM]; unsigned char buf_active; // 中断当前读取的缓冲 bit buf_updated; // 主循环通知中断可切换 // 在3.3节中断的声道切换位置追加下列逻辑 // if (bit_cnt 0 cur_ws 1) { // // 一帧左右各16bit发送完毕 // if (buf_updated) { // buf_active ^ 1; // buf_updated 0; // } // } void main(void) { buf_active 0; buf_updated 0; i2s_init(); while (1) { // 生成或读取下一帧音频样本 unsigned int next_l read_wav_sample_l(); unsigned int next_r read_wav_sample_r(); // 写入非活动缓冲然后置标志允许切换 dac_l[buf_active ^ 1] next_l; dac_r[buf_active ^ 1] next_r; buf_updated 1; } }逻辑说明中断里每完成一帧cur_ws回到左声道就检查buf_updated标志一旦置位就切换buf_active。切换发生在帧边界而不是随机时刻所以DAC读到的永远是完整的一帧数据不会出现高低字节被拆开的情况。主循环写入的是buf_active ^ 1即当前中断不读的那一侧因此不需要关总中断BCK也不会因为EA0而抖动。参数说明双缓冲适用于数据源连续的场景。如果数据源本身是突发性的比如串口一次收到512字节那缓冲深度应该改成4个以上并在主循环里做水位判断否则读空缓冲后中断会反复输出最后一个样本听感变成固定频率的嗡嗡声。4.3 有符号PCM转偏移码TDA1311的零点在哪16bit音频文件多数以有符号数存储范围是-32768到32767零值表示静音。但TDA1311这类单电源DAC内部以VDD/2为基准输入码字落在0x0000到0xFFFF之间0x8000对应模拟输出零点。直接把有符号样本赋给unsigned int正负号会错位听感变成音量极小且带直流偏置的破音。必须先把有符号数加0x8000偏置得到无符号码。int sample_in; // 来自WAV或解码库的有符号样本 unsigned int code_to_dac; if (sample_in 0) code_to_dac (unsigned int)sample_in 0x8000u; else code_to_dac 0x8000u - (unsigned int)(-sample_in);逻辑说明正数部分从0x8000向上延伸负数部分从0x8000向下递减保证0输入映射到中点0x8000正满幅映射到0xFFFF负满幅映射到0x0000。有些代码习惯写成(unsigned int)(sample_in 0x8000)这在Keil C51里对int加法先溢出再截断实测多数情况下结果一致但可读性和可移植性都差还是用分支写法。参数说明如果DAC后级有运放做电平抬升也可以不转换、直接按补码发送但此时零点偏移不在VDD/2处输出会削波或直流偏移建议统一按0x8000方案处理。音量调节放在这一步之后做例如code_to_dac ((code_to_dac - 0x8000) * vol 8) 0x8000把乘法放在偏移域而不是线性域避免小音量下出现可感知的量化台阶。5. 采样率边界与I2S波形验证方法5.1 到底能跑多高实测中断占用3.2节的表是按理论机器周期估算的实际验证方法很简单在定时器中断入口翻转一个测试引脚P1.7主循环空转用示波器看P1.7高电平宽度占总周期的比例这个比例就是中断程序占用的CPU时间。用STC15实测fs16kHz时约为90%fs8kHz时约45%。比例超过70%后主循环任何非预期耗时都可能拖过半个位周期导致BCK出现毛刺所以实用上应该把fs控制在16kHz以下或者换主频更高的1T芯片再往上推。BCK只要一断TDA1311内部的解串状态机就会失锁恢复后第一个有效帧之前会有短暂噪声。这个现象和普通音频放大器的“咔哒”不同表现为持续几十毫秒的白噪声尾巴排查时要先确认BCK全程连续再看数据内容。5.2 逻辑分析仪验收三件事模拟I2S的波形验收只需要抓三件事WS高、低电平各持续多少个BCK。16bit标准帧必须恰好各16个BCK多于或少于都会造成声道错位。DATA的最高位是否比WS边沿晚一个BCK。抓一个完整帧展开数BCK边沿即可确认。BCK是否连续。把触发沿设为BCK下降沿观察是否有超过一个位周期的空隙。逻辑分析仪采样率建议设到10MHz以上。软件I2S的512kHz位时钟只有2MHz的边沿密度5MHz采样也能看个大概但要数“晚一个BCK”这种细节采样率不足容易误判。触发用BCK下降沿配合DATA信号一探头就能同时看到WS和DATA相对位时钟的位置。5.3 听感现象与硬件原因对照如果波形验收全过但听感不对按下面的顺序排现象先查时序再查硬件无声WS极性是否反相SNS/SCL电平、VDD退耦左右声道互换WS极性反了无底噪高BCK频率偏差、抖动信号线过长、电源纹波有爆音主循环关中断时间过长触发电平不稳调试TDA1311时不要在输出端直接接高灵敏度耳机先接一个100Ω电阻负载看示波器波形。DAC输出脚带载能力有限直接驱动耳机可能造成波形塌陷掩盖时序问题让你误以为是数据没配对。先用固定频率方波验证DAC工作正常再上真实音乐数据整个系统就很少出幺蛾子。本文还有配套的精品资源点击获取