简介:面向51单片机开发者的ADS1118高精度ADC驱动源代码,基于C语言编写并带有详细注释,主要解决模拟电压采集、转换及通过串口发送到PC实时显示的问题。程序结构清晰,包含初始化、配置、读取数据等完整函数,适合需要快速上手ADS1118或学习单片机与外部ADC芯片通信的工程师与爱好者。压缩包共17个文件,约41KB,以C源文件、头文件、Keil工程文件、Hex烧录文件以及备份和编译中间文件为主,其中源文件和头文件构成程序主体,工程文件与Hex可方便导入或直接烧录验证。该资源已有841人学习下载,对于理解ADC工作原理和嵌入式数据采集流程有实际参考价值。借助代码注释,可掌握ADS1118的寄存器配置、多通道读取及串口输出逻辑,还能基于现有工程模板快速修改移植到自己的电压采集项目中,减少重复开发工作。 ADS1118这颗芯片,我先后在三个项目里用过:电池单体电压巡检、热电偶冷端补偿采集、称重传感器的毫伏信号采集。第一次调它的时候,我以为只不过是把ADS1115从I2C换成SPI接口而已,结果被“一个SPI帧同时完成读写”这个机制卡了整整一天。后来把寄存器时序和源代码理顺了,才发现这颗16位AD芯片其实非常顺手——既能做高精度采集,又自带温度传感器和PGA放大,单芯片能顶好几个外围器件的活。这篇文章把驱动源码、量程切换时序、以及我实测踩过的坑一次性写清楚,适合正在做嵌入式高精度采集、需要16位分辨率但预算有限、又不想上24位ADS1256的工程师参考。

1. 为什么我会在采集项目里反复选择ADS1118

1.1 16位分辨率与实际有效位数

ADS1118是TI推出的16位Δ-Σ ADC,采样率从8SPS到860SPS可选,供电范围2.0V到5.5V,内置可编程增益放大器(PGA)和低温漂基准。单从参数表看,它就是为“工业级中等精度采集”设计的:16位分辨率意味着在±4.096V量程下理论LSB是125µV,这个精度用于电池电压监测、温度采集、应变桥称重都够用。

但这里我必须说句实在话:16位分辨率不等于16位有效精度。Δ-Σ ADC的有效位数受噪声影响很大,尤其在高增益档位下,实际有效分辨率往往会掉到14位甚至更低。我见过不少新手看到“16位”就以为能分辨到微伏级,结果一测噪声就懵了。所以选型时你要清楚,ADS1118定位是“低成本中等精度”,不是仪表级的24位方案。

1.2 与ADS1115、ADS1256的取舍

很多工程师第一个接触的芯片是ADS1115(I2C接口),然后看到ADS1118觉得“不就是SPI版本吗”。这个理解对了一半。ADS1118和ADS1115核心ADC结构相似,但多了几个关键能力:内置温度传感器、DOUT/DRDY复用引脚、内部上拉使能选项。尤其是内置温度传感器,做热电偶采集时可以用它做冷端补偿,省掉一颗独立的温度传感器芯片,成本和布线都省了不少。

我在项目里做选型时通常这样取舍:

芯片 接口 分辨率 内置温度传感器 适用场景
ADS1115 I2C 16位 无 常规电压/电流采集
ADS1118 SPI 16位 有 热电偶、称重、多通道采集
ADS1256 SPI 24位 无 高精度测量、微弱信号

如果系统里已经有I2C总线且挂载设备不多,用ADS1115够;如果走SPI且需要冷端补偿,ADS1118是更优解;真到了微伏级测量,老老实实上ADS1256。选ADS1118的最大理由,其实是它在“精度、成本、功耗、外围复杂度”之间找到了一个很舒服的平衡点。

2. 配置寄存器和量程切换的关键时序

2.1 配置寄存器的位段拆解

ADS1118的配置寄存器是16位,通过SPI帧写入。它的位段分布和ADS1115不一样,不能直接照搬,我第一次就是栽在这里。按数据手册定义,各位段大致如下(以TI官方手册为准):

  • Bit 15:OS。单次模式下写1启动一次转换;连续模式下读取此位可判断转换是否完成。
  • Bit 14:保留位。
  • Bit 13-10:MUX[3:0],输入多路选择器配置,决定AIN0-AIN3之间是差分还是单端。
  • Bit 9-7:PGA[2:0],增益配置,对应±6.144V到±0.256V六档量程。
  • Bit 6:MODE,0为连续转换,1为单次转换。
  • Bit 5-3:DR[2:0],数据速率配置。
  • Bit 2:TS_MODE,0为ADC模式,1为温度传感器模式。
  • Bit 1:PULL_UP_EN,使能DOUT/DRDY引脚内部上拉。
  • Bit 0:NOP,保留位,通常写0。

写这16位配置字时,我习惯用宏定义把每个字段的编码值先算好,避免硬编码。这样代码的可读性和可维护性会好很多,后面切换通道、切换量程都只是改一个宏的事。

2.2 一个SPI帧同时完成读写

ADS1118和I2C接口芯片最大的不同在于:每次16位SPI传输是同时进行的。主控制器通过DIN引脚移出16位配置字,同时从DOUT引脚移入16位转换结果。也就是说,你在写“下一次配置”的同时,读到的其实是“上一次转换结果”。

这个机制非常高效,但也让很多第一次用的人犯迷糊。比如你想切换量程并读取新数据,如果只发一个帧,拿到的一定是切换前的旧数据。正确做法是先发一个含新配置的帧启动转换,等转换完成后再发第二帧读结果。理解了这一帧两用的机制,后续代码逻辑就顺了。

2.3 重新配置量程后到底要等多久

这是很多人在实际项目中最关心的问题:我改了PGA量程,多久之后读到的数据才是新量程下的有效数据?

答案取决于你用的是单次模式还是连续模式。

单次模式下,你在配置字里置OS=1,芯片会放弃当前空闲状态,按新配置启动一次完整转换。转换完成后DOUT/DRDY引脚会拉低(需先配置上拉或外部上拉),或者你可以按数据率算时间。比如DR设为128SPS,一个转换周期约7.8125ms,实际等10ms左右就能安全读取。如果设成8SPS,转换周期是125ms,你至少要等130ms,提前读回来的码值没有意义。

连续模式下,切换量程后芯片并不会立刻丢弃旧配置下的转换结果。正在进行的那个转换周期仍按旧PGA完成,随后才切换到新配置。所以你写完新配置后需要等1到2个转换周期,并且建议把前1到2次读到的数据直接丢弃,从第三个数据开始使用。这个“丢弃预热数据”的步骤,在连续模式采集里几乎是必须的,别省。

数据速率和转换时间的对应关系,我建议直接记一张表:

DR设置 数据速率 单次转换周期
000 8 SPS 125 ms
001 16 SPS 62.5 ms
010 32 SPS 31.25 ms
011 64 SPS 15.625 ms
100 128 SPS 7.8125 ms
101 250 SPS 4 ms
110 475 SPS 2.105 ms
111 860 SPS 1.1628 ms

如果你用轮询方式而不用DRDY中断,我建议的余量是转换周期的1.2到1.5倍,别卡着理论值。芯片在单次模式下每次启动转换还有一小段内部振荡器稳定时间,卡太紧会导致偶尔读到旧数据,排查起来很费劲。

3. ADS1118驱动源代码:一次SPI帧完成读写

3.1 底层SPI传输层

先把底层SPI传输函数写好。这里我给一个不依赖具体MCU平台的模拟SPI实现,方便你移植到任意芯片。用硬件SPI的话,把下面函数替换成你平台的SPI收发函数即可,核心逻辑不变。

// ads1118.h
#ifndef __ADS1118_H
#define __ADS1118_H

#include <stdint.h>

// 配置寄存器位定义(按数据手册,不同版本请以手册为准)
#define ADS1118_CFG_OS          (1u << 15)  // 单次转换启动位

// MUX 输入通道选择,起始位 bit10
#define ADS1118_MUX_AIN0_AIN1   (0u << 10)
#define ADS1118_MUX_AIN0_AIN3   (1u << 10)
#define ADS1118_MUX_AIN1_AIN3   (2u << 10)
#define ADS1118_MUX_AIN2_AIN3   (3u << 10)
#define ADS1118_MUX_AIN0_GND    (4u << 10)
#define ADS1118_MUX_AIN1_GND    (5u << 10)
#define ADS1118_MUX_AIN2_GND    (6u << 10)
#define ADS1118_MUX_AIN3_GND    (7u << 10)

// PGA 增益,起始位 bit7
#define ADS1118_PGA_6144        (0u << 7)   // ±6.144V
#define ADS1118_PGA_4096        (1u << 7)   // ±4.096V
#define ADS1118_PGA_2048        (2u << 7)   // ±2.048V
#define ADS1118_PGA_1024        (3u << 7)   // ±1.024V
#define ADS1118_PGA_512         (4u << 7)   // ±0.512V
#define ADS1118_PGA_256         (5u << 7)   // ±0.256V

// MODE,bit6
#define ADS1118_MODE_CONTINUE   (0u << 6)
#define ADS1118_MODE_SINGLE     (1u << 6)

// DR 数据速率,起始位 bit3
#define ADS1118_DR_8SPS         (0u << 3)
#define ADS1118_DR_16SPS        (1u << 3)
#define ADS1118_DR_32SPS        (2u << 3)
#define ADS1118_DR_64SPS        (3u << 3)
#define ADS1118_DR_128SPS       (4u << 3)
#define ADS1118_DR_250SPS       (5u << 3)
#define ADS1118_DR_475SPS       (6u << 3)
#define ADS1118_DR_860SPS       (7u << 3)

// TS 温度传感器模式,bit2
#define ADS1118_TS_ADC          (0u << 2)
#define ADS1118_TS_TEMP         (1u << 2)

// PULL-UP EN,bit1
#define ADS1118_PU_ENABLE       (1u << 1)

uint16_t ads1118_transfer_16(uint16_t tx);
float    ads1118_read_voltage(uint16_t mux, uint16_t pga, uint16_t dr);
float    ads1118_read_temperature(void);

#endif

底层传输我用简单GPIO模拟,循环16次逐位收发。注意采样边沿要和芯片匹配,ADS1118是在SCLK上升沿采样DOUT,所以主机置高SCLK后读取输入引脚。如果你用硬件SPI,配置成SPI Mode 1(CPOL=0, CPHA=1)或Mode 3(CPOL=1, CPHA=1)一般都能跑通,具体以你平台实测为准。

// ads1118.c
#include "ads1118.h"
#include "delay.h"
#include "gpio.h"   // 平台相关GPIO操作,自行适配

#define ADS1118_CS_LOW()   GPIO_CS_RESET()
#define ADS1118_CS_HIGH()  GPIO_CS_SET()
#define ADS1118_SCLK(x)    GPIO_SCLK_WRITE(x)
#define ADS1118_DIN(x)     GPIO_DIN_WRITE(x)
#define ADS1118_DOUT()     GPIO_DOUT_READ()

// 16位数据传输,发送tx同时接收rx
uint16_t ads1118_transfer_16(uint16_t tx)
{
    uint16_t rx = 0;
    int8_t i;

    ADS1118_CS_LOW();
    for (i = 15; i >= 0; i--) {
        ADS1118_DIN((tx >> i) & 0x01);
        ADS1118_SCLK(0);
        ADS1118_SCLK(1);                // 上升沿,主机采样DOUT
        rx = (rx << 1) | (ADS1118_DOUT() ? 1 : 0);
    }
    ADS1118_SCLK(1);
    ADS1118_CS_HIGH();

    return rx;
}

3.2 单次模式读取流程

单次模式是我最推荐的工作模式。虽然多了一个等待转换周期,但功耗低、逻辑清晰、时序好控制,多通道采集时不会串数据。读取一个通道电压的完整流程如下:

static float ads1118_lsb(uint16_t pga)
{
    switch (pga) {
        case ADS1118_PGA_6144: return 6.144f / 32768.0f;
        case ADS1118_PGA_4096: return 4.096f / 32768.0f;
        case ADS1118_PGA_2048: return 2.048f / 32768.0f;
        case ADS1118_PGA_1024: return 1.024f / 32768.0f;
        case ADS1118_PGA_512:  return 0.512f / 32768.0f;
        case ADS1118_PGA_256:  return 0.256f / 32768.0f;
        default:               return 4.096f / 32768.0f;
    }
}

static void ads1118_wait_conversion(uint16_t dr)
{
    // 按数据率计算等待时间,这里按128SPS配置示例
    // 更稳妥的方式是等待DOUT/DRDY引脚拉低
    delay_ms(10);   // 128SPS 对应约8ms,取10ms余量
}

float ads1118_read_voltage(uint16_t mux, uint16_t pga, uint16_t dr)
{
    uint16_t cfg = mux | pga | ADS1118_MODE_SINGLE | dr | ADS1118_TS_ADC;
    int16_t raw;

    // 第一帧:写入配置并置OS启动转换,返回值是旧数据,丢弃
    (void)ads1118_transfer_16(cfg | ADS1118_CFG_OS);

    // 等待转换完成
    ads1118_wait_conversion(dr);

    // 第二帧:读回本次转换结果,同时写入相同配置
    raw = (int16_t)ads1118_transfer_16(cfg | ADS1118_CFG_OS);

    return (float)raw * ads1118_lsb(pga);
}

第一帧会把配置写进寄存器并启动转换,第二帧顺带写入相同配置是为了保持寄存器状态不变。如果你在循环里连续调用这个函数,每一帧既是上一帧的延续,又是下一帧的开始,逻辑很顺畅。

3.3 连续模式读取流程

连续模式下不需要每次启动转换,芯片会按设定的数据率不停转换。你只要周期性地拉CS发帧,就能在DOUT上读到上一次的结果。这里有个容易踩的坑:连续模式下,配置字里不要置OS位。一旦置了OS,芯片可能从连续模式跳回单次行为,结果会乱。

float ads1118_read_voltage_continuous(uint16_t mux, uint16_t pga, uint16_t dr)
{
    uint16_t cfg = mux | pga | ADS1118_MODE_CONTINUE | dr | ADS1118_TS_ADC;
    static uint8_t first_read = 1;
    int16_t raw;

    if (first_read) {
        // 上电或切换通道后第一次读,配置写入并丢弃预热数据
        (void)ads1118_transfer_16(cfg);
        delay_ms(20);
        first_read = 0;
    }

    raw = (int16_t)ads1118_transfer_16(cfg);
    return (float)raw * ads1118_lsb(pga);
}

连续模式的优点是读取速度快、无需反复启动转换,适合需要连续监测的场景。但多通道扫描时你得自己管理通道切换和预热丢弃,稍麻烦一些。我的经验是:如果不是对采样连续性有硬性要求,单次模式更省心。

3.4 内置温度传感器读取

ADS1118内置温度传感器的读取方式和ADC通道不同,只需要把TS_MODE位置1。转换结果是14位补码数据,左对齐存在16位寄存器的高14位,实际使用时右移2位,再乘以0.03125°C/LSB。

float ads1118_read_temperature(void)
{
    uint16_t cfg = ADS1118_MUX_AIN0_AIN1 | ADS1118_PGA_4096 |
                   ADS1118_MODE_SINGLE | ADS1118_DR_128SPS |
                   ADS1118_TS_TEMP;
    int16_t raw;
    float temp;

    // 启动温度转换
    (void)ads1118_transfer_16(cfg | ADS1118_CFG_OS);
    delay_ms(10);

    // 读回温度结果
    raw = (int16_t)ads1118_transfer_16(cfg | ADS1118_CFG_OS);

    temp = (float)(raw >> 2) * 0.03125f;
    return temp;
}

这个温度传感器精度虽然比不上专业的数字温度芯片,但做热电偶冷端补偿足够。实际项目中,我通常开机先读一次温度作为冷端参考,然后周期性更新,这样热电偶测出来的温度就是冷端补偿后的真实温度,误差能控制在可接受范围。

4. SPI时序细节与几个实测过的坑

4.1 时钟极性和相位不能拍脑袋定

ADS1118的SPI时序要求SLCK空闲时高低电平、数据在哪个边沿采样,这些都是数据手册里明确画了的。我最初直接用主控默认的Mode 0,结果读出来的数据完全不对,后来仔细对时序才发现采样边沿反了。

ADS1118更适合用SPI Mode 1(CPOL=0, CPHA=1)或者Mode 3(CPOL=1, CPHA=1),核心是主机在SCLK上升沿采样DOUT数据,而从机在下降沿更新数据。用模拟SPI时,我上面的代码已经固定了“先拉低SCLK,再拉高,然后采样”的顺序,相当于Mode 1。用硬件SPI时,建议先在数据手册上核对采样边沿,再用逻辑分析仪实测波形,别光靠猜。

4.2 DOUT/DRDY复用引脚不能随便接

ADS1118的DOUT和DRDY共用同一个引脚,这一点和ADS1115有明显区别。很多参考电路里这个引脚直接接MCU的MISO,但如果你想用中断方式等待转换完成,得把这个引脚同时接一个GPIO输入,或者用带中断功能的MISO引脚。

我踩过的坑是这样的:单次模式里不查DRDY、只靠延时,本来没问题,但我把延时算得太死,数据率从128SPS切到860SPS时忘了改延时,程序偶尔会读到上一次转换的旧数据。后来改成等待DRDY引脚拉低再读,问题立刻消失。所以我的建议是:如果IO资源允许,DOUT/DRDY最好接一个可读取的GPIO,用“配置启动→等DRDY→读数据”这套流程,比死等延时可靠得多。

4.3 配置写进去但不生效的排查链路

如果你发现改了PGA或通道后读到的值完全没变化,先别怀疑芯片坏了,按下面顺序排查:

  1. 确认写入的配置字真的通过SPI发出去了。用逻辑分析仪抓DIN上的波形,逐位核对配置字。
  2. 确认CS时序完整。CS拉低期间SCLK必须完整跑16个边沿,提前拉高会导致配置字只写入一半,芯片直接忽略。
  3. 确认没有在连续模式下误置OS位。这个问题我在3.3里提过,一旦置位,芯片可能卡在单次逻辑里。
  4. 确认SPI时钟不超过4MHz。ADS1118的最高SCLK频率约4MHz,我试过用10MHz时钟跑,读出来的数据偶发错位,表现为个别bit随机跳变,很难定位。

其中第4个问题很隐蔽。你如果只是做简单验证,低速跑没问题,一旦系统时钟跑快,随机误码就开始冒出来。这也是为什么我建议SPI时钟降一档,3MHz以内用起来很稳定。

5. 从源代码到稳定采集:噪声、标定和PCB经验

5.1 不同量程下的有效分辨率实测

我特意做过一组测试:把ADS1118输入短接到GND,在不同PGA档位下连续读1000个点,统计码值波动范围。结果是PGA=±4.096V档位时波动大约±2~3个LSB,PGA=±0.256V档位时波动明显增大,实际有效分辨率会掉到14位附近。这说明高增益放大的同时,输入等效噪声和基准噪声都被一起放大了。

所以你在选择量程时,不是越小越好。如果信号是1V左右的直流电压,我建议选±2.048V或±4.096V档,留够余量的同时兼顾分辨率。只有像称重传感器这种毫伏级信号,才需要上高增益档,但这时就要做好软件滤波。

简单滤波我常用滑动平均和中值滤波结合:滑动平均对高斯噪声效果好,中值滤波能干掉偶尔出现的毛刺。比如128SPS采样率,取16点滑动平均,等效噪声能降一个量级左右,响应速度还在可接受范围。

5.2 内部基准、电源去耦和PCB布局

ADS1118内置基准,省掉了外部基准芯片,但内部基准不是完美的。温度变化会引起基准漂移,进而影响转换结果。如果你做的是跨季节、跨温区的户外采集设备,建议定期做一次标定,或者把设备温度一起记录,后续软件补偿。

供电上去耦电容要靠近电源引脚放,我用的是0.1µF陶瓷电容加4.7µF钽电容组合,分别放在芯片电源脚附近。DVDD和AVDD最好分别走线,别共用一个细走线。模拟输入走线越短越好,在输入端加一阶RC低通滤波(比如1kΩ加0.1µF),截至频率约1.6kHz,对高频干扰有明显抑制。

多说一句PCB布局:ADS1118是SSOP-10小封装,引脚间距小,手工焊接容易连锡。焊完后用万用表量一遍电源对地阻抗,确认没短路再上电。我第一次画板就吃过这个亏,一个连锡导致A0通道电压始终不对,查了半天才发现是两个相邻引脚焊在一起了。

5.3 量程切换后的标定流程

每次量程切换后,不能直接信任读出的电压,尤其是高增益档位。我常用的标定流程很简单:

  1. 输入短接到GND,读100个码值取平均,记录零点偏移。
  2. 输入一个已知精密电压(比如用参考电压源或高精度LDO输出),读100个码值取平均,计算实际增益。
  3. 把零点和增益写入Flash,每次上电后做一次两点校准。

实际操作中,ADS1118的零点偏移在单次模式下通常很小,但高增益档下不做零点修正,测量结果会有明显偏差。做了这个标定之后,我测得的最差精度基本都能落在0.1%以内,对于项目需求已经足够了。

我个人在实际项目里最深的体会是:ADS1118这颗芯片本身很皮实,绝大多数问题都出在时序和配置上。把寄存器位字段吃透,把SPI帧一图两用的机制理解透,再结合DRDY做同步,代码写起来就会非常稳。如果你正在调这颗芯片,建议先把单次模式跑通,再逐步加连续模式、温度传感器和量程切换,一步一步验证,别指望一把梭能调通。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

更多推荐