
简介这是一套基于Arduino平台、驱动ESP32与ADS1256实现高精度模数转换的完整工程源码适合嵌入式开发者和电子制作爱好者参考。项目通过SPI接口连接ADS1256提供易用API读取电压数据并可在代码中灵活配置寄存器以调整采样速率与工作模式适用于传感器信号采集、仪器仪表等需要高精度模拟量数字化的场景。压缩包共11个文件以cpp和h源码为主同时包含readme说明文档、接线示意图及PlatformIO工程配置文件体积仅564KB结构简洁便于快速移植。目前已有83人学习下载。借助这份资料读者可获得完整可编译的工程代码、寄存器配置示例、硬件接线指导及API调用说明能明显降低ADS1256的入门门槛为自制数据采集系统提供直接参考。1. 为什么把ADS1256接到ESP32上做模数转换系统一片 ESP32 的板载 12 位 ADC 在很多场合够用但当你去测称重传感器的毫伏输出、工业变送器的采样电阻压降或者需要长期盯着一个缓慢漂移的电压时12 位量化噪声和非线性会让你不停怀疑接线。把 TI 的 24 位 Delta-Sigma ADC ADS1256 接到 ESP32 的 SPI 总线上是低成本仪表级采集最常见的一招。这个组合通过寄存器配置增益和数据速率能覆盖从 0.0625 SPS 到 30000 SPS 的采样范围配合 ESP32 的 Wi-Fi 和蓝牙正好把采集节点变成无线传感器网络。下面按接线、驱动、单通道读数和多通道扫描的顺序展开你不需要额外采购 Arduino 扩展板直接用杜邦线连接 ADS1256 模块和 ESP32 就能调通。2. ADS1256与ESP32的接线引脚分配和电源设计2.1 ADS1256关键引脚与ESP32 GPIO对照在 Arduino 框架下ESP32 默认把 VSPI 映射到固定的几个 GPIO。用下面这组引脚最省事后续代码也直接沿用。ADS1256 引脚ESP32 GPIO说明CSGPIO5SPI 片选低电平有效SCLKGPIO18SPI 时钟必须使用 SPI_MODE1DINGPIO23MOSIESP32 向 ADC 写入命令和寄存器DOUTGPIO19MISOADC 向 ESP32 返回数据DRDYGPIO17数据就绪下降沿表示本周期转换完成RESETGPIO16硬件复位低电平脉冲复位SYNC3.3V同步引脚接高电平保持正常运行这组引脚对应的是 ESP32 的 VSPI 默认映射。如果你板子上这些 GPIO 被占用也可以换到 HSPI 或任意引脚但要在代码里调用SPI.begin(sck, miso, mosi, ss)明确指定。注意 DRDY 不是普通输出必须配置成输入而且要就近加一个 10kΩ 左右的上拉电阻否则复位期间可能随机误触发。2.2 模拟电源与基准电压的讲究ADS1256 是 3.3V 器件AVDD和DVDD可以共用一个 3.3V 电源但不建议直接和舵机、继电器、电机驱动共用一个开关电源。模拟电源纹波会直接耦合到采样结果里你看到的数据可能就是带着 50Hz 工频毛刺的曲线。一般我会用一颗低压差 LDO 单独给模拟部分供电并从 AVDD 到 AGND 放 10µF 钽电容和 0.1µF 陶瓷电容并联去耦。很多现成模块已经把 2.5V 基准和晶振集成在板子上接上就能用。如果用的是裸芯片方案VREF 引脚必须外接精密 2.5V 基准源同时要按数据手册要求接正确频率的晶振。注意输入信号不能超过模拟电源范围按增益 1、参考电压 2.5V 算单端接法下满量程是 0 到 2.5V信号源超过这个范围就要用电阻分压或运放调理到合适区间。2.3 SPI模式1与信号完整性ADS1256 的 SPI 时序比较挑剔它要求CPOL0, CPHA1也就是 SCLK 空闲为低数据在第二个边沿采样。Arduino 的硬件 SPI 默认是模式 0直接用会读出一堆0xFF或者乱码。每次传输前都要用SPISettings指定模式 1而不是只设置一次。adcSPI.beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1));1920000是 1.92MHz 时钟频率对 ADS1256 来说不算高但在杜邦线连接时比 5MHz 稳定得多。MSBFIRST表示高位先出ADS1256 所有命令和寄存器数据都是从最高有效位开始传输。接线超过 10cm 时SCLK 边沿过冲会导致 MISO 采样错误建议把时钟降到 1MHz 或 512kHz 再试。3. 驱动实现底层SPI读写与寄存器配置3.1 引脚定义与SPI初始化在 Arduino IDE 里新建 ESP32 工程不需要安装额外 ADS1256 库。先定义引脚并初始化 VSPI#include SPI.h const uint8_t ADS1256_CS 5; const uint8_t ADS1256_DRDY 17; const uint8_t ADS1256_RESET 16; SPIClass adcSPI(VSPI); void adcInitSPI() { pinMode(ADS1256_CS, OUTPUT); digitalWrite(ADS1256_CS, HIGH); pinMode(ADS1256_RESET, OUTPUT); digitalWrite(ADS1256_RESET, HIGH); pinMode(ADS1256_DRDY, INPUT_PULLUP); adcSPI.begin(); }SPIClass adcSPI(VSPI)声明一个使用 VSPI 总线的 SPI 对象adcSPI.begin()直接套用默认引脚 18/19/23/5。DRDY 用INPUT_PULLUP是为了在 ADC 未就绪时不出现悬空电平实际使用时如果你已经接了一个上拉电阻这里也可以改成INPUT。3.2 寄存器读写与命令封装ADS1256 的寄存器地址从 0x00 到 0x07读写命令分别是0x10和0x50加上寄存器地址左移一位。下面把片选、命令发送和寄存器读写封装成独立函数。void adcSelect(bool isSelected) { digitalWrite(ADS1256_CS, isSelected ? LOW : HIGH); } void ads1256SendCommand(uint8_t cmd) { adcSPI.beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); adcSelect(true); adcSPI.transfer(cmd); adcSelect(false); adcSPI.endTransaction(); } uint8_t ads1256ReadRegister(uint8_t reg) { adcSPI.beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); adcSelect(true); adcSPI.transfer(0x10 | (reg 1)); // 读出命令 adcSPI.transfer(0x00); // 读 1 个寄存器 uint8_t val adcSPI.transfer(0x00); adcSelect(false); adcSPI.endTransaction(); return val; } void ads1256WriteRegister(uint8_t reg, uint8_t val) { adcSPI.beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); adcSelect(true); adcSPI.transfer(0x50 | (reg 1)); // 写命令 adcSPI.transfer(0x00); // 写 1 个寄存器 adcSPI.transfer(val); adcSelect(false); adcSPI.endTransaction(); }reg 1是因为地址占 4 位左移一位给命令标志位留出空间。每次片选拉低之后所有字节都必须在同一个 SPI 事务里完成所以beginTransaction和endTransaction要包裹完整操作。3.3 初始化复位、停止连续模式、写寄存器、自校准ADS1256 上电后默认进入连续读取模式在这种模式下直接写寄存器是不生效的。初始化第一件事就是发SDATAC命令停止连续模式然后配置 MUX、PGA 和速率。void ads1256Reset() { digitalWrite(ADS1256_RESET, LOW); delay(1); digitalWrite(ADS1256_RESET, HIGH); delay(10); } void ads1256Init() { adcInitSPI(); ads1256Reset(); ads1256SendCommand(0x0F); // SDATAC退出连续读取模式 ads1256WriteRegister(0x01, 0x08); // MUX: AIN0 正输入AINCOM 负输入 ads1256WriteRegister(0x02, 0x00); // ADCON: PGA 1 ads1256WriteRegister(0x03, 0x0D); // DRATE: 30 SPS ads1256SendCommand(0xFC); // SYNC delayMicroseconds(10); ads1256SendCommand(0x00); // WAKEUP ads1256SendCommand(0xF0); // SELFCAL 自校准 while (digitalRead(ADS1256_DRDY) HIGH) { // 等待校准完成DRDY 变为低电平 } }MUX寄存器的高 4 位选择正输入低 4 位选择负输入。0x08表示正输入是 AIN0负输入是 AINCOM这就是常用的单端测量接法。如果你想用差分输入比如测量 AIN0 和 AIN1 的差改成0x01即可。自校准SELFCAL会完成一次片内偏移和增益校准校准完成后 DRDY 会拉低所以初始化结束前要等这个信号。提示MUX 寄存器里0x08的低四位选的是 AINCOM而不是随便写进去的。差分接线时低四位换成要接负端的通道号。3.4 读取转换结果的时序读取单次转换结果的标准做法是等待 DRDY 下降沿发RDATA命令连续读 3 个字节。数据是 24 位二进制补码需要把最高位扩展到 32 位再转成有符号整数。bool ads1256WaitDRDY(uint32_t timeoutMs) { uint32_t start millis(); while (digitalRead(ADS1256_DRDY)) { if (millis() - start timeoutMs) return false; } return true; } bool ads1256ReadADCOnce(int32_t adcValue) { if (!ads1256WaitDRDY(100)) return false; adcSPI.beginTransaction(SPISettings(1920000, MSBFIRST, SPI_MODE1)); adcSelect(true); adcSPI.transfer(0x01); // RDATA uint32_t raw 0; for (uint8_t i 0; i 3; i) { raw (raw 8) | adcSPI.transfer(0x00); } adcSelect(false); adcSPI.endTransaction(); if (raw 0x800000UL) { raw | 0xFF000000UL; // 扩展符号位 } adcValue (int32_t)raw; return true; }RDATA命令在连续转换模式下可以直接用它会读出最近一次完成转换的数据不会中断转换过程。返回值adcValue的范围是 -8388608 到 8388607。超时返回false而不是一个假数据这样 loop 里可以区分这次读取是否有效。4. 单通道读取与电压换算把原始码值变成真实电压4.1 在loop里读取并打印初始化完成后每次读回 24 位原始码值再换算成实际电压。先跑通一个最简版本float pga 1.0f; void setup() { Serial.begin(115200); ads1256Init(); } void loop() { int32_t raw; if (ads1256ReadADCOnce(raw)) { float voltage (float)raw * 2.5f / (8388608.0f * pga); Serial.print(raw); Serial.println(raw); Serial.print(voltage); Serial.println(voltage, 6); } delay(200); }8388608就是 2 的 23 次方对应 24 位二进制补码的一半。当原始码值为 8388607 时输入差分电压约为正满量程VREF / PGA原始码值为 0 时输入差分电压为 0。如果你把pga改成 2、4、8满量程会相应缩小计算公式里的pga也要同步改。4.2 电压换算公式与满量程边界ADS1256 是双极性差分 ADC所谓“单端”实际上是把负输入接到 AINCOM 或者 AGND。假设 VREF 是 2.5VPGA 是 1正满量程对应 2.5V负满量程对应 -2.5V。换算公式可以整理成电压值 原始码值 × VREF ÷ (PGA × 8388608)这个公式里用的是差分电压。如果你把 AINCOM 接到 AGND那么读到的电压就是 AIN0 对 AGND 的电压。但要注意输入引脚绝对电压不能超过 AVDD如果信号源本身是 0 到 5V 的工业信号直接接入会拉坏 ADC前面必须加分压电阻或运放。4.3 数据速率配置与DRATE寄存器参考改动数据速率不能只写 DRATE 寄存器配置完还要发 SYNC 和 WAKEUP 让滤波器重新启动。下面的函数可以在运行中切换速率void ads1256SetDataRate(uint8_t drate) { ads1256SendCommand(0x0F); // SDATAC ads1256WriteRegister(0x03, drate); // 写新速率 ads1256SendCommand(0xFC); // SYNC delayMicroseconds(10); ads1256SendCommand(0x00); // WAKEUP ads1256WaitDRDY(100); // 等待新速率下第一次转换完成 }常用速率和寄存器值对照如下。速率越高噪声越大速率越低滤波越充分适合缓慢变化的传感器信号。DRATE 寄存器值输出速率 (SPS)适用场景0x0A100快速巡检噪声偏大0x0B60需要抑制 60Hz 干扰时0x0C50需要抑制 50Hz 干扰时0x0D30工频干扰环境和慢信号通用0x1010高分辨率慢速采集0x122.5极小带宽下的稳定读数如果你的现场有明显 50Hz 或 60Hz 工频干扰优先选对应整数的输出速率数字滤波器能把对应频率和整数倍频率压掉一部分。追求有效位数时选 10SPS 以下更划算代价是每次读取要等待更长时间。5. 多通道扫描与零点漂移的排查技巧5.1 切换通道时等两次DRDY多通道采集不是简单改 MUX 寄存器就能立即读到新通道。模拟输入开关切过去之后内部滤波器和调理电路需要几个数据周期稳定。常见做法是发 SYNC/WAKEUP然后等两个 DRDY 下降沿第一个下降沿代表新通道的第一笔不算数第二个才是稳定数据。void ads1256SetChannel(uint8_t ch) { uint8_t mux (ch 4) | 0x08; // AINn - AINCOM ads1256SendCommand(0x0F); // SDATAC ads1256WriteRegister(0x01, mux); ads1256SendCommand(0xFC); delayMicroseconds(10); ads1256SendCommand(0x00); ads1256WaitDRDY(100); ads1256WaitDRDY(100); // 多等一次确保滤波器完全稳定 }如果只改寄存器不重新同步第一次读到的新通道数据往往会和上一通道混叠。在低速率下这个稳定时间会更长所以不要每个循环都切通道最好一次扫描完所有通道后让 ADC 待在最后一个通道上做连续转换减少切换次数。5.2 浮空通道和地噪声的识别把某个输入通道悬空读出来的数据大概率不是 0而是一个几十到几百微伏的乱跳值。这是输入漏电流和环境下耦共同作用的结果不代表 ADC 坏了。排查硬件时把 AIN 和 AGND 短路观察原始码值波动如果波动在几个 LSB 以内说明 ADC 和电源部分正常如果波动达到几百甚至几千 LSB就要检查数字电源对模拟电源的干扰。另外Arduino IDE 的串口绘图仪会把字符串和数字混在一起产生视觉上的“毛刺”。调试时尽量只输出原始码值或纯数字比如Serial.println(raw);数据也会更平滑。如果你用 Wokwi 这类在线仿真平台可以验证 ESP32 的 SPI 初始化逻辑但它没有 ADS1256 的晶体管级模型模拟出来的 24 位精度不是真实的别拿仿真结果当作实际精度的依据。5.3 用量程校验判断增益和基准是否设置正确一个很实用的校验技巧是把输入接到一个稳定分压器分别量一次 0V 附近和接近满量程的电压记录原始码值。然后计算实测分辨率是否接近理论值。如果实际 LSB 步进和理论值差了一个固定比例优先怀疑基准电压不是 2.5V或者 PGA 寄存器写错。比如你写ADCON 0x01期望增益 2实际换算公式里 PGA 却用 1那读数就会翻倍。把ads1256ReadRegister(0x02)打出来看一次确认低四位是不是你想要的值。如果零输入时原始码值有固定偏移自校准命令SELFCAL已经处理过厂内偏移但还是压不掉热漂移。等系统通电十分钟后再执行一次自校准或者每次开机后延迟一段时间再校准能明显改善长期漂移。把这套检查流程固化到测试程序里比盯着串口猜问题快得多。本文还有配套的精品资源点击获取