ARTICLE DETAIL

资讯详情

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

GD32F303驱动AD7616:16通道ADC采集的SPI配置与调试全记录

GD32F303驱动AD7616:16通道ADC采集的SPI配置与调试全记录 做配电终端项目那阵子要在主控板上同时采 16 路交流模拟量电压、电流都要进还得支持软件切量程、留出同步采样的余地。当时对比过好几颗 ADC最后定了 AD7616MCU 选了 GD32F303。原因很直接AD7616 一颗片子就能覆盖 16 路双极性输入、16bit 分辨率最高 1MSPS 采样率内部基准和输入调理也省了不少外围GD32 在同等外设资源下成本更友好SPIDMA 这套组合做数据采集也足够稳。这篇文章把我在这个平台上的驱动思路、关键寄存器配置、GD32 外设初始化和调试中踩过的坑都整理出来。不管是从 STM32 转到 GD32 的工程师还是第一次碰 AD7616 这类 SAR ADC 的按这条思路走一遍大概率能少走很多弯路。1. 方案选型与硬件连接1.1 为什么是 AD7616 加 GD32先说项目需求。我要采的是多路工频交流信号电压、电流加在一起超过 8 路如果用 AD7606 这种 8 通道片子就得放两片不仅面积变大片间同步也要额外处理。AD7616 是真正的 16 通道双极性输入 ADC一组 8 通道输入可以用 CONVST A 触发另一组用 CONVST B 触发也可以把两组拼起来用非常适合多回线监测、配电终端这类场景。AD7616 的几个关键点放在项目里非常讨喜16 通道差分/单端灵活配置每一路由软件或硬件选择 ±10V、±5V、±2.5V 量程16bit 无失码最高 1MSPS 吞吐率内置 2.5V 基准也可以外部输入基准省掉高精度基准芯片选型数据输出支持串行 SPI 和并行接口串行模式下有 SDOA、SDOB 两个输出可以降低 SPI 时钟压力内置过采样和数字滤波器对电力现场的噪声有一定抑制能力。GD32 这边的优势就更明显了。同样主频和 Flash 容量GD32F303 的成本比同级芯片低一截而且 SPI、DMA、定时器资源都不缺GPIO 兼容 5V 输入和 AD7616 这类 3.3V/5V 混供电的模拟器件对接也方便。实际项目里 GD32F303 跑 120MHzSPI 时钟配到 9MHz 左右配合 DMA 连续采集 16 通道完全跑得动。有一点要注意GD32 虽然和 STM32 的引脚兼容性做得不错但外设库函数、寄存器位定义并不完全一致。直接拿 STM32 的工程改芯片型号这种事我试过看起来能编译跑起来就是各种玄学问题。老老实实按 GD32 标准外设库来写后面会省心很多。1.2 关键引脚连接建议我项目里的连接方式如下板子是自己画的MCU 是 GD32F303VET6ADC 是 AD7616BSTZ全部用 SPI 串行模式。AD7616 引脚功能接到 GD32F303说明CS片选PB12普通 GPIO软件控制SCLK串行时钟PB13SPI1_SCK复用模式SDI配置/命令输入PB15SPI1_MOSISDOAA 组数据输出PB14SPI1_MISOSDOBB 组数据输出暂不接或接 PB3单 SDO 模式可不接BUSY转换结束指示PB4配成外部中断输入下降沿触发RESET复位PB5普通 GPIOCONVST AA 组转换启动PB6定时器输出或 GPIO 脉冲CONVST BB 组转换启动PB7可并到 CONVST ASER/PAR串行/并行选择接 GND低电平选串行H/S硬件/软件配置接 3.3V高电平选软件配置VDRIVESDO 电平参考接 3.3V决定 SPI 输出电平REFIN/REFOUT基准输入输出2.5V使用内部基准时加去耦电容这里最容易忽略的是 VDRIVE。它决定 SDOA/SDOB 输出的高电平是多少必须和 MCU 的 IO 电平匹配。我用的是 3.3V MCU所以 VDRIVE 接 3.3V否则可能出现 MCU 读不到高电平或者长期工作损伤 IO 的问题。模拟电源 AVCC 我用了 5VDVCC 用 3.3V。AVCC 从系统 5V 经过 LC 滤波进来L 选 10uH 功率电感C 用 10uF 0.1uF 组合。REFCAP、REGCAPA、REGCAPD 这些引脚旁边必须放高质量陶瓷电容位置尽量靠近引脚这在 SAR ADC 布线上是老规矩了不要省。1.3 开发环境与调试工具项目用的 IDE 是 KEIL MDK也测试过 GD32 Embedded Builder两个都能编译 GD32F303 的工程。如果刚开始接触 GD32我建议直接去官网下对应型号的固件库和 PackKEIL 里把 Device 型号选对否则后面调试时会遇到 JTAG/SWD 无法连接或者 internal command error 这类问题。调试器我这边用了 STLINK 和 JLink 两种。STLINK 接 GD32 需要确认 SWD 驱动正常在 KEIL 里选择 CMSIS-DAP Debugger 或 ST-Link Debugger再设置好 SW 模式JLink 烧录 GD32 时要注意 DLL 版本和芯片型号选择如果提示无法识别内核最好用 JLink 官方软件更新一下驱动。串口打印用的 CH340 和 CP2102 转接板手上都有常规做法是预留一个 UART把采集到的裸值和换算后的电压值定时往 PC 发用来做数据验证。这个简单手段在驱动调试阶段比仿真器单步还实用。2. AD7616 工作模式和寄存器配置要点2.1 先搞清楚硬件模式还是软件模式AD7616 的 H/S 引脚直接决定你是用硬件跳线配置还是用寄存器配置。硬件模式下量程、通道选择这些靠外部引脚电平决定好处是驱动简单上电就能跑适合前期验证链路缺点是不够灵活改量程得动板子。软件模式下量程、通道选择、过采样等全部通过 SPI 写寄存器实现项目后期维护方便远程切换量程也容易。我项目里 H/S 接高电平走软件配置模式。还有一点AD7616 支持串行和并行两种接口SER/PAR 引脚拉低选串行。并行模式虽然吞吐更高但占用的 IO 太多GD32 的 GPIO 资源虽然够但为了给其他外设留位置我最终没有用并行方式。2.2 软件模式下的寄存器配置流程AD7616 的寄存器访问我是按数据手册的串行寄存器映射表做的。项目里用到的寄存器主要是通道选择、量程配置、过采样配置这几个具体地址和位段定义我以当时用的 AD7616BSTZ 版本 datasheet 为准换批次或换封装时最好重新核对一遍。寄存器作用项目中的配置通道选择寄存器决定参与转换的通道序列先配置 16 通道全开量程配置寄存器分别设置各路通道量程全部设为 ±10V过采样/滤波寄存器设置过采样倍数设为 x4折中采样率和信噪比诊断/状态寄存器读回状态位用来校验配置是否生效写寄存器流程我总结成固定套路复位芯片后延时一小段时间再写配置写完必须读回校验。有些工程师图省事写完寄存器不读回结果实际量程和预期不一样排查了半天才发现是寄存器位写错了。给一段示意代码void ad7616_init(void) { ad7616_reset(); delay_us(200); /* 按手册要求进入配置模式 */ ad7616_enter_config_mode(); /* 配置通道和量程 */ ad7616_write_reg(AD7616_REG_CH_SEL, 0xFFFF); /* 16通道全开 */ ad7616_write_reg(AD7616_REG_RANGE_A, 0x0000); /* A组通道量程配置 */ ad7616_write_reg(AD7616_REG_RANGE_B, 0x0000); /* B组通道量程配置 */ ad7616_write_reg(AD7616_REG_FILTER, 0x0002); /* 过采样x4 */ /* 读回校验 */ if (ad7616_read_reg(AD7616_REG_CH_SEL) ! 0xFFFF) { while (1); /* 配置失败停在调试点 */ } }实际项目中读回校验不一定要全卡死可以打日志。但这个过程必须有尤其是板子第一次上电调试时能帮你快速区分是 SPI 通信问题还是寄存器配置问题。2.3 SPI 数据帧格式与转换结果AD7616 在串行模式下每个操作帧本质上是 16bit 为主SCLK 连续给 16 个脉冲完成一次读写。CS 拉低后SDI 上送命令或配置字同时 SDOA 上输出对应的读回数据或转换结果这就是 SPI 全双工的好处。SPI 的极性和相位必须对准。AD7616 的串行时序是 SCLK 空闲为高数据在下降沿由 ADC 输出MCU 用上升沿采样比较稳妥所以 GD32 的 SPI 参数我配成 CPOL1、CPHA1对应 GD32 库里的 SPI_CK_PL_HIGH_PH_2EDGE。如果这里配反了表现就是寄存器读回全 0 或者数据错位非常容易被误判成硬件故障。转换结果默认是 16bit 二进制补码。满量程对应关系大概是±10V 量程0x7FFF 对应约 9.9997V0x8000 对应 -10V±5V 量程0x7FFF 对应约 4.9998V0x8000 对应 -5V±2.5V 量程0x7FFF 对应约 2.4999V0x8000 对应 -2.5V。换算公式统一用float voltage (int16_t)raw_value * full_scale / 32768.0f;注意要把 raw_value 先转成 int16_t 再做除法否则无符号数除以 32768 出来的结果永远都是正值。3. GD32 驱动实现细节3.1 GD32 SPI 和 GPIO 初始化GD32 的标准外设库和 STM32 SPL 风格很像但函数名和宏定义有差异。我直接用 GD32 库的写法SPI1 用 PB13/14/15 三个引脚复用功能要一起配置。void bsp_spi1_init(void) { rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_SPI1); gpio_af_set(GPIOB, GPIO_AF_5, GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15); spi_parameter_struct spi_para; spi_struct_para_init(spi_para); spi_para.trans_mode SPI_TRANSMODE_FULLDUPLEX; spi_para.device_mode SPI_MASTER; spi_para.frame_size SPI_FRAMESIZE_16BIT; spi_para.clock_polarity SPI_CK_PL_HIGH_PH_2EDGE; spi_para.prescale SPI_PSC_8; /* PCLK1 60MHz除以8得到7.5MHz */ spi_para.endian SPI_ENDIAN_MSB; spi_init(SPI1, spi_para); spi_enable(SPI1); }这几个参数里最容易错的是 frame_size。默认很多人的库初始化模板是 8bit如果不清零直接改后面读写 16bit 命令就会错位。另一个是 prescaleSPI 时钟不是越高越好AD7616 有最大 SCLK 限制还要考虑布线长度一般先配到 7.5MHz 或 9MHz跑稳定后再往上提。3.2 AD7616 底层读写函数AD7616 的寄存器操作和读取转换结果本质上都是 SPI 收发。封装成两个函数项目其他地方不要直接裸操作 SPI方便以后换接口。uint16_t ad7616_spi_xfer(uint16_t tx_data) { uint16_t rx_data 0; AD7616_CS_LOW(); while (RESET spi_i2s_flag_get(SPI1, SPI_FLAG_TBE)); spi_i2s_data_transmit(SPI1, tx_data); while (RESET spi_i2s_flag_get(SPI1, SPI_FLAG_RBNE)); rx_data spi_i2s_data_receive(SPI1); AD7616_CS_HIGH(); return rx_data; }这里要注意 CS 的时序。有些 SAR ADC 允许 CS 在 16bit 帧内保持低电平连续读取多个通道但 AD7616 的寄存器配置阶段我更习惯每帧都完整拉高再拉低保证边界干净。CS 高电平持续时间太短会导致下一帧数据错位这个问题在后面调试部分还会提到。寄存器写函数void ad7616_write_reg(uint8_t reg, uint16_t val) { uint16_t cmd ((uint16_t)reg 12) | (val 0x0FFF); /* 具体命令字格式以 datasheet 为准这里保留位组合示例 */ ad7616_spi_xfer(cmd); }寄存器读函数类似先发读命令再读回 SDOA 上的数据。不同芯片版本对命令字位定义可能有差异所以我的代码里都留了注释移植时务必对照手册。3.3 采集主流程CONVST 触发加 BUSY 判断我用的采集流程是定时器触发 CONVSTBUSY 下降沿中断通知 MCU再通过 SPI 把结果读回来。这样做的好处是采样周期完全由定时器控制不依赖 SPI 读取时间采样率稳定。主流程伪代码void main_loop(void) { while (1) { if (ad7616_conv_complete) { ad7616_conv_complete 0; /* 按通道顺序读取结果 */ for (int i 0; i AD7616_CH_NUM; i) { raw[i] ad7616_read_conversion_result(i); } /* 换算电压缓存到数组 */ for (int i 0; i AD7616_CH_NUM; i) { volts[i] (int16_t)raw[i] * 10.0f / 32768.0f; } /* 串口打印或送入上层算法 */ } } }BUSY 中断里只拉高一个标志位不要在中断里做 SPI 读取和浮点运算否则会把定时器采样周期打乱甚至导致中断嵌套冲突。这个习惯对做采集的人来说很重要。3.4 用 DMA 提升连续采集效率如果只是低速看波形轮询 SPI 就够了。但项目里要做连续的电压电流记录16 通道一路一路轮询会占用大量 CPU 时间。我后来把 SPI 读取改成 DMA 方式SPI1 的接收请求挂到 DMA1 对应通道上配合循环模式实现不定长数据的后台搬运。GD32 的 DMA 配置大致如下void bsp_dma_init(void) { rcu_periph_clock_enable(RCU_DMA1); dma_parameter_struct dma_para; dma_deinit(DMA1, DMA_CH2); dma_para.direction DMA_PERIPHERAL_TO_MEMORY; dma_para.periph_addr (uint32_t)SPI_DATA(SPI1); dma_para.memory_addr (uint32_t)rx_buf; dma_para.memory_inc DMA_MEMORY_INCREASE_ENABLE; dma_para.periph_inc DMA_PERIPH_INCREASE_DISABLE; dma_para.periph_width DMA_PERIPHERAL_WIDTH_16BIT; dma_para.memory_width DMA_MEMORY_WIDTH_16BIT; dma_para.number BUF_SIZE; dma_para.priority DMA_PRIORITY_HIGH; dma_init(DMA1, DMA_CH2, dma_para); dma_circulation_enable(DMA1, DMA_CH2); dma_channel_enable(DMA1, DMA_CH2); }DMA 循环模式是重点。普通模式搬完指定数量的数据就停了采集场景下还要不断重新开启 DMA中间可能丢数据。循环模式让 DMA 自动重新装载配合半传输中断和传输完成中断可以做成双缓冲上层在读取一块数据时 DMA 正在写另一块吞吐量能上来不少。4. 调试过程与问题排查实录4.1 读出来全是 0xFFFF 或 0x0000这是最典型的第一次上板问题。我遇到的场景是SPI 已经初始化发出读命令但 SDOA 返回全 0 或者全 1。排查步骤按优先级来用逻辑分析仪抓 CS、SCLK、SDI、SDOA 四根线看 CS 是否有明显下降沿SCLK 是否完整给到 16 个脉冲看 SDOA 在 SCLK 下降沿之后是否有数据变化如果一直是固定电平说明 ADC 可能没进串行模式或者 SDOA 引脚虚焊检查 SER/PAR 是否真的接了低电平H/S 是否按预期接了高电平这两个引脚如果悬空内部上下拉可能导致模式不对用万用表量 VDRIVE 电压SDOA 输出高电平不会超过 VDRIVE如果 VDRIVE 没电SDOA 永远读不到高电平。我那次问题的最终原因就是 SER/PAR 引脚虚焊芯片实际跑在并行模式SPI 自然读不到数据。重新补焊后一次通过。4.2 寄存器写不进去读回全 0SPI 通信正常但寄存器写不进读回来是 0。这种问题多半不是硬件而是 SPI 帧格式或者时序保持时间不满足。我总结的检查点RESET 后必须给足够的延时再操作寄存器我项目里用的是 200us比手册最小值放宽了几倍寄存器写命令的位长必须是 16bit配成 8bit 或者 24bit 都会出错CS 拉高后的保持时间太短下一帧 CS 拉低时 ADC 还处在上一帧的结尾状态命令就接收不到写完寄存器马上读回时中间加一点延时不要像普通 SPI EEPROM 那样以为写完立即生效。这里的本质是 SAR ADC 的串行接口不是简单的移位寄存器它内部有状态机CS 的高低电平宽度都会被当成时序条件。遇到这种问题用一个低速 SPI 时钟先跑比如 1MHz排除掉时序余量不足的可能再慢慢提高时钟。4.3 数据偶尔跳变通道错位数据能出来了但偶尔跳变或者通道顺序对不上。这个问题曾经困扰我一个下午。通道错位大概率是触发和数据读取的次序不对。AD7616 的 16 个通道是按组转换的CONVST A 触发后A 组转换结果按固定顺序在 SDOA 上输出如果读取时没按这个顺序取或者中间混入了配置阶段的返回数据通道肯定错位。跳变问题的检查点每个采样周期只拉一次 CONVST不要在 BUSY 还没拉低前又触发下一次BUSY 下降沿中断里只置标志主循环读到标志后再去读 SPI避免在转换过程中打断数据输出SPI 时钟频率过高也会导致采样不稳定尤其是飞线连接时我最后稳定在 7.5MHz过采样配置不同BUSY 高电平时间也不同如果配置了过采样等待时间要重新评估。4.4 采样率达不到预期16 通道全开每通道想跑到 50kSPS但实测只有 20kSPS 左右。这个不用怀疑片子多半是读取效率太低。粗略算一下。AD7616 最高 1MSPS如果 16 通道同时转换单通道等效采样率就是 1MSPS 除以 16即 62.5kSPS。但这是在 CONVST 触发、SPI 读取足够快的前提下。实际链路里每次读 16bitSPI 时钟 7.5MHz单帧理论只要 2.1us16 路加上 CS 切换和命令开销大约需要 60us 左右对应 16kSPS 左右和实测接近。想提高采样率方向有几个用双 SDO 模式SDOA 读 A 组SDOB 读 B 组SPI 需要两个 MISO 通道可以用两路 SPI 或者 GPIO 模拟第二路读取提高 SPI 时钟到 15MHz 左右前提是 ADC 手册允许且 PCB 走线质量过关减少配置帧和读结果帧之间的空闲时间用 DMA 连续拉数据。如果对同步采样有更高要求可以考虑切到并行模式用 DB 总线直接读不过 IO 占用会多很多。4.5 电源和布局带来的噪声问题采样值整体跳动范围比理论偏大或者波形上有固定频率毛刺大概率不是驱动代码问题而是电源和布局。AVCC 上不能直接并数字芯片的开关电源纹波AD7616 的 AVCC 和 DVCC 要分开走线在靠近引脚处汇合。模拟地尽量用完整地平面不要被数字信号走线切断。飞线调试时这个问题尤其明显我早期用杜邦线连接采样噪声能到十几毫伏改成 PCB 贴片连接后明显改善。还有一个容易被忽略的是基准去耦。REFCAP 引脚上的电容大小和材质直接影响内部基准噪声我用了 10uF 钽电容并联 0.1uF 陶瓷电容位置紧贴芯片效果比单只 0.1uF 好很多。5. 数据换算与后续扩展5.1 电压转换与两点校准原始值转电压的公式在上面提过但实机上直接用理论比例算出来的电压往往有零点偏移和增益误差温度一变误差更明显。项目里我做了两点校准方法很简单。用标准源给每一路分别输入 0V 和接近满量程的电压比如 ±10V 量程下输入 9V记录原始码值然后算校准系数/* raw0 是0V输入时的原始码raw1 是9V输入时的原始码 */ gain 9.0f / ((int16_t)raw1 - (int16_t)raw0); offset -(int16_t)raw0 * gain; /* 校准后的电压 */ voltage (int16_t)raw_value * gain offset;每块板子的校准系数可以存在 Flash 末尾出厂时写好运行中读取。这个做法在做批量设备时几乎是必须的否则 ADC 本身的增益误差就能带来几十毫伏的偏差。5.2 多片级联与同步采样扩展单颗 AD7616 的 16 通道不够用时可以级联多片。我的做法是把所有 AD7616 的 CONVST A/B 并联由同一个定时器输出触发这样所有片子同时开始转换天然实现同步采样。CS 用不同 GPIO 控制读取时逐片选通互不干扰。同步采样在多路电力信号分析里很重要如果各通道之间有几微秒的时间差计算相位角时误差会被放大。用一片 GD32 驱动两片 AD7616正好可以覆盖 32 路模拟量这也是这个项目的后续扩展方案。如果只是通道多但不需要同步也可以用 GPIO 模拟片选轮询读取每条 SPI 总线上挂多片程序上更省事。最后再分享一个实际经验做这种 ADC 驱动第一版不要追求高采样率和花哨功能先把单通道 1kHz 正弦波采出来在串口工具里看波形确认数据和时序没问题再慢慢扩展通道数、提高采样率、加 DMA。我见过太多同事一上来就写 16 通道 DMA 环形缓冲最后出问题根本定位不到是寄存器配置错还是 DMA 配置错。一步步来反而最快。
返回列表