简介:一套完整的芯片驱动工程,基于官方固件库,可直接在主流集成开发环境中打开使用。它所驱动的模拟数字转换器具有十六位分辨率、四个输入通道,内置可编程增益放大器,适用于工业测量与传感器信号采集场景,也适合医疗设备和科学仪器使用。压缩包内共有一百九十二个文件,大小约八点八五兆字节,包含芯片数据手册、驱动源码、工程配置文件以及编译过程中生成的中间文件,目录按内核、外设库、用户代码等模块划分清晰。目前已有五百六十一人学习下载。借助现成的引脚图表,硬件工程师可以快速确认接线方式;跟随示例代码,开发者可以掌握寄存器初始化、数据读取和错误处理的完整流程,大大减少驱动调试时间。
1. ADS112C04:I2C 接口的 16 位 ΔΣ ADC 到底解决什么问题
设想你手上有一颗 PT100、一只称重传感器和一个只剩 I2C 引脚的单片机。ADS112C04 就是为这种场景准备的:16 位分辨率、4 路模拟输入、内部可编程增益放大器、内部温度传感器,全部通过 I2C 访问。它与知名度更高的 ADS1120 属于同一系列,后者走 SPI,它把通信引脚压到 SDA/SCL 两根,代价是吞吐率更低,所以定位很明确:低速高精度采集。下面从引脚图开始,依次讲驱动层、读取程序和调试手段,覆盖寄存器配置、I2C 时序、数据换算和常见故障。适合正在画传感器板或把现有 SPI ADC 迁移到 I2C 的工程师。
2. ADS112C04 引脚图对照与最小硬件连接
2.1 引脚图上有五类信号,先按功能域分组
拿到 ADS112C04 的引脚图,不要先背引脚号,按功能域分组记忆会快很多。整个器件可以分成电源、模拟输入、参考电压、I2C 通信、地址/状态五组。按这个顺序去对照手册里的引脚图,基本不会漏接。
| 功能域 | 引脚/网络 | 接法说明 | 常见坑 |
|---|---|---|---|
| 模拟电源 | AVDD | 2.7V~5.5V,靠近引脚放 100nF | 与数字电源共用时噪声会被采集进去 |
| 数字电源 | DVDD | 与 AVDD 同电压 | DVDD 波动会导致 I2C 电平不稳 |
| 地 | AGND / DGND | 单点汇合,AGND 下方铺完整地 | DGND 电流大时参考电压会漂 |
| 模拟输入 | AIN0~AIN3 | 差分接传感器,未用输入接 AGND | 悬空输入会读成随机跳动 |
| 参考输入 | REFP0 / REFN0 | 外部低噪声基准;验证阶段可以改内部参考 | 直接用 AVDD 做参考会损失精度 |
| I2C | SDA / SCL | 必须开漏 + 外部上拉 | 推挽配置会让总线锁死 |
| 地址选择 | AD0 | 接 GND 或 DVDD 切换地址 | 同总线多个 ADC 会冲突 |
| 状态输出 | DRDY/ALERT | 可上拉后接 MCU 中断 | 不接就按最坏转换时间延时等待 |
注意:不同封装的丝印顺序有差异,画 PCB 时以数据手册里的封装图为准,不要直接套用网上引脚图上的编号。
2.2 SDA/SCL 开漏为什么需要外部上拉
ADS112C04 的 SDA/SCL 是开漏输出,这是 I2C 的标准做法。MCU 侧必须配置成开漏模式,并接上拉电阻到 DVDD。上拉电阻常见取值 2.2kΩ 到 4.7kΩ;如果总线上挂了多个从机,上拉电阻要相应减小,否则上升沿太慢,高速通信会出现随机错误。STM32 的引脚配置是这样的:
/* I2C1: PB6=SCL, PB7=SDA */ GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_I2C1_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; /* I2C 必须开漏 */ GPIO_InitStruct.Pull = GPIO_PULLUP; /* 内部上拉做兜底 */ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);关键在GPIO_MODE_AF_OD。如果误配成推挽输出,SDA/SCL 会在总线空闲时主动输出高电平,与开漏从机冲突,表现就是 I2C 总线卡死、SCL 波形异常。内部上拉只是兜底,不能替代外部电阻,因为 MCU 内部上拉一般在 40kΩ 左右,驱动快 I2C 总线时上升沿会明显变缓。
2.3 电源和参考电压是读数稳不稳的根源
AGND 和 DGND 通常建议单点连接,ADC 下方铺完整地平面。AVDD 和 DVDD 都要加 100nF 电容,位置尽量靠近电源引脚。如果传感器距离远,模拟输入端再加 RC 低通,时间常数按目标信号频率选,比如 PT100 的响应本身很慢,RC 取 1kΩ + 100nF 不会影响测量。
参考电压对读数影响最大。验证阶段最省事的方法是使用内部参考,不接 REFP0/REFN0;要追求精度时再接外部基准。第一次上电调程序时,变量越少越好,内部参考能排除一大类接线问题。
2.4 硬件先查这几个点再写程序
不要上电后直接调寄存器。先用示波器看 SCL 空闲电平是否为 DVDD,用万用表量 AVDD/DVDD 是否都在规格内,再确认 SDA/SCL 上确实有外部上拉电阻。这三个点出问题,后面程序无论怎么写都是白费。
3. ADS112C04 驱动的 I2C 命令层与四个配置寄存器
3.1 RREG/WREG 命令怎么发
ADS112C04 的寄存器操作通过 I2C 写命令字节实现。读一个寄存器时,单片机先写 RREG 命令和寄存器号,然后从设备返回该寄存器内容;写一个寄存器时,直接写 WREG 命令、寄存器号和要写入的值。下面这个封装是后面所有驱动的基础:
#define ADS112C04_I2C_ADDR 0x40 /* 7-bit 地址,AD0 接 GND */ #define CMD_RREG 0x20 /* 0b0010rrrr */ #define CMD_WREG 0x40 /* 0b0100rrrr */ #define CMD_START 0x08 /* 开始单次/连续转换 */ #define CMD_RESET 0x06 /* 软件复位 */ #define CMD_RDATA 0x10 /* 读取最新转换结果 */ static uint8_t ads112c04_i2c_addr = ADS112C04_I2C_ADDR << 1; /* HAL 需要 8-bit 地址 */ uint8_t ads112c04_write_reg(uint8_t reg, uint8_t val) { uint8_t buf[3]; buf[0] = CMD_WREG | (reg & 0x0F); /* 高 4 位是命令,低 4 位是寄存器地址 */ buf[1] = 0x00; /* 单寄存器写,寄存器数 N-1 = 0 */ buf[2] = val; return HAL_I2C_Master_Transmit(&hi2c1, ads112c04_i2c_addr, buf, 3, 100) == HAL_OK; } uint8_t ads112c04_read_reg(uint8_t reg) { uint8_t cmd[2] = { CMD_RREG | (reg & 0x0F), 0x00 }; uint8_t val; HAL_I2C_Master_Transmit(&hi2c1, ads112c04_i2c_addr, cmd, 2, 100); HAL_I2C_Master_Receive(&hi2c1, ads112c04_i2c_addr, &val, 1, 100); return val; }这里最容易被坑的是地址左移。STM32 HAL 的DevAddress参数要求 7 位地址左移 1 位,如果直接把 0x40 传进去,总线上实际发的是 8 位地址 0x81,芯片永远不会 ACK。寄存器地址只有 0 到 3,所以reg & 0x0F完全够用。
3.2 四个配置寄存器一张表说清楚
ADS112C04 的寄存器不多,但每个位域的含义都直接影响测量结果。上电后先按这张表逐项核对,可以少走很多弯路:
| 寄存器 | 地址 | 主要位域 | 作用 | 典型值 |
|---|---|---|---|---|
| CONFIG0 | 0x00 | MUX[1:0]、GAIN[2:0]、PGA_BYPASS、VREF_SEL[1:0] | 输入通道、增益、PGA、参考源 | 0x00 |
| CONFIG1 | 0x01 | MODE、DR[2:0]、CM | 转换模式、数据率、比较器 | 0x04 |
| CONFIG2 | 0x02 | TS[1:0]、VREF_INT、LOW_SIDE | 温度传感器开关、内部参考、低侧开关 | 0x10 |
| CONFIG3 | 0x03 | IDAC[2:0]、I1MUX[2:0]、I2MUX[2:0] | 恒流源激励电流和输出脚 | 0x00 |
典型值只代表一种常见配置,不是固定答案。比如把 CONFIG0 改成其他 MUX 值,通道就切到 AIN2/AIN3;把 CONFIG2 的温度传感器位打开,读取的数据就变成裸片温度。
3.3 初始化序列先复位再写寄存器
驱动初始化一定要先发软件复位,再逐个写寄存器。否则芯片可能停留在上次的未知配置里,读回来的寄存器值和你设的完全对不上。初始化函数如下:
uint8_t ads112c04_init(void) { uint8_t cmd = CMD_RESET; HAL_Delay(10); /* 复位后等待内部上电稳定 */ if (HAL_I2C_Master_Transmit(&hi2c1, ads112c04_i2c_addr, &cmd, 1, 100) != HAL_OK) return 1; /* AIN0-AIN1 差分输入,PGA=1,内部参考 */ ads112c04_write_reg(0x00, 0x00); /* 单次转换模式,数据率 20 SPS */ ads112c04_write_reg(0x01, 0x04); /* 关闭内部温度传感器,启用内部参考 */ ads112c04_write_reg(0x02, 0x10); /* 关闭 IDAC */ ads112c04_write_reg(0x03, 0x00); return 0; }每个参数都有讲究。CONFIG0 写成 0x00,表示差分输入 AIN0-AIN1、PGA=1、参考源由 VREF_SEL 决定;CONFIG1 的 0x04 把芯片置于单次转换模式,数据率约 20 SPS,这一个档位对 50Hz 工频有抑制作用;CONFIG2 的 0x10 打开内部 2.048V 参考,不依赖外部基准;CONFIG3 为 0x00 时 IDAC 关闭,避免恒流源电流流进信号源。
3.4 单次转换和连续转换的配置差异
单次转换的流程是:发 START 命令,等转换完成,读数据,芯片自动回到空闲模式。这个模式功耗低,多个传感器轮询时逻辑最可控。连续转换是把 CONFIG1 的模式位改成连续,发一次 START 后芯片自己转,之后每次 RDATA 都拿最新结果。高速采样时用连续模式,但要注意连续模式下中途读寄存器可能对转换有干扰,所以轮询多通道时我一般用单次转换。
4. ADS112C04 读取程序:原始码到物理量的换算与滤波
4.1 先跑通单次转换读取函数
一个能用的读取函数要处理三件事:发启动命令、等待转换完成、读回两个字节。代码可以这样写:
int16_t ads112c04_read_raw(void) { uint8_t cmd = CMD_START; uint8_t data[2] = {0}; HAL_I2C_Master_Transmit(&hi2c1, ads112c04_i2c_addr, &cmd, 1, 100); HAL_Delay(60); /* 20 SPS 对应 50ms,多留 10ms 余量 */ uint8_t rcmd = CMD_RDATA; HAL_I2C_Master_Transmit(&hi2c1, ads112c04_i2c_addr, &rcmd, 1, 100); HAL_I2C_Master_Receive(&hi2c1, ads112c04_i2c_addr, data, 2, 100); return (int16_t)((data[0] << 8) | data[1]); }等待时间由 CONFIG1 里的数据率决定。20 SPS 时一个周期是 50ms,留 60ms 余量;把数据率调到更高档,延时可以缩短:
| 目标数据率 | 最小等待时间 | 代码建议值 |
|---|---|---|
| 20 SPS | 50ms | 60ms |
| 90 SPS | 11ms | 15ms |
| 330 SPS | 3ms | 5ms |
| 1000 SPS | 1ms | 2ms |
如果板上拉了 DRDY/ALERT 中断脚,把 HAL_Delay 去掉,改成等待 GPIO 下降沿,时序更精确,也不会受延时余量影响。
4.2 双极性补码怎么转电压
ADS112C04 的输出是 16 位二进制补码,正满量程 0x7FFF,负满量程 0x8000。电压换算公式:
float ads112c04_raw_to_volt(int16_t raw, float vref, float gain) { return ((float)raw * vref) / (32768.0f * gain); }注意这里除的是 32768.0f,不是 65535。补码的正半轴是 32767 对应正满量程,负半轴到 -32768 对应负满量程。假如 vref=2.048V、gain=1,原始码 0x7FFF 约等于 2.048V,0x8000 约等于 -2.048V。PGA 增益在 CONFIG0 里设置,换算时要除一遍 gain,否则读出来的电压会整体偏大。
4.3 用比例法把 RTD 电阻算出来
测 PT100 时不要直接拿算出的电压除以激励电流,因为流过 Pt100 的电流和参考电压会同时漂。常见做法是让同一个 IDAC 同时流过 PT100 和精密基准电阻 RREF,用 ADC 分别读两个电阻上的电压比,这样参考电压和激励电流在比例项里同时约掉:
#define RREF 4300.0f /* 精密基准电阻,单位欧姆 */ float ads112c04_measure_rtd(void) { int16_t raw_rtd = ads112c04_read_raw_ch(0); /* RTD 通道 */ int16_t raw_ref = ads112c04_read_raw_ch(1); /* RREF 通道 */ float ratio = (float)raw_rtd / (float)raw_ref; return ratio * RREF; }raw_rtd / raw_ref是比例测量的核心。真实的 PT100 线性化还要查分度表或使用 Callendar-Van Dusen 方程,驱动层只负责提供电阻值,不要把温度公式写死在驱动里,否则换一种传感器就要改驱动。
4.4 数据率与滤波程序
数据率越高,噪声越大。20 SPS 适合工频环境;如果必须在 1000 SPS 下工作,程序里加一个移动平均:
#define FILTER_N 16 static int16_t history[FILTER_N]; static uint8_t idx; float ads112c04_read_filtered(void) { history[idx++] = ads112c04_read_raw(); if (idx >= FILTER_N) idx = 0; int32_t sum = 0; for (uint8_t i = 0; i < FILTER_N; i++) sum += history[i]; return (float)sum / FILTER_N; }移动平均的代价是延迟变大。16 点平均会把系统响应时间拉长 16 倍,如果场景是阀门缓变,可以接受;如果是快速突变监测,不如把数据率降下来,用硬件本身的过采样能力换稳定性。
5. 用这些手段验证 ADS112C04 驱动和硬件有没有接对
5.1 把内部温度传感器当“环回自检”
上电后先不接传感器,把 CONFIG2 的温度传感器位打开,读出来的电压和裸片温度强相关。如果读到的值稳定在 25°C 到 45°C 范围,说明 I2C 通路、寄存器写入、数据读取三段链路都正常:
float ads112c04_die_temp(void) { ads112c04_write_reg(0x02, 0xC0); /* 打开内部温度传感器 */ int16_t raw = ads112c04_read_raw(); float v = ads112c04_raw_to_volt(raw, 2.048f, 1.0f); return (v - 0.05f) / 0.003f; /* 线性近似,精度要求高请做标定 */ }这里的 0.05V 偏移和 0.003V/°C 只是通常的典型参数,不同批次会有偏差。实际操作时先记录一个温度点,用手摸芯片看读数是否跟着变,比直接搬手册公式可靠。
5.2 排查表
| 现象 | 优先检查顺序 |
|---|---|
| I2C 总线无 ACK | SDA/SCL 是否开漏、外部上拉有没有、7 位地址左移有没做、AD0 电平 |
| 读寄存器全 0xFF | DVDD/AVDD 电源是否正常、WREG 命令第二个字节是否写成 0 |
| 转换值一直 0x7FFF | 输入是否超过满量程、参考电压是否太低、PGA 是否过大 |
| 转换值跳动很大 | 模拟输入是否悬空、未用通道是否接 AGND、电源去耦电容位置 |
| 温度读数一动不动 | 寄存器写入可能没生效,直接读回 CONFIG2 确认 |
5.3 把 I2C 层抽出来做注入测试
驱动写多了会发现,排错最难的不是功能,而是区分寄存器到底写没写进去。我会在驱动代码里放一个开关宏,把寄存器读写接口单独抽象出来:
#ifdef ADS112C04_MOCK #define i2c_write(reg, val) mock_write(reg, val) #else #define i2c_write(reg, val) ads112c04_write_reg(reg, val) #endif这个开关在 PC 上配一套 mock_write,用固定的假寄存器表跑初始化代码,就能在没有硬件的情况下验证配置流程对不对。上板后万一还是不对,先用ads112c04_read_reg(0x02)把寄存器读回来和写入值对比;写入值和读回值不一致,问题大概率在 WREG 命令的第二个字节,而不是传感器。I2C 波形、寄存器值、内部温度三点都对上之后,再接外部传感器;接上后读数异常,范围就只剩下模拟前端。
本文还有配套的精品资源,点击获取