首页 / 资讯中心 / 文章详情

STM32驱动AD7175 24位ADC:寄存器配置、SPI时序与中断采样全解析

STM32驱动AD7175 24位ADC:寄存器配置、SPI时序与中断采样全解析 ★ FEATURED ARTICLE
简介面向嵌入式开发者提供一套完整的STM32平台驱动AD7175高性能十六位Σ-Δ模数转换器的源码工程适用于工业控制与医疗仪表等需要高精度采集的场景可解决串行外设接口通信、寄存器配置、转换模式选择及数据读取等关键问题。压缩包内共有两个源码文件分别是头文件与源文件头文件包含寄存器地址映射、命令字节定义和函数声明源文件则实现芯片初始化、连续转换与单次转换配置、内部基准电压及温度传感器读取等驱动逻辑整体压缩包仅三KB结构精简、便于移植。已有三千九百四十九人学习下载适合需要快速集成AD7175或AD7172驱动或参考串行外设接口时钟配置、数字滤波器参数设置及断码校验思路的开发者。源码可直接复用配合官方数据手册可帮助深刻理解单端或差分输入选择、转换结果解码与底层调试优化等细节显著缩短驱动开发周期。1. AD7175 到底要 STM32 干什么24 位数据怎么变成可用的电压拿到 STM32 驱动 AD7175 源码.rar 这个压缩包的人多半不是来围观而是手里已经有了一块 AD7175 或者准备打样做高精度采集。AD7175 是 ADI 的 24 位 Σ-Δ ADC最大输出速率标称 250kSPS很多做称重、热电偶、电桥测量的工程师都绕不开这颗芯片。难点不在焊电路而在于它和 STM32 之间只有一条 SPI 线但寄存器状态机、滤波器建立时间、校准时机全都要在驱动层处理好。这篇笔记就是按我实际调这块板子的顺序从寄存器视图、硬件接线、可抄的初始化代码一直讲到中断连续采样最后把最容易翻车的地方单独列出来。适合刚拿到代码包准备移植、或者正在排查采集数据乱跳的开发者。2. 从数据手册到工程图驱动 AD7175 必须先吃透的 5 张寄存器视图2.1 通讯寄存器每条 SPI 帧的第一个字节都是它AD7175 的 SPI 操作有个特点每次访问寄存器主机必须先发一个命令字节这个字节决定了后续是读还是写、目标寄存器地址是什么、要不要连续操作。很多第一次写驱动的人上来就想直接拉低片选然后狂发时钟结果读回来的全是 0x00 或者 0xFF问题就出在漏了命令字节。命令字节的最高位是读写标志0 代表写、1 代表读第 6 位是连续读写使能低 6 位是寄存器地址。这个布局在所有 ADI 的 Σ-Δ ADC 里都很相似写驱动时我一般会把它拆成两个底层函数一个写寄存器、一个读寄存器所有上层操作都只传寄存器名和数据指针。注意命令字节里没有 寄存器长度 这个信息长度完全由读写函数的调用方用参数传进去所以驱动代码里到处能看到ad7175_write_reg(reg, buf, len)这种签名不要把长度写死在底层。2.2 状态寄存器与数据寄存器一次 SPI 读回几个字节读数据寄存器是驱动里最常用的操作。AD7175 的数据寄存器是 24 位但很多参考工程在读取时会连状态寄存器一起读也就是一次 SPI 事务里读回 4 个字节第一个字节是状态字后三个字节才是转换结果。状态字里有最高位 RDY 标志低有效低 4 位是当前通道 ID在多通道扫描时靠它确认数据是不是来自期望的通道。我自己的习惯是驱动里保留两套读法一套是只读 3 字节数据适合单通道轮询另一套是读 4 字节适合多通道切换后做校验。代码包里如果只提供一种通常默认是 3 字节版本因为省一次 SPI 时钟在 250kSPS 满速运行时差别还是能感知到的。但如果你发现读回来的数据和通道对不上先检查是不是漏了状态字里的通道 ID 判断。2.3 ADC 配置寄存器通道、编码格式、参考源、极性这个寄存器决定了模拟前端怎么解释 ADC 的结果。主要配置项有选择了哪个输入通道、是双极性还是单极性编码、增益倍数典型为 1 倍、是否使能内部基准源。AD7175 的配置寄存器和通道数是相关的每个通道都有自己的一组配置位初始化时必须先选中目标通道再写它的配置。双极性和单极性之间的换算容易搞混。双极性下满量程输入对应的原始码是 0xFFFFFF 到 0x000000中间零点在 0x800000单极性下 0 对应 0x000000满量程对应 0xFFFFFF。如果你把双极性配置的裸码直接当单极性算电压结果会凭空多出半个量程的偏移。我一般在代码包里会看到两套宏定义ADC_OFFSET 和 ADC_GAIN 都单独存在寄存器里就是用来修正这种映射关系的。2.4 滤波配置寄存器SINC3/SINC5、输出速率与建立时间AD7175 的输出速率不是随便设的它由滤波选择SINC3 或 SINC5和滤波字共同决定。滤波字越大输出速率越低但分辨率越高滤波字越小输出速率越高但噪声也越大。手册里通常会给出一个对照表写驱动时直接按目标速率查表填滤波寄存器的值。这一项是驱动里和 准确性 关系最大的参数因为它还牵扯建立时间。SINC3 滤波器的阶跃建立时间大约是 3 个输出周期SINC5 大约是 5 个输出周期。也就是说你改了输入通道或者刚完成校准后立刻去读第一个数据那个数据大概率是不准的。源码包里如果初始化流程后面紧跟着连续读多次取平均那是在等建立时间如果只读一次就拿来用你要自己加延时。2.5 校准与 ID 寄存器上电后第一件该做的事AD7175 内部有自校准功能分为内部零刻度校准和内部满刻度校准触发方式写在 ADC 模式寄存器里。上电后只做寄存器配置不做校准你读出来的零点可能偏几个 LSB 到几十个 LSB具体取决于芯片个体和温度。我见过不少工程把校准省略掉短时间看不出问题等设备预热半小时后漂移就明显了。ID 寄存器是调试第一利器。驱动写好 SPI 底层、复位完成之后第一件事就是读 ID 寄存器和手册值比对。如果不一致说明 SPI 模式、片选时序、或者供电有问题这时候不要继续往下调数据寄存器先把通信修通。很多源码包里的ad7175_init第一步就是做这件事这不是多余步骤是最省时间的排查手段。3. 硬件先立住接线、供电与 STM32 SPI 参数设置3.1 四线 SPI 怎么接CS、SCLK、DIN、DOUT 的接法细节AD7175 的 SPI 有四根线片选 CS、时钟 SCLK、数据输入 DIN、数据输出 DOUT。很多人把 CS 直接接地想着反正只有一个设备省一个 GPIO。在 AD7175 上这个偷懒会带来隐藏问题软件复位依赖 CS 配合产生时序且上电时芯片需要 CS 拉高完成启动流程所以 CS 必须由 STM32 的普通 GPIO 单独控制。DOUT 这根线要特别说一句它不只是数据输出平时空闲时是被内部上拉电阻拉高的每次转换完成会拉低表示数据就绪。这个特性决定了 DOUT 可以直接接到 STM32 的 EXTI 中断引脚做中断驱动采样时不用额外判断。有些开发板会在 DOUT 上串联电阻再接 LED表面上为了指示实际上会拖慢信号边沿高速采样时可能导致边沿采样到毛刺建议直接 GPIO 接 MCU 引脚。硬件上我建议 SPI 引脚全部使用 STM32 的复用功能不要用模拟 IO 手动翻转模拟 SPI。AD7175 对 SPI 时序要求严格手动翻转的速度慢且抖动大几百 kHz 输出速率下还能勉强跑到 250kSPS 时基本会丢数据。3.2 供电与参考电压AVDD、DVDD、REF 的接线顺序模拟电源 AVDD 和数字电源 DVDD 虽然都可以接 3.3V但不要直接从一个 LDO 输出端分出来就完事。AD7175 是 24 位 ADC电源纹波会直接折合到转换结果里。常见做法是模拟电源用独立 LDO输出端并联 10uF 钽电容和 0.1uF 陶瓷电容数字电源同样并联去耦电容而且两个电源域之间的地要单点连接避免数字噪声灌进模拟地。参考电压 REF 的选择比电源更影响精度。AD7175 可以用内部基准也可以外部参考。内部基准的好处是简单但有温漂且默认电压未必匹配你的传感器量程。外部参考我一般选择低噪声的基准源芯片输出端加一个简单的 RC 滤波再进 REF 引脚。注意 REF 引脚必须接去耦电容规格书里要求的电容值不要减小这个位置省电容是最不值得的。3.3 STM32 SPI 参数CPOL/CPHA、时钟分频怎么设AD7175 的 SPI 工作在 Mode 1 或 Mode 2 都可以但绝大多数参考设计用的是 CPOL0、CPHA1也就是时钟空闲为低、数据在第二个边沿采样。这个参数在 CubeMX 里就是 SPI Mode 1。用错模式的表现很典型读寄存器能返回数据但第一字节总是错位或者数据高低位颠倒。SCLK 分频的选择主要受两个约束一个是 STM32 SPI 外设的最快频率上限另一个是 AD7175 规格书里的最大 SCLK 频率。AD7175 的 SCLK 可以跑到很高但我一般不会刻意拉满用一个偏保守的分频值先把功能调通比如 20MHz 的 APB 时钟配 8 分频得到 2.5MHz SCLK再用逻辑分析仪确认时序。满速跑 SCLK 是最后一步优化不是第一步调试手段。参数参考下表SPI 参数推荐设置说明CPOL0时钟空闲为低电平CPHA1第二个边沿采样数据数据帧格式8 位AD7175 按字节操作首字节 MSB先发高位与 ADI 统一分频系数8~16先慢后快调通再提速有一个容易忽略的点AD7175 的命令字节和回复数据存在半字节对齐的问题如果 STM32 的 SPI 配成 16 位帧格式整个驱动逻辑会乱套。务必用 8 位帧格式不要在底层做任何位宽转换。3.4 复位时序上电复位和软件复位的区别AD7175 有两种复位途径。上电复位由内部 POR 电路完成但不可控你不能准确知道复位完成的确切时刻。软件复位则是通过向 DOUT/SCLK 引脚发送至少 64 个 SCLK 脉冲同时 CS 保持低电平来触发。这个操作不需要命令字节纯粹是一串时钟脉冲实现上就是 CS 拉低后连续写 64 个 0xFF。软件复位后芯片需要一小段时间完成内部初始化这时候立刻读寄存器会失败。我看到过不少工程复位后第一笔就去读 ID然后把失败误判成 SPI 配置错误。正确顺序是复位延时一小段时间再读 ID。延时时长没有严格要求我通常给 1ms 左右足够内部逻辑稳定。4. 驱动源码拆解复位、初始化、校准与单通道读数的完整序列4.1 SPI 收发与片选控制最底层的 3 个函数不管源码包里的文件怎么组织底层一定是这三个函数片选拉低、片选拉高、一个字节的 SPI 收发。下面这段是我常用的一种写法用 HAL 库封装#define AD7175_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define AD7175_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET) static uint8_t spi_write_read(uint8_t tx_data) { uint8_t rx_data 0; // 每次传输只交换一个字节时钟由 SPI 外设产生 HAL_SPI_TransmitReceive(hspi, tx_data, rx_data, 1, 100); return rx_data; } static void ad7175_write_reg(uint8_t reg_addr, uint8_t *buf, uint8_t len) { // 命令字节最高位为 0 表示写bit6 置 1 表示连续写多个字节 uint8_t cmd (0 7) | (0 6) | (reg_addr 0x3F); AD7175_CS_LOW(); spi_write_read(cmd); for (uint8_t i 0; i len; i) { spi_write_read(buf[i]); } AD7175_CS_HIGH(); }这段代码里cmd的拼接是整个 SPI 通信的根基。写操作最高位是 0连续写时 bit6 置 1低 6 位放寄存器地址。len决定后续发送多少个字节这个长度来自上层调用者比如写滤波寄存器就是 2 字节写模式寄存器就是 1 字节。HAL_SPI_TransmitReceive是同时收发因为 SPI 是全双工即使只写数据也要读回同样长度的数据否则接收缓冲会积累垃圾数据影响下一次传输的帧对齐。4.2 复位与 ID 校验驱动能不能跑的第一步初始化函数的第一个动作通常是软件复位然后读 ID 寄存器验证 SPI 通路。这个顺序不能反而且复位后必须加延时。uint8_t ad7175_reset_and_check(uint16_t expect_id) { uint8_t id 0; // 拉低片选后连续产生至少 64 个 SCLK 脉冲 AD7175_CS_LOW(); for (uint8_t i 0; i 66; i) { spi_write_read(0xFF); } AD7175_CS_HIGH(); // 等待内部复位完成1ms 足够 HAL_Delay(1); // 读 ID 寄存器确认通信正常 ad7175_read_reg(AD7175_REG_ID, id, 1); if (id ! expect_id) { return 0; // 通信异常返回失败 } return 1; }这里写 66 个字节是因为 64 是最低要求多写两个字节不产生副作用但少了就可能导致复位不完整。expect_id应该从手册里查不同后缀型号的 ID 值不完全一样把这个参数放在接口里而不是写死在函数内方便换芯片型号时只改调用处的传入值。返回 0 时不要在继续往下跑先把硬件连接和 SPI 配置重新查一遍。4.3 初始化序列模式寄存器、配置寄存器、滤波寄存器一次配齐复位验证通过之后按顺序配置三个关键寄存器ADC 模式寄存器设定转换模式ADC 配置寄存器选定通道和量程滤波寄存器设定输出速率。顺序写反会导致部分配置被覆盖所以按下面的顺序执行。void ad7175_init(uint16_t filter_word) { // 1. 设置 ADC 模式寄存器连续转换模式内部时钟 // 具体位定义查阅头文件中的宏不同源码包命名不同 uint8_t mode_reg AD7175_MODE_CONTINUOUS | AD7175_MODE_CLK_INT; ad7175_write_reg(AD7175_REG_MODE, mode_reg, 1); // 2. 配置通道这里以通道 0 为例 // 双极性模式、使能内部基准、增益 1 倍 uint16_t conf AD7175_CONF_BIPOLAR | AD7175_CONF_REF_EN | AD7175_CONF_GAIN_1; uint8_t conf_buf[2]; conf_buf[0] (conf 8) 0xFF; conf_buf[1] conf 0xFF; ad7175_write_reg(AD7175_REG_ADC_CONFIG, conf_buf, 2); // 3. 写滤波寄存器输出速率由滤波器参数决定 uint8_t filter_buf[2]; filter_buf[0] (filter_word 8) 0xFF; filter_buf[1] filter_word 0xFF; ad7175_write_reg(AD7175_REG_FILTER, filter_buf, 2); }注意这里AD7175_CONF_BIPOLAR这类宏的值要和你手里的头文件保持一致不同版本的源码包命名可能略有差异。我写这段代码时特意把位域定义留在外部是因为它们在每个工程里确实不一样照抄寄存器值反而容易在换片子时出错。配置寄存器的 bit 位含义和手册里的寄存器映射是对应的双极性、参考源、增益这三项是无论如何都要确认的。4.4 校准内部零刻度与满刻度校准触发与等待初始化配置写完后不要立刻采集数据先做一次内部校准。校准的本质是芯片内部自动测量零点和满量程的误差写入偏移寄存器和增益寄存器。触发方式是在模式寄存器中写入校准命令然后等待 DOUT 变高表示校准完成。uint8_t ad7175_calibrate(void) { uint8_t mode_reg 0; // 读当前模式寄存器只改校准命令位不改其他配置 ad7175_read_reg(AD7175_REG_MODE, mode_reg, 1); // 触发内部零刻度校准 mode_reg | AD7175_MODE_CAL_ZERO; ad7175_write_reg(AD7175_REG_MODE, mode_reg, 1); // 等待校准完成DOUT 拉高表示内部操作结束 uint32_t timeout 10000; while (HAL_GPIO_ReadPin(DOUT_GPIO_Port, DOUT_Pin) GPIO_PIN_RESET) { if (--timeout 0) return 0; } // 触发内部满刻度校准 mode_reg ~AD7175_MODE_CAL_ZERO; mode_reg | AD7175_MODE_CAL_GAIN; ad7175_write_reg(AD7175_REG_MODE, mode_reg, 1); timeout 10000; while (HAL_GPIO_ReadPin(DOUT_GPIO_Port, DOUT_Pin) GPIO_PIN_RESET) { if (--timeout 0) return 0; } return 1; }校准期间 DOUT 的状态变化和正常转换时不同步所以等待逻辑用的是DOUT 为低说明忙碌为高说明空闲和读数据时的判断方向相反。这个细节特别容易写反很多人在校准等待时套用了 DOUT 变低表示有数据 的逻辑结果一直等到超时。超时计数器这里给了 10000 次循环实际校准时长远小于这个数给大一点是为了调试时能看到超时现象而不是死循环。4.5 轮询读转换结果等 DOUT 拉低再读 4 字节完整帧所有配置和校准完成后进入正常的采集循环。最简单可靠的方式是轮询 DOUT当 DOUT 变为低电平说明转换数据已经准备好此时启动一次 SPI 读事务把状态字节和数据字节一起读回来。uint8_t ad7175_read_data(int32_t *raw_value, uint8_t *channel_id) { uint8_t buf[4] {0}; uint8_t cmd (1 7) | (0 6) | (AD7175_REG_DATA 0x3F); // 等待 DOUT 拉低表示新数据就绪 uint32_t timeout 100000; while (HAL_GPIO_ReadPin(DOUT_GPIO_Port, DOUT_Pin) GPIO_PIN_SET) { if (--timeout 0) return 0; } // 一次 SPI 事务读 4 字节状态 24 位数据 AD7175_CS_LOW(); spi_write_read(cmd); for (uint8_t i 0; i 4; i) { buf[i] spi_write_read(0xFF); } AD7175_CS_HIGH(); // 24 位数据按位拼装注意是 MSB 先出 uint32_t code ((uint32_t)buf[1] 16) | ((uint32_t)buf[2] 8) | buf[3]; // 双极性模式下 0x800000 是零点转换为有符号数 *raw_value (int32_t)code - 0x800000; if (channel_id ! NULL) { *channel_id buf[0] 0x0F; } return 1; }命令字节里读标志位为 1连续读写位为 0因为这里要读的 4 个字节是芯片固定的输出序列不需要设置连续模式。buf[0]是状态字节其中低 4 位是通道 ID如果你在单通道场景下不想管它可以把channel_id参数传 NULL。三字节数据拼接用到了移位而不是内存拷贝是为了避免大小端的问题STM32 是小端机直接强制转换指针拿到的字节序是反的移位拼接最稳妥。轮询方式在输出速率较低比如 10kSPS 以下时完全够用MCU 占用率很低。但如果你把输出速率调到 100kSPS 以上轮询循环本身的指令开销就可能成为瓶颈这就需要换成中断驱动也就是第 6 章的内容。5. 避坑实录AD7175 驱动最容易翻车的 5 个现场5.1 现象读 ID 一直不对返回的不是手册值原因软件复位时序不完整。很多人只发送了几十个时钟脉冲或者在发送期间 CS 没有始终保持低电平。另一个常见原因是上电后立刻读写寄存器芯片还没从 POR 中完全恢复。解决确保 CS 拉低后连续发送 64 个以上的 0xFF完成后拉高 CS再延时 1ms。ID 校验放在延时之后。这个流程我用一句话概括先复位再等再读 ID不要试图节省这一步。5.2 现象CS 拉高后 SPI 数据偶发错位波形上看起来一切正常原因CS 释放的时机太靠近最后一个 SCLK 边沿。有些外设在 CS 拉高的瞬间还在驱动 SCLK导致芯片把多余的边沿当作数据时钟。解决每个字节收发结束后加一个极短的延时或者在最后一个字节传输完成后等待 SPI 总线空闲标志确认再拉高 CS。我见过一个工程用HAL_SPI_TransmitReceive返回后立刻拉高 CS 仍然偶发错位后来在整帧读完后加了几个空操作的延时才稳定。这属于时序余量问题不是逻辑错误示波器上很难一眼看出。5.3 现象改完滤波寄存器后零点漂移得厉害原因滤波设置影响的是数字滤波器的阶数和时间常数修改后内部零点状态没有自动更新。校准是在旧的滤波参数下完成的参数变了校准结果不再匹配。解决每次修改滤波寄存器、增益配置或通道切换后重新执行一遍内部校准流程。这件事做起来成本很低但很多驱动代码里没有暴露一个统一的 配置后校准 接口导致用户只改参数不管校准数据自然漂。5.4 现象读回来的 24 位数据高低字节颠倒换算电压完全不对原因拼装顺序错误。AD7175 的数据寄存器是 MSB 先出收到的第一个字节是最高字节。如果直接用小端机的指针强制转换或者把字节顺序拼成 LSB 在前结果就反了。解决统一用移位方式拼装代码里明确写出(buf[1] 16) | (buf[2] 8) | buf[3]。同时确认 SPI 的数据帧格式为 MSB First如果 CubeMX 里误配成 LSB First也会出现同样的症状。5.5 现象输出速率设到 250kSPS 后数据跳得厉害完全不能用于测量原因满速输出时 Σ-Δ 调制器的过采样余量大幅下降有效分辨率会明显降低这是芯片特性而不是驱动 bug。另外高速转换下模拟前端如果没有抗混叠滤波带外噪声会折回通带。解决先按 16kSPS 或 8kSPS 调试确认数据稳定后再逐步提高速率同时观察有效位数的变化。如果应用场景真的需要高速高精度需要在 AD7175 前端增加 RC 或运放构成的抗混叠滤波器这不是驱动能解决的。6. 进阶把轮询改成 EXTI 中断驱动用 DOUT 下降沿触发采集轮询读数据在低速场景下没有问题但把输出速率提高到几十 kSPS 以上时轮询会让 STM32 的主循环基本卡死在等待 DOUT 上。更合适的做法是把 DOUT 接到 STM32 的一个 EXTI 引脚上用下降沿触发中断在中断回调里读数据。AD7175 的 DOUT 在每次转换完成后会拉低这个下降沿天然就是数据就绪的中断信号。void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { int32_t raw 0; uint8_t ch 0; if (GPIO_Pin DOUT_Pin) { // 在中断里快速读取数据读完后 DOUT 自动恢复高电平 if (ad7175_read_data(raw, ch)) { // 数据存入一个 volatile 全局变量主循环里消费 g_last_raw raw; g_data_ready 1; } } }中断回调里最忌讳做耗时的处理所以我只保存原始码和置一个标志位。电压换算是乘系数运算放在主循环里做。另一个要注意的是ad7175_read_data里面有超时等待和 HAL 库函数调用严格意义上不适合放在中断上下文实际使用时我一般会简化这个函数去掉超时循环直接按固定步骤读 SPI因为中断本身已经到了数据就绪是确定的事件。如果你发现中断回调里执行时间太长导致后续中断丢失就用 DMA 方式读 SPI这是更进一步的做法但逻辑上和这里的核心思路一致。调试中断驱动时有一个习惯值得养成先在主循环里把g_data_ready清零再等它置 1观察置 1 的频率是否与设定的输出速率一致。如果不一致多半是 SPI 读数据花的时间超过了转换周期芯片在上一笔数据还没读完时已经生成了下一笔此时要么降低输出速率要么提高 SCLK 频率。把 DOUT 的边沿和 SPI 传输的结束时刻放到示波器上对比是最直接的验证手段。第一次调这个驱动时我把 CS 拉高的时机提前了半个 SCLK 周期数据偶尔会错一位排查了两天才在示波器上抓到问题。后来我养成了一个习惯所有 SPI 驱动都先验证复位加读 ID再调数据采集时序上的余量宁可多留不可卡临界。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站