资讯动态

STM32 SPI驱动AD7172高精度ADC:从实验20移植到稳定读数

发布时间:2026/9/11 20:27:03 来源:尧图企业网站定制
简介ALIENTEK MINISTM32 实验20 SPI实验配套资料包围绕STM32通过SPI总线驱动AD7172数模转换器展开面向嵌入式开发者和STM32学习者解决SPI通信协议实现与ADC驱动程序设计的关键问题。AD7172是一款16位双通道低功耗SAR型ADC广泛用于工业自动化和测量系统资料基于ALIENTEK MINISTM32平台完整演示了SPI接口初始化、时钟频率与极相配置、读写命令封装、数据解析及错误处理全过程。包体共168个文件压缩包仅3.24MB以41个C源文件、39个头文件为主体另有Keil工程配置、hex/axf固件、备份文件和脚本结构清晰便于直接打开工程边看边练。目前已有790人学习下载。资源不仅给出ad7172_init、ad7172_read、ad7172_set_config等API还覆盖通道选择、滤波器设置、中断与定时器配合等驱动细节能够帮助读者快速掌握STM32平台SPI外设驱动的完整写法为后续工业级项目积累实用代码与调试经验。1. 从 ALIENTEK 实验20 到 AD7172同样是 SPI 为什么不能直接跑手上有一块 ALIENTEK MiniSTM32做完实验20 的 SPI 读写 W25Q128很多人会顺手把同一套 SPI1 初始化直接拿去接 AD7172 高精度 ADC结果不是读到全 0xFF 就是数据完全对不上。原因不复杂实验20 的 Flash 通信本质是“发命令、写地址、收发页数据”时序只要满足 W25Q128 的数据手册就行而 AD7172 这类 24 位 Σ-Δ ADC 对 SPI 的要求完全不同——每次读写寄存器前要先写通信寄存器“指路”读转换结果要看 DOUT/RDY 引脚而不是固定延时滤波器建立时间也直接决定第一包数据能不能用。这篇博文就把这套驱动拆开从 CubeMX 的 SPI 参数、引脚规划到寄存器初始化、轮询读码和电压换算最后落到逻辑分析仪验证和压测技巧给出一套在 MiniSTM32 上能直接复现的 AD7172 驱动方案。2. SPI 主模式配置CubeMX 参数与 MiniSTM32 引脚规划2.1 CubeMX 里 SPI1 参数从实验20 默认配置改起正点原子 MiniSTM32STM32F103RCT6的 SPI 实验默认使用 SPI1挂在 APB2 总线上主频 72MHz。实验20 里 SPI 读写 W25Q128 常用预分频 4 或 8即 SCLK 为 18MHz 或 9MHz。这个速率对 FLASH 没问题但对 AD7172 太高了高精度 Σ-Δ ADC 的 SPI 接口虽然理论上支持最高 20MHz 左右但 PCB 走线、杜邦线连接、电源噪声都会劣化时序而且驱动代码里还要穿插寄存器配置和状态查询跑得太快反而容易踩到建立时间的边沿。在 CubeMX 中配置 SPI1 为 Master 模式时需要按下面这张表调整参数。注意和实验20 的默认配置有两个关键差异一是时钟极性极相位要改成 AD7172 要求的模式二是初始通信速率要压低到 1MHz 级别。参数项实验20 典型值AD7172 推荐值说明ModeMasterMasterMiniSTM32 作为主机控制 ADCDirection2 Lines Full Duplex2 Lines Full Duplex读寄存器时需要同时发命令收数据Data Size8 bit8 bit命令字和寄存器数据都以字节为单位CLKPolarityCPOLLowCPOL0HighCPOL1SCLK 空闲状态改为高电平CLKPhaseCPHA1 EdgeCPHA02 EdgeCPHA1数据在第二个时钟边沿采样NSSSoftwareSoftware使用 GPIO 控制片选不用硬件 NSSBaudRatePrescaler4 或 86472MHz 分频后约 1.125MHzFirstBitMSB FirstMSB FirstAD7172 寄存器字段均为 MSB 先行提示AD7172 系列通常配置为 SPI Mode 3即 CPOL1、CPHA1。部分型号也支持 Mode 1但初始化最好先用 Mode 3 验证通信跑通后再回头对比时序图确认。把预分频设为 64 后SCLK 大约是 1.125MHz这个速率下即使用杜邦线连接 AD7172 评估板也不容易出问题。等复位、读 ID、配置寄存器这些流程都验证完毕再逐步提高速率到 2.25MHz 或 4.5MHz。注意 STM32 的 SPI 波特率寄存器只有有限的几档72MHz 分别除以 2、4、8、16、32、64、128、256选择最接近且不超过目标值的一档即可。2.2 引脚与电平MiniSTM32 3.3V 与 AD7172 电源域实验20 已经把 SPI1 的引脚固定下来了PA5 做 SCKPA6 做 MISOPA7 做 MOSI。这三个引脚直接复用即可不需要改板子。需要额外分配的是 AD7172 的片选 CS 和数据就绪引脚 DOUT/RDY。推荐用 PA4 做软件片选避开 JTAG 占用的 PA15。DOUT/RDY 建议接到 PA0 或 PB0 这类支持外部中断的引脚后续如果想从轮询改成中断读取硬件上不用再飞线。下面是一张典型的引脚连接表STM32 引脚方向AD7172 引脚说明PA5 / SPI1_SCK输出SCLKSPI 时钟空闲为高PA6 / SPI1_MISO输入DOUT/RDYADC 数据输出兼作转换完成指示PA7 / SPI1_MOSI输出DIN命令字和数据写入PA4 / GPIO 推挽输出CS软件片选低有效3.3V电源AVDD/DVDD见下方电平说明GND电源GND/AGND/DGND单点接地或短接电平这里有个容易踩的坑。MiniSTM32 板载的 IO 是 3.3V 电平而 AD7172 的 AVDD 如果接到 5V它的 SCLK、DIN、CS 输入高电平阈值会按照 5V 电源域计算3.3V 的高电平虽然通常在 VIH 范围内但余量很小加上杜邦线噪声可能导致误判。稳妥的做法是把 AD7172 的 AVDD 也接到 3.3V这样电平完全匹配而且 AD7172 在 3.3V 下依然能保证数据手册中的性能指标。如果系统里已经有 5V 基准或 5V 模拟前端再考虑加电平转换芯片不要在 MiniSTM32 上直接硬怼 5V。DOUT/RDY 引脚还要注意它是开漏输出需要外部上拉电阻到 DVDD。许多 AD7172 评估板上已经有上拉但如果你是自己画的转接板必须在 DOUT/RDY 和 DVDD 之间接一个 10kΩ 到 100kΩ 的上拉否则轮询该引脚时会一直读到低电平误以为转换永远完成。2.3 硬件片选还是软件片选改掉实验20 的片选逻辑这里专门说一下片选方案因为实验20 和 AD7172 驱动在这一点上的处理方式不一样。实验20 读写 W25Q128 时片选在整个命令-地址-数据帧期间一直拉低操作完再拉高AD7172 要求更严格片选必须在第一个 SCLK 有效沿之前拉低并且在最后一个 SCLK 结束之后保持低电平一小段时间再拉高。用硬件 NSS 的话STM32 会在每次字节传输结束时自动释放 NSS这正好破坏了 AD7172 的多字节寄存器读写帧。所以在 AD7172 驱动里要禁用硬件 NSS改用 GPIO 软件控制片选。STM32CubeMX 中的设置是 NSS 选择 Disable然后在 GPIO 配置里把 PA4 设为推挽输出。HAL 库生成的初始化代码框架如下void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_HIGH; /* CPOL 1空闲高电平 */ hspi1.Init.CLKPhase SPI_PHASE_2EDGE; /* CPHA 1第二个边沿采样 */ hspi1.Init.NSS SPI_NSS_SOFT; /* 禁用硬件片选 */ hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } __HAL_SPI_ENABLE(hspi1); }这段代码里最关键的是SPI_POLARITY_HIGH和SPI_PHASE_2EDGE这两项和实验20 的默认值不同。CPOL1 表示 SCLK 空闲时为高电平CPHA1 表示数据在 SCLK 的第二个边沿这里就是下降沿被主设备采样。AD7172 在 SCLK 下降沿输出数据在上升沿采样输入正好和 Mode 3 的时序匹配。GPIO 片选的控制宏建议这样定义方便后续在每个读写函数里统一使用#define AD7172_CS_LOW() HAL_GPIO_WritePin(AD7172_CS_GPIO_Port, AD7172_CS_Pin, GPIO_PIN_RESET) #define AD7172_CS_HIGH() HAL_GPIO_WritePin(AD7172_CS_GPIO_Port, AD7172_CS_Pin, GPIO_PIN_SET)3. AD7172 寄存器与命令字先建通信层再谈采样3.1 通信寄存器是什么每次读写前先“指路”AD7172 有十几个控制寄存器但 SPI 接口只留了一个通信寄存器作为入口。读写任何寄存器之前主机必须先向通信寄存器写一个命令字节告诉 ADC 接下来要访问哪个寄存器、是读还是写。这个机制和实验20 里 W25Q128 的“发命令字节 地址字节”有点类似但 AD7172 的通信寄存器可寻址范围更大而且读和写的命令字节格式不同。通信寄存器命令字节的格式大致是最高位是启动位之后是读写方向位中间是寄存器地址位。驱动里通常把命令字节的构造封装成宏避免在每个函数里手工拼位#define AD7172_REG_ID 0x04 #define AD7172_REG_ADC_MODE 0x08 #define AD7172_REG_IF_MODE 0x0C #define AD7172_REG_CHMAP 0x20 #define AD7172_REG_FILTER 0x28 #define AD7172_REG_DATA 0x5D #define AD7172_CMD_READ(reg) (0x40 | ((reg) 1) | 0x01) #define AD7172_CMD_WRITE(reg) (0x40 | ((reg) 1) | 0x00)提示上面寄存器地址是从常见 AD7172-4 驱动中整理的一组不同批次或不同型号AD7172-2、AD7172-6的寄存器映射可能有差异上板前务必对照你手上数据手册的 Register Map 确认。读操作的标准时序是拉低 CS发送读命令字节然后主机继续提供 SCLK 时钟ADC 在 MISO 上依次移出寄存器数据最后拉高 CS。写操作则是拉低 CS发送写命令字节再发送要写入的数据字节拉高 CS。整个过程中 CS 不能被释放否则 ADC 会认为通信帧结束丢掉后面的字节。3.2 上电复位与 ID 校验判断 SPI 是否真的通很多人在 AD7172 驱动上卡住的第一关不是代码逻辑而是 SPI 根本没通。判断 SPI 通信是否建立的最快方法是读 ID 寄存器。但在读 ID 之前必须先复位 ADC。AD7172 的复位方法比较特殊将 DIN 拉高连续给出至少 64 个 SCLK 脉冲。这段复位时序可以这样实现void AD7172_Reset(void) { uint8_t dummy 0xFF; AD7172_CS_LOW(); for (int i 0; i 8; i) /* 8 字节 x 8 bit 64 个 SCLK */ { HAL_SPI_Transmit(hspi1, dummy, 1, 100); } AD7172_CS_HIGH(); HAL_Delay(10); /* 等待内部上电复位完成 */ }代码中发送0xFF是为了确保 DIN 在整个复位期间保持高电平。8 次单字节发送合起来正好 64 个 SCLK。HAL_SPI_Transmit是阻塞发送每次发送之间 SCLK 会有短暂的间隙但 CS 一直保持低电平这对复位时序没有影响。复位后延时 10ms 是最保险的做法如果后续发现 ID 偶尔读不到可以把延时加到 50ms。复位完成后就可以读 ID 了。读 ID 需要构造读命令字节通过 HAL 的收发函数一次完成命令发送和两个字节的数据接收uint16_t AD7172_ReadReg(uint8_t regAddr) { uint8_t txBuf[3] {0}; uint8_t rxBuf[3] {0}; uint16_t value 0; txBuf[0] AD7172_CMD_READ(regAddr); /* 第一个字节是读命令 */ txBuf[1] 0x00; /* 后续字节由主机提供 SCLK */ txBuf[2] 0x00; AD7172_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, txBuf, rxBuf, 3, 100); AD7172_CS_HIGH(); value ((uint16_t)rxBuf[1] 8) | rxBuf[2]; return value; }HAL_SPI_TransmitReceive同时收发三个字节第一个字节把读命令移出去此时 MISO 上返回的是无效数据直接丢弃第二个和第三个字节由主机产生 SCLKADC 在 MISO 上依次移出寄存器的高字节和低字节。返回值的拼装方式是把rxBuf[1]左移 8 位后与rxBuf[2]相或。注意这段代码依赖 CS 在三次字节传输期间一直为低所以不能把命令发送和数据接收拆成两个独立的 HAL 调用。AD7172 的 ID 寄存器内容因型号而异一般是一个固定值。校验代码可以写成uint16_t id AD7172_ReadReg(AD7172_REG_ID); if (id 0xD0) { /* AD7172-4 常见 ID具体值以数据手册为准 */ } else { /* 通信异常或型号不对 */ }如果读回的全是0xFF基本可以断定片选没拉低、SCLK 极性接反或者 SPI 未使能。如果读回的全是0x00检查 DOUT/RDY 的上拉电阻是否焊接以及 AD7172 是否一直处于复位状态。3.3 初始化序列时钟、滤波器与输入范围怎么选ID 校验通过后初始化配置寄存器。AD7172 的初始化通常遵循固定的顺序先配置 ADC 模式寄存器时钟源、转换模式再配置接口模式寄存器数据格式、状态字接着配置滤波器寄存器输出速率、滤波类型最后配置通道寄存器和设置寄存器输入引脚映射、PGA 增益。滤波器配置是整个初始化里对数据质量影响最大的部分。AD7172 内置 sinc5 和 sinc1 滤波器可以组合出不同的输出速率和工频抑制效果。一个典型的配置是把输出速率设为 50SPS使用 sinc5 滤波器这样既能抑制 50Hz 工频干扰又有较低的噪声。滤波器寄存器需要设置滤波类型和输出速率代码大致如下void AD7172_Init(void) { AD7172_Reset(); if (AD7172_ReadReg(AD7172_REG_ID) ! 0xD0) { /* 处理通信异常 */ return; } /* ADC 模式内部时钟连续转换模式 */ AD7172_WriteReg(AD7172_REG_ADC_MODE, 0x8000); /* 接口模式数据格式 24 位无状态字 */ AD7172_WriteReg(AD7172_REG_IF_MODE, 0x0000); /* 滤波器sinc5输出速率 50SPS具体字段值查手册滤波器表 */ AD7172_WriteReg(AD7172_REG_FILTER, 0x0012); /* 通道 0AIN0 为正输入AIN1 为负输入使能通道 */ AD7172_WriteReg(AD7172_REG_CHMAP, 0x0001); /* 设置寄存器PGA 1使用外部基准 */ AD7172_WriteReg(AD7172_REG_SETUP, 0x0000); }每个寄存器的 16 位字段和具体位定义在数据手册里都有表格上板前建议把每个写入值和手册里的 Bit Description 对照一遍重点确认ADC 模式寄存器的 MODE 位是连续转换还是单次转换滤波器寄存器的 ODR 位对应的实际输出速率通道寄存器的 AINP/AINN 选择位是否和你实际接线一致。这几个字段配置错了后面读出的数据可能有效但对应错误的输入通道排查起来非常隐蔽。4. 驱动落地轮询 DOUT/RDY、读码与码值换算4.1 最小读写函数NSS 时序要与命令帧严格对应前面已经写了AD7172_ReadReg现在补上写寄存器的函数。和读函数对称写函数发送命令字节加两个数据字节整个过程中 CS 保持低电平void AD7172_WriteReg(uint8_t regAddr, uint16_t value) { uint8_t txBuf[3] {0}; txBuf[0] AD7172_CMD_WRITE(regAddr); /* 写命令 */ txBuf[1] (uint8_t)(value 8); /* 数据高字节 */ txBuf[2] (uint8_t)(value 0xFF); /* 数据低字节 */ AD7172_CS_LOW(); HAL_SPI_Transmit(hspi1, txBuf, 3, 100); AD7172_CS_HIGH(); }这里用的是HAL_SPI_Transmit而不是TransmitReceive因为读函数才需要关心 MISO。写寄存器的时序要求 CS 从第一个字节的起始位之前拉低直到最后一个字节的停止位之后才能拉高。AD7172_CS_LOW()和AD7172_CS_HIGH()之间不能插入任何其他 GPIO 操作或者长延时否则 ADC 的片选超时逻辑可能把这次写操作丢弃。如果系统里还跑着 RTOS 或者有多个任务会并发访问 SPI 总线应该在AD7172_CS_LOW()之前加互斥锁或临界区保护否则两个任务同时发起 SPI 通信会导致 MISO 数据错乱。常见做法是关中断保护读写函数或者在 RTOS 里用一个二值信号量包住整个 CS 低到 CS 高的过程。4.2 等 DOUT/RDY 再读数据不要用固定延时AD7172 的 DOUT/RDY 引脚在转换结果准备好时拉低主机应该在这个引脚变低后再发起读数据操作而不是靠延时盲等。使用固定延时的做法在采样率低时看起来没问题但一旦修改输出速率或者切换通道延时估值就不可靠了而且白白浪费 CPU。轮询方式代码如下#include main.h #define AD7172_RDY_PORT GPIOA #define AD7172_RDY_PIN GPIO_PIN_0 int32_t AD7172_ReadData(void) { uint8_t txBuf[4] {0}; uint8_t rxBuf[4] {0}; uint32_t raw 0; int32_t val 0; uint16_t timeout 0xFFFF; /* 等待 DOUT/RDY 引脚变低表示转换完成 */ while (HAL_GPIO_ReadPin(AD7172_RDY_PORT, AD7172_RDY_PIN) GPIO_PIN_SET) { if (--timeout 0) { return 0; /* 超时处理 */ } } txBuf[0] AD7172_CMD_READ(AD7172_REG_DATA); /* 读数据寄存器 */ AD7172_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, txBuf, rxBuf, 4, 100); AD7172_CS_HIGH(); /* 24 位数据rxBuf[1] 高位rxBuf[3] 低位 */ raw ((uint32_t)rxBuf[1] 16) | ((uint32_t)rxBuf[2] 8) | (uint32_t)rxBuf[3]; /* 有符号数扩展第 23 位是符号位 */ val (int32_t)(raw 8) 8; return val; }while循环里读 DOUT/RDY 引脚拉低说明数据寄存器已经更新。超时计数timeout防止 ADC 掉线时死循环。发送读数据命令后主机需要继续提供足够的 SCLK 时钟让 ADC 把 24 位数据全部移出这里一次收发 4 个字节第一个字节是命令后三个字节是数据。raw按大端方式拼装(int32_t)(raw 8) 8是标准的 24 位有符号数符号扩展写法先左移 8 位把符号位挪到第 31 位再算术右移 8 位最高位自动填充符号位。4.3 单次与连续转换三种读数据方式的代码差异AD7172 支持单次转换、连续转换和连续读三种数据读取方式驱动里按需选择。多数采集场景用连续转换配置好 ADC 模式寄存器后ADC 自动以设定速率不断转换主机检测到 DOUT/RDY 拉低就读取最新结果。单次转换模式适合需要精确控制采集时刻的场景。每读一次数据前先把 ADC 模式寄存器改成单次转换等待转换完成再读数据。代码和连续模式的区别在于读数据前多了一步写模式寄存器的操作int32_t AD7172_ReadSingle(void) { /* 写 ADC 模式寄存器MODE 位设为单次转换 */ AD7172_WriteReg(AD7172_REG_ADC_MODE, 0x2000); /* 等待 DOUT/RDY 拉低后读数据 */ return AD7172_ReadData(); }连续读模式更省命令开销配置好寄存器后主机可以持续提供 SCLKADC 会自动输出当前通道的转换结果。但连续读模式下 DOUT/RDY 引脚的电平变化不如前两种模式直观调试初期不建议启用。读回 24 位码值后要把它换算成真实的电压值。AD7172 是双极性 ADC输入范围为 ±VREF/PGA24 位码值的满量程对应 83886082 的 23 次方。换算公式如下float AD7172_CodeToVoltage(int32_t code, float vref, float pga) { return (float)code * vref / (8388608.0f * pga); }如果设置 PGA 为 1VREF 为 2.5V那么码值 8388607 对应约 2.5V-8388608 对应约 -2.5V。实际使用时注意 AD7172 的正满量程是 VREF/Gain × (1 - 1/2^23)上面公式在这个精度下完全可以接受但做计量级应用时需要用校准系数修正。5. 验证与压测把 SPI 时序、码值和“有效位”都过一遍5.1 用逻辑分析仪核对命令与 MISO 时序驱动跑通后第一步是用逻辑分析仪抓SCLK、MOSI、MISO和CS四根线的波形确认时序和 AD7172 数据手册里的 Serial Interface Timing 图一致。采样率至少设为 SCLK 的 10 倍比如 SCLK 为 1.125MHz逻辑分析仪就用 20MHz 或更高的采样率。触发条件设置成CS下降沿这样每次通信帧开始都会抓到一帧完整数据。重点检查三个位置CS 下降沿是否在第一个 SCLK 有效沿之前至少几十纳秒SCLK 空闲电平是否为高最后一个字节结束后 CS 上升沿是否晚于最后一个 SCLK。如果看到 SCLK 空闲电平是低说明 CPOL 设置反了如果发现 MISO 上的数据比 MOSI 命令晚半拍说明 CPHA 或者采样沿不对。这三个问题在实验20 里不常见但在 AD7172 驱动里是最容易犯的。5.2 三个易踩坑滤波整定、硬件片选、基准噪声调试过程中有三个坑几乎每次都会遇到整理成一张排查表方便对照症状可能原因处理方法ID 永远是 0xFFFF片选未拉低或 SCLK 极性不对用逻辑分析仪看 CS 时序确认片选宏生效数据跳动超过期望值滤波器 ODR 设置过高或基准电源噪声大把 ODR 降到 50SPS检查基准去耦电容读到的电压和输入不符通道寄存器 AINP/AINN 映射接反对照通道选择位检查交换正负输入偶发读回 0上一帧 CS 释放太早SPI 时序临界降低 SCLK 预分频加大 CS 拉高前的间隔按键或 GPIO 操作导致数据异常软件片选期间被打断读写函数加临界区保护或者上 RTOS 互斥压测时把 ADC 正负输入短接用公式换算后的电压值应当稳定在零点附近。AD7172 在 50SPS 输出速率下峰峰值跳动通常在 2 到 5 个 LSB 以内如果换算成电压后跳了几百个微伏先怀疑基准电压源而不是 ADC 本身。MiniSTM32 板上常用 3.3V LDO 供电LDO 本身噪声可能就有几十微伏量级做高精度测试时给 AD7172 的 REF 引脚接一个独立的低噪声基准芯片比如 2.5V 或 4.096V 的基准源才是定位问题的正确起点。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价