
简介一份面向嵌入式开发者的数字信号处理器DSP数模转换例程包围绕TMS320F28335浮点数字信号处理器与DAC7724四通道数模转换芯片展开。资料核心是C语言源程序及配套的工程框架包含CCS工程文件、链接配置文件、调试生成的目标文件与编译日志演示如何通过处理器精确控制十六位数模转换芯片实现高精度模拟量输出。压缩包共25个文件约329KB除源代码与工程配置外还有官方驱动模块的中间目标文件、调试输出与映射文件以及符号数据库文件基本覆盖从程序编译到烧写调试的完整环节。目前已有217人学习下载。由于包内同时保留调试中间文件和源码学习者可对照源码逐模块分析初始化流程、数模转换时序和输出更新方法快速迁移到电机控制、信号发生、电源管理等实际项目中。1. 先看清楚28335的DAC困局选外置DAC7724的理由一块TMS320F28335AD口有16个通道片上DAC却一个都没有。做运动控制或者波形发生器时模拟输出只能靠外部DAC补位。DAC7724是TI早期的并行12位四通道DAC数据更新快双缓冲结构正好适合多轴同步输出。虽然SPI型DAC如DAC8552接线更少但28335的GPIO模拟SPI要消耗CPU周期对纳秒级同步有要求时并行接口直接写入更利索。这篇文章把28335用GPIO驱动DAC7724的完整做法包括引脚接线、GPIO模拟时序、精度校准和PCB布局要点按可复现的方式写出来。手里有这两颗料正在画原理图和调程序的同事可以直接按这个路数推进。2. DAC7724的引脚时序和28335接口设计先把DAC7724到底怎么被“操作”讲清楚再决定接线和代码结构。2.1 DAC7724的通道选择、双缓冲和一次完整更新DAC7724内部有四个完整的DAC通道每个通道由两级锁存器组成输入锁存器和DAC锁存器。A1、A0地址线负责选中当前输入锁存器数据线DB11~DB0在WR上升沿到来时被锁存到对应通道的输入锁存器。LDAC信号低电平时四个输入锁存器的数据同时被送到各自的DAC锁存器这个动作才是DAC输出真正变化的时间点。这样的好处是四个通道的电压值可以先分别写进去再统一拉低LDAC做到同步更新。编程时最容易犯的错是只关心数据码值忽略A1/A0之前是否稳定导致数据写进了不期望的通道。写时序很简单把CS、WR都拉低数据放到总线上地址线给上通道号然后让WR从低到高数据就被锁存。之后再做LDAC处理。28335的GPIO输出翻转速度在几十ns量级完全匹配DAC7724的写入要求。提示DAC7724没有读回功能所有寄存器都是只写因此排错时不能用总线回读确认状态必须依赖示波器和输出电平时序。2.2 28335的GPIO与DAC7724的接线映射表实际工程中直接用GPIO模拟总线不建议用XINTF因为XINTF的时序配置对初学者不友好而且DAC7724的写入速度要求并不高。常用引脚分配如下表这是一套经过验证的映射也可以按自己板子调整。DAC7724引脚28335的GPIO方向说明DB0~DB11GPIO0~GPIO11输出12位并行数据连接到28335低12位数据GPIOA0GPIO12输出通道地址最低位A1GPIO13输出通道地址最高位CSGPIO14输出片选低有效WRGPIO15输出写信号低有效上升沿锁存LDACGPIO16输出更新信号低有效RESETGPIO17输出复位低有效平时拉高这里有一个兼容性关键点DAC7724的逻辑输入高电平阈值在VLOGIC5V时大约是2.0V而28335的GPIO输出高电平为3.3V所以不需要电平转换芯片。但要注意DAC7724的VLOGIC和数字地建议接3.3V电源域避免长线传输压降导致高电平不够。2.3 读写时序参数和一组GPIO宏DAC7724手册给出的典型时序参数中数据建立时间、保持时间都在几十纳秒量级。28335的GPIO操作使用内存映射寄存器单次赋值大概几十ns加上数据传输和地址设置完全满足需求。为了代码可读性先把控制引脚定义为宏#define DAC_CS_LOW GpioDataRegs.GPADAT.bit.GPIO14 0 #define DAC_CS_HIGH GpioDataRegs.GPADAT.bit.GPIO14 1 #define DAC_WR_LOW GpioDataRegs.GPADAT.bit.GPIO15 0 #define DAC_WR_HIGH GpioDataRegs.GPADAT.bit.GPIO15 1 #define DAC_LDAC_LOW GpioDataRegs.GPADAT.bit.GPIO16 0 #define DAC_LDAC_HIGH GpioDataRegs.GPADAT.bit.GPIO16 1这些宏直接操作寄存器比调用函数快得多。注意如果其它外设用了GPIO16需要先确认没有抢占。模拟总线的核心动作就是先给地址再写数据最后提升WR。不需要额外的IO口做读操作因为DAC7724没有读回总线。这就是最底层的操作逻辑。3. 28335用GPIO模拟写DAC7724的完整C代码下面给出可以直接跑的最小代码。这里不依赖TI的官方驱动库函数只写顶层寄存器操作在CCS环境和裸机工程里都能编译。3.1 GPIO复用与方向配置DSP28335上电后GPIO默认是IO模式但GPAMUX寄存器可能被boot程序改写。最稳妥的办法是在初始化代码里显式设置复用和方向。下面是初始化函数void DAC_GPIO_Init(void) { EALLOW; // GPIO0~GPIO15 由 GPAMUX1 控制GPIO16~GPIO17 由 GPAMUX2 控制 // 全部设为普通IO模式 GpioCtrlRegs.GPAMUX1.all 0x00000000; // GPIO0~GPIO15 GpioCtrlRegs.GPAMUX2.all 0x00000000; // GPIO16~GPIO31 // 方向GPIO0~GPIO17 全部输出 GpioCtrlRegs.GPADIR.all 0x0003FFFF; // 低18位置1 // 控制引脚恢复默认状态CS、WR、LDAC、RESET 全部置高 GpioDataRegs.GPADAT.all | 0x0003FFFF; EDIS; }这段代码里GPAMUX1和GPAMUX2全部清零确保没有任何GPIO引脚被复用为外设功能。GPADIR低18位置1把GPIO0到GPIO17全部配置为输出。最后通过GPADAT把CS、WR、LDAC、RESET都置高避免上电瞬间的毛刺触发误写入。EALLOW和EDIS是TMS320F28335访问受保护寄存器的标准写法不写的话寄存器写操作会被CPU忽略。3.2 写DAC通道的函数实现写DAC时要把数据放到GPIO0~GPIO11上但同时不能干扰高位的控制引脚。所以使用GPADAT.all读出当前状态再对低12位做替换void DAC_WriteChannel(unsigned int ch, unsigned int data) { unsigned long temp; // 限制ch范围0~3data范围0~4095 ch ch 0x03; data data 0x0FFF; // 设置通道地址 A0、A1 GpioDataRegs.GPADAT.bit.GPIO12 ch 0x01; GpioDataRegs.GPADAT.bit.GPIO13 (ch 1) 0x01; // 把数据写入低12位保留高6位GPIO12~17状态 temp GpioDataRegs.GPADAT.all; temp (temp 0xFFFFF000) | data; GpioDataRegs.GPADAT.all temp; // 产生WR上升沿锁存数据 DAC_CS_LOW; DAC_WR_LOW; asm( NOP); asm( NOP); DAC_WR_HIGH; DAC_CS_HIGH; }参数说明ch是0到3对应DAC7724的A1/A0组合data是12位码值0x000到0xFFF。为什么要用temp而不是直接改GPADAT因为GPADAT的写操作会影响整个端口如果把所有位一起写了其他控制引脚状态就会丢失。这里的asm( NOP)是为了保证WR低电平持续时间足够两条NOP后低电平宽度通常在几十纳秒满足多数DAC7724批次的要求。如果你的芯片走线过长或者时钟频率较低可以增加到四条NOP。3.3 双缓冲四通道同步更新双缓冲的意义在于同时改变四路输出。写代码时先调用四次上面的函数把数据送进输入锁存器最后一口气拉低LDACvoid DAC_UpdateAll(unsigned int dac0, unsigned int dac1, unsigned int dac2, unsigned int dac3) { DAC_WriteChannel(0, dac0); DAC_WriteChannel(1, dac1); DAC_WriteChannel(2, dac2); DAC_WriteChannel(3, dac3); DAC_LDAC_LOW; asm( NOP); asm( NOP); DAC_LDAC_HIGH; }这里有一个需要注意的细节DAC_WriteChannel内部已经拉高了LDAC所以四通道写入之间输出不会变化等DAC_LDAC_LOW同步更新。如果你的应用需要每个通道立刻反映码值那就在写完单通道后单独拉低一下LDAC。两种用法各有场景伺服同步输出用DAC_UpdateAll普通调试单通道用前者。4. 输出精度、双极性校准和PCB抗噪的3个关键点代码跑通只是第一步DAC输出值准不准、纹波大不大往往决定这块板子能不能用在控制环里。4.1 基准电压和12位码值到输出电压的换算DAC7724的输出电压由基准电压VREF决定。在单极性模式下输出范围是0到2倍VREF计算公式是Vout (N / 4096) × 2 × VREF这里的N是写入的12位码值。若VREF2.5V那么输出范围约0到5V。1个LSB对应1 LSB (2 × VREF) / 4096 5 / 4096 ≈ 1.22mV双极性输出则常用外部运放把0~5V偏置到±2.5VVout ((N / 4096) × 2 × VREF) - VREF比如N0时输出-2.5VN2048时输出0VN4095时接近2.5V。这个换算关系要提前做成表格或者宏定义否则PID算法里一旦用百分比表示期望电压很容易把码值和实际电压算反。注意DAC7724的零点和满度都有微小失调批量板子之间会有几个LSB偏差要求高时需要在后台做一次出厂校准把校准系数存到EEPROM或Flash里。4.2 双极性偏置电路的接法DAC7724本身不直接输出负电压需要外接一个运放做偏移。常见做法是在DAC输出端加一个跟随器隔离再与一个-2.5V基准通过加法器合成增益为1。调零时给DAC写入0x800调节电位器使输出为0V满度校准时写0xFFF调整增益电阻使输出为接近2.5V。这里要注意运放的输入失调电压和温漂。如果选LM358这类通用运放零位可能会漂几十毫伏对12位输出来说不可接受。建议使用高精度低失调的运放例如OPA2188或OP07等级。另外一个隐含坑是DAC7724的输出放大器是轨到轨吗不是它靠近电源轨时有饱和区0码值和满码值附近的线性度会变差所以设计双极性电路时给输出留出5%的裕量不要满量程硬怼到电源电压。4.3 时钟抖动与电源噪声对DAC输出影响的3个PCB布局要点“时钟抖动与电源噪声”是并行DAC的老话题。DSP28335的GPIO翻转会产生开关电流布局不合理时噪声会耦合到DAC参考源和输出端。要点1在DAC7724的每个电源引脚就近放0.1uF陶瓷电容和10uF钽电容。AVDD和VREF必须分开走线模拟电源从LDO单独出来后先到10uF再到0.1uF然后才进DAC引脚。数字电源和模拟电源不要共用一个过孔否则高频开关电流会直接串入模拟电源。要点2保持VREF走线下面有完整的地平面不要在基准线下面走数字GPIO线。GPIO数据线在靠近28335引脚或靠近DAC引脚处加22欧姆到33欧姆串联电阻抑制信号振铃。这个电阻值不是随便定的它匹配了信号源阻抗和走线特征阻抗能把上升沿过冲减小三分之一以上。要点3DSP的时钟源或XCLKOUT不要贴着DAC7724的VREF引脚。很多工程师为了布局方便把晶振放在模拟区结果VREF上出现几十毫伏的毛刺。把晶振和数字开关器件移到板边模拟区走线尽量加宽并远离时钟线。这些措施都是在输出精度上真正起决定性的东西比软件多写几行滤波重要得多。5. 验证DAC7724输出波形的一个实用技巧5.1 用斜坡码值配合示波器观察单调性先用一个简单的斜坡输出函数从0递增到4095每步做一个很小的延时单通道输出void DAC_RampTest(void) { volatile unsigned int i, j; for(i 0; i 4096; i) { DAC_WriteChannel(0, i); for(j 0; j 1000; j); } }用示波器直流耦合看输出应该看到整齐的阶梯波。如果出现某一步跳变或者台阶方向反了多半是数据线接反了用GPIO映射表逐根核对如果波形是纯离散的乱跳先查GPADIR方向寄存器是不是被意外改掉了。5.2 用定时器中断实现等间隔DAC输出很多波形生成要求固定采样率靠软件延时并不精确。实用技巧是使用28335的CPU定时器0产生周期中断在中断服务函数里取出下一个码值写入DAC。定时器周期值这样算周期值 主频 / 采样频率例如150MHz主频希望10kHz采样率周期就是15000。interrupt void CpuTimer0_ISR(void) { static unsigned int idx 0; // 查表输出16点正弦波表长可以自行扩展 DAC_WriteChannel(0, sinTable[idx]); idx (idx 1) % 16; PieCtrlRegs.PIEACK.all 0xFFFF; }这里要注意中断服务函数里不做浮点数学运算正弦表提前计算好。中断触发的同步性比主循环好很多抖动可以控制在微秒量级。验证多通道同步时可以用一个空闲GPIO在DAC_UpdateAll前翻转用双通道示波器同时看两路输出确认LDAC同步动作是否一致。本文还有配套的精品资源点击获取