STM32+AD7190四路高精度电压采集实战:SPI配置与数据处理 📅 发布时间:2026/9/10 2:25:07 👁 浏览次数: 简介面向需要高精度模拟量采集的STM32开发者这份软件Demo源码演示了24bit ADC AD7190的四路单端电压采集与串口打印完整流程。资源基于STM32官方HAL库编写主函数清晰展示外设初始化、AD7190检测与单极性多通道配置等关键步骤适合做仪器仪表、传感器数据采集等项目参考。压缩包共660个文件以C源码和头文件为主包含IAR与Keil工程文件、链接脚本及配置文件整体仅6.29MB便于快速导入工程验证。已有1599人学习下载资源附带编译脚本与调试描述文件可帮助开发者快速定位硬件连接与寄存器配置要点降低24bit ADC的入门门槛。1. 从一片AD7190到四路高精度电压这套DEMO在解决什么问题做多路电池巡检或者高精度传感器采集时STM32内置ADC的12bit分辨率和那几十毫伏的噪声很容易把人逼疯。与其在模拟前端堆运放不如直接上一颗24bit的Σ-Δ ADC——AD7190就是这一档里比较常用的选择。这套DEMO是YS-F4Pro开发板上的STM32F4通过SPI读取AD7190的四路单端电压再把24bit原始码值和换算后的电压值通过串口打印出来。你拿到之后可以直接烧录插上串口就能在调试助手里看到通道数据硬件部分只要保证AD7190模块供电和SPI接线正确剩下的寄存器配置、通道切换、数据读取流程都可以在这个工程模板上改。对于正在选型高精度ADC、又不想从寄存器手册第一页开始啃的人这套例程是一个很好的起点。2. 把SPI先跑通AD7190寄存器映射与HAL初始化2.1 为什么AD7190对SPI时序这么敏感AD7190的通信接口是四线SPI但它的SPI协议和普通Flash/RAM不太一样每次寄存读写都需要先发一个8bit命令字告诉芯片接下来要读或写哪个寄存器然后再跟数据字节。写数据寄存器时要连续读三次把三个字节合并成一个24bit码值。这个过程中片选必须一直拉低中间任何一次GPIO抖动都可能导致读回的数据错位。更关键的是时钟极性和相位。AD7190要求SCLK空闲为高数据在下降沿由芯片输出、上升沿被主机采样所以STM32端SPI必须配置成Mode 3也就是CPOL1、CPHA1。如果你用标准示例里的Mode 0去初始化大概率能读到数据但高位会随机跳变看起来像接触不良。另外SCLK频率不要图快AD7190的MCLK通常由外部晶振或内部时钟决定SCLK过高会导致输出数据建立时间不足datasheet建议一般控制在几MHz以内demo工程里放在低速档位是合理的数据率不高的时候根本不需要把SPI跑满。2.2 硬件接线与SPI初始化代码在实际接线时STM32和AD7190的连接表格如下AD7190引脚STM32引脚说明SCLKSPI2_SCKSPI时钟空闲高DINSPI2_MOSI主机发送给AD7190DOUT/RDYSPI2_MISOAD7190输出数据/数据就绪信号CSPB12片选软件控制AIN1-AIN4模拟输入四路单端信号输入AINCOM信号地GND单端测量公共端AVDD/DVDD3.3V或5V按模块要求供电GNDGND共地SPI外设初始化可以直接用CubeMX生成也可以手动写。工程里使用了HAL库初始化函数大致是这样的SPI_HandleTypeDef hspi2; void MX_SPI2_Init(void) { hspi2.Instance SPI2; hspi2.Init.Mode SPI_MODE_MASTER; hspi2.Init.Direction SPI_DIRECTION_2LINES; hspi2.Init.DataSize SPI_DATASIZE_8BIT; hspi2.Init.CLKPolarity SPI_POLARITY_HIGH; /* CPOL1 */ hspi2.Init.CLKPhase SPI_PHASE_2EDGE; /* CPHA1 */ hspi2.Init.NSS SPI_NSS_SOFT; hspi2.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; hspi2.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi2); }这段配置里有几个参数会直接影响AD7190稳定性SPI_POLARITY_HIGH和SPI_PHASE_2EDGE锁定了Mode 3SPI_BAUDRATEPRESCALER_64把SPI时钟压到比较安全的速度如果系统主频168MHzAPB1上的SPI2实际约2MHz多一点此时多数情况下能稳定读取。主频不同或模块走线较长时可以改用分频128或256先保证通信再逐步提高。2.3 上电自检读ID和初始化函数工程中main函数里调用AD7190_Init()来判断芯片是否存在这个函数的第一步通常是读ID寄存器。AD7190的ID寄存器复位值为0x4D通过SPI发命令字0x45读寄存器5再读回一个字节uint8_t ad7190_CheckID(void) { uint8_t cmd 0x45; /* 读ID寄存器 */ uint8_t id 0; AD7190_CS_LOW(); HAL_SPI_Transmit(hspi2, cmd, 1, 10); HAL_SPI_Receive(hspi2, id, 1, 10); AD7190_CS_HIGH(); return (id 0x4D) ? 1 : 0; }读ID之后还需要对模式寄存器和配置寄存器做一次初始化把芯片放到连续转换模式。注意刚上电的AD7190内部模拟部分需要几个毫秒稳定时间main函数里反复调用AD7190_Init()配合HAL_Delay(1000)这个重试逻辑不是多余的如果外部传感器或基准源上电慢第一次检测失败很正常稍等一下再检测就能通过。这个ID校验虽然简单但它能帮你把SPI接线、电平转换、时钟极性等问题一次性筛掉。3. 四路单端扫描配置寄存器、通道切换与连续读取3.1 CONF寄存器怎么决定四路单端AD7190的配置寄存器CONF地址0x02是这套DEMO的核心。CONF是16bit寄存器定义了很多开关极性、增益、斩波、通道选择、缓冲等。这个工程要实现四路单端电压采集最关键的是三个部分单极性模式、增益1倍、通道选择。单极性模式下AD7190的输入信号相对AINCOM不能为负正好适合测0到几伏的电压增益1倍时满量程输入等于基准电压通常用2.5V或5V基准。通道选择位决定当前转换的是哪一路模拟输入AD7190在单端接法下AIN1-AIN4分别对AINCOM构成四个测量通道。虽然AD7190本身是Σ-Δ架构不能像SAR型ADC那样多通道同时采样但通过配置寄存器快速切换可以在中低速测量场景下近似完成四路轮询。为了让代码可读性更好工程里一般把位段定义成宏#define AD7190_CONF_CH_AIN1 (0x0000u) /* AIN1 - AINCOM */ #define AD7190_CONF_CH_AIN2 (0x0001u) /* AIN2 - AINCOM */ #define AD7190_CONF_CH_AIN3 (0x0002u) /* AIN3 - AINCOM */ #define AD7190_CONF_CH_AIN4 (0x0003u) /* AIN4 - AINCOM */ #define AD7190_CONF_GAIN_1 (0x0000u) /* 增益1倍 */ #define AD7190_CONF_UNIPOLAR (0x0000u) /* 单极性实际位段取0 */ #define AD7190_CONF_CHOP_EN (0x8000u) /* 斩波使能按需开 */这些位的位置与datasheet上CONF寄存器位图一一对应具体数值以数据手册为准。在demo工程中ad7190_unipolar_multichannel_conf()的本质就是把这些宏按位或起来写回配置寄存器。3.2 通道切换与连续读取实现完整的四路单端采集流程是先写CONF寄存器选中通道0等待该通道完成一次转换读数据寄存器得到24bit码值然后写CONF选中通道1等待后读数据依此类推。下面这个函数把四路轮询封装成一个数组输出void ad7190_unipolar_multichannel_conf(void) { /* 单极性、增益1、关斩波后续切换通道时只改CH位 */ uint16_t conf AD7190_CONF_UNIPOLAR | AD7190_CONF_GAIN_1; ad7190_write_reg(AD7190_REG_CONF, 2, (uint8_t *)conf); } void ad7190_read_4ch(uint32_t raw[4]) { const uint16_t ch_map[4] { AD7190_CONF_CH_AIN1, AD7190_CONF_CH_AIN2, AD7190_CONF_CH_AIN3, AD7190_CONF_CH_AIN4 }; uint8_t data[3] {0}; for (int i 0; i 4; i) { uint16_t conf AD7190_CONF_UNIPOLAR | AD7190_CONF_GAIN_1 | ch_map[i]; ad7190_write_reg(AD7190_REG_CONF, 2, (uint8_t *)conf); /* 等待SDO拉低表示数据就绪 */ while (HAL_GPIO_ReadPin(AD7190_DOUT_GPIO_Port, AD7190_DOUT_Pin) ! GPIO_PIN_RESET); ad7190_read_reg(AD7190_REG_DATA, 3, data); raw[i] ((uint32_t)data[0] 16) | ((uint32_t)data[1] 8) | ((uint32_t)data[2]); } }这里有一个很容易被忽略的点切换通道后不能立刻去读SDO因为AD7190在通道切换后需要几个转换周期来稳定。工程里如果开启了较低的输出数据率比如10SPS每次通道切换后轮询SDO拉低是没问题的但如果把数据率调高到几百SPS代码里最好加一个HAL_Delay或者等待固定次数否则会读到上一次通道的残留数据。ad7190_write_reg和ad7190_read_reg是按命令字格式封装的底层级函数。写寄存器时先发命令字命令字Bit7为0表示写操作后跟寄存器地址然后再发送数据。读寄存器时命令字Bit7为1随后读回数据。数据寄存器是24bit所以读三次合并。工程里源码的文件名main.c.bak保留了现场说明这个demo还留有改动痕迹实际移植时可以直接抠这三个函数。3.3 24bit码值到电压的换算方法拿到raw值后不能直接当电压打印。AD7190单极性模式下的码值范围是0到0xFFFFFF对应0到正满量程。电压计算公式是float vref 2.5f; /* 实际基准电压 */ float gain 1.0f; float voltage (float)raw * vref / (gain * 0xFFFFFFu);这里的0xFFFFFF对应24bit全满码而不是2^24因为码值从0开始计数。假设基准是2.5Vraw为0x800000计算出的电压约1.25V。如果实际板子上基准不是2.5V或者打开PGA设置了增益就必须把vref和gain都改成实际值。很多新手在串口打印时直接把raw打出来然后发现数值不停跳动其实不是芯片问题而是还没有做转换和滤波。4. 串口把数据送出去printf重定向与帧格式设计4.1 printf重定向到UART1工程里串口初始化走的是MX_DEBUG_USART_Init()代码中把调试串口映射到了USART1引脚PA9/PA10波特率默认115200。为了能在main里直接用printf需要重定向标准输出到HAL的UART发送函数。典型的做法是在usart.c或者main.c里实现#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 100); return ch; }这段代码让所有printf字符都从USART1发出去。注意使用HAL_UART_Transmit时第三个参数是发送超时时间100ms足够如果系统里中断比较多可以改大一些。如果工程配置了MicroLIB则不需要额外处理浮点printf参数如果使用的是标准库工程设置里要勾选“Use MicroLIB”否则printf可能需要挂载_sys_exit等底层函数编译能过但运行会卡死。4.2 输出帧格式让上位机好解析串口打印不只是给人看更多时候要喂给PySerial、串口助手或LabVIEW。四路电压数值最好用固定的逗号分隔格式每行结束加换行printf(%lu,%lu,%lu,%lu\n, raw[0], raw[1], raw[2], raw[3]);如果直接打印浮点电压用%f会让我后面滤波时的数据量变大而且在printf库较重时执行速度慢。一个更稳的写法是把电压值换算成微伏整数再打印既保留分辨率又不依赖浮点格式化int32_t uv[4]; for (int i 0; i 4; i) { uv[i] (int32_t)(((int64_t)raw[i] * 2500000) / 0xFFFFFF); } printf(ch0%d, ch1%d, ch2%d, ch3%d uV\n, uv[0], uv[1], uv[2], uv[3]);这里假设基准2.5V2500000是2.5V对应的微伏数如果把基准改成5V就换成5000000。这样上位机拿到的就是整数微伏不用再做浮点解析。4.3 串口工具的常见设置在实际调试时如果串口助手收到的全是乱码首先查波特率是否配成115200其次是CH340这类USB转串口驱动有没有装好。demo工程里的串口打印默认8位数据、无校验、1停止位多数串口工具默认也是这样不需要额外设置。如果打印内容明显重复或者丢行检查发送间隔AD7190四路轮询一周期可能几十毫秒串口端如果使用每秒任意发送并不会阻塞因为发送是异步的。但如果HAL_UART_Transmit的阻塞发送时间超过了ADC采样周期打印就会拖慢采集节奏此时应该改用DMA发送或者降低打印频率。更好的做法是让ADC数据准备好后一次性printf而不是在while循环里高频轮询打印。5. 校准、滤波与四个常见坑5.1 零点校准与满量程校准AD7190内部有零点校准和满量程校准寄存器合理使用可以减少增益误差。零点校准最简单的办法是把所有输入短接到AINCOM也就是0V读出偏移码之后每次测量都减掉这个偏移满量程校准则需要把通道输入精确接到基准电压源再反向修正。工程环境下没有高精度源时我一般用一把四位半万用表先测基准再对AD7190做单点校准比完全相信芯片出厂值要可靠得多。注意执行校准时通道选择和增益必须与正式测量一致校准结果只对当时配置生效。5.2 软件滤波平均和中值到底怎么选24bit ADC虽然分辨率高但后级噪声会把低位数据抖动得很厉害。四路单端信号如果传感器本身噪声不大简单做16次平均就能看到稳定的读数如果信号偶尔有毛刺平均会被极限值拖偏这时需要先做中值滤波去掉最大和最小值。实际操作中我习惯把四路原始码值各存一个长度为16的环形缓冲每来一个新样本先填入缓冲然后做一次排序取中值再对中值结果做指数加权平均。这个组合的延迟在低速采集中可以接受但能让串口输出的最后一位不再乱跳。5.3 四个容易踩的坑第一次调AD7190的朋友大部分时间都耗在下面这四个地方。第一上电后立刻读写SPI经常读到FF或00原因是AD7190内部还没完成复位和自检至少要等几十毫秒demo里用循环检测ID来解决是对的。第二CONF寄存器通道切换后没有等待足够时间读出来的四路数据有串扰尤其在相邻通道同时接电位器时特别明显。第三SPI的MISO多根线共用时如果AD7190的DOUT引脚没有上拉电容波形边沿太缓会导致采样可以适当降低SPI分频或加一只10kΩ上拉。第四printf重定向时没有开启MicroLIB导致程序死在printf里入口现象是串口第一帧正常后面完全卡死排查时先烧一个只发固定字符的程序确认串口链路。5.4 用原始码值验证整条链路完成上面所有步骤后验证不要只看LED闪烁要用精确电压源或高精度电阻分压从AIN1到AIN4分别输入1V、2V、3V、4V然后把串口输出的微伏值与实际计算值对比。如果四路误差都在几百微伏内说明SPI、寄存器配置和电压换算链路已经打通。最后你可以打开工程里的删除编译信息文件脚本把中间文件清一下体积会小很多方便版本管理。这套代码最终留在手里的价值不是那几行AD7190驱动而是让你知道SPI慢速设备、Σ-Δ ADC这类外设调试时应该从哪里下手。本文还有配套的精品资源点击获取