公司动态

DSP软件模拟SPI驱动AD5724 DAC实现精密电压输出

📅 2026/8/27 6:01:30
DSP软件模拟SPI驱动AD5724 DAC实现精密电压输出
1. 项目概述从数字到模拟的桥梁搭建在嵌入式DSP开发中我们常常需要将DSP芯片内部运算得到的数字结果转换为真实的、可驱动外部设备的模拟信号比如控制一个电机的转速、调整一个音频放大器的音量或者像这个实验一样精确地输出一个电压值。这个过程的核心就是数模转换DAC。本次实验“2-20 数模转换实验模拟SPI总线输出电压值”正是聚焦于如何使用DSP通过软件模拟SPI总线的方式去控制一颗外部的DAC芯片AD5724从而实现程序可控的电压输出。这不仅仅是调用一个库函数那么简单它涉及到底层通信协议的深刻理解、硬件时序的精确把控以及嵌入式系统中软硬件协同的典型工作流。对于嵌入式学习者尤其是DSP方向的新手这个实验具有极高的教学价值。它跳过了集成DAC或硬件SPI外设的“黑箱”操作迫使你去理解SPI通信的每一位Bit是如何在GPIO引脚上“跳动”的去计算命令字和数据的组成去关注建立时间和保持时间。通过亲手用代码“搭建”出SPI时序你对通信协议的理解会从概念层面深入到硬件电气层面。而AD5724作为一款典型的12位、四通道电压输出DAC其内部寄存器结构、控制命令格式又为你理解更复杂的芯片数据手册和驱动编写提供了绝佳的范本。可以说完成这个实验你收获的不仅是如何输出一个电压更是一套应对未来各种外设芯片的通用方法论。2. 实验核心思路与硬件平台解析2.1 实验目标与核心器件选型逻辑本实验的终极目标是通过DSP的通用输入输出GPIO引脚模拟出SPI总线的时序向AD5724 DAC芯片写入特定的数字量使其在指定通道输出一个对应的、稳定的模拟电压。为什么选择AD5724这背后有几层考量。首先教学友好性。AD5724是一款12位分辨率的DAC这意味着它的输出可以有40962^12个不同的电压等级精度足够用于大多数教学演示同时又不会因为位数太高如16位、24位而让数据格式过于复杂。其次接口典型性。它采用标准的SPI兼容串行接口这是嵌入式领域最主流、最基础的通信协议之一掌握其驱动具有广泛的迁移价值。再者功能集成度。它内部集成了输出放大器可以直接驱动一定的负载简化了外围电路。最后多通道特性。其四通道输出允许我们在一个实验中探索多路信号控制增加了实验的扩展性和趣味性。选择它能在一个实验中覆盖DAC核心原理、SPI协议和芯片驱动等多个知识点。2.2 硬件连接原理与信号定义要让DSP和AD5724对话我们必须先为它们搭建正确的“物理通道”。SPI是一种全双工、同步的串行通信总线通常需要四根线标准模式。但在AD5724的写入操作中我们主要关注三根线因为AD5724作为从设备其数据输出SDO线在本实验中仅写入数据可以悬空或不接。SPI时钟SCLK由DSP主设备产生用于同步数据位传输的节拍。每个时钟周期传输一位数据。时钟的极性CPOL和相位CPHA共同定义了SPI的模式这是正确通信的基石。AD5724支持模式1CPOL0 CPHA1和模式3CPOL1 CPHA1本实验通常采用模式1。主出从入MOSI数据从DSP主设备输出输入到AD5724从设备。我们要发送的命令和数据就是通过这根线一位一位地传过去的。片选SYNC在AD5724上这个引脚常被命名为SYNC或CS。当它为低电平时AD5724被“选中”开始监听SCLK和MOSI线上的信号当它为高电平时AD5724忽略通信。任何一次完整的通信帧都必须以拉低SYNC开始以拉高SYNC结束。除了这三根通信线还必须确保DSP和AD5724共享同一个“地”GND为信号提供参考电位。同时AD5724需要稳定的供电如5V或15V取决于型号和所需输出范围。硬件连接示意图虽简单但务必在实验前用万用表确认连通性避免因接触不良导致调试陷入困境。注意不同型号的DSP实验箱其GPIO引脚编号和物理接口可能不同。务必根据你的实验箱原理图确认DSP上用于模拟SCLK、MOSI、SYNC的具体引脚并在代码中进行正确映射。这是实验成功的第一步也是最容易出错的一步。2.3 软件模拟SPI vs 硬件SPI外设为什么我们要“模拟”SPI直接使用DSP片内的硬件SPI外设不是更方便吗这个问题触及了嵌入式学习的深度。使用硬件SPI外设确实简单通常只需配置几个寄存器填充数据缓冲区中断或DMA会自动完成发送。但这种方式像在开自动挡汽车你只知道踩油门和刹车对发动机SPI时序如何工作知之甚少。软件模拟SPIBit-Banging则是手动控制GPIO引脚的高低电平变化来“拼凑”出SPI时序。你需要精确控制SYNC拉低后延迟多久产生第一个SCLK边沿在每个SCLK边沿的前后MOSI上的数据必须保持稳定满足建立时间和保持时间发送完所有位后再延迟多久拉高SYNC。这个过程让你对SPI协议的时序参数如时钟频率、数据有效性窗口有了肌肉记忆般的理解。在资源受限的单片机中当硬件SPI外设被占用或引脚冲突时软件模拟是唯一的解决方案。此外模拟SPI可以轻松适配任何非标准的或时序特殊的串行协议。因此掌握软件模拟SPI是一项非常底层且实用的技能。本实验采用此法目的就是“授人以渔”让你透彻理解协议本质。3. AD5724芯片驱动深度解析3.1 内部寄存器结构与命令字格式AD5724并非一个简单的“数字输入-电压输出”的转换器。它是一个可编程的智能器件内部有一组寄存器来控制其行为。我们通过SPI发送的正是一个个“命令字数据”的帧来读写这些寄存器。主要需要关注的寄存器有DAC数据寄存器x这是核心。我们想要输出的数字量0x000到0xFFF就是写入对应通道A, B, C, D的这个寄存器。写入后DAC并不会立即更新输出除非你发出更新命令。DAC控制寄存器功能强大包括输出范围选择可以配置为0~5V 0~10V ±5V ±10V等多种范围。这决定了你写入的数字量0xFFF对应多大的输出电压。输出使能/关闭可以单独关闭某个通道的输出以节能或进入已知的安全状态如输出0V或高阻。内部参考电压使能如果使用芯片内部参考源需要在此开启。清空码寄存器可以设置当CLR引脚被激活时所有DAC输出被置为何种值如零刻度、中间刻度等。SPI通信帧的格式通常是24位或32位。以一次写操作为例帧结构可能如下具体需查阅AD5724数据手册[命令位C2:C0] [地址位A3:A0] [数据位DB15:DB0]命令位指明是写寄存器还是读寄存器。例如010可能表示“写数据到DAC寄存器并更新输出”。地址位指明要操作的是哪个通道的寄存器A, B, C, D或哪个控制寄存器。数据位就是要写入的具体16位数据。对于12位DAC数据通常放在低12位DB11:DB0高4位补零或忽略。理解这个帧格式是编写驱动代码的关键。你必须根据数据手册准确地构造出这个24位或32位的整数然后通过SPI一位一位地发送出去。3.2 输出电压计算与量程配置这是将数字量转化为物理量的核心公式。假设我们选择0~10V的单极性输出范围。分辨率 满量程电压 / (2^12) 10V / 4096 ≈ 0.00244 V/步进 (2.44 mV)。如果我们要输出3.000V那么需要写入的数字量D (期望电压 / 满量程电压) * (2^12) (3.0 / 10.0) * 4096 0.3 * 4096 1228.8。数字量必须是整数所以我们需要取整D 1229 (0x4CD)。写入0x4CD后实际输出电压 (1229 / 4096) * 10V ≈ 3.0002V误差在分辨率之内。如果配置为±5V的双极性范围计算略有不同。此时0x000对应-5V0x800对应0V0xFFF对应5V减去1个LSB。计算公式为 输出电压 (D / 4096) * 10V - 5V。 要输出2.5V则 D ((2.5 5) / 10) * 4096 0.75 * 4096 3072 (0xC00)。量程配置通常通过向DAC控制寄存器写入特定的位域来完成。务必在初始化AD5724时根据实验箱的硬件设计如运放供电电压和实验要求正确配置此范围。配置错误可能导致输出饱和始终为最高或最低电压或损坏外部电路。4. 软件模拟SPI驱动实现详解4.1 GPIO引脚初始化与宏定义在开始模拟时序之前我们需要将用到的三个GPIO引脚配置为正确的方向并设置初始状态。// 假设使用DSP的GPIO引脚具体端口和引脚号需根据实验箱原理图修改 #define DAC_SYNC_PIN (GpioDataRegs.GPBDAT.bit.GPIO34) // SYNC/CS 引脚 #define DAC_SCLK_PIN (GpioDataRegs.GPBDAT.bit.GPIO35) // SCLK 引脚 #define DAC_MOSI_PIN (GpioDataRegs.GPBDAT.bit.GPIO36) // MOSI 引脚 // 初始化函数 void DAC_SPI_Init(void) { EALLOW; // 解除寄存器保护 // 配置SYNC, SCLK, MOSI 引脚为通用输出 GPIO GpioCtrlRegs.GPBMUX1.bit.GPIO34 0; // 功能选择 GPIO GpioCtrlRegs.GPBDIR.bit.GPIO34 1; // 方向 输出 GpioCtrlRegs.GPBPUD.bit.GPIO34 0; // 使能上拉可选 GpioCtrlRegs.GPBMUX1.bit.GPIO35 0; GpioCtrlRegs.GPBDIR.bit.GPIO35 1; GpioCtrlRegs.GPBPUD.bit.GPIO35 0; GpioCtrlRegs.GPBMUX1.bit.GPIO36 0; GpioCtrlRegs.GPBDIR.bit.GPIO36 1; GpioCtrlRegs.GPBPUD.bit.GPIO36 0; EDIS; // 恢复寄存器保护 // 设置初始状态 SYNC高不选中 SCLK低模式1 CPOL0 MOSI任意 DAC_SYNC_PIN 1; DAC_SCLK_PIN 0; DAC_MOSI_PIN 0; }这里的关键是理解DSP的GPIO控制寄存器组。通常需要操作功能复用寄存器MUX、方向寄存器DIR和上拉/下拉寄存器PUD。EALLOW和EDIS是C2000系列DSP特有的指令用于允许/禁止对受保护的寄存器进行写操作防止误修改。4.2 SPI时序模拟函数编写模式1我们将编写一个通用的SPI_WriteWord函数用于发送一个16位或24位的数据。这里以发送24位数据适用于AD5724的典型帧长为例采用SPI模式1CPOL0 CPHA1。/** * brief 模拟SPI模式1发送24位数据 * param data: 要发送的24位数据存储在32位变量的低24位 * retval 无 */ void SPI_WriteWord(uint32_t data) { uint8_t i; // 1. 启动传输拉低SYNC选中芯片 DAC_SYNC_PIN 0; // 可插入一个微小延时确保SYNC有效建立 DELAY_US(1); // 2. 循环发送24位数据高位MSB先出 for(i 24; i 0; i--) { // 模式1 CPHA1在时钟上升沿采样数据。 // 因此在时钟上升沿到来之前数据必须稳定建立时间。 // 先设置数据位 if(data (1UL (i - 1))) { // 从最高位开始判断 DAC_MOSI_PIN 1; } else { DAC_MOSI_PIN 0; } // 等待数据稳定建立时间 DELAY_US(1); // 延时时间需根据SCLK频率调整 // 产生时钟上升沿从设备在此刻采样MOSI DAC_SCLK_PIN 1; DELAY_US(1); // 保持高电平时间 // 产生时钟下降沿准备下一次数据切换 DAC_SCLK_PIN 0; // 数据可以在下降沿后改变保持时间已过 } // 3. 结束传输拉高SYNC释放芯片 // 确保最后一个时钟下降沿后数据已稳定 DELAY_US(1); DAC_SYNC_PIN 1; // 可选延时满足SYNC无效到下次有效的最小时间要求 DELAY_US(1); }代码逻辑与时序要点解析MSB Firstfor(i 24; i 0; i--)和(1UL (i - 1))确保了从最高位第24位开始发送。这是SPI通信和AD5724的常见要求务必确认数据手册。建立时间Setup Time与保持时间Hold Time在DAC_SCLK_PIN 1上升沿之前我们设置了数据并DELAY_US(1)这模拟了数据建立时间。在时钟变低后数据可以变化这模拟了保持时间。这两个延时是模拟SPI稳定性的关键其具体值需要根据你期望的SCLK频率和GPIO翻转速度来调整。频率越低延时可以越长通信越稳定频率越高延时需越精确接近GPIO极限。DELAY_US函数你需要一个微秒级的延时函数。在DSP上通常通过读取高精度定时器或使用空循环来实现。例如#define CPU_FREQ_MHZ 150.0 // 假设DSP主频150MHz #define DELAY_US(us) DSP28x_usDelay((((long double)us * 1000.0L) / (long double)CPU_FREQ_MHZ) - 10.0L) // 或者使用更简单的空循环需校准 void delay_us(uint32_t us) { uint32_t loops us * (CPU_FREQ_MHZ / 5); // 粗略估算需实际测试校准 while(loops--); }延时校准是调试模拟SPI的重中之重。不准确的延时会导致SCLK频率偏差或建立/保持时间不足通信失败。4.3 AD5724初始化与数据写入函数封装有了底层的SPI_WriteWord函数我们就可以封装针对AD5724的高级操作函数了。// AD5724命令定义示例请根据实际数据手册调整 #define CMD_WRITE_UPDATE_DAC 0x02 // 写入DAC寄存器并更新输出 #define ADDR_DAC_A 0x00 // DAC A通道地址 #define ADDR_DAC_B 0x01 // DAC B通道地址 // ... 其他通道和控制寄存器地址 #define RANGE_0_10V 0x01 // 控制寄存器中对应0-10V范围的位域值 /** * brief 初始化AD5724设置输出范围等 */ void AD5724_Init(void) { uint32_t cmd_data; // 1. 初始化SPI GPIO DAC_SPI_Init(); // 2. 软件复位如果芯片支持或写入默认控制寄存器 // 例如配置所有通道为0-10V范围输出使能 cmd_data (CMD_WRITE_CTRL_REG 19) | (RANGE_0_10V 4); // 构造24位命令字 SPI_WriteWord(cmd_data); // 延时等待DAC上电稳定 DELAY_US(100); } /** * brief 设置指定DAC通道的输出电压 * param ch: 通道号 (0:A, 1:B, 2:C, 3:D) * param value: 12位数字量 (0-0xFFF) */ void AD5724_SetVoltage(uint8_t ch, uint16_t value) { uint32_t cmd_data; uint8_t dac_addr; if(ch 0) dac_addr ADDR_DAC_A; else if(ch 1) dac_addr ADDR_DAC_B; // ... 其他通道判断 // 构造24位帧命令 地址 数据 // 假设帧格式[命令3位][地址4位][数据12位][无关位5位] cmd_data (CMD_WRITE_UPDATE_DAC 21) | (dac_addr 17) | ((uint32_t)value 5); SPI_WriteWord(cmd_data); }在AD5724_SetVoltage函数中最关键的一步是正确构造cmd_data。你需要根据数据手册第7页“串行接口”章节的帧格式图精确地移位和组合命令、地址和数据位。一个常见的错误是位对齐错误导致写入错误的寄存器或数据。建议将构造好的cmd_data值用调试器或通过串口打印出来与手册示例对比验证。5. 实验步骤与现象观测5.1 实验箱硬件连接确认在给实验箱上电前请务必进行以下检查电源用万用表测量AD5724的供电引脚VDD, VSS等确保电压在数据手册规定的范围内如5V, 15V且极性正确。地线确认DSP的GND和AD5724的GND已用导线可靠连接。这是所有信号参考的基础。信号线对照原理图确认SCLK、MOSI、SYNC三根线连接正确。最好使用不同颜色的导线以便区分。输出端将AD5724的电压输出引脚如VOUTA连接到实验箱的测量孔或示波器探头。5.2 软件工程创建与代码集成以TI的Code Composer Studio (CCS)为例新建工程选择对应的DSP型号如TMS320F28335。导入基础库将必要的DSP头文件、库文件和CMD链接命令文件添加到工程。编写主程序#include DSP28x_Project.h #include ad5724_driver.h // 将我们编写的驱动函数头文件包含进来 void main(void) { // 1. 系统初始化 InitSysCtrl(); // 初始化系统控制时钟看门狗 InitGpio(); // 初始化GPIO可能会覆盖我们的设置注意顺序或修改 DINT; // 关全局中断 InitPieCtrl(); // 初始化PIE控制 IER 0x0000; // 禁止CPU中断 IFR 0x0000; // 清除CPU中断标志 InitPieVectTable(); // 初始化PIE向量表 // 2. 初始化AD5724 AD5724_Init(); // 3. 主循环控制电压输出 uint16_t dac_value 0; while(1) { // 示例让DAC A通道输出一个从0V到满量程的锯齿波 AD5724_SetVoltage(0, dac_value); // 通道A输出 dac_value 16; // 每次增加约 16/4096 * 10V ≈ 0.039V if(dac_value 4096) { dac_value 0; } // 延时控制锯齿波频率 DELAY_US(1000); // 延时1ms周期约为4096*1ms4.1s } }编译与下载确保无错误后将程序下载到DSP实验箱。5.3 调试与电压测量上电与观测给实验箱上电。使用数字万用表的直流电压档测量AD5724输出引脚如VOUTA对地的电压。静态测试在main函数中先尝试固定输出一个值例如AD5724_SetVoltage(0, 2048)。理论上在0-10V量程下应输出约5.0V。用万用表测量看是否接近。如果偏差巨大或为0进入调试。动态测试运行锯齿波程序。用示波器连接到输出引脚你应该能看到一个上升的阶梯波形由于DAC值离散变化。调整main循环中的延时可以改变锯齿波的频率。多通道测试修改程序让通道A和通道B输出不同电压或波形用双通道示波器观察验证多通道独立控制能力。6. 常见问题排查与调试心得6.1 典型问题速查表现象可能原因排查步骤与解决方案无输出电压或电压为01. 电源未接通或错误。2. SYNC引脚始终为高芯片未选中。3. SPI时序完全错误芯片未收到任何有效指令。4. 输出被禁用控制寄存器配置错误。1. 测电源引脚电压。2. 用示波器看SYNC引脚是否有低电平脉冲。3. 用示波器同时抓取SYNC、SCLK、MOSI三线时序与数据手册对比。这是最有效的调试手段。4. 检查初始化代码中控制寄存器的写入值。输出电压固定为最大值或最小值1. 数据位顺序错误LSB先出 vs MSB先出。2. 帧格式构造错误写入了错误的寄存器如写到了清空寄存器。3. 参考电压有问题。1. 确认SPI_WriteWord函数发送顺序与AD5724要求一致通常是MSB先出。2. 仔细核对数据手册帧格式打印或调试查看构造出的cmd_data值。3. 测量参考电压引脚如REFIN电压是否正常。输出电压值不准确、跳动1. 电源噪声或地线干扰大。2. SPI时钟频率过快GPIO延时不足导致时序边际不够。3. DAC数字电源DVDD和模拟电源AVDD未隔离或滤波不良。1. 在电源引脚就近增加去耦电容如100nF和10uF并联。2. 增加SPI_WriteWord函数中的DELAY_US值降低通信频率。3. 检查PCB布局和电源滤波电路。只能输出个别特定电压值写入的数据被部分篡改可能是某一位或几位数据线/时钟线受到干扰或软件移位计算有误。1. 用示波器放大观察MOSI波形看每个位的数据是否与预期一致。2. 单步调试检查AD5724_SetVoltage函数中value参数和最终cmd_data的计算过程。代码运行一次后DSP无响应1. 看门狗未禁用或未定期喂狗导致复位。2. 堆栈溢出。3. 访问了非法内存地址。1. 在InitSysCtrl()后禁用看门狗DisableDog()。2. 在CMD文件中增大堆栈段.stack大小。3. 检查数组越界、指针错误。6.2 示波器调试技巧实录当通信不正常时示波器是你的“眼睛”。如何高效使用多通道同步触发将示波器的三个通道分别连接到SYNC、SCLK和MOSI。设置触发模式为下降沿触发触发源设为SYNC通道触发电平设为SYNC高电平的中间值如2.5V。这样每次DSP拉低SYNC开始传输时示波器就会自动捕获并稳定显示一整帧波形。观察关键时序捕获到波形后放大观察SYNC拉低后到第一个SCLK边沿的时间即建立时间。MOSI数据变化点与SCLK上升沿的关系。在模式1下MOSI数据应在SCLK上升沿之前保持稳定。数据位宽度测量两个SCLK上升沿之间的时间这就是你的SCLK周期其倒数即通信频率。检查是否超出AD5724的最大SPI时钟频率。帧长度数一数SYNC低电平期间有多少个SCLK脉冲。应该是24个或32个一个不能多一个不能少。解码SPI数据许多现代示波器带有SPI协议解码功能。在设置好通道对应关系哪个是CLK哪个是MOSI哪个是CS和阈值电压后开启解码。示波器会自动将波形翻译成十六进制或二进制数据你可以直接看到发送的cmd_data值是否与你的代码预期一致这是终极验证手段。6.3 软件模拟SPI的延时校准心得软件延时DELAY_US的准确性直接决定了SPI时序的稳定性和最高通信速率。在无操作系统的裸机环境下我常用的校准方法是编写一个测试函数让一个GPIO引脚连续产生一个已知周期的方波例如拉高-延时10us-拉低-延时10us-循环。用示波器测量这个方波的实际周期。假设你期望是20us但实测是22us。计算修正因子修正因子 期望延时 / 实际延时 20us / 22us ≈ 0.909。应用到你的DELAY_US函数中在计算延时循环次数时乘以这个修正因子。或者更简单粗暴的方法是如果实测周期偏大说明延时函数慢了就减少循环次数反之则增加。通过几次迭代可以将误差控制在几个百分点以内。对于C2000 DSPTI提供了经过优化的DSP28x_usDelay函数它使用汇编循环精度很高。但前提是你需要正确配置和传入基于系统时钟的循环次数。使用它通常比自己写的空循环更可靠。最后一个重要的经验是在项目初期尽量使用较低的SPI时钟频率如100kHz以下。这能给你宽松的时序容限大大提高通信成功率。等整个驱动调试稳定后再尝试逐步减小延时提高频率并观察波形和输出电压是否依然稳定。