
简介压缩包内含基于STM32F103的ADS1118高精度ADC驱动例程面向需要快速移植TI官网代码的嵌入式开发者。工程采用标准库分层结构集成SPI/I2C通信、延时、按键、软定时器、多按键处理等模块并保留官方驱动源文件便于逐行对照学习。全包共86个文件以40个C源文件和39个H头文件为主涵盖驱动实现与接口声明另含启动文件、链接脚本、Keil工程配置文件uvprojx/uvoptx、编译产物bin/hex以及一键清理脚本目录完整。整体仅303KB轻量易部署。针对ADS1148的兼容性开发者只需按手册调整配置参数即可复用底层驱动。目前已有673人学习下载适合正在调试ADS1118/ADS1148多通道信号采集、或需要参考STM32标准库外设编程的研发人员直接套用。1. 把ADS1118从官网例程搬到STM32先搞清楚这芯片到底怎么用ADS1118是TI出的一款16位、带内部基准和温度传感器的SPI接口ADC和ADS1148属于同一家族的寄存器风格很多ADS1148的例程也能直接套用。官网上给的ADS1118例程通常跑在C2000或MSP430上甚至直接是一个.c文件加一堆寄存器操作有人把so9op、everoh5这些第三方例程下下来一抄发现SPI读回来的数据完全不对问题基本都出在时序和寄存器配置上。这篇文章就顺着官网例程的思路把ADS1118的寄存器、SPI时序、STM32移植和排错一步步拆开。适合用过SPI ADC但对ADS1118不熟的人移植完能立刻读电压和温度并且知道每个参数改了什么。2. ADS1118的SPI时序与寄存器官网例程里不会给你讲的部分2.1 为什么ADS1118必须用32个SCLK16位配置指令16位数据ADS1118的SPI接口和普通ADC不太一样。你向它发送一个16位的配置字同时它会在这一帧里返回上一次转换的16位结果。所以一次完整的通信需要32个SCLK前16个SCLK发送配置后16个SCLK读取数据。官网例程里常见的操作是先拉低CS然后发送16位配置接着再发16位空的SCLK把数据读出来最后拉高CS。SPI模式必须用Mode 3也就是CPOL1、CPHA1。ADS1118在SCLK的下降沿输出数据在上升沿采集输入和STM32的SPI模式3正好对应。如果你用Mode 0去读配置字能发进去但读回来的数据会整体错位表现是电压值乱跳或者恒为某个固定值。// 读取ADS1118一次转换结果cfg为16位配置字 uint16_t ADS1118_ReadData(uint16_t cfg) { uint16_t rx 0; CS_LOW(); // 发送16位配置 HAL_SPI_Transmit(hspi1, (uint8_t*)cfg, 2, 100); // 再发16个时钟读取转换结果 uint16_t dummy 0; HAL_SPI_TransmitReceive(hspi1, (uint8_t*)dummy, (uint8_t*)rx, 2, 100); CS_HIGH(); return rx; }这段代码里cfg是16位配置字STM32的SPI如果配置成8位数据宽度HAL_SPI_Transmit发两个字节时要注意大小端问题。STM32是小端直接强制转换后发送是先发低字节再发高字节但ADS1118期望先收到高字节。所以更稳妥的写法是把配置字拆成高8位和低8位按顺序发送。很多移植失败的案例就卡在这里寄存器配置明明是对的但字节序反了芯片根本不响应。2.2 配置寄存器的16位含义MUX、PGA、DR、TS_MODE逐个拆ADS1118的16位配置寄存器从高位到低位依次是SS启动转换、MUX[2:0]输入多路选择、PGA[2:0]可编程增益、MODE连续/单次、DR[2:0]数据速率、TS_MODE温度传感器模式、PULL_UP内部上拉、NOP[1:0]、RESERVED[1:0]。位名称取值含义15SS1单次模式启动转换14:12MUX000AIN0相对AIN1差分14:12MUX100AIN0相对GND单端11:9PGA000±6.144VLSB187.5uV11:9PGA001±4.096VLSB125uV11:9PGA010±2.048VLSB62.5uV8MODE0连续转换8MODE1单次转换7:5DR100128SPS复位默认4TS_MODE0ADC模式4TS_MODE1温度传感器模式3PULL_UP1使能DOUT/DRDY内部上拉单次模式下写入配置字后SS位为1会触发一次转换转换完成后数据才能从DOUT/DRDY引脚读取。连续模式下只需配置一次芯片持续转换每次读取返回上一次结果。MUX和PGA是调整量程和输入通道的关键参数如果测量电压超过PGA对应的满量程转换结果会削顶。2.3 数据读出格式左对齐的16位有符号数ADS1118的数据是16位有符号数最高位是符号位。官网例程里通常直接返回原始的uint16_t但做电压换算时要先转成int16_t。ADC结果和电压的关系是电压 ADC值 * LSB其中LSB由PGA配置决定。比如PGA010时满量程±2.048V对应16位满量程32767LSB就是2.048×2/65536≈62.5uV。#define ADS1118_PGA_6144 0x0000 #define ADS1118_PGA_4096 0x0200 #define ADS1118_PGA_2048 0x0400 float ADS1118_GetVoltage(uint16_t raw, uint16_t pga_cfg) { int16_t val (int16_t)raw; // 转为有符号数 float lsb 0.0f; switch (pga_cfg) { case ADS1118_PGA_6144: lsb 0.0001875f; break; case ADS1118_PGA_4096: lsb 0.000125f; break; case ADS1118_PGA_2048: lsb 0.0000625f; break; default: lsb 0.0000625f; break; } return val * lsb; }注意这里的符号扩展uint16_t强转成int16_t是标准做法但如果直接用uint16_t做乘法负半轴的电压会变成很大的正数。官网例程里有一个宏定义ADS1118_GET_VOLTAGE本质也是把寄存器值先转成int16_t再做运算。温度数据也一样温度传感器模式下返回的数据高14位有效除以128得到摄氏度。3. 在STM32上把官网ADS1118例程跑通的最小代码骨架3.1 用CubeMX配置SPICPOL1、CPHA1这一关过不去后面全白搭STM32侧的SPI配置是移植ADS1118例程第一道坎。CubeMX里选择SPI1参数设置如下Baud Rate Prescaler选32或64取决于你的时钟频率目标是SCLK在1MHz到4MHz之间ADS1118最大支持4MHz。CPOL选HighCPHA选2 Edge这对应SPI Mode 3。Data Size选8位因为ADS1118的16位帧拆成两个字节发送更可控。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;FirstBit必须是MSB先行ADS1118不支持LSB模式。NSS用软件控制把CS引脚配成普通GPIO输出。SPI时钟频率建议先保守一点用1MHz起步调通后再往上升。SCLK太快时如果杜邦线较长信号反射会导致MISO采样出错表现是偶尔读出0xFFFF或者数据跳变。3.2 移植官方库函数把AMS1118_ReadData改成自己的SPI读写官网ADS1118例程里通常有几个基础函数ADS1118_Init、ADS1118_WriteRead、ADS1118_GetTemperature。移植到STM32时只需要保留逻辑把底层SPI_XFER替换成HAL库调用。下面是一个完整的ads1118.c骨架// ads1118.c - STM32 HAL移植版本 #include ads1118.h #include spi.h #include gpio.h static void CS_LOW(void) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); } static void CS_HIGH(void) { HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); } // 向ADS1118写入配置并读取上一次转换结果 uint16_t ADS1118_Transfer(uint16_t cfg) { uint8_t tx_buf[2], rx_buf[2]; tx_buf[0] (cfg 8) 0xFF; // 先发高字节 tx_buf[1] cfg 0xFF; CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 2, 10); // 再发两个字节取数据 tx_buf[0] 0x00; tx_buf[1] 0x00; HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 2, 10); CS_HIGH(); return ((uint16_t)rx_buf[0] 8) | rx_buf[1]; }代码里拆成两次HAL_SPI_TransmitReceive第一次把配置字发给芯片第二次产生SCLK读取数据。有些做法是直接用16位数据宽度一次发完但对齐和字节序问题会麻烦很多。这个函数每次调用都产生一次完整的32-SCLK通信调用频率就是你的采样频率上限。注意HAL_SPI_TransmitReceive在发送和接收是同时进行的所以第二次发送的字节内容无所谓只是用来产生时钟。3.3 用单次转换模式读一个通道代码换算电压单次转换是最容易调试的模式。配置字的关键位SS1启动转换MUX100表示AIN0对GND单端PGA010表示±2.048V量程MODE1单次模式DR100表示128SPS。组合起来配置字是0x8584高字节0x85低字节0x84。#define CFG_SINGLE_AIN0 0x8584 // SS1, MUX100, PGA010, MODE1, DR100, TS0 float ReadADS1118_AIN0(void) { uint16_t raw ADS1118_Transfer(CFG_SINGLE_AIN0); // 第一次读回的是无效数据需要再读一次 raw ADS1118_Transfer(CFG_SINGLE_AIN0); return (int16_t)raw * 0.0000625f; }为什么读两次第一次ADS1118_Transfer发出配置字后返回的是上一次转换的旧数据此时新的转换才刚刚开始。第二次调用时转换已完成读到的才是这次配置对应通道的数据。单次模式下每次读数据都要发配置字这是官网例程里的标准流程改不动。多通道扫描时这个特性尤其关键切换通道后第一次读到的数据一定是上一个通道的残留。4. 把例程改造成能用温度传感器、多通道轮询、连续模式与数据速率选择4.1 读内部温度传感器TS_MODE置1后回归配置要小心温度传感器是ADS1118比较实用的功能,配置字里TS_MODE1即可。温度传感器的数据格式是高14位有效转换公式是温度 原始值 2 / 128。读取流程和ADC一样也是先发配置字再读数据。#define CFG_TEMP 0x8784 // TS_MODE1其余保持单次转换配置 float ADS1118_ReadTemp(void) { uint16_t raw ADS1118_Transfer(CFG_TEMP); raw ADS1118_Transfer(CFG_TEMP); int16_t val (int16_t)raw; return (val 2) * 0.0078125f; // 除以128 }一个容易踩的坑是温度模式配置字内部有PULL_UP位和NOP位的组合直接修改别人的例程时经常把高字节的低几位搞混。比如官网例程给的是0x878B多出来的0x07是PULL_UP1外加两个NOP01这些位不影响温度读取但如果你把一个ADC通道的配置字只改TS_MODE位而不动其他位读出来的温度会偏高或偏低。另外从温度模式切回ADC模式时第一个读到的数据作废需要多读一次原理和通道切换一样。4.2 多通道扫描与MUX切换每次切换后要丢一次数据多通道测量场景下MUX字段需要按序切换。单端输入可以有四个通道AIN0、AIN1、AIN2、AIN3各对GND。MUX编码分别是100、101、110、111。轮询代码如下const uint16_t mux_cfg[4] { 0x8584, // AIN0 0x95C4, // AIN1, 注意PGA位也变了 0xA5C4, // AIN2 0xB5C4 // AIN3 }; void ScanAllChannels(float *voltages) { for (int i 0; i 4; i) { ADS1118_Transfer(mux_cfg[i]); // 切换通道 uint16_t raw ADS1118_Transfer(mux_cfg[i]); // 丢弃第一次 voltages[i] (int16_t)raw * 0.0000625f; } }通道切换后丢弃第一次读数是必须的因为ADS1118的输入多路器和PGA需要建立时间单次模式下转换周期内信号还没稳定就采样结果会有几个LSB的跳动。如果你把数据速率调到860SPS这个建立时间的影响会更明显低速8SPS或16SPS下基本看不出来。实际项目中如果四个通道的电压范围差异很大建议每个通道单独配PGA而不是共用一个增益。4.3 连续转换和DRDY引脚从轮询到中断单次模式的缺点是每次读数据都要发配置字CPU占用高。连续模式下配置一次后芯片自动转换你只需要在需要时读取最新数据。连续模式的配置字把MODE位设为0读取时不发配置字发任意16位即可拿数据。但有个细节连续模式下如果收到一个新的配置字哪怕是全0芯片会把它当作新配置执行所以读取时要特别小心不要破坏配置。#define CFG_CONT_AIN0 0x8580 // 连续模式AIN0PGA010128SPS uint16_t last_raw 0; void ADS1118_InitContinuous(void) { ADS1118_Transfer(CFG_CONT_AIN0); // 启动连续转换 } uint16_t ADS1118_ReadContinuous(void) { // 读数据时发全0但不改变寄存器 uint8_t tx[2] {0x00, 0x00}, rx[2]; CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 10); CS_HIGH(); return ((uint16_t)rx[0] 8) | rx[1]; }连续模式下如果要切换通道或改PGA必须重新发一次完整的配置字。DRDY引脚在每次转换完成时会产生一个下降沿可以接到STM32的EXTI中断在中断里读数据。这样比轮询省CPU但要注意中断服务函数里不能做太多事最合理的做法是中断里置一个标志位主循环检测到标志后再去读SPI。模式配置字MODE位读取方式适用场景单次1每次读都要发配置字低速多通道、省功耗连续0发全0读数据单通道高速采集、DRDY中断5. 三个必看的排错位置SPI波形、配置字、数据换算5.1 用逻辑分析仪看SS、SCLK、DIN/MISOADS1118移植失败时第一步不是改代码而是抓波形。把逻辑分析仪接到SS、SCLK、DINMOSI、MISO四个引脚上触发条件设为SS下降沿抓一次完整的通信过程。先数SCLK数量一次通信必须是32个SCLK多一个少一个都说明SPI配置或代码逻辑有问题。再看DIN上的第一个字节如果是配置0x85二进制是1000 0101MSB先行下第一个SCLK上升沿时DIN必须是高电平。最后看MISO前16个SCLK期间MISO输出的是旧数据后16个SCLK输出新数据如果MISO一直为高或一直为低说明芯片没有正确响应配置字。常见情况是MISO始终为高这可能是因为DOUT/DRDY引脚没有上拉。ADS1118的DOUT/DRDY是开漏输出必须接上拉电阻或者使能内部PULL_UP位。如果你用的是开发板上的SPI接口板子一般已经接了上拉如果是自己飞线务必加一个10kΩ上拉到3.3V。5.2 常见症状对照症状可能原因排查方向读回全是0xFFFFDOUT上拉缺失或SPI空闲时钟极性错误检查上拉电阻和CPOL数据是理论值的一半字节序反了或数据左移了一位检查MSB/LSB和数据对齐电压跳变无规律供电不稳或SCLK太快降低波特率分频切换通道后数据滞后没丢弃切换后第一次数据补一次空读温度读数接近0TS_MODE设置后没有重新配置检查配置字是否被覆盖数据换算方面如果读回来的原始值乘以LSB后和万用表测的电压差一个固定倍数先检查PGA是否配置正确。比如PGA用了010±2.048V但实际寄存器里写成了100±1.024VLSB变小一倍换算出来的电压就偏大。5.3 验证技巧把AIN接GND和VDD测满量程调通代码后做两个确定性的测试。第一把AIN0接到GND读到的原始值应该在0附近波动正负几个LSB是正常噪声。第二把AIN0接到3.3VPGA配置为±6.144V时3.3V对应原始值约17600左右误差在1%以内。这两个点验证通过后焊接电路和传感器误差才能放心地归因到外部。如果接GND时读数不是0而是某个固定的正数或负数先查PCB走线是否有串扰再查GND是否和AGND隔离。ADC前端加一个RC低通滤波器比如1kΩ100nF可以有效抑制高频噪声但会引入几十微安的漏电流路径高阻源场景下宁可不加。温度传感器的验证更简单用ADS1118_ReadTemp读室温25°C左右读数的误差应该小于2°C如果偏差过大先确认val 2有没有写错成val 4这个位移差一档温度读数会完全对不上。本文还有配套的精品资源点击获取