
简介这是一份基于STM32F103的AD7616多通道电压采集学习项目标注为C实际以嵌入式C语言代码为主适合从事单片机开发、ADC数据采集或工业测量场景的工程师参考。压缩包共包含148个文件整体大小仅2.97MB其中24个.c源文件和22个.h头文件构成主要代码Keil的.uvprojx工程文件可以直接打开.hex与.axf文件用于烧录和调试.o与.crf等中间文件则方便了解整个编译过程。工程中同时提供了AD7616串行和并行两种接口版本并配有STM32_TFT显示相关工程及font16、font20、font24字体文件能够帮助读者打通从模拟量采集到液晶屏显示的一整条链路。当前已有220人学习下载对于希望在公司学习代码基础上快速上手的开发者可以通过梳理AD7616初始化、时序配置、电压转换与显示更新等关键环节理解各模块之间的调用关系再迁移到自己的项目里做二次开发或功能裁剪。1. 为什么工业电压采集会选 STM32 AD7616 这套组合做变电站保护设备、电机综合保护器或者电能质量分析仪的时候前端电压互感器出来的信号基本都是 ±10V、±5V 的交流有效值直接送进 STM32 内置 ADC 是不可能的一来量程不够二来负电压会烧引脚。AD7616 这类 16 位、16 通道、可配置双极性输入的同步采样 ADC就是专门为这个场景准备的。标题里的 zip 包属于典型的「MCU 工业 ADC」固件工程核心工作是完成 SPI 驱动、寄存器配置、连续读取和双极性电压换算。这篇文章顺着这个标题把从选型理由到调试技巧的完整路径讲清楚新手能照着一行行写出来熟手也能从帧对齐和 DMA 连续采样部分拿到可复用的细节。2. AD7616 的关键特性与 STM32 侧选型理由2.1 双极性输入、双同步采样到底解决了什么问题AD7616 的逻辑结构是 6 组差分输入 V1A/V1B 到 V6A/V6B每组内部有一个同时采样保持器意味着 A 和 B 两路是在同一个时刻被采下来的不存在先后偏差。这一点对工频电压的相角测量非常重要如果 A、B 两路之间差出几十微秒换算成功率因数或者进行矢量计算时相位误差已经不可接受了。输入范围通过外部引脚 P7S0/P7S1/P7S2 或者内部寄存器配置成 ±10V、±5V、±2.5V 三档硬件量程切换在继电器行业里很常见因为电压互感器二次侧的输出有时在 100VAC 对应 ±10V有时在 57.7VAC 对应 ±5V固件里直接切量程比改硬件分压网络成本低得多。还有一个容易忽略的点AD7616 内部输入缓冲是 1MΩ 级别的阻抗可以直接挂电阻分压网络不用像有些高精度 SAR ADC 那样必须加运放跟随。整机硬件成本里省掉六颗精密运放对批量产品来说意义很大。2.2 与 STM32F103 内置 ADC 的对比对比项STM32F103 内置 ADCAD7616输入类型单端0~3.3V双极性差分/单端±10V/±5V/±2.5V分辨率12 位16 位通道数最多 16 路复用16 路分组同步采样采样保持单通道保持每组 A/B 双通道同时保持负压处理需要运放电平抬升直接输入对外通信内部寄存器SPI最高 12.8MHz实测下来用内置 ADC 做多路交流电压采集时最难受的不是分辨率而是通道切换后的建立时间和负电压抬升电路的温度漂移。12 位分辨率在 3.3V 量程下 LSB 是 0.8mV经过互感器和分压网络折算到一次侧往往已经看不出小信号畸变。AD7616 在 ±10V 量程下 LSB 是 0.305mV同样的分压精度下小信号细节能体现出来。2.3 SPI 时序约束速率、帧结构、CS 低电平期间的读写AD7616 的 SPI 从机接口是全双工同步收发一次 16 位帧中SDI 上发出的是下一次转换的通道配置SDO 上同时返回的是上一次配置的通道结果这个「前帧配置、后帧出数」的流水线特性是写驱动时最容易出错的地方。SCLK 最高支持 12.8MHzSTM32F103 的 SPI1 挂在 APB2 上72MHz 主频下分频到 9MHz 是稳妥选择。SCLK 每给出 16 个脉冲SDO 上一次的 16 位数据就移出去了。CS 在整个读取过程中要保持低电平如果中途拉高内部帧计数器会复位那整帧数据就错位了。IRQ_RDY 引脚会在新转换结果准备好时拉低这是触发读取的最佳时刻。注意 VDRIVE 引脚的电压必须和 STM32 的 I/O 电平一致AD7616 的 SDO 输出电平和 VDRIVE 绑定如果 VDRIVE 接 5V 而 STM32 是 3.3V就必须串电阻分压或者加电平转换芯片。这类问题在硬件调试时非常隐蔽往往表现为数据偶发跳变。3. STM32 SPI 初始化与 AD7616 寄存器配置的最小代码3.1 硬件接线SPI1 引脚分配与电源去耦常见的接法是 STM32F103C8T6 的 SPI1 挂 AD7616STM32 引脚AD7616 引脚说明PA5 (SPI1_SCK)SCLKSPI 时钟PA6 (SPI1_MISO)SDO数据输出到 STM32PA7 (SPI1_MOSI)SDI配置数据从 STM32 输出PA4 (GPIO)CS片选软件控制PA3 (EXTI)IRQ_RDY转换完成中断可选PB0/PB1/PB2 (GPIO)P7S0/P7S1/P7S2量程选择可选供电上AVCC 用 4.5V~5.5V 单电源DVCC 同样接 5VVDRIVE 接 3.3V。AVCC 和 VDRIVE 之间要加 10μF 钽电容并联 0.1μF 陶瓷电容尤其是 AVCC 对 AGND 的 0.1μF 电容必须尽量靠近芯片电源引脚这个电容的位置比容值更重要。CS 拉低到第一个 SCLK 上升沿之间留至少 10nsAD7616 手册里要求 CS 下降沿建立时间。STM32 GPIO 翻转速度在 2MHz 模式下一般足够但不要在极端情况下去卡这个时序。3.2 CubeMX 中的 SPI 参数配置在 STM32CubeMX 里把 SPI1 配成 Master、16 位数据宽度、MSB FirstCPOL 和 CPHA 的选择是关键。对照 AD7616 手册时序图通常配置为时钟极性 Low、第二个边沿采样也就是 SPI 模式 1。也可以反着来但写驱动时必须严格对应不要改完极性忘了改代码里的采样边沿。波特率预分频选择 8 分频9MHz低于芯片极限 12.8MHz。因为 SDO 数据在 SCLK 下降沿更新STM32 在上升沿采样留出半个周期的建立时间对布线质量一般的 PCB 更友好。如果发现读出来的数据偶发最高位错误可以先把分频改到 16排除时序余量问题。3.3 HAL 库全通道读取代码#include ad7616.h static void AD7616_CS_Low(void) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); } static void AD7616_CS_High(void) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); } uint8_t AD7616_ReadAllChannels(uint16_t *buf, uint8_t len) { uint16_t tx 0x0000; uint8_t i; if (len 12) { return 1; } AD7616_CS_Low(); for (i 0; i len; i) { if (HAL_SPI_TransmitReceive(hspi1, (uint8_t *)tx, (uint8_t *)buf[i], 1, 10) ! HAL_OK) { AD7616_CS_High(); return 2; } } AD7616_CS_High(); return 0; }这段代码的核心是 CS 低电平期间连续发送 12 个 16 位帧SDO 上按固定顺序吐出 V1A、V1B、V2A、V2B……V6B 的结果。第一次调用读出来的数据是无意义的因为 AD7616 上电后还没有产生过有效转换工程里通常在初始化后先调用一次并丢弃让内部流水线填充起来之后再读才是正确顺序。HAL_SPI_TransmitReceive的 Size 参数写 1是因为 SPI 已经配成 16 位数据宽度这里 1 代表传输 1 个 16 位字。如果配的是 8 位宽度这里就必须写成 2两种写法容易混建议统一用 16 位宽度。3.4 寄存器写入软复位与量程配置AD7616 上电默认状态是 J 型菊花链关闭、所有量程由外部引脚决定。如果硬件上把 P7S0/P7S1/P7S2 直接接了固定电平固件里不需要写任何寄存器。但用到软复位和内部量程切换时需要向 SDI 发送寄存器配置帧。常见做法是封装一个寄存器写函数帧格式按照数据手册中的配置字结构组装一般帧的高字节包含设备地址和寄存器地址低字节是待写入的数据。写完后可以紧接着读同一地址回验。工程中的ad7616_write_reg放在独立文件里并用宏定义寄存器地址避免魔法数散落在业务代码中。软复位的一个实际应用场景是重同步当 SPI 帧错位导致读到的通道顺序乱掉时软复位后重新建立流水线比不断翻转 CS 更可靠复位间隔要至少留出 4 个 SCLK 的稳定时间再开始首次读取。4. 电压换算、校准与多通道扫描采集的实现细节4.1 从补码到实际电压值的换算公式AD7616 的 16 位输出是二进制补码±10V 量程下0V 对应 0x0000正向满量程对应正向补码反向满量程对应负向补码。换算公式为float AD7616_CodeToVoltage(uint16_t code, float vref) { int16_t signed_code (int16_t)code; float voltage ((float)signed_code / 32768.0f) * vref; return voltage; }(int16_t)code是把无符号读数强行解释为有符号数这一步叫符号扩展。之后按比例映射到满量程电压vref 在 ±10V 档位填 10.0f±5V 档位填 5.0f±2.5V 档位填 2.5f。关键边界值0x7FFF 对应接近正向满量程0x8000 对应负向满量程0xFFFF 对应约 -0.3mV不是 -10V这是编程新手最容易算反的地方。换算里加一个 saturating 判断更稳妥if (signed_code 32000 || signed_code -32000) { // 标记该通道数据异常不做电压计算 }防止信号过压时算出离谱的电压值影响上层逻辑。4.2 两点校准失调误差与增益误差的修正16 位 ADC 的积分非线性通常在 ±2 LSB 以内但失调误差和增益误差会随温度漂移批量产线里每块板子都不一样。用一个固定公式去算电压精度上限大约在 0.1% 量级要提升到 0.05% 以下必须做两点校准。校准步骤输入端接地读取 100 次取平均得到失调码offset_code。输入端接标准 5.000V 直流在 ±10V 档位下读取 100 次取平均得到gain_code。增益系数gain 5.0 / ((gain_code - offset_code) / 32768.0 * 10.0)。实际测量时代入校准系数float AD7616_CalibratedVoltage(uint16_t code, float offset_code, float gain) { int16_t signed_code (int16_t)code - (int16_t)offset_code; return ((float)signed_code / 32768.0f) * 10.0f * gain; }注意校准系数计算时gain_code减掉offset_code后再参与比例换算否则把失调误差折进了增益里会导致低压段准、高压段偏。校准数据要存进片内 Flash 或外部 EEPROM掉电后必须恢复批量烧录时一般由产测工位在出厂前写入。4.3 多通道扫描时的帧顺序与通道流水线AD7616 的 12 路通道读取顺序是固定的初始化后第一次读回的是全零或无意义数据第二次起才是有效结果。连续读取时当你发出第 N 帧配置时SDO 返回的是第 N-1 帧对应的通道数据。这在非扫描模式下影响不大但在「读一帧立刻切换通道」的场景下实际读出的是上一次切换的结果差了一个周期。处理办法是把「切换指令」和「读取数据」分开两个操作先把要切换的通道配置字发出去再发一帧空数据拿回结果或者干脆保持顺序扫描不切换直接按固定顺序取数。顺序扫描方式最适合交流电压采集因为所有通道都要不存在切换代价。如果只需要其中某几路可以在扫描模式下跳过不用的通道AD7616 置位相应通道的控制位就行但要注意扫描地址必须从 V1A 开始连续编号跨组跳跃会打乱输出顺序调试时看到通道错乱先怀疑这个。5. 定时器触发 DMA 连续采样与帧重同步调试技巧5.1 用定时器 PWM 触发转换SPI DMA 搬数据工频电压采集通常按每周期 64 点或 128 点等间隔采样采样间隔由 AD7616 的 IPPS 引脚输入的脉冲触发。STM32 侧的做法是配置一个定时器输出 PWM频率就是目标采样率占空比无所谓PWM 引脚直接接 IPPS。这样转换起始时刻完全由硬件决定不受主循环任务调度影响对于需要计算相位的应用是必要手段。转换完成后 IRQ_RDY 拉低把这个引脚映射到 EXTI在中断服务函数里启动一次 SPI DMA 读取不需要 CPU 参与数据搬移。void AD7616_StartDMATransfer(uint16_t *rx_buf, uint16_t len) { AD7616_CS_Low(); HAL_SPI_TransmitReceive_DMA(hspi1, (uint8_t *)dummy_tx, (uint8_t *)rx_buf, len); }DMA 回调里HAL_SPI_TxRxCpltCallback中拉高 CS把rx_buf交给环形缓冲区。整个采样过程 CPU 只在 DMA 中断里做一个指针挪动一个 72MHz 的 F103 同时跑 7 路 AD7616 的 SPI 读取和 Modbus 协议栈也还有余量。注意 SPI DMA 模式下 CS 必须由软件控制不能在每次传输后自动翻转。工程里 CS 拉低到 DMA 启动之间需要用__DSB()确保写寄存器的操作顺序不然可能出现 DMA 已经跑起来但 CS 还没拉低的竞态问题。实际调试时用逻辑分析仪抓 CS 和 SCLK能清楚看到这个问题。5.2 帧错位重同步软复位与 CS 级联法全通道顺序扫描时如果中途有一次 SPI 通信被高优先级中断打断CS 低电平时间被拉长或者被提前拉高内部帧计数器就会错位后续所有通道的数据都往前挪了一位。此时读出来的 V1A 通道数据实际是 V1B 的所有电压值表现为相邻通道交换。重新同步的手段有两个。一是拉高 CS 至少 100ns 以上再拉低重新开始整组帧读取这个方法最快但偶尔不生效因为内部状态机可能停在半帧位置。更可靠的是软复位向配置寄存器写入软复位命令让 AD7616 回到默认状态然后重新做一次丢弃式读取清空流水线。推荐的同步代码模式void AD7616_Resync(void) { AD7616_WriteReg(AD7616_REG_SOFT_RESET, 0x0001); HAL_Delay(1); AD7616_ReadAllChannels(discard_buf, 12); }工程中如果发现某一个电压值长期偏大或偏小不要急着怀疑硬件先查询同步计数器的值看看重同步发生过多少次。连续多次重同步说明 SPI 时序余量不足。5.3 调试阶段的三个必查点位调试 AD7616 驱动时我一般会用逻辑分析仪同时抓 CS、SCLK、SDO 三条线。第一个必查点是 CS 低电平期间是否完整出现了 12 组 16 个 SCLK 脉冲少一组就说明读取长度配错了字节数。第二个必查点是 SDO 的第一位是否在 CS 拉低后的第二个 SCLK 下降沿才出现如果第一位数据出现在第一个边沿说明 SCLK 极性配反了。第三个必查点是量程引脚的电平状态直接量 P7S0 到 P7S2 的对应电压如果量程是 ±10V 而代码里按 ±5V 换算数据是准的电压值差一倍。如果遇到电脑端设备管理器里 STM32 虚拟串口有感叹号先看 VCP 驱动版本是否对应你的芯片再看 PA9/PA10 是否被其他外设占用确认无误后把打印电压值的串口波特率降到 115200用重新枚举 USB 的方式验证。驱动装好后串口输出乱码优先检查发送函数的长度参数和数据类型我见过很多次sprintf里 %d 和 uint16_t 混用导致输出错乱的情况。5.4 把电压数据接入业务层FreeModbus 与多机共享电压采集的最终目的是上传到上位机或者送给其他 MCU 做联动控制。工业现场最常见的做法是把 AD7616 的电压值换算成整型毫伏数存入 Modbus 保持寄存器然后通过 FreeModbus 协议栈的从站模式供 PLC 或触摸屏轮询。寄存器表设计时注意把通道号、量程、校准序号放进只读区上位机读一次确认就能判断数据是否可信。如果需要和 K210 之类的算力芯片配合做异常分析可以通过串口把电压值和采样时刻一起发过去注意用带帧头的结构体而不是裸发数值两边约定好波特率和字节序。一个完整的交流电压采集节点关键数据流是定时器触发转换、DMA 返回补码、换算修正、Modbus 寄存器更新。最后一个小技巧在 DMA 缓冲区的头部预留两个字节的帧序号每次补一上位机如果发现序号不连续说明发生了 DMA 丢帧这时候优先查中断优先级配置把 SPI DMA 中断优先级提到定时器中断之上丢帧概率会明显下降。本文还有配套的精品资源点击获取