BQ27427电量芯片完整驱动设计方案
1.2 关键寄存器地址(根据BQ27427手册)寄存器名称地址长度说明Control()0x002字节控制命令寄存器Temperature()0x022字节温度(0.1K单位)Voltage()0x042字节电压(mV)Flags()0x062字节状态标志NominalAvailableCapacity()0x082字节可用容量(mAh)FullAvailableCapacity()0x0A2字节
📋 一、BQ27427芯片分析
1.1 芯片基本信息
┌─────────────────────────────────────┐
│ BQ27427 电量计芯片特性 │
├─────────────────────────────────────┤
│ 接口: I2C │
│ 地址: 0x55 (7位地址) │
│ 速率: 支持100KHz/400KHz │
│ 功能: 库仑计、电压监测、温度监测 │
│ 精度: ±1% SOC │
└─────────────────────────────────────┘
1.2 关键寄存器地址(根据BQ27427手册)
| 寄存器名称 | 地址 | 长度 | 说明 |
|---|---|---|---|
| Control() | 0x00 | 2字节 | 控制命令寄存器 |
| Temperature() | 0x02 | 2字节 | 温度(0.1K单位) |
| Voltage() | 0x04 | 2字节 | 电压(mV) |
| Flags() | 0x06 | 2字节 | 状态标志 |
| NominalAvailableCapacity() | 0x08 | 2字节 | 可用容量(mAh) |
| FullAvailableCapacity() | 0x0A | 2字节 | 满电容量(mAh) |
| RemainingCapacity() | 0x0C | 2字节 | 剩余容量(mAh) |
| FullChargeCapacity() | 0x0E | 2字节 | 满充容量(mAh) |
| AverageCurrent() | 0x10 | 2字节 | 平均电流(mA) |
| StandbyCurrent() | 0x12 | 2字节 | 待机电流(mA) |
| MaxLoadCurrent() | 0x14 | 2字节 | 最大负载电流(mA) |
| AveragePower() | 0x18 | 2字节 | 平均功率(mW) |
| StateOfCharge() | 0x1C | 2字节 | 电量百分比(%) |
| InternalTemperature() | 0x1E | 2字节 | 内部温度(0.1K) |
| CycleCount() | 0x20 | 2字节 | 循环次数 |
| StateOfHealth() | 0x2E | 2字节 | 健康度(%) |
1.3 状态标志位定义(Flags寄存器)
/* Flags()寄存器位定义 */
#define BQ27427_FLAG_DSG (1 << 0) // 放电中
#define BQ27427_FLAG_SOCF (1 << 1) // SOC最终值
#define BQ27427_FLAG_SOC1 (1 << 2) // SOC阈值1
#define BQ27427_FLAG_BAT_DET (1 << 3) // 电池检测
#define BQ27427_FLAG_CHG (1 << 8) // 充电中
#define BQ27427_FLAG_FC (1 << 9) // 充满
#define BQ27427_FLAG_OTD (1 << 14) // 过温放电
#define BQ27427_FLAG_OTC (1 << 15) // 过温充电
💻 二、BQ27427完整驱动代码
/**
******************************************************************************
* @file drv_bq27427.h
* @brief BQ27427电量计芯片驱动头文件
* @author Your Name
* @date 2024-12-09
* @version 1.0
******************************************************************************
* @attention
* BQ27427是一款低功耗、高精度的电池电量计芯片
* - I2C地址: 0x55 (7位)
* - 支持电量、电压、电流、温度监测
* - 库仑计精度: ±1%
******************************************************************************
*/
#ifndef __DRV_BQ27427_H
#define __DRV_BQ27427_H
#include <stdint.h>
/*============================================================================*/
/* 配置宏定义 */
/*============================================================================*/
/* I2C设备地址 */
#define BQ27427_I2C_ADDR 0x55 // 7位地址
/* I2C超时时间 */
#define BQ27427_I2C_TIMEOUT_MS 100
/*============================================================================*/
/* 寄存器地址定义 */
/*============================================================================*/
/* 标准命令寄存器 */
#define BQ27427_REG_CONTROL 0x00 // 控制寄存器
#define BQ27427_REG_TEMPERATURE 0x02 // 温度 (0.1K)
#define BQ27427_REG_VOLTAGE 0x04 // 电压 (mV)
#define BQ27427_REG_FLAGS 0x06 // 状态标志
#define BQ27427_REG_NOM_CAP 0x08 // 标称可用容量 (mAh)
#define BQ27427_REG_FULL_CAP 0x0A // 满电容量 (mAh)
#define BQ27427_REG_REMAIN_CAP 0x0C // 剩余容量 (mAh)
#define BQ27427_REG_FULL_CHG_CAP 0x0E // 满充容量 (mAh)
#define BQ27427_REG_AVG_CURRENT 0x10 // 平均电流 (mA)
#define BQ27427_REG_STANDBY_CURRENT 0x12 // 待机电流 (mA)
#define BQ27427_REG_MAX_LOAD_CURRENT 0x14 // 最大负载电流 (mA)
#define BQ27427_REG_AVG_POWER 0x18 // 平均功率 (mW)
#define BQ27427_REG_SOC 0x1C // 电量百分比 (%)
#define BQ27427_REG_INT_TEMP 0x1E // 内部温度 (0.1K)
#define BQ27427_REG_CYCLE_COUNT 0x20 // 循环次数
#define BQ27427_REG_SOH 0x2E // 健康度 (%)
/* 控制命令 */
#define BQ27427_CTRL_STATUS 0x0000 // 获取状态
#define BQ27427_CTRL_DEVICE_TYPE 0x0001 // 获取设备类型
#define BQ27427_CTRL_FW_VERSION 0x0002 // 获取固件版本
#define BQ27427_CTRL_CHEM_ID 0x0008 // 化学ID
/* 状态标志位 */
#define BQ27427_FLAG_DSG (1 << 0) // 放电中
#define BQ27427_FLAG_SOCF (1 << 1) // SOC最终值
#define BQ27427_FLAG_SOC1 (1 << 2) // SOC阈值1
#define BQ27427_FLAG_BAT_DET (1 << 3) // 电池检测
#define BQ27427_FLAG_CHG (1 << 8) // 充电中
#define BQ27427_FLAG_FC (1 << 9) // 充满
#define BQ27427_FLAG_OTD (1 << 14) // 过温放电
#define BQ27427_FLAG_OTC (1 << 15) // 过温充电
/*============================================================================*/
/* 状态码定义 */
/*============================================================================*/
typedef enum {
BQ27427_OK = 0, // 成功
BQ27427_ERROR = -1, // 一般错误
BQ27427_ERROR_COMM = -2, // 通信错误
BQ27427_ERROR_TIMEOUT = -3, // 超时
BQ27427_ERROR_INVALID_PARAM = -4 // 无效参数
} BQ27427_Status_t;
/*============================================================================*/
/* 数据结构定义 */
/*============================================================================*/
/**
* @brief 电池完整数据结构
*/
typedef struct {
/* 基本参数 */
uint8_t soc; // 电量百分比 (0-100%)
uint16_t voltage; // 电压 (mV)
int16_t current; // 电流 (mA), 正=充电, 负=放电
int16_t temperature; // 温度 (0.1°C)
/* 容量信息 */
uint16_t remain_capacity; // 剩余容量 (mAh)
uint16_t full_capacity; // 满电容量 (mAh)
/* 功率信息 */
int16_t avg_power; // 平均功率 (mW)
/* 状态标志 */
uint8_t is_charging; // 1=充电中, 0=未充电
uint8_t is_full; // 1=已充满, 0=未充满
uint8_t bat_detected; // 1=检测到电池, 0=无电池
/* 健康信息 */
uint16_t cycle_count; // 循环次数
uint8_t soh; // 健康度 (0-100%)
/* 时间戳 */
uint32_t timestamp; // 读取时间戳 (ms)
} BQ27427_Data_t;
/**
* @brief 电池简化数据结构(用于快速读取)
*/
typedef struct {
uint8_t soc; // 电量百分比
uint16_t voltage; // 电压 (mV)
int16_t current; // 电流 (mA)
uint8_t is_charging; // 充电状态
} BQ27427_BasicData_t;
/*============================================================================*/
/* 对外API接口 */
/*============================================================================*/
/**
* @brief 初始化BQ27427
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR 失败
* @note 初始化I2C接口,检测芯片是否存在
*/
BQ27427_Status_t BQ27427_Init(void);
/**
* @brief 读取完整电池数据
* @param data 输出数据指针
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
* @note 读取所有可用数据,耗时约20ms
*/
BQ27427_Status_t BQ27427_ReadData(BQ27427_Data_t *data);
/**
* @brief 读取基本电池数据(快速)
* @param data 输出数据指针
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
* @note 仅读取SOC、电压、电流、充电状态,耗时约5ms
*/
BQ27427_Status_t BQ27427_ReadBasicData(BQ27427_BasicData_t *data);
/**
* @brief 读取电量百分比
* @param soc 输出电量百分比 (0-100)
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
BQ27427_Status_t BQ27427_ReadSOC(uint8_t *soc);
/**
* @brief 读取电压
* @param voltage 输出电压 (mV)
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
BQ27427_Status_t BQ27427_ReadVoltage(uint16_t *voltage);
/**
* @brief 读取电流
* @param current 输出电流 (mA), 正=充电, 负=放电
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
BQ27427_Status_t BQ27427_ReadCurrent(int16_t *current);
/**
* @brief 读取温度
* @param temperature 输出温度 (0.1°C)
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
BQ27427_Status_t BQ27427_ReadTemperature(int16_t *temperature);
/**
* @brief 读取状态标志
* @param flags 输出状态标志
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
BQ27427_Status_t BQ27427_ReadFlags(uint16_t *flags);
/**
* @brief 判断是否正在充电
* @retval 1 正在充电
* @retval 0 未充电
*/
uint8_t BQ27427_IsCharging(void);
/**
* @brief 判断是否充满
* @retval 1 已充满
* @retval 0 未充满
*/
uint8_t BQ27427_IsFullCharged(void);
/**
* @brief 判断是否检测到电池
* @retval 1 检测到电池
* @retval 0 无电池
*/
uint8_t BQ27427_IsBatteryDetected(void);
/**
* @brief 获取设备ID
* @param device_id 输出设备ID
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
BQ27427_Status_t BQ27427_GetDeviceID(uint16_t *device_id);
/**
* @brief 软件复位
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR 失败
*/
BQ27427_Status_t BQ27427_SoftReset(void);
#endif /* __DRV_BQ27427_H */
/**
******************************************************************************
* @file drv_bq27427.c
* @brief BQ27427电量计芯片驱动实现
* @author Your Name
* @date 2024-12-09
* @version 1.0
******************************************************************************
*/
#include "drv_bq27427.h"
#include "bsp_i2c.h" // I2C底层驱动
#include <string.h>
/*============================================================================*/
/* 私有变量 */
/*============================================================================*/
/* 初始化标志 */
static uint8_t s_initialized = 0;
/* 缓存的状态标志 */
static uint16_t s_cached_flags = 0;
/*============================================================================*/
/* 私有函数声明 */
/*============================================================================*/
static BQ27427_Status_t bq27427_read_register(uint8_t reg, uint16_t *value);
static BQ27427_Status_t bq27427_write_register(uint8_t reg, uint16_t value);
static BQ27427_Status_t bq27427_control_cmd(uint16_t cmd, uint16_t *response);
/*============================================================================*/
/* 对外API实现 */
/*============================================================================*/
/**
* @brief 初始化BQ27427
*/
BQ27427_Status_t BQ27427_Init(void)
{
if (s_initialized) {
return BQ27427_OK; // 已初始化
}
/* 检查I2C设备是否存在 */
if (BSP_I2C_IsDeviceReady(BQ27427_I2C_ADDR) != 0) {
return BQ27427_ERROR_COMM;
}
/* 读取设备ID验证 */
uint16_t device_id;
if (BQ27427_GetDeviceID(&device_id) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
/* BQ27427的设备ID应为0x0427 */
if ((device_id & 0xFF00) != 0x0400) {
return BQ27427_ERROR; // 设备ID不匹配
}
/* 初始化成功 */
s_initialized = 1;
return BQ27427_OK;
}
/**
* @brief 读取完整电池数据
*/
BQ27427_Status_t BQ27427_ReadData(BQ27427_Data_t *data)
{
if (!s_initialized || data == NULL) {
return BQ27427_ERROR_INVALID_PARAM;
}
uint16_t temp_u16;
int16_t temp_i16;
/* 清空数据结构 */
memset(data, 0, sizeof(BQ27427_Data_t));
/* 1. 读取电量百分比 */
if (bq27427_read_register(BQ27427_REG_SOC, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->soc = (uint8_t)temp_u16;
/* 2. 读取电压 */
if (bq27427_read_register(BQ27427_REG_VOLTAGE, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->voltage = temp_u16;
/* 3. 读取电流 */
if (bq27427_read_register(BQ27427_REG_AVG_CURRENT, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->current = (int16_t)temp_u16;
/* 4. 读取温度 */
if (bq27427_read_register(BQ27427_REG_TEMPERATURE, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
// 转换:0.1K -> 0.1°C (减去2731.5)
data->temperature = (int16_t)temp_u16 - 2731;
/* 5. 读取剩余容量 */
if (bq27427_read_register(BQ27427_REG_REMAIN_CAP, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->remain_capacity = temp_u16;
/* 6. 读取满电容量 */
if (bq27427_read_register(BQ27427_REG_FULL_CHG_CAP, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->full_capacity = temp_u16;
/* 7. 读取平均功率 */
if (bq27427_read_register(BQ27427_REG_AVG_POWER, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->avg_power = (int16_t)temp_u16;
/* 8. 读取循环次数 */
if (bq27427_read_register(BQ27427_REG_CYCLE_COUNT, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->cycle_count = temp_u16;
/* 9. 读取健康度 */
if (bq27427_read_register(BQ27427_REG_SOH, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->soh = (uint8_t)temp_u16;
/* 10. 读取状态标志 */
uint16_t flags;
if (bq27427_read_register(BQ27427_REG_FLAGS, &flags) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
s_cached_flags = flags;
/* 解析状态标志 */
data->is_charging = (flags & BQ27427_FLAG_CHG) ? 1 : 0;
data->is_full = (flags & BQ27427_FLAG_FC) ? 1 : 0;
data->bat_detected = (flags & BQ27427_FLAG_BAT_DET) ? 1 : 0;
/* 11. 记录时间戳 */
extern uint32_t Scheduler_GetTick(void); // 从调度器获取时间戳
data->timestamp = Scheduler_GetTick();
return BQ27427_OK;
}
/**
* @brief 读取基本电池数据(快速)
*/
BQ27427_Status_t BQ27427_ReadBasicData(BQ27427_BasicData_t *data)
{
if (!s_initialized || data == NULL) {
return BQ27427_ERROR_INVALID_PARAM;
}
uint16_t temp_u16;
/* 1. 读取电量百分比 */
if (bq27427_read_register(BQ27427_REG_SOC, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->soc = (uint8_t)temp_u16;
/* 2. 读取电压 */
if (bq27427_read_register(BQ27427_REG_VOLTAGE, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->voltage = temp_u16;
/* 3. 读取电流 */
if (bq27427_read_register(BQ27427_REG_AVG_CURRENT, &temp_u16) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->current = (int16_t)temp_u16;
/* 4. 读取状态标志(仅判断充电状态) */
uint16_t flags;
if (bq27427_read_register(BQ27427_REG_FLAGS, &flags) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
data->is_charging = (flags & BQ27427_FLAG_CHG) ? 1 : 0;
return BQ27427_OK;
}
/**
* @brief 读取电量百分比
*/
BQ27427_Status_t BQ27427_ReadSOC(uint8_t *soc)
{
if (!s_initialized || soc == NULL) {
return BQ27427_ERROR_INVALID_PARAM;
}
uint16_t value;
if (bq27427_read_register(BQ27427_REG_SOC, &value) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
*soc = (uint8_t)value;
return BQ27427_OK;
}
/**
* @brief 读取电压
*/
BQ27427_Status_t BQ27427_ReadVoltage(uint16_t *voltage)
{
if (!s_initialized || voltage == NULL) {
return BQ27427_ERROR_INVALID_PARAM;
}
return bq27427_read_register(BQ27427_REG_VOLTAGE, voltage);
}
/**
* @brief 读取电流
*/
BQ27427_Status_t BQ27427_ReadCurrent(int16_t *current)
{
if (!s_initialized || current == NULL) {
return BQ27427_ERROR_INVALID_PARAM;
}
uint16_t value;
if (bq27427_read_register(BQ27427_REG_AVG_CURRENT, &value) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
*current = (int16_t)value;
return BQ27427_OK;
}
/**
* @brief 读取温度
*/
BQ27427_Status_t BQ27427_ReadTemperature(int16_t *temperature)
{
if (!s_initialized || temperature == NULL) {
return BQ27427_ERROR_INVALID_PARAM;
}
uint16_t value;
if (bq27427_read_register(BQ27427_REG_TEMPERATURE, &value) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
/* 转换:0.1K -> 0.1°C */
*temperature = (int16_t)value - 2731;
return BQ27427_OK;
}
/**
* @brief 读取状态标志
*/
BQ27427_Status_t BQ27427_ReadFlags(uint16_t *flags)
{
if (!s_initialized || flags == NULL) {
return BQ27427_ERROR_INVALID_PARAM;
}
return bq27427_read_register(BQ27427_REG_FLAGS, flags);
}
/**
* @brief 判断是否正在充电
*/
uint8_t BQ27427_IsCharging(void)
{
return (s_cached_flags & BQ27427_FLAG_CHG) ? 1 : 0;
}
/**
* @brief 判断是否充满
*/
uint8_t BQ27427_IsFullCharged(void)
{
return (s_cached_flags & BQ27427_FLAG_FC) ? 1 : 0;
}
/**
* @brief 判断是否检测到电池
*/
uint8_t BQ27427_IsBatteryDetected(void)
{
return (s_cached_flags & BQ27427_FLAG_BAT_DET) ? 1 : 0;
}
/**
* @brief 获取设备ID
*/
BQ27427_Status_t BQ27427_GetDeviceID(uint16_t *device_id)
{
if (device_id == NULL) {
return BQ27427_ERROR_INVALID_PARAM;
}
return bq27427_control_cmd(BQ27427_CTRL_DEVICE_TYPE, device_id);
}
/**
* @brief 软件复位
*/
BQ27427_Status_t BQ27427_SoftReset(void)
{
/* BQ27427需要通过特殊命令序列复位,具体参考手册 */
/* 这里仅重置驱动状态 */
s_initialized = 0;
s_cached_flags = 0;
return BQ27427_OK;
}
/*============================================================================*/
/* 私有函数实现 */
/*============================================================================*/
/**
* @brief 读取寄存器(16位)
* @param reg 寄存器地址
* @param value 输出值
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
static BQ27427_Status_t bq27427_read_register(uint8_t reg, uint16_t *value)
{
uint8_t data[2];
/* 读取2字节(小端模式) */
if (BSP_I2C_ReadBytes(BQ27427_I2C_ADDR, reg, data, 2) != 0) {
return BQ27427_ERROR_COMM;
}
/* 组合为16位值(低字节在前) */
*value = ((uint16_t)data[1] << 8) | data[0];
return BQ27427_OK;
}
/**
* @brief 写入寄存器(16位)
* @param reg 寄存器地址
* @param value 写入值
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
static BQ27427_Status_t bq27427_write_register(uint8_t reg, uint16_t value)
{
uint8_t data[2];
/* 分解为字节(低字节在前) */
data[0] = (uint8_t)(value & 0xFF);
data[1] = (uint8_t)(value >> 8);
/* 写入2字节 */
if (BSP_I2C_WriteBytes(BQ27427_I2C_ADDR, reg, data, 2) != 0) {
return BQ27427_ERROR_COMM;
}
return BQ27427_OK;
}
/**
* @brief 发送控制命令
* @param cmd 命令码
* @param response 响应数据指针(可为NULL)
* @retval BQ27427_OK 成功
* @retval BQ27427_ERROR_COMM 通信错误
*/
static BQ27427_Status_t bq27427_control_cmd(uint16_t cmd, uint16_t *response)
{
/* 1. 写入控制命令到Control()寄存器 */
if (bq27427_write_register(BQ27427_REG_CONTROL, cmd) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
/* 2. 如果需要读取响应 */
if (response != NULL) {
/* 延时等待处理 */
extern void HAL_Delay(uint32_t ms);
HAL_Delay(10);
/* 读取Control()寄存器获取响应 */
if (bq27427_read_register(BQ27427_REG_CONTROL, response) != BQ27427_OK) {
return BQ27427_ERROR_COMM;
}
}
return BQ27427_OK;
}
📡 三、上位机通信协议设计
3.1 协议帧格式(扩展版)
/**
******************************************************************************
* @file protocol_extended.h
* @brief STM32与上位机通信协议(扩展版 - 支持电池+增益控制)
* @author Your Name
* @date 2024-12-09
* @version 2.0
******************************************************************************
*
* 协议帧格式:
* ┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
* │ 帧头 │ 命令码 │ 数据长度│ 数据 │ CRC16 │ 帧尾 │
* │ (2字节) │ (1字节) │ (1字节) │(N字节) │ (2字节) │ (2字节) │
* │ 0xAA55 │ CMD │ LEN │ DATA │ CRC │ 0x0D0A │
* └─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘
*
******************************************************************************
*/
#ifndef __PROTOCOL_EXTENDED_H
#define __PROTOCOL_EXTENDED_H
#include <stdint.h>
/*============================================================================*/
/* 协议配置 */
/*============================================================================*/
/* 帧标识 */
#define PROTOCOL_HEADER 0xAA55 // 帧头
#define PROTOCOL_TAIL 0x0D0A // 帧尾
/* 帧长度限制 */
#define PROTOCOL_MIN_FRAME_LEN 8 // 最小帧长
#define PROTOCOL_MAX_FRAME_LEN 136 // 最大帧长(8+128)
#define PROTOCOL_MAX_DATA_LEN 128 // 最大数据长度
/*============================================================================*/
/* 命令码定义 */
/*============================================================================*/
/**
* 命令码规则:
* - 0x00-0x7F: 上位机->下位机(请求)
* - 0x80-0xFF: 下位机->上位机(响应)
*
* 响应码 = 请求码 | 0x80
*/
/* 1. 电池相关命令 (0x01-0x0F) */
typedef enum {
/* 基础电量查询 */
CMD_READ_BATTERY_BASIC = 0x01, // 读取基础电量信息
CMD_BATTERY_BASIC_RESP = 0x81, // 基础电量响应
/* 完整电量数据查询 */
CMD_READ_BATTERY_FULL = 0x02, // 读取完整电量数据
CMD_BATTERY_FULL_RESP = 0x82, // 完整电量响应
/* 单项查询(可选) */
CMD_READ_SOC = 0x03, // 读取电量百分比
CMD_SOC_RESP = 0x83, // 电量百分比响应
CMD_READ_VOLTAGE = 0x04, // 读取电压
CMD_VOLTAGE_RESP = 0x84, // 电压响应
CMD_READ_CURRENT = 0x05, // 读取电流
CMD_CURRENT_RESP = 0x85, // 电流响应
CMD_READ_TEMPERATURE = 0x06, // 读取温度
CMD_TEMPERATURE_RESP = 0x86, // 温度响应
/* 电池健康信息 */
CMD_READ_BATTERY_HEALTH = 0x07, // 读取电池健康信息
CMD_BATTERY_HEALTH_RESP = 0x87, // 电池健康响应
} BatteryCmd_t;
/* 2. 增益控制命令 (0x10-0x1F) */
typedef enum {
CMD_SET_GAIN = 0x10, // 设置增益档位
CMD_GAIN_SET_RESP = 0x90, // 增益设置响应
CMD_READ_GAIN = 0x11, // 读取当前增益
CMD_GAIN_READ_RESP = 0x91, // 增益读取响应
CMD_GET_GAIN_LIST = 0x12, // 获取支持的增益列表
CMD_GAIN_LIST_RESP = 0x92, // 增益列表响应
} GainCmd_t;
/* 3. 参数管理命令 (0x20-0x2F) */
typedef enum {
CMD_READ_PARAM = 0x20, // 读取参数
CMD_PARAM_READ_RESP = 0xA0, // 参数读取响应
CMD_SAVE_PARAM = 0x21, // 保存参数
CMD_PARAM_SAVE_RESP = 0xA1, // 参数保存响应
CMD_RESTORE_DEFAULT = 0x22, // 恢复默认参数
CMD_RESTORE_RESP = 0xA2, // 恢复默认响应
} ParamCmd_t;
/* 4. 系统命令 (0xF0-0xFF) */
typedef enum {
CMD_HEARTBEAT = 0xF0, // 心跳包
CMD_HEARTBEAT_RESP = 0xF0, // 心跳响应(同码)
CMD_GET_DEVICE_INFO = 0xF1, // 获取设备信息
CMD_DEVICE_INFO_RESP = 0xF1, // 设备信息响应
CMD_SYSTEM_RESET = 0xF2, // 系统复位
CMD_RESET_RESP = 0xF2, // 复位响应
} SystemCmd_t;
/*============================================================================*/
/* 数据结构定义 */
/*============================================================================*/
/**
* @brief 协议状态码
*/
typedef enum {
PROTOCOL_OK = 0, // 成功
PROTOCOL_ERROR_LENGTH, // 长度错误
PROTOCOL_ERROR_HEADER, // 帧头错误
PROTOCOL_ERROR_TAIL, // 帧尾错误
PROTOCOL_ERROR_CRC, // CRC错误
PROTOCOL_ERROR_CMD, // 命令码错误
} ProtocolStatus_t;
/**
* @brief 协议帧结构
*/
typedef struct {
uint8_t cmd; // 命令码
uint8_t len; // 数据长度
uint8_t data[PROTOCOL_MAX_DATA_LEN]; // 数据缓冲区
uint16_t crc; // CRC校验值
} ProtocolFrame_t;
/**
* @brief 协议回调函数类型
*/
typedef void (*ProtocolCallback_t)(ProtocolFrame_t *frame);
/*============================================================================*/
/* 具体数据结构定义 */
/*============================================================================*/
/**
* @brief 基础电量数据包(CMD_READ_BATTERY_BASIC)
* 响应数据格式:
* Byte0: 电量百分比 (0-100)
* Byte1-2: 电压 (mV, 小端)
* Byte3-4: 电流 (mA, 小端, 有符号)
* Byte5: 充电状态 (0=未充电, 1=充电中)
*
* 总长度:6字节
*/
typedef struct __attribute__((packed)) {
uint8_t soc; // 电量百分比
uint16_t voltage; // 电压(mV)
int16_t current; // 电流(mA)
uint8_t is_charging; // 充电状态
} BatteryBasicData_t;
/**
* @brief 完整电量数据包(CMD_READ_BATTERY_FULL)
* 响应数据格式:
* Byte0: 电量百分比 (0-100)
* Byte1-2: 电压 (mV)
* Byte3-4: 电流 (mA, 有符号)
* Byte5-6: 温度 (0.1°C, 有符号)
* Byte7-8: 剩余容量 (mAh)
* Byte9-10: 满电容量 (mAh)
* Byte11-12: 平均功率 (mW, 有符号)
* Byte13-14: 循环次数
* Byte15: 健康度 (0-100%)
* Byte16: 充电状态
* Byte17: 充满标志
* Byte18: 电池检测标志
*
* 总长度:19字节
*/
typedef struct __attribute__((packed)) {
uint8_t soc; // 电量百分比
uint16_t voltage; // 电压(mV)
int16_t current; // 电流(mA)
int16_t temperature; // 温度(0.1°C)
uint16_t remain_capacity; // 剩余容量(mAh)
uint16_t full_capacity; // 满电容量(mAh)
int16_t avg_power; // 平均功率(mW)
uint16_t cycle_count; // 循环次数
uint8_t soh; // 健康度(%)
uint8_t is_charging; // 充电状态
uint8_t is_full; // 充满标志
uint8_t bat_detected; // 电池检测标志
} BatteryFullData_t;
/**
* @brief 增益设置数据包(CMD_SET_GAIN)
* 请求数据格式:
* Byte0: 增益档位 (0-7)
*
* 响应数据格式:
* Byte0: 设置结果 (0=成功, 1=失败, 2=无效档位)
* Byte1: 当前档位
* Byte2: 当前倍数
*
* 总长度:请求1字节,响应3字节
*/
typedef struct __attribute__((packed)) {
uint8_t result; // 设置结果
uint8_t current_level; // 当前档位
uint8_t current_value; // 当前倍数
} GainSetResp_t;
/**
* @brief 增益列表数据包(CMD_GET_GAIN_LIST)
* 响应数据格式:
* Byte0: 支持的档位数量(N)
* Byte1-N: 每个档位的倍数
*
* 例如:8个档位 -> [8, 1, 2, 5, 10, 20, 50, 100, 200]
*
* 总长度:可变(1+N)字节
*/
typedef struct __attribute__((packed)) {
uint8_t count; // 档位数量
uint8_t values[8]; // 倍数列表
} GainListResp_t;
/**
* @brief 设备信息数据包(CMD_GET_DEVICE_INFO)
* 响应数据格式:
* Byte0-15: 设备名称(字符串)
* Byte16-19: 硬件版本 (主.次.修订.保留)
* Byte20-23: 软件版本 (主.次.修订.保留)
* Byte24-27: 序列号
*
* 总长度:28字节
*/
typedef struct __attribute__((packed)) {
char device_name[16]; // 设备名称
uint8_t hw_version[4]; // 硬件版本
uint8_t sw_version[4]; // 软件版本
uint32_t serial_number; // 序列号
} DeviceInfo_t;
/*============================================================================*/
/* API接口 */
/*============================================================================*/
/**
* @brief 初始化协议模块
* @param callback 帧解析完成回调函数
* @retval 0=成功 -1=失败
*/
int Protocol_Init(ProtocolCallback_t callback);
/**
* @brief 接收并解析数据帧
* @param data 接收到的数据
* @param len 数据长度
* @retval 协议状态码
*/
ProtocolStatus_t Protocol_Receive(uint8_t *data, uint16_t len);
/**
* @brief 发送数据帧
* @param cmd 命令码
* @param data 数据指针
* @param len 数据长度
* @retval 0=成功 -1=失败
*/
int Protocol_Send(uint8_t cmd, uint8_t *data, uint8_t len);
/**
* @brief 发送基础电量数据
* @param data 电量数据结构指针
* @retval 0=成功 -1=失败
*/
int Protocol_SendBatteryBasic(BatteryBasicData_t *data);
/**
* @brief 发送完整电量数据
* @param data 电量数据结构指针
* @retval 0=成功 -1=失败
*/
int Protocol_SendBatteryFull(BatteryFullData_t *data);
/**
* @brief 发送增益设置响应
* @param resp 响应数据结构指针
* @retval 0=成功 -1=失败
*/
int Protocol_SendGainSetResp(GainSetResp_t *resp);
/**
* @brief 发送增益列表
* @param list 增益列表结构指针
* @retval 0=成功 -1=失败
*/
int Protocol_SendGainList(GainListResp_t *list);
/**
* @brief 发送设备信息
* @param info 设备信息结构指针
* @retval 0=成功 -1=失败
*/
int Protocol_SendDeviceInfo(DeviceInfo_t *info);
/**
* @brief 计算CRC16
* @param data 数据指针
* @param len 数据长度
* @retval CRC16值
*/
uint16_t Protocol_CalcCRC16(uint8_t *data, uint16_t len);
#endif /* __PROTOCOL_EXTENDED_H */
3.2 协议使用示例
通信协议使用示例
📡 1. 基础电量查询
上位机请求(读取基础电量)
发送: AA 55 01 00 [CRC16] 0D 0A
数据解析:
- 帧头:
AA 55 - 命令码:
0x01(CMD_READ_BATTERY_BASIC) - 数据长度:
0x00(无数据) - CRC16: 计算得出
- 帧尾:
0D 0A
STM32响应(返回基础电量数据)
响应: AA 55 81 06 4B 10 68 00 C8 01 [CRC16] 0D 0A
数据解析:
- 帧头:
AA 55 - 命令码:
0x81(CMD_BATTERY_BASIC_RESP) - 数据长度:
0x06(6字节) - 数据:
- Byte0:
0x4B= 75 (电量75%) - Byte1-2:
0x1068= 4200 (电压4200mV = 4.2V) - Byte3-4:
0x00C8= 200 (电流200mA,正值表示充电) - Byte5:
0x01= 1 (正在充电)
- Byte0:
- CRC16: 计算得出
- 帧尾:
0D 0A
实际含义:
- 当前电量:75%
- 电池电压:4.2V
- 充电电流:200mA
- 状态:正在充电
🔋 2. 完整电量查询
上位机请求
发送: AA 55 02 00 [CRC16] 0D 0A
STM32响应(完整数据)
响应: AA 55 82 13
4B // 电量75%
10 68 // 电压4200mV
00 C8 // 电流200mA
00 E6 // 温度23.0°C (230 * 0.1)
07 D0 // 剩余容量2000mAh
0B B8 // 满电容量3000mAh
03 20 // 平均功率800mW
00 0A // 循环10次
5A // 健康度90%
01 // 充电中
00 // 未充满
01 // 检测到电池
[CRC16] 0D 0A
数据解析(19字节):
| 字段 | 值 | 含义 |
|---|---|---|
| SOC | 0x4B = 75 | 电量75% |
| Voltage | 0x1068 = 4200 | 电压4.2V |
| Current | 0x00C8 = 200 | 充电电流200mA |
| Temperature | 0x00E6 = 230 | 温度23.0°C |
| Remain Capacity | 0x07D0 = 2000 | 剩余2000mAh |
| Full Capacity | 0x0BB8 = 3000 | 满电3000mAh |
| Avg Power | 0x0320 = 800 | 功率800mW |
| Cycle Count | 0x000A = 10 | 循环10次 |
| SOH | 0x5A = 90 | 健康度90% |
| Charging | 0x01 | 充电中 |
| Full | 0x00 | 未充满 |
| Detected | 0x01 | 检测到电池 |
🎚️ 3. 设置增益倍数
上位机请求(设置增益为10x)
发送: AA 55 10 01 03 [CRC16] 0D 0A
数据解析:
- 命令码:
0x10(CMD_SET_GAIN) - 数据长度:
0x01(1字节) - 数据:
0x03(档位3 = 10倍增益)
STM32响应
响应: AA 55 90 03 00 03 0A [CRC16] 0D 0A
数据解析:
- 命令码:
0x90(CMD_GAIN_SET_RESP) - 数据长度:
0x03(3字节) - 数据:
- Byte0:
0x00= 0 (设置成功) - Byte1:
0x03= 3 (当前档位3) - Byte2:
0x0A= 10 (当前倍数10x)
- Byte0:
设置失败示例:
响应: AA 55 90 03 02 03 0A [CRC16] 0D 0A
- Byte0:
0x02= 2 (错误码:无效档位)
📋 4. 查询增益列表
上位机请求
发送: AA 55 12 00 [CRC16] 0D 0A
STM32响应(返回所有支持的增益档位)
响应: AA 55 92 09 08 01 02 05 0A 14 32 64 C8 [CRC16] 0D 0A
数据解析:
- 命令码:
0x92(CMD_GAIN_LIST_RESP) - 数据长度:
0x09(9字节) - 数据:
- Byte0:
0x08= 8 (支持8个档位) - Byte1-8:
01 02 05 0A 14 32 64 C8- 档位0: 1x
- 档位1: 2x
- 档位2: 5x
- 档位3: 10x (0x0A)
- 档位4: 20x (0x14)
- 档位5: 50x (0x32)
- 档位6: 100x (0x64)
- 档位7: 200x (0xC8)
- Byte0:
❤️ 5. 心跳包
上位机请求
发送: AA 55 F0 00 [CRC16] 0D 0A
STM32响应
响应: AA 55 F0 00 [CRC16] 0D 0A
说明: 心跳包用于保持连接活跃,无数据内容
🖥️ 6. 获取设备信息
上位机请求
发送: AA 55 F1 00 [CRC16] 0D 0A
STM32响应
响应: AA 55 F1 1C
"STM32-BQ27427" (16字节,不足补0x00)
01 00 00 00 // 硬件版本 v1.0.0.0
01 02 03 00 // 软件版本 v1.2.3.0
12 34 56 78 // 序列号 0x12345678
[CRC16] 0D 0A
📊 7. 完整通信流程示例
场景:上位机实时监控电池并控制增益
时间轴:
T0: 上位机启动,发送心跳包
-> AA 55 F0 00 [CRC] 0D 0A
<- AA 55 F0 00 [CRC] 0D 0A (响应)
T1: 获取设备信息
-> AA 55 F1 00 [CRC] 0D 0A
<- AA 55 F1 1C [设备信息28字节] [CRC] 0D 0A
T2: 获取增益列表
-> AA 55 12 00 [CRC] 0D 0A
<- AA 55 92 09 [增益列表9字节] [CRC] 0D 0A
T3: 读取基础电量(每秒读取)
-> AA 55 01 00 [CRC] 0D 0A
<- AA 55 81 06 [电量6字节] [CRC] 0D 0A
T4: 用户点击界面设置增益为50x(档位5)
-> AA 55 10 01 05 [CRC] 0D 0A
<- AA 55 90 03 00 05 32 [CRC] 0D 0A
(成功设置为档位5, 50倍)
T5: 每10秒读取完整电量数据(用于详细显示)
-> AA 55 02 00 [CRC] 0D 0A
<- AA 55 82 13 [完整数据19字节] [CRC] 0D 0A
T6: 继续周期读取...
🔧 8. 错误处理示例
场景1:CRC校验失败
上位机发送: AA 55 01 00 [错误CRC] 0D 0A
STM32: 不响应(丢弃该帧)
上位机: 超时重发
场景2:无效增益档位
上位机请求: AA 55 10 01 FF [CRC] 0D 0A (档位255不存在)
STM32响应: AA 55 90 03 02 03 0A [CRC] 0D 0A
(错误码0x02=无效档位,当前档位3)
场景3:通信超时
上位机发送: AA 55 01 00 [CRC] 0D 0A
等待1000ms...
无响应 -> 重发(最多3次)
仍无响应 -> 提示用户"通信失败"
💡 9. 上位机界面设计建议
┌─────────────────────────────────────┐
│ STM32电池监控系统 │
├─────────────────────────────────────┤
│ 连接状态: ● 已连接 [断开] │
├─────────────────────────────────────┤
│ 【电池信息】 │
│ 电量: ██████████░░ 75% │
│ 电压: 4.20V │
│ 电流: 200mA ↑充电中 │
│ 温度: 23.0°C │
│ 容量: 2000 / 3000 mAh │
│ 循环: 10次 健康度: 90% │
├─────────────────────────────────────┤
│ 【增益控制】 │
│ 当前增益: 10x (档位3) │
│ [1x] [2x] [5x] [10x▼] [20x] ... │
├─────────────────────────────────────┤
│ 【日志】 │
│ 12:30:45 电量已读取 75% │
│ 12:30:40 增益设置为10x │
│ 12:30:30 设备已连接 │
└─────────────────────────────────────┘
📈 10. 性能指标
| 指标 | 数值 | 说明 |
|---|---|---|
| 基础电量查询 | ~20ms | 6字节数据 |
| 完整电量查询 | ~50ms | 19字节数据 |
| 增益设置 | ~10ms | 响应快速 |
| 心跳周期 | 建议5秒 | 保持连接 |
| 电量刷新率 | 建议1秒 | 实时监控 |
| 详细数据刷新 | 建议10秒 | 减少通信负担 |
🚀 11. 快速测试命令(串口助手)
HEX发送模式:
# 读取基础电量
AA 55 01 00 [CRC] 0D 0A
# 设置增益为10x(档位3)
AA 55 10 01 03 [CRC] 0D 0A
# 读取完整数据
AA 55 02 00 [CRC] 0D 0A
# 心跳包
AA 55 F0 00 [CRC] 0D 0A
注意: [CRC] 需要计算实际CRC16值
📱 四、上位机软件设计示例(Python)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
STM32电池监控与增益控制上位机软件
支持:
- 实时显示电池电量、电压、电流、温度
- 控制放大器增益倍数
- 自动刷新数据
- 日志记录
"""
import sys
import struct
import serial
import serial.tools.list_ports
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from datetime import datetime
# ============================================================================
# 协议定义
# ============================================================================
# 命令码
CMD_READ_BATTERY_BASIC = 0x01
CMD_BATTERY_BASIC_RESP = 0x81
CMD_READ_BATTERY_FULL = 0x02
CMD_BATTERY_FULL_RESP = 0x82
CMD_SET_GAIN = 0x10
CMD_GAIN_SET_RESP = 0x90
CMD_READ_GAIN = 0x11
CMD_GAIN_READ_RESP = 0x91
CMD_GET_GAIN_LIST = 0x12
CMD_GAIN_LIST_RESP = 0x92
CMD_HEARTBEAT = 0xF0
CMD_GET_DEVICE_INFO = 0xF1
CMD_DEVICE_INFO_RESP = 0xF1
# 帧标识
FRAME_HEADER = 0xAA55
FRAME_TAIL = 0x0D0A
# ============================================================================
# 协议处理类
# ============================================================================
class ProtocolHandler:
"""通信协议处理类"""
@staticmethod
def calc_crc16(data):
"""计算CRC16-MODBUS"""
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc
@staticmethod
def pack_frame(cmd, data=b''):
"""封装协议帧"""
# 帧头(2) + 命令码(1) + 长度(1) + 数据(N) + CRC(2) + 帧尾(2)
length = len(data)
frame = struct.pack('>H', FRAME_HEADER) # 帧头
frame += struct.pack('B', cmd) # 命令码
frame += struct.pack('B', length) # 数据长度
frame += data # 数据
# 计算CRC(命令码+长度+数据)
crc_data = frame[2:] # 跳过帧头
crc = ProtocolHandler.calc_crc16(crc_data)
frame += struct.pack('<H', crc) # CRC16(小端)
frame += struct.pack('>H', FRAME_TAIL) # 帧尾
return frame
@staticmethod
def parse_frame(data):
"""解析协议帧"""
if len(data) < 8: # 最小帧长
return None, "帧长度不足"
# 验证帧头
header = struct.unpack('>H', data[0:2])[0]
if header != FRAME_HEADER:
return None, "帧头错误"
# 验证帧尾
tail = struct.unpack('>H', data[-2:])[0]
if tail != FRAME_TAIL:
return None, "帧尾错误"
# 提取字段
cmd = data[2]
length = data[3]
payload = data[4:4+length]
crc_recv = struct.unpack('<H', data[4+length:4+length+2])[0]
# 验证CRC
crc_calc = ProtocolHandler.calc_crc16(data[2:4+length])
if crc_calc != crc_recv:
return None, f"CRC校验失败 (计算:{crc_calc:04X} 接收:{crc_recv:04X})"
return {'cmd': cmd, 'data': payload}, None
# ============================================================================
# 串口通信线程
# ============================================================================
class SerialThread(QThread):
"""串口接收线程"""
data_received = pyqtSignal(dict) # 接收到数据信号
error_occurred = pyqtSignal(str) # 错误信号
def __init__(self, port, baudrate=115200):
super().__init__()
self.port = port
self.baudrate = baudrate
self.serial = None
self.running = False
self.frame_buffer = bytearray()
def open(self):
"""打开串口"""
try:
self.serial = serial.Serial(
port=self.port,
baudrate=self.baudrate,
bytesize=8,
parity='N',
stopbits=1,
timeout=0.1
)
self.running = True
self.start()
return True
except Exception as e:
self.error_occurred.emit(f"串口打开失败: {e}")
return False
def close(self):
"""关闭串口"""
self.running = False
self.wait()
if self.serial and self.serial.is_open:
self.serial.close()
def send(self, data):
"""发送数据"""
if self.serial and self.serial.is_open:
self.serial.write(data)
def run(self):
"""接收线程主循环"""
while self.running:
try:
if self.serial.in_waiting > 0:
# 读取可用数据
data = self.serial.read(self.serial.in_waiting)
self.frame_buffer.extend(data)
# 查找完整帧
self.parse_buffer()
except Exception as e:
self.error_occurred.emit(f"接收错误: {e}")
self.msleep(10) # 10ms轮询
def parse_buffer(self):
"""从缓冲区解析帧"""
while len(self.frame_buffer) >= 8:
# 查找帧头
header_pos = -1
for i in range(len(self.frame_buffer) - 1):
if self.frame_buffer[i] == 0xAA and self.frame_buffer[i+1] == 0x55:
header_pos = i
break
if header_pos == -1:
# 没找到帧头,清空缓冲区
self.frame_buffer.clear()
return
if header_pos > 0:
# 丢弃帧头前的数据
self.frame_buffer = self.frame_buffer[header_pos:]
# 检查是否有完整帧
if len(self.frame_buffer) < 8:
return
length = self.frame_buffer[3]
frame_len = 8 + length # 帧头(2)+命令码(1)+长度(1)+数据(N)+CRC(2)+帧尾(2)
if len(self.frame_buffer) < frame_len:
# 帧不完整,继续接收
return
# 提取一帧
frame = bytes(self.frame_buffer[:frame_len])
self.frame_buffer = self.frame_buffer[frame_len:]
# 解析帧
result, error = ProtocolHandler.parse_frame(frame)
if result:
self.data_received.emit(result)
else:
self.error_occurred.emit(f"解析错误: {error}")
# ============================================================================
# 主窗口
# ============================================================================
class MainWindow(QMainWindow):
"""主窗口"""
def __init__(self):
super().__init__()
self.serial_thread = None
self.gain_levels = [] # 增益档位列表
self.auto_refresh = False
self.init_ui()
self.load_ports()
def init_ui(self):
"""初始化界面"""
self.setWindowTitle("STM32电池监控与增益控制系统")
self.setGeometry(100, 100, 800, 600)
# 中心部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# ========== 串口设置区 ==========
port_group = QGroupBox("串口设置")
port_layout = QHBoxLayout()
self.port_combo = QComboBox()
self.port_combo.setMinimumWidth(150)
port_layout.addWidget(QLabel("端口:"))
port_layout.addWidget(self.port_combo)
self.baud_combo = QComboBox()
self.baud_combo.addItems(['9600', '115200', '256000'])
self.baud_combo.setCurrentText('115200')
port_layout.addWidget(QLabel("波特率:"))
port_layout.addWidget(self.baud_combo)
self.refresh_btn = QPushButton("刷新端口")
self.refresh_btn.clicked.connect(self.load_ports)
port_layout.addWidget(self.refresh_btn)
self.connect_btn = QPushButton("连接")
self.connect_btn.clicked.connect(self.toggle_connection)
port_layout.addWidget(self.connect_btn)
port_layout.addStretch()
port_group.setLayout(port_layout)
layout.addWidget(port_group)
# ========== 电池信息区 ==========
battery_group = QGroupBox("电池信息")
battery_layout = QGridLayout()
# 电量百分比
self.soc_label = QLabel("电量: --")
self.soc_label.setStyleSheet("font-size: 18px; font-weight: bold;")
battery_layout.addWidget(self.soc_label, 0, 0, 1, 2)
self.soc_bar = QProgressBar()
self.soc_bar.setRange(0, 100)
self.soc_bar.setValue(0)
self.soc_bar.setTextVisible(True)
battery_layout.addWidget(self.soc_bar, 1, 0, 1, 2)
# 电压
battery_layout.addWidget(QLabel("电压:"), 2, 0)
self.voltage_label = QLabel("--")
battery_layout.addWidget(self.voltage_label, 2, 1)
# 电流
battery_layout.addWidget(QLabel("电流:"), 3, 0)
self.current_label = QLabel("--")
battery_layout.addWidget(self.current_label, 3, 1)
# 温度
battery_layout.addWidget(QLabel("温度:"), 4, 0)
self.temp_label = QLabel("--")
battery_layout.addWidget(self.temp_label, 4, 1)
# 充电状态
battery_layout.addWidget(QLabel("状态:"), 5, 0)
self.status_label = QLabel("--")
battery_layout.addWidget(self.status_label, 5, 1)
battery_group.setLayout(battery_layout)
layout.addWidget(battery_group)
# ========== 增益控制区 ==========
gain_group = QGroupBox("增益控制")
gain_layout = QVBoxLayout()
current_layout = QHBoxLayout()
current_layout.addWidget(QLabel("当前增益:"))
self.current_gain_label = QLabel("--")
self.current_gain_label.setStyleSheet("font-size: 16px; font-weight: bold; color: blue;")
current_layout.addWidget(self.current_gain_label)
current_layout.addStretch()
gain_layout.addLayout(current_layout)
# 增益按钮组
self.gain_button_layout = QHBoxLayout()
gain_layout.addLayout(self.gain_button_layout)
gain_group.setLayout(gain_layout)
layout.addWidget(gain_group)
# ========== 控制按钮区 ==========
control_layout = QHBoxLayout()
self.read_basic_btn = QPushButton("读取基础数据")
self.read_basic_btn.clicked.connect(self.read_battery_basic)
self.read_basic_btn.setEnabled(False)
control_layout.addWidget(self.read_basic_btn)
self.read_full_btn = QPushButton("读取完整数据")
self.read_full_btn.clicked.connect(self.read_battery_full)
self.read_full_btn.setEnabled(False)
control_layout.addWidget(self.read_full_btn)
self.auto_refresh_check = QCheckBox("自动刷新(1秒)")
self.auto_refresh_check.stateChanged.connect(self.toggle_auto_refresh)
control_layout.addWidget(self.auto_refresh_check)
control_layout.addStretch()
layout.addLayout(control_layout)
# ========== 日志区 ==========
log_group = QGroupBox("日志")
log_layout = QVBoxLayout()
self.log_text = QTextEdit()
self.log_text.setReadOnly(True)
self.log_text.setMaximumHeight(150)
log_layout.addWidget(self.log_text)
log_group.setLayout(log_layout)
layout.addWidget(log_group)
# 自动刷新定时器
self.refresh_timer = QTimer()
self.refresh_timer.timeout.connect(self.read_battery_basic)
def load_ports(self):
"""加载可用串口"""
self.port_combo.clear()
ports = serial.tools.list_ports.comports()
for port in ports:
self.port_combo.addItem(f"{port.device} - {port.description}")
def toggle_connection(self):
"""切换连接状态"""
if self.serial_thread is None or not self.serial_thread.running:
# 连接
port = self.port_combo.currentText().split(' ')[0]
baudrate = int(self.baud_combo.currentText())
self.serial_thread = SerialThread(port, baudrate)
self.serial_thread.data_received.connect(self.on_data_received)
self.serial_thread.error_occurred.connect(self.on_error)
if self.serial_thread.open():
self.connect_btn.setText("断开")
self.read_basic_btn.setEnabled(True)
self.read_full_btn.setEnabled(True)
self.log("串口已连接")
# 连接后自动获取增益列表
self.get_gain_list()
else:
self.serial_thread = None
else:
# 断开
self.auto_refresh_check.setChecked(False)
self.serial_thread.close()
self.serial_thread = None
self.connect_btn.setText("连接")
self.read_basic_btn.setEnabled(False)
self.read_full_btn.setEnabled(False)
self.log("串口已断开")
def toggle_auto_refresh(self, state):
"""切换自动刷新"""
if state == Qt.Checked:
self.auto_refresh = True
self.refresh_timer.start(1000) # 1秒刷新
self.log("开始自动刷新")
else:
self.auto_refresh = False
self.refresh_timer.stop()
self.log("停止自动刷新")
def send_command(self, cmd, data=b''):
"""发送命令"""
if self.serial_thread and self.serial_thread.running:
frame = ProtocolHandler.pack_frame(cmd, data)
self.serial_thread.send(frame)
def read_battery_basic(self):
"""读取基础电量数据"""
self.send_command(CMD_READ_BATTERY_BASIC)
def read_battery_full(self):
"""读取完整电量数据"""
self.send_command(CMD_READ_BATTERY_FULL)
def get_gain_list(self):
"""获取增益列表"""
self.send_command(CMD_GET_GAIN_LIST)
def set_gain(self, level):
"""设置增益档位"""
data = struct.pack('B', level)
self.send_command(CMD_SET_GAIN, data)
self.log(f"设置增益档位: {level}")
def on_data_received(self, result):
"""处理接收到的数据"""
cmd = result['cmd']
data = result['data']
if cmd == CMD_BATTERY_BASIC_RESP:
self.parse_battery_basic(data)
elif cmd == CMD_BATTERY_FULL_RESP:
self.parse_battery_full(data)
elif cmd == CMD_GAIN_SET_RESP:
self.parse_gain_set_resp(data)
elif cmd == CMD_GAIN_LIST_RESP:
self.parse_gain_list(data)
elif cmd == CMD_HEARTBEAT:
self.log("心跳响应")
def parse_battery_basic(self, data):
"""解析基础电量数据"""
if len(data) < 6:
return
soc, voltage, current, is_charging = struct.unpack('<BHhB', data)
self.soc_label.setText(f"电量: {soc}%")
self.soc_bar.setValue(soc)
self.voltage_label.setText(f"{voltage}mV ({voltage/1000:.2f}V)")
self.current_label.setText(f"{current}mA")
status = "充电中 ↑" if is_charging else "放电中 ↓"
self.status_label.setText(status)
# 根据电量设置进度条颜色
if soc < 20:
style = "QProgressBar::chunk { background-color: #f44336; }"
elif soc < 50:
style = "QProgressBar::chunk { background-color: #ff9800; }"
else:
style = "QProgressBar::chunk { background-color: #4caf50; }"
self.soc_bar.setStyleSheet(style)
def parse_battery_full(self, data):
"""解析完整电量数据"""
if len(data) < 19:
return
values = struct.unpack('<BHhhhHHhHBBBB', data)
soc, voltage, current, temp, remain, full, power, cycle, soh, \
is_charging, is_full, bat_detected = values
# 更新基础信息
self.soc_label.setText(f"电量: {soc}%")
self.soc_bar.setValue(soc)
self.voltage_label.setText(f"{voltage}mV ({voltage/1000:.2f}V)")
self.current_label.setText(f"{current}mA")
self.temp_label.setText(f"{temp/10:.1f}°C")
status = "充电中 ↑" if is_charging else "放电中 ↓"
if is_full:
status += " (已充满)"
self.status_label.setText(status)
self.log(f"完整数据 - 容量:{remain}/{full}mAh 功率:{power}mW 循环:{cycle}次 健康:{soh}%")
def parse_gain_set_resp(self, data):
"""解析增益设置响应"""
if len(data) < 3:
return
result, level, value = struct.unpack('<BBB', data)
if result == 0:
self.current_gain_label.setText(f"{value}x (档位{level})")
self.log(f"增益设置成功: {value}x")
else:
self.log(f"增益设置失败: 错误码{result}")
def parse_gain_list(self, data):
"""解析增益列表"""
if len(data) < 2:
return
count = data[0]
self.gain_levels = list(data[1:1+count])
# 清空旧按钮
while self.gain_button_layout.count():
item = self.gain_button_layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
# 创建增益按钮
for i, value in enumerate(self.gain_levels):
btn = QPushButton(f"{value}x")
btn.clicked.connect(lambda checked, lvl=i: self.set_gain(lvl))
self.gain_button_layout.addWidget(btn)
self.log(f"获取到{count}个增益档位: {self.gain_levels}")
def on_error(self, error_msg):
"""错误处理"""
self.log(f"错误: {error_msg}")
def log(self, message):
"""添加日志"""
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.append(f"[{timestamp}] {message}")
# ============================================================================
# 主程序入口
# ============================================================================
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
📊五、完整开发总结
5.1 你需要做的工作
| 序号 | 任务 | 文件 | 说明 |
|---|---|---|---|
| 1️⃣ | 集成BQ27427驱动 | drv_bq27427.c/h |
已提供完整代码 |
| 2️⃣ | 修改电池应用层 | app_battery.c |
调用BQ27427驱动替代原有代码 |
| 3️⃣ | 扩展通信协议 | protocol.c/h |
添加新命令码和数据包处理 |
| 4️⃣ | 开发上位机软件 | pc_monitor.py |
已提供完整Python代码 |
| 5️⃣ | 测试验证 | - | 验证通信和功能 |
5.2 代码集成步骤
步骤1:添加BQ27427驱动到工程
Drivers/Device/
├── drv_bq27427.c ✅ 新增
└── drv_bq27427.h ✅ 新增
步骤2:修改app_battery.c
// 原来的代码:
#include "drv_battery.h" // 通用电量芯片驱动
// 改为:
#include "drv_bq27427.h" // BQ27427专用驱动
// 初始化改为:
int App_Battery_Init(void) {
if (BQ27427_Init() != BQ27427_OK) {
return -1;
}
// ...
}
// 读取数据改为:
void App_Battery_Task(void) {
BQ27427_BasicData_t bq_data;
if (BQ27427_ReadBasicData(&bq_data) == BQ27427_OK) {
battery_status.soc = bq_data.soc;
battery_status.voltage = bq_data.voltage;
battery_status.current = bq_data.current;
battery_status.is_charging = bq_data.is_charging;
// ...
}
}
步骤3:扩展protocol.c处理新命令
void protocol_parse_callback(ProtocolFrame_t *frame) {
switch (frame->cmd) {
case CMD_READ_BATTERY_BASIC:
// 读取并发送基础电量数据
App_Battery_SendBasicData();
break;
case CMD_READ_BATTERY_FULL:
// 读取并发送完整电量数据
App_Battery_SendFullData();
break;
case CMD_SET_GAIN:
// 设置增益
App_Gain_OnCommand(frame);
break;
// ... 其他命令
}
}
5.3 测试验证流程
5.4 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| I2C无响应 | 地址错误/线路问题 | 用I2C扫描工具验证地址,检查上拉电阻 |
| 数据全为0 | 芯片未配置 | 检查是否需要写入配置寄存器 |
| 电量不准确 | 容量参数未设置 | 参考手册配置电池容量参数 |
| 通信CRC错误 | 字节序错误 | 检查大小端处理 |
| 上位机无响应 | 波特率不匹配 | 确认115200,8,N,1配置 |
🎯 总结
我已经为你提供了:
✅ 完整的BQ27427驱动代码(drv_bq27427.c/h)
✅ 扩展的通信协议定义(支持电池+增益)
✅ 详细的数据包格式和示例
✅ 完整的Python上位机软件(带GUI界面)
✅ 集成指南和测试流程
你的下一步:
- 将
drv_bq27427.c/h添加到工程 - 修改
app_battery.c调用BQ27427驱动 - 扩展
protocol.c支持新命令 - 运行Python上位机软件测试
- 验证所有功能正常工作
有任何问题随时问我! 🚀
电量显示与放大倍数控制通信协议设计
📋 一、需求分析
根据你的实际需求,我帮你精简设计一个极简高效的通信协议:
核心需求
- 电量显示:上位机显示电池百分比(0-100%)
- 放大倍数控制:上位机设置/读取放大倍数(1-255倍)
设计原则
✅ 固定长度:所有数据包长度统一
✅ 简单高效:命令码简洁明了
✅ 双向确认:每次通信都有响应
✅ 校验可靠:使用简单校验和
📡 二、精简协议设计
2.1 帧格式(固定8字节)
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
│ 帧头 │ 命令 │ 数据1│ 数据2│ 预留 │ 预留 │ 校验 │ 帧尾 │
│ 1字节│ 1字节│ 1字节│ 1字节│ 1字节│ 1字节│ 1字节│ 1字节│
│ 0xAA │ CMD │ DATA1│ DATA2│ 0x00 │ 0x00 │ SUM │ 0x55 │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
字段说明:
- 帧头:固定
0xAA,用于同步 - 命令码:1字节,定义操作类型
- 数据1:1字节,主数据(电量百分比或放大倍数)
- 数据2:1字节,辅助数据(状态标志)
- 预留:2字节,保留扩展用
- 校验和:1字节,简单累加校验
- 帧尾:固定
0x55,用于帧结束标识
校验算法:
校验和 = (命令码 + 数据1 + 数据2 + 预留1 + 预留2) & 0xFF
2.2 命令码定义
| 命令码 | 名称 | 方向 | 说明 |
|---|---|---|---|
| 0x01 | 读取电量 | 上→下 | 上位机请求读取电池电量 |
| 0x81 | 电量响应 | 下→上 | 下位机返回电池电量 |
| 0x02 | 设置放大倍数 | 上→下 | 上位机设置放大倍数 |
| 0x82 | 设置响应 | 下→上 | 下位机确认设置结果 |
| 0x03 | 读取放大倍数 | 上→下 | 上位机查询当前放大倍数 |
| 0x83 | 倍数响应 | 下→上 | 下位机返回当前倍数 |
| 0xF0 | 心跳包 | 双向 | 保持连接活跃 |
规则: 响应码 = 请求码 | 0x80
📝 三、完整通信协议文档
电量显示和放大倍数控制通信协议
版本:V1.0
1. 修订历史
| 版本号 | 修订说明 | 修订人 | 修订日期 |
|---|---|---|---|
| V1.0 | 初版发布 | [您的名字] | 2024-12-09 |
2. 串口配置
| 参数 | 值 |
|---|---|
| 物理接口 | USB (CH340转串口) |
| 波特率 | 115200 |
| 数据位 | 8 |
| 停止位 | 1 |
| 校验位 | 无 |
| 流控 | 无 |
3. 协议帧格式
3.1 帧结构(固定8字节)
┌──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ Byte0 │ Byte1 │ Byte2 │ Byte3 │ Byte4 │ Byte5 │ Byte6 │ Byte7 │
├──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
│ 帧头 │ 命令码 │ 数据1 │ 数据2 │ 预留1 │ 预留2 │ 校验和 │ 帧尾 │
│ 0xAA │ CMD │ DATA1 │ DATA2 │ 0x00 │ 0x00 │ SUM │ 0x55 │
└──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┴──────────┘
3.2 字段说明
| 字段 | 长度 | 值域 | 说明 |
|---|---|---|---|
| 帧头 | 1字节 | 0xAA | 固定值,用于帧同步 |
| 命令码 | 1字节 | 0x00-0xFF | 定义操作类型 |
| 数据1 | 1字节 | 0-255 | 主数据(电量%或放大倍数) |
| 数据2 | 1字节 | 0-255 | 辅助数据(状态标志) |
| 预留1 | 1字节 | 0x00 | 保留扩展(当前为0) |
| 预留2 | 1字节 | 0x00 | 保留扩展(当前为0) |
| 校验和 | 1字节 | 0-255 | 累加校验 |
| 帧尾 | 1字节 | 0x55 | 固定值,帧结束标识 |
3.3 校验算法
计算公式:
校验和 = (命令码 + 数据1 + 数据2 + 预留1 + 预留2) & 0xFF
验证方法:
接收校验和 == 计算校验和 // 相等则校验通过
示例:
帧数据: AA 01 4B 01 00 00 ?? 55
校验和 = (0x01 + 0x4B + 0x01 + 0x00 + 0x00) & 0xFF
= 0x4D
完整帧: AA 01 4B 01 00 00 4D 55
4. 命令码定义
4.1 命令码列表
| 命令码 | 命令名称 | 方向 | 功能描述 |
|---|---|---|---|
| 0x01 | READ_BATTERY | PC → STM32 | 读取电池电量 |
| 0x81 | BATTERY_RESP | STM32 → PC | 电池电量响应 |
| 0x02 | SET_GAIN | PC → STM32 | 设置放大倍数 |
| 0x82 | GAIN_SET_RESP | STM32 → PC | 设置结果响应 |
| 0x03 | READ_GAIN | PC → STM32 | 读取放大倍数 |
| 0x83 | GAIN_READ_RESP | STM32 → PC | 倍数读取响应 |
| 0xF0 | HEARTBEAT | 双向 | 心跳包(保活) |
命名规则: 响应命令码 = 请求命令码 | 0x80
5. 通信流程详解
5.1 电池电量查询
请求帧(PC → STM32)
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ AA │ 01 │ 00 │ 00 │ 00 │ 00 │ 01 │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
| 字段 | 值 | 说明 |
|---|---|---|
| 帧头 | 0xAA | 固定 |
| 命令码 | 0x01 | READ_BATTERY |
| 数据1 | 0x00 | 无数据 |
| 数据2 | 0x00 | 无数据 |
| 预留1 | 0x00 | 保留 |
| 预留2 | 0x00 | 保留 |
| 校验和 | 0x01 | 0x01+0x00+0x00+0x00+0x00=0x01 |
| 帧尾 | 0x55 | 固定 |
响应帧(STM32 → PC)
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ AA │ 81 │ 4B │ 01 │ 00 │ 00 │ CD │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
| 字段 | 值 | 说明 |
|---|---|---|
| 帧头 | 0xAA | 固定 |
| 命令码 | 0x81 | BATTERY_RESP |
| 数据1 | 0x4B | 电量75% |
| 数据2 | 0x01 | 充电状态(0=放电, 1=充电) |
| 预留1 | 0x00 | 保留 |
| 预留2 | 0x00 | 保留 |
| 校验和 | 0xCD | 0x81+0x4B+0x01=0xCD |
| 帧尾 | 0x55 | 固定 |
数据解析:
- 电量百分比:75%
- 充电状态:正在充电
5.2 设置放大倍数
请求帧(PC → STM32)
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ AA │ 02 │ 0A │ 00 │ 00 │ 00 │ 0C │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
| 字段 | 值 | 说明 |
|---|---|---|
| 帧头 | 0xAA | 固定 |
| 命令码 | 0x02 | SET_GAIN |
| 数据1 | 0x0A | 设置为10倍 |
| 数据2 | 0x00 | 保留 |
| 预留1 | 0x00 | 保留 |
| 预留2 | 0x00 | 保留 |
| 校验和 | 0x0C | 0x02+0x0A=0x0C |
| 帧尾 | 0x55 | 固定 |
响应帧(STM32 → PC)
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ AA │ 82 │ 0A │ 00 │ 00 │ 00 │ 8C │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
| 字段 | 值 | 说明 |
|---|---|---|
| 帧头 | 0xAA | 固定 |
| 命令码 | 0x82 | GAIN_SET_RESP |
| 数据1 | 0x0A | 确认设置为10倍 |
| 数据2 | 0x00 | 设置结果(0=成功, 非0=失败) |
| 预留1 | 0x00 | 保留 |
| 预留2 | 0x00 | 保留 |
| 校验和 | 0x8C | 0x82+0x0A=0x8C |
| 帧尾 | 0x55 | 固定 |
数据2错误码:
- 0x00:设置成功
- 0x01:参数错误(倍数超出范围)
- 0x02:硬件故障
- 0xFF:未知错误
5.3 读取放大倍数
请求帧(PC → STM32)
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ AA │ 03 │ 00 │ 00 │ 00 │ 00 │ 03 │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
响应帧(STM32 → PC)
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ AA │ 83 │ 0A │ 00 │ 00 │ 00 │ 8D │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
| 字段 | 值 | 说明 |
|---|---|---|
| 数据1 | 0x0A | 当前放大倍数10倍 |
| 数据2 | 0x00 | 保留 |
5.4 心跳包
心跳帧(双向)
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ AA │ F0 │ 00 │ 00 │ 00 │ 00 │ F0 │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
用途:
- 检测通信链路是否正常
- 建议每5秒发送一次
- 对方收到后立即原样返回
6. 数据字段详细定义
6.1 电池电量(数据1)
| 值 | 含义 |
|---|---|
| 0x00 | 0% (电池耗尽) |
| 0x01-0x63 | 1%-99% |
| 0x64 | 100% (电池满电) |
显示建议:
- 0-20%:红色警告
- 21-50%:黄色提示
- 51-100%:绿色正常
6.2 充电状态(数据2 in 电量响应)
| 值 | 含义 |
|---|---|
| 0x00 | 放电中(未充电) |
| 0x01 | 充电中 |
6.3 放大倍数(数据1)
| 值范围 | 含义 |
|---|---|
| 0x01-0xFF | 1倍-255倍 |
| 0x00 | 无效值 |
常用倍数:
- 1倍:0x01
- 2倍:0x02
- 5倍:0x05
- 10倍:0x0A
- 20倍:0x14
- 50倍:0x32
- 100倍:0x64
- 200倍:0xC8
7. 通信时序与策略
7.1 推荐通信频率
| 功能 | 频率 | 说明 |
|---|---|---|
| 电量查询 | 1次/秒 | 实时显示电池状态 |
| 倍数查询 | 按需查询 | 界面打开时读取一次 |
| 倍数设置 | 按需发送 | 用户操作时发送 |
| 心跳包 | 1次/5秒 | 检测连接状态 |
7.2 超时重传机制
发送请求
↓
等待响应 (1000ms超时)
↓
收到响应? ──Yes→ 处理数据
↓ No
重发(最多3次)
↓
仍失败? → 提示"通信失败"
超时时间: 1000ms
重试次数: 3次
重试间隔: 500ms
8. 错误处理
8.1 帧校验失败
if (校验和不匹配) {
丢弃该帧;
不做响应;
}
8.2 连续通信失败
if (连续3次超时) {
断开连接;
提示用户检查串口;
}
8.3 异常数据处理
| 异常情况 | 处理方式 |
|---|---|
| 电量 > 100% | 显示为100% |
| 电量 < 0% | 显示为0% |
| 倍数 = 0 | 显示为错误 |
| 帧头/帧尾错误 | 丢弃该帧 |
9. 示例代码片段
9.1 STM32发送电量响应(C语言)
void send_battery_response(uint8_t soc, uint8_t is_charging)
{
uint8_t frame[8];
frame[0] = 0xAA; // 帧头
frame[1] = 0x81; // 命令码
frame[2] = soc; // 电量百分比
frame[3] = is_charging; // 充电状态
frame[4] = 0x00; // 预留1
frame[5] = 0x00; // 预留2
frame[6] = (frame[1] + frame[2] + frame[3] + frame[4] + frame[5]) & 0xFF; // 校验和
frame[7] = 0x55; // 帧尾
UART_Send(frame, 8);
}
// 使用示例
send_battery_response(75, 1); // 发送75%电量,正在充电
9.2 上位机解析电量(Python)
def parse_battery_response(frame):
"""
解析电量响应帧
frame: 8字节数据
返回: (电量%, 充电状态, 校验结果)
"""
if len(frame) != 8:
return None, None, False
# 验证帧头和帧尾
if frame[0] != 0xAA or frame[7] != 0x55:
return None, None, False
# 验证校验和
checksum_calc = (frame[1] + frame[2] + frame[3] + frame[4] + frame[5]) & 0xFF
if checksum_calc != frame[6]:
return None, None, False
# 提取数据
soc = frame[2] # 电量百分比
is_charging = (frame[3] == 0x01) # 充电状态
return soc, is_charging, True
# 使用示例
frame = bytes([0xAA, 0x81, 0x4B, 0x01, 0x00, 0x00, 0xCD, 0x55])
soc, charging, ok = parse_battery_response(frame)
if ok:
print(f"电量: {soc}%, 充电中: {charging}")
9.3 上位机发送设置倍数(Python)
def send_set_gain(serial_port, gain_value):
"""
发送设置放大倍数命令
gain_value: 1-255
"""
frame = bytearray(8)
frame[0] = 0xAA # 帧头
frame[1] = 0x02 # SET_GAIN命令
frame[2] = gain_value # 放大倍数
frame[3] = 0x00 # 保留
frame[4] = 0x00 # 保留1
frame[5] = 0x00 # 保留2
frame[6] = (frame[1] + frame[2] + frame[3] + frame[4] + frame[5]) & 0xFF
frame[7] = 0x55 # 帧尾
serial_port.write(frame)
# 使用示例
import serial
ser = serial.Serial('COM3', 115200)
send_set_gain(ser, 10) # 设置为10倍
10. 完整通信流程示例
场景:上位机启动后监控电池并设置倍数
时间轴:
T0: 上位机启动,打开串口COM3
→ 发送心跳包测试连接
→ 等待响应...
T1: 收到心跳响应,连接成功
✓ 显示"已连接"
T2: 读取当前放大倍数
→ 发送: AA 03 00 00 00 00 03 55
← 收到: AA 83 0A 00 00 00 8D 55
✓ 显示"当前倍数: 10倍"
T3: 开始周期读取电量(每1秒)
→ 发送: AA 01 00 00 00 00 01 55
← 收到: AA 81 4B 01 00 00 CD 55
✓ 显示"电量: 75%, 充电中"
T4: 用户点击设置按钮,改为20倍
→ 发送: AA 02 14 00 00 00 16 55
← 收到: AA 82 14 00 00 00 96 55
✓ 显示"设置成功: 20倍"
T5: 继续周期读取电量...
→ 每1秒发送读取命令
→ 更新界面显示
11. 附录:测试用例
11.1 正常场景测试
| 测试项 | 发送 | 期望响应 | 说明 |
|---|---|---|---|
| 读取电量 | AA 01 00 00 00 00 01 55 |
AA 81 XX 0X 00 00 XX 55 |
XX为电量值 |
| 设置10倍 | AA 02 0A 00 00 00 0C 55 |
AA 82 0A 00 00 00 8C 55 |
设置成功 |
| 读取倍数 | AA 03 00 00 00 00 03 55 |
AA 83 0A 00 00 00 8D 55 |
当前10倍 |
| 心跳包 | AA F0 00 00 00 00 F0 55 |
AA F0 00 00 00 00 F0 55 |
原样返回 |
11.2 异常场景测试
| 测试项 | 发送 | 期望行为 | 说明 |
|---|---|---|---|
| 校验和错误 | AA 01 00 00 00 00 FF 55 |
无响应 | 丢弃错误帧 |
| 无效倍数 | AA 02 00 00 00 00 02 55 |
AA 82 00 01 00 00 83 55 |
返回错误码0x01 |
| 超时测试 | 发送后断开串口 | 1秒后超时重发 | 最多重试3次 |
12. 版本升级路线
V1.0(当前版本)
- ✅ 基础电量查询
- ✅ 放大倍数设置/读取
- ✅ 心跳保活
V1.1(计划)
- 添加电压显示(扩展数据2字段)
- 添加温度监控
- 支持参数保存
V2.0(未来)
- 支持多种电池类型
- 增加自动校准功能
- OTA固件升级支持
13. 常见问题FAQ
Q1: 为什么不显示电压?
A: 上位机只需显示百分比,电压属于调试信息。如需添加,可在数据2字段扩展(电压/100)。
Q2: 倍数最大支持多少?
A: 当前255倍(0xFF),如需更大倍数,可用数据1+数据2组合为16位值(最大65535)。
Q3: 心跳包必须吗?
A: 建议使用。可检测串口是否正常,避免静默失败。
Q4: 校验和可以改成CRC吗?
A: 可以。简单累加适用于大部分场景,CRC16更可靠但增加计算开销。
Q5: 如何调试通信?
A: 使用串口调试助手,HEX模式发送测试帧,观察返回数据。
14. 联系与支持
技术支持: [您的邮箱]
文档更新: 2024-12-09
协议版本: V1.0
附录:快速测试命令(HEX格式)
# 读取电量
AA 01 00 00 00 00 01 55
# 设置10倍
AA 02 0A 00 00 00 0C 55
# 设置20倍
AA 02 14 00 00 00 16 55
# 设置50倍
AA 02 32 00 00 00 34 55
# 设置100倍
AA 02 64 00 00 00 66 55
# 读取倍数
AA 03 00 00 00 00 03 55
# 心跳包
AA F0 00 00 00 00 F0 55
🎯 总结
我为你设计了一个极简高效的通信协议:
✅ 固定8字节:所有帧统一长度,解析简单
✅ 3个核心命令:读电量、设置倍数、读倍数
✅ 简单校验:累加校验,计算快速
✅ 双向确认:每次请求都有响应
✅ 扩展性:预留2字节,未来可添加功能
协议特点:
- 比你原有协议简化了80%
- 保留核心功能(电量+倍数)
- 去掉复杂的CRC16(用简单累加)
- 去掉多余字段(容量、温度、健康度等)
- 固定长度,便于解析
这个协议文档可以直接用于项目开发,包含了完整的帧格式、命令定义、示例代码和测试用例。如果需要调整任何细节,随时告诉我!🚀
更多推荐

所有评论(0)