ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

51单片机与TLC5615的正弦信号发生器设计详解

51单片机与TLC5615的正弦信号发生器设计详解 简介一套基于51单片机与TLC5615的正弦信号发生器设计资料面向电子、嵌入式方向的初学者及电子设计竞赛学生帮助理解单片机控制DAC生成波形的完整流程。压缩包内含16个文件涵盖C语言源代码、Proteus仿真DSN工程与DBK备份、HEX烧录文件、Keil工程配置等类型整体仅72KB精简但资料链完整。目前已有3619人学习下载适用于希望以最小成本快速上手单片机模拟输出的读者。资源将电路原理图、控制源码与仿真验证集中呈现可对照学习单片机串行通信、TLC5615的12位DAC转换原理、正弦波查表生成及输出滤波设计通过Proteus直观查看波形输出能有效排查硬件连接与代码逻辑问题。对想通过一个完整案例串联51编程、数模转换和电路仿真技能的初学者是很有价值的迷你实践项目。1. 用51单片机TLC5615做正弦信号发生器先搞清这套方案要解决什么实验室里临时要一路几十赫兹到几百赫兹的正弦信号用来驱动传感器线圈或者给音频电路做扫频测试手边没有信号发生器翻元件盒却找出了几片AT89C52和TLC5615。于是就用最直接的查表法把正弦波按等间隔采成128个点存进单片机ROM每隔固定时间取出一个点送给DAC输出端用运放缓冲再接一个低通滤波一条正弦波就这么搭出来了。TLC5615是10位串行DAC只占三根IO波形离散精度对多数演示和低频测量都够用。对刚做完51课程设计、想把手头单片机真正用起来的人来说这是一个性价比很高的组合。下文把原理、电路图、源代码、Proteus仿真串成一条完整链路照着搭就能自己看到波形。2. TLC5615的工作原理与正弦波查表方法先搭对理论再动手2.1 TLC5615的引脚、输出公式与串行时序TLC5615是TI公司的10位电压输出DAC采用三线SPI兼容接口。选它而不是并行DAC原因很实际DAC0832这类并行芯片要占8根甚至12根数据线51单片机的P0、P1口基本被吃光还得额外加拉锁存器TLC5615只要DIN、SCLK、CS三根线剩下IO还能接按键、LCD和编码器。对于IO本来就紧张的51单片机来说这是第一位的选型理由。引脚功能集中在DIN、SCLK、CS、REF、VOUT以及AGND、DGND上。DIN是串行数据输入SCLK上升沿把数据一位一位移入寄存器CS拉低后开始一次写入拉高时DAC才把新数据锁存到输出端。REF是基准电压输入它直接决定满幅输出范围。VOUT是模拟输出脚不能直接带重负载后面必须接运放缓冲。引脚名称作用接线要点1DIN串行数据输入接51的P3.5数据高位在前2SCLK串行时钟接51的P3.4空闲时为低3CS片选接51的P3.6低电平有效4REF基准电压接2.5V基准或电阻分压5VOUT模拟输出接运放同相输入端6/7AGND/DGND模拟地与数字地建议单点接地8VDD电源4.5V到5.5V就近放0.1μF电容输出公式是固定的增益2结构VOUT VREF × D / 1024 × 2。当REF接2.5V时数字量0对应0V数字量1023对应5V中间每一步约4.88mV。如果直接把5V接到REF满幅输出是10V运放和电源轨全部饱和所以REF不能简单接VCC。串行帧是最容易写错的地方。TLC5615每次完整写入是16位前4位是填充位可以写0中间10位是有效DAC数据后2位必须为0。数据从最高位开始逐位移入。网上很多代码只发12位或者忘记低2位补零结果要么完全没有输出要么波形幅度和相位全乱。后面第4章源码会给出严格按16位帧实现的模拟SPI程序。2.2 查表法生成正弦波512511×sin()是怎么算出来的正弦波是连续信号单片机只能在离散时刻给出离散电平因此要先对周期采样。把一个完整正弦周期均匀切成N份每份的幅度用10位DAC能表示的数字量来近似value_i 512 511 × sin(2πi/N)i 0, 1, ..., N-1这里512是零点。因为DAC的数字量0对应0V、1023对应5V满幅5V正弦波的中心是2.5V对应数字量512所以偏移量取512。振幅部分取511而不是512是为了避免峰值超过1023因为1024超出了10位表示范围会出现削顶或卷绕。如果你把表格算成512512×sin结果在峰顶变成1024输出瞬间翻到0附近波形直接撕裂。N取多少合适128点是常见选择点数越多波形越接近理想正弦但同样的目标频率需要更高的DAC刷新率点数太少比如16点输出看起来就像带圆角的锯齿波。做Proteus演示和低频信号源128点是一个兼顾精度与速度的起点。生成这张表不需要手算后面用一段Python代码就能一次性输出全部数组值。2.3 波形频率的计算与定时器初值的选取定时器中断每隔t秒取出一个点送给DAC正弦表有N个点输出波形一个周期需要N×t秒所以输出频率为f_out 1 / (N × t)这是一切频率设计的出发点。晶振12MHz时标准12T的51单片机机器周期是1μs定时器工作方式1是16位计数器溢出间隔为t (65536 - 初值) × 1μs。想要128点、40Hz附近的输出t 1/(128×40) 195.3μs定时器初值大约为65536 - 195 65341十六进制就是0xFF3D实际取0xFF38时输出约39Hz。但有一个非常现实的限制中断服务程序内部要完成查表、16位模拟SPI发送这部分在12MHz的51上单帧就要消耗100μs以上。如果中断间隔小于200μs波形会出现周期性停顿表现为频率上不去或者输出像被打断的阶梯。所以标准12T单片机加模拟SPI时这套方案实际适合工作在几十赫兹到一百多赫兹。想要更高频率要么提高晶振到24MHz要么换1T内核这些放到第6章展开。3. 电路原理图设计51单片机与TLC5615的接线、基准电压与运放输出3.1 原理图整体结构最小系统、DAC、运放三段式原理图按三段来画思路最清晰51最小系统、TLC5615转换电路、运放输出缓冲。TLC5615的DIN、SCLK、CS分别接到51的P3.5、P3.4、P3.6。为什么选P3口而不是P1口因为部分STC51型号的P3.4、P3.5对应硬件SPI引脚以后升级到带硬件SPI的单片机时改动的代码量最少P3.6做片选也是很多Proteus仿真教程的默认习惯照这个连法查资料最省事。最小系统这部分和“51单片机点亮一个LED灯”的基础电路没有本质区别晶振两端各接一个20~30pF负载电容复位电路用10μF电容加10kΩ电阻。Proteus仿真里晶振频率设置为12MHz复位电路画不画都不影响仿真结果但实板上缺了它程序根本跑不起来所以原理图里必须保留。DAC输出端要接电压跟随器。TLC5615的VOUT内部等效输出阻抗不低直接接示波器或后级负载时负载电流会引起输出电压跌落正弦波幅度失真。常见做法是接一个LM358或TL072同相输入端接VOUT反相输入端直接连输出端输出变成低阻抗的干净信号。Proteus元件库里LM358可以搜到单电源5V供电即可工作TL072需要双电源或单电源抬高参考点LM358在这个低频场景里更省事。3.2 基准电压REF怎么给分压电阻或TL431别直接接电源REF引脚决定满幅输出这是原理图里最容易连错的地方。直接把5V VCC接到REF后输出公式变成VOUT 5 × D / 1024 × 2满幅10V远超出5V运放的输出范围正弦波顶部会被硬生生削平。因此REF必须接一个低于供电电压的基准常见取2.5V。最简单的做法是两个10kΩ电阻把5V分压成2.5V后接REFProteus里也可以直接放一个2.5V的DC电压源。电阻分压胜在便宜但REF会抽取一定电流分压点电压会略低于理论值而且电源纹波会直接串进DAC输出示波器上能看到正弦波表面附着毛刺。讲究一点就用TL431搭2.5V基准或者用专门的REF5025这类基准芯片。调试阶段建议先用理想2.5V电压源验证代码确认波形正确后再换分压方案看精度差异。验证REF和DAC是否匹配有一个很有效的静态方法临时把正弦表改成固定输出数字量512也就是写入中值运放输出端理论上应该等于REF电压。如果测出来不对问题基本出在REF接法或运放跟随器接错不必怀疑程序。3.3 输出缓冲与电源去耦TLC5615输出不能直接带重负载TLC5615虽然功耗不大但数字地与模拟地在芯片内部是分开的原理图上要遵循单点接地原则AGND和DGND先汇合到芯片附近的一点再统一连接到电源地平面避免数字开关噪声通过地线串进模拟输出。去耦电容的摆放顺序是DAC电源脚就近放0.1μF陶瓷电容运放电源脚同样放0.1μF电源入口处再放一个10μF电解电容吸收低频波动。Proteus仿真环境本身是理想化的不加这些电容也能通过但实板上缺少去耦电容时输出正弦波上会看到高频毛刺频率越高越明显所以原理图里必须预留位置。还要注意不要直接把滤波电容并联在运放输出端。运放输出直接接容性负载会引起振铃波形边缘出现衰减振荡。正确的滤波方式是在运放输出之后串电阻再接电容到地构成RC低通这部分在第6章会给出具体参数计算。4. C语言源代码实现查表驱动TLC5615生成正弦波的最小工程4.1 完整C51源代码正弦表、模拟SPI、定时器中断最小工程只需要一个main.c在Keil C51里新建工程、选择AT89C52、勾选Output选项卡里的Create HEX File然后把下面代码编译出HEX文件Proteus里直接加载。正弦表部分用Python生成后粘贴代码里不要相信手算出来的128个浮点数值。#include reg52.h #include math.h #define POINTS 128 #define TIMER_H 0xFF #define TIMER_L 0x38 sbit TLC_SCLK P3^4; sbit TLC_DIN P3^5; sbit TLC_CS P3^6; /* 由右边的Python语句生成后粘贴替换 */ /* python -c import math; print(, .join(str(round(512511*math.sin(2*math.pi*i/128))) for i in range(128))) */ unsigned int code sin_table[POINTS] { /* 数组太长实际工程中从生成命令输出复制 */ }; void TLC5615_Write(unsigned int dat); void main(void) { TMOD 0x01; /* 定时器016位定时方式1 */ TH0 TIMER_H; TL0 TIMER_L; ET0 1; /* 打开定时器0中断 */ EA 1; /* 打开总中断 */ TR0 1; /* 启动定时器 */ while (1) { /* 主循环空跑波形输出全部交给中断 */ } } void Timer0_ISR(void) interrupt 1 { static unsigned char idx 0; TH0 TIMER_H; /* 重装初始值保证间隔稳定 */ TL0 TIMER_L; TLC5615_Write(sin_table[idx]); idx; if (idx POINTS) idx 0; } void TLC5615_Write(unsigned int dat) { unsigned char i; unsigned int frame; frame (dat 0x03FF) 2; /* 10位数据挪到bit11-bit2低2位补0 */ TLC_CS 0; for (i 0; i 16; i) { TLC_SCLK 0; if (frame 0x8000) TLC_DIN 1; else TLC_DIN 0; frame 1; TLC_SCLK 1; /* 上升沿到来数据被移入芯片 */ } TLC_CS 1; /* 拉高片选DAC输出更新 */ }正弦表用下面这行Python生成运行结果直接粘贴到sin_table数组花括号里import math vals [round(512 511 * math.sin(2 * math.pi * i / 128)) for i in range(128)] print(, .join(str(v) for v in vals))TLC5615_Write是整套代码的核心。frame (dat 0x03FF) 2先把10位有效数据放到16位帧的bit11到bit2高4位和低2位全部补0正好对应TLC5615的16位串行帧结构。发送顺序从MSB开始循环里检测frame 0x8000就是判断当前最高位每次左移一位把下一位顶到最高。外层TLC_CS 0在16个时钟前拉低最后一个上升沿之后TLC_CS 1DAC输出才更新这个时序不能反。4.2 关键参数说明点数、初值、幅值的修改位置如果要换输出频率只需要改TIMER_H和TIMER_L。计算步骤是先确定目标频率f和点数N算出中断间隔t 1/(f×N)再算定时器初值 65536 - t/机器周期。12MHz晶振、12T单片机时机器周期为1μs所以初值直接等于65536减去以μs为单位的t。下面表格列出的三组常用配置都基于128点正弦表目标频率中断间隔定时器初值12MHz标准51能否运行19.5Hz400μs0xFE70可以波形干净39Hz200μs0xFF38可以模拟SPI刚好来得及78Hz100μs0xFF9C很紧张需24MHz或1T内核幅值的修改有三个途径。第一是改表生成公式中的振幅系数511但要注意实际上限仍是1023第二是改REF电压波形幅度线性跟随REF变化最推荐第三是在运放输出端加可调电阻分压实板和仿真都有效。第一种会动到整张表建议只在重新生成数组时处理。4.3 模拟SPI的三个坑帧长度、SCLK极性和CS时序先说帧长度。TLC5615写入必须是完整16位不是12位也不是8位。少发几位DAC拿到的有效数据会整体错位输出的正弦波幅度明显偏小或出现直流偏移多发几位多余的时钟会继续移入寄存器把最后的数据挤出去。代码里循环固定执行16次这是最稳妥的写死方式不要试图用while加计数器偷懒。第二个坑是SCLK极性。TLC5615要求空闲时SCLK为低数据在SCLK上升沿被移入是标准的SPI模式0。如果从网上复制的代码约定空闲为高数据会在下降沿采样结果是所有位都错开一拍波形看起来完全不对。调试时用示波器看SCLK和DIN的相对关系上升沿时刻DIN必须是稳定的有效电平。第三个坑是CS时序。CS在16位发送过程中必须保持低不能中途拉高否则内部移位寄存器被提前锁存输出更新到一半的数据。每次发送结束再拉高CS这时DAC才真正更新输出。另外复位后进入main函数之前最好先把CS引脚置为高电平避免上电瞬间TLC5615处于不确定的锁存状态这一点Proteus偶尔会出现实板更容易遇到。5. Proteus仿真电路搭建与波形验证从编译到示波器出波形5.1 Proteus元件库添加与电路连线步骤Proteus 8 Professional新建工程后从左侧元件模式点“P”打开元件库搜索关键词AT89C52放置单片机。TLC5615需要在元件库里搜“TLC5615”通常位于Data Converters分类下封装为DIP8。如果搜索不到检查Proteus版本或者把过滤器切换到全部类别再试主流版本元件库里都有这个器件。连线顺序按电路图结构来先把单片机晶振频率在属性面板改成12MHz然后放置TLC5615把DIN、SCLK、CS三脚分别连到P3.5、P3.4、P3.6。接着放置LM358同相输入接TLC5615的VOUT反相输入端和输出端短接。REF引脚放一个2.5V的DC电压源或者用两个电阻从5V分压。最后把所有地连到同一个网络电源电压统一设为5V。连线完成后不要急着仿真先检查网络连接状态。Proteus里连好线后引脚附近会出现绿色小方块表示网络连接成功如果出现悬浮的红色或黄色标记说明引脚没接到有效网络。特别是AGND和DGND很多新手只接了数字地模拟地悬空结果VOUT输出异常。5.2 用Proteus虚拟示波器看波形并判断失真Proteus的虚拟示波器在左侧“Virtual Instruments”菜单里图标是一个带波形的示波器面板。拖到原理图上把运放输出接到A通道示波器地线接电路地。双击示波器打开面板耦合方式选DC垂直方向每格调到1V左右水平方向先设成20ms/格运行仿真后应该能看到规则的正弦波。如果看到的是明显阶梯状而不是平滑圆弧先区分两种情况。在TLC5615的VOUT直接测波形可以看到一个点一个点的保持台阶这是DAC的采样保持特性属正常现象。运放输出端如果依然全是台阶说明缺少低通滤波波形没有经过平滑如果台阶幅度忽大忽小多半是定时器中断间隔和模拟SPI发送时间冲突把中断间隔调大到200μs以上再观察。Proteus里判断频率对不对很简单数示波器一格内有多少个完整周期。200μs中断、128点表时一个周期是25.6ms也就是20ms/格下约1.28格一个周期。如果实测周期和理论计算偏差超过5%优先检查晶振频率是否真的设置为12MHz而不是看默认的1MHz。5.3 仿真不动、频率不对、DAC没输出三个常见问题仿真完全不动时第一嫌疑是HEX文件没加载。Keil工程必须勾选“Create HEX File”重新编译编译成功后Output窗口里会显示HEX文件路径然后在Proteus里双击AT89C52在Program File栏指定这个HEX文件。注意路径不要含中文和空格否则Proteus偶尔加载失败。频率不对基本上就是定时器初值取整问题。例如想要50Hz200μs让我准确算50Hz、128点周期20ms中断间隔156.25μs初值为65536-15665380十六进制0xFF64实际频率是50.08Hz误差不到0.2%但如果想要100Hz中断间隔78.125μs初值取0xFFB1或0xFFB2对应频率分别是99.6Hz或104Hz左右取整跳变会明显一些。这种场景建议改DDS累加器方案连续变频更平滑第6章会写。DAC没输出时按这个顺序排查先用电压探针量REF脚确认2.5V存在再量TLC5615的VOUT看是否有固定直流电压如果VOUT始终是0V代码里的CS或SCLK初始化有问题如果VOUT有直流但不变化说明串行数据没有正确写入。把TLC5615_Write里的循环次数从16改成24如果波形乱了但位置偏移就能侧面确认原来是帧长度不匹配。6. 进阶技巧用低通滤波压失真用DDS思想实现连续变频6.1 输出端加二阶低通滤波把阶梯感压下去DAC输出的原始信号是阶梯波幅度谱在采样频率处有一簇谐波。采样频率就是中断频率128点表、200μs中断时采样频率为5kHz而正弦波只有39Hz两者隔了很远非常适合用简单RC低通把台阶抹平。一阶RC低通的截止频率fc 1/(2πRC)。以39Hz输出为例采样台阶在5kHz把截止频率设在500Hz左右正弦基波完全不受影响而5kHz处已有明显衰减。一种常用取值是R3.3kΩ、C100nFfc约482Hz想衰减更大就再做一级两级之间用1kΩ内阻的运放隔开避免两级互相影响。Proteus里直接放两个电阻两个电容串联就能看到效果VOUT处的台阶在滤波后变成光滑圆弧。一阶RC在5kHz处只衰减约20dB阶梯的残余还能看到一点二阶RC衰减约40dB肉眼基本分辨不出台阶。注意电容不要并连在运放反馈环内滤波必须放在运放输出之后否则运放会因容性负载产生振铃。6.2 用相位累加器实现连续可调频率固定间隔加查表索引累加的方式改频率时必须重新计算定时器初值每次改动都会带来相位跳变而且定时器初值取整会引入频率误差。更好的做法是把相位用32位变量维护中断间隔固定不变每次中断只做加法static unsigned long phase_acc 0; unsigned long step 0; void Timer0_ISR(void) interrupt 1 { TH0 TIMER_H; TL0 TIMER_L; phase_acc step; /* 32位相位取高7位作为128点表的索引 */ idx (unsigned char)((phase_acc 25) 0x7F); TLC5615_Write(sin_table[idx]); }32位相位累加器里2^32对应一个完整正弦周期。step与目标频率的关系是step f_target × 2^32 × t其中t是固定中断间隔。比如t200μs时想要39Hzstep 39 × 4294967296 × 0.0002 ≈ 33500700十六进制约为0x01FF2ABC。改频率只需要改step这一个变量中断间隔完全不用动相位连续性也得到保证输出不会出现周期性突跳。这是DDS的直接相位累加思想TLC5615的10位分辨率恰好够用查表索引精度和DAC量化误差都不会成为瓶颈。6.3 频率上限的边界在哪里标准12T的51单片机在12MHz晶振下模拟SPI发一帧16位数据需要上百微秒这直接锁死了输出频率上限。128点表时中断间隔最小也约100μs对应输出频率约78Hz再往下压缩中断间隔波形就支离破碎了。实板想要几百Hz的正弦波常见方案有换STC8G这类1T内核单片机机器周期缩短12倍同样代码能跑到几百赫兹或者用带硬件SPI的单片机SCLK由外设自动产生中断服务里只写一个字节发送时间降到几十微秒以内再或者把正弦表从128点降到32点频率上限提高4倍但谐波明显增大。如果目标是1kHz以上TLC5615本身能工作瓶颈全在单片机侧。实际项目里要做更高频信号不如直接换DDS专用芯片TLC5615方案的优势区间就是几十赫兹到几百赫兹的低频信号源电路简单、代码直观、Proteus验证方便。把这套边界记清楚就不会出现“程序看起来对、示波器波形却乱掉”的尴尬。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进