2026/9/2 18:48:42

STM32驱动ADS8688 ADC芯片完整教程:SPI通信与寄存器配置详解

STM32驱动ADS8688 ADC芯片完整教程:SPI通信与寄存器配置详解 简介面向嵌入式开发者的 ADS8688 STM32 驱动代码包针对 TI 公司 8 通道、16 位分辨率、最高 1Msps 的高速 ADC 芯片解决 STM32 经由 SPI 完成通道选择、采样启动、转换结果读取与中断处理等关键问题。代码包以 C 语言实现共 2 个文件.c 源文件涵盖 SPI 引脚与时钟初始化、ADS8688 内部寄存器配置、多通道数据采集以及超时与通信错误处理逻辑.h 头文件则给出接口声明、参数宏和寄存器地址定义方便开发者直接移植到基于 HAL 或 LL 库的 STM32 工程。压缩包仅 3KB结构精简、无多余依赖。目前已有 5577 人学习下载。代码对逐次逼近型 ADC 的时序控制、转换完成中断和低功耗管理模式进行了清晰的模块划分既适合中小型数据采集系统快速集成也可作为学习高速 ADC 驱动开发的参考模板集成后即可支持多路模拟信号同时采样提升项目开发效率。1. 为什么需要一颗这样的ADC芯片做工业数据采集或者精密仪器类的项目时ST官方给每个外设都准备好了示例代码但ADS8688这种外部ADC芯片的驱动程序就得自己去写。很多工程师一看到数据手册里几十页的寄存器描述就头疼其实摸清楚它的脾气之后写一个稳定可靠的驱动并不难。这次分享的是一个在STM32平台上驱动ADS8688的完整实现。ADS8688是TI推出的16位、8通道、内置基准源的逐次逼近型ADC最大采样率500kSPS输入通道支持多种模拟输入范围包括±10.24V、±5.12V、±2.56V以及0~10.24V等通过寄存器可灵活配置。它最吸引人的地方在于输入通道可以直接耐受高达±20V的电压这意味着前端不需要额外的精密运放做电平搬移省掉一大块模拟电路设计成本。我在这套驱动里做了通道轮询、数据换算、SPI时序适配、DMA方式读取这类核心逻辑还踩了不少时序上的坑把这台机器的脾气摸了个透写出来给后面要用的朋友做个参考。这套代码适合正在做多通道模拟量采集、PLC模拟量输入模块、电机驱动器电流电压回采、电池测试设备等项目的工程师直接抄作业也适合刚接触SPI接口ADC的嵌入式开发者用来理解“怎么让一颗外挂ADC和MCU高效配合”这件事。2. 整体方案思路与外围电路设计2.1 ADS8688工作模式怎么选拿到这颗芯片第一件事不是写代码而是先对方案。ADS8688的工作模式分硬件模式Hardware Mode和软件模式Software Mode两种默认状态下如果用硬件模式系统会有一个固定默认配置但灵活性不够。我的方案选择用软件模式核心诉求有两点一是要能动态切换量程二是希望通过寄存器读取芯片内部状态方便后续做诊断。软件模式下ADS8688通过SPI接口接收命令帧来配置寄存器命令帧格式是8位寄存器地址加8位数据。系统上电后芯片默认按默认配置运行默认输入范围大概是±10.24V各通道使能状态默认也覆盖到了但既然我们要写驱动就要把配置流程完整走一遍。选择软件模式还有个重要原因ADS8688的复位行为是寄存器复位后芯片不会自动恢复到你想要的特定量程配置必须由MCU主动下发配置命令。写驱动时把配置封装成函数后续换量程、改通道顺序都方便。2.2 STM32与ADS8688的硬件连接ADS8688是标准SPI接口但有个很重要的细节——它支持与1.8V到5V逻辑电平直接连接所以和STM32相连非常省事。如果你用3.3V供电的STM32直接连就行不用做电平转换。硬件连接可参考以下引脚映射ADS8688引脚功能说明连接目标备注SCLKSPI时钟输入STM32 SPI_SCK时钟极性可配置SDI命令/数据输入STM32 SPI_MOSI用于写入命令帧SDO转换结果输出STM32 SPI_MISO读取转换数据CS片选信号STM32 GPIO/SPI_NSS低电平有效RST复位任一GPIO低电平复位REFIO/REFCAP基准输入/输出电容旁路内部基准已启用DVDD/AVDD数字/模拟电源3.3V/5V按数据手册要求RVS忙输出可悬空或GPIO用于忙检测我在实际调试中发现CS信号尽量用普通GPIO软控不要用SPI硬件的NSS自动控制因为ADS8688要求SCLK空闲时CS不能被拉高打断。STM32的SPI硬件NSS在某些配置下会自动翻转容易触发误操作软件片选更可控。2.3 供电与基准源设计经验ADS8688内部集成基准源但也支持外部基准输入。默认情况下内部基准启动后大概要十几毫秒稳定时间所以驱动初始化时一定要给一段延时。基准引脚处要放一个至少1uF的低ESR电容我在实际设计里放了10uF钽电容并联0.1uF陶瓷电容读取数据全程没有出现基准抖动引起的码值跳变。模拟供电AVDD和数字供电DVDD如果使用同一个电源轨建议分别用磁珠隔离减少数字开关噪声耦合到模拟端的风险。这个细节在ADC数据采集类项目里极其重要单独分一路LDO给模拟供电是比较稳妥的做法。3. 驱动代码核心实现与关键细节3.1 底层SPI读写函数封装STM32端使用HAL库做底层封装SPI配置时需要注意时钟极性CPOL和时钟相位CPHA的匹配。查了ADS8688数据手册时序图SCLK空闲状态为低电平数据在SCLK上升沿被芯片采集所以SPI模式应该选择模式1CPOL0CPHA1。下面给出一个可直接移植的SPI底层封装样例/* SPI句柄声明 */ extern SPI_HandleTypeDef hspi2; /* 片选控制 */ #define ADS8688_CS_LOW() HAL_GPIO_WritePin(ADS_CS_GPIO_Port, ADS_CS_Pin, GPIO_PIN_RESET) #define ADS8688_CS_HIGH() HAL_GPIO_WritePin(ADS_CS_GPIO_Port, ADS_CS_Pin, GPIO_PIN_SET) /* 写1字节 */ static void ADS8688_WriteByte(uint8_t data) { uint8_t txData data; uint8_t rxData 0; HAL_SPI_TransmitReceive(hspi2, txData, rxData, 1, 10); } /* 读1字节 */ static uint8_t ADS8688_ReadByte(void) { uint8_t txData 0x00; uint8_t rxData 0; HAL_SPI_TransmitReceive(hspi2, txData, rxData, 1, 10); return rxData; }这段代码看起来简单但有两点需要注意一是HAL_SPI_TransmitReceive超时时间不能太短我遇到过用10ms正常但系统主频提高后超时异常的情况所以建议使用50ms以上或者改为阻塞式发送二是如果SPI外设开启了DMA需要确保DMA传输完成后再拉高CS否则CS提前拉高会直接终止转换结果输出。3.2 寄存器配置与命令帧结构ADS8688的寄存器配置命令帧是16位高8位是命令字低8位是数据。其中命令字里又包含寄存器地址和读/写标志位。我在此处把常用的寄存器配置封装成了专门函数便于读代码时一目了然。/* 寄存器地址定义 */ #define ADS8688_REG_AUTO_SEQ 0x01 #define ADS8688_REG_CH_PWR_DOWN 0x02 #define ADS8688_REG_RANGE_SEL 0x03 #define ADS8688_REG_OTR_RANGE 0x04 #define ADS8688_REG_DEVICE_ID 0x05 /* 通用写寄存器 */ static void ADS8688_WriteReg(uint8_t regAddr, uint8_t data) { uint16_t cmd 0; cmd (0x80 | regAddr) 8; /* 写命令bit151 */ cmd | data; ADS8688_CS_LOW(); ADS8688_WriteByte((cmd 8) 0xFF); ADS8688_WriteByte(cmd 0xFF); ADS8688_CS_HIGH(); }这里补充说明一下命令帧的位定义。ADS8688命令帧的D15为读/写控制位读操作时该位为0写操作时该位为1D14~D8是寄存器地址D7~D0是写入的数据。也就是写命令时命令字高字节是0x80 | regAddr读命令时高字节是regAddr。3.3 初始化流程完整实现ADS8688上电之后建议的初始化顺序是上电延时→复位→写通道掉电寄存器→设置量程→配置自动扫描序列→写使能命令。这个顺序有讲究如果先配置量程再复位复位会把配置清掉白干。uint8_t ADS8688_Init(void) { uint8_t id 0; /* 复位芯片 */ ADS8688_RST_LOW(); HAL_Delay(10); ADS8688_RST_HIGH(); HAL_Delay(20); /* 读取设备ID验证SPI通信 */ id ADS8688_ReadReg(ADS8688_REG_DEVICE_ID); if ((id 0xF0) ! 0xA0) { return 1; /* 通信异常 */ } /* 将所有通道设置为正常工作不关断 */ ADS8688_WriteReg(ADS8688_REG_CH_PWR_DOWN, 0x00); /* 通道0~7量程配置为±10.24V */ ADS8688_WriteReg(ADS8688_REG_RANGE_SEL, 0x00); ADS8688_WriteReg(ADS8688_REG_OTR_RANGE, 0x00); /* 自动扫描使能 */ ADS8688_WriteReg(ADS8688_REG_AUTO_SEQ, 0x80); /* 主机触发模式使能 */ ADS8688_WriteReg(ADS8688_REG_AUTO_SEQ, 0xC0); return 0; }设备ID校验这块建议保留。我遇到过SPI引脚虚焊导致通信完全异常如果没有这一步ID读取后面所有寄存器配置都会失败而且很难快速定位问题。另外量程配置的对应关系上ADS8688的 range 寄存器每一组4位对应一个通道具体对应关系为0000表示±10.24V0001表示±5.12V0010表示±2.56V0011表示±1.28V0100表示0~10.24V0101表示0~5.12V。如果后续要多通道不同量程混用就需要按位去设置对应通道的4位数据不要把整字节直接写成0x00。3.4 单通道读取与转换结果计算驱动代码的核心功能是读取各通道的转换结果。读取数据的时序是先发送一条带通道地址的读命令帧芯片会开始转换转换完成后在下一个16个SCLK周期从SDO引脚输出结果。我封装的单通道读取函数如下uint16_t ADS8688_ReadChannel(uint8_t ch) { uint8_t cmd 0; uint16_t rawData 0; /* 通道地址编码bit7必须为0代表读命令 */ cmd (ch 0x07) 4; ADS8688_CS_LOW(); /* 发送命令帧第一个16位 SCLK */ ADS8688_WriteByte(cmd); ADS8688_WriteByte(0x00); /* 第二个16位 SCLK读取结果 */ rawData ADS8688_ReadByte(); rawData 8; rawData | ADS8688_ReadByte(); ADS8688_CS_HIGH(); return rawData; }有一点一定要提醒读取结果的第二个16位周期中前4位是状态标志后12位才是有效的转换数据的高12位加上第三个周期的低4位才是完整16位结果。所以上述读取函数在第二个周期读回的字节其实只包含了高8位数据低8位需要再读一个字节。具体而言ADS8688在/CS拉低后第一个16位周期写入命令第二个16位周期输出上一次转换结果的高8位第三个16位周期输出低8位。我第一次调试时只读了16位结果数据完全不对后来用逻辑分析仪抓了时序才反应过来。所以完整的读取流程应该发送命令后读取两个字节其中第一个字节是数据高字节第二个字节是数据低字节。/* 更准确的读取方式 */ uint16_t ADS8688_ReadChannelEx(uint8_t ch) { uint8_t cmd (ch 0x07) 4; uint16_t data 0; ADS8688_CS_LOW(); ADS8688_WriteByte(cmd); ADS8688_WriteByte(0x00); /* 第一个字节为高字节 */ data ADS8688_ReadByte(); data 8; /* 第二个字节为低字节 */ data | ADS8688_ReadByte(); ADS8688_CS_HIGH(); return data; }3.5 实际电压换算以及负数处理数据读回来是裸码必须换算成真实电压这一步必须结合输入量程来算。ADS8688的输出编码默认是二进制补码中心点0V对应的码值是0x0000正满量程对应0x7FFF负满量程对应0x8000。这里有个新手容易犯的错误直接把裸码除以65536再乘以满量程电压得到的结果只对单极性有效对双极性输入比如±10.24V会算错一半。对应±10.24V量程的换算公式float ADS8688_ConvertToVoltage(uint16_t rawData, float fsr) { int32_t signedData 0; float voltage 0.0f; /* 将16位无符号转为有符号 */ if (rawData 0x8000) { signedData (int32_t)rawData - 65536; } else { signedData (int32_t)rawData; } /* 电压换算fsr取±量程值如10.24V */ voltage ((float)signedData / 32768.0f) * fsr; return voltage; }补充一个细节这里除数不是65536而是32768因为补码数据在双极性模式下满量程只占用一半的码值空间。我用±10.24V量程测试下来换算出来的电压和万用表实测值误差在1~2mV以内效果不错。若还需要更高的精度可以考虑用更多位的浮点数运算但在嵌入式环境里浮点误差对多数场景不敏感。3.6 多通道轮询与DMA方式优化如果只是单通道读取上面的函数完全够用。但8个通道全量采集时一个一个调用会占用较长CPU时间。我在驱动里加了自动扫描模式配合DMA的批量读取方案。配置自动扫描后芯片会在每次命令后自动递增通道SDO引脚会连续输出当前通道的转换结果。#define ADS8688_CHANNEL_COUNT 8 uint16_t g_adcResults[ADS8688_CHANNEL_COUNT]; void ADS8688_ReadAllChannels_DMA(void) { uint8_t cmd 0x00; HAL_GPIO_WritePin(ADS_CS_GPIO_Port, ADS_CS_Pin, GPIO_PIN_RESET); /* 发送自动扫描触发命令 */ HAL_SPI_Transmit(hspi2, cmd, 1, 10); /* 连续读取16个字节每通道2字节 */ HAL_SPI_Receive(hspi2, (uint8_t *)g_adcResults, 16, 50); HAL_GPIO_WritePin(ADS_CS_GPIO_Port, ADS_CS_Pin, GPIO_PIN_SET); }注意这里HAL_SPI_Receive接收的16字节里相邻两个字节组成一个通道的原始转换值顺序从通道0到通道7。在实际项目里如果MCU主频不够高或者系统任务繁忙建议把这部分放到DMA中断里做避免阻塞主循环。另外自动扫描模式下转换结果的顺序与寄存器配置顺序有关建议通道顺序固定不要中途切换扫描序列否则解析数据时容易错位。4. 踩坑记录与调试经验4.1 读取数据全是0xFF的排查这个问题是SPI通信失败的典型症状。我排查时先测了CS引脚的波形发现CS被拉低后马上又被拉高怀疑是SPI时钟没有正常工作。用逻辑分析仪抓SCLK引脚发现SCLK没有输出时钟脉冲。检查初始化代码后发现SPI句柄优先级配置太高导致DMA请求把SCLK时钟配置挤掉了调整外设优先级后恢复正常。如果遇到读取数据始终是0建议按这个顺序排查先确认CS时序是否正确→再确认SCLK有无时钟→再确认SDI上的命令字波形是否正常→最后确认SDO上是否有数据返回。可以先用STM32的SPI回环测试验证SPI外设本身正常再去查ADS8688这边的问题。4.2 数据跳动大、低几位不稳定读数在低位跳个不停这个很多时候不是芯片问题而是硬件布局或基准源问题。如果基准引脚滤波电容离芯片太远ESL会变大内部基准噪声就会耦合到转换结果。我是在基准引脚处用了一个0805封装的0.1uF陶瓷电容紧贴芯片放置再在稍远位置放一个10uF钽电容问题基本消失。软件层面则可以把SPI时钟频率降低一些确保满足芯片的SCLK最大频率要求同时给转换结果加一个滑动平均滤波。实测对50Hz工频干扰有较明显改善。4.3 双极性输入负电压换算错误的教训我第一次测试时输入一个-5V的电压读回数值0xC000按无符号数除以65536再乘10.24算出来是7.68V这显然是错的。后来才意识到补码数据的特殊性。换算时建议先把rawData转成int16_t再除以32768乘以量程。这一步如果不用补码而直接用无符号负电压的值会完全不对。我在代码注释里特意写清楚这个坑避免后续维护的同事再踩。4.4 通道隔离度不足的处理多通道采集时如果前一个通道电压很高后一个通道会有微小串扰。这主要是输入多路复用器的导通电阻和源阻抗不匹配引起的。我的做法是在每个模拟输入通道前加一个RC低通滤波R取1kΩC取1nF截止频率约159kHz既能滤掉高频噪声又能减小通道间的串扰。这个RC参数并不是固定的如果信号频率很低可以适当增大C值进一步滤除干扰。5. 关于这套驱动代码的后续扩展ADS8688驱动代码的核心部分就是初始化、配置寄存器、读取转换结果、电压换算这几块。把这几部分封装好之后后续做多通道数据记录、闭环控制、阈值报警这些功能都是在这个基础上加应用层逻辑。我后来在这个驱动上跑了PID温度控制8个通道的温度采样率做到每通道50kSPSCPU负载完全扛得住。如果在使用自带HAL库的STM32工程里需要注意SPI中断和DMA中断的优先级设置保证采样节拍的稳定性。另外如果计划使用FreeRTOS建议把ADC读取任务放在高优先级任务里并用信号量同步转换完成事件避免多任务并发读写同一个SPI句柄导致数据错乱。我现在还在计划给这套驱动加一个基于状态机的自校准流程可以通过内置自检寄存器判断通道是否开路或短路这个在工业现场特别有用。等实现稳定了再写一篇完整的分享出来。本文还有配套的精品资源点击获取