简介:这是一套面向电子工程专业学生与嵌入式初学者的完整收音机系统设计资料,以C51单片机为核心,配合TDA5767调频/调幅立体声解码芯片,覆盖硬件电路与软件编程两条主线,可用于课程设计、毕业设计或自学练手。压缩包共9个文件,约22KB,包含4个Proteus原理图文件、3个C语言源文件与2个头文件,分别对应电路搭建、主程序逻辑、液晶与按键交互、I2C及SPI通信等模块,结构紧凑便于按功能查阅。目前已有119人学习下载。通过研读源码,读者可掌握C51寄存器操作、单片机与TDA5767的数据交互方式、音频输出控制及用户界面编程思路;借助Proteus仿真,还能在无实物硬件时先行验证电路行为、调整参数并排查设计缺陷,从而在动手制作前优化方案,是兼顾理论理解与工程实践的一份实用参考。
1. 从一块 TDA5767 和一颗单片机说起:这套收音机方案到底解决什么问题
很多人第一次看到“基于单片机+TDA5767的收音机系统设计电路proteus仿真+软件code源代码”这个标题,第一反应是:这不就是个毕业设计题目吗?但如果你真的动手做过,就会发现它其实是一套非常典型的“模拟前端 + 数字控制”混合系统。TDA5767 是一颗电调谐调频收音芯片,内部集成了本振、混频、中放和鉴频,外部只需要少量阻容和一颗晶振就能工作;单片机负责通过 I2C 总线写寄存器,完成频点设置、搜台、音量控制,再把当前频率显示在 LCD 或数码管上。这套组合之所以在 51 单片机、STC 单片机课程设计和收音机 DIY 圈子里反复出现,是因为它把“收音机本振电路原理”这种原本需要绕线圈、调中周的模拟活,变成了可以软件配置的数字活。你不需要频谱仪,也不需要高频示波器,只要会写 I2C 时序,就能让喇叭里出声音。这篇文章面向的是想把这套系统真正跑起来的人:不管你是用 Proteus 仿真先验证逻辑,还是打算打板焊一块实物,下面会从芯片选型、Proteus 建模、代码结构到调试踩坑,一步步拆开讲。
2. TDA5767 与单片机的硬件连接:为什么它比传统收音机电路更好落地
2.1 TDA5767 内部框图与外围元件选型
TDA5767 的官方资料里给的是 40 引脚或 44 引脚封装,核心引脚包括 SDA、SCL、VCC、GND、ANT、AFOUT 等。它内部的本振是一个 LC 压控振荡器,但电感电容已经集成在芯片里,外部只需要接一颗 32.768 kHz 或 13 MHz 的参考晶振。这一点非常关键:传统收音机的本振电路需要你自己绕中周、配变容二极管,稍微分布参数不对就停振;TDA5767 把这一块全部封装进去,你只需要保证 I2C 时序正确,芯片就会按照你写的分频比输出对应频点。
外围元件里,最容易被忽视的是电源退耦。TDA5767 的模拟部分对电源纹波很敏感,常见做法是在 VCC 引脚附近放一颗 100 nF 陶瓷电容和一颗 10 μF 钽电容,地线尽量短。天线输入如果只是做实验,用一根 75 cm 左右的软导线就够;如果要接拉杆天线,中间串一颗 10 pF 到 22 pF 的耦合电容,避免直流偏置被短路。音频输出端 AFOUT 的幅度大约在 100 mV 到 300 mV 之间,直接接耳机听声音偏小,一般要加一级 LM386 或者 TDA2822 功放。如果你在 Proteus 里仿真,可以先用一个 AC 电压探针看波形,确认有音频输出后再接功放模型。
单片机这边,51 系列或者 STC8 系列都行,关键是 I2C 引脚要能输出开漏或者准双向模式。TDA5767 的 I2C 地址是固定的 0xC0(写)和 0xC1(读),7 位地址是 0x60。很多新手在这里翻车:以为地址可以改,结果写了一天没反应。实际上 TDA5767 不支持地址配置,总线上只能挂这一颗芯片,或者用 I2C 多路复用器。如果你用的是 STC8G1K17 这类增强型 51,硬件 I2C 和软件模拟 I2C 都可以,但软件模拟更可控,因为 TDA5767 的时序要求并不高,标准 100 kHz 就能稳定工作。
2.2 最小系统连接表与 Proteus 元件对照
在 Proteus 里搭这套电路,第一步是找到正确的元件名。Proteus 的元件库和实际型号有时候对不上,下面这张表是我自己常用的对照关系:
| 实际器件 | Proteus 元件名 | 备注 |
|---|---|---|
| TDA5767 | TDA5767 | 部分版本库中没有,可用 TEA5767 替代 |
| 51 单片机 | AT89C51 / AT89C52 | 仿真够用,实物建议 STC8 |
| 晶振 32.768 kHz | CRYSTAL | 频率在属性里改 |
| LCD1602 | LM016L | 注意数据线接 P0 时要加上拉 |
| 功放 | LM386 | 仿真里可省略,直接看 AFOUT |
| 按键 | BUTTON | 搜台、音量、复位 |
接线逻辑是:单片机的 P2.0 接 SDA,P2.1 接 SCL,两个引脚各接一颗 4.7 kΩ 上拉到 VCC。TDA5767 的 VCC 接 5 V,GND 共地。晶振接在芯片的晶振引脚上,负载电容一般 12 pF 到 22 pF。LCD1602 的 RS、RW、E 分别接 P2.2、P2.3、P2.4,数据线 D4 到 D7 接 P0.4 到 P0.7。如果你用的是 STC 单片机,P0 口内部没有上拉,驱动 LCD 时一定要外接 10 kΩ 排阻,否则屏幕会显示乱码或者完全不亮。这个坑我在第一次做的时候踩过,仿真里因为 Proteus 默认给 P0 加了弱上拉,看起来正常,一打板就翻车。
提示:Proteus 8.17 之后的版本对 TDA5767 的支持并不统一,如果元件库搜不到,直接搜 TEA5767,寄存器定义基本兼容,只是个别位有差异,写代码时以 TDA5767 数据手册为准。
3. 软件代码结构:I2C 驱动、搜台算法与显示刷新怎么组织
3.1 软件模拟 I2C 的时序与寄存器写入
TDA5767 的寄存器是 5 个字节,第一个字节是地址 0xC0,后面跟 5 个数据字节。上电后芯片处于默认状态,需要先写配置字才能设定频点。下面这段代码是 51 单片机上的软件 I2C 写字节函数,延时参数按 11.0592 MHz 晶振调过,可以直接抄:
#include <reg52.h> #include <intrins.h> sbit SDA = P2^0; sbit SCL = P2^1; void I2C_Delay() { _nop_(); _nop_(); _nop_(); _nop_(); } void I2C_Start() { SDA = 1; SCL = 1; I2C_Delay(); SDA = 0; I2C_Delay(); SCL = 0; I2C_Delay(); } void I2C_Stop() { SDA = 0; SCL = 1; I2C_Delay(); SDA = 1; I2C_Delay(); } void I2C_WriteByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { SDA = (dat & 0x80) ? 1 : 0; dat <<= 1; I2C_Delay(); SCL = 1; I2C_Delay(); SCL = 0; I2C_Delay(); } SDA = 1; // 释放总线等待 ACK I2C_Delay(); SCL = 1; I2C_Delay(); SCL = 0; I2C_Delay(); } void TDA5767_Write(unsigned char *buf) { unsigned char i; I2C_Start(); I2C_WriteByte(0xC0); // 写地址 for (i = 0; i < 5; i++) { I2C_WriteByte(buf[i]); } I2C_Stop(); }这段代码里,I2C_Delay的四个_nop_()对应大约 4 μs 的延时,SCL 频率在 100 kHz 左右。如果你用 STC8 系列,主频可以跑到 24 MHz 甚至 35 MHz,这时候需要把延时加长,否则时序太快 TDA5767 可能不响应。判断有没有响应,最简单的办法是用示波器看 SDA 在第 9 个时钟周期是否被拉低;如果没有拉低,说明地址写错了或者芯片没焊好。寄存器数据里,第一个字节的高 2 位是频率的高位,后面两个字节是频率的低位,具体换算公式是:频率(kHz) = 4 * (PLL + 225),其中 PLL 是 14 位分频比。比如你要收 98.7 MHz,PLL 就是 98700 / 4 - 225 = 24450,转成十六进制是 0x5F82,高字节 0x5F,低字节 0x82。
3.2 自动搜台与信号强度判断
TDA5767 的寄存器 3 的高 4 位是信号强度,范围 0 到 15。自动搜台的逻辑就是:从当前频率开始,每次步进 100 kHz,写频率,延时 50 ms 等芯片稳定,读寄存器 3,如果信号强度大于某个阈值(比如 8),就认为搜到台,停下来;否则继续往上搜。下面是一个简化的搜台函数:
unsigned char TDA5767_ReadReg3() { unsigned char dat; I2C_Start(); I2C_WriteByte(0xC1); // 读地址 dat = I2C_ReadByte(); I2C_Stop(); return dat; } void AutoSearch() { unsigned int pll; unsigned char reg[5]; for (pll = 0; pll < 16384; pll++) { reg[0] = (pll >> 8) & 0x3F; reg[1] = pll & 0xFF; reg[2] = 0x00; // 其他配置位 reg[3] = 0x00; reg[4] = 0x00; TDA5767_Write(reg); Delay_ms(50); if ((TDA5767_ReadReg3() >> 4) > 8) { // 搜到台,更新显示 break; } } }这里有个细节:读寄存器的时候,TDA5767 会连续输出 5 个字节,你只需要读第 4 个字节(从 0 开始数就是第 3 个)的高 4 位。很多例程里读一个字节就发 Stop,其实芯片内部地址指针会停在当前字节,下次读会继续往下走,所以要么读满 5 个字节,要么每次读之前重新发 Start 和读地址。我一般习惯读满 5 个字节,把信号强度和立体声标志都取出来。搜台阈值不要设得太高,否则弱台会被跳过;也不要设得太低,否则噪声也会被当成电台。实测在市区环境,阈值 8 到 10 比较合适,郊区可以降到 6。
3.3 LCD 显示刷新与按键处理的状态机
显示部分用 LCD1602 就够了,第一行显示当前频率,第二行显示信号强度和立体声状态。刷新频率不要太高,200 ms 一次足够,否则 LCD 会闪。按键处理建议用状态机,不要用delay死等。下面是一个简单的状态机框架:
enum { KEY_IDLE, KEY_DEBOUNCE, KEY_PRESSED, KEY_RELEASE } key_state = KEY_IDLE; void KeyScan() { static unsigned int count = 0; unsigned char key = P3 & 0x0F; // 假设按键接 P3.0-P3.3 switch (key_state) { case KEY_IDLE: if (key != 0x0F) { key_state = KEY_DEBOUNCE; count = 0; } break; case KEY_DEBOUNCE: if (++count > 20) { // 20ms 消抖 if (key != 0x0F) { key_state = KEY_PRESSED; } else { key_state = KEY_IDLE; } } break; case KEY_PRESSED: // 处理按键动作 if (key == 0x0F) { key_state = KEY_RELEASE; count = 0; } break; case KEY_RELEASE: if (++count > 20) { key_state = KEY_IDLE; } break; } }这个状态机的好处是主循环不会被按键阻塞,搜台和显示刷新可以并行。如果你用 51 单片机,定时器 0 产生 1 ms 中断,在中断里调用KeyScan,主循环里处理搜台和显示,整个系统响应就很流畅。注意 LCD1602 的忙标志判断,如果不用忙标志,每条指令之间要延时 2 ms 以上,否则显示会丢字符。
4. Proteus 仿真与实物调试的避坑记录
4.1 仿真能跑、实物不响:电源和晶振的锅
现象:Proteus 里波形完美,喇叭有声音,打板后一点动静都没有。原因:TDA5767 的晶振引脚负载电容不匹配,或者电源退耦没做好。解决:先量晶振引脚有没有起振,用示波器看 32.768 kHz 波形,幅度应该在 1 Vpp 左右。如果没有起振,把负载电容从 22 pF 降到 12 pF 试试。电源方面,在 VCC 和 GND 之间并一颗 100 nF 加一颗 10 μF,地线走单点接地,模拟地和数字地在芯片下方分开,最后汇合到电源入口。
4.2 I2C 写不进去:上拉电阻和地址的玄学
现象:单片机一直发数据,但 TDA5767 没有任何反应,读回来的信号强度始终是 0。原因:SDA 和 SCL 没有上拉,或者上拉阻值太大。解决:51 单片机的 P2 口内部有弱上拉,但驱动 I2C 时还是建议外接 4.7 kΩ 上拉到 3.3 V 或 5 V。如果总线上挂了其他 I2C 器件,检查地址冲突。TDA5767 的地址是固定的,不能改,所以总线上只能有它一颗,或者用 TCA9548 这类多路复用器。
4.3 搜台搜不到:阈值和步进设置
现象:自动搜台一直循环,停不下来。原因:信号强度阈值设得太高,或者步进太大。解决:先把阈值降到 5,步进改成 50 kHz,看看能不能搜到本地强台。如果能搜到,再逐步提高阈值。另外,TDA5767 的搜台模式有两种:一种是搜索模式,芯片内部自动扫描;另一种是直接写频率。我一般用直接写频率加软件判断,这样可控性更强。
4.4 LCD 显示乱码:P0 口上拉和初始化时序
现象:LCD1602 第一行显示黑块,第二行什么都没有。原因:P0 口没有上拉,或者初始化延时不够。解决:P0 口接 10 kΩ 排阻到 VCC。初始化时,先延时 15 ms,再写 0x38 两次,再写 0x0C、0x06、0x01,每条指令之间延时 2 ms。如果还是乱码,检查 RS、RW、E 的接线有没有接反。
4.5 实物有噪声:音频地和数字地没分开
现象:喇叭里有“滋滋”声,调音量也没用。原因:数字地和模拟地混在一起,单片机的高频噪声串到音频通道。解决:在 PCB 布局时,TDA5767 的音频输出部分和单片机部分用地线隔开,音频地单独走线到电源地。功放芯片的电源加一颗 100 μF 电解电容和一颗 0.1 μF 陶瓷电容。如果已经打板了,可以在 AFOUT 和功放输入之间串一颗 1 kΩ 电阻,再并一颗 100 pF 电容到地,能明显改善噪声。
5. 从能响到好听:TDA5767 的进阶调优与验证方法
5.1 用寄存器位精细控制立体声与静音
TDA5767 的寄存器 2 和寄存器 3 里有一些容易被忽略的控制位。比如寄存器 2 的 bit 3 是立体声/单声道切换,bit 4 是静音,bit 5 是软静音。如果你在搜台时想暂时静音,可以把 bit 4 置 1,搜到台后再清 0。立体声在信号弱的时候会引入噪声,这时候强制单声道反而听感更好。下面这段代码演示了如何在不改变频率的情况下切换静音:
void TDA5767_Mute(unsigned char enable) { unsigned char reg[5]; // 先读当前寄存器值(简化处理,实际应先读再改) reg[0] = 0x5F; // 示例频率高字节 reg[1] = 0x82; // 示例频率低字节 reg[2] = enable ? 0x08 : 0x00; // bit3 静音 reg[3] = 0x00; reg[4] = 0x00; TDA5767_Write(reg); }实际项目中,我建议维护一个全局的radio_regs[5]数组,每次修改只改对应位,然后整体写入。这样不会因为忘记保留其他位而导致频率跳变。
5.2 用信号强度做天线调整的量化依据
TDA5767 的信号强度是 4 位,范围 0 到 15。你可以写一个简单的测试程序,每 500 ms 读一次寄存器 3,把高 4 位显示在数码管上。然后调整天线的长度和方向,观察数值变化。实测在室内,一根 75 cm 的软线,信号强度在 8 到 12 之间;换成拉杆天线并调整角度,可以到 13 到 15。这个数值比“听起来清楚”要客观得多,尤其适合在调试阶段判断天线匹配好不好。
5.3 验证接收性能的一个小技巧
如果你手头有信号发生器,可以输出一个 98.7 MHz 的调频信号,调制 1 kHz 正弦波,幅度 1 mV 到 10 mV,然后看 TDA5767 的 AFOUT 输出。用示波器看音频波形,失真度用耳朵听就行。如果没有信号发生器,可以用手机下载一个调频发射 App,虽然功率很小,但在半米内足够验证接收通道是否正常。注意手机发射的频点要避开本地强台,否则会被压制。我一般选一个本地没有电台的频点,比如 87.5 MHz 或者 108 MHz,这样背景干净,容易判断。
5.4 一个让我后悔没早做的习惯
每次改完代码,先别急着烧片,在 Proteus 里跑一遍 I2C 时序,用虚拟示波器看 SDA 和 SCL 的波形。确认起始条件、地址字节、ACK 位都正确,再烧到实物。这个习惯帮我省下了至少一半的调试时间。另外,TDA5767 的寄存器写入后需要一点时间稳定,尤其是搜台时,不要连续写频率,每次写之间至少隔 50 ms。这个延时在仿真里看不出来,实物上如果太短,芯片会直接忽略后面的写入。希望帮到你。
本文还有配套的精品资源,点击获取