ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32驱动AD7708实现多路模拟量采集:SPI接口高精度ADC应用指南

STM32驱动AD7708实现多路模拟量采集:SPI接口高精度ADC应用指南 简介一套STM32驱动AD7708的代码例程面向需要接入16位Σ-Δ型模数转换器的嵌入式开发者解决高精度模拟量采集时的通信与数据转换问题。AD7708支持单端/差分输入、内置PGA与数字滤波例程覆盖初始化、寄存器配置、SPI读写、数据转换及错误检查可直接集成到实际电路中。压缩包共3个文件含2个头文件与1个C源文件整体仅5KB头文件分别负责接口声明和GPIO、时钟等硬件相关配置C文件实现驱动逻辑及转换处理便于移植到不同STM32型号。使用时修改硬件抽象层引脚定义调用初始化与读取函数即可得到转换结果代码还提供状态检查与中断处理逻辑便于排查通信错误并保证转换稳定。整体结构清晰适合传感器接口、工业控制、医疗设备等场景的快速验证。已有874人学习/下载是轻量完整的参考实现。 做过多路模拟量采集的朋友应该都有体会MCU内置ADC在低速、高精度、多通道场景下真的不够用尤其是传感器输出是毫伏级信号时内置12位ADC加上参考电压的噪声直接把有效分辨率拉到个位数。我前阵子做一块8路压力采集板选型时翻到了AD7708这颗芯片SPI接口、16位分辨率、自带可编程增益放大看起来完全对症。但真正动手写STM32驱动代码例程时才发现网上中文资料少得可怜只能对着英文数据手册一点点啃。这篇文章就是我把驱动调通之后的完整记录从寄存器协议到初始化顺序从24位数据拼接再到实测踩过的坑一次性讲清楚。1. 为什么要用AD7708而不是直接怼MCU内置ADC先说说选型逻辑。如果你只是在做玩具级项目ADC读数抖个几十LSB可能无所谓但工业采集板上一个0~5V的压力变送器输出换算到物理量可能1mV就对应几Pa的误差这时候内置ADC的劣势就非常明显了。首先是分辨率。STM32F103内置ADC是12位理论上一共4096个码值配合3.3V参考电压每个码值对应的电压是0.8mV左右。看着还行但实际测量中参考电压本身也有波动加上输入通道切换时采样电容的充放电末位两三个码基本全是噪声实际有效位数往往只有10位上下。AD7708是16位Σ-Δ型ADC配合内部数字滤波器在低速率下能稳稳跑出14到15个有效位这中间差了两个数量级。其次是通道数量和信号调理。AD7708最多支持8路伪差分输入也可以配置成4路全差分关键是每个通道前面都带可编程增益放大器增益范围能做到1到128倍。这意味着应变片、热电偶这类毫伏级信号可以直接怼到ADC输入端省掉一级外部仪表放大器BOM成本能压下来不少。再就是抗混叠和滤波。Σ-Δ架构的ADC内部有数字低通滤波器可以通过寄存器配置滤波字把输出数据速率压到几赫兹到几十赫兹对工频干扰有很强的抑制能力。我在这块采集板上测过滤波字配大之后50Hz工频串扰基本被滤干净读数的峰峰值跳动从几十个码降到两三个码。总结一句话AD7708本质上是一个带数字滤波的多路精密测量前端MCU只负责通过SPI配置它、读它的结果所有模拟域的麻烦事都由这颗芯片包了。2. 硬件连接引脚不多但参考电压和RDY引脚不能偷懒AD7708和STM32之间的接口非常简洁本质就是一个SPI从机加两个控制引脚。我这里用的是STM32F103C8T6的SPI1引脚分配如下STM32引脚AD7708引脚功能说明PA4CSSPI片选低有效PA5SCLKSPI时钟PA6DOUT/MISOAD7708数据输出PA7DIN/MOSIAD7708数据输入PA3RDY转换完成标志低有效3.3VAVDD、DVDD数字和模拟电源GNDGND公共地电源部分我直接把AVDD和DVDD都接在3.3V上每个电源引脚旁边放了一颗0.1uF陶瓷电容加一颗10uF钽电容。注意去耦电容要尽量靠近芯片引脚这是模拟电路的基本功摆远了等于没放。这里要重点说两个容易被忽略的引脚。第一个是参考电压。AD7708支持内部参考但内部基准的温漂和噪声都不如外部基准既然项目是奔着高精度去的就别省这颗料。我用的是ADR421的2.5V基准输出直接接REFINREFIN-接模拟地。基准芯片的输出端同样要加0.1uF和10uF去耦否则基准噪声会直接耦合进ADC结果里。第二个是RDY引脚。AD7708转换完成后会把RDY拉低这个引脚在芯片内部是开漏输出所以必须接一个10kΩ左右的上拉电阻到3.3V然后连接到STM32的GPIO。我在代码里选择查询方式读取如果你对实时性要求高可以把这个引脚接到STM32的外部中断线上RDY下降沿触发一次数据读取这样MCU不用一直空转等待。还有一个接地细节。STM32数字部分和AD7708模拟部分虽然共用一个3.3V电源但PCB布局上要把模拟地和数字地做单点连接不要让数字逻辑的开关电流串进模拟地回路。我第一次画板子时图省事直接大面积铺铜共地结果数据显示固定的周期性跳变后来改成单点接地才恢复正常。3. 先把寄存器协议理顺所有读写都先过通信寄存器这一关AD7708的寄存器操作方式和普通SPI器件不太一样。它内部有多个寄存器但你无法直接通过地址去选中它们每次CS拉低后的第一个字节必须写通信寄存器告诉芯片你接下来要操作哪个寄存器、是读还是写、操作哪个通道。通信寄存器本身是8位的位分配大概是这样位含义Bit7读写方向1为读0为写Bit5-3目标寄存器地址共8个Bit2-0通道选择0~7对应AIN1~AIN8寄存器映射表我整理了一份常用的地址寄存器说明000通信寄存器每次操作的第一字节001状态寄存器RDY状态、校准状态010ADC控制寄存器模式、增益、极性、缓冲011I/O控制寄存器通用IO配置100滤波寄存器配置数字滤波器101数据寄存器24位转换结果为什么要这样设计我个人的理解是ADI把寻址信息全部收进了一个统一入口这样芯片引脚的复用效率更高但代价就是代码里封装一个生成通信命令的函数非常有必要。我自己在工程里是这么写的// 生成通信寄存器命令 // rw: 1读, 0写 // reg: 寄存器地址 // ch: 通道号 uint8_t ad7708_comm_cmd(uint8_t rw, uint8_t reg, uint8_t ch) { return (rw 7) | ((reg 0x07) 3) | (ch 0x07); }底层SPI字节收发函数也不复杂硬件SPI1模式8位数据MSB先行static uint8_t ad7708_spi_byte(uint8_t dat) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, dat); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); return SPI_I2S_ReceiveData(SPI1); }有了这两个基础函数读写任意寄存器就变得很统一了void ad7708_write_reg(uint8_t reg, uint8_t ch, uint8_t val) { ad7708_cs_low(); ad7708_spi_byte(ad7708_comm_cmd(AD7708_RW_WRITE, reg, ch)); ad7708_spi_byte(val); ad7708_cs_high(); } uint8_t ad7708_read_reg(uint8_t reg, uint8_t ch) { uint8_t val; ad7708_cs_low(); ad7708_spi_byte(ad7708_comm_cmd(AD7708_RW_READ, reg, ch)); val ad7708_spi_byte(0xFF); ad7708_cs_high(); return val; }这里有个细节要提醒CS拉低之后芯片会严格按照字节流来解析所以第一次命令字节之后的每一个字节都会被当成当前选中寄存器的内容去处理。如果中途CS没有正确拉高协议状态机就会错乱后续读数全不对。我调试时遇到过几次这种情况最后都是靠逻辑分析仪抓波形才发现是CS时序毛刺导致的。4. 初始化流程配置命令的先后顺序比你想的更讲究AD7708上电之后的初始化顺序直接影响校准结果我第一次直接把滤波器和增益一次性写完就读数结果数据偏移很大后来查手册才明白是漏了自校准这一步。我的标准初始化流程分四步。第一步上电稳定等待。AD7708上电后内部电路需要时间稳定我给了一个100ms的延时这个时间完全够用。第二步写ADC控制寄存器把芯片设成正常模式、增益1、双极性、使能输入缓冲。每一类传感器的增益需求不一样比如应变片输出只有几毫伏增益就要配大一些但这个配置在正常模式下需要提前设定不能在校准过程中随意切换。第三步写滤波寄存器。滤波字决定数字滤波器的时间常数对应输出数据速率。滤波字越大输出速率越低噪声越低。我实际工程里用的滤波字是0x0A输出速率大概几十赫兹对压力信号来说够用读数非常稳。第四步启动内部自校准。AD7708会在当前增益和极性配置下自动完成失调和增益误差的修正校准完成后RDY引脚会拉低。这一步非常关键特别是增益大于1的时候如果不校准放大器的失调会被跟着放大直接反映到输出码值上。对应的初始化代码是这样void ad7708_init(void) { delay_ms(100); // 正常模式增益1、双极性、输入缓冲开启 ad7708_write_reg(AD7708_REG_ADCCTRL, AD7708_CH0, AD7708_ADC_NORMAL); // 滤波字设为 0x0A压低输出速率换取低噪声 ad7708_write_reg(AD7708_REG_FILTER, AD7708_CH0, 0x0A); // 启动自校准 ad7708_write_reg(AD7708_REG_ADCCTRL, AD7708_CH0, AD7708_ADC_SELFCAL); // 等待校准完成RDY 拉低表示结束 while (ad7708_rdy_read() ! 0) { } }自校准本质上就是芯片内部把输入短接到内部参考测出一组校正系数存起来之后每次转换都会自动套用。所以校准完成之后不能再随便改动增益和极性配置否则校准系数就失效了。如果你初始化之后发现读数整体偏大或偏小先想想是不是改完配置忘了重新校准。5. 读数据与电压换算24位寄存器里有效数据到底在哪几位AD7708的数据寄存器是24位的但芯片本身是16位ADC它把16位的转换结果左对齐存储在24位寄存器里也就是高16位是有效数据低8位固定为0。这个事实搞清楚之后读数据就不会犯迷糊了。读取时序是先等RDY拉低表示一次转换完成然后CS拉低发送“读数据寄存器”的通信命令接着连续读取3个字节最后CS拉高。代码如下uint32_t ad7708_read_raw(uint8_t ch) { uint32_t raw 0; uint8_t buf[3]; uint8_t i; // 等待一次转换完成 while (ad7708_rdy_read() ! 0) { } ad7708_cs_low(); ad7708_spi_byte(ad7708_comm_cmd(AD7708_RW_READ, AD7708_REG_DATA, ch)); for (i 0; i 3; i) { buf[i] ad7708_spi_byte(0xFF); } ad7708_cs_high(); raw ((uint32_t)buf[0] 16) | ((uint32_t)buf[1] 8) | buf[2]; return raw; }如果你只想读两个字节只取buf[0]和buf[1]其实也行低8位反正是0。我习惯读满3字节再移位这样逻辑统一。电压换算是另一个容易踩坑的地方。我配置的是双极性模式输入范围是-REF到REF参考电压2.5V。16位有符号数的范围是-32768到32767所以每个LSB对应的电压是2.5V除以32768大约是76.3uV。换算函数如下float ad7708_read_voltage(uint8_t ch, float vref) { uint32_t raw ad7708_read_raw(ch); int16_t code (int16_t)(raw 8); // 取高16位按补码解释 return (float)code * vref / 32768.0f; }比如读出来是0x800000右移8位得到0x8000也就是-32768对应电压就是-2.5V读到0x7FFF80右移8位得到0x7FFF对应差不多2.4999V。我实测把一个精密电压源输出2.5V接到输入端读回来的电压是2.4998V误差在1mV以内线性度表现符合预期。如果你把极性配置改成单极性那换算公式也不同16位无符号数范围是0~65535对应0~REF公式变成电压等于code乘vref再除以65536.0f。所以初始化时的极性配置和换算公式必须配套这个一定要在代码注释里写清楚不然过两个月你自己都会忘。6. 实测踩坑记录这些坑不踩一遍真的想不到驱动能跑起来和能稳定跑是两码事。这套代码在实验室和老旧温控设备旁边各跑过一段时间下面这几个问题是我实际遇到并且逐一解决的列出来给大家参考。第一个坑是SPI时钟极性和相位。AD7708数据手册的时序图读起来有点让人迷惑我最初按照网上很多例程配成了SPI Mode 3结果读状态寄存器返回的全是0xFFCS、时钟、数据线都用逻辑分析仪抓过波形看着也对就是数据全是FF。后来把所有可行组合都试了一遍最终发现我这块板子上SPI Mode 0是最稳的也就是CPOL为低、CPHA选择第一个边沿采样。如果你的板子通信一上来就不对先别怀疑代码直接用枚举法把四种SPI模式全部试一遍基本十分钟就能定位。第二个坑是滤波器配置和输出速率的关系。我一开始图快把滤波字配得很小想让输出速率跑到几百赫兹结果发现读数末位一直在跳从几十个码到上百个码都有。后来把滤波字加大到0x0A速率降下来之后噪声肉眼可见地减少。所以AD7708这种Σ-Δ型ADC本质上是用速度换精度你追求高输出速率的时候分辨率一定会打折。第三个坑是RDY引脚的时序。AD7708在转换结束后会把RDY拉低但如果你读数据寄存器读得太频繁它会等下一次转换完成才再次拉低。我在连续采样模式下吃过大亏代码里轮询RDY的时候没注意它是一边转换一边刷新结果读回来的数据时快时慢。最后加了一个简单的时间戳判断两次读数之间至少间隔一个转换周期问题就消失了。第四个坑是接地和参考电压噪声。我前面提到过AD7708对参考电压噪声极其敏感第一次测试时发现码值始终有周期性的波动排查了很久才发现是板子上DC-DC电源的开关纹波通过地回路耦合进了参考电压。后来把基准电源从DC-DC的输出端独立出来换成LDO供电再加强去耦噪声才压下去。AD7708的参考电压引脚最好用示波器看一下纹波纹波超过几个毫伏数据跳变基本无法避免。关于模拟SPI和硬件SPI的区别我也补充一句。AD7708的SPI速率并不高所以GPIO模拟SPI完全可行而且时序可以自己控制得更精确。我最终用的是STM32硬件SPI1因为代码更简洁但如果你在调试中遇到莫名的数据错位不妨先换成GPIO模拟SPI试试这样能排除硬件SPI的极性配置问题。两者代码结构一致切换成本很低。调试AD7708的过程中我还有一个体会不要一上来就盯着数据寄存器读。先用状态寄存器自检确认芯片通信正常、校准正常再去看转换数据。AD7708的状态寄存器里包含RDY位和校准状态位我封装了ad7708_read_reg(AD7708_REG_STATUS, ch)这个函数调试时直接在命令行里打印状态值比反复读数据猜问题高效得多。这套驱动代码我只用了几天就调通了但后面做EMC测试时又暴露了几个硬件问题让我意识到软件之外的部分同样不能忽视。如果你也在用这颗芯片希望这些经验能帮你少走几步弯路。本文还有配套的精品资源点击获取
返回列表