ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

STM32驱动AD7616:16通道同步电压采集与换算详解

STM32驱动AD7616:16通道同步电压采集与换算详解 简介这是一份基于STM32F103微控制器的AD7616电压采集工程源码面向嵌入式开发者与工业数据采集学习者演示16通道同步采样ADC的驱动移植、时序控制与数据处理方法。资源同时覆盖串行与并行两种接口方式附TFT液晶显示和多文件工程组织结构可直接导入Keil查看参考。压缩包共148个文件以24个C源文件和22个头文件为核心配合uvprojx/uvoptx工程配置、hex/axf固件、map/lst编译输出等辅助文件便于从源码到烧录、调试全流程学习整体约2.97MB。已有220人浏览学习。读者可从中获得完整的可编译工程范例包括寄存器初始化、SPI/并口读取时序、电压换算、显示刷新逻辑以及工业采集场景下的排错思路特别适合在公司内部培训、ADC驱动入门或快速搭建数据采集原型时作为参考蓝本。1. 采集电压不是读个 ADC 那么简单AD7616 值得单独写一套驱动一块 AD7616 采集板最快能在多大程度上帮你把电压数据拿到手这里讨论的不是 STM32 片内 12 位 ADC而是 16 通道、16 位、双路同步采样的独立 ADC。当 8 路电压和 8 路电流需要同时采样或者一台配电终端要求在同一时刻锁定三相电压的瞬时值STM32 片内 ADC 的通道数和同步性就不够用。AD7616 的输入范围可按 ±10V、±5V、±2.5V 配置后端可直接对接互感器调理电路前端用 STM32 的 FSMC 或 SPI 读取读出来的是二进制补码要经过换算才是真正的电压值。这篇文章从硬件接法、寄存器操作、电压换算到现场排错完整铺开适合做电力采集、电机驱动监测和电池电压巡检的嵌入式工程师也适合第一次把并行 ADC 接到 STM32 上的人。2. 硬件接线怎么选FSMC 映射还是 SPI 采集AD7616 的数字接口有并行和串行两种STM32 侧选哪种取决于芯片型号、封装以及 FSMC 是否可用。选错接口方案后面代码写得再漂亮也跑不起来。2.1 AD7616 的关键引脚与量程选择AD7616 有 16 个模拟输入通道按 A/B 两组各 8 路排列两组内部各有一个 ADC 内核由同一个 CONVST 信号触发同步采样。也就是说A 组和 B 组的数据是在同一个时刻被采样保持的这一点在计算功率、相位时特别重要。引脚类型引脚名方向说明模拟输入VA1~VA8VB1~VB8输入16 路双极性电压输入电压基准REFP/REFM输入外部基准建议用高精度基准源转换控制CONVST输入启动 A/B 两组同步转换状态输出BUSY输出转换期间为高转换结束拉低接口模式PAR/SERW/B输入选择并行/串行、16 位/8 位范围选择RANGE输入选择 ±10V/±5V/±2.5V地址/数据A0~A3DB0~DB15输入/输出并行模式下的通道选择与数据线量程配置可以由硬件引脚完成也可以通过内部配置寄存器写值具体看芯片版本和固件包的支持情况。工程上最常见的是用 RANGE 引脚整体设定一个量程因为大部分应用场景里一路调理电路下来所有通道的满量程是一致的。如果要逐通道配不同量程需要确认所选型号有没有开放对应的寄存器位不能只靠改一个全局宏就认为全部通道都生效了。2.2 并行模式把 AD7616 映射成 STM32 的一段地址空间AD7616 的并行读时序和 SRAM 非常接近CS 拉低、RD 下降沿把数据送上总线这让它可以直接挂在 STM32 的 FSMC 上。要注意STM32F1 系列只有 RE 后缀以上的型号才有 FSMC比如 F103ZE而最常见的 F103C8T6 没有这个外设。F407、F429、H7 系列的 FMC 同样支持这种接法。AD7616STM32 FSMC 接口DB0~DB15FSMC_D0~FSMC_D15CSFSMC_NE1基地址 0x60000000RDFSMC_NOEA0~A3FSMC_A0~FSMC_A3CONVST任意 GPIO 或定时器输出BUSY任意 GPIO 输入这样接好以后读某一个通道的数据其实就是读一段内存地址。代码上可以写得非常简洁#define AD7616_BASE_ADDR (0x60000000U) #define AD7616_READ(ch) (*(volatile uint16_t *)(AD7616_BASE_ADDR (((uint32_t)(ch)) 1U))) uint16_t ad7616_read_parallel(uint8_t ch) { return AD7616_READ(ch); /* 读地址即选通道FSMC自动产生CS/RD时序 */ }这个函数里的ch取值范围是 0~15对应 VA1~VB8。并行模式下AD7616 的低 4 位地址线 A0~A3 直接参与通道选择所以读不同地址就是读不同通道。因为是 16 位数据总线宽度写入到 FSMC 时的字节地址偏移是ch 1也就是每个通道间隔 2 字节这和普通 SRAM 的访问方式一样。FSMC 的时序参数刚开始可以给得宽松一些比如在 HAL 里这样配置FSMC_NORSRAM_TimingTypeDef timing {0}; timing.AddressSetupTime 15; /* 地址建立时间先放宽 */ timing.DataSetupTime 15; /* 数据建立时间先放宽 */ timing.AccessMode FSMC_ACCESS_MODE_A;跑通以后再用逻辑分析仪抓 RD 信号宽度逐步把这两个值往小调。很多新手一上来就把时序压到极限结果数据偶尔对偶尔错很难排查。先保证功能正确再谈性能。2.3 串行模式没有 FSMC 的芯片怎么接如果用的是 F103C8T6 这类没有 FSMC 的芯片最常见方案是 SPI 或 GPIO 模拟。AD7616 串行模式下主要用到 SCLK、SDO、SDI、CS 这四根线另外需要一根 GPIO 接 BUSY。以 F103C8T6 接 SPI1 为例AD7616STM32F103C8T6SCLKPA5SPI1_SCKSDOPA6SPI1_MISOSDIPA7SPI1_MOSICSPA4GPIO 输出BUSYPA3GPIO 输入串行模式每次读数据前需要按数据手册的 SPI 映射表发送命令字这个命令字可能是读某个通道转换结果也可能是写内部配置寄存器。不同版本资料里的寄存器地址定义可能不一样移植固件时不要只照抄宏定义一定要对着手头的芯片型号看一遍。SPI 时钟先用 1MHz 起步确认读写正常后再逐步提高频率。3. 用 STM32 HAL 库驱动 AD7616初始化、触发转换与读取这套代码用 HAL 库组织核心是三条GPIO 初始化、CONVST 触发、BUSY 等待。很多采集异常都出在触发和等待的顺序上先把这三个动作理清楚后面加量程、加通道就顺了。3.1 初始化GPIO、复位与供电检查AD7616 的控制引脚不多初始化也简单。CONVST 配置成推挽输出BUSY 配置成输入不要开上下拉因为 BUSY 由芯片内部驱动外部再挂一个上下拉反而可能影响电平判断。__HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitTypeDef gpio {0}; gpio.Pin AD7616_CONVST_Pin; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(AD7616_CONVST_GPIO_Port, gpio); gpio.Pin AD7616_BUSY_Pin; gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_NOPULL; HAL_GPIO_Init(AD7616_BUSY_GPIO_Port, gpio);复位时序按数据手册来一般是给 RESET 引脚一个低脉冲。如果硬件上没有引出 RESET可以通过寄存器软复位但要确认芯片上电后寄存器处于已知状态。一个简单的ad7616_reset()可以这样写void ad7616_reset(void) { HAL_GPIO_WritePin(AD7616_RESET_GPIO_Port, AD7616_RESET_Pin, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(AD7616_RESET_GPIO_Port, AD7616_RESET_Pin, GPIO_PIN_SET); HAL_Delay(1); }如果卡在后面的 BUSY 等待上先回头检查 AD7616 的模拟电源、数字电源和基准电压。AD7616 需要外部基准基准源不上电或电压不对芯片不会正常工作。用万用表量基准电压是排除问题的第一步。3.2 一次完整转换先触发再等待最后读数据AD7616 的转换流程是CONVST 给一个触发边沿BUSY 拉高转换完成后 BUSY 拉低。主控要等 BUSY 拉低以后才能去读数据。这里有一个容易犯的错就是读完一个通道马上读下一个中间没有等 BUSY 准备下一次转换导致读到的还是上一轮的数据。void ad7616_start_convert_pulse(void) { HAL_GPIO_WritePin(AD7616_CONVST_GPIO_Port, AD7616_CONVST_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(AD7616_CONVST_GPIO_Port, AD7616_CONVST_Pin, GPIO_PIN_RESET); } void ad7616_wait_busy(void) { while (HAL_GPIO_ReadPin(AD7616_BUSY_GPIO_Port, AD7616_BUSY_Pin) ! GPIO_PIN_RESET) { /* 等待转换完成必要时加超时保护 */ } }CONVST 的触发极性以数据手册为准这里用的是拉高再拉低的脉冲方式。等待 BUSY 的循环一定要加超时比如定义一个计数器超过一定次数就跳出并报错否则模拟输入悬空或者芯片损坏时程序会死在一个 while 循环里。DEBUG 阶段尤其需要超时保护。3.3 真正同步的读法一次触发连读 16 通道如果你需要 16 路同一时刻的电压值顺序必须是这样先发一次 CONVST等 BUSY然后连续读 16 个通道。因为 AD7616 的采样保持是在 CONVST 边沿同时发生的后续的读取只是把已经锁存的数据搬出来。反过来如果每个通道都先触发再读取那 16 个通道之间就有了时间差三相功率和相位计算会偏差很大。uint16_t samples[16]; ad7616_start_convert_pulse(); ad7616_wait_busy(); for (uint8_t ch 0; ch 16; ch) { samples[ch] ad7616_read_parallel(ch); }这才是 AD7616 双路同步采样的正确用法。read_parallel本身不做转换它只是通过地址线选通道并读回数据。16 路数据在总线上是分时传输的但在模拟端它们是同一时刻被锁存的。3.4 固定采样率用定时器触发 CONVST采集工频信号一般需要固定采样率比如每周期 128 点。这时可以把 CONVST 接到定时器某个输出通道由硬件产生触发脉冲采样时刻不受中断延迟影响。如果手里只有一个普通 GPIO 控制 CONVST那就在定时器中断里拉脉冲。void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM6) { ad7616_start_convert_pulse(); ad7616_wait_busy(); read_all_channels(samples); } }注意这段代码里的read_all_channels依然是轮询读取。当采样率超过 10kHz 时中断里等待 BUSY 和 16 次读取会占用大量 CPU 时间这时候要把读取动作改到状态机或 DMA 里去做。中断里只做触发主循环根据标志位再读数据是比较稳妥的过渡方案。4. 把原始码换算成电压补码、量程与校准AD7616 的输出编码是二进制补码和 STM32 片内 ADC 的无符号输出完全是两回事。直接拿 uint16_t 去显示负数会变成很大的正数这就是很多人第一次读 AD7616 时觉得“数据怪怪的”的原因。4.1 补码输出和 C 语言 int16_t 的关系以 ±10V 量程为例AD7616 的正满量程对应 0x7FFF负满量程对应 0x8000零点就是 0。这个编码方式刚好和 C 语言里的int16_t一致读取以后直接强转成int16_t符号位自然就正确了。输入电压±10V 量程原始码int16_t 值接近 10V0x7FFF327670V0x00000接近 -10V0x8000-32768raw 0xFFFF先拿到 16 位无符号值再转成int16_t得到有符号值这个转换不能省略。直接用 uint16_t 做运算后面所有换算公式都会出错。4.2 电压换算公式与不同量程的 LSB换算电压的公式很简单把有符号读数除以 32768再乘以当前量程对应的满幅值。float ad7616_voltage(int16_t raw, float range) { return ((float)raw / 32768.0f) * range; }调用时range分别传 10.0、5.0、2.5对应 ±10V、±5V、±2.5V 三种量程。这个公式里除以 32768 而不是 32767是因为补码的负半轴比正半轴多一个点按 32768 做对称除法在工程上误差小于 1 LSB而且代码更简洁。量程LSB 电压值±10V约 305.19 µV±5V约 152.59 µV±2.5V约 76.29 µVLSB 的计算公式是2 × range / 65536。如果前端有互感器或分压电阻最终显示的实际电压 ADC 口电压 × 分压比。在代码里要把分压比放在单独一个宏或配置项里不要写死在换算函数里面否则换一次互感器就要改一遍算法。4.3 零点偏移和增益误差的校准方法AD7616 的线性度很好但系统误差会来自调理电路、基准电压和 PCB 布局。最常见的两种误差是零点偏移和增益误差用两点校准就能修掉。每通道准备两个浮点数一个增益系数一个偏移量。typedef struct { float gain; float offset; } ad7616_cal_t; float ad7616_apply_cal(float measured, const ad7616_cal_t *cal) { return measured * cal-gain cal-offset; }标定方法也简单先让输入接地多次采样取平均得到mean_zero这时的offset -mean_zero。再输入一个已知电压Vref多次采样取平均得到mean_ref然后计算cal-offset -mean_zero; cal-gain Vref / (mean_ref - mean_zero);这个Vref最好用实际测量值而不是理论值因为信号源本身也有误差。校准参数可以在生产时写进 Flash 或 EEPROM每通道存一组。这样现场更换调理板或者基准芯片后不需要改固件只需要重新标定参数。4.4 采集交流电压时的 RMS 计算很多电压采集项目最终要的是有效值不是瞬时值。连续采样 N 个点后计算 RMS是最常用的做法float rms_value 0.0f; for (uint16_t i 0; i N; i) { rms_value samples[i] * samples[i]; } rms_value sqrtf(rms_value / N);计算前最好先减去直流分量否则一个小的零点偏移会让 RMS 结果明显偏大。N 要覆盖整数个工频周期比如 50Hz 信号用 20ms 窗口采样率 10kHz 就取 200 个点。窗口不是整数周期时RMS 结果会出现周期性波动这个现象在调试时很常见。5. 读回来不对时怎么排错以及过采样技巧最后这部分给几个排错方向和过采样的用法。AD7616 这类芯片问题通常不是代码逻辑难而是硬件状态没确认就急着看数据。5.1 典型异常现象与定位顺序现象优先检查一直读到 0x7FFF输入是否过压量程是否选对模拟电源是否正常一直读到 0x8000输入欠压或接线断量程和负满量程不匹配16 个通道全是 0CS/RD 没拉低FSMC 映射地址不对通道数据错位A0~A3 接线顺序反了SPI 命令字位序不对结果上下波动大基准源噪声SPI 时钟过快采样保持时间不足排查顺序我一般是先量电源和基准电压再用逻辑分析仪抓 CS、RD、BUSY 的时序最后看代码里的地址偏移。并行模式下最常见的问题就是 16 位总线宽度下的地址计算base ch和base 2*ch差一倍表现为通道 0 正常、通道 1 读到通道 2 的数据。用 ST-Link 或 J-Link 在线调试时可以直接把 FSMC 地址当指针读配合 Memory 窗口查看连续地址的数据能快速判断地址映射是否正确。5.2 用直流信号判断是前端还是 ADC 的问题把输入通道接到一个已知直流电压上比如给 ±5V 量程输入 2.5V理想读数应该是 16384 左右。如果读数和万用表量出来的一致说明 AD7616 这条链路是通的问题往往在前端调理电路。再把输入对地短接读数应该贴近 0。如果短接后还有明显偏移先怀疑调理电路的零点再回头查 PCB 铺地和 AGND/DGND 的连接方式。5.3 过采样倍率和采样率怎么取舍AD7616 的过采样功能可以压低随机噪声、提高有效位数但代价是转换时间成倍增加。如果单次转换时间是 t_conv过采样倍率设为 8那么每次有效转换时间大约是 8 × t_conv。定时器触发周期必须大于这个值否则上一次转换还没结束下一次 CONVST 又来了BUSY 永远等不到拉低。工频采样里每周波 128 点是比较常用的配置这个速率下过采样开 2 倍或 4 倍是常见的做法。过采样开得越大能支持的采样率越低。判断是否合适的办法很简单看 BUSY 两次下降沿之间的时间用示波器或逻辑分析仪量确认它比定时器中断周期短。最后说一个我常用的习惯把通道数、量程、过采样倍率和每通道校准参数统一放到一个ad7616_config_t结构体里初始化时给一个默认实例现场换互感器或改接线时只改结构体不碰驱动函数。这样每次切换工况后验证数据正确性只需要对比结构体参数和实际输入不用重新编译整个工程。本文还有配套的精品资源点击获取
返回列表