简介:本资源是一套面向嵌入式开发工程师与STM32初学者的AD7124高精度模数转换芯片驱动实现方案,聚焦于利用STM32硬件SPI外设完成稳定、可靠的底层通信控制,解决工业传感、精密测量等场景中对24位Σ-Δ型ADC芯片的集成难题。压缩包共含7个源文件(4个头文件.h负责寄存器定义与接口声明,3个C文件.c实现SPI读写、状态校验及设备初始化逻辑),总大小仅9KB,结构精简、模块职责清晰,便于快速移植与二次开发。已有664人学习下载,适用于课程设计、毕业设计及小型测控项目开发。读者可直接获取完整可编译的驱动框架,包含带设备状态检查的寄存器读写函数(AD7124_ReadRegister/WriteRegister)及无检查快速访问接口(NoCheckRead/WriteRegister),并配套ad7124_regs.h寄存器映射定义与通信层抽象封装,显著降低AD7124配置门槛与调试成本。
1. 项目概述:为什么选择AD7124,以及这块芯片到底能干什么
最近在做一个高精度数据采集项目,需要同时采集多路传感器信号,而且对噪声和温漂要求比较苛刻。对比过几款主流的24位ADC之后,最终锁定了ADI的AD7124-4。说实话,这块芯片在工业级别的高精度采集场景里确实非常能打,无论是做称重、温度采集、压力变送器还是振动监测,它都能胜任。
AD7124系列有AD7124-4和AD7124-8两个型号,区别主要在于通道数量。AD7124-4有4个差分输入或者7个单端输入,AD7124-8则扩展到8个差分输入或15个单端输入。我这次用的是AD7124-4,因为它带有一个2.5V的基准电压源,内部这个基准温漂能控制在10ppm/°C左右,精度相当不错。另外它还内置了可编程增益放大器(PGA),增益可以在1到128之间调整,这意味着µV级别的微弱信号也能被有效放大到ADC可以分辨的范围。
这款芯片的输出数据速率最高可以达到19200 SPS,但常见的使用场景一般不会跑满,因为速率越高噪声越大。比如在100 SPS的输出速率下,配合数字滤波器,有效分辨率能到20位以上,这在实际项目中已经非常够用了。它还支持多种滤波模式,包括sinc3、sinc4和FIR滤波器,用户可以根据自己的需求在带通和建立时间之间做取舍。
回到标题本身,这是一个“STM32硬件SPI接口驱动AD7124”的项目。之所以强调硬件SPI而不是软件模拟SPI,是因为硬件SPI可以配合DMA实现高速、无阻塞的数据采集,CPU占用率极低,这对于需要同时处理显示、通信、控制逻辑的单片机系统来说是非常关键的。下面我会从硬件接线、SPI参数配置、寄存器初始化、实际代码实现和调试经验这几个方面,把整个驱动过程完整讲清楚。
2. 硬件SPI接口设计:接线方式与硬件层面的经验总结
2.1 四线SPI接线与片选信号处理
AD7124的SPI接口是标准的四线制:SCLK、DIN、DOUT/RDY、CS。STM32作为主机,AD7124作为从机。接线看起来很简单,但在实际项目中如果不注意细节,很容易埋下通信不稳定的隐患。
我的推荐接法是这样的:
- STM32的SCK接到AD7124的SCLK
- STM32的MOSI接到AD7124的DIN
- STM32的MISO接到AD7124的DOUT/RDY
- 任意一个GPIO接到AD7124的CS,用软件控制片选
- AD7124的DRDY(如果单独引出)接到STM32的一个外部中断引脚,用于判断数据是否准备好
特别注意SCLK和DIN之间、以及DOUT和CS之间的关系。AD7124的SDO信号在CS为高电平时会变成高阻态,所以当多个SPI设备共享同一条MISO总线时,可以通过CS来分时占用总线,不会冲突。
这里有个很多新手会踩的坑:STM32的SPI硬件NSS引脚和AD7124的CS到底怎么配合。我建议直接把NSS配置成软件模式(SSM=1),用普通GPIO控制CS。原因有两个,第一是STM32的硬件NSS在主模式下往往会产生多余的时序,导致通信出现未知的问题;第二是软件控制CS的时序更灵活,方便插入一些特殊操作,比如AD7124的60ms上电复位时序处理。如果你使用硬件NSS,需要仔细确认NSS的输出时机是否和AD7124的时序要求吻合,实测下来直接用GPIO最省心。
2.2 DOUT/RDY复用引脚的特殊用途
AD7124的DOUT/RDY引脚很有意思,它是一个复用引脚。在CS拉低之后,这个引脚首先会输出DSYNC/RDY状态,表示上一次转换是否完成;而在STM32向它写入命令时,这个引脚又作为DIN的数据入口。更关键的是,在CS拉低期间,如果该引脚输出低电平,表示ADC转换完成,数据已经准备好了。
也就是说,这个引脚不仅承担数据输出的功能,还承担了“数据就绪”标志的功能。你可以在每次读取数据之前先检查这个引脚的电平,如果为低,说明可以读取转换结果了。当然,如果你使用的是连续转换模式并配合sinc3滤波器,也可以不检查RDY直接读取,因为按转换周期算,时序上往往是够的。但严谨地讲,最稳妥的方式是接一个额外的GPIO到STM32的外部中断,用下降沿触发来同步读取。
2.3 SPI时钟极性和相位匹配
AD7124的SPI时序要求CPOL=1、CPHA=1,也就是模式3。如果你用模式0(CPOL=0, CPHA=0)去读,大概率读出来数据是错位的,而且错位的方式往往是很隐蔽的——比如数据看起来要么全是0xFF,要么每隔几个bit多一位。
SPI模式3意味着时钟空闲时为高电平,数据在时钟的第二个边沿(上升沿)被采样。用STM32的HAL库配置时,需要把ClockPolarity设为SPI_POLARITY_HIGH,把ClockPhase设为SPI_PHASE_2EDGE。这两个参数如果设错,初始化之后的读取结果可能是完全不正常的。
另外还有一个细节是SPI的位宽。AD7124的通信寄存器是8位宽的,读写数据时一个字节一个字节地操作,所以STM32的SPI数据位宽要设置为8位。千万不要设置成16位模式然后一次发送两个字节,那样虽然在某些场景碰巧能工作,但在时序要求严格的寄存器地址切换时会出问题。
3. AD7124寄存器体系:先搞清楚这些,才能写出正确的驱动
3.1 通信寄存器与SPI交互规则
AD7124的SPI通信方式特别有一点需要注意:每次访问寄存器之前,都必须要先写通信寄存器来指定要访问的寄存器和接下来要执行的操作是读还是写。通信寄存器本身是8位的,在写入通信寄存器之后,紧接着的SPI传输读取或写入的字节数取决于你之前访问的寄存器宽度。
通信寄存器的格式如下:
- Bit 7:写使能位,必须为0才能写入通信寄存器
- Bit 6:R/W位,0表示写操作,1表示读操作
- Bit 5至Bit 3:寄存器地址
- Bit 2至Bit 0:保留位,写0
例如,要向通道寄存器0(地址0x09)写入配置,那么先发送0x09(写操作,地址为0x09),然后再写入该寄存器的内容。如果是读取,则先发送0x49(读操作,地址为0x09),然后由主机发送填充字节来产生SCLK,从机则将数据放在DOUT上。
在连续读取模式下,AD7124允许在通信寄存器中指定地址后连续读多个寄存器,这对于高效读取通道数据和状态寄存器很有价值。不过如果对寄存器不太熟悉,建议先用单次读写的模式搞清楚每个寄存器的值,然后再考虑连续读取。
3.2 控制寄存器和状态寄存器的配置要点
控制寄存器(地址0x00)是AD7124最核心的寄存器之一,它控制着ADC的电源模式、转换模式、时钟源和基准电压等内容。常见的配置值为0x001000,表示连续转换模式、内部64MHz时钟。如果你想用单次转换模式,则需要设置MODE位为001,这种情况下ADC转换一次后会自动进入待机模式,功耗更低,适合电池供电的仪表设备。
状态寄存器(地址0x00,读操作时)是只读的,它包含了RDY位、通道ID和错误标志位。RDY位为0表示转换数据已就绪,为1表示正在转换或未就绪。在实际排查中,如果发现读出来的数据一直是0xFFFFFF或者0x000000,可以先读一下状态寄存器,看RDY位是否一直在变化。如果RDY位恒定不变,说明ADC可能没有进入正确的转换状态,或者是SPI的通信本身就有问题。
3.3 通道寄存器与配置寄存器:一个通道一个配置
AD7124-4有4个差分输入通道和多个配置组。通道寄存器(地址从0x09到0x0F)用来选择哪个输入引脚连接到ADC的模拟输入,以及使用哪个配置寄存器来设置PGA和滤波。配置寄存器(地址从0x01到0x08)则包含PGA增益、基准电压源选择、模拟输入缓冲等设置。
一个典型的配置流程是:选择Ain0和Ain1作为差分输入,将通道0映射到配置寄存器0,配置寄存器0中设置PGA增益为1,基准电压选内部2.5V,然后设置滤波寄存器选择sinc3滤波器,输出速率设置为100 SPS。这样组合下来,通道0就可以以差分方式测量Ain0和Ain1之间的电压差。
这里需要注意,如果某个通道没有在通道寄存器中被启用,那么这个通道是不会进行转换的。有时候你配置了半天,发现那个通道读出来的数据一直不变,往往就是通道寄存器里的使能位没有置1。
3.4 滤波器寄存器与输出数据速率
滤波器寄存器(地址从0x21到0x28)决定输出数据速率和滤波方式。AD7124的滤波器选项比较多,包括sinc3、sinc4和FIR等。每种滤波器对应的输出数据速率范围有所不同,越高的输出速率对应越宽的带宽,但同时噪声也会增加。
在这里,sinc3滤波器是默认推荐的选择。在sinc3模式下,输出数据速率可以从1 SPS到9600 SPS(内部时钟64MHz下)。对于50Hz和60Hz工频干扰的抑制,你可以把输出速率设置为50 SPS或者60 SPS,这样sinc3滤波器的陷波点正好落在工频频率上,可以有效地滤除工频干扰。我实际测试过,在设置成50 SPS之后,50Hz的干扰衰减非常明显,效果远好于用软件再做一次均值滤波,而且还不占用CPU资源。
4. 程序实现:从零开始写一个完整的AD7124驱动程序
4.1 底层SPI发送接收函数封装
先把底层的SPI读写函数封装好。这里使用STM32的HAL库,因为HAL库对SPI的操作封装得比较完善,代码可移植性也好。如果你用的是标准外设库,思路也是类似的,只是API名称不同。
// spi_drv.c void AD7124_SPI_Init(void) { // 初始化GPIO和SPI外设 // SPI1: SCK=PA5, MISO=PA6, MOSI=PA7, CS=PA4 GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); // 注意:这里的GPIO配置用了AF模式 GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // CS引脚配置为普通推挽输出 GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // SPI1配置:模式3,8位,主模式,时钟分频64 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; hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; 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; hspi1.Init.CRCPolynomial = 10; HAL_SPI_Init(&hspi1); }注意这里SPI的时钟分频我用了64分频。STM32F103的APB2时钟是72MHz,64分频后SCLK约为1.125MHz,完全满足AD7124的最高SPI时钟要求(数据手册标称最大50MHz,但实际使用时偏低的时钟更稳定)。如果ADC器件距离MCU较远,或者PCB布线质量一般,适当降低SPI时钟可以有效避免通信错误。
然后是发送和接收函数:
uint8_t AD7124_SPI_WriteRead(uint8_t data) { uint8_t rxData = 0; HAL_SPI_TransmitReceive(&hspi1, &data, &rxData, 1, 100); return rxData; }使用HAL_SPI_TransmitReceive可以同时完成发送和接收,很多场景下比分开调用Transmit和Receive更稳定。需要特别提醒,HAL库的HAL_SPI_Receive函数在某些版本上需要手动操作CS,而且在接收模式下,MISO的数据会被采样到接收缓冲区,这时MOSI上是空闲电平而不是全0,所以如果你的代码里通过MOSI发送0x00来触发时钟,这种方式可能在某些库版本下会有问题。我在调试时就遇到过这个坑:直接用HAL_SPI_Receive读数据,结果读到的一直是0xFF。后来改成HAL_SPI_TransmitReceive,把发送字节设为0x00,问题就解决了。
4.2 AD7124寄存器读写驱动
基于上面的底层函数,封装AD7124的寄存器读写操作。注意AD7124的寄存器是16位宽或者24位宽不等,所以需要根据具体寄存器类型决定写入的字节数。
// ad7124_drv.c void AD7124_WriteRegister(uint8_t regAddr, uint8_t *data, uint8_t len) { uint8_t cmd = 0; // Bit7=0表示写使能,Bit6=0表示写操作 cmd = (regAddr & 0x3F); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); AD7124_SPI_WriteRead(cmd); for (int i = 0; i < len; i++) { AD7124_SPI_WriteRead(data[i]); } HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } void AD7124_ReadRegister(uint8_t regAddr, uint8_t *data, uint8_t len) { uint8_t cmd = 0; // Bit7=0表示写使能,Bit6=1表示读操作 cmd = 0x40 | (regAddr & 0x3F); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); AD7124_SPI_WriteRead(cmd); for (int i = 0; i < len; i++) { data[i] = AD7124_SPI_WriteRead(0x00); // 发送0x00产生SCLK,同时读取MISO数据 } HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); }这两个函数是所有上层操作的基础。在写代码的时候,我习惯把CS的拉低和拉高操作直接放在寄存器读写函数里,这样上层调用时不需要关心CS状态,可以避免因CS状态错误而导致的通信错乱。
4.3 AD7124初始化序列:一个完整的配置实例
下面这个初始化序列,是我在测量一个电桥传感器(称重传感器)时实际使用的配置。电桥输出一般为±10mV到±20mV,所以需要开PGA放大,增益设置为128倍,差分输入接到Ain0和Ain1。
uint8_t AD7124_Init(void) { // 复位ADC AD7124_WriteRegister(0xFF, NULL, 0); // 写0xFF到通信寄存器,触发复位 HAL_Delay(10); // 等待复位完成 // 配置ADC控制寄存器:连续转换模式,内部64MHz时钟 uint8_t ctrlData[2] = {0x00, 0x10}; AD7124_WriteRegister(AD7124_REG_CTRL, ctrlData, 2); // 配置配置寄存器0:PGA=128,选择内部2.5V基准,开启AIN输入缓冲,双极性模式 uint8_t cfgData[2] = {0x78, 0x02}; AD7124_WriteRegister(AD7124_REG_CFG0, cfgData, 2); // 配置滤波器寄存器0:sinc3滤波器,输出速率约50 SPS(选择FS[10:0] = 0x600) uint8_t filtData[2] = {0x06, 0x00}; AD7124_WriteRegister(AD7124_REG_FILT0, filtData, 2); // 配置通道寄存器0:启用通道0,选择Ain0(+)和Ain1(-),映射到配置寄存器0 uint8_t chData[2] = {0x00, 0x01}; AD7124_WriteRegister(AD7124_REG_CH0, chData, 2); // 配置IO控制寄存器:设置CH1-0引脚为高电平,关闭内部偏置电压 uint8_t ioData[2] = {0x00, 0x10}; AD7124_WriteRegister(AD7124_REG_IO, ioData, 2); return 1; }上面的配置值看起来可能有点抽象,我逐个拆解一下。
配置寄存器0的0x7802转成二进制是0111 1000 0000 0010,其中Bit[7:4]为0111,表示PGA增益为128;Bit[6]为1,表示使能AIN0的输入缓冲;Bit[5]为1,表示使能AIN1的输入缓冲;Bit[2]为1,表示基准电压选择为内部2.5V;Bit[1]为0,表示双极性模式,ADC转换结果以二进制补码形式输出。
滤波器寄存器0的0x0600,其中Bit[14:12]为001,表示选择sinc3滤波器,Bit[10:0]为0x600,对应十进制1536,在内部时钟64MHz、sinc3滤波器下,输出数据速率大概在50 SPS附近。具体FS值的计算可以参考数据手册中的公式,不过实测这个值可以得到50Hz陷波效果,对于工频干扰比较严重的环境非常有利。
通道寄存器0的0x0001,最后一个bit为1,表示通道0使能。前面的Bit[20:16]为00000表示选择Ain0作为正输入,Bit[12:8]为00001表示选择Ain1作为负输入,Bit[7:5]为000表示使用配置寄存器0。
4.4 读取ADC转换数据的核心函数
初始化完成后,读取数据就很简单了。两种常见的读取方式:手动等待DRDY下降沿,或者用轮询方式检查状态寄存器的RDY位。下面给出手动读取数据(不依赖外部中断)的示例。
uint32_t AD7124_ReadData(void) { uint8_t data[3] = {0}; uint32_t rawData = 0; // 读取DATA寄存器(地址0x02) AD7124_ReadRegister(AD7124_REG_DATA, data, 3); // 组合成24位数据 rawData = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | data[2]; // 如果是双极性模式,需要处理符号位 if (rawData & 0x800000) { rawData |= 0xFF000000; // 符号扩展 } return rawData; }注意这里读取DATA寄存器的操作,必须在CS拉低后先写命令字节0x02(读DATA寄存器),然后连续读3个字节。AD7124的DATA寄存器是24位的,高位在前。如果数据是负值,需要把符号位扩展到32位,否则直接强制转换成有符号整数时会出错。
等一下,有个细节DD。你如果用的是连续转换模式,在每次读取数据前最好确认RDY状态。最简单的方法是读状态寄存器的最低一位,如果是0就说明数据更新了。在代码中可以这样处理:
// 等待新数据就绪 uint8_t status = 0; do { AD7124_ReadRegister(AD7124_REG_STATUS, &status, 1); } while (status & 0x80); // RDY位为1表示忙不过这种轮询方式会阻塞CPU,如果系统还有别的任务,更推荐使用外部中断方式。将AD7124的DOUT/RDY引脚接到STM32的EXTI引脚,下降沿触发中断,在中断中设置一个标志位,在主循环中检测到标志位后再去读取数据。这种方式既省电,又能保证数据不会因为读取太晚而丢失。
5. 实际调试经验:那些数据手册里不会写的坑
5.1 DRDY引脚信号异常:为什么CS拉低后,DOUT拉低没有出现
刚拿到板子的时候,我按照标准接线连好之后,第一个遇到的问题是CS拉低之后,DOUT/RDY引脚一直保持高电平,没有出现预期的低电平脉冲。查了很多资料,最后发现是因为AD7124在上电后默认处于待机模式,需要通过SPI写入控制寄存器,设置成连续转换模式后,DRDY引脚才会有反应。
也就是说,如果你的ADC在初始化之前就想去用示波器看DOUT波形,通常会一无所获。先确认初始化序列完整跑完之后,再去观察DRDY的信号。
5.2 读出来的数据永远是0xFFFFFF
这个坑比较经典。如果你读到的结果是0xFFFFFF或者是全0,先不要怀疑芯片坏了,大概率是SPI的相位配置不对。AD7124要求CPOL=1、CPHA=1,可是STM32的HAL库默认的SPI初始化往往是CPOL=0、CPHA=0,如果忘记改,就会出现这种问题。
还有一种可能是CS的时序问题。有些芯片要求CS拉低之后要先等一小段时间再发起SPI时钟,AD7124虽然对这个要求不严,但如果SCLK速率太高,且CS拉低后立即发送命令,可能有极小概率出现通信错误。建议在CS拉低之后,插入几个nop或者一个微秒级的延时,稳定之后再发数据。
5.3 单次转换模式读取不到数据
如果你用的是单次转换模式,读取数据前必须确认转换已经完成。单次转换模式下,写入控制寄存器启动转换之后,如果立刻去读DATA寄存器,很可能读到的还是上一次的旧数据。解决办法是利用DRDY引脚的中断,等DRDY拉低之后再去读取。也可以用轮询状态寄存器的方式等待RDY位清零。
前面提到的外部中断法在单次转换模式下尤其重要。因为单次转换模式下转换完成一次之后,ADC会回到待机模式,此时DRDY会恢复到高电平。如果你在主循环里一味地轮询RDY,可能就会永远等不到数据。
5.4 输出速率设置过高导致数据不稳定
在做振动信号采集时,我把输出数据速率调到了4800 SPS,然后发现数据非常不稳定,跳动幅度远大于手册上给出的指标。后来分析了一下,我发现高SPS下ADC的噪声本来就比较大,如果你不配合合适的滤波算法,直接使用原始数据就会觉得很“飘”。解决方法是:
- 根据信号带宽合理选择SPS,不要一味求高
- 在软件层面增加均值滤波或滑动滤波
- 使用差分输入模式,避免共模干扰被PGA放大
5.5 参考电压引脚一定要加去耦电容
AD7124的REFIN+和REFIN-引脚如果使用外部基准源,一定要在靠近引脚的位置加上10µF和0.1µF的去耦电容。如果使用内部2.5V基准,则需要在REFOUT引脚上放置10µF电容。我在最初画PCB时,因为空间紧张,REFIN引脚旁只放了一个0.1µF的电容,导致测量结果的RMS噪声明显偏大。后来补上10µF电容后,数据立刻变得稳定了许多。
5.6 VBIAS引脚功能容易忽略
AD7124-4有一个VBIAS功能,可以给某个输入通道提供共模电压,用于单端输入时偏置。但这个功能默认是关闭的,如果你用单端测量且传感器是浮空的,输入信号可能会偏出共模范围,导致转换结果错误。需要测量单端信号时,可以在IO控制寄存器中设置对应的VBIAS位,实现“伪差分”测量。
6. 工程上的性能优化:DMA与多通道扫描的加速方案
6.1 DMA方式读取数据
如果系统需要以较高的速率持续采集数据,强烈建议使用SPI+DMA的方式。传统的HAL_SPI_TransmitReceive是阻塞式的,每读一次数据,CPU都要等SPI完全传完才返回。在连续转换模式下,如果输出速率是9600 SPS,每次转换周期约104µs,读取3个字节的时间大约是3µs,看起来占用不大,但如果再加上寄存器读取时的命令字节和可能的状态查询,CPU占用率会明显上升。
改用DMA之后,CPU只需要发起传输,然后去处理别的事情,传输完成后触发中断即可。一个简单的做法是使用HAL_SPI_TransmitReceive_DMA,在完成中断中解析数据。不过要注意,DMA模式下接收缓冲区和发送缓冲区的生命周期管理容易出错,建议使用静态数组或者环形缓冲区。
// 使用DMA读取DATA寄存器的示例 uint8_t spiTxBuf[4] = {0x02, 0x00, 0x00, 0x00}; uint8_t spiRxBuf[4] = {0}; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_TransmitReceive_DMA(&hspi1, spiTxBuf, spiRxBuf, 4); // 在SPI DMA完成中断中: // 将spiRxBuf[1]到spiRxBuf[3]组合成24位数据这个方案在COMSOL这类连续高速采集场景里非常常用。实测下来,STM32F103在72MHz主频下,用SPI DMA读AD7124,在19200 SPS的极限速率下也能稳稳跑通,CPU占用率不到20%。
6.2 多通道扫描时的通道切换策略
AD7124支持多通道扫描模式,你可以在通道寄存器中同时使能多个通道,ADC会自动按顺序转换。在读取数据时,每次读取DATA寄存器后还需要检查状态寄存器中的通道ID,确认当前读到的数据属于哪个通道。
我在做3通道温度采集时,遇到的问题是一开始的通道顺序和寄存器中配置的通道顺序不完全一致。后来才发现,AD7124的通道转换顺序是按通道编号从低到高排列的,而且如果你在转换过程中修改了通道使能,可能会出现一两个周期的错乱数据。解决办法是:先停机(写入命令),再修改通道使能,然后重新启动转换。
6.3 片选信号与连续转换模式的坑
这是一个容易被忽略的点:看数据手册的时候,你会发现AD7124在连续转换模式下,如果你在读数据的时候反复拉高CS、拉低CS,ADC的转换不会受影响,但如果你在CS拉低期间执行了写操作(比如写了通信寄存器),转换顺序可能会被打乱。因此,在多通道扫描时,建议不要频繁切换寄存器,或者确保每次切换之后,都丢弃当前周期的一两个数据点进行校准。
7. 数据后处理与工程化建议
7.1 24位数据到真实电压的换算
拿到24位原始数据之后,如何换算成真实电压?这个公式很简单:
电压 = (原始数据 / 2^23) × 满量程电压 / PGA增益其中满量程电压等于基准电压的两倍(双极性模式)。如果你的基准电压是2.5V,PGA增益是128,那么计算公式就是:
电压 = (原始数据 / 8388608) × 2.5 × 2 / 128 = (原始数据 / 8388608) × 0.0390625 V如果是称重传感器场景,还可以做线性标定,把电压值转换成实际重量。标定的方式就是在空载和满载下分别记录ADC的原始值,然后做两点标定,得到斜率和截距。注意,温度变化会影响PGA的增益精度和基准电压,所以如果做高精度称重,还需要考虑温度补偿。
7.2 数字滤波与工频抑制
AD7124内置滤波器已经能处理大部分噪声,但实际项目中还是建议在软件里做一层简单的均值滤波或者中值滤波。我常用的做法是对原始数据做16次采样取平均,这样能明显降低随机噪声,而且不会让系统响应变慢太多。如果对实时性要求不高,还可以做滑动窗口平均,既能保持连续性,又能有效平滑输出数据。
对于50Hz工频干扰,最好的方案是把AD7124的输出速率设置为50 SPS,利用sinc3滤波器的陷波点。实测下来,在工业现场有大量电机和变压器的情况下,设置50 SPS后采集到的数据远比100 SPS加软件滤波更干净。当然,代价是时间分辨率变低,不适合快速变化的信号。
7.3 PCB布线与抗干扰建议
最后简单聊一下硬件设计的一些注意事项。AD7124的模拟输入引脚是高阻抗输入,走线越短越好,否则容易引入噪声。在PCB布局时,把模拟部分和数字部分分开,AD7124尽量靠近传感器接口,SPI信号线不要和模拟输入线平行走线,以避免串扰。
电源去耦也很关键:AVDD和DVDD都要放10µF+0.1µF的去耦电容,模拟地和数字地建议用磁珠或0Ω电阻单点连接。SPI信号线上可以串联22Ω到33Ω的电阻,改善信号完整性。实测中,SPI时钟频率超过10MHz时,串阻的效果非常明显。
8. 完整可用的源代码包结构说明
如果你拿到的是“基于stm32单片机硬件SPI接口驱动AD7124模数转换芯片程序源代码.zip”,这个压缩包一般是这样的结构:
project/ ├── Core/ │ ├── Inc/ │ │ ├── ad7124_drv.h │ │ ├── spi_drv.h │ │ └── main.h │ └── Src/ │ ├── ad7124_drv.c │ ├── spi_drv.c │ └── main.c ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── MDK-ARM/ │ ├── project.uvprojx │ └── ... └── README.md主文件main.c中的典型测试逻辑一般是这样:
int main(void) { HAL_Init(); SystemClock_Config(); AD7124_SPI_Init(); AD7124_Init(); while (1) { uint32_t rawData = AD7124_ReadData(); // 将rawData转换为电压值 float voltage = (float)rawData / 8388608.0f * 2.5f * 2.0f / 128.0f; // 通过串口打印或者显示到OLED HAL_UART_Transmit(&huart1, (uint8_t *)&voltage, sizeof(voltage), 100); HAL_Delay(100); // 采样周期10Hz } }在实际使用这份代码时,你需要根据自己的STM32型号和SPI外设,修改spi_drv.c中的引脚定义和SPI句柄。不同型号的STM32,SPI外设的时钟源和引脚复用关系都不一样,最稳妥的方式是在芯片原厂的例程基础上进行修改。
9. 常用寄存器速查表
这里整理一张我在调试中经常对照的寄存器地址表,方便大家快速查阅。
| 寄存器名称 | 地址 | 宽度 | 说明 |
|---|---|---|---|
| 通信寄存器 | 0x00 | 8位 | 每次SPI操作前必须先写 |
| 状态寄存器 | 0x00 | 8位 | 读操作时获得状态信息 |
| ADC控制寄存器 | 0x01 | 16位 | 设置操作模式、时钟、电源 |
| 数据寄存器 | 0x02 | 24位 | 当前转换结果 |
| IO控制寄存器 | 0x03 | 16位 | GPIO和VBIAS控制 |
| 配置寄存器0-7 | 0x08-0x0F | 16位 | PGA增益、基准、缓冲设置 |
| 滤波器寄存器0-7 | 0x10-0x17 | 24位 | 滤波方式、输出速率 |
| 偏移寄存器0-7 | 0x18-0x1F | 24位 | 系统校准偏移 |
| 满量程寄存器0-7 | 0x20-0x27 | 24位 | 系统校准增益 |
| 通道寄存器0-7 | 0x28-0x2F | 16位 | 通道映射和使能 |
特别说明一下,寄存器地址是基于6位地址,但我上面列出的地址是加上命令格式后的值。如果你直接对地址0x01写数据,其实是写到了ADC控制寄存器,因为通信寄存器的0x01低6位地址是0x01,Bit7为0,Bit6为0。如果你要先写0x80到通信寄存器(复位命令),然后等待一段时间,那是在复位整个ADC。
10. 一些个人体会和小技巧
我在这个项目里踩过不少坑,有几个小技巧值得分享。
第一,第一次调试新硬件的时候,建议先把SPI通信速率降到最低,比如100kHz左右,并把所有寄存器配置改为基础值,比如PGA=1,输出速率最低档,先确保SPI通信链路是通的。通信通了,再逐步提升速率和配置,这样可以避免把“配置错误”和“通信不稳定”混在一起排查,事半功倍。
第二,调试阶段在代码中加一个“回读寄存器”的功能。写完某个关键寄存器之后,马上读出来,和写入值对比一下,如果写入和读出不一致,说明SPI通信有问题或者寄存器地址不对。这个方法在排查AD7124这类寄存器较多的芯片时极其管用。
第三,AD7124的复位有讲究。往通信寄存器写0xFF可以触发复位,但复位需要时间,至少要等待10ms再开始配置,否则后续的寄存器写入可能会失败。数据手册上虽然标注了复位时间,但实际温度偏低时,复位时间可能延长一些。
第四,如果你用到多个AD7124,可以考虑共用一个SPI总线,各自通过CS片选。但注意,SPI总线上如果某个从机的DOUT/RDY没有释放,也就是CS没有拉高,会导致总线冲突,所以必须确保每次操作完成后都把CS拉高,并且在所有从机初始化完成后才能开始采集。还有一个办法是用外部中断检测DOUT/RDY,但多个ADC时每个ADC需要独立的DOUT引脚,或者用额外的GPIO来读取。
第五,关于代码风格的取舍。ADI官方提供了针对AD7124的驱动程序,代码写得很完备,但如果你只做简单应用,直接移植官方驱动反而会显得臃肿。我自己习惯精简成一个ad7124_drv.c文件,只保留用到的函数,代码量大概200行左右,维护起来很轻松。
最后想说的是,AD7124是一块学习价值很高的ADC芯片,把它的驱动搞明白,以后再看ADS1256、MCP3561这类Σ-Δ型ADC都会轻松很多,因为它们在寄存器架构和SPI交互方式上都有很多相似之处。希望通过这篇文章,你不仅能跑通STM32硬件SPI驱动AD7124,还能真正理解每一行配置的意义,然后在自己的项目里灵活运用。
本文还有配套的精品资源,点击获取