一、项目背景
在移动设备、无人机、可穿戴设备等领域,锂电池电压的实时监测是保障设备安全运行的核心需求。传统监测方案往往存在体积大、功耗高、显示单一等问题。本文基于Beetle RP2350开发板设计了一套嵌入式电压监测系统,通过高精度ADC采样、数据滤波算法和OLED动态显示,实现了电压曲线的实时绘制、三位小数精度显示及可视化电量提示,为便携设备开发提供了高集成度解决方案。
二、硬件平台
1. 核心优势
双核架构:Arm Cortex-M33与RISC-V双核设计,150MHz主频,满足高速数据处理需求。
超小体积:25×20.5mm硬币尺寸,适合空间敏感型应用。
低功耗设计:休眠电流仅25μA,搭配锂电池可长期工作。
丰富接口:支持11个GPIO、UART、I2C、SPI及模拟输入,扩展性强。
2. 关键功能引脚
ADC输入:GPIO27(A1)用于电压采样,12位分辨率(代码中通过超采样提升至等效14位)。
OLED驱动:GPIO4(SDA)、GPIO5(SCL)硬件I2C接口,支持128×64像素显示。
电池管理:集成充电电路与电压监测(BAT引脚),支持3.7V锂电池。
三、系统设计思路
1. 电压测量原理
分压电路:通过电阻R1=100kΩ(实测99.2kΩ)和R2=200kΩ(实测197.2kΩ)将锂电池电压(3.7~4.2V)分压至ADC量程(0~3.3V)。
计算公式:

其中校准系数(CAL_FACTOR)用于补偿硬件误差。
2. 数据处理优化
滑动窗口滤波:32次采样窗口均值滤波,有效抑制噪声。
超采样技术:单次测量8次ADC采样取平均,提升分辨率。
3. 显示界面设计
动态曲线:108像素宽度曲线区域,每秒5次刷新,展示电压波动趋势。
三位小数精度:左上角显示电压值(如3.745V),满足精密监测需求。
电池图标:右上角四格电量指示,对应4.0V/3.8V/3.6V/3.4V阈值。
左侧刻度:标注4.2V、4.0V、3.8V、3.6V关键电压值,便于快速比对。
本次设计同样支持上位机蓝牙通讯

四、代码解析(关键模块)
1. 滤波结构实现
struct {
float buffer[SAMPLE_WINDOW] = {0};
uint8_t index = 0;
float sum = 0;
} filter;
通过循环队列管理采样窗口,每次更新时移除最旧数据并加入新数据,计算均值时复杂度仅为O(1)。
2. 坐标映射算法
int mapVoltageToY(float voltage) {
const float minV = 3.4, maxV = 4.2;
voltage = constrain(voltage, minV, maxV);
return 20 + static_cast<int>((maxV - voltage)/(maxV - minV)*GRAPH_HEIGHT);
}
将电压值线性映射到OLED的Y轴坐标(20~60像素),确保曲线在指定区域绘制。
3. OLED绘制逻辑

void updateOLED(float voltage) {
// 绘制刻度、曲线、数值、电池图标
drawVoltageScale();
for (int x = 1; x < GRAPH_WIDTH; x++) {
u8g2.drawLine(x+GRAPH_X_OFFSET-1, prevY, x+GRAPH_X_OFFSET, currY);
}
u8g2.print(voltage, 3); // 三位小数显示
drawBatteryIndicator(voltage);
}
通过U8g2库的链式调用,优化绘制效率,避免屏幕闪烁。
五、实际应用场景
便携医疗设备:精确监测供电电压,保障设备稳定性。
智能穿戴设备:紧凑设计可直接集成至手表/手环,提升用户体验。
太阳能储能系统:动态跟踪电池充放电曲线,优化能量管理。
六、优化与扩展方向
低功耗优化:
利用RP2350休眠模式,按键唤醒采样,进一步降低功耗。
动态调整采样频率(如高负载时加速采样)。
功能扩展:
增加蓝牙/Wi-Fi模块,实现手机端远程监控。
安全增强:
设置电压阈值报警(蜂鸣器/LED闪烁)。
充放电循环计数,评估电池寿命。
七、结语
本项目充分挖掘了Beetle RP2350的高性能与小体积优势,结合滤波算法与可视化设计,打造了一套高精度、低成本的电压监测系统。代码已开源并适配Arduino生态,开发者可快速复现或二次开发。
附件
#include <Arduino.h>
#include <SoftwareSerial.h>
#include <U8g2lib.h>
// 硬件配置
const int ADC_PIN = 27;
const float R1 = 99200.0;
const float R2 = 197200.0;
const float VREF_ACTUAL = 3.312;
const int SAMPLE_WINDOW = 32;
const float CAL_FACTOR = 1.063;
// OLED显示参数
#define GRAPH_WIDTH 108 // 曲线实际宽度
#define GRAPH_X_OFFSET 30 // 左侧刻度区宽度
#define GRAPH_HEIGHT 40 // 曲线区域高度
float voltageHistory[GRAPH_WIDTH] = {0};
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(
U8G2_R0,
/* reset=*/ U8X8_PIN_NONE,
/* clock=*/ 5, // SCL-D5
/* data=*/ 4 // SDA-D4
);
SoftwareSerial hardwareSerial(9, 8); // RX-D9, TX-D8
// 滤波结构
struct {
float buffer[SAMPLE_WINDOW] = {0};
uint8_t index = 0;
float sum = 0;
} filter;
// 坐标映射(Y轴范围:20-60)
int mapVoltageToY(float voltage) {
const float minV = 3.4, maxV = 4.2; // 刻度范围3.4-4.2V
voltage = constrain(voltage, minV, maxV);
return 20 + static_cast<int>((maxV - voltage)/(maxV - minV)*GRAPH_HEIGHT);
}
// 绘制左侧电压刻度
void drawVoltageScale() {
u8g2.setFont(u8g2_font_5x7_tr);
// 绘制刻度线和标签
const float voltages[] = {4.2, 4.0, 3.8, 3.6};
for(int i=0; i<4; i++){
int y = mapVoltageToY(voltages[i]);
u8g2.drawLine(GRAPH_X_OFFSET-8, y, GRAPH_X_OFFSET-2, y); // 刻度线
u8g2.setCursor(0, y+3); // 文本位置微调
u8g2.print(voltages[i],1);
}
}
// 右上角电池图标(保持原始位置)
void drawBatteryIndicator(float voltage) {
int level = 0;
if (voltage >= 4.0) level = 4;
else if (voltage >= 3.8) level = 3;
else if (voltage >= 3.6) level = 2;
else if (voltage >= 3.4) level = 1;
u8g2.drawFrame(106, 2, 20, 12); // 电池外框
u8g2.drawBox(126, 5, 2, 6); // 正极触点
for (int i = 0; i < 4; i++) {
if (level > i) {
u8g2.drawBox(108 + i*4, 4, 3, 8);
}
}
}
float readPrecisionVoltage() {
filter.sum -= filter.buffer[filter.index];
uint32_t rawSum = 0;
for(int i=0; i<8; i++){
rawSum += analogRead(ADC_PIN);
delayMicroseconds(150);
}
float newSample = rawSum / 8.0;
filter.buffer[filter.index] = newSample;
filter.sum += newSample;
filter.index = (filter.index + 1) % SAMPLE_WINDOW;
float dividerVoltage = (filter.sum / SAMPLE_WINDOW * VREF_ACTUAL) / 4095.0;
return dividerVoltage * (R1 + R2) / R2 * CAL_FACTOR;
}
void updateOLED(float voltage) {
u8g2.clearBuffer();
// 绘制左侧电压刻度
drawVoltageScale();
// 绘制电压曲线
for (int x = 1; x < GRAPH_WIDTH; x++) {
int prevY = mapVoltageToY(voltageHistory[x-1]);
int currY = mapVoltageToY(voltageHistory[x]);
u8g2.drawLine(x+GRAPH_X_OFFSET-1, prevY, x+GRAPH_X_OFFSET, currY);
}
// 左上角电压显示(三位小数)
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.setCursor(0, 15);
u8g2.print(voltage, 3);
u8g2.print("V");
// 绘制电池图标
drawBatteryIndicator(voltage);
u8g2.sendBuffer();
}
void setup() {
Serial.begin(115200);
hardwareSerial.begin(115200);
analogReadResolution(12);
u8g2.begin();
u8g2.setContrast(150);
u8g2.clearDisplay();
u8g2.setFlipMode(0);
}
void loop() {
static uint32_t lastUpdate = 0;
if (millis() - lastUpdate >= 200) {
float voltage = readPrecisionVoltage();
// 更新历史数据
memmove(voltageHistory, voltageHistory + 1, sizeof(float)*(GRAPH_WIDTH-1));
voltageHistory[GRAPH_WIDTH-1] = voltage;
// 串口输出
char report[40];
snprintf(report, sizeof(report), "VOL:%.3f RAW:%.1f", voltage, filter.sum/SAMPLE_WINDOW);
Serial.println(report);
hardwareSerial.println(report);
updateOLED(voltage);
lastUpdate = millis();
}
}
评论