ARTICLE DETAIL

资讯详情

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

STM32 SPI驱动AD7172:从Flash例程迁移到24位ADC的完整实践

STM32 SPI驱动AD7172:从Flash例程迁移到24位ADC的完整实践 简介ALIENTEK MINISTM32 实验20 SPI实验配套资料包聚焦STM32通过SPI总线驱动AD7172双通道16位ADC的完整实现属于嵌入式驱动开发与工业采集类应用的基础实战资源适合需要学习STM32外设通信与驱动代码编写的工程师。资料包共168个文件约3.24MB包含41个头文件与39个C源文件、Keil工程文件、编译生成的hex/axf固件及map映射文件也附有src等中间文件便于直接打开工程或对照源码理解编译流程并可从源码跳转到烧录验证。已有790人学习。内容覆盖SPI时序参数配置、AD7172初始化与寄存器操作并提供ad7172_init/ad7172_read/ad7172_set_config等驱动API封装同时通过实例展示中断、定时器和GPIO在通信中的配合帮助理解SCK、MISO、MOSI、SS信号线工作机制。研读这份代码可以掌握STM32 SPI驱动外设的通用方法学习驱动分层设计与排错思路对工业自动化、医疗设备等ADC应用开发有直接参考价值。1. 实验 20 的 SPI 例程换到 AD7172 后为什么读不到数把 ALIENTEK MiniSTM32 实验 20 里的 SPI 例程从 SPI Flash 换到 AD7172 这类 24 位 ADC 上最常见的现象是CS 拉低了、SCK 也亮了、写什么寄存器好像都能编过但读回来的数据永远不是手册里的值。这里不是 SPI 没跑起来而是把“适合读 Flash 的代码”直接拿去伺候 ADC 了。实验 20 的教材对象通常是按地址读写的 FlashAD7172 要的是寄存器命令帧、数据寄存器连续读取和 DRDY 等待两者的“同一套 SPI”完全是两种用法。这篇文章要整理的是能直接落到板子上的一条路径在 STM32CubeMX 里把 SPI 配成模式 3用手动 GPIO 当片选再把 AD7172 的数据手册翻译成可执行的寄存器读写驱动。正在做采集板、看 SPI 波形看到头大的读者可以按这个顺序把驱动重写一遍。2. AD7172 的 SPI 时序和实验 20 里的 Flash 有什么不同2.1 AD7172 用什么方式表达“我要写哪个寄存器”SPI 本身不关心字节含义同样一个0x04送给 Flash 可能代表页地址送给 AD7172 却代表要访问哪个寄存器。AD7172 把第一个字节当作“通信寄存器”bit7 必须写 0bit6 是读/写控制低 6 位是要访问的寄存器地址。这个设计和 STM32 的寄存器映射完全不同也是从 Flash 例程迁移过来时最容易出错的地方。通信字节 bit7:0操作目标寄存器0x04写操作偏移 0x04 的寄存器数据寄存器附近0x44读操作偏移 0x04 的寄存器0x40 | reg读操作reg 为 0x00~0x3F 中的任意地址实际函数里一般写成0x40 | regreg 就是数据手册寄存器地址的低 6 位。注意 bit6 是 1 时表示读取不能把地址值本身直接拿来当读命令否则会把地址错当成写命令寄存器被改乱ADC 输出自然不对。这个命令字节之后从机在同一个 CS 低电平窗口里继续收发数据字节整帧结束再把 CS 拉高。这里有一个关键区别Flash 先发地址再发数据片选期间可以有多个字节的地址AD7172 则是命令字节后紧跟寄存器内容寄存器有多宽就传多少字节。实验 20 的例程如果保留 Flash 的那种“发完命令发两字节地址”的逻辑AD7172 会把地址里的数据也吞进寄存器导致第二次读出来的都是错位数据。写驱动前先把这个帧结构画在纸上比直接抄代码有用得多。2.2 时钟极性和采样沿为什么 CubeMX 里常用模式 3AD7172 的 SPI 时序图里SDO 数据通常在 SCLK 下降沿切换主机在 SCLK 上升沿采样。空闲状态下 SCLK 保持高电平数据在第二个边沿被采样对应 SPI Mode 3也就是 CPOLHigh、CPHA2 Edge。如果按 Flash 例程默认的模式 0 跑会出现读到的数据整体移位一位或完全是噪声的问题但 SCLK 波形看起来又很正常。SPI 模式CPOLCPHA适用场景说明MODE0Low1 Edge很多 NOR Flash 默认实验 20 常用MODE1Low2 Edge少数 ADC、EEPROMMODE2High1 Edge部分传感器MODE3High2 EdgeAD7172 等多数 ADI ADC 的常用模式用 CubeMX 配置 SPI1 时直接在 SPI 参数里把 Clock Polarity 设为 High、Clock Phase 设为 2 Edge生成代码后hspi1.Init.CLKPolarity SPI_POLARITY_HIGHhspi1.Init.CLKPhase SPI_PHASE_2EDGE。这两个参数对应 STM32 的CPOL和CPHA位建议在调试时先用 500 kHz 到 1 MHz 的 SCLK 跑通再慢慢提高分频倍数。MiniSTM32 板载晶振通常是 8 MHzSYSCLK 72 MHz 下 SPI1 挂载在 APB2时钟源为 72 MHz选BaudRatePrescaler_64后 SCLK 约 1.125 MHz属于比较稳的起点。2.3 片选用软件 GPIO 拉低而不是硬件 NSS实验 20 的例程里片选一般叫SPI_FLASH_CS或直接CS本质上是软件片选用普通 GPIO 输出控制。这个习惯在 AD7172 驱动里应该保留。STM32 的硬件 NSS 在 SPI 传输结束时可能自动释放或者被 SPI 主模式下的 NSS 输出逻辑提前切换而 AD7172 要求一整条读命令加数据期间 CS 必须保持低电平。如果非要谈“SPI 硬件片选与软件片选”的取舍我的建议是ADC 驱动一律用软件片选因为寄存器帧长度不固定硬件 NSS 在字节之间可能产生的时间窗口会造成帧错位。CubeMX 里把 NSS 设成 Software然后在 GPIO 初始化里把片选引脚配置成推挽输出、初始电平为高后续用HAL_GPIO_WritePin控制即可。这样还能方便地接多个 AD7172每个片选一个 GPIO不需要改 SPI 外设配置。3. 用 STM32CubeMX 搭 SPI 最小工程在 ALIENTEK MiniSTM32 上接好 AD71723.1 CubeMX 的引脚分配与 SPI 参数设置打开 STM32CubeMX芯片选 STM32F103RBT6 或手头 MiniSTM32 实际型号。在 Pinout 视图里找到 SPI1把 Mode 设为 Full-Duplex Master然后手动分配 PA5 为 SCK、PA6 为 MISO、PA7 为 MOSI。AD7172 的 CS 不接 NSS单独用一个 GPIO例如 PA4配置为 GPIO_Output。如果板子上的 SPI1 引脚被 LCD 或 Flash 占用也可以换到 SPI2但代码里hspi1要跟着改成hspi2。CubeMX 参数设置值理由SPI ModeFull-Duplex MasterAD7172 需要主机同时发送和接收SCLK Pin / MISO Pin / MOSI PinPA5 / PA6 / PA7MiniSTM32 SPI1 默认引脚NSS 信号Software手动控制片选避免帧错位Clock PolarityHighCPOL1对应模式 3Clock Phase2 EdgeCPHA1SCLK 第二个边沿采样Prescaler64SCLK 约 1.125 MHz调试期够用Data Size / First Bit8 bits / MSB FirstAD7172 寄存器均是 8 位字节序在 Clock Configuration 里确认 PCLK2 是否为 72 MHz。如果 SPI1 挂在 PCLK2 上预分频 64 后得到约 1.125 MHz这对 AD7172 的短走线连接完全足够。生成代码后MX_SPI1_Init()会出现在main.c默认参数就是上面表格里的值。3.2 用 HAL 库实现一个底层字节收发函数AD7172 是典型的全双工 SPI读操作时必须向 MOSI 发送任意字节来提供 SCLK同时从 MISO 取数。不要用HAL_SPI_Transmit来读数据因为它在发送期间不会接收也不要先HAL_SPI_Transmit再HAL_SPI_Receive这样可以保证 CS 低电平期间从第一个 SCLK 开始就同步接收。uint8_t ad7172_spi_read_write_byte(uint8_t tx_byte) { uint8_t rx_byte 0xFF; HAL_StatusTypeDef status; status HAL_SPI_TransmitReceive(hspi1, tx_byte, rx_byte, 1, 100); if (status ! HAL_OK) { return 0xFF; // 超时或错误时返回 0xFF便于调用方察觉 } return rx_byte; }函数内部通过HAL_SPI_TransmitReceive同时发送和接收一字节timeout给 100 ms。调试 SPI 驱动时最怕死等 SCLK加超时能快速暴露接线问题。调用HAL_SPI_TransmitReceive时必须保证 SPI 已使能CubeMX 生成的代码在HAL_SPI_Init尾部做了__HAL_SPI_ENABLE但如果自己初始化外设别漏了这一步。3.3 移植实验 20 例程时的三个必改点如果手里已经有 ALIENTEK 实验 20 的工程不要只替换spi_flash.c。第一个必改点是片选引脚原例程里 Flash 的 CS 不一定在 PA4把它换成 AD7172 实际连接的 GPIO并在MX_GPIO_Init里设置对应引脚的初始电平为高。第二个必改点是 SPI 参数例程为了匹配 Flash 可能用了模式 0改成高极性和第二个沿采样。第三个必改点是关闭 Flash 例程里的等待忙信号AD7172 用 DRDY 引脚表示数据就绪不是像 Flash 那样查询状态寄存器。改完这三处再用ad7172_spi_read_write_byte收发一个0xFF复位序列观察 MISO 上是否有回波。SPI 的 MISO 回波不一定代表 AD7172 已响应只能说明链路物理连接正常真正的寄存器 ID 校验要到下一步。4. 从寄存器级编写 ad7172 驱动初始化、复位和读取转换值4.1 寄存器读写函数的三层封装AD7172 驱动一般分三层底层字节收发、寄存器读写、应用层读取数据。底层就是上面的ad7172_spi_read_write_byte寄存器层负责拼通信命令和收发数据应用层只关心通道配置和转换结果。下面这段代码保持了 Ad7172 常见驱动的风格寄存器地址用宏表示后面按手册查值。#define AD7172_REG_COMMS 0x00 #define AD7172_REG_DATA 0x04 #define AD7172_REG_ID 0x07 #define AD7172_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define AD7172_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET) void ad7172_write_reg(uint8_t reg, uint8_t *data, uint8_t len) { AD7172_CS_LOW(); ad7172_spi_read_write_byte(reg 0x3F); // bit60 表示写 for (uint8_t i 0; i len; i) { ad7172_spi_read_write_byte(data[i]); } AD7172_CS_HIGH(); } void ad7172_read_reg(uint8_t reg, uint8_t *data, uint8_t len) { AD7172_CS_LOW(); ad7172_spi_read_write_byte((reg 0x3F) | 0x40); // bit61 表示读 for (uint8_t i 0; i len; i) { data[i] ad7172_spi_read_write_byte(0xFF); } AD7172_CS_HIGH(); }写寄存器时命令字节的低 6 位直接放地址bit7 为 0bit6 为 0。读寄存器时把 bit6 置 1也就是0x40 | reg。注意在连续读时SCLK 必须保持连续不能把 CS 拉高再重新拉低否则 AD7172 会认为当前事务结束。上面代码把 CS 低电平窗口覆盖了整条命令和数据这是最稳妥的做法。参数上reg必须是数据手册里的 6 位寄存器地址len取决于寄存器宽度。AD7172 大多数控制寄存器是 1 字节数据寄存器是 3 字节ID 寄存器是 1 字节。如果读回的数据总是 0xFF 或 0x00先查 CS 是否真的拉低再查 SCLK 极性和相位。很多人在这一步就会发现SPI 配置里少选了SPI_MODE_MASTER生成的函数路径完全不对。4.2 AD7172 上电后的初始化顺序上电后 AD7172 内部默认配置可能已经能转换但为了排除之前遗留的寄存器状态我一般先发一个软件复位再读 ID 校验。ADI 的软件复位序列是连续向通信寄存器写四个0xFF对应代码实现如下。void ad7172_reset(void) { uint8_t dummy 0xFF; AD7172_CS_LOW(); for (int i 0; i 4; i) { ad7172_spi_read_write_byte(dummy); } AD7172_CS_HIGH(); HAL_Delay(10); }复位后需要等内部振荡器稳定延时 10 ms 左右然后读取 ID 寄存器。AD7172-4 的 ID 常见值是 0x18不同后缀版本可能不同以数据手册为准。如果 ID 不对不只是 SPI 接线问题还要检查数字电源、片选上拉和 DRDY 是否被外部拉死。检查对象现象可能原因CS 引脚示波器看到 CS 低电平GPIO 配置错误、片选未初始化SCLK 引脚低电平高电平变化频繁SPI 未开启、模式配置错误MISO 引脚空闲时高电平AD7172 未供电、DOUT 未连接DRDY 引脚一直低电平转换未启动、SPI 状态机卡住4.3 读取 24 位转换值并换算成电压AD7172 数据寄存器是 3 个字节按 MSB 先出。读取数据寄存器的命令是0x40 | 0x04也就是 0x44。一次完整读取需要 CS 拉低后依次收发 4 个字节第一个字节是读取命令后面三个字节是 0xFF用来提供 SCLK 并接收数据。下面的函数在读取前等待 DRDY 变低保证读到的是一帧完整转换结果。int32_t ad7172_read_data(void) { uint8_t buf[3] {0}; while (HAL_GPIO_ReadPin(DRDY_GPIO_Port, DRDY_Pin) ! GPIO_PIN_RESET) { // 等待 DRDY 为低电平表示 ADC 数据已更新 } AD7172_CS_LOW(); ad7172_spi_read_write_byte(0x44); // 读数据寄存器 buf[0] ad7172_spi_read_write_byte(0xFF); buf[1] ad7172_spi_read_write_byte(0xFF); buf[2] ad7172_spi_read_write_byte(0xFF); AD7172_CS_HIGH(); int32_t raw ((int32_t)buf[0] 16) | ((int32_t)buf[1] 8) | ((int32_t)buf[2]); if (raw 0x800000) { raw - 0x1000000; // 24 位有符号数转为 32 位 } return raw; }这段代码直接把原始 ADC 码计算出来取值范围是 -8388608 到 8388607。换算电压时看 AD7172 配置成双极性还是单极性。双极性输入下电压公式是V code / 8388608.0 * Vref如果是单极性模式则V code / 16777215.0 * Vref。如果发现 raw 在 0 附近跳动但跳动的幅度小于预期的噪声先把参考电压引脚上的电容从 0.1uF 换成 1uF很多采集板的问题不是驱动而是参考源不稳。AD7172 的内部基准可以启用但外部基准更可控驱动里不要急着把基准配置寄存器改复杂先用默认外部基准跑通。4.4 把驱动从连续转换模式改成单次模式默认上电后 AD7172 会持续转换DRDY 周期性拉低。如果后面要接入 RTOS或者想降低 MCU 的 GPIO 查询压力可以改成单次转换模式。在 ADC 模式寄存器里把转换模式位改成相应值写一次启动一次转换完成后 DRDY 拉低读取数据寄存器后自动回到空闲状态。单次模式的好处是每次转换都可以在固定时刻读取避免连续模式下数据被覆盖。坏处是每读一帧前都要重新写一次启动命令代码路径比连续模式多一条寄存器写操作。如果你的程序里已经有ad7172_write_reg单次模式只是多一行uint8_t mode 0x20; // 单次转换模式具体值查芯片型号对应手册 ad7172_write_reg(0x01, mode, 1);注意 0x01 是 ADC 模式寄存器的偏移地址不同后缀芯片的位域略有差异。别把 AD7172-4 和 AD7172-8 的设置完全照搬它们虽然都是 AD7172 家族但寄存器位宽和控制位不完全一致。驱动文件里留一个#define AD7172_CHIP_AD7172_4的开关方便换芯片时切换寄存器配置。5. 验证 AD7172 驱动的两条捷径读 ID 和抓 DRDY 波形5.1 先读 ID 寄存器而不是直接读 ADC 数据很多 SPI 驱动调试失败是因为一上来就调 ADC 数据结果 SPI 命令本身错了转换结果当然不对。最省时间的办法是先读 AD7172 的 ID 寄存器只要 ID 能读对说明 SPI 帧结构、时钟极性和片选逻辑全部正确。下面的代码可以直接放到 main 函数的初始化之后作为自检。uint8_t id 0; ad7172_reset(); ad7172_read_reg(AD7172_REG_ID, id, 1); if (id 0x18) { // ID 正确继续配置 ADC 和读取数据 } else { while (1) { // 停在这里检查 SCLK、MOSI、MISO、CS 四根线 } }这段代码用前面封装的ad7172_read_reg把 ID 读到id变量里。如果 ID 一直读成 0x00多半是 CS 没有持续拉低或者 SPI 模式被配置成了模式 0。如果 ID 读成 0xFF先查 MISO 是否连到 PA6很多 MiniSTM32 板子的 SPI1 排针需要跳帽连接漏插跳帽时 MISO 悬空读回全 1。5.2 用逻辑分析仪确认 CS、SCLK、DRDY 三者关系读通 ID 后再进入 ADC 数据调试。这时示波器或逻辑分析仪观察三根信号CS、SCLK、DRDY。理想时序是DRDY 先由高变低MCU 检测到后把 CS 拉低然后 SCLK 连续输出至少 32 个脉冲CS 再拉高。DRDY 会在数据读出后自动变高准备下一次转换。信号正常情况常见异常CS命令和数据期间保持低整帧结束才拉高每个字节都拉高拉低帧被打断SCLK模式 3空闲高传输时连续脉冲有长时间毛刺、频率太高导致边沿模糊DRDY转换完成后拉低约数百纳秒到几微秒一直低说明数据未读走或配置错误MISO/DOUTCS 拉低后随 SCLK 输出一直高ID 读不对MISO 接线断如果 DRDY 引脚一直为低先看代码里是否在每次转换完成后都调用ad7172_read_data。连续模式下数据寄存器如果不读DRDY 不会拉高程序就会卡在等待拉低的 while 循环里。把读取代码放在一个定时器中断里比在 main 循环里死等更稳。另一种做法是直接用外部中断 EXTI 触发读取在下降沿回调里调用 read_data然后把结果放进环形缓冲这样不会漏掉数据。在调试 SPI 软件片选时可以故意把 CS 拉低后不读数据观察 SCLK 是否还有其他时钟穿过。AD7172 对片选窗口很敏感多余时钟会把读指针推到下一个寄存器。所以驱动代码里所有读写函数都要确保片选低电平窗口内只有一个完整事务不在中途调用其他 SPI 操作。把这一条当成纪律AD7172 就不会再给你突然冒出 0xFFFFFF 的“惊喜”。本文还有配套的精品资源点击获取
返回列表