ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103 SPI驱动AD7175-2:24位Σ-Δ高精度ADC采集实战

STM32F103 SPI驱动AD7175-2:24位Σ-Δ高精度ADC采集实战 简介本资源是一份面向嵌入式开发工程师与STM32初学者的高精度ADC驱动实践代码聚焦STM32F103微控制器与AD7175-2 24位Σ-Δ模数转换器的SPI通信实现解决工业传感、精密测量等场景中高分辨率模拟信号采集的核心问题。压缩包为RAR格式仅含1个关键源文件main.c2KB完整实现了GPIO与SPI外设初始化、AD7175-2寄存器配置含参考电压、PGA增益、采样速率、24位转换数据分次读取与电压值解析等核心逻辑代码结构清晰、注释充分便于理解协议时序与硬件协同机制。已有1821人学习下载适合需要快速上手高精度ADC驱动、掌握SPI多字节传输细节及AD7175-2典型应用流程的中级嵌入式开发者。 搞高精度数据采集的人对 AD7175 这颗 ADC 应该都不陌生。它经常出现在 PLC 模拟量输入模块、高精度称重、热电偶采集、压力变送器这类项目里是 ADI 在 24 位 Σ-Δ ADC 里出货量很大的一个系列。我这次要分享的项目很直接STM32F103 通过 SPI 驱动 AD7175-2项目文件夹取名就叫main_STM32F103_AD7175-2主控用的是最常见的 STM32F103C8T6 最小系统板ADC 是 AD7175-2 的评估板掰下来的模块。整个项目内容不复杂但把 AD7175 的上手流程、寄存器配置、main 函数怎么写、调试踩过的坑串起来对刚接触这颗芯片的人会很有参考价值。文章里我会把代码结构和配置逻辑都拆开讲适合手里正好有这两颗料、想快速跑通数据的工程师也适合正在选型高精度 ADC 的人。1. 项目整体设计与选型思路1.1 AD7175-2 的核心优势与选型原因AD7175-2 是 ADI 的 24 位 Σ-Δ 型 ADC最大输出速率能做到 250kSPS这在 Σ-Δ 架构里属于相当快的了。很多同类 24 位芯片做到几十 kSPS 就到顶AD7175-2 还能保持 24 位有效分辨率的同时跑高速这是它最大的卖点。它内部集成了 PGA 可编程增益放大器增益范围 1 到 128 倍意味着小信号不用外部再放大直接进 ADC 就能测。内部还带一个 2.5V 基准源温漂指标做得不错精度要求不太苛刻的应用可以省掉外部基准芯片。选它还有一个原因输入通道配置灵活。AD7175-2 支持 2 路全差分输入也可以配成 4 路单端输入很多 PLC 模拟量采集模块一个芯片就能覆盖多路信号板子面积能省不少。另外它内部有数字滤波器sinc5、sinc3、sinc5sinc1 都可以配置其中 sinc5sinc1 滤波器对 50Hz 和 60Hz 工频干扰有很强的抑制能力做工业现场采集时这个特性非常实用。在我们这个项目里信号源是桥式传感器输出的差分小信号量程在 ±20mV 左右刚好需要用到内部 PGA 放大选 AD7175-2 可以说是一步到位。1.2 为什么主控选 STM32F103STM32F103 在这个项目里属于典型的够用就好。驱动 AD7175-2 只需要一路 SPI外加一个 GPIO 读取 DOUT/RDY 数据就绪信号对 MCU 的主频、内存、外设资源要求都不高。我用了最便宜的 STM32F103C8T6 蓝色 pill 最小系统板20 块钱以内搞定跑 SPI 时钟 4MHz 毫无压力。实际上 AD7175-2 的 SCLK 上限是 8MHzSTM32F103 的 SPI1 最高能跑到 18MHz挂在 APB2 总线上所以瓶颈在 ADC 侧不在 MCU 侧。很多新手容易犯一个错误选了很强的 MCU 却不知道怎么搭最小系统。用最小系统板就能避开晶振、复位、下载电路这些坑把精力集中在 ADC 通信调通上。等项目跑顺了再画自己的板子时照抄最小系统板的电路即可。F103 的中文参考手册网上到处都是SPI 寄存器配置对着手册查非常方便这也是很多工业老项目至今还用 F103 的原因之一——资料全、生态成熟、库函数用得顺手。1.3 硬件连接方案与去耦细节AD7175-2 与 STM32F103 之间只需要 4 根信号线SCLK、DIN、DOUT/RDY、CS另外最好把 SYNC 引脚也接一个 GPIO方便做同步复位不接也能工作。我用的接法如下表所示STM32F103 引脚AD7175-2 引脚说明PA5 (SPI1_SCK)SCLKSPI 时钟配成模式 3PA6 (SPI1_MISO)DOUT/RDY数据输出兼数据就绪信号PA7 (SPI1_MOSI)DIN数据输入PA4 (普通 GPIO)CS片选软件控制PA3 (普通 GPIO)SYNC同步输入可悬空或拉高3.3VAVDD / IOVDD供电AVDD 需要 1μF 和 0.1μF 去耦GNDGND共地这里最关键的是 DOUT/RDY 引脚它不光是 MISO还承担着数据就绪的功能。ADC 完成一次转换后 DOUT/RDY 会拉低通知主机可以读数了。我在项目里直接把它接到 STM32 的 PA6也就是 SPI1_MISO同时在主循环里用 GPIO 轮询的方式等它变低。如果追求高速响应可以把 DOUT/RDY 同时接到一个外部中断引脚用 EXTI 中断听通知。注意 STM32F103 的 MISO 引脚在 SPI 外设初始化后会被外设接管GPIO 轮询功能就没法直接用了所以我这里其实额外用了一个普通 GPIO 复制了 DOUT/RDY 信号或者在读数据前临时读取 PA6 的电平这个细节后面再展开。电源去耦值得多说一句。AD7175-2 是 24 位 ADC对电源纹波敏感AVDD 引脚上我放了 1μF0.1μF 两个电容并且 0.1μF 尽量靠近芯片引脚。参考电压如果启用内部基准REFCAP 引脚必须外接 1μF 电容这个电容的材质建议选 C0G 或 X7R漏电小的电容有助于稳定基准。我最初就是因为 REFCAP 电容漏电偏大导致读数在最后一位跳动换电容后立刻稳定了。2. 上手 AD7175-2SPI 时序与寄存器基础2.1 SPI 工作模式与命令字格式AD7175-2 的 SPI 接口支持 CPOL1/CPHA1模式 3部分资料也说支持模式 0但我实际验证下来模式 3 最稳。STM32F103 的 SPI1 初始化时设置为 CPOL1、CPHA1、MSB First、8 位数据长度SCLK 预分频设成 16 分频主频 72MHz 下得到 4.5MHz 的 SCLK满足 8MHz 上限留有一定余量。片选 CS 用软件控制每次通信前拉低通信结束拉高。AD7175-2 的寄存器操作命令是 16 位的分两字节发送。命令字的高字节和低字节结构如下位段含义bit15-14保留写 0bit13R/W1读0写bit12-8寄存器地址5 位bit7-6保留写 0bit5-4数据长度001 字节012 字节103 字节114 字节bit3-0保留写 0举个例子读取 0x05 地址的 ID 寄存器2 字节命令字就是R/W1地址00101长度01组合成二进制 0010 0101 0001 0000即 0x2510。主机把 0x25 和 0x10 两个字节依次发给 ADC然后继续给 SCLK从 DOUT 上收两字节就能拿到 ID 值。这个命令字构造逻辑搞明白之后读任何寄存器都一个套路。2.2 必须最先认识的几个寄存器AD7175-2 寄存器很多但项目上手阶段只需要关注四个0x01 ADC 模式寄存器2 字节。操作模式连续转换、单次转换、连续读、校准模式、软件复位位都在这里。往这个寄存器写 0x0800 即可触发软件复位芯片会自动恢复默认寄存器值。注意复位命令发出后要等至少 1ms 再操作其他寄存器。0x02 接口模式寄存器1 字节。控制 SPI 的时序细节比如要不要隐藏 DOUT/RDY 脉冲、数据输出是否用 16 位 CRC。默认值通常就够用项目里我没有改它。0x04 数据寄存器4 字节。这是最常读的寄存器高 3 字节是 24 位转换结果最低字节是状态寄存器的值。注意数据格式是偏移二进制0x800000 对应 0V 输入满量程正对应 0xFFFFFF满量程负对应 0x000000这一点在换算电压时分重要。0x05 ID 寄存器2 字节。AD7175-2 的 ID 固定是 0x40D2。上电后第一件事就是读 ID验证 SPI 通信链路正确如果读回的值不是 0x40D2说明接线、SPI 模式或命令字有地方不对先排查这些再往下走。2.3 复位后最少几步能读到数据很多 ADC 芯片不配置寄存器根本无法输出数据但 AD7175-2 有个对新手很友好的特性复位后默认就是连续转换模式。也就是说上电复位之后其他寄存器什么都不动芯片已经在满速跑转换了你只需要不断把数据寄存器读回来即可。这个特性让最小验证流程变得极短上电延时 10ms软件复位写 0x0800 到 0x01再延时 2ms读 0x05 得到 ID确认等于 0x40D2等待 DOUT/RDY 拉低读 0x04 数据寄存器把偏移二进制转成有符号数并换算电压。我建议项目初期一定要先按这个最小流程跑通看到数据在动再进行通道配置、PGA 增益、滤波器的调整。不要一上来就对着数据手册配置十几个寄存器出了问题都不知道是哪一步引起的。3. main 函数与驱动代码框架3.1 main 函数的分层设计项目文件结构如下麻雀虽小五脏俱全main_STM32F103_AD7175-2/ ├── Core/ │ ├── main.c │ └── ... ├── Driver/ │ ├── bsp_spi.c // SPI 底层 │ ├── bsp_spi.h │ ├── ad7175.c // AD7175 驱动 │ └── ad7175.h ├── inc/ │ └── ... └── ...HAL 库文件目录main.c 里只做三件事初始化系统时钟、初始化外设、进入主循环轮询读数。AD7175 的具体操作全部封装在 ad7175.c 里这也是为了以后换平台时好移植。很多项目把 SPI 读写和 ADC 逻辑混在 main.c 里文件越来越大到最后连自己都懒得看。分层的习惯早点养成后面做复杂项目会非常受益。3.2 SPI 底层初始化代码基于 STM32CubeMX 生成的代码手动检查几个关键配置项是否正确。SPI1 初始化时我手动调整为下面这样SPI_HandleTypeDef hspi1; void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_HIGH; // CPOL1 hspi1.Init.CLKPhase SPI_PHASE_2EDGE; // CPHA1模式3 hspi1.Init.NSS SPI_NSS_SOFT; // CS 软件控制 hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_16; // 4.5MHz hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; HAL_SPI_Init(hspi1); }SPI 底层发送接收函数用 HAL 封装即可我的实现里每次只交换一个字节uint8_t spi_txrx(uint8_t byte) { uint8_t rx 0; HAL_SPI_TransmitReceive(hspi1, byte, rx, 1, 10); return rx; }注意这里超时时间设了 10ms在 4MHz 时钟下一字节只要 2μs 左右10ms 足够宽松。如果出现 HAL_BUSY 卡住的情况多半是 SPI 配置有问题或者片选逻辑没配对。3.3 AD7175 驱动命令构造、复位、读 ID先写一个通用的命令字构造函数后面所有寄存器操作都复用static uint16_t ad7175_build_cmd(uint8_t reg, uint8_t len, uint8_t rw) { uint16_t cmd 0; if (rw) cmd | (1 13); // R/W bit cmd | ((reg 0x1F) 8); // 寄存器地址 cmd | ((len - 1) 0x03) 4; // 数据长度字段 return cmd; }然后封装寄存器写和读函数void ad7175_write_reg(uint8_t reg, uint16_t data, uint8_t len) { uint16_t cmd ad7175_build_cmd(reg, len, 0); AD7175_CS_LOW(); spi_txrx(cmd 8); spi_txrx(cmd 0xFF); for (int i len - 1; i 0; i--) { spi_txrx((data (8 * i)) 0xFF); } AD7175_CS_HIGH(); }读寄存器稍复杂主机发送命令后还需要再发若干 dummy 字节来产生 SCLK 时钟同时从 MISO 收数据void ad7175_read_reg(uint8_t reg, uint8_t buf[], uint8_t len) { uint16_t cmd ad7175_build_cmd(reg, len, 1); AD7175_CS_LOW(); spi_txrx(cmd 8); spi_txrx(cmd 0xFF); for (int i 0; i len; i) { buf[i] spi_txrx(0xFF); } AD7175_CS_HIGH(); }读 ID 和软件复位实现如下#define AD7175_ID 0x40D2 uint16_t ad7175_read_id(void) { uint8_t buf[2] {0}; ad7175_read_reg(0x05, buf, 2); return ((uint16_t)buf[0] 8) | buf[1]; } void ad7175_reset(void) { ad7175_write_reg(0x01, 0x0800, 2); // 写 ADC 模式寄存器置位 RESET HAL_Delay(2); }这里有个经验读 ID 时主机发完命令后必须连续给 2 个字节的时钟ADC 才会依次移出 ID 的高字节和低字节。如果读回来的结果是 0x4000 或者 0x00D2 之类的“半截”数据大概率是时钟给少了或者片选提前拉高了。3.4 主循环里的数据读取与电压换算主循环代码比较直观核心是等 DOUT/RDY 拉低后读数据寄存器int32_t ad7175_read_data(void) { uint8_t buf[4] {0}; uint32_t raw 0; int32_t code; // 等待 DOUT/RDY 变低表示有新的转换结果 while (HAL_GPIO_ReadPin(ADC_RDY_GPIO_Port, ADC_RDY_Pin) ! GPIO_PIN_RESET); ad7175_read_reg(0x04, buf, 4); raw ((uint32_t)buf[0] 16) | ((uint32_t)buf[1] 8) | buf[2]; // 偏移二进制转补码 code (raw 0x800000) ? (int32_t)(raw - 0x800000) : (int32_t)raw - (int32_t)0x800000; return code; }换算成实际电压的公式如下其中 vref 取 2.5Vgain 是 PGA 配置的放大倍数float ad7175_code_to_voltage(int32_t code, float vref, float gain) { return (float)code * vref / 8388608.0f / gain; }为什么是 8388608因为 24 位偏移二进制的零点是 0x800000也就是 8388608满量程正对应 8388607满量程负对应 -8388608所以电压分辨率就是 vref / 8388608 / gain。一个常见误区是把 0xFFFFFF 当成满量程直接除以 16777215那样算出来会偏小约 0.000006%对 24 位精度来说这个误差反而不可接受所以一定要用 8388608 作分母。main 函数骨架如下int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); ad7175_reset(); uint16_t id ad7175_read_id(); if (id ! AD7175_ID) { /* 通信异常可以点个 LED 或者进错误处理 */ while (1); } /* 默认连续转换模式直接读数即可 */ while (1) { int32_t code ad7175_read_data(); float volt ad7175_code_to_voltage(code, 2.5f, 1.0f); /* 把 volt 通过串口发出去或者显示到屏上 */ } }这个最小代码已经能稳定读到数据。项目里真正上线时我还做了两项增强一是把 DOUT/RDY 接到 EXTI 外部中断实现中断驱动采集省掉主循环轮询的占用二是在初始化里增加了通道寄存器0x09和设置寄存器0x10的配置让通道 0 对应 AIN0/AIN1 差分输入关联 setup0这样能把输入引脚固定下来防止误配。这些配置的位域逻辑每个项目需求不同建议按数据手册的寄存器表逐位填不要直接抄网上的数值。4. 实际调试中的问题与排障记录4.1 读 ID 全是 0xFF 或 0x00 怎么查这个坑几乎每个人都会踩。读 ID 返回 0xFFFF 或者 0x0000先别怀疑芯片坏了按下面的顺序排查第一步确认 SPI 模式。AD7175-2 工作在模式 3CPOL1、CPHA1如果 STM32 这边配置成了模式 0SCLK 空闲电平和采样沿都不对ADC 根本不会响应。用逻辑分析仪抓 SCLK 和 DIN正常命令字是 0x25 0x10如果抓到的波形对不上就是发送数据的问题。第二步确认 DOUT/RDY 方向。有些开发板的 SPI1 MISO 引脚上带了上拉电阻上拉过强会在 ADC 驱动 DOUT 时产生电流冲突导致高电平被拉低或者低电平被抬高到逻辑阈值附近。我遇到过一路 DOUT/RDY 外接了 1kΩ 上拉的情况读 ID 一直是 0xC0D2把上拉去掉就恢复正常。第三步确认片选时序。CS 必须在整个命令字和读数据期间保持低电平不能在发完命令字后立刻拉高。ADC 只有在 CS 为低时才把数据移出 DOUTCS 拉高后 DOUT 会变成高阻态。很多人的代码是发送命令函数和读取数据函数分开写的结果中间有别的操作把 CS 拉高了。4.2 数据跳字和噪声偏大的几个原因24 位 ADC 末位跳几个 LSB 属正常现象但如果跳得离谱先从三个方面查电源纹波。AVDD 纹波会直接耦合进模拟前端。先用示波器看 AVDD 上的噪声建议峰峰值控制在 10mV 以内。如果超标加大去耦电容容量或者在 AVDD 入口串一个磁珠。参考电压稳定性。使用内部基准时REFCAP 的电容必须靠近 REFCAP 引脚并且不能用高介质吸收的电容。我这里踩过坑用了普通陶瓷电容读数会出现周期性的慢漂移换成 C0G 材质电容后问题消失。如果还是不稳可以改成外部基准用 ADR4525 这类低噪声基准芯片驱动 REF 和 REF-。布局问题。ADC 的模拟输入信号线如果和 SPI 数字信号线平行走线数字跳变会通过寄生电容耦合到模拟输入。我把模拟输入走线包了地和数字区域隔开噪声立刻降了一个数量级。板子空间宽裕的话给 AVDD 和模拟输入做一个小面积的地分割单点连接效果最好。另外一个容易被忽略的是滤波配置。AD7175-2 默认 sinc5 滤波器如果采样速率设得很高滤波效果变弱噪声会明显增大。对低速高精度需求把 ODR 降到 1kSPS 以下配合 sinc5sinc1 滤波器50Hz 工频干扰能压得很低。我用 9600 波特率串口输出数据时看到 50Hz 噪声纹波很明显后来把 ODR 从 50kSPS 降到 1kSPS 后读数稳定多了。4.3 偏移和增益校准要怎么做AD7175-2 支持内部校准和系统校准。上电后如果发现输入 0V 时读数不是 0x800000而是偏了几十个 LSB这就是偏移误差。项目里我做了内部偏移校准把 ADC 模式寄存器切到内部偏移校准模式等待 DOUT/RDY 拉低表示校准完成切回连续转换模式。具体编码方式在数据手册 ADC 模式寄存器的 MODE 位里不同模式对应不同二进制码这里不贴完整表。校准完成后芯片会把计算出的偏移值写入偏移校准寄存器后续转换会自动扣除。注意校准只能用一次如果换了增益档位需要重新校准。增益校准针对的是满量程误差做法类似输入一个已知高精度电压触发内部满量程校准芯片会调整增益寄存器。如果校准后还是偏多半是外部基准或者分压电阻精度不够。经验是用 6 位半万用表实测基准电压然后把这个实测值写进程序里的 vref 常量比校准更直接有效。项目最终交付时我在程序里预留了一个ADC_VREF_CALI宏方便现场校准时把基准值改成实测值。4.4 其他值得注意的实操坑软件复位后立刻读寄存器可能读到旧值。原因是复位命令发出后芯片内部还需要几个时钟周期才能真正完成复位寄存器值才会变成默认值。我加了 2ms 延时之后就没再出过问题。有时候因为晶振起振慢需要更长延时建议至少 5ms 比较保险。连续转换模式下读数据建议一次性读完 4 字节。AD7175-2 的数据寄存器自带锁存功能在片选拉低、本文还有配套的精品资源点击获取
返回列表