从硬件分压到软件查表:手把手教你为你的Arduino/STM32项目添加精准电量显示功能

📅 2026/7/5 2:25:24 👁️ 阅读次数 📝 编程学习
从硬件分压到软件查表:手把手教你为你的Arduino/STM32项目添加精准电量显示功能

从硬件分压到软件查表:手把手教你为Arduino/STM32项目添加精准电量显示

在创客项目中,电池供电设备的电量显示一直是个既基础又关键的痛点。想象你正在调试一台自制的便携气象站,突然断电导致数据丢失;或是遥控车在关键时刻电量耗尽——这些场景都凸显了实时电量监控的重要性。本文将用最接地气的方式,带你从硬件分压电路搭建到软件算法实现,构建一套适用于Arduino和STM32的精准电量显示系统。

1. 硬件设计:分压电路与ADC采集

1.1 分压电路设计原理

锂电池的电压范围(3.0V-4.2V)通常超过MCU的ADC输入限制(3.3V),因此分压电路必不可少。一个典型设计采用两个电阻串联:

电池正极 —— [R1] —— [R2] —— 地 | ADC输入

电阻选型计算公式

def calculate_voltage(R1, R2, Vbat): return Vbat * R2 / (R1 + R2)

常用电阻组合对比:

电池类型R1值R2值最大输出电压
3.7V锂电10kΩ20kΩ2.47V
12V铅酸30kΩ10kΩ3.0V

提示:选择电阻时需考虑功耗,过小的阻值会导致电池持续放电。建议总阻值在50kΩ以上。

1.2 ADC配置实战

以STM32CubeIDE为例,配置ADC的步骤如下:

  1. 在Pinout视图启用ADC通道
  2. 配置参数:
    • Resolution: 12位(4096级)
    • Scan Conversion Mode: Disabled
    • Continuous Conversion Mode: Enabled
  3. 生成代码后添加采集函数:
uint16_t read_ADC(ADC_HandleTypeDef* hadc) { HAL_ADC_Start(hadc); HAL_ADC_PollForConversion(hadc, 100); return HAL_ADC_GetValue(hadc); }

Arduino的模拟读取更简单:

int adcValue = analogRead(A0);

2. 电压-电量映射算法

2.1 查表法实现

锂电池放电曲线非线性,查表法比线性计算更准确。首先通过实验获取电压-电量对应关系:

电压(V)电量(%)
4.20100
3.9580
3.8050
3.6520
3.300

C语言实现示例:

const float voltage_table[] = {4.20, 3.95, 3.80, 3.65, 3.30}; const uint8_t percent_table[] = {100, 80, 50, 20, 0}; uint8_t get_battery_percent(float voltage) { for(int i=0; i<4; i++) { if(voltage >= voltage_table[i+1]) { float range = voltage_table[i] - voltage_table[i+1]; float ratio = (voltage - voltage_table[i+1]) / range; return percent_table[i+1] + (percent_table[i] - percent_table[i+1]) * ratio; } } return 0; }

2.2 软件滤波技巧

ADC读数易受干扰,采用移动平均滤波:

#define SAMPLE_SIZE 5 float filtered_voltage() { static float samples[SAMPLE_SIZE]; static int index = 0; samples[index] = analogRead(A0) * 3.3 / 1024.0 * 3; // 假设分压比1:2 index = (index + 1) % SAMPLE_SIZE; float sum = 0; for(int i=0; i<SAMPLE_SIZE; i++) { sum += samples[i]; } return sum / SAMPLE_SIZE; }

3. 完整系统集成

3.1 OLED电量显示实现

搭配0.96寸OLED,创建可视化电量指示:

#include <U8g2lib.h> U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0); void draw_battery(float percent) { u8g2.firstPage(); do { // 电池外框 u8g2.drawRFrame(10, 10, 30, 15, 2); u8g2.drawBox(40, 15, 3, 5); // 电量填充 int width = map(percent, 0, 100, 0, 26); u8g2.drawBox(12, 12, width, 11); // 百分比文本 char buf[10]; sprintf(buf, "%d%%", (int)percent); u8g2.drawStr(50, 20, buf); } while(u8g2.nextPage()); }

3.2 低电量预警系统

添加声音和LED预警:

#define BUZZER_PIN PA0 #define LED_PIN PA1 void check_battery(uint8_t percent) { if(percent < 10) { // 急促蜂鸣 for(int i=0; i<5; i++) { HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_PIN, GPIO_PIN_SET); HAL_Delay(100); HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_PIN, GPIO_PIN_RESET); HAL_Delay(100); } // LED闪烁 HAL_GPIO_TogglePin(LED_GPIO_Port, LED_PIN); } }

4. 进阶优化技巧

4.1 温度补偿

电池电压受温度影响,添加NTC温度传感器补偿:

float read_temperature() { int adc = analogRead(TEMP_PIN); float resistance = 10000.0 / (1023.0 / adc - 1); // 10K NTC float steinhart = log(resistance / 10000.0) / 3950.0 + 1.0 / (25.0 + 273.15); return (1.0 / steinhart) - 273.15; } float compensated_voltage(float raw_voltage, float temp) { if(temp < 0) return raw_voltage * 0.98; if(temp > 40) return raw_voltage * 1.03; return raw_voltage; }

4.2 电量预测算法

基于历史耗电率预测剩余使用时间:

typedef struct { float voltage_history[10]; uint32_t time_history[10]; int index; } BatteryMonitor; void update_history(BatteryMonitor* mon, float voltage) { mon->voltage_history[mon->index] = voltage; mon->time_history[mon->index] = HAL_GetTick(); mon->index = (mon->index + 1) % 10; } float predict_remaining(BatteryMonitor* mon) { float dv = mon->voltage_history[0] - mon->voltage_history[9]; uint32_t dt = (mon->time_history[9] - mon->time_history[0]) / 1000; float rate = dv / dt; // V/s float remaining_voltage = 3.0 - mon->voltage_history[9]; return remaining_voltage / fabs(rate); // 剩余秒数 }

在项目调试中发现,采用10次历史样本的移动窗口预测,在持续负载下误差可控制在15%以内。对于突发性负载变化,建议结合电流传感器进一步提升精度。