📋 一、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 (正在充电)
  • 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)

设置失败示例:

响应: 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)

❤️ 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 测试验证流程

硬件连接
BQ27427初始化测试
初始化成功?
检查I2C接线
读取设备ID验证
读取基础电量数据
上位机通信测试
增益控制测试
长时间稳定性测试
完成

5.4 常见问题排查

问题 可能原因 解决方案
I2C无响应 地址错误/线路问题 用I2C扫描工具验证地址,检查上拉电阻
数据全为0 芯片未配置 检查是否需要写入配置寄存器
电量不准确 容量参数未设置 参考手册配置电池容量参数
通信CRC错误 字节序错误 检查大小端处理
上位机无响应 波特率不匹配 确认115200,8,N,1配置

🎯 总结

我已经为你提供了:

完整的BQ27427驱动代码(drv_bq27427.c/h)
扩展的通信协议定义(支持电池+增益)
详细的数据包格式和示例
完整的Python上位机软件(带GUI界面)
集成指南和测试流程

你的下一步:

  1. drv_bq27427.c/h添加到工程
  2. 修改app_battery.c调用BQ27427驱动
  3. 扩展protocol.c支持新命令
  4. 运行Python上位机软件测试
  5. 验证所有功能正常工作

有任何问题随时问我! 🚀


电量显示与放大倍数控制通信协议设计

📋 一、需求分析

根据你的实际需求,我帮你精简设计一个极简高效的通信协议:

核心需求

  1. 电量显示:上位机显示电池百分比(0-100%)
  2. 放大倍数控制:上位机设置/读取放大倍数(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(用简单累加)
  • 去掉多余字段(容量、温度、健康度等)
  • 固定长度,便于解析

这个协议文档可以直接用于项目开发,包含了完整的帧格式、命令定义、示例代码和测试用例。如果需要调整任何细节,随时告诉我!🚀

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐