这是一个非常有趣的话题,因为它完美地展示了 C 语言在系统级编程和硬件交互方面的强大能力,在 C 语言层面,我们通常不直接通过引脚去读取电池的电压或电流,而是通过操作系统提供的接口来获取电池信息。

下面我将从不同平台(桌面、嵌入式)和底层原理两个方面来详细解释。
核心思想:通过操作系统 API 获取电池信息
在大多数现代操作系统(如 Windows, Linux, macOS)上,应用程序(包括用 C 语言编写的)不能直接访问硬件,操作系统提供了一个抽象层,应用程序通过调用特定的 API(应用程序编程接口)来请求信息,比如电池状态。
在 Windows 平台上使用 C 语言获取电池信息
在 Windows 上,最标准的方法是使用 GetSystemPowerStatus 函数,这个函数来自 kernel32.dll,它会填充一个 SYSTEM_POWER_STATUS 结构体,其中包含了详细的电池信息。
示例代码
这是一个完整的 C 语言示例,展示了如何获取并打印电池状态。

#include <windows.h>
#include <stdio.h>
int main() {
SYSTEM_POWER_STATUS sps;
// 获取系统电源状态
if (GetSystemPowerStatus(&sps)) {
printf("Battery Information:\n");
printf("--------------------\n");
// AC 电源状态
if (sps.ACLineStatus == 0) {
printf("AC Power: Offline\n");
} else if (sps.ACLineStatus == 1) {
printf("AC Power: Online\n");
} else {
printf("AC Power: Unknown\n");
}
// 电池充电状态
if (sps.BatteryFlag & 1) {
printf("Battery Status: High\n");
}
if (sps.BatteryFlag & 2) {
printf("Battery Status: Low\n");
}
if (sps.BatteryFlag & 4) {
printf("Battery Status: Critical\n");
}
if (sps.BatteryFlag & 8) {
printf("Battery Status: Charging\n");
}
if (sps.BatteryFlag & 128) {
printf("Battery Status: No system battery\n");
}
if (sps.BatteryFlag == 0) {
printf("Battery Status: Unknown status\n");
}
// 电池电量百分比 (0-100)
printf("Battery Life Percent: %d%%\n", sps.BatteryLifePercent);
// 电池剩余时间(秒)
// 注意:如果正在充电或无法计算,这个值可能为 -1
if (sps.BatteryLifeTime == -1) {
printf("Battery Life Time: Unknown\n");
} else {
int minutes = sps.BatteryLifeTime / 60;
int seconds = sps.BatteryLifeTime % 60;
printf("Battery Life Time: %d minutes, %d seconds\n", minutes, seconds);
}
// 电池全充时间(秒)
if (sps.BatteryFullLifeTime == -1) {
printf("Battery Full Life Time: Unknown\n");
} else {
int minutes = sps.BatteryFullLifeTime / 60;
int seconds = sps.BatteryFullLifeTime % 60;
printf("Battery Full Life Time: %d minutes, %d seconds\n", minutes, seconds);
}
} else {
// 获取失败
DWORD error = GetLastError();
printf("Failed to get system power status. Error code: %lu\n", error);
}
return 0;
}
如何编译和运行
-
将上述代码保存为
battery_info.c。 -
打开 Visual Studio 的开发者命令提示符(或使用 MinGW)。
-
使用以下命令编译:
# 使用 Visual C++ 编译器 (cl.exe) cl battery_info.c /link /subsystem:console # 或者使用 GCC (g.exe) g battery_info.c -o battery_info.exe
-
运行生成的
battery_info.exe文件。
(图片来源网络,侵删)
在 Linux 平台上使用 C 语言获取电池信息
在 Linux 上,电池信息通常存储在 /sys 文件系统下的特定目录中,笔记本电脑的电池信息通常在 /sys/class/power_supply/BAT0/ 目录下。
你可以使用 C 语言的文件 I/O 函数(如 fopen, fscanf, fclose)来读取这些文件。
示例代码
这个示例会读取 /sys/class/power_supply/BAT0/status 和 /sys/class/power_supply/BAT0/capacity 文件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义电池信息文件的路径
#define BATTERY_PATH "/sys/class/power_supply/BAT0"
#define STATUS_FILE BATTERY_PATH "/status"
#define CAPACITY_FILE BATTERY_PATH "/capacity"
int main() {
FILE *status_file, *capacity_file;
char status[12];
int capacity;
// 打开状态文件
status_file = fopen(STATUS_FILE, "r");
if (status_file == NULL) {
perror("Could not open battery status file");
return 1;
}
// 读取状态(如 "Discharging", "Charging")
if (fgets(status, sizeof(status), status_file) != NULL) {
// 去掉末尾的换行符
status[strcspn(status, "\n")] = 0;
}
fclose(status_file);
// 打开容量文件
capacity_file = fopen(CAPACITY_FILE, "r");
if (capacity_file == NULL) {
perror("Could not open battery capacity file");
return 1;
}
// 读取容量(0-100的整数)
if (fscanf(capacity_file, "%d", &capacity) != 1) {
perror("Could not read battery capacity");
fclose(capacity_file);
return 1;
}
fclose(capacity_file);
// 打印结果
printf("Battery Information:\n");
printf("--------------------\n");
printf("Status: %s\n", status);
printf("Capacity: %d%%\n", capacity);
return 0;
}
如何编译和运行
- 将代码保存为
battery_info_linux.c。 - 使用 GCC 编译:
gcc battery_info_linux.c -o battery_info_linux
- 运行(你可能需要
sudo权限来访问某些文件,但通常/sys下的文件是普通用户可读的):./battery_info_linux
注意:如果你的电池不是 BAT0,而是 BAT1 或其他名称,你需要修改代码中的 BATTERY_PATH 宏。
在嵌入式系统上直接操作硬件
在像 Arduino、ESP32 或 STM32 这样的微控制器上,情况就完全不同了,这些系统没有复杂的操作系统,你的 C 代码会直接运行在硬件上,直接与 GPIO(通用输入输出)引脚、ADC(模数转换器)和专门的电池管理芯片(如 BQ24610, MAX17055)通信。
基本原理
-
电压检测:
- 电池电压通常通过一个分压电阻网络降低到 MCU 的 ADC 可以安全测量的范围(3.3V 或 5V)。
- MCU 的 ADC 引脚读取这个分压后的电压值。
- 通过计算,将 ADC 的读数(一个数字值)转换回原始的电池电压。
-
电流检测:
- 使用一个采样电阻串联在电池和负载之间。
- 当电流流过采样电阻时,会产生一个微小的电压降(V = I * R)。
- 使用一个运算放大器放大这个微小的电压降。
- MCU 的 ADC 读取放大后的电压,然后根据公式计算出电流。
示例概念代码(基于 Arduino 风格)
这是一个概念性的代码,展示了如何读取一个通过分压电阻连接到电池的引脚。
// 假设我们正在为 Arduino 编程
// 定义 ADC 引脚和参考电压
#define BATTERY_PIN A0
#define REFERENCE_VOLTAGE 5.0 // Arduino 的参考电压通常是 5V
#define ADC_RESOLUTION 1024 // Arduino 的 ADC 是 10 位,所以有 1024 个级别 (2^10)
// 假设分压电阻比是 2:1 (R1 = R2)
// V_adc = V_battery * (R2 / (R1 + R2))
// R1 = R2, V_adc = V_battery / 2
#define VOLTAGE_DIVIDER_RATIO 2.0
void setup() {
// 初始化串口通信,用于打印结果
Serial.begin(9600);
// 设置 ADC 的参考电压(如果需要)
// analogReference(DEFAULT); // 默认是 5V
}
void loop() {
// 1. 从 ADC 读取原始值 (0-1023)
int adc_value = analogRead(BATTERY_PIN);
// 2. 将原始值转换为电压 (ADC引脚上的电压)
float voltage_at_pin = (adc_value * REFERENCE_VOLTAGE) / ADC_RESOLUTION;
// 3. 根据分压比计算电池的实际电压
float battery_voltage = voltage_at_pin * VOLTAGE_DIVIDER_RATIO;
// 4. 打印结果
Serial.print("Battery Voltage: ");
Serial.print(battery_voltage, 2); // 保留两位小数
Serial.println(" V");
delay(1000); // 每秒读取一次
}
| 平台 | 方法 | 核心技术 | 优点 | 缺点 |
|---|---|---|---|---|
| Windows | GetSystemPowerStatus API |
调用操作系统提供的库函数 | 简单、可靠、信息全面 | 仅限 Windows 平台 |
| Linux | 读取 /sys/class/power_supply/ 文件 |
标准 C 文件 I/O (fopen, fscanf) |
跨发行版(只要遵循标准)、轻量 | 需要知道具体的文件路径 |
| 嵌入式 | 直接操作 ADC 和 GPIO | 硬件寄存器操作、底层驱动开发 | 实时性高、可定制性强、成本最低 | 需要深厚的硬件知识和驱动开发能力,安全风险高 |
对于绝大多数应用程序开发者来说,使用操作系统的 API(Windows 或 Linux)是最佳选择,只有在进行嵌入式系统开发时,才需要深入到底层硬件层面去直接操作电池。
