简介这是一份面向嵌入式开发者、以STM32F103C6T6为核心控制器的AD7791高精度模数转换例程压缩包。工程基于STM32CubeIDE环境与HAL库实现采用模拟SPI总线协议读取24位Σ-Δ型ADC芯片AD7791的电压采样数据完整覆盖芯片初始化、寄存器读写、转换时序控制、数据拼接与校验等环节适用于低频高精度测量、传感器采集、仪器仪表等需要稳定高速采样的项目参考。压缩包共收录163个文件大小约64.38MB主要包含HAL库底层驱动源码C/H、工程构建配置project/cproject/ioc/makefile、链接脚本与编译中间文件并附带说明文档便于对照学习工程的组织方式和外设配置。目前已有880人学习下载适合正在使用模拟SPI驱动AD7791或同类24位ADC的开发者快速搭建实验环境节省摸索底层时序与外设初始化的时间。1. 项目概述与设计思路拿到这个 STM32F103C6T6AD7791 的工程压缩包时我第一反应是看里面到底是完整可编译的固件还是一堆零散的参考代码。把工程结构和初始化逻辑捋了一遍后发现这套组合的使用场景非常聚焦主控通过 SPI 控制 AD7791完成 24 位高精度 ADC 采集再把转换结果通过串口发出去。它不追求高采样率而是把精度和稳定性放在第一位适合称重、应变、压力、微伏级信号测量这类典型应用。1.1 这套组合的典型应用场景AD7791 是 ADI 的 24 位 Σ-Δ 型 ADC输出速率在 9.5Hz 到 120Hz 之间。这个速率区间注定它不是用来做波形采集的而是用来做“慢速但精确”的测量。手头常见的场景包括称重传感器信号采集、桥式压力传感器变送器、热电偶或 RTD 温度采集中前级信号调理后的数字化以及电池内阻测试仪里对小电压信号的精密采样。STM32F103C6T6 在这个组合里扮演的角色很简单系统控制器。它提供 SPI 主站接口、串口通信、数据处理和显示控制。C6T6 有 64KB Flash 和 20KB RAM主频 72MHz跑一个 ADC 采集状态机加滑动平均滤波资源占用非常宽裕。哪怕后面再加一个 TFT 屏幕、几个按键、一组继电器控制Flash 也基本够用这也是这个芯片成为低成本仪表首选的原因之一。1.2 为什么不用 STM32 内置 ADC很多新手会问“STM32F103C6T6 内部不是自带 12 位 ADC 吗为什么还要外挂芯片”这个问题问到点子上了。内置 ADC 的优势是多通道、速度快、零额外成本但它的精度和稳定性在微弱信号场景下不够用。12 位分辨率在 3.3V 参考电压下理论最小量化步进约 0.8mV实际噪声影响下稳定读数往往只能到 10 位左右也就是约 3mV 的等效分辨率。而称重传感器满量程输出通常只有几毫伏到十几毫伏如果直接用内置 ADC一个很小的重量变化都反映不出来。外接 AD7791 后24 位分辨率加上内部缓冲器可以直接对接高阻传感器输出无需额外的仪表放大器。这就是“术业有专攻”的道理高速多通道场景靠 MCU 内置 ADC高精度低噪声场景交给专用 Σ-Δ ADC。1.3 AD7791 的核心参数说明AD7791 的供电范围是 2.7V 到 5.25V内部有一个出厂校准过的 2.5V 基准源。增益支持 1 倍和 16 倍两档选择 16 倍增益时满量程输入范围约 ±156mV适合接应变桥这类小信号。需要特别注意的是它内部还带一个输入缓冲器可以打开或关闭。打开后输入阻抗很高能减轻传感器负担关闭后输入范围可以更靠近电源轨但输入阻抗会明显降低。还有一个容易被忽略的参数是更新速率与有效分辨率的关系。AD7791 的输出速率越低数字滤波器积分时间越长有效分辨率越高。比如 9.5Hz 速率下往往能拿到 23 位左右的有效分辨率而 120Hz 速率下有效位数会下降到 20 位上下。工程里默认配置在 50Hz 速率算是一个均衡点。实际项目中如果对速度没有要求尽量调低速率能明显减少软件滤波压力。2. 硬件连线与注意事项打开压缩包里的原理图可以看到主控和 ADC 的连接集中在 SPI1 接口上。SPI1 默认引脚是 PA5SCK、PA6MISO、PA7MOSI片选用的是 PA4。这个选择比较合理因为这组引脚在 LQFP48 封装里距离 ADC 芯片近走线短不容易引入干扰。2.1 STM32F103C6T6 的 SPI 资源分配很多人第一次看 STM32 和 AD7791 的接线图会有个疑问AD7791 的数据线明明只有一根 DOUT/DIN为什么 SPI 要接两根这其实是 SPI 协议的特性决定的。STM32 作为主机MOSI 负责向从机写指令和数据MISO 负责从从机读转换结果。AD7791 内部把 DOUT 和 DIN 合并成一根双向线但在硬件连接上DOUT/DIN 引脚可以同时接到 STM32 的 MISO 和 MOSI 上吗答案是看具体芯片手册。AD7791 的 DIN 是输入DOUT 是输出二者在芯片内部不是推挽驱动同一个引脚所以常规做法是把 DOUT 接 PA6MISO用于读取DIN 接 PA7MOSI用于写入。如果用的是 AD7791 的 16 脚版本或者不同封装一定要先确认引脚定义。压缩包里的接线是把 AD7791 的 DIN 到 PA7、DOUT 到 PA6、SCLK 到 PA5这样 SPI 全双工通信自然就打通了。代码里还需要注意一个细节AD7791 的 DOUT/RDY 引脚兼任“数据就绪”信号。每次转换完成后该引脚会从高电平拉低表示可以读取数据。工程里用轮询方式检测 PA6 的电平变化如果检测到低电平就启动一次 SPI 读取。这种方式 CPU 占用很低120Hz 速率下完全够用。2.2 与 AD7791 的接线顺序如果从 AD7791 芯片角度重新梳理一遍引脚关系关键连线如下AD7791 引脚作用STM32F103C6T6 引脚SCLK串行时钟输入PA5SPI1_SCKDIN串行数据输入PA7SPI1_MOSIDOUT/RDY数据输出/就绪指示PA6SPI1_MISOSYNC同步输入接 VCC 或 GPIO 拉高RESET复位输入上拉电容复位电路AVDD模拟电源3.3V 经磁珠隔离AGND模拟地接地平面DVDD数字电源3.3VREFIN/REFIN-参考电压输入可悬空使用内部基准SYNC 和 RESET 都不要直接悬空。SYNC 接一个 10kΩ 上拉到 VCCRESET 也用一个 10kΩ 上拉并联 0.1μF 电容到地形成上电复位。这个组件的目的是防止复位引脚在噪声环境下误动作否则初始化过程中芯片可能经常回到默认状态表现为“配置丢失”。2.3 电源与基准电压处理24 位 ADC 的精度上限受电源和参考电压限制。电源噪声直接耦合到模拟输入会让转换结果末位跳动。工程里较稳妥的做法是3.3V 数字电源经过一个磁珠后供给 AVDD磁珠选 600Ω/100MHz 阻抗的型号即可。磁珠只对高频噪声有效对低频纹波作用不大所以 AVDD 引脚附近还要放一个 10μF 钽电容和一个 0.1μF 陶瓷电容分别吸收低频和高频噪声。参考电压方面压缩包工程默认使用 AD7791 内部 2.5V 基准。这个基准在 25°C 环境下精度不错但温度系数相对一般如果产品要做宽温范围建议外接 ADR4525 这类低温漂基准。外接基准时REFIN 接基准输出REFIN- 接模拟地并在引脚处加去耦电容。地线布局是很多人忽略的重点。ADC 芯片下方不要走任何数字信号线特别是 SCLK 和 MOSI。如果两层板布线空间紧张至少要保证 ADC 和模拟输入区域有一块完整的地平面数字信号线和模拟信号线垂直交叉而不是平行长距离走线。3. 固件实现从初始化到数据读取固件部分是这个工程值得仔细读的核心。我调试 ADI 的 Σ-Δ ADC 时有一个固定流程先通 SPI再读寄存器最后才看数据。下面按这个顺序把关键代码和思路拆开讲。3.1 SPI 外设初始化工程使用标准外设库初始化 SPI1。这里最值得注意的是 SPI 模式选择AD7791 要求空闲时钟为高、第二个边沿采样也就是 SPI 模式 3CPOL1、CPHA1。有些工程师会用 GPIO 模拟 SPI也推荐按模式 3 来写。如果时序不对会出现“能写入但读不到数据”或者“数据错位”的情况。void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK: PA5, MOSI(DIN): PA7复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // MISO(DOUT): PA6浮空输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_High; // 模式3 SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_32; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }波特率这里选的 32 分频也就是 72MHz/322.25MHz。AD7791 的 SCLK 上限虽然更高但留出裕量更可靠。SPI 速率太高时DOUT/RDY 信号翻转时间和 STM32 采样窗口可能匹配不好导致读回的数据偶尔错位。3.2 AD7791 配置序列AD7791 的寄存器操作靠一个通信寄存器引导。上电后先发送一次复位序列把芯片恢复到默认状态然后配置模式寄存器和滤波寄存器。压缩包工程里的初始化代码经过验证可以正常工作关键部分如下void AD7791_Init(void) { // 1. 复位连续写6个0xFF AD7791_CS_LOW(); for (uint8_t i 0; i 6; i) { SPI_WriteByte(0xFF); } AD7791_CS_HIGH(); delay_ms(5); // 2. 写模式寄存器连续转换模式缓冲使能增益1倍 // 这里用封装宏代替通信字节具体数值参考数据手册寄存器映射 AD7791_WriteReg(AD7791_REG_MODE, AD7791_MODE_CONT_CONV | AD7791_MODE_BUF | AD7791_MODE_GAIN_1); // 3. 写滤波寄存器选择50Hz输出速率 AD7791_WriteReg(AD7791_REG_FILTER, AD7791_FILTER_50HZ); }初始化完成后建议读一遍模式寄存器确认写入值和读回值一致。如果读回全 0 或全 1说明 SPI 通信有问题不要继续往下调数据。3.3 连续转换模式下的数据读取连续转换模式下AD7791 会按照设定速率不断更新数据寄存器DOUT/RDY 引脚每个周期拉低一次。主控检测到这个下降沿后发起读操作连续读 3 个字节拼成一个 24 位补码数据。uint32_t AD7791_ReadData(void) { uint8_t buf[3] {0}; uint32_t result 0; // 等待数据就绪PA6作为DOUT/RDY while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) ! 0); AD7791_CS_LOW(); // 发送读数据寄存器指令这里是通信寄存器选择数据寄存器 SPI_WriteByte(AD7791_CMD_READ_DATA); buf[0] SPI_WriteByte(0xFF); buf[1] SPI_WriteByte(0xFF); buf[2] SPI_WriteByte(0xFF); AD7791_CS_HIGH(); result ((uint32_t)buf[0] 16) | ((uint32_t)buf[1] 8) | (uint32_t)buf[2]; return result; }三个字节的拼包顺序是高位在前这是 AD7791 固定的输出格式。如果读回来的数据高低字节颠倒了检查 SPI_FirstBit 是否配成了 MSB。STM32 SPI 默认是 MSB 先行一般不会出问题但 GPIO 模拟 SPI 时很容易搞反。3.4 数据换算与标定AD7791 的输出是有符号 24 位补码满量程对应范围是 -1 到 1 的比例。换算成电压时先要把原始数据转成有符号数再除以 2^23再乘以参考电压。如果增益是 16 倍还需要再除以 16。float AD7791_ToVoltage(uint32_t raw) { int32_t value (int32_t)(raw 8) 8; // 扩展符号位 float voltage (float)value / 8388608.0f * VREF; return voltage; }这段代码的关键是符号扩展。直接把 uint32_t 转成 float 会导致负电压算成很大的正数这是新手最容易踩的坑。称重应用里通常不需要转电压而是直接做两点标定把 ADC 码值映射到重量值。选一个零点砝码和一个满量程砝码记录各自的平均码值然后线性插值。4. 联调排错与实测心得这套方案我在实际项目里调过几轮也见过不少朋友遇到类似的问题。下面把最常见的问题和排查思路整理出来帮你节省一些踩坑时间。4.1 读取数据全是零怎么办如果 DOUT/RDY 引脚一直为高说明芯片没有进入转换状态。先检查复位电路RESET 不要悬空再查 SPI 通信用示波器看 SCLK 上有没有时钟。如果时钟正常但 DOUT 一直高检查 AVDD 是否真的上电很多原理图上 AVDD 和 DVDD 画在一起实际板上焊漏了。如果 DOUT 有下降沿但读回来是 0多半是 SPI 采样相位不对。从模式 3 改成模式 1 再试一次。还有一个隐藏问题发送读指令后立即连续读字节如果中间没有延时第一个字节可能读的是通信寄存器自己的回显而不是数据。工程里如果发现数据整体错位可以在读指令后加一个微秒级延时。4.2 数据跳动大、末位不稳24 位 ADC 末位跳几个 LSB 是正常现象但如果跳动达到几千个 LSB优先检查电源纹波。用示波器看 AVDD如果看到几十毫伏的周期性噪声说明磁珠和去耦电容没有起到作用。磁珠选型不对或电容离引脚太远都会导致这个问题。另一个常见原因是输入悬空。任何高精度 ADC 的输入端都不能悬空必须有一个确定的直流电位。如果只是测试把 AIN 接到 AGND看读数是否接近 0。如果接 AGND 后读数仍然乱跳说明问题不在输入信号而在电源或地线。4.3 用示波器确认 SPI 时序我调试 ADC 通信的习惯是先抓 CS、SCLK、DIN、DOUT 四路波形。重点关注 CS 拉低后SCLK 的第一个时钟沿和 DIN 第一个数据位的相对位置。如果 CS 拉低后 SCLK 立刻翻转但 DIN 数据还没建立就属于“先拉低 CS 马上发数据”太激进最好在 SPI_WriteByte 之前加一个小的延时。抓波形时使用逻辑分析仪更方便不用 4 个通道同时接。设置好 SPI 协议解码可以直接看到主机发送的每个字节对照数据手册验证指令是否正确。4.4 常见问题速查现象可能原因排查方法读不到数据SPI 未初始化或引脚复用配置错用逻辑分析仪抓波形数据为 0xFFFFFF输入悬空或超出量程检查输入信号和基准末位抖动大电源噪声或参考源不稳加大去耦电容降低更新率读数数值翻倍增益配置错误读模式寄存器确认初始化后仍为默认状态复位时序不正确检查 RESET 电路和 SPI 时钟线数据高低字节颠倒SPI 位序配错确认 MSB 先行5. 从验证到落地的经验备忘把工程跑通只是第一步真正到产品阶段还有很多细节值得打磨。5.1 低功耗场景的优化如果这套系统需要电池供电连续转换模式就显得太浪费了。AD7791 支持单次转换模式每次需要测量时启动一次转换完成后自动进入待机电流消耗大大降低。低功耗系统的主控也可以配合测量前唤醒启动转换后休眠等 DOUT/RDY 中断唤醒后再读数据。需要注意的是STM32F103C6T6 从 STOP 模式唤醒后虽然很快但 SPI 外设会被复位需要重新初始化 SPI 相关寄存器。AD7791 的配置没丢不用重新初始化。这样一整套流程下来系统平均功耗能做到毫瓦级别以下。5.2 同系列芯片替换与扩展AD7791 有一个特点它没有可编程增益和电流源功能相对精简。如果项目后续需要支持多种输入量程或做两线制 RTD 测温可以考虑换 AD7792 或 AD7793。它们寄存器结构高度兼容ADC 采集的主函数可以直接复用只需要改一小部分配置字节。预算有限的验证板也可以先用 AD779016 位版本跑通逻辑最后再换成 AD7791。两个芯片的寄存器映射一致代码改动量很小。但要注意AD7790 的精度上限低只适合做联调不适合直接当产品。5.3 最后说点实际体会这套系统设计起来不难难的是把噪声压下去、把每个隐蔽的边角处理干净。我自己在实际测试中有一个习惯不加传感器、直接把输入端短接到 AGND先看 ADC 底噪。如果底噪能稳到几个微伏级别再接传感器这样一层层排查能快速定位是前端信号问题还是后端采集问题。再分享一个小技巧如果你的数据偶尔出现一个很大的跳变先不着急加软件滤波用示波器看看是不是传感器线束在受机械振动。很多“ADC 不稳”其实不是芯片问题而是机械接触不良。把接头重新压紧问题就消失了。调试高精度采集保持怀疑和耐心比什么都重要。本文还有配套的精品资源点击获取