[电池电源管理] 中颖SH367309锂电池保护板方案代码深度解析:从代码实现看功能逻辑

[复制链接]
108|0
Xiashiqi 发表于 2025-11-6 08:54 | 显示全部楼层 |阅读模式
锂电池 保护板方案 中颖SH367309方案 原理图 PCB 源代码 保护板方案 中颖SH367309方案 原理图 PCB 源代码 锂电池、保护板方案、中颖SH367309方案、原理图和PCB源代码。 锂电池是一种常见的可充电电池,由锂离子在正负极之间的迁移来储存和释放电能。它们具有高能量密度、长寿命和较低的自放电率等优点,因此在许多电子设备中得到广泛应用。 保护板方案是用于保护锂电池的电路设计方案。它的主要功能是监测电池的电压、电流和温度等参数,并在必要时采取措施来防止电池过充、过放、过流或过温。保护板方案可以提高锂电池的安全性和可靠性。 中颖SH367309方案是一种特定的保护板方案。该方案可能包括特定的电路设计、元件选择和算法等,以实现对锂电池的保护功能。 原理图是电子设备设计中的一种图表,用于展示电路的连接方式和元件之间的关系。它是设计师用来理解和实现电路功能的重要工具。 PCB(Printed Circuit Board,印刷电路板)是电子设备中的一种基础组件,用于支持和连接电子元件。它由一层或多层导电材料构成,通过印刷、蚀刻和穿孔等工艺制成。PCB上的导线和连接点可以实现电路的连接和信号传输。

一、代码背景与整体架构
本次解析的代码来自中颖SH367309锂电池保护板方案,基于STM32F10x系列微控制器,围绕锂电池安全保护核心需求,构建了“内核驱动-硬件适配-功能应用”三层代码架构。代码总数135个,核心文件集中在CORE(内核)与HARDWARE(硬件驱动/应用)目录,通过模块化设计实现电池参数采集、保护控制、数据存储、人机交互等功能。本文将聚焦代码细节,从函数实现、数据结构、寄存器操作等维度,逐一拆解各模块功能逻辑。

二、CORE目录:Cortex-M3内核驱动代码解析
CORE目录包含corecm3.c/.h(内核功能实现)与startupstm32f10x_hd.s/.md.s(启动文件),是整个系统的底层支撑,严格遵循CMSIS标准,适配多编译器环境(ARM Compiler、IAR、GNU、TASKING)。

(一)core_cm3.c:内核核心功能代码实现
该文件通过汇编与C语言混合编程,封装Cortex-M3内核寄存器操作,提供堆栈管理、中断控制、数据处理等底层接口,核心代码片段与功能解析如下:

1. 编译器适配宏定义
代码开篇通过条件编译区分不同编译器的关键字差异,确保底层接口兼容性,例如:

#if defined ( __CC_ARM   )
  #define __ASM            __asm                                      /*!< asm keyword for ARM Compiler          */
  #define __INLINE         __inline                                   /*!< inline keyword for ARM Compiler       */
#elif defined ( __ICCARM__ )
  #define __ASM           __asm                                       /*!< asm keyword for IAR Compiler          */
  #define __INLINE        inline                                      /*!< inline keyword for IAR Compiler       */
#elif defined   (  __GNUC__  )
  #define __ASM            __asm                                      /*!< asm keyword for GNU Compiler          */
  #define __INLINE         inline                                     /*!< inline keyword for GNU Compiler       */
#endif


功能作用:统一不同编译器的asm(汇编)与inline(内联函数)关键字,避免语法错误,确保代码跨编译器可编译。

2. 堆栈指针操作函数
Cortex-M3内核支持主堆栈(MSP,用于内核异常/特权模式)与进程堆栈(PSP,用于用户线程),代码通过汇编函数实现堆栈指针的读写:

// 读取进程堆栈指针(PSP)
__ASM uint32_t __get_PSP(void)
{
  mrs r0, psp  // 将PSP寄存器值存入r0
  bx lr        // 返回,r0作为返回值
}

// 设置进程堆栈指针(PSP)
__ASM void __set_PSP(uint32_t topOfProcStack)
{
  msr psp, r0  // 将r0(输入参数)的值写入PSP寄存器
  bx lr        // 返回
}



代码解析:

mrs(Move Register from Special Register):将特殊寄存器(如PSP、MSP)的值读取到通用寄存器(r0)。
msr(Move Special Register from Register):将通用寄存器的值写入特殊寄存器。
函数返回时,bx lr通过链接寄存器(lr)返回调用处,符合ARM Thumb指令集规范。
功能作用:支持操作系统线程切换(如RTOS)时的堆栈上下文保存与恢复,是多任务运行的基础。

3. 中断与优先级控制函数
代码实现了内核中断屏蔽、优先级配置相关函数,例如基础优先级(BASEPRI)的读写:

// 读取BASEPRI寄存器(控制中断优先级屏蔽)
__ASM uint32_t __get_BASEPRI(void)
{
  mrs r0, basepri  // 读取BASEPRI值到r0
  bx lr
}

// 设置BASEPRI寄存器
__ASM void __set_BASEPRI(uint32_t basePri)
{
  msr basepri, r0  // 将输入参数(r0)写入BASEPRI
  bx lr
}



关键说明:

BASEPRI寄存器:仅屏蔽优先级低于该值的中断(例如BASEPRI=0x80时,屏蔽优先级≥0x80的中断),用于精细控制中断响应。
代码中basePri & 0xFF的隐含处理(部分编译器实现):确保仅使用低8位(Cortex-M3优先级寄存器共8位,实际使用4位或8位取决于芯片设计)。
功能作用:在关键任务(如电池参数采集、保护逻辑执行)运行时,屏蔽低优先级中断,避免任务被打断导致数据异常。

4. 数据处理 intrinsics函数
针对嵌入式场景中常用的字节/位反转需求,代码封装了硬件加速指令:

// 16位无符号数字节反转(如0x1234 → 0x3412)
__ASM uint32_t __REV16(uint16_t value)
{
  rev16 r0, r0  // ARM指令:反转16位数据的字节顺序
  bx lr
}

// 16位有符号数字节反转并符号扩展为32位(如0x8000 → 0xFFFF8000)
__ASM int32_t __REVSH(int16_t value)
{
  revsh r0, r0  // ARM指令:反转16位数据字节顺序,符号扩展到32位
  bx lr
}



代码优势:直接调用ARM硬件指令,比软件实现(如通过移位+掩码)效率提升10倍以上,适用于高速数据处理(如ADC采集数据的端序转换)。

(二)startup_stm32f10x_hd.s:启动代码解析
启动文件为汇编编写,负责STM32F10x芯片上电后的初始化流程,核心代码片段与功能如下:

1. 堆栈空间定义
; 定义堆栈大小(8192字节),NOINIT表示不初始化,READWRITE表示可读写,ALIGN=3表示8字节对齐
Stack_Size      EQU     0x00002000
                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size  ; 分配Stack_Size字节的堆栈空间
__initial_sp    ; 定义栈顶指针(堆栈空间的末地址)

; 定义堆大小(1024字节),用于动态内存分配(如malloc)
Heap_Size       EQU     0x00000400
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base     ; 堆起始地址
Heap_Mem        SPACE   Heap_Size   ; 分配Heap_Size字节的堆空间
__heap_limit    ; 堆结束地址



关键说明:

AREA:汇编指令,定义代码/数据段,STACK/HEAP为段名,NOINIT表示上电后不自动初始化(避免浪费启动时间)。
ALIGN=3:8字节对齐(2^3),符合Cortex-M3内核对堆栈指针的对齐要求(否则可能触发硬件异常)。
功能作用:为系统提供堆栈空间,栈用于函数调用时的局部变量存储、寄存器保护;堆用于动态内存分配(如my_cpuset结构体的动态创建)。

2. 中断向量表
中断向量表是启动文件的核心,存储所有异常与外设中断的入口地址:

                AREA    RESET, DATA, READONLY  ; 定义只读数据段(中断向量表)
                EXPORT  __Vectors           ; 导出向量表起始地址,供链接器使用
__Vectors       DCD     __initial_sp         ; 0: 栈顶指针
                DCD     Reset_Handler        ; 1: 复位异常处理函数
                DCD     NMI_Handler          ; 2: NMI异常处理函数
                DCD     HardFault_Handler    ; 3: 硬故障异常处理函数
                ; ... 省略其他内核异常 ...
                DCD     SysTick_Handler      ; 15: 系统滴答定时器中断
                ; 外部中断开始
                DCD     WWDG_IRQHandler      ; 16: 窗口看门狗中断
                DCD     PVD_IRQHandler       ; 17: 电源电压检测中断
                DCD     EXTI0_IRQHandler     ; 18: 外部中断0(对应GPIOB0)
                ; ... 省略其他外设中断 ...
__Vectors_End
__Vectors_Size  EQU  __Vectors_End - __Vectors  ; 计算向量表大小



代码逻辑:

芯片上电后,PC指针自动指向向量表第0项(栈顶指针),随后跳转到第1项(Reset_Handler)执行。
每个中断对应一个DCD(Define Code/Data)指令,存储中断处理函数的地址;未使用的中断默认指向Default_Handler(无限循环)。
功能作用:建立中断与处理函数的映射关系,是系统响应中断的基础(如EXTI0中断触发时,CPU自动跳转到EXTI0_IRQHandler)。

3. 复位处理函数
Reset_Handler   PROC        ; 定义复位处理函数过程
                EXPORT  Reset_Handler [WEAK]  ; 弱定义,允许用户重写
                IMPORT  __main               ; 导入C库的__main函数
                IMPORT  SystemInit           ; 导入系统初始化函数(如时钟配置)
                LDR     R0, =SystemInit      ; R0 = SystemInit函数地址
                BLX     R0                  ; 调用SystemInit(配置系统时钟)
                LDR     R0, =__main         ; R0 = __main函数地址
                BX      R0                  ; 跳转到__main(最终调用main函数)
                ENDP                       ; 函数结束



执行流程:

调用SystemInit:配置STM32的系统时钟(默认72MHz,通过HSE+PLL实现)、外设时钟使能等。
跳转到main:C库函数,负责初始化堆/栈、全局变量,最终调用用户编写的main函数。
关键细节:

BLX(Branch with Link and Exchange):带链接的跳转,同时切换指令集(ARM/Thumb),适合调用不同指令集的函数。
[WEAK]:弱符号属性,若用户定义了同名的Reset_Handler,则覆盖该实现,增强代码灵活性。
三、HARDWARE目录:硬件驱动与应用代码解析
HARDWARE目录是方案的功能核心,包含ADC(参数采集)、EXTI(中断处理)、KEY(用户输入)、LCD(显示)、CPU_TX(参数管理)等模块,以下聚焦代码实现细节。

(一)ADC模块:adc.c/.h——电池参数采集
ADC模块负责采集锂电池的电压、电流等模拟信号,为保护逻辑提供数据输入,核心代码解析如下:

1. ADC初始化函数`Adc_Init`
void Adc_Init(void)
{  
  ADC_InitTypeDef ADC_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;

  // 使能GPIOA和ADC1的时钟(APB2总线)
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);  

  // 配置ADC时钟分频:PCLK2(72MHz)/6=12MHz(ADC最大时钟不能超过14MHz)
  RCC_ADCCLKConfig(RCC_PCLK2_Div6);  

  // 配置GPIOA0/1/4为模拟输入模式(无上下拉、无推挽)
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_4;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  // 模拟输入模式
  GPIO_Init(GPIOA, &GPIO_InitStructure);  

  // 复位ADC1(恢复默认配置)
  ADC_DeInit(ADC1);  

  // 配置ADC1工作模式
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  // 独立模式(仅ADC1工作)
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;       // 单通道模式(非扫描)
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;  // 单次转换(非连续)
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  // 软件触发
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  // 数据右对齐
  ADC_InitStructure.ADC_NbrOfChannel = 1;  // 转换通道数:1
  ADC_Init(ADC1, &ADC_InitStructure);  // 初始化ADC1

  // 使能ADC1
  ADC_Cmd(ADC1, ENABLE);  

  // ADC校准(确保采集精度)
  ADC_ResetCalibration(ADC1);  // 复位校准寄存器
  while(ADC_GetResetCalibrationStatus(ADC1));  // 等待复位完成
  ADC_StartCalibration(ADC1);   // 启动校准
  while(ADC_GetCalibrationStatus(ADC1));  // 等待校准完成
}



代码关键逻辑:

时钟配置:RCCAPB2PeriphClockCmd使能对应外设时钟(GPIOA和ADC1均挂载在APB2总线);RCCADCCLKConfig确保ADC时钟在允许范围内,避免采集误差。
引脚配置:GPIOModeAIN(模拟输入)模式下,GPIO引脚断开数字电路,仅保留模拟通路,避免数字信号干扰模拟采集。
校准流程:ADC上电后必须执行校准,通过ADCResetCalibration和ADCStartCalibration消除硬件偏移,提升采集精度(校准后误差可降至±1LSB)。
2. 单通道采集函数`Get_Adc`
u16 Get_Adc(u8 ch)   
{
  // 配置ADC1的规则通道:通道ch,转换序列1,采样时间239.5周期
  ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5);  

  // 软件触发ADC转换
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);  

  // 等待转换完成(EOC:End of Conversion)
  while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));

  // 返回转换结果(12位ADC,右对齐后范围0~4095)
  return ADC_GetConversionValue(ADC1);
}



采集流程:

通道配置:ADC_RegularChannelConfig指定当前采集的ADC通道(ch=0/1/4),采样时间239.5周期(越长,采集越稳定,但速度越慢,适合电池参数这类慢变信号)。
触发转换:ADC_SoftwareStartConvCmd通过软件触发转换(无外部触发源)。
等待完成:ADC_GetFlagStatus检查EOC标志,确保转换完成后再读取数据,避免读取无效值。
数据范围:STM32F10x的ADC为12位,转换结果范围0~4095,对应输入电压范围0~3.3V(若有分压电路,需乘以分压系数得到实际电池电压)。

3. 多通道轮询采集`TIME_to_CAdc`
// 全局变量:采集缓冲区(16个元素,循环存储)
u16 cad_temp1[16], cad_temp2[16], cad_temp3[16];
u8 cad_nox = 0, cad_noy = 0;  // 通道索引(0-2)、缓冲区索引(0-15)

void TIME_to_CAdc(void)
{
  if(cad_nox == 0)
  {
    // 读取当前ADC结果,存入cad_temp1(通道0对应的数据)
    cad_temp1[cad_noy] = ADC_GetConversionValue(ADC1);
    // 配置下一次采集通道1,采样时间239.5周期
    ADC_RegularChannelConfig(ADC1, 1, 1, ADC_SampleTime_239Cycles5);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);  // 触发转换
  }
  else if(cad_nox == 1)
  {
    cad_temp2[cad_noy] = ADC_GetConversionValue(ADC1);  // 读取通道1结果
    ADC_RegularChannelConfig(ADC1, 4, 1, ADC_SampleTime_239Cycles5);  // 配置通道4
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
  }
  else if(cad_nox == 2)
  {
    cad_temp3[cad_noy] = ADC_GetConversionValue(ADC1);  // 读取通道4结果
    ADC_RegularChannelConfig(ADC1, 0, 1, ADC_SampleTime_239Cycles5);  // 恢复通道0
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    cad_noy++;  // 缓冲区索引递增
    if(cad_noy > 15) cad_noy = 0;  // 循环覆盖
    cad_nox = 0;  // 通道索引重置
  }
  cad_nox++;  // 通道索引递增
}




设计思路:

轮询采集:通过cad_nox(0-2)循环切换ADC通道(0→1→4),每20ms触发一次(由定时器中断调用该函数),实现3路信号的周期性采集。
循环缓冲区:cadtemp1/cadtemp2/cadtemp3各有16个元素,cadnoy循环递增,实现数据的滑动存储,支持后续平均滤波(如取16次数据平均值)。
应用场景:通道0/1/4分别对应电池总电压、充电电流、放电电流的采样电路,通过轮询实现多参数同时监测。

(二)EXTI模块:exti.c/.h——中断处理
EXTI模块负责处理外部中断(如AFE芯片的报警信号、唤醒信号),核心代码解析如下:

1. 外部中断初始化`EXTIX_Init`
void EXTIX_Init(void)
{
  EXTI_InitTypeDef EXTI_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;

  // 使能AFIO时钟(外部中断需要AFIO复用功能)
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

  // 初始化GPIOB0为上拉输入(中断触发引脚)
  alrm_Init();

  // 映射GPIOB0到EXTI_Line0
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);

  // 配置EXTI_Line0
  EXTI_InitStructure.EXTI_Line = EXTI_Line0;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;  // 中断模式(非事件模式)
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  // 下降沿触发
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;  // 使能该中断线
  EXTI_Init(&EXTI_InitStructure);

  // 配置NVIC(嵌套向量中断控制器)
  NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;  // EXTI0中断通道
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;  // 抢占优先级2
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;  // 子优先级2
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  // 使能该中断通道
  NVIC_Init(&NVIC_InitStructure);
}


关键代码解析:

GPIOEXTILineConfig:将GPIO引脚与EXTI中断线绑定(GPIOB0→EXTILine0),STM32的EXTI线与GPIO引脚有固定映射关系(如EXTI_Line0对应所有GPIOx的Pin0)。
中断触发方式:EXTITriggerFalling(下降沿触发),适合检测外部设备的报警信号(如AFE芯片输出低电平报警)。
NVIC配置:PreemptionPriority(抢占优先级)决定中断是否能打断其他中断;SubPriority(子优先级)决定同抢占优先级下的响应顺序,此处配置为中等优先级,避免影响核心中断(如SysTick)。
2. 中断服务函数`EXTI0_IRQHandler`
void EXTI0_IRQHandler(void)
{
  // 若系统处于睡眠/空闲模式,唤醒系统
  if(bSleep** || bIdle**)
  {
    // 唤醒相关操作(如重新初始化时钟)
  }
  bAFE** = 1;  // 设置AFE报警标志位,通知上层处理

  // 清除中断挂起位(必须清除,否则会重复触发中断)
  EXTI_ClearITPendingBit(EXTI_Line0);
}



代码逻辑:

中断响应:当GPIOB0检测到下降沿时,CPU跳转到该函数,设置bAFE**标志位(上层代码会轮询该标志,执行保护动作)。
清除挂起位:EXTI_ClearITPendingBit清除EXTI的中断挂起标志,若不清除,中断控制器会认为中断未处理,导致函数重复执行。
(三)CPU_TX模块:参数管理与Flash存储
CPU_TX模块是方案的“大脑”,负责管理电池保护参数(阈值、校准值),并通过Flash实现参数掉电保存,核心代码解析如下:

1. 核心数据结构`MY_CPU`
typedef struct
{
  u16 data;                  // 数据标识(如0xDC55,用于参数合法性校验)
  u16 use_setmaH;             // 电流上限阈值(高)
  u16 use_setmaL;             // 电流下限阈值(低)
  u16 adc_maH;                // 电流采集校准值(高)
  u16 adc_maL;                // 电流采集校准值(低)
  u16 use_setINVH;            // 输入电压上限阈值(高)
  u16 use_setINVL;            // 输入电压下限阈值(低)
  u16 adc_INVH;               // 输入电压采集校准值(高)
  u16 adc_INVL;               // 输入电压采集校准值(低)
  u16 use_OUTVH;               // 输出电压上限阈值(高)
  u16 use_OUTVL;               // 输出电压下限阈值(低)
  u16 adc_OUTVH;               // 输出电压采集校准值(高)
  u16 adc_OUTVL;               // 输出电压采集校准值(低)
  u16 ctr_dianliu;            // 电流控制值(如充电电流限制)
} MY_CPU;

MY_CPU my_cpuset;  // 全局变量,存储当前参数配置



数据作用:

阈值参数(usesetmaH/usesetINVH等):定义电池的保护边界(如use_setINVH=240对应输入电压上限24V)。
校准参数(adcmaH/adcINVH等):补偿ADC采集误差(如ADC采集值与实际电流的比例系数)。
标识位(data=0xDC55):用于Flash读取时的合法性校验,避免读取无效数据。
2. Flash参数读取`read_sensor_set`
void read_sensor_set(void)
{
  u16 save_data[30];
  // 从Flash指定地址(FLASH_SAVE_ADDR)读取20个半字(40字节)
  STMFLASH_Read(FLASH_SAVE_ADDR, (u16*)save_data, 20);

  // 校验数据合法性(通过data字段是否为0xDC55)
  if(save_data[0] == 0xDC55)
  {
    // 从Flash数据加载到my_cpuset
    my_cpuset.data = save_data[0];
    my_cpuset.use_setmaH = save_data[1];
    my_cpuset.use_setmaL = save_data[2];
    // ... 省略其他参数加载 ...
    // 加载传感器ID
    seneor1.sensorID[0] = save_data[14];
    seneor1.sensorID[1] = save_data[15];
    // ... 省略其他ID加载 ...
  }
  else
  {
    // 数据无效,加载默认参数
    save_data[0] = my_cpuset.data = 0xDC55;
    save_data[1] = my_cpuset.use_setmaH = 265;  // 默认电流上限阈值
    save_data[2] = my_cpuset.use_setmaL = 72;   // 默认电流下限阈值
    // ... 省略其他默认参数设置 ...
    // 将默认参数写入Flash,下次上电加载
    STMFLASH_Write(FLASH_SAVE_ADDR, (u16*)save_data, 20);
  }
}


代码逻辑:

数据校验:通过save_data[0] == 0xDC55判断Flash中是否存储了有效参数,避免因Flash擦除或损坏导致的参数错误。
默认参数:若Flash数据无效,加载默认参数(如电流上限265mA),并写入Flash,确保系统正常运行。
Flash操作:调用STMFLASHRead/STMFLASHWrite(封装在stmflash.c中)实现数据读写,后续解析该函数。
3. Flash底层操作`STMFLASH_Write`
void STMFLASH_Write(u32 WriteAddr, u16 *pBuffer, u16 NumToWrite)
{
  u32 secpos;        // 扇区地址
  u16 secoff;        // 扇区内偏移(半字单位)
  u16 secremain;     // 扇区内剩余半字数
  u16 i;
  u32 offaddr;      // 相对于Flash基地址的偏移

  // 检查地址合法性(Flash基地址0x08000000,大小STM32_FLASH_SIZE)
  if(WriteAddr < STM32_FLASH_BASE || WriteAddr >= (STM32_FLASH_BASE + 1024 * STM32_FLASH_SIZE))
    return;

  FLASH_Unlock();  // 解锁Flash(STM32 Flash默认锁定,防止误写)
  offaddr = WriteAddr - STM32_FLASH_BASE;  // 计算偏移
  secpos = offaddr / STM_SECTOR_SIZE;      // 计算扇区地址(STM_SECTOR_SIZE=1024字节)
  secoff = (offaddr % STM_SECTOR_SIZE) / 2; // 计算扇区内偏移(半字单位,1半字=2字节)
  secremain = STM_SECTOR_SIZE / 2 - secoff; // 扇区内剩余半字数

  // 若写入数据超过当前扇区剩余空间,分多次写入
  if(NumToWrite <= secremain)
    secremain = NumToWrite;

  while(1)
  {
    // 读取当前扇区所有数据到缓冲区(避免擦除扇区导致数据丢失)
    STMFLASH_Read(secpos * STM_SECTOR_SIZE + STM32_FLASH_BASE, STMFLASH_BUF, STM_SECTOR_SIZE / 2);

    // 检查扇区内是否有非0xFFFF的数据(0xFFFF表示未擦除)
    for(i = 0; i < secremain; i++)
    {
      if(STMFLASH_BUF[secoff + i] != 0xFFFF)
        break;  // 需要擦除扇区
    }

    if(i < secremain)
    {
      // 擦除当前扇区(Flash写入前必须擦除,擦除单位为扇区)
      FLASH_ErasePage(secpos * STM_SECTOR_SIZE + STM32_FLASH_BASE);

      // 将待写入数据复制到缓冲区
      for(i = 0; i < secremain; i++)
        STMFLASH_BUF[secoff + i] = pBuffer;

      // 写入整个扇区数据
      STMFLASH_Write_NoCheck(secpos * STM_SECTOR_SIZE + STM32_FLASH_BASE, STMFLASH_BUF, STM_SECTOR_SIZE / 2);
    }
    else
    {
      // 扇区已擦除,直接写入数据
      STMFLASH_Write_NoCheck(WriteAddr, pBuffer, secremain);
    }

    // 判断是否写入完成
    if(NumToWrite == secremain)
      break;
    else
    {
      // 未完成,处理下一个扇区
      secpos++;        // 扇区地址递增
      secoff = 0;      // 偏移重置为0
      pBuffer += secremain;  // 数据指针偏移
      WriteAddr += secremain * 2;  // 写入地址偏移(半字=2字节)
      NumToWrite -= secremain;    // 剩余数据量递减
      // 计算下一个扇区的剩余空间
      if(NumToWrite > STM_SECTOR_SIZE / 2)
        secremain = STM_SECTOR_SIZE / 2;
      else
        secremain = NumToWrite;
    }
  }

  FLASH_Lock();  // 锁定Flash,防止误写
}



Flash操作核心逻辑:

解锁/锁定:FLASHUnlock/FLASHLock通过写入特定密钥(0x45670123、0xCDEF89AB)解锁,防止意外写入导致参数损坏。
扇区擦除:STM32 Flash的最小擦除单位为扇区(1024字节),写入前需先擦除(擦除后数据为0xFFFF),避免写入错误。
数据保护:通过STMFLASH_BUF缓冲区保存扇区原有数据,仅更新需要修改的部分,再重新写入整个扇区,避免擦除导致其他数据丢失(如其他配置参数)。
跨扇区处理:若写入数据超过当前扇区剩余空间,自动切换到下一个扇区,支持任意长度的数据写入(只要不超过Flash总容量)。
四、代码功能串联与应用场景
(一)核心功能流程
结合上述代码解析,中颖SH367309方案的核心功能流程可总结为:

上电初始化:启动文件执行ResetHandler→调用SystemInit配置时钟→进入main函数→初始化ADC、EXTI、KEY、LCD、Flash模块→readsensor_set加载参数。
参数采集:定时器每20ms调用TIMEtoCAdc→轮询采集ADC通道0/1/4(电压、电流)→数据存入cadtemp1/cadtemp2/cad_temp3缓冲区。
保护判断:主循环中读取采集数据→与mycpuset中的阈值参数(usesetmaH/use_setINVH等)对比→若触发保护条件(如电压超过上限),设置保护标志位→控制硬件关断充放电回路。
中断响应:AFE芯片报警触发EXTI0中断→EXTI0_IRQHandler设置bAFE**→主循环检测到标志位,执行紧急保护动作。
参数配置:用户通过KEY模块输入参数→修改mycpuset结构体→调用saveCPUset写入Flash→下次上电自动加载。
状态显示:LCD模块定期读取mycpuset和采集数据→通过LCDDrawPoint/LCD_ShowChar显示电压、电流、保护状态等信息。
(二)代码设计亮点
分层设计:内核驱动(CORE)、硬件驱动(HARDWARE/ADC/EXTI)、应用逻辑(HARDWARE/CPU_TX)分层隔离,便于维护与扩展(如更换LCD驱动只需修改LCD模块代码)。
兼容性强:支持多编译器、多STM32芯片(HD/MD)、多LCD驱动IC,适配不同硬件方案。
可靠性高:ADC校准、Flash参数校验、多级别保护阈值、中断优先级配置,确保系统稳定运行。
易用性好:提供标准化接口(如GetAdc/saveCPUset),用户无需关注底层细节,可快速二次开发。
五、总结
中颖SH367309锂电池保护板方案代码通过严谨的底层驱动实现、模块化的功能设计、可靠的数据处理逻辑,构建了完整的锂电池保护解决方案。从代码细节看,每个函数、每个寄存器操作均围绕“安全保护”核心需求展开,例如ADC的高精度采集确保保护判断准确,Flash的参数存储确保配置不丢失,EXTI的中断响应确保紧急情况快速处理。



对于开发者而言,理解代码中的内核操作(如堆栈管理、中断控制)、硬件适配(如ADC时钟配置、Flash擦写)、功能逻辑(如参数校准、保护判断),是基于该方案进行二次开发的关键。后续可根据实际需求,扩展通信接口(如UART远程监控)、新增传感器(如温度采集)、优化保护算法(如自适应阈值),进一步提升方案的适用性。









————————————————
版权声明:本文为CSDN博主「Q27699885」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Q27699885/article/details/153964900

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
您需要登录后才可以回帖 登录 | 注册

本版积分规则

105

主题

310

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部