用51单片机模拟I2S时序驱动TDA1311:从原理到代码实践
2026/9/16 4:10:52 网站建设 项目流程

简介:面向51单片机开发者的音频接口实现资源,以驱动TDA1311 DAC为核心,演示如何通过IIS接口传输数字音频数据,并借助软件模拟I2S协议完成芯片的升级配置,适合嵌入式入门、低成本音频播放及音响设备开发等场景参考。压缩包内仅1个C语言源文件,大小不足1KB,代码集中展示了IIS接口初始化、TDA1311寄存器配置、模拟I2S时序控制等关键函数,可帮助读者快速理解底层硬件操作。目前已有207人学习,资源虽小但直击51平台音频输出的常见难点,尤其对需要实现音频DAC驱动或兼容I2S设备的开发者有较大参考价值。通过阅读这份源码,可以学到基于51单片机的高精度时钟同步与帧同步处理思路,包括位时钟与左右声道时钟的协调方法,以及中断或延时方式下的时序模拟技巧,为后续扩展更复杂的音频系统打下基础。

1. 用 51 单片机模拟 I2S,把 TDA1311 重新盘活

标题里的 IIS 就是 I2S(Inter-IC Sound)总线的另一种写法,TDA1311 则是飞利浦那代 CD 随身听解码板上非常常见的一颗 1bit 立体声 DAC。很多人手里还留着用 TDA1311 的旧解码板,想加蓝牙、加单片机音源,结果发现手边主控没有硬件 I2S 外设,于是才有了标题里这条路径:用 51 单片机模拟 I2S 时序,把音频数据喂给 TDA1311。这件事第一眼看上去不靠谱——51 的机器周期是微秒级,而 44.1kHz 采样率下 BCK 位时钟要跑到 2.8MHz。但把采样率降到 8kHz,BCK 只需要 256kHz,51 定时器完全顶得住。这篇文章把这条链路拆开讲:I2S 时序怎么对、51 怎么模拟、TDA1311 怎么接,最后再讲怎么把方案升级成硬件 I2S。

2. I2S 与 TDA1311 的时序约定:先把三根线的相位关系定死

要模拟 I2S,先得把 I2S 本身说清楚。很多人在这一步就翻车,不是因为不理解“有 BCLK、LRCK、DATA 三根线”,而是没注意到这三根线的相位关系。TDA1311 是标准 I2S 输入,对这一点尤其敏感。

2.1 一帧数据里 BCK、WS、DATA 到底怎么对齐

I2S 总线在三根线上传输立体声音频数据:BCK(位时钟,也叫 BCLK 或 SCK)、WS(声道选择,也叫 LRCK)、DATA(串行数据)。飞利浦标准 I2S 的约定是:WS 为低电平传输左声道,高电平传输右声道;WS 的跳变发生在 BCK 的下降沿之后;数据在 BCK 的下降沿之后更新,在 BCK 的上升沿被接收端采样;每声道数据从 MSB(最高位)开始发送。

这里最容易写错的点是数据的 MSB 位置。标准 I2S 里,MSB 并不是紧挨着 WS 跳变沿出现,而是在 WS 跳变后的第二个 BCK 上升沿被采样,也就是 WS 跳变后先空出一个 BCK 周期。TDA1311 的数据手册里要求的就是这种飞利浦标准时序,它内部按“MSB first、二进制补码、16bit”来解释数据。如果送的是 8bit 或 24bit 数据,位数不对齐,DAC 会按 16bit 截断或移位,出来的声音会变成明显的噪声。

WS: __|‾‾‾‾‾‾‾‾|____|‾‾‾‾‾‾‾‾|____ 左声道低、右声道高 BCK: _‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ 每个BCK上升沿采样一个bit DATA:x<----MSB-------LSB----><----MSB-->

上面的示意里,DATA 的第一个有效位比 WS 跳变晚一个 BCK,这就是“飞利浦对齐”和“左对齐(Left Justified)”的本质区别。很多主控的 SPI 外设可以输出左对齐格式,但 TDA1311 只认 I2S,所以模拟时必须把 WS 晚半拍这个相位关系做对。51 里没有硬件 I2S 外设,WS 的切换时机完全靠代码控制,反而比用 SPI 硬靠更保险。

2.2 为什么定时器拼出来的 BCK 也能被 TDA1311 接受

软件模拟 I2S 的本质,是用定时器中断把 GPIO 的翻转时刻精确复现出来。TDA1311 这类老 DAC 对时钟的要求集中在两点:一是 BCK 和 WS 之间保持整数倍关系,二是 DATA 在 BCK 上升沿来到之前必须稳定。它并不要求 BCK 的占空比严格 50%,也不要求 BCK 上升沿和下降沿绝对对称。这意味着,用 51 定时器产生一个周期固定、但高电平宽度只有几条指令的 BCK 脉冲,对 TDA1311 来说是完全可接受的。

占空比不用纠结,但抖动必须控制。如果每次中断里执行的指令数不固定,BCK 的周期就会忽长忽短。TDA1311 内部虽然有时钟恢复电路,它锁的是 BCK 的平均频率,无法跟踪逐周期的抖动。抖动最终会体现在模拟输出上,听感就是底噪升高、声音发毛。所以模拟 I2S 的中断服务函数里不要写任何分支耗时差异大的代码,所有变量更新都走固定路径,这是后面代码设计的核心约束。

2.3 51 的输出电平与 TDA1311 接口电平的匹配

TDA1311 是 5V 逻辑器件,数字输入高电平最低阈值大概是 2.0V 到 2.4V 之间。51 单片机用 5V 供电时,准双向口输出高电平接近 VCC,可以直接连接,不需要电平转换。但这里有一个前提:51 的 P0 口是开漏结构,必须外接上拉电阻才能输出高电平;P1、P2、P3 口内部有上拉,可以直接推 TDA1311 的输入。

如果升级方案里把主控换成了 3.3V 的 STM32 或 ESP32,情况就不一样了。3.3V 输出高电平接近 3.3V,虽然高于 TDA1311 的 Vil 阈值,但余量不大,而且现代 DAC 芯片的输入耐压往往只有 3.6V,反过来接 5V 的 TDA1311 输出会烧引脚。所以标题里“51 模拟 I2S”这个场景,最省事的组合就是 5V 供电的 51 直连 5V 供电的 TDA1311,三根信号线加一个共地就完事。

3. 用 51 定时器模拟 I2S 驱动 TDA1311:最小可出声的代码

理论部分说完,接下来是能跑起来的最小系统。硬件上只需要三个部分:一块 51 开发板(89C52 或 STC 系列均可)、一个 TDA1311 芯片及外围电路、三根杜邦线。软件上用 Keil C51 编写,定时器 0 工作在方式 1(16 位定时),中断里完成所有 I2S 时序生成。

3.1 引脚分配与最小系统接线

我一般把 BCK、WS、DATA 分别安排在 P1.0、P1.1、P1.2,原因是 P1 口是准双向口,复位后默认高电平,不需要额外配置。接线表如下:

51 引脚信号接 TDA1311 引脚说明
P1.0BCKBCK/SCK 输入位时钟,256kHz 方波
P1.1WSWS/LRCK 输入声道选择,8kHz 方波
P1.2DATADATA 输入串行音频数据
GNDVSSD/VSSA必须共地
VCC 5V电源VDDA/VDDD注意 TDA1311 模拟和数字电源分开走线

上电前先把 BCK 输出拉低,这个细节影响上电瞬间 TDA1311 是否误判帧起始。TDA1311 的数据输入在 BCK 停止时最好保持低电平,否则内部移位寄存器可能停在随机状态,上电后会输出一小段噗声。

3.2 定时器中断里完成位搬运

核心代码全部放在定时器 0 的中断服务函数里。每次中断产生一个 BCK 周期:先更新 DATA,再拉高 BCK(上升沿采样),再拉低 BCK(下降沿后允许数据变化)。这是标准 I2S 的时序要求,数据必须在 BCK 下降沿之后、上升沿之前保持稳定。

/* C51 环境,89C52 @ 12MHz,定时器0方式1,8kHz采样率 */ #include <reg52.h> sbit BCK = P1^0; /* 位时钟输出 */ sbit WS = P1^1; /* 声道选择输出 */ sbit DAT = P1^2; /* 串行数据输出 */ #define BITS_PER_FRAME 32 /* 左右声道各16bit */ unsigned int dac_l, dac_r; /* 当前正在输出的左右声道样本 */ bit cur_channel; /* 0=左声道,1=右声道 */ unsigned char bit_cnt; /* 当前帧内已发送位数 */ void timer0_isr(void) interrupt 1 { TH0 = 0xFF; /* 定时初值,对应 8kHz 采样率,见 3.3 节 */ TL0 = 0xFC; /* 1. 先更新 DATA,保证 BCK 上升沿采样时数据稳定 */ if (cur_channel == 0) { DAT = (dac_l & 0x8000) ? 1 : 0; dac_l <<= 1; } else { DAT = (dac_r & 0x8000) ? 1 : 0; dac_r <<= 1; } /* 2. 产生 BCK 上升沿,TDA1311 在此时刻采样 DAT */ BCK = 1; BCK = 0; /* 下降沿,数据允许变化 */ /* 3. 帧计数与 WS 切换 */ if (++bit_cnt >= BITS_PER_FRAME) { bit_cnt = 0; cur_channel = !cur_channel; WS = cur_channel; /* WS 在 BCK 低电平期间切换 */ } } void main(void) { TMOD = 0x01; /* 定时器0,方式1,16位 */ TH0 = 0xFF; TL0 = 0xFC; ET0 = 1; /* 使能定时器0中断 */ EA = 1; /* 使能总中断 */ TR0 = 1; /* 启动定时器 */ BCK = 0; /* 复位后总线默认低电平 */ WS = 0; DAT = 0; dac_l = 0x4000; /* 初始样本,约 50% 幅度的直流偏置 */ dac_r = 0x4000; while (1) { /* 主循环可以更新 dac_l/dac_r,比如从 WAV 缓冲取数 */ } }

这段代码里,dac_ldac_r是当前帧正在发送的左右声道样本。每次中断发送一个 bit,发送完成后左移,下一次中断取出新的最高位。bit_cnt计数满 32 表示一帧 16bit×2 声道发送完毕,此时切换cur_channel和 WS。WS的切换发生在 BCK 低电平期间,符合 2.1 节提到的飞利浦标准相位关系。

需要注意两点。第一,BCK = 1; BCK = 0;这两条语句产生的高电平宽度只有一条指令周期,在 12MHz 晶振下也就是 1us 左右,TDA1311 的输入施密特触发器可以识别,但如果你把主频降到 6MHz 以下,或者换了更慢的芯片,建议在拉高后加两个_nop_()延长高电平时间。第二,中断服务函数里避免调用任何函数、避免switch这类多条分支,所有指令路径固定,才能把 BCK 抖动压到最低。

3.3 采样率、BCK 与定时器初值怎么配对

TDA1311 按 16bit、双声道理解一帧数据,所以 BCK 频率等于采样率乘以 32。51 单片机的定时器初值由晶振频率、分频系数和 BCK 周期共同决定:机器周期等于晶振周期的 12 倍,定时器每计一个数经过一个机器周期。初值计算公式是:

/* FOSC 单位 Hz,BCK 单位 Hz,机器周期 = 12 / FOSC */ unsigned int reload = 65536 - (FOSC / 12 / BCK);

以 12MHz 晶振为例,机器周期 1us,8kHz 采样率时 BCK = 256kHz,BCK 周期约 3.9us,定时器计数 3.9 次,取整 4 次,所以初值是 65536 - 4 = 0xFFFC。下面是常用参数对照表:

采样率 fsBCK = 32×fsBCK 周期定时器计数(12MHz/12T)初值
8kHz256kHz3.9us40xFFFC
16kHz512kHz1.95us20xFFFE
32kHz1.024MHz约1us10xFFFF

实际运行时还要算中断服务函数的指令开销。上面这段代码从进入中断到退出大约执行 25 到 40 条指令,在 12MHz 下就是 25us 到 40us,远大于 BCK 周期。这意味着表格里的 16kHz 和 32kHz 参数根本无法实现,中断还没执行完,下一次中断请求已经到了,BCK 的实际频率会被中断处理时间拉低。8kHz 采样率下中断间隔 3.9us,指令开销 25us,同样有问题。

所以真正可靠的方案是降低晶振频率或者提高主频。把 89C52 的晶振从 12MHz 换到 6MHz,机器周期变 2us,同样的指令开销还是 25us 以上,问题依旧。STC 系列 1T 单片机是个更好的选择,1T 模式下机器周期等于晶振周期,用 11.0592MHz 晶振时,一条普通的赋值语句只要 1 到 2 个时钟周期,整个中断服务函数可以压到 10us 以内,这样 8kHz 采样率就能稳定跑。如果你手里只有普通的 12T 51 芯片,建议把采样率进一步降到 4kHz,BCK 降到 128kHz,中断间隔约 7.8us,勉强够用,但音质已经没有太多实用价值。这恰恰说明软件模拟 I2S 的边界在算力而不在时序逻辑,也为下一章升级硬件 I2S 埋下伏笔。

4. 升级 TDA1311 的两条路:换解码芯片与换 I2S 主控

标题里的“升级 TDA1311”有两条技术路径。一条是保留 51 软件模拟 I2S,把 TDA1311 这颗老芯片替换成现代 DAC 芯片;另一条是保留 TDA1311,把音源主控从 51 换成带硬件 I2S 外设的 STM32 或 ESP32。两条路不冲突,实际做数播升级时经常先后都走一遍。

4.1 把 TDA1311 换成现代 I2S DAC,接线和配置差在哪

TDA1311 最大的历史局限是只接受 16bit 数据和标准 I2S 对齐,且供电要 5V。现代 DAC 芯片如 PCM5102A、ES9023、CS4344 普遍支持 16/24/32bit 数据格式,供电降到了 3.3V,输出从电流型变成了电压型,部分芯片还内置了模拟低通滤波,外围元件比 TDA1311 少得多。两者对比如下:

项目TDA1311PCM5102A 等现代 DAC
数据格式标准 I2S,16bitI2S / 左对齐 / DSP,16/24/32bit
系统时钟 MCLK不强制,内部恢复通常需要 256fs 或 512fs
数据输出电流型,需外部 I/V 转换电压型,内置输出级
供电电压5V3.3V
滤波电路需外接运放和阻容网络部分芯片内置

升级时最大的坑是 MCLK。TDA1311 没有 MCLK 输入引脚,它的内部时钟从 BCK 通过锁相环恢复;PCM5102A 这类芯片则普遍要求主控额外提供 MCLK 信号,频率通常是采样率的 256 倍或 512 倍。如果 51 软件模拟已经让 BCK 跑到极限,再让软件模拟输出 MCLK 几乎不可能。所以换现代 DAC 的同时,至少要把主控换成带硬件 I2S 外设的 MCU,一次性解决 MCLK 和时序精度两个问题。

4.2 STM32 硬件 I2S 接管 51 的模拟职责

STM32F103 的 SPI2 和 SPI3 可以复用为 I2S2 和 I2S3,支持飞利浦标准 I2S 时序,BCK 和 WS 由硬件自动生成。下面的初始化代码用 STM32 标准外设库,配置成 8kHz 采样率、16bit 数据、飞利浦对齐,直接对接 TDA1311。

/* STM32F103RCT6,SPI2/I2S2 主模式,16bit,飞利浦 I2S 时序 */ #include "stm32f10x_spi.h" #include "stm32f10x_gpio.h" void i2s_dac_init(void) { GPIO_InitTypeDef gpio; I2S_InitTypeDef i2s; /* 使能 GPIOB 和 SPI2 时钟,PB12=WS、PB13=CK、PB15=SD */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); gpio.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_15; gpio.GPIO_Mode = GPIO_Mode_AF_PP; gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &gpio); SPI_I2S_DeInit(SPI2); i2s.I2S_Mode = I2S_Mode_MasterTx; /* 主发送模式 */ i2s.I2S_Standard = I2S_Standard_Phillips; /* 飞利浦标准 I2S */ i2s.I2S_DataFormat = I2S_DataFormat_16b; /* 16bit 数据 */ i2s.I2S_MCLKOutput = I2S_MCLKOutput_Disable; /* TDA1311 不需要 MCLK */ i2s.I2S_AudioFreq = I2S_AudioFreq_8k; /* 采样率 8kHz */ i2s.I2S_CPOL = I2S_CPOL_Low; /* BCK 空闲低电平 */ SPI_I2S_Init(SPI2, &i2s); SPI_I2S_Cmd(SPI2, ENABLE); }

参数里有三个地方要和硬件对应。I2S_MCLKOutput_Disable是针对 TDA1311 的,TDA1311 没有 MCLK 引脚,打开 MCLK 输出只会浪费一个引脚;如果换用 PCM5102A,要把这个参数改成I2S_MCLKOutput_Enable,并把I2S_AudioFreq对应的倍率关系算好。I2S_DataFormat_16b对应 TDA1311 的 16bit 输入,如果往 PCM5102A 送 24bit 数据,这里改成I2S_DataFormat_24b,同时要保证发送缓冲区内按 32bit 对齐。I2S_CPOL_Low表示 BCK 空闲时为低电平,这是大多数 DAC 的默认设置,但如果换的芯片手册里要求 BCK 空闲高电平,必须同步调整。

STM32 硬件 I2S 接管之后,51 模拟 I2S 的代码可以整体删掉,51 退回到控制角色。比如让 51 负责读取按键、处理音量、解析红外遥控,把音量计算结果通过串口或 I2C 发给 STM32,STM32 再通过 DMA 从 WAV 缓冲区取数据灌进 I2S 外设。这种分工比让 51 单独扛音频数据流更合理。

4.3 51 模拟 I2S 在升级链路里的真正位置

升级完成后的链路里,51 模拟 I2S 不是被彻底否定,而是变成了一个备用的调试接口。硬件 I2S 跑通之后,如果 DAC 不出声,我常用的排查办法是把 STM32 的 I2S 输出断开,临时把板子上原来的 51 模拟 I2S 程序烧回去,如果 TDA1311 能出声,说明 DAC 芯片和模拟输出电路正常,问题只出在 STM32 的 I2S 配置上;如果 51 模拟的也不出声,问题大概率在 DAC 的供电或输出级。这种“低算力方案和高算力方案互相验证”的思路,比拿示波器盲测更直接。

5. 模拟 I2S 验证与抖动排查:示波器上看三个点就够了

最后落到验证环节。软件模拟 I2S 和硬件 I2S 的排查重心完全不同,硬件 I2S 一般查配置,软件模拟 I2S 查的是时序和信号完整性。示波器上重点看 BCK、WS、电源三个点,顺序不要颠倒。

5.1 BCK 抖动是模拟 I2S 最大的声音杀手

把示波器探头夹在 BCK 引脚上,设置余辉模式,让波形累积显示几十秒钟。如果看到的上升沿是一条细线,说明 BCK 周期稳定;如果上升沿位置散开成一片光带,宽度超过 500ns,说明中断服务函数执行时间不稳定。用 12T 模式的普通 51 跑 8kHz 采样率时,光带通常很宽,因为中断里dac_l <<= 1dac_r <<= 1的移位操作耗时不同,分支判断也会引入额外延时。优化办法是把移位改成固定次数循环展开,或者把两个声道的数据预组装成一个 32bit 长的临时变量,每次中断只做一次移位。

抖动如果无法消除,优先降低采样率而不是优化代码。把采样率从 8kHz 降到 4kHz,BCK 周期放宽一倍,中断服务函数的指令耗时占比大幅下降,抖动对听感的影响明显减小。模拟 I2S 的音质上限由主控制算力决定,代码优化只能逼近这个上限,不能突破。

5.2 电源噪声与 PCB 布局的三个要点

TDA1311 是模拟数字混合芯片,电源噪声会直接调制到模拟输出上。排查时用示波器 DC 耦合看 VDDA 引脚,满载播放时纹波超过 50mV 就必须处理。布局上守住三个要点:第一,DAC 电源引脚到去耦电容的走线不超过 1mm,用 0.1uF 陶瓷电容并联 10uF 电解电容,电容地端直接回到 DAC 的地引脚;第二,模拟地和数字地单点连接,连接点选在 DAC 芯片下方,避免数字电流流过模拟地回路;第三,BCK、WS、DATA 三根信号线远离模拟输出走线,间距至少 3 倍线宽,必要时在信号线上串联 33 欧姆电阻抑制过冲。

检查项判断标准不满足时的典型症状
BCK 上升沿余辉宽度小于 200ns底噪升高、声音发沙
WS 切换位置发生在 BCK 低电平期间声道串扰、左右音量不一致
VDDA 纹波满载播放小于 50mV输出有交流哼声
DAC 输出直流偏移小于 50mV上电噗声、运放饱和

5.3 出声失败先查这三项

三根线都接了、代码烧进去了、电源也正常,但 TDA1311 就是不出声,按顺序查三个地方:第一,用万用表量 WS 引脚的直流电平,正常播放时 WS 是 8kHz 方波,万用表会显示约 2.5V,如果显示 0V 或 5V,说明 WS 没有翻转,先查定时器是否真的启动了;第二,示波器单次触发抓 BCK 上升沿和 DATA 的时序关系,确认 DATA 在 BCK 上升沿前至少 100ns 已经稳定,如果数据和时钟同时变化,DAC 采到的是不确定值;第三,查 TDA1311 的复位和静音引脚,很多板子上静音引脚被拉到了低电平,芯片一直处于静音状态,这种情况下 BCK、WS、DATA 再正常也没有输出。

把示波器探头换成短地线弹簧,测到的 BCK 抖动数据会立刻变漂亮,这是排查时最容易忽略的假象,短地线能减少探头环路引入的共模噪声,看到的数据才是芯片引脚上的真实波形。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询