✔零知开源(零知IDE)是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台,在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录,让开发重心从 "配置环境" 转移到 "创意实现",极大降低了技术门槛。零知IDE编程软件,内置上千个覆盖多场景的示例代码,支持项目源码一键下载,项目文章在线浏览。零知开源(零知IDE)平台通过软硬件协同创新,让你的创意快速转化为实物,来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
目录
[1.1 硬件清单](#1.1 硬件清单)
[1.2 接线方案表](#1.2 接线方案表)
[1.3 接线示意图](#1.3 接线示意图)
[1.4 实物连接图](#1.4 实物连接图)
[3.1 初始化配置](#3.1 初始化配置)
[3.2 多模式数据采集](#3.2 多模式数据采集)
[3.3 Web数据接口](#3.3 Web数据接口)
[3.4 前端数据可视化](#3.4 前端数据可视化)
[4.1 操作流程](#4.1 操作流程)
[4.2 视频演示](#4.2 视频演示)
[5.1 寄存器配置](#5.1 寄存器配置)
[5.2 I2C 通信协议](#5.2 I2C 通信协议)
[Q2:Web 页面能打开,但数值不更新 / 波形无变化?](#Q2:Web 页面能打开,但数值不更新 / 波形无变化?)
[Q3: I2C地址冲突怎么办?](#Q3: I2C地址冲突怎么办?)
项目概述
本项目基于零知ESP32 -WROOM-32主控板,结合TI ADS1115 16位高精度ADC模数转换器,实现了一个功能完整的多通道数据采集与Web监控系统。系统不仅支持传统的4通道单端电压测量,更实现了A0-A1差分电压测量功能,并通过现代化的Web界面实时展示数据变化趋势
项目难点及解决方案
问题描述:在单次转换模式下,每次测量都需要重新配置MUX通道选择寄存器。频繁切换差分和单端模式可能导致数据读取错误
**解决方案:**采集流程中先读取差分数据、再读取单端数据,每次读取前库自动完成寄存器重新配置,避免手动操作寄存器的出错
一、系统接线部分
1.1 硬件清单
| 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 主控板 | 零知ESP32-WROOM-32 | 1 | 核心控制器 |
| ADC模块 | ADS1115 16位 | 1 | 高精度模数转换 |
| 杜邦线 | 公对公 | 若干 | 连接线 |
| 电位器 | 10kΩ | 2 | 模拟信号源 |
| 电源 | 5V/2A | 1 | 供电 |
1.2 接线方案表
根据代码设计,接线配置如下:
| ESP32引脚 | ADS1115引脚 | 连接说明 | 备注 |
|---|---|---|---|
| GPIO21 | SDA | I2C数据线 | 模块内置上拉电阻 |
| GPIO22 | SCL | I2C时钟线 | 模块内置上拉电阻 |
| 3.3V | VDD | 电源正极 | 电源5V供电,避免模块损坏 |
| GND | GND | 电源地 | 共地很重要 |
| 电位器1 | AIN0 | 通道0输入 | 差分正输入端 |
| 电位器2 | AIN1 | 通道1输入 | 差分负输入端 |
| GND | AIN2 | 通道2输入 | 单端测量 |
| - | AIN3 | 通道3输入 | 单端测量 |
注意:GAIN_ONE模式输入电压必须在±4.096V范围内
1.3 接线示意图

PS:旋转电位器和ADS1115 ADC模块都采用系统3.3V供电
1.4 实物连接图

二、安装与使用部分
2.1 开源平台-输入"ADS1115 多通道数据采集 "并搜索-代码下载自动打开

2.2 连接-验证-上传

2.3 调试-串口监视器

三、代码讲解部分
代码文件结构
主程序文件,实现 WiFi 连接、ADS1115 数据采集、WebServer 搭建;头文件,存储 Web 页面的 HTML/CSS/JavaScript 代码
3.1 初始化配置
cpp
// 增益设置: GAIN_ONE (+/- 4.096V)
// 1 bit = 0.125mV
const float multiplier = 0.000125;
void setup() {
Serial.begin(115200);
// 1. 初始化 ADS1115
if (!ads.begin()) {
Serial.println("无法初始化 ADS1115,请检查连线!");
while (1);
}
// 设置增益以匹配 3.3V 系统,同时保留一定的余量
ads.setGain(GAIN_ONE);
// 2. 连接 WiFi
WiFi.begin(ssid, password);
Serial.print("正在连接 WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi 已连接");
Serial.print("访问地址: http://");
Serial.println(WiFi.localIP());
// 3. 配置 Web 服务器路由
// 首页
server.on("/", HTTP_GET, []() {
server.send_P(200, "text/html", index_html);
});
// API 接口:返回 JSON 数据
server.on("/data", HTTP_GET, []() {
String json = "{";
json += "\"diff_raw\":" + String(raw_diff) + ",";
json += "\"diff_v\":" + String(volt_diff, 4) + ",";
json += "\"v0\":" + String(volt_a0, 4) + ",";
json += "\"v1\":" + String(volt_a1, 4) + ",";
json += "\"v2\":" + String(volt_a2, 4);
json += "}";
server.send(200, "application/json", json);
});
server.begin();
Serial.println("HTTP 服务器已启动");
}
GAIN_ONE增益倍数1,满量程±4.096V;multiplier每个LSB对应的电压值(0.125mV)
3.2 多模式数据采集
cpp
// --- 变量存储 ---
int16_t raw_diff; // 差分原始值
int16_t raw_a0, raw_a1, raw_a2; // 单端原始值
float volt_diff, volt_a0, volt_a1, volt_a2; // 转换后的电压值
void loop() {
server.handleClient();
// --- 关键步骤:切换模式读取数据 ---
// 注意:频繁切换配置寄存器会消耗一点时间,但为了同时显示单端和差分是必须的。
// 1. 读取 A0-A1 差分值 (Differential)
// 如果 A0 > A1,值为正;如果 A1 > A0,值为负
raw_diff = ads.readADC_Differential_0_1();
volt_diff = raw_diff * multiplier;
// 2. 读取单端值 (Single Ended) 用于参考
raw_a0 = ads.readADC_SingleEnded(0);
volt_a0 = raw_a0 * multiplier;
raw_a1 = ads.readADC_SingleEnded(1);
volt_a1 = raw_a1 * multiplier;
raw_a2 = ads.readADC_SingleEnded(2); // 备用通道
volt_a2 = raw_a2 * multiplier;
// 简单的串口调试 (可选)
// Serial.printf("Diff: %.4f V, A0: %.4f V, A1: %.4f V\n", volt_diff, volt_a0, volt_a1);
delay(5); // 短暂延时,避免 CPU 过载
}
测量AIN0和AIN1之间的电压差、结果可正可负,代表相对极性、差分模式能抑制共模噪声,提高测量精度
3.3 Web数据接口
cpp
// --- 全局对象 ---
WebServer server(80);
// 3. 配置 Web 服务器路由
// 首页
server.on("/", HTTP_GET, []() {
server.send_P(200, "text/html", index_html);
});
// API 接口:返回 JSON 数据
server.on("/data", HTTP_GET, []() {
String json = "{";
json += "\"diff_raw\":" + String(raw_diff) + ",";
json += "\"diff_v\":" + String(volt_diff, 4) + ",";
json += "\"v0\":" + String(volt_a0, 4) + ",";
json += "\"v1\":" + String(volt_a1, 4) + ",";
json += "\"v2\":" + String(volt_a2, 4);
json += "}";
server.send(200, "application/json", json);
});
server.begin();
使用JSON格式便于前端解析、保留4位小数确保精度同时提供原始值和计算值
3.4 前端数据可视化
cpp
<script>
// --- Chart.js 初始化配置 ---
const ctx = document.getElementById('voltageChart').getContext('2d');
// 创建渐变填充
let gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, 'rgba(102, 252, 241, 0.4)'); // 顶部颜色
gradient.addColorStop(1, 'rgba(102, 252, 241, 0)'); // 底部透明
const voltageChart = new Chart(ctx, {
type: 'line',
data: {
labels: [], // 时间轴标签
datasets: [{
label: 'Differential Voltage (A0 - A1)',
data: [],
borderColor: '#66fcf1',
backgroundColor: gradient,
borderWidth: 2,
pointRadius: 0, // 隐藏数据点,使线条更平滑
fill: true,
tension: 0.4 // 曲线平滑度
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { display: false }, // 隐藏X轴标签保持简洁
y: {
grid: { color: '#333' },
ticks: { color: '#888' },
suggestedMin: -4.0,
suggestedMax: 4.0
}
},
plugins: {
legend: { labels: { color: '#c5c6c7' } }
},
animation: false // 禁用动画以提高实时性能
}
});
// --- 数据获取与更新逻辑 ---
const maxDataPoints = 100; // 图表保留的数据点数量
function updateDashboard() {
fetch('/data')
.then(response => response.json())
.then(data => {
// 1. 更新卡片数值
document.getElementById('valDiff').innerText = data.diff_v.toFixed(4);
document.getElementById('val0').innerText = data.v0.toFixed(4);
document.getElementById('val1').innerText = data.v1.toFixed(4);
document.getElementById('val2').innerText = data.v2.toFixed(4);
// 2. 更新图表
// 获取当前时间戳作为简单的标签
const now = new Date().toLocaleTimeString();
// 添加新数据
voltageChart.data.labels.push(now);
voltageChart.data.datasets[0].data.push(data.diff_v);
// 如果数据点过多,移除最早的数据
if (voltageChart.data.labels.length > maxDataPoints) {
voltageChart.data.labels.shift();
voltageChart.data.datasets[0].data.shift();
}
voltageChart.update();
})
.catch(error => console.error('Error:', error));
}
// 设置刷新频率 (100ms = 10Hz刷新率)
setInterval(updateDashboard, 100);
</script>
系统流程图

差分测量模式深度解析
cpp
// 读取A0-A1差分原始值,底层自动配置寄存器
raw_diff = ads.readADC_Differential_0_1();
// 转换为实际电压(有符号,支持正负电位差)
volt_diff = raw_diff * multiplier;
ADS1115 的差分测量是将两个输入引脚(A0/A1)的电位差作为输入信号,而非相对于 GND 的单端信号,核心优势是抑制共模干扰
差分测量原理图
当 A0 电压 > A1 电压时,
raw_diff为正,volt_diff为正;当 A0 电压 < A1 电压时,raw_diff为负,volt_diff

差分量程与增益配置一致(GAIN_ONE 对应 ±4.096V),即 A0-A1 的电位差范围为 - 4.096V~+4.096V
四、项目结果演示
4.1 操作流程
按照接线方案表完成 ESP32 与 ADS1115 的连接,A0/A1 接电位器(模拟差分信号),A2 接备用传感器,修改主程序SSID和PASSWORD为实际 WiFi 路由信息
上电运行
给 ESP32 上电,打开串口监视器,设置波特率为 115200,等待 WiFi 连接成功,记录打印的 "访问地址,如http://192.168.x.x

数据查看
打开电脑 / 手机浏览器,输入上述访问地址,即可看到实时电压数值和差分电压波形
信号调试
调节电位器,观察 A0/A1 电压变化,以及差分电压的实时波形变化

4.2 视频演示
ESP32+ADS1115多通道采集:差分电压实时波形可视化
完整演示 ESP32 驱动 ADS1115 的多通道单端 / 差分采集流程,包含代码烧录、代码修改、WiFi 连接、Web 可视化全流程,直观展示差分电压随输入信号变化的实时波形,以及各通道电压的高精度显示效果
五、ADS1115技术讲解
ADS1115 具有 一个输入多路复用器 (MUX),可实现双路差分输入或 四路单端输入测量。兼容 I 2C 的 16 位低功耗精密模数转换器 (ADC)

Multiplexer 复用器

ADS1115包含两个差分输入,AIN0和AIN1可以与AIN3进行差分测量,多路复用器由配置寄存器中的位MUX [2:0]配置
5.1 寄存器配置
ADS1115 有 4 个寄存器,本项目核心用到配置寄存器(0x01) 和转换寄存器(0x00)
转换寄存器(0x00)

存储 ADC 转换后有符号的原始值,ESP32 读取该寄存器值后,乘以转换系数即可得到实际电压
配置寄存器(0x01)[15:0]

16位配置寄存器用于控制工作模式、输入选择、数据速率、满量程范围和比较器模式
①MUX位详解(输入选择):

本项目设置MUX[2:0]位为000,使用差分通道AIN0和AIN1
②PGA位详解(增益设置):

本项目设置PGA[2:0]位为001,采用GAIN_ONE增益
5.2 I2C 通信协议
① I2C地址选择
ADS1115有一个地址引脚 ADDR,用于配置器件的 I2C 地址。该引脚可连接至 GND、VDD、SDA 或 SCL,通过一个引脚即可选择四种不同的地址
② 向寄存器写入数据
要访问 ADS111x 中的特定寄存器,主机必须首先向地址指针寄存器中的寄存器地址指针位 P [1:0] 写入适当的值。
地址指针寄存器是在从机地址字节、低电平的读 / 写位以及从机成功应答之后直接写入,从机进行应答,主机发出停止条件或重复起始条件
③ 从寄存器读取数据
从 ADS111x 读取数据时,先前写入位 P [1:0] 的值决定了要读取的寄存器。要更改读取的寄存器,必须向 P [1:0] 写入新值

④ 数据格式
以二进制补码格式提供 16 位数据。正满量程(+FS)输入产生的输出代码为 7FFFh,负满量程(--FS)输入产生的输出代码为 8000h

对于超过满量程的信号,输出会钳位在这些代码上
电压计算原理
ADS1115的输出代码与输入电压的关系为:电压 = (ADC读数 × 满量程电压) / (2¹⁵ - 1)
在GAIN_ONE模式下:满量程电压 = 4.096V、分辨率 = 4.096V / 32767 ≈ 0.125mV
六、常见问题解答(FAQ)
Q1:测量负电压的原理?
*A: ADS1115本身不能直接测量负电压(相对GND)。但通过以下方法间接测量:*使用差分模式时V-接负电压,V+接GND、使用电平移位电路将负电压抬升到0-3.3V范围、使用双电源供电给ADS1115提供±2.5V电源
Q2:Web 页面能打开,但数值不更新 / 波形无变化?
*A:排查步骤:*检查 ESP32 是否仍连接 WiFi、检查浏览器控制台(F12)是否有报错、确认代码中setInterval(updateDashboard, 100)未被注释,刷新频率正常
Q3: I2C地址冲突怎么办?
A:ADS1115的I2C地址由ADDR引脚决定:默认ADDR接地0x48、ADDR接VDD地址为0x49、ADDR接SDA地址为0x4A、ADDR接SCL地址为0x4B。在代码Adafruit_ADS1115 ads(0x49); 中修改地址为0x49
项目资源整合:
ADS1115库文件:Adafruit_ADS1X15
ADS1115数据手册:ADS111x datasheet