STM32驱动ADS1118:SPI接口实现多路ADC与热电偶测温完整教程 📅 发布时间:2026/9/9 19:13:26 👁 浏览次数: 简介基于STM32F103与STM32F407的ADS1118完整驱动方案主要面向嵌入式开发者、电子竞赛学生以及从事高精度数据采集的工程师。程序覆盖4路单端、2路双差分和片内温度传感器三种采集模式实现从寄存器初始化、SPI读写时序到多通道数据转换与错误处理的全流程封装。代码结构清晰关键函数均附带注释便于直接移植到实际项目适合用于温度监测、压力采集或工业模拟量输入等场景。资源同时提供对应的实验测试数据包含不同输入模式下的转换结果与温度读数可借此校验驱动正确性并了解芯片实际精度与噪声表现。配套的ADS1118技术手册与应用说明能帮助读者深入理解16位ADC的配置逻辑、增益选择和采样率设定是一份上手与进阶兼顾的实用资料。整套资源以RAR压缩包发布大小约11.82MB已吸引318人学习参考对于希望快速掌握ADS1118开发的工程师和学生而言具有较高的学习和参考价值。 做多路模拟量采集或者热电偶测温的时候ADS1118 算是一颗绕不开的片子16 位分辨率、内置 PGA、自带温度传感器还通过 SPI 接口和 MCU 通信。但真开始写驱动才发现网上能找到的例程大多是 ADS1115 的 I2C 版本SPI 的 ADS1118 要么是寄存器片段要么只支持单通道连 STM32F103 和 STM32F407 的初始化差异都要自己慢慢试。前段时间我把这套驱动完整整理了一遍覆盖 4 路单端、2 路差分和内部温度传感器三种模式顺手把两代 STM32 的移植差异也理清了。这篇就按我的实际工程顺序讲一遍——哪些地方必须设置对、哪些坑我踩过之后不会再踩照着抄基本能一次点亮。1. 先从硬件说起为什么 ADS1118 值得单独写一份驱动ADS1118 是 TI 的 16 位 ΔΣ ADC和更常见的 ADS1115 最大的区别是接口ADS1115 走 I2CADS1118 走 SPI。很多人一开始会疑惑同样是 16 位 ADC为什么不用 I2C 版本答案很简单SPI 没有设备地址限制、速率更高而且 ADS1118 的数据率最高能到 860SPS做多通道轮询时比 I2C 舒服很多。更重要的是ADS1118 内部集成了一个温度传感器这颗传感器是专门为热电偶冷端补偿设计的。也就是说一路差分接热电偶同时读内部温度做冷端补偿一颗芯片全干完了。硬件接线不复杂核心引脚就是标准的 SPI 四线ADS1118 引脚STM32F103STM32F407说明SCLKPA5PA5SPI1 时钟DINPA7PA7SPI1 MOSI写命令字DOUT/DRDYPA6PA6SPI1 MISO读数据/转换完成指示CSPA4PA4任意 GPIO软件控制VDD3.3V3.3V模拟和数字电源共用GNDGNDGND必须共地有两个硬件细节我建议一开始就处理好。第一ADS1118 的输入绝对电压范围不能超过 VDD 0.3V即使 PGA 配置成 ±6.144V 满量程输入引脚也扛不住 6V。如果你要测 5V 或者更高的电压必须用电阻分压把信号按比例降到 3.3V 以内。第二DOUT/DRDY 这个引脚比较特殊它既是数据输出又是转换完成标志。有的板子 DOUT 悬空时电平不稳定建议在 DOUT 上接一个 10kΩ 上拉到 3.3V这样查询 DRDY 低电平时更可靠也不会影响 SPI 时序。至于电源去耦ADS1118 的 AVDD 和 DVDD 可以接在一起但芯片旁边至少要放一个 0.1µF 陶瓷电容最好再并一个 10µF 钽电容。模拟输入如果来自高阻抗源比如热电偶信号线上加一个 RC 低通滤波会稳很多后面实测部分我再细说。2. 移植前必须先搞清楚的 SPI 初始化差异F103 vs F407ADS1118 对 SPI 的时序要求很明确CPOL1、CPHA1也就是常说的 SPI Mode 3数据帧必须是 16 位高位在前SCLK 最高 4MHz。这几个参数只要有一个不对读回来的数据就是乱的。为什么必须是 Mode 3简单说ADS1118 在 SCLK 的下降沿更新输出数据主机在上升沿采样。SPI Mode 3 空闲时时钟为高第一个边沿是下降沿正好用来更新数据第二个边沿是上升沿主机采样。如果你配置成 Mode 0采样沿和更新沿完全反过来数据就错位了。这个错位不是每一次都错偶尔能读出看似正常的数据但会时不时跳变非常难排查。时钟分频也要算一下。ADS1118 的 SCLK 上限是 4MHzSTM32F103 的 SPI1 挂在 APB2 上72MHz 主频下 16 分频是 4.5MHz超了STM32F407 的 SPI1 同样在 APB2如果按 168MHz 主频、APB2 取 84MHz16 分频是 5.25MHz同样超了。所以两边我都用 32 分频F103 实际 2.25MHzF407 实际 2.625MHz都落在安全范围内。实测下来这个速度对 ADS1118 完全够用16 位数据一次事务也就几微秒。初始化代码我用标准外设库写F103 和 F407 的差异点主要在两处RCC 使能时钟的写法相同但 F407 需要额外调用 GPIO_PinAFConfig 把引脚复用成 SPI1 功能。SPI 外设本身的初始化参数两套平台完全一致/* F103 与 F407 通用SPI1 初始化 */ void ADS1118_SPI_Init(void) { SPI_InitTypeDef SPI_InitStructure; SPI_I2S_DeInit(SPI1); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_16b; SPI_InitStructure.SPI_CPOL SPI_CPOL_High; /* Mode 3 */ SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; /* Mode 3 */ SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_32; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }GPIO 部分 F103 这样写void ADS1118_GPIO_Init_F103(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); /* SCK PA5, MOSI PA7 复用推挽输出 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); /* MISO PA6 浮空输入 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); /* CS PA4 普通推挽输出默认拉高 */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); }F407 的差别就是复用映射void ADS1118_GPIO_Init_F407(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_SPI1); GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF; GPIO_InitStructure.GPIO_OType GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, GPIO_InitStructure); /* CS 仍然普通 GPIO */ GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_OUT; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_SetBits(GPIOA, GPIO_Pin_4); }CS 用普通 GPIO 控制别用 SPI 的硬件 NSS。ADS1118 要求在每次 SPI 事务之间有完整的 CS 高电平恢复时间软件手动控制最灵活硬件 NSS 在多字节传递时会自动翻转电平容易出问题。3. ADS1118 的核心交互逻辑写一次配置读回上一轮数据ADS1118 的配置寄存器一共 16 位每次 SPI 事务主机在 MOSI 上发送 16 位命令字ADS1118 同时在 MISO 上把上一次的转换结果移出来。这个边写边读旧值的机制是所有驱动的关键不理解它代码写出来就是乱的。配置寄存器各位的定义如下位名称作用bit15SS单次转换启动位1 触发转换bit14-12MUX输入通道选择bit11-9PGA满量程/增益配置bit8MODE1 单次模式0 连续模式bit7-5DR数据率选择bit4TS1 启用内部温度传感器bit3-0保留写 0单次模式下的完整转换流程应该是CS 拉低发送带 SS1 的配置字启动一次转换。此时 MISO 移出的是上电默认值或者上一次转换的结果这个数据不能要。CS 拉高等待转换完成。等待时间由数据率决定也可以用 DOUT/DRDY 引脚查询。CS 再次拉低发送不带 SS 位的相同配置字MISO 返回的就是本次转换结果。CS 拉高解析数据。为什么第二次发送配置字要不带 SS因为 ADS1118 在单次模式下SS1 会再触发一次新转换。你只是想读当前结果结果又启动了一次转换逻辑就乱了。不带 SS 只是把相同配置重新写一遍不触发转换MISO 移出的正好是刚转换完的数据。第一次读回的数据为什么不能要很多人上电后第一次读发现读回一个 0xFFFF 或者奇怪的固定值以为是硬件问题。其实就是因为 MISO 移出的是旧数据和当前输入信号一点关系都没有。这个不是故障是芯片的设计行为。转换等待时间直接查数据率数据率转换时间建议延时8 SPS125ms130ms16 SPS62.5ms70ms32 SPS31.25ms40ms64 SPS15.625ms20ms128 SPS7.8ms10ms250 SPS4ms6ms475 SPS约 2.1ms4ms860 SPS约 1.16ms2ms延时是最简单的方案适合绝大多数项目。如果想更精确可以查询 DOUT/DRDY 引脚。这个引脚在转换完成时会输出低电平检测到低电平再读数据不需要留那么多裕量。但要注意DOUT 就是 SPI 的 MISO 引脚查询低电平时要把引脚从 SPI 复用模式切回普通输入模式读完再切回去代码会麻烦不少。我的经验是除非对采样间隔有严格实时性要求否则直接延时反而更省心。4. 四路单端、两路差分和温度传感器的全部驱动代码驱动文件我拆成 ads1118.h 和 ads1118.c 两部分全部代码可以直接搬进工程。先看头文件配置宏都定义在这里/* ads1118.h */ #ifndef __ADS1118_H #define __ADS1118_H #include stm32f10x.h /* 如果用 F407改成 stm32f4xx.h */ /* 配置寄存器位定义 */ #define ADS1118_CFG_SS 0x8000u #define ADS1118_CFG_MUX_MASK 0x7000u #define ADS1118_CFG_MUX_DIFF_0_1 0x0000u #define ADS1118_CFG_MUX_DIFF_0_3 0x1000u #define ADS1118_CFG_MUX_DIFF_1_3 0x2000u #define ADS1118_CFG_MUX_DIFF_2_3 0x3000u #define ADS1118_CFG_MUX_AIN0 0x4000u #define ADS1118_CFG_MUX_AIN1 0x5000u #define ADS1118_CFG_MUX_AIN2 0x6000u #define ADS1118_CFG_MUX_AIN3 0x7000u #define ADS1118_CFG_PGA_6144 0x0000u #define ADS1118_CFG_PGA_4096 0x0200u #define ADS1118_CFG_PGA_2048 0x0400u #define ADS1118_CFG_PGA_1024 0x0600u #define ADS1118_CFG_PGA_512 0x0800u #define ADS1118_CFG_PGA_256 0x0A00u #define ADS1118_CFG_MODE_CONT 0x0000u #define ADS1118_CFG_MODE_SINGLE 0x0100u #define ADS1118_CFG_DR_8SPS 0x0000u #define ADS1118_CFG_DR_16SPS 0x0020u #define ADS1118_CFG_DR_32SPS 0x0040u #define ADS1118_CFG_DR_64SPS 0x0060u #define ADS1118_CFG_DR_128SPS 0x0080u #define ADS1118_CFG_DR_250SPS 0x00A0u #define ADS1118_CFG_DR_475SPS 0x00C0u #define ADS1118_CFG_DR_860SPS 0x00E0u #define ADS1118_CFG_TS 0x0010u /* 默认基础配置PGA2048单次模式128SPS */ #define ADS1118_BASE_CFG (ADS1118_CFG_PGA_2048 | ADS1118_CFG_MODE_SINGLE | ADS1118_CFG_DR_128SPS) #define ADS1118_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define ADS1118_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4) void ADS1118_Init(void); uint16_t ADS1118_Transfer(uint16_t cmd); uint16_t ADS1118_ReadADC(uint16_t mux_cfg); int16_t ADS1118_ReadDiff(uint16_t mux_cfg); float ADS1118_ReadTemp(void); #endif底层的 SPI 传输函数就是一次完整的 16 位收发CS 包住整个事务/* ads1118.c */ #include ads1118.h void ADS1118_Init(void) { /* 调用前面写的 GPIO 和 SPI 初始化函数 */ ADS1118_GPIO_Init_F103(); /* 或 F407 */ ADS1118_SPI_Init(); } uint16_t ADS1118_Transfer(uint16_t cmd) { uint16_t rx 0; ADS1118_CS_LOW(); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, cmd); while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) RESET); rx SPI_I2S_ReceiveData(SPI1); ADS1118_CS_HIGH(); return rx; } static void ADS1118_WaitConvert(uint16_t cfg) { /* DR 编码对应数据率8/16/32/64/128/250/475/860 SPS */ static const uint32_t wait_ms[8] {130, 70, 40, 20, 10, 6, 4, 2}; uint8_t dr (uint8_t)((cfg 5) 0x07); delay_ms(wait_ms[dr]); }单端和差分共用一个底层区别只是 MUX 配置不同。读单端时原始值按无符号解析读差分时按有符号二进制补码解析uint16_t ADS1118_ReadADC(uint16_t mux_cfg) { uint16_t cfg ADS1118_BASE_CFG | (mux_cfg ADS1118_CFG_MUX_MASK); /* 写配置并启动转换此时读回的是旧数据丢弃 */ ADS1118_Transfer(cfg | ADS1118_CFG_SS); /* 等待转换完成 */ ADS1118_WaitConvert(cfg); /* 重新写入相同配置但不带 SS读回本次转换结果 */ return ADS1118_Transfer(cfg); } int16_t ADS1118_ReadDiff(uint16_t mux_cfg) { uint16_t cfg ADS1118_BASE_CFG | (mux_cfg ADS1118_CFG_MUX_MASK); ADS1118_Transfer(cfg | ADS1118_CFG_SS); ADS1118_WaitConvert(cfg); return (int16_t)ADS1118_Transfer(cfg); }温度读取更简单关键是把配置里的 TS 位置 1然后读回来的原始值除以 128.0 就是摄氏度float ADS1118_ReadTemp(void) { uint16_t cfg (ADS1118_BASE_CFG ~ADS1118_CFG_MUX_MASK) | ADS1118_CFG_TS; ADS1118_Transfer(cfg | ADS1118_CFG_SS); ADS1118_WaitConvert(cfg); return (float)((int16_t)ADS1118_Transfer(cfg)) / 128.0f; }主程序里把三组数据全跑一遍可以直观看到整个驱动的调用方式/* main.c 示例 */ const uint16_t mux_single[4] { ADS1118_CFG_MUX_AIN0, ADS1118_CFG_MUX_AIN1, ADS1118_CFG_MUX_AIN2, ADS1118_CFG_MUX_AIN3 }; int main(void) { uint16_t single_raw[4]; int16_t diff_raw[2]; float temp; float voltage[4]; const float fsr 2.048f; /* 和 BASE_CFG 里的 PGA_2048 对应 */ SystemInit(); ADS1118_Init(); while (1) { /* 4 路单端 */ for (int i 0; i 4; i) { single_raw[i] ADS1118_ReadADC(mux_single[i]); voltage[i] (float)single_raw[i] * fsr / 32768.0f; } /* 2 路差分AIN0-AIN1 和 AIN2-AIN3 */ diff_raw[0] ADS1118_ReadDiff(ADS1118_CFG_MUX_DIFF_0_1); diff_raw[1] ADS1118_ReadDiff(ADS1118_CFG_MUX_DIFF_2_3); /* 内部温度 */ temp ADS1118_ReadTemp(); delay_ms(50); } }电压换算公式就是电压 原始值 × 满量程 / 32768。PGA2048 时满量程是 ±2.048V所以 1 LSB 对应 62.5µV。差分通道的满量程和单端一样只是原始值要按 int16 处理负电压对应负值。温度换算为什么除以 128ADS1118 内部温度传感器实际是 14 位有效数据左对齐到 16 位寄存器最后两位固定为 0。14 位数据每个 LSB 是 0.03125°C左对齐后相当于 16 位数据每个 LSB 是 0.0078125°C也就是除以 128。这个细节网上很多代码都没讲清楚直接给一个 magic number现在知道原理就不用怕了。5. 实测最容易踩的四个坑5.1 重新配置量程后第一笔数据到底能不能要这个问题也是很多人问过的如果我重新配置量程需要等待多少时间答案是不仅要等转换完成还要丢掉切换后的第一笔转换结果。ADS1118 内部有可编程增益放大器切换 PGA 增益后放大器建立需要时间。你写完新配置立刻启动转换虽然 DRDY 也正常拉低转换时序也完整但增益切换瞬间引入的建立误差还没消失这笔数据往往偏大或偏小看起来像毛刺。工程上的做法是切换量程或通道后先做一次完整转换并丢弃再采集有效值。写成一个函数就是uint16_t ADS1118_ReadADC_Stable(uint16_t mux_cfg) { ADS1118_ReadADC(mux_cfg); /* 第一次结果丢弃 */ return ADS1118_ReadADC(mux_cfg); /* 第二次结果作为有效值 */ }如果你的采样循环一直在同一通道上跑不需要每次都丢弃只有切换配置的那一次需要处理。如果 4 路通道轮流采集MUX 一直在切换建议每路都按这个方式读代价是采样周期变长但数据可信度明显更高。5.2 SPI Mode 用错后的症状ADS1118 必须工作在 SPI Mode 3也就是 CPOL1、CPHA1。我曾经为了在某个工程里和另一片 Flash 共用 SPI 总线图省事把 CPOL 改成了 0结果读出来的数据表现为低位偶尔跳变、整数值偏差固定倍数、数据率和实际对不上。当时一度怀疑是 PCB 飞线太长干扰后来单独用逻辑分析仪抓时序才发现是 SPI 模式不匹配。判断 SPI 模式是否搞对最直接的方法是切到 8 SPS 低速读温度传感器。温度是缓慢变化的如果连续读 20 次数值稳定、室温在合理范围基本可以确认时序没问题。如果每次读出来数据乱跳先查 Mode别怀疑芯片。5.3 DOUT 读不到数据的排查方向读数据一直读到 0xFFFF 或者每次都一样先检查三件事。第一CS 拉低后有没有足够的建立时间CS 下降沿到第一个 SCLK 上升沿之间至少要留出一个 SCLK 周期的时间用软件控制时一般没问题但如果 CS 引脚速度配置太慢要留意。第二DOUT 引脚是否浮空ADS1118 的 DOUT/DRDY 在一些配置下需要上拉电阻加了 10kΩ 上拉基本能解决。第三确认 SPI 配置真的是 16 位数据帧末尾采样如果错配成 8 位模式一次事务只传输半个命令字芯片根本不响应读回的全是垃圾。还有一个容易忽略的点ADS1118 的 DOUT 在 CS 拉高时处于高阻态如果 MISO 线上只有一个设备问题不大如果 MISO 上挂了多个 SPI 设备务必确保其他设备的 CS 是拉高的否则总线冲突会把数据拉坏。5.4 采样稳定性与滤波建议ADS1118 虽然分辨率高但数据率越高噪声越大。860SPS 下的有效分辨率明显不如 8SPS。实测 128SPS 是性价比最高的档位响应速度和噪声控制都比较均衡。如果测的是温度这类缓变信号建议降到 32SPS 或 16SPS数据会稳很多。硬件的 RC 滤波对抑制共模噪声很有用。输入引脚对地各接一个 100Ω 到 1kΩ 的串联电阻再对地接 0.1µF 电容能明显减少长线引入的毛刺。软件上做多次平均也有效比如连续读 16 次取平均代价是采样周期变长但 128SPS 下 16 次平均仍然能保持每秒 7~8 次的更新率绝大多数传感器场景都够用。我自己的项目里128SPS 加 16 次平均测 0~2V 的传感器信号稳定度能做到最后一位基本不动这个组合你们可以直接参考。本文还有配套的精品资源点击获取