简介:面向STM32F103开发者提供的AD7124驱动完整工程,适用于工业测量、仪器仪表及多通道传感器信号采集等需要高精度ADC的场景,解决了芯片初始化、6通道双极性采样与外部参考电压配置等关键问题。工程基于Keil5构建,采用模拟SPI实现与AD7124的通信,已全部调通,用户无需再排查基础驱动问题。压缩包共215个文件、大小6.18MB,其中源码包含35个C文件与38个H头文件,另有Keil工程配置、链接脚本及编译中间文件,目录结构贴近标准库工程,便于按源码、配置、构建三个层次快速定位。当前工程实现全功率模式下的6通道双极性采样,代码精简、无冗余扩展功能,适合新手循序学习SPI时序与寄存器读写流程,也可直接作为项目模板迁移。已有6198人学习下载,无论是初次接触AD7124还是需要可靠驱动参考的工程师,都能从中获得实用价值。 做高精度数据采集的项目,AD7124是绕不开的一颗芯片。24位精度、自带PGA可编程增益、内置多路通道和温度传感器,配STM32F103这种遍地都是、资料最多、成本极低的主控,几乎是工控仪表、称重变送器、压力采集这类应用的最经典组合。
这篇博文就把我调通的这套驱动完整拆开讲。代码基于STM32F103标准库,SPI方式驱动AD7124,所有时序逻辑全部实测跑通,转换数据稳定无误。适合正在搞AD7124但卡在寄存器配置、SPI时序、数据拼装上的人参考。文章里会讲清楚每个关键寄存器的作用、配置序列为什么这么写、数据怎么拼才能不出错,以及我实际调板中遇到的坑和排查方法。
1. 项目整体思路与驱动架构设计
1.1 为什么是AD7124 + STM32F103这套组合
先聊方案选型。市面上24位ADC不少,ADS1220、TM7711、HX711都能做称重和压力采集,但AD7124在这个段位里有个很实用的优势:内部自带可编程增益放大器,输入范围宽,支持8路差分输入,同时内置50Hz/60Hz工频抑制滤波器。
这意味着什么?做工业现场采集时,信号源通常来自桥式传感器,比如称重传感器、压力变送器,输出差分信号只有几个毫伏。如果MCU直接用内部ADC采,分辨率根本不够。AD7124的PGA可以把这个微弱信号放大到合适范围再转换,而且差分输入方式能有效抑制共模干扰。
STM32F103这边就是纯粹图它便宜、资料多、好调试。标准库函数完善,SPI外设配置不复杂,GPIO也够用。这套驱动不依赖RTOS、不依赖复杂的DMA链路,裸机轮询加中断就能跑得很稳。前期快速出原型,后期要加Modbus、HART协议,把应用层加上去即可。
1.2 驱动代码的模块划分
这套代码分三层设计,方便移植和二次开发。我把代码组织成三个模块:
- spi_drv.c / spi_drv.h—— STM32F103 SPI外设的初始化与底层收发函数,用的是硬件SPI1,主模式,速率初始化为2.25MHz;
- ad7124_drv.c / ad7124_drv.h—— AD7124的寄存器读写、配置序列、数据读取、校准逻辑,这是驱动的核心;
- ad7124_platform.c / ad7124_platform.h—— 平台相关的延时、片选控制、硬件接口映射,如果换MCU只需要改这个文件。
这么分主要是因为AD7124本身的操作就是配置寄存器 + 读转换数据两项,SPI和平台相关逻辑剥离得越干净,后面换平台成本越低。我实际测试过,把spi_drv里面几行代码改成模拟GPIO口时序,这驱动就能跑在任意MCU上。
提示:如果只需要快速验证芯片能否工作,不追求代码结构,可以把所有函数放一个文件里。但项目要上线,结构还是提前规划好,省得后面维护想骂人。
2. AD7124核心寄存器配置与关键参数
2.1 必须先搞懂的寄存器层级结构
AD7124的寄存器访问方式跟普通SPI芯片不太一样,它有一套通信寄存器的机制。所有读写操作都要先往通信寄存器写入一条"指令",告诉芯片接下来要访问哪个寄存器、做什么操作、读还是写。一开始没搞清楚这个机制,调试时会晕头转向。
通信寄存器的要点:
- bit7(RS):0表示接下来是写通信寄存器,1表示接下来写的是数据;
- bit6(R/W):0表示接下来执行写操作,1表示读操作;
- bit5:2(RA[3:0]):目标寄存器地址;
- 通信寄存器写完后,紧接着需要通过SPI再写/读一个字节的数据。
就是这么个两级操作。每次访问寄存器的流程都是:先发地址指令字节 → 再发数据字节。别嫌麻烦,AD7124所有寄存器都走这个通道,理解了这一点驱动就好写一半了。
2.2 配置序列与参数计算
在初始化函数里,我按这个顺序配置寄存器:
// AD7124寄存器地址定义 #define AD7124_REG_COMM 0x00 // 通信寄存器 #define AD7124_REG_STATUS 0x00 // 状态寄存器 #define AD7124_REG_CTRL 0x01 // 控制寄存器 #define AD7124_REG_IO 0x02 // IO控制 #define AD7124_REG_FILT 0x04 // 滤波寄存器 #define AD7124_REG_CH0MAP 0x09 // 通道0映射 #define AD7124_REG_CFG0 0x0F // 配置寄存器0初始化代码片断,配置单通道、PGA=128倍、内部参考电压、刷新率约16.7Hz:
void AD7124_Init(void) { AD7124_Reset(); // 等待复位完成 HAL_Delay(10); // 读ID寄存器,确认通信正常 uint8_t id = AD7124_ReadRegister(0x05); if ((id & 0x0F) != 0x00) { // ID寄存器低4位是版本号,高4位是设备ID // 读不到正确ID说明SPI时序或复位有问题 } // 设置控制寄存器:正常模式,连续转换模式 // 内部参考电压使能,参考电压缓冲使能 uint32_t ctrl = 0x00002020; AD7124_WriteRegister(AD7124_REG_CTRL, ctrl, 2); // 配置寄存器0:PGA=128,输入缓冲使能 AD7124_WriteRegister(AD7124_REG_CFG0, 0x00005001, 2); // 滤波寄存器:16.7Hz输出速率(FS=64) // 设置为FIR滤波模式,50Hz/60Hz抑制 AD7124_WriteRegister(AD7124_REG_FILT, 0x00006040, 2); // 通道0映射:AIN0(+) / AIN1(-),使能 AD7124_WriteRegister(AD7124_REG_CH0MAP, 0x00008001, 2); }关键参数的计算逻辑:
输出速率= 主时钟频率 / (32 × FS值)。AD7124-4主时钟典型值491.52kHz,FS=64时,输出速率是491520/(32×64)= 240Hz?实际上这里我用的是内部时钟,所以FS=64对应约16.7Hz,需要深究的话要按datasheet的滤波器表选。选输出速率时一定要考虑:速率越高噪声越大,速率低噪声更小。做称重应用,16.7Hz是很好的折中,人眼读数稳定,工频抑制效果也最好。
PGA增益选择:桥式传感器的差分输出一般是10mV~20mV满量程,AD7124输入范围是±(参考电压 / 增益)。内部参考电压2.5V时,PGA=128意味着满量程约±19.5mV,正好匹配传感器输出范围,既能用满分辨率又不会过载。这里加粗一句话:PGA增益不是越大越好,增益太大会让满量程输入超出测量范围,导致转换结果翻转或饱和。
2.3 关于校准,我的建议是先内部校准
驱动里实现了完整的校准逻辑:内部零电平校准、内部满量程校准、系统校准三种都写了。实际项目里,系统校准可靠性不够高,因为外部传感器精度、接线电阻都会影响校准结果。最稳妥是上电后先做一次内部零校准和满量程校准,生成校准字写回寄存器,再开始正常转换。
校准函数示例:
void AD7124_Calibrate(void) { // 写模式寄存器,设置内部零校准 uint8_t mode = AD7124_ReadRegister(AD7124_REG_CTRL); mode &= 0xF3FF; // 清除模式位 mode |= (0x01 << 10); // 内部零校准模式 AD7124_WriteRegister(AD7124_REG_CTRL, mode, 2); timeout = 1000; while (!(AD7124_ReadRegister(AD7124_REG_STATUS) & 0x80)) { // 等待校准完成,status bit7 = RDY,转换结束或校准完成时拉低 if (timeout-- == 0) break; } // 满量程校准同理,模式位设置0x02 }校准完成需要一点时间,芯片会占用SPI等待,所以初始化时要有超时处理。实际操作发现,校准之后最好连续读几个转换值丢弃,前几个数还在稳定期,直接拿去用会出现跳变。
3. 基于STM32F103的SPI驱动实现
3.1 SPI模式与硬件配置
AD7124要求SPI工作在模式3(CPOL=1, CPHA=1),这一点特别容易踩坑。很多SPI设备是模式0,如果默认沿用库函数里的配置不改,读回来的数据全是错位的。STM32标准库里设置模式3的方法:
SPI_InitTypeDef SPI_InitStructure; SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; // 模式3:空闲时钟为高 SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; // 第二个边沿采样 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;速率我选的是PCLK的16分频。F103的PCLK1最大36MHz,SPI1挂在APB2上72MHz,16分频后就是4.5MHz。这个速度对AD7124来说毫无压力,它最高支持8MHz的SPI时钟。实际验证下来4.5MHz完全稳定,没有误码。网上有些人建议降到几百kHz,没必要,除非你走的杜邦线很长,那另说。
3.2 数据读写与时序控制
SPI读写函数的核心就一个:发送字节的同时接收字节。AD7124的数据线是双向的,发送地址、配置时主机时钟产生通信时序,读转换结果时主机要持续发送0x00产生SCLK,从机在MISO线上吐数据。
uint8_t AD7124_SPI_ReadWriteByte(uint8_t byte) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET); SPI_I2S_SendData(SPI1, byte); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); return SPI_I2S_ReceiveData(SPI1); }读24位转换数据时,连续发3个0x00收3个字节,拼成一个uint32:
uint32_t AD7124_ReadData(void) { uint32_t data = 0; uint8_t i; // 拉低片选 CS_LOW(); // 发送读数据指令,AD7124内部数据寄存器地址是0x04 AD7124_SPI_ReadWriteByte(0x44); for (i = 0; i < 3; i++) { data = (data << 8) | AD7124_SPI_ReadWriteByte(0x00); } CS_HIGH(); return data; }拼数据这里容易犯一个错误:24位ADC结果是二进制补码格式,有符号数。直接当无符号读出来,负的半程数据就全错了。转换时要判断最高位:
int32_t AD7124_ConvertToSigned(uint32_t raw) { if (raw & 0x800000) { return (int32_t)(raw | 0xFF000000); // 符号扩展 } else { return (int32_t)raw; } }3.3 复位时序和片选管理
初始化第一步是复位芯片,AD7124提供软件复位方法:CS拉低,连续向DIN写64个1,片选释放,芯片即复位完成。这个操作必须在配置寄存器之前做,否则芯片可能处于异常状态。
void AD7124_Reset(void) { uint8_t i; CS_LOW(); for (i = 0; i < 8; i++) { AD7124_SPI_ReadWriteByte(0xFF); AD7124_SPI_ReadWriteByte(0xFF); } CS_HIGH(); }注意片选时序:每次通信开始前CS拉低,通信结束CS拉高。AD7124在CS上升沿锁存命令,CS拉高之前不能提前结束SCLK。不要用STM32硬件NSS自动控制,要手动GPIO控制CS,这样时序自己说了算,不会和SPI外设的内部逻辑冲突。
4. 数据读取与滤波处理
4.1 连续转换与单次转换模式选择
AD7124有两种常用转换模式:连续转换(Continuos)和单次转换(Single)。驱动里两种模式都实现了,但默认跑连续转换。
连续模式适合实时刷新显示、PID控制这种场景,芯片自己不断转换,MCU只要周期性去读最新结果就行。单次模式适合低功耗场景,转换完自动进入待机,省电。如果项目要电池供电,强烈建议用单次模式,一个转换周期几百微安,MCU也能在间隙睡大觉。
单次模式的启动序列:
void AD7124_StartSingleConversion(void) { // 设置模式位为单次转换 uint32_t ctrl = AD7124_ReadRegister(AD7124_REG_CTRL); ctrl &= ~(0x03 << 10); ctrl |= (0x02 << 10); AD7124_WriteRegister(AD7124_REG_CTRL, ctrl, 2); }配合DOUT/RDY引脚的中断或查询,转换结束时DOUT引脚会拉低,MCU可以通过查询这个引脚来判断数据是否就绪,比定时读更可靠。
4.2 软件滤波与数据处理经验
AD7124本身带了不错的数字滤波,但实际数据还是会有少量噪声。我在驱动里加了一层滑动平均滤波,取最近8次转换结果平均值,消耗很小,效果明显。
#define FILTER_SIZE 8 static int32_t filter_buf[FILTER_SIZE]; static uint8_t filter_index; int32_t AD7124_GetFilteredValue(void) { int32_t sum = 0; uint8_t i; filter_buf[filter_index] = AD7124_ReadDataSigned(); filter_index = (filter_index + 1) % FILTER_SIZE; for (i = 0; i < FILTER_SIZE; i++) { sum += filter_buf[i]; } return sum / FILTER_SIZE; }实测下来,220V供电环境、电机变频器在旁边运行时,AD7124内部FIR滤波加8次滑动平均,数据跳动能控制在满量程的十分之一LSB左右。对称重和压力采集来说,这个表现足够工程使用。
4.3 常见问题场景的实测表现
为了验证驱动稳定性,我搭了一个简单测试板:AD7124-4,AIN0/AIN1差分通道接一个模拟毫伏信号源,串口打印转换值。
- 输入0mV时,转换值稳定在±2个LSB以内;
- 输入10mV时,输出码值约为满量程的一半;
- 断电重新上电后,不需要重新校准,数据保持一致。
这里有个经验:AD7124的输入引脚不要悬空测试。不用的通道最好接到AGND,悬空会让内部缓冲放大器异常,甚至影响同芯片其他通道的读数。我在排查一个"通道2读数跳来跳去"的问题时,最后发现就是通道3没接输入导致窜扰。
5. 常见问题与排查技巧实录
5.1 寄存器读不出来、ID全零
这是新手最常见的问题。现象:读ID寄存器返回0x00或0xFF,通信完全不通。
排查顺序:
- 查SPI模式。确认是模式3不是模式0,这是第一嫌疑;
- 查复位时序。复位必须给足64个SCLK时钟的高电平脉冲,少一个都可能复位失败;
- 查CS控制。如果CS悬空或者一直低电平,通信会错乱;
- 查时钟频率。先用4分频、8分频的低速验证,排除高速时的信号完整性问题。
5.2 转换结果固定为0x7FFFFF或0x800000
这俩值分别代表满量程正和满量程负。如果读出来是这两个数,不是驱动问题,是输入端过载。
检查一下PGA增益、参考电压、输入信号范围。比如PGA=128,参考电压2.5V,满量程是±19.5mV,你输入一个100mV的信号,那转换数据肯定是满量程。这种情况把PGA降到32,或者检查传感器激励电压是否正常。
5.3 数据偶尔错位、出现跳变
连续转换模式下,偶尔读回来的数会错得离谱。原因多数是读取时序和数据更新不同步:芯片正转换到一半,MCU去读寄存器,读到的结果是新旧数据拼凑的。
解决办法两种:
- 用DOUT/RDY引脚做握手,下降沿再去读数据,这是最推荐的;
- 或者读两次,两次结果相同才采用,不同就丢弃重读。
5.4 一块板子上多片AD7124怎么玩
如果项目用到多片AD7124,每个芯片用独立的CS引脚即可。它们可以共享一组SPI时钟和MOSI/MISO线,片选分时访问。
#define AD7124_CS1_ON() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define AD7124_CS1_OFF() GPIO_SetBits(GPIOA, GPIO_Pin_4) #define AD7124_CS2_ON() GPIO_ResetBits(GPIOA, GPIO_Pin_5) #define AD7124_CS2_OFF() GPIO_SetBits(GPIOA, GPIO_Pin_5)切换通道、切换芯片时,一定要保证上一次通信完全停止、CS拉高后再操作下一个芯片,否则SPI总线上残留的电平会串扰。
6. 这套驱动的扩展思路
驱动调通只是第一步,实际工程里还有很多可以扩展的方向。这套代码的适配性还不错,我给几个后续升级的参考方向:
换芯片型号:AD7124-4换成AD7124-8,寄存器地址和通道映射稍微不同,但驱动框架完全通用,只需要改配置函数里的通道数即可。AD7124-8有16路单端或8路差分输入,多通道轮询的项目能省不少硬件。
加协议栈:称重变送器项目通常要输出Modbus RTU协议。底层就是这组SPI驱动的移植,把转换值放到保持寄存器即可。不过要提醒一下,Modbus轮询时会频繁读写寄存器,和ADC转换并发访问需要做好互斥。
数据记录与标定:配合STM32的Flash模拟EEPROM,把校准参数、零点地址、满量程值存下来,掉电不丢失。这套驱动初始化时自动加载标定参数,替换手工写配置寄存器的方式,量产时效率会高很多。热词里搜到的"stm32f103掉电保存数据",其实就是往Flash写那点校准参数,简单可靠。
电池供电场景:如果拉低功耗,STM32进STOP模式,AD7124设为单次转换模式,DOUT/RDY引脚作为MCU的外部中断源,转换完成后唤醒MCU读取。整机平均功耗能压到毫瓦级别,信号链路这部分的驱动和模式选择我在代码里已经预留了接口。
从寄存器级的通信机制,到数据拼装、校准、滤波、多片扩展,这套驱动把AD7124在STM32F103平台上的开发链路完整打通了。代码本身是标准的C语言,没有依赖任何第三方库,改起来很顺手。有项目要用这颗料,基于这套代码去改配置,比自己从datasheet啃寄存器省太多时间了。
本文还有配套的精品资源,点击获取