IAP二级启动系统

内存:C/单片机内存管理,仿真keii-CSDN博客

IIC和EEPROM(24c02):12:HAL----I2C_hal i2c-CSDN博客

FLASH(W25Q64):11:STM32---spl通信_根据右边的 spl flash 的连接原理图和下面的读时序图,用你所熟悉的单片机语言,写-CSDN博kuzhan

目标:

项目概述

在实际工作中,终端模块类设备需要远程升级、长期无人值守。为保障安全可靠、灵活升级、防止fash坏块程序采用A&B冗余程序系统设计,采用多种接口二级引导系

并可远程升级。

项目实现

● MCU任意RTOS可选freeTos/ucosi/RTI、PC软件可选QT/Vinform,通讯端口UART+ETH(iwip+tcp)+wifi模组(spi+OTA),三种通讯方式皆要有。

● 串口通讯可采用自定义协议(参考modbus),协议格式包含文字节前音、2字节总长度、N字节数据区、4字节保留、2字节ccc16检验。

● boot程序+app程序、FLASH存储分部及分区、boot区/参数区/运行区/A存储区/B存储区。

● boot程序等待若干秒,等待过程中如果收到上位机指令,跳转到永久等待模式,等待接收上位机下发的app.bin,或其它指令。如果等待超时,根据标志区标志,搬

到运行区,并跳转到运行区执行app。

● boot可为裸机程序。app需要做出tros程序,可做2个打印任务作为最后成功现象观察。

bootloader:

cpp 复制代码
static uint8_t BootLoader_Enter(uint8_t timeout);
static void BootLoader_Info(void);
static void LOAD_A(uint32_t app_addr);
static void BootLoader_Clear(void);
typedef void (*load_a)(void);     //函数指针类型的定义
load_a load_A;

void BootLoader_Brance(void)
{
	if(BootLoader_Enter(30)==0){	
		if(OTA_Info.OTA_flag == OTA_SET_FLAG){       //判断OTA_flag是不是OTA_SET_FLAG定义的值,是的话进入if
			u1_printf("OTA更新\r\n");                //串口0输出信息
		//	BootStaFlag |= UPDATA_A_FLAG;            //置位标志位,表明需要更新A区
			//UpDataA.W25Q64_BlockNB = 0;              //W25Q64_BlockNB等于0,表明是OTA要更新A区		
		}else{                                       //判断OTA_flag是不是OTA_SET_FLAG定义的值,不是的话进入else
			u1_printf("跳转A分区\r\n");              //串口0输出信息
			LOAD_A(0x08020000);                    //跳转到运行区
		}
	}
	u1_printf("进入BootLoader命令行\r\n");
	BootLoader_Info();
}


static uint8_t BootLoader_Enter(uint8_t timeout){
	u1_printf("%ds内,输入小写字母 w ,进入BootLoader命令行\r\n",(timeout*100)/1000);
	while(timeout--){
		delay_ms(100);
		if(USART1_RXBuf[0] == 'w'){
			return 1;                            //进入命令行
		}
	}
	return 0;                                    //不进入命令行
}
static void BootLoader_Info(void){
	u1_printf("\r\n");
	u1_printf("[1]擦除A区\r\n");
	u1_printf("[2]串口IAP下载A区程序\r\n");
	u1_printf("[3]设置OTA版本号\r\n");
	u1_printf("[4]查询OTA版本号\r\n");
	u1_printf("[5]向外部Flash下载程序\r\n");
	u1_printf("[6]使用外部Flash内程序\r\n");
	u1_printf("[7]重启\r\n");
	u1_printf("[8]设置服务器连接信息\r\n");
}



__asm void MSR_SP(uint32_t addr){
	MSR MSP, r0
	BX r14   
}


//跳出boot的时候需要把32复位到初始状态()

// 关键修改:直接操作寄存器复位外设(无库函数依赖)
static void BootLoader_Clear(void)
{
    // 1. 关闭全局中断
    __disable_irq();

    // 2. 复位GPIOA~GPIOD(AHB1总线外设)
    // 步骤:置位复位位 → 延时 → 清除复位位(完成复位)
    RCC->AHB1RSTR |= (RCC_AHB1RSTR_GPIOARST |  // GPIOA复位
                      RCC_AHB1RSTR_GPIOBRST |  // GPIOB复位
                      RCC_AHB1RSTR_GPIOCRST |  // GPIOC复位
                      RCC_AHB1RSTR_GPIODRST);  // GPIOD复位
    delay_ms(1);  // 用自定义延时,避免依赖HAL_Delay
    RCC->AHB1RSTR &= ~(RCC_AHB1RSTR_GPIOARST |  // 解除GPIOA复位
                       RCC_AHB1RSTR_GPIOBRST |  // 解除GPIOB复位
                       RCC_AHB1RSTR_GPIOCRST |  // 解除GPIOC复位
                       RCC_AHB1RSTR_GPIODRST);  // 解除GPIOD复位

    // 3. 复位USART1(APB2总线外设)
    RCC->APB2RSTR |= RCC_APB2RSTR_USART1RST;  // USART1复位
    delay_ms(1);
    RCC->APB2RSTR &= ~RCC_APB2RSTR_USART1RST;  // 解除USART1复位

    // 4. 复位SPI1(APB2总线外设)
    RCC->APB2RSTR |= RCC_APB2RSTR_SPI1RST;  // SPI1复位
    delay_ms(1);
    RCC->APB2RSTR &= ~RCC_APB2RSTR_SPI1RST;  // 解除SPI1复位

    // 5. 恢复时钟默认状态(HSI 16MHz)
    RCC->CR |= RCC_CR_HSION;  // 使能HSI
    while((RCC->CR & RCC_CR_HSIRDY) == 0);  // 等待HSI就绪
    RCC->CFGR = 0x00000000;  // 时钟配置复位(系统时钟=HSI)
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI);  // 等待HSI生效

    // 6. 清除所有中断(禁用+清除挂起)
    for(uint8_t i = 0; i < 8; i++){
        NVIC->ICER[i] = 0xFFFFFFFF;  // 禁用所有中断
        NVIC->ICPR[i] = 0xFFFFFFFF;  // 清除所有挂起中断
    }

    // 7. 使能全局中断
    __enable_irq();
}



static void LOAD_A(uint32_t app_addr){  // app_addr:APP在Flash的起始地址(如0x08008000)
    uint32_t app_stack_top;  // APP栈顶地址(向量表第1个元素,必须在IRAM1或IRAM2)
    uint32_t app_entry;      // APP入口地址(向量表第2个元素,必须在Flash)

    // 1. 校验APP起始地址合法性(Flash范围,如STM32F4的Flash通常是0x08000000~0x080FFFFF)
    if(app_addr < 0x08000000 || app_addr > 0x080FFFFF){
        u1_printf("跳转失败:APP地址非法(不在Flash范围)\r\n");
        return;
    }

    // 2. 读取APP向量表的前2个核心元素
    app_stack_top = *(volatile uint32_t*)app_addr;    // 向量表第1位:栈顶地址(SRAM) SP
    app_entry     = *(volatile uint32_t*)(app_addr+4);// 向量表第2位:复位入口地址(Flash) PC

    // 3. 校验栈顶地址合法性(必须在IRAM1或IRAM2范围内,且8字节对齐)
    // 3.1 检查是否在IRAM1或IRAM2中
    if( !((app_stack_top >= IRAM1_START && app_stack_top <= IRAM1_END) || 
          (app_stack_top >= IRAM2_START && app_stack_top <= IRAM2_END)) ){
        u1_printf("跳转失败:栈顶地址不在SRAM分区内\r\n");
        u1_printf("合法范围:IRAM1(0x%X~0x%X) 或 IRAM2(0x%X~0x%X)\r\n",
                  IRAM1_START, IRAM1_END, IRAM2_START, IRAM2_END);
        return;
    }
    // 3.2 检查8字节对齐(Cortex-M架构强制要求,否则硬件异常)
    if((app_stack_top & 0x07) != 0){
        u1_printf("跳转失败:栈顶地址未8字节对齐(地址:0x%X)\r\n", app_stack_top);
        return;
    }

    // 4. 校验APP入口地址合法性(必须在Flash范围内,且是可执行代码地址)
    if(app_entry < 0x08000000 || app_entry > 0x080FFFFF){
        u1_printf("跳转失败:APP入口地址非法(不在Flash范围)\r\n");
        return;
    }

    load_A = (load_a)app_entry;  
    BootLoader_Clear();
    MSR_SP(app_stack_top);
    load_A();

}

因为他的boot对于其他来说都是单向的所以只需要PC,SP指针

PC 是 CPU 中最关键的寄存器之一,它的值始终指向 **"当前正在执行的指令的下一条指令的存储地址"**。 R15

栈(Stack)是内存中一块连续的区域,用于临时存储数据(如函数参数、返回地址、局部变量、上下文信息等),SP 寄存器的值始终指向栈的 "当前顶部"(栈顶)。R13
因为栈在SARM中在LOAD_A函数中需要判断栈顶指针有没有在合法sarm中

FLASH规划

我们使用的是STM32F407ZGT6一共有1M的FALSH非常大,所以不需要我们外挂FLASH。

STM32F407 系列的 Flash 总容量为 1MB(1024KB),硬件上固定分为2 个 Bank ,每个 Bank 的容量为512KB,划分规则由地址范围严格界定:

  • Bank 1 :地址范围 0x08000000 ~ 0x0807FFFF(共 512KB)
  • Bank 2 :地址范围 0x08080000 ~ 0x080FFFFF(共 512KB)
扇区编号 起始地址 结束地址 扇区大小 所属 Bank 备注
0 0x08000000 0x08003FFF 16KB Bank 1 小扇区
1 0x08004000 0x08007FFF 16KB Bank 1 小扇区
2 0x08008000 0x0800BFFF 16KB Bank 1 小扇区
3 0x0800C000 0x0800FFFF 16KB Bank 1 小扇区
4 0x08010000 0x0801FFFF 64KB Bank 1 中扇区
5 0x08020000 0x0803FFFF 128KB Bank 1 大扇区
6 0x08040000 0x0805FFFF 128KB Bank 1 大扇区
7 0x08060000 0x0807FFFF 128KB Bank 1 大扇区(Bank 1 结束)
8 0x08080000 0x0809FFFF 128KB Bank 2 大扇区(Bank 2 开始)
9 0x080A0000 0x080BFFFF 128KB Bank 2 大扇区
10 0x080C0000 0x080DFFFF 128KB Bank 2 大扇区
11 0x080E0000 0x080FFFFF 128KB Bank 2 大扇区(总结束地址)

我们的划分的区域:

  • 擦除必须以扇区为单位(或者全部擦除) :任何扇区中的数据,若要修改其中一部分,必须先完整擦除整个扇区 (擦除后扇区所有地址值为0xFFFFFFFF),不能单独擦除扇区中的某一段。
  • 编程(写入)需基于擦除后的区域 :只能向已擦除(值为0xFFFFFFFF)的地址写入数据,无法直接覆盖非0xFFFFFFFF的区域(即不能 "局部改写" 未擦除的区域)。
区域名称 包含扇区 扇区数量 单扇区大小 区域总大小 起始地址 结束地址 所属 Bank 说明
Boot 区 扇区 0~3 4 个 16KB 64KB 0x08000000 0x0800FFFF Bank1 启动引导程序
参数区 扇区 4 1 个 64KB 64KB 0x08010000 0x0801FFFF Bank1 OTA 标志、设备参数等
运行区 扇区 5~7 3 个 128KB 384KB 0x08020000 0x0807FFFF Bank1 主应用程序区
APP1 区 扇区 8~9 2 个 128KB 256KB 0x08080000 0x080BFFFF Bank2 备用应用程序区(OTA 升级用)
APP2 区 扇区 10~11 2 个 128KB 256KB 0x080C0000 0x080FFFFF Bank2 备用应用程序区(OTA 升级用)

代码:

cpp 复制代码
// STM32F407ZGT6 Flash扇区编号与地址对应表(关键!)
//FLAHS标志区---#define FLASH_SECTOR_4    4U    // 0x08010000-0x0801FFFF, 64KB

/**
  * @brief  STM32F407擦除Flash(按扇区操作)
  * @param  start_sector: 起始扇区编号(0-11,对应上述宏定义)
  * @param  sector_num: 要擦除的扇区数量(1-12,不超过总扇区数)
  * @note   擦除前需确保扇区未存储运行中的代码
  */
void STM32F4_EraseFlash(uint16_t start_sector, uint16_t sector_num)
{
    FLASH_EraseInitTypeDef flash_erase_init = {0};
    uint32_t error_addr = 0; // 存储擦除错误地址
    HAL_StatusTypeDef status = HAL_OK;

    // 1. 检查参数有效性(扇区编号不越界)
    // 修正后:
	if (start_sector > 11 || (start_sector + sector_num) > 12) return; 
	// 说明:扇区编号最大11,总扇区数12个(0~11),确保不越界

    // 2. Flash解锁
    HAL_FLASH_Unlock();

    // 3. 配置擦除参数
    flash_erase_init.TypeErase = FLASH_TYPEERASE_SECTORS; // 扇区擦除模式
    flash_erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 供电电压3.3V(根据实际调整)
    flash_erase_init.Sector = start_sector; // 起始扇区
    flash_erase_init.NbSectors = sector_num; // 扇区数量

    // 4. 执行擦除(带错误检查)
    status = HAL_FLASHEx_Erase(&flash_erase_init, &error_addr);
    if (status != HAL_OK)
    {
        // 擦除失败处理(可添加日志或指示灯提示)
        uint32_t err_code = HAL_FLASH_GetError();
        (void)err_code; // 避免未使用变量警告,实际可扩展错误处理
    }

    // 5. Flash锁定
    HAL_FLASH_Lock();
}

/**
  * @brief  STM32F407写入Flash(32位字编程) 1字(Word)	=4 字节
  * @param  address: 写入起始地址(必须4字节对齐,且在0x08000000-0x080FFFFF范围内)
  * @param  data: 待写入数据指针(uint32_t类型数组)
  * @param  wnum: 待写入总字节数(必须是4的整数倍,对应wnum/4个32位字)
  */
void STM32F4_WriteFlash(uint32_t address, uint32_t *data, uint32_t wnum)
{
    HAL_StatusTypeDef status = HAL_OK;

    // 1. 参数有效性检查
    if (data == NULL || wnum == 0) return;
    if (address < 0x08000000 || address > 0x080FFFFF) return;
    if ((address % 4) != 0) return; // 地址必须4字节对齐

    // 2. Flash解锁
    HAL_FLASH_Unlock();

    // 3. 循环写入(按32位字编程)
    while (wnum > 0 && address <= 0x080FFFFF)
    {
        status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address, *data);
        if (status == HAL_OK)
        {
            wnum -= 4;      // 减少4字节(1个32位字)
            address += 4;   // 地址偏移4字节
            data++;         // 数据指针偏移
        }
        else
        {
            // 写入失败处理
            uint32_t err_code = HAL_FLASH_GetError();
            (void)err_code;
            break;
        }
    }

    // 4. Flash锁定
    HAL_FLASH_Lock();
}

//
// 读取Flash数据(配套使用)
void STM32F4_ReadFlash(uint32_t address, uint32_t *data, uint32_t rnum)
{
    if (data == NULL || rnum == 0) return;
    if (address < 0x08000000 || address > 0x080FFFFF) return;

    while (rnum > 0 && address <= 0x080FFFFF)
    {
        *data = *(volatile uint32_t *)address;
        rnum -= 4;
        address += 4;
        data++;
    }
}


void ReadOTAInfo()
 {
	memset(&OTA_Info,0,OTA_INFOCB_SIZE);
 
	STM32F4_ReadFlash(0x08010000, (uint32_t *)&OTA_Info, OTA_INFOCB_SIZE);

}

uint32_t=4个字节 一个字=4个字节

为什么需要按32写入?

一、Flash 硬件控制器的编程单位限制

STM32 的 Flash 存储器控制器(FPEC,Flash Program/Erase Controller)对编程操作的最小单位有明确规定:

  • 支持的编程模式包括:字节(8 位)、半字(16 位)、字(32 位) ,但实际硬件层面,32 位字编程是最基础、最稳定的模式
  • 底层硬件设计中,Flash 的存储阵列通常按 32 位宽度组织(与 Cortex-M4 内核的 32 位架构匹配),一次编程操作可以直接覆盖一个 32 位存储单元,而字节 / 半字编程需要控制器内部做额外的 "拼接" 处理(先读出整个 32 位单元,修改对应字节 / 半字,再重新写入),反而增加了复杂度和出错风险。

二、地址对齐要求

Flash 编程对地址的对齐性有严格要求:

  • 32 位字编程(FLASH_TYPEPROGRAM_WORD)要求地址必须是4 字节对齐 (即地址的最后 2 位必须为 0,如0x080200000x08020004);
  • 16 位半字编程(FLASH_TYPEPROGRAM_HALFWORD)要求地址2 字节对齐
  • 8 位字节编程(部分型号支持)要求地址1 字节对齐

但在实际开发中,32 位字编程是最优选择

  • 代码中address % 4 != 0的检查,正是为了确保地址符合 32 位编程的对齐要求,否则会触发FLASH_ERROR_PG(编程错误);
  • 若强行使用字节 / 半字编程,不仅需要额外处理对齐问题,还可能因硬件时序不匹配导致数据写入错误(尤其是高频时钟下)

USART接收数据设

boot:使用串口+DMA空闲中断

开辟一块栈(USART使用DMA接收,DMA把UASRT的数据转运的地方)----USART_RXBuf[USART1_RXDataLen]

开启一块数组里面放入start、end指针对,用于入栈和出栈的操作。和每当数据来的时候URxDataPtr[X]->END放入标记,然后移动到一下数据标记(URxDataPtr++),每次标记的时候需要和end对比防止越界。对比剩下的内存能不能放下单次接收的最大数据,不行回卷。

出栈:如果入栈的数组URxDataOUT的URxDataIN和不一样表示有数据

空闲中断:USART起始位-->标志一个数据帧的开始,固定为低电平

停止位:用于数据帧间隔,固定为高电平

USART 的空闲中断(IDLE)触发机制是:当串口接收数据过程中,总线上连续出现一个完整的字节传输时间(由波特率决定,如 115200 波特率下约 8.68μs)的高电平(空闲状态)时,USART 的状态寄存器(SR)会置位 IDLE 标志,进而触发中断。这一特性恰好用于判断 "一帧数据结束"------ 无论 DMA 是否还在传输(只要总线空闲达到阈值),即可中断即可会被唤醒处理当前帧。

cpp 复制代码
#define U1_RX_MAX  256  //单次接收最大数据
#define NUM        10
#define USART1_TXDataLen 2048
#define USART1_RXDataLen  (U1_RX_MAX*NUM)

extern uint8_t USART1_TXBuf[USART1_TXDataLen];
extern uint8_t USART1_RXBuf[USART1_RXDataLen];

typedef struct{
    uint8_t *start;  // 缓冲区起始地址指针(指向数据块的第一个字节)
    uint8_t *end;    // 缓冲区结束地址指针(指向数据块的最后一个字节的下一位,用于判断边界)
}UCB_URxBuffptr;

typedef struct{
    uint16_t URxCounter;               // 接收计数器:可能用于记录当前有效缓冲区数量,或累计接收字节数
    UCB_URxBuffptr URxDataPtr[NUM];    // 接收缓冲区数组:存储NUM个缓冲区的起止指针(NUM为宏定义,代表缓冲区总数)
                                       // 作用:管理多个独立的接收缓冲区,实现"双缓冲"或"多缓冲"机制
    UCB_URxBuffptr *URxDataIN;         // 入队指针:指向当前可写入数据的缓冲区(新接收的数据写入该缓冲区)
    UCB_URxBuffptr *URxDataOUT;        // 出队指针:指向当前可读取数据的缓冲区(应用程序从该缓冲区读取数据)
    UCB_URxBuffptr *URxDataEND;        // 结束指针:指向缓冲区数组的末尾(用于判断缓冲区循环边界,避免越界)
}UCB_CB;

核心实现代码:

cpp 复制代码
static void U1RX_PtrInit(void)
{
		U1CB.URxDataIN=&U1CB.URxDataPtr[0];
		U1CB.URxDataOUT=&U1CB.URxDataPtr[0];
		U1CB.URxDataEND=&U1CB.URxDataPtr[NUM-1];
		U1CB.URxDataIN->start=USART1_RXBuf;
		U1CB.URxCounter=0;
}
void MX_USART1_UART_Init(void)
{
    .....正常配置就ok
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断
    /*
    DMA数据转运:每收到一个字节,自动转运到USART1_RXBuf地址中去
    当转运的数据=U1_RX_MAX+1的时候触发DMA完成中断
    */
	HAL_UART_Receive_DMA(&huart1,USART1_RXBuf,U1_RX_MAX+1);
	U1RX_PtrInit();

}

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

	if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) {
		// 1. 清除空闲中断标志(必须先读SR寄存器,再读DR寄存器,HAL库内部已实现)
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);
		
		// 2. 中止当前DMA接收(停止当前帧的传输,以便计算接收长度)
		HAL_UART_DMAStop(&huart1);
		
		// 3. 计算当前接收长度:总配置长度 - DMA剩余未传输的长度
		// U1_RX_MAX+1是DMA初始配置的接收长度,__HAL_DMA_GET_COUNTER获取剩余未传字节数
		U1CB.URxCounter += (U1_RX_MAX + 1) - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 单独存储单帧长度
		
		// 4. 记录当前帧的结束位置(环形缓冲区管理)
		U1CB.URxDataIN->end = &USART1_RXBuf[U1CB.URxCounter - 1];
		U1CB.URxDataIN++;  // 移动到下一个帧指针
		if (U1CB.URxDataIN == U1CB.URxDataEND) {
			U1CB.URxDataIN = &U1CB.URxDataPtr[0];  // 环形回卷
		}
		
		// 5. 配置下一次DMA接收的起始地址(避免缓冲区溢出)
		if (USART1_RXDataLen - U1CB.URxCounter >= U1_RX_MAX) {
			// 剩余空间足够,从当前位置继续接收
			U1CB.URxDataIN->start = &USART1_RXBuf[U1CB.URxCounter];
		} else {
			// 剩余空间不足,回卷到缓冲区起始位置
			U1CB.URxDataIN->start = USART1_RXBuf;
			U1CB.URxCounter = 0;
		}
		
		HAL_UART_Receive_DMA(&huart1, U1CB.URxDataIN->start, U1_RX_MAX + 1);
	}
	
}

while (1)
	{
			/* USER CODE END WHILE */
	/* USER CODE BEGIN 3 */
	if(U1CB.URxDataOUT != U1CB.URxDataIN){
u1_printf("本次接收了%d字节数据\r\n",U1CB.URxDataOUT->end - U1CB.URxDataOUT->start + 1);
for(i=0;i<U1CB.URxDataOUT->end - U1CB.URxDataOUT->start + 1;i++) u1_printf("%c",U1CB.URxDataOUT->start[i]);
	u1_printf("\r\n\r\n");
				
U1CB.URxDataOUT++;
	if(U1CB.URxDataOUT==U1CB.URxDataEND){
		U1CB.URxDataOUT = &U1CB.URxDataPtr[0];
		}
	}
}

串口iap下载:

使用串口的Xmode协议实现,使用bin文件下载到FLASH中:

XMODE:

https://blog.csdn.net/m0_74739916/article/details/154140691?sharetype=blogdetail&sharerId=154140691&sharerefer=PC&sharesource=m0_74739916&spm=1011.2480.3001.8118https://blog.csdn.net/m0_74739916/article/details/154140691?sharetype=blogdetail&sharerId=154140691&sharerefer=PC&sharesource=m0_74739916&spm=1011.2480.3001.8118

cpp 复制代码
int main(void)
{
    。。。。。
	ReadOTAInfo();

	BootLoader_Brance();

	while (1)
	{
   
	 Boot_Usart_Reception();
	CopyToRunArea() ;	
 
  }





void BootLoader_Brance(void)
{
	
	if(BootLoader_Enter(30)==0){	
		if(OTA_Info.OTA_flag == OTA_SET_FLAG){       //判断OTA_flag是不是OTA_SET_FLAG定义的值,是的话进入if
			u1_printf("OTA更新\r\n");                //串口0输出信息
			//BootStaFlag |= UPDATA_A_FLAG;            //置位标志位,表明需要更新A区
			UpDataA.W25Q64_BlockNB = 0;              //W25Q64_BlockNB等于0,表明是OTA要更新A区		
		}else{                                       //判断OTA_flag是不是OTA_SET_FLAG定义的值,不是的话进入else
			u1_printf("跳转运行区\r\n");              //串口0输出信息
			CopyToRunArea();
			LOAD_A(STM32_RUN_SADDR);                    //跳转到运行区
		}
	}
	u1_printf("进入BootLoader命令行\r\n");
	BootLoader_Info();
}


static uint8_t BootLoader_Enter(uint8_t timeout){
	u1_printf("%ds内,输入小写字母 w ,进入BootLoader命令行\r\n",(timeout/10));
	while(timeout--){
	delay_ms(8000);
		if(USART1_RXBuf[0] == 'w'){
			return 1;                            //进入命令行
		}
	}
	return 0;                                    //不进入命令行
}
static void BootLoader_Info(void){
	u1_printf("\r\n");
	u1_printf("[0]擦除运行区\r\n");
	u1_printf("[1]擦除A区\r\n");
	u1_printf("[2]擦除B区\r\n");
	
	u1_printf("[3]串口IAP下载A区程序\r\n");
	u1_printf("[4]串口IAP下载B区程序\r\n");
	
	u1_printf("[5]运行A区程序\r\n");
	u1_printf("[6]运行B区程序\r\n");
	u1_printf("[7]重启\r\n");

}


/*-------------------------------------------------*/
/*函数名:BootLoader处理串口数据                   */
/*参  数:data:数据指针      datalen:数据长度    */
/*返回值:无                                       */
/*-------------------------------------------------*/
void BootLoader_Event(uint8_t *data, uint16_t datalen)
{
	int temp,i;                                                                            //temp用于版本号sscanf判断格式    i用于for循环

	/*--------------------------------------------------*/
	/*          没有任何事件,判断顶层命令              */
	/*--------------------------------------------------*/

	if(BootStaFlag == 0){                                                                  //如果BootStaFlag等于0,没有任何事件,进入if,判断是哪个命令
		
		if((datalen==1)&&(data[0]=='0')){                                                  //如果数据长度1字节 且 是字符 1
			u1_printf("擦除运行区\r\n");                                                      //串口0输出信息                                                   
			STM32F4_EraseFlash(STM32_RUN_START_SECTOR,STM32_RUN_SECTOR_NUM);                            //擦除A分区占用的扇区
		}
		else if((datalen==1)&&(data[0]=='1')){                                                  //如果数据长度1字节 且 是字符 1
			u1_printf("擦除A区\r\n");                                                      //串口0输出信息                                                   
			STM32F4_EraseFlash(8,2);                            //擦除A分区占用的扇区
		}
		else if((datalen==1)&&(data[0]=='2')){                                                  //如果数据长度1字节 且 是字符 1
			u1_printf("擦除B区\r\n");                                                      //串口0输出信息                                                   
			STM32F4_EraseFlash(10,2);                            //擦除B分区占用的扇区
		}
		else if((datalen==1)&&(data[0]=='3')){                                             //如果数据长度1字节 且 是字符 2
			u1_printf("通过Xmodem协议,串口IAP下载A区程序,请使用bin格式文件\r\n");        //串口0输出信息 
 
			STM32F4_EraseFlash(STM32_APP1_START_SECTOR,STM32_APP1_SECTOR_NUM);
			BootStaFlag |= (AIAP_XMODEMC_FLAG|AIAP_XMODEMD_FLAG);                            //置位 IAP_XMODEMC_FLAG 和 IAP_XMODEMD_FLAG 标志位
			UpDataA.XmodemTimer = 0;                                                       //Xmodem发送大写C 间隔时间变量清零
			UpDataA.XmodemNB= 0; 
                                                          //保持接收Xmodem协议数据包个数的变量清零
		}
		else if((datalen==1)&&(data[0]=='4')){                                             //如果数据长度1字节 且 是字符 2
			u1_printf("通过Xmodem协议,串口IAP下载B区程序,请使用bin格式文件\r\n");        //串口0输出信息 
			STM32F4_EraseFlash(10,2);                           
			BootStaFlag |= (BIAP_XMODEMC_FLAG|BIAP_XMODEMD_FLAG);                            //置位 IAP_XMODEMC_FLAG 和 IAP_XMODEMD_FLAG 标志位
			UpDataB.XmodemTimer = 0;                                                       //Xmodem发送大写C 间隔时间变量清零
			UpDataB.XmodemNB= 0;                                                           //保持接收Xmodem协议数据包个数的变量清零
		}

		
		else if((datalen==1)&&(data[0]=='5')){                                             //如果数据长度1字节 且 是字符 3
	     u1_printf("准备运行A程序\r\n");                                            //串口0输出信息
				OTA_Info.UpData_Flag=UPDATA_FROM_A_FLAG;                                               //置位 SET_VERSION_FLAG 
			
		}
		else if((datalen==1)&&(data[0]=='6')){                                             //如果数据长度1字节 且 是字符 4
				OTA_Info.UpData_Flag=UPDATA_FROM_B_FLAG;
				u1_printf("准备运行B程序\r\n");                                                     //串口0输出信息
					WriteOTAInfo(); 																										//串口输出命令行信息      
		}
//		else if((datalen==1)&&(data[0]=='5')){                                             //如果数据长度1字节 且 是字符 5
//			u0_printf("向外部Flash下载程序,输入需要使用的块编号(1~9)\r\n");             //串口0输出信息
//			BootStaFlag |= CMD_5_FLAG;                                                     //置位 CMD_5_FLAG      
//		}
//		else if((datalen==1)&&(data[0]=='6')){                                             //如果数据长度1字节 且 是字符 6
//			u0_printf("使用外部Flash内的程序,输入需要使用的块编号(1~9)\r\n");           //串口0输出信息
//			BootStaFlag |= CMD_6_FLAG;                                                     //置位 CMD_6_FLAG 
//		}
		else if((datalen==1)&&(data[0]=='7')){                                             //如果数据长度1字节 且 是字符 7
			u1_printf("重启\r\n");                                                         //串口0输出信息
			delay_ms(100);                                                                 //延时
			NVIC_SystemReset(); 	                                                       //重启		
		}
	}
	/*--------------------------------------------------*/
	/*          发生Xmodem A事件,处理该事件              */
	/*--------------------------------------------------*/
		else if (BootStaFlag & AIAP_XMODEMD_FLAG)  // Xmodem协议接收数据标志
		{
				// 1. 处理标准数据包(133字节:帧头0x01+包号+反包号+128字节数据+2字节CRC)
				if ((datalen == 133) && (data[0] == 0x01))
				{
						BootStaFlag &= ~AIAP_XMODEMC_FLAG;  // 已收到数据包,停止发送'C'(Xmodem握手信号)
						
						// 计算128字节数据的CRC
						UpDataA.XmodemCRC = Xmodem_CRC16(&data[3], 128);
						
						// 校验CRC(接收的CRC:高8位data[131],低8位data[132])
						if (UpDataA.XmodemCRC == (data[131] << 8 | data[132]))  // 用<<8替代*256,更高效
						{
								UpDataA.XmodemNB++;  // 接收包计数+1
								
								// 暂存数据到缓冲区:按"包号%8"计算偏移(8包=1页)
								memcpy(
										&UpDataA.Updatabuff[((UpDataA.XmodemNB - 1) % (STM32_PAGE_SIZE / 128)) * 128],
										&data[3],  // 有效数据从第3字节开始(跳过帧头、包号、反包号)
										128        // 每次接收128字节
								);
								
								// 当接收满8包(1024字节=1页),写入STM32内部Flash
								if ((UpDataA.XmodemNB % (STM32_PAGE_SIZE / 128)) == 0)
								{
										// 计算写入地址:A区起始地址 + 已完成的页数×页大小
										uint32_t write_addr = STM32_APP1_SADDR + 
																				 ((UpDataA.XmodemNB / (STM32_PAGE_SIZE / 128)) - 1) * STM32_PAGE_SIZE;
										
										// 写入1页数据(1024字节,4的整数倍,符合STM32要求)
										STM32F4_WriteFlash(write_addr, (uint32_t *)UpDataA.Updatabuff, STM32_PAGE_SIZE);
								}
								
								u1_printf("\x06");  // CRC正确,返回ACK
						}
						else
						{
								u1_printf("\x15");  // CRC错误,返回NAK(要求重发)
						}
				}
				
				// 2. 处理结束包(EOT:1字节0x04,表示数据传输完成)
				if ((datalen == 1) && (data[0] == 0x04))
				{
						u1_printf("\x06");  // 收到EOT,返回ACK
						
						// 处理不足1页的剩余数据(若有)
						if ((UpDataA.XmodemNB % (STM32_PAGE_SIZE / 128)) != 0)
						{
								// 计算剩余数据长度(包数×128字节)
								uint32_t remain_len = (UpDataA.XmodemNB % (STM32_PAGE_SIZE / 128)) * 128;
								
								// 补全长度为4的整数倍(STM32 Flash写入要求)
								if (remain_len % 4 != 0)
								{
										remain_len += (4 - remain_len % 4);
								}
								
								// 计算写入地址:A区起始地址 + 已完成的页数×页大小
								uint32_t write_addr = STM32_APP1_SADDR + 
																		 (UpDataA.XmodemNB / (STM32_PAGE_SIZE / 128)) * STM32_PAGE_SIZE;
								
								// 写入剩余数据
								STM32F4_WriteFlash(write_addr, (uint32_t *)UpDataA.Updatabuff, remain_len);
						}
						
						// 清除Xmodem接收标志
						BootStaFlag &= ~AIAP_XMODEMD_FLAG;
	
//						// 处理命令5启动的Xmodem(保存OTA信息到内部Flash)
//						if (BootStaFlag & CMD5_XMODEM_FLAG)
//						{
							//	BootStaFlag &= ~CMD5_XMODEM_FLAG;  // 清除命令5标志
								// 计算总长度(包数×128字节)并保存到OTA信息
								OTA_Info.UpData_Flag=UPDATA_FROM_A_FLAG;
								UpDataA.W25Q64_BlockNB=1;
								OTA_Info.Firelen[UpDataA.W25Q64_BlockNB] = UpDataA.XmodemNB * 128;
								WriteOTAInfo();  // 写入STM32内部Flash(替代24C02)
								delay_ms(100);
								BootLoader_Info();  // 输出命令行信息
								delay_ms(100);
								NVIC_SystemReset();  // 重启生效

				}
		}
		
			/*--------------------------------------------------*/
	/*          发生Xmodem B事件,处理该事件              */
	/*--------------------------------------------------*/
			// B区:Xmodem协议接收数据(对应命令4:串口IAP下载B区程序)
		else if (BootStaFlag & BIAP_XMODEMD_FLAG)  // 替换:B区Xmodem接收标志
		{
				// 1. 处理标准数据包(133字节:帧头0x01+包号+反包号+128字节数据+2字节CRC)
				if ((datalen == 133) && (data[0] == 0x01))
				{
						BootStaFlag &= ~BIAP_XMODEMC_FLAG;  // 替换:停止发送'C'(B区握手标志)
						
						// 计算128字节数据的CRC(替换:使用B区结构体变量UpDataB)
						UpDataB.XmodemCRC = Xmodem_CRC16(&data[3], 128);
						
						// 校验CRC(接收的CRC:高8位data[131],低8位data[132])
						if (UpDataB.XmodemCRC == (data[131] << 8 | data[132]))
						{
								UpDataB.XmodemNB++;  // 替换:B区接收包计数+1
								
								// 暂存数据到B区缓冲区(替换:使用UpDataB.Updatabuff)
								memcpy(
										&UpDataB.Updatabuff[((UpDataB.XmodemNB - 1) % (STM32_PAGE_SIZE / 128)) * 128],
										&data[3],  // 有效数据起始位置不变
										128        // 每次接收128字节不变
								);
								
								// 接收满8包(1页=1024字节),写入B区Flash
								if ((UpDataB.XmodemNB % (STM32_PAGE_SIZE / 128)) == 0)
								{
										// 替换:写入地址=B区起始地址 + 已完成页数×页大小
										uint32_t write_addr = STM32_APP2_SADDR + 
																				((UpDataB.XmodemNB / (STM32_PAGE_SIZE / 128)) - 1) * STM32_PAGE_SIZE;
										
										// 写入1页数据(替换:使用B区缓冲区UpDataB.Updatabuff)
										STM32F4_WriteFlash(write_addr, (uint32_t *)UpDataB.Updatabuff, STM32_PAGE_SIZE);
								}
								
								u1_printf("\x06");  // CRC正确,返回ACK(不变)
						}
						else
						{
								u1_printf("\x15");  // CRC错误,返回NAK(不变)
						}
				}
				
				// 2. 处理结束包(EOT:1字节0x04,表示数据传输完成)
				if ((datalen == 1) && (data[0] == 0x04))
				{
						u1_printf("\x06");  // 收到EOT,返回ACK(不变)
						
						// 处理不足1页的剩余数据(替换:使用UpDataB变量)
						if ((UpDataB.XmodemNB % (STM32_PAGE_SIZE / 128)) != 0)
						{
								// 计算剩余数据长度(B区接收包数×128字节)
								uint32_t remain_len = (UpDataB.XmodemNB % (STM32_PAGE_SIZE / 128)) * 128;
								
								// 补全为4字节整数倍(STM32 Flash写入要求,不变)
								if (remain_len % 4 != 0)
								{
										remain_len += (4 - remain_len % 4);
								}
								
								// 替换:写入地址=B区起始地址 + 已完成页数×页大小
								uint32_t write_addr = STM32_APP2_SADDR + 
																		(UpDataB.XmodemNB / (STM32_PAGE_SIZE / 128)) * STM32_PAGE_SIZE;
								
								// 写入剩余数据(替换:使用B区缓冲区)
								STM32F4_WriteFlash(write_addr, (uint32_t *)UpDataB.Updatabuff, remain_len);
						}
						
						// 清除B区Xmodem接收标志(替换:清除BIAP_XMODEMD_FLAG)
						BootStaFlag &= ~BIAP_XMODEMD_FLAG;

						// 保存B区OTA更新信息(替换:B区专属标志和索引)
						OTA_Info.UpData_Flag = UPDATA_FROM_B_FLAG;  // 替换:B区更新标志
						UpDataB.W25Q64_BlockNB = 2;                  // 替换:B区对应Firelen索引=2
						OTA_Info.Firelen[UpDataB.W25Q64_BlockNB] = UpDataB.XmodemNB * 128;  // B区固件长度
						WriteOTAInfo();  // 写入参数区(不变)
						delay_ms(100);
						BootLoader_Info();  // 输出命令行(不变)
						delay_ms(100);
						NVIC_SystemReset();  // 重启生效(不变)
				}
		}
//	/*

LWIP以太网实现:

https://blog.csdn.net/m0_74739916/article/details/154571736https://blog.csdn.net/m0_74739916/article/details/154571736LWIP传输需要双方指定协议,我们实验Xmode协议,现在实验python调试,全部搞完了实验QT写出上位机

cpp 复制代码
......................
...../**
 * @brief       lwIP tcp_recv()函数的回调函数
 * @param       arg   : 回调函数传入的参数
 * @param       tpcb  : TCP控制块
 * @param       p     : 网络数据包
 * @param       err   : 错误码
 * @retval      返回错误码
 */
err_t lwip_tcp_client_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {
    uint32_t data_len = 0;
    struct pbuf *q;
    struct tcp_client_struct *es = (struct tcp_client_struct *)arg;
    err_t ret_err = ERR_OK;

    // XMODEM协议常量定义
    #define XMODEM_SOH      0x01    // 128字节数据块帧头
    #define XMODEM_ACK      0x06    // 确认响应
    #define XMODEM_NAK      0x15    // 否定响应(请求重发)
    #define XMODEM_CAN      0x18    // 取消传输
    #define XMODEM_EOT      0x04    // 传输结束标志
    #define XMODEM_BLOCK_SIZE  128  // 标准数据块大小(128字节)
    #define XMODEM_FRAME_LEN   133  // 帧长度:1(SOH) + 1(块号) + 1(反块号) + 128(数据) + 2(CRC)

    LWIP_ASSERT("arg != NULL", arg != NULL);

    if (p == NULL) { // 连接关闭
        es->state = ES_TCPCLIENT_CLOSING;
        es->p = p;
        g_lwip_send_flag &= ~(1 << 5);
        u1_printf("TCP连接关闭\r\n");
    }
    else if (err != ERR_OK) { // 接收错误
        if (p) pbuf_free(p);
        ret_err = err;
     //   u1_printf("TCP接收错误: %d\r\n", err);
    }
    else if (es->state == ES_TCPCLIENT_CONNECTED) { // 连接状态
        // 拷贝pbuf数据到全局接收缓冲区
        memset(g_lwip_demo_recvbuf, 0, LWIP_DEMO_RX_BUFSIZE);
        for (q = p; q != NULL; q = q->next) {
            uint32_t copy_len = (q->len > (LWIP_DEMO_RX_BUFSIZE - data_len)) ? 
                              (LWIP_DEMO_RX_BUFSIZE - data_len) : q->len;
            memcpy(g_lwip_demo_recvbuf + data_len, q->payload, copy_len);
            data_len += copy_len;
            if (data_len >= LWIP_DEMO_RX_BUFSIZE) break;
        }
        tcp_recved(tpcb, p->tot_len);
        pbuf_free(p);
        g_lwip_send_flag |= (1 << 6);
				
      //  u1_printf(" %d\r\n", data_len);
        // -------------------------- A区XMODEM处理(与参考逻辑完全对齐) --------------------------
        if (BootStaFlag & LWIPA_YMODEMD_FLAG) {
					  BootStaFlag &= ~LWIPA_YMODEMC_FLAG;  // 停止发送握手信号'C'
            // 1. 处理标准数据块(SOH帧:133字节=0x01+块号+反块号+128数据+2CRC)
            if ((data_len == XMODEM_FRAME_LEN) && (g_lwip_demo_recvbuf[0] == XMODEM_SOH)) {
              
                uint8_t block_num = g_lwip_demo_recvbuf[1];
                uint8_t block_num_inv = g_lwip_demo_recvbuf[2];

                // CRC校验(128字节数据)
                UpDataA.XmodemCRC = Xmodem_CRC16(&g_lwip_demo_recvbuf[3], XMODEM_BLOCK_SIZE);
                uint16_t crc_recv = (g_lwip_demo_recvbuf[131] << 8) | g_lwip_demo_recvbuf[132];
                if (UpDataA.XmodemCRC == crc_recv) {
                    UpDataA.XmodemNB++;  // 接收包计数+1
                    
                    // 暂存数据到缓冲区:按"包号%8"计算偏移(8包=1页)
                    uint32_t buf_idx = ((UpDataA.XmodemNB - 1) % (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE)) * XMODEM_BLOCK_SIZE;
                    memcpy(&UpDataA.Updatabuff[buf_idx], &g_lwip_demo_recvbuf[3], XMODEM_BLOCK_SIZE);
                    
                    // 满1页(8包=1024字节)时写入Flash
                    if ((UpDataA.XmodemNB % (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE)) == 0) {
                        uint32_t write_addr = STM32_APP1_SADDR + 
                                             ((UpDataA.XmodemNB / (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE)) - 1) * STM32_PAGE_SIZE;
                        STM32F4_WriteFlash(write_addr, (uint32_t *)UpDataA.Updatabuff, STM32_PAGE_SIZE);
                    }
                    
                    TCP_SendSingleByte(tpcb, es, XMODEM_ACK);  // CRC正确,发送ACK
                } else {
                    u1_printf("A区CRC校验失败:计算0x%04X,接收0x%04X\r\n", UpDataA.XmodemCRC, crc_recv);
                    TCP_SendSingleByte(tpcb, es, XMODEM_NAK);  // CRC错误,发送NAK请求重发
                    return ret_err;
                }
            }

            // 2. 处理结束包(EOT:1字节0x04,传输完成)
            else if ((data_len == 1) && (g_lwip_demo_recvbuf[0] == XMODEM_EOT)) {
                TCP_SendSingleByte(tpcb, es, XMODEM_ACK);  // 收到EOT,发送ACK

                // 处理不足1页的剩余数据
                uint32_t remain_blocks = UpDataA.XmodemNB % (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE);
                if (remain_blocks != 0) {
                    uint32_t remain_len = remain_blocks * XMODEM_BLOCK_SIZE;
                    
                    // 补全长度为4的整数倍
                    if (remain_len % 4 != 0) {
                        remain_len += (4 - remain_len % 4);
                    }
                    
                    uint32_t write_addr = STM32_APP1_SADDR + 
                                         (UpDataA.XmodemNB / (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE)) * STM32_PAGE_SIZE;
                    STM32F4_WriteFlash(write_addr, (uint32_t *)UpDataA.Updatabuff, remain_len);
                }

                // 清除Xmodem标志,保存OTA信息,重启生效
                BootStaFlag &= ~LWIPA_YMODEMD_FLAG;
                OTA_Info.UpData_Flag = UPDATA_FROM_A_FLAG;
                OTA_Info.Firelen[1] = UpDataA.XmodemNB * XMODEM_BLOCK_SIZE;
                WriteOTAInfo();  // 写入内部Flash(替代24C02)
                delay_ms(100);
               u1_printf("APP1 XMODEM接收完毕!固件大小:%lu字节,准备重启\r\n", OTA_Info.Firelen[1]);
                NVIC_SystemReset();  // 系统重启
            }
            else {
                u1_printf("未知XMODEM帧类型,发送NAK\r\n");
                TCP_SendSingleByte(tpcb, es, XMODEM_NAK);
            }
        }
				  // -------------------------- B区XMODEM处理(与参考逻辑完全对齐) --------------------------
					 if (BootStaFlag & LWIPB_YMODEMD_FLAG) {
					  BootStaFlag &= ~LWIPB_YMODEMC_FLAG;  // 停止发送握手信号'C'
            // 1. 处理标准数据块(SOH帧:133字节=0x01+块号+反块号+128数据+2CRC)
            if ((data_len == XMODEM_FRAME_LEN) && (g_lwip_demo_recvbuf[0] == XMODEM_SOH)) {
              
                uint8_t block_num = g_lwip_demo_recvbuf[1];
                uint8_t block_num_inv = g_lwip_demo_recvbuf[2];

                // CRC校验(128字节数据)
                UpDataB.XmodemCRC = Xmodem_CRC16(&g_lwip_demo_recvbuf[3], XMODEM_BLOCK_SIZE);
                uint16_t crc_recv = (g_lwip_demo_recvbuf[131] << 8) | g_lwip_demo_recvbuf[132];
                if (UpDataB.XmodemCRC == crc_recv) {
                    UpDataB.XmodemNB++;  // 接收包计数+1
                    
                    // 暂存数据到缓冲区:按"包号%8"计算偏移(8包=1页)
                    uint32_t buf_idx = ((UpDataB.XmodemNB - 1) % (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE)) * XMODEM_BLOCK_SIZE;
                    memcpy(&UpDataB.Updatabuff[buf_idx], &g_lwip_demo_recvbuf[3], XMODEM_BLOCK_SIZE);
                    
                    // 满1页(8包=1024字节)时写入Flash
                    if ((UpDataB.XmodemNB % (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE)) == 0) {
                        uint32_t write_addr = STM32_APP2_SADDR + 
                                             ((UpDataB.XmodemNB / (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE)) - 1) * STM32_PAGE_SIZE;
                        STM32F4_WriteFlash(write_addr, (uint32_t *)UpDataB.Updatabuff, STM32_PAGE_SIZE);
                    }
                    
                    TCP_SendSingleByte(tpcb, es, XMODEM_ACK);  // CRC正确,发送ACK
                } else {
                    u1_printf("A区CRC校验失败:计算0x%04X,接收0x%04X\r\n", UpDataB.XmodemCRC, crc_recv);
                    TCP_SendSingleByte(tpcb, es, XMODEM_NAK);  // CRC错误,发送NAK请求重发
                    return ret_err;
                }
            }

            // 2. 处理结束包(EOT:1字节0x04,传输完成)
            else if ((data_len == 1) && (g_lwip_demo_recvbuf[0] == XMODEM_EOT)) {
                TCP_SendSingleByte(tpcb, es, XMODEM_ACK);  // 收到EOT,发送ACK

                // 处理不足1页的剩余数据
                uint32_t remain_blocks = UpDataB.XmodemNB % (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE);
                if (remain_blocks != 0) {
                    uint32_t remain_len = remain_blocks * XMODEM_BLOCK_SIZE;
                    
                    // 补全长度为4的整数倍
                    if (remain_len % 4 != 0) {
                        remain_len += (4 - remain_len % 4);
                    }
                    
                    uint32_t write_addr = STM32_APP2_SADDR + 
                                         (UpDataB.XmodemNB / (STM32_PAGE_SIZE / XMODEM_BLOCK_SIZE)) * STM32_PAGE_SIZE;
                    STM32F4_WriteFlash(write_addr, (uint32_t *)UpDataB.Updatabuff, remain_len);
                }

								
                // 清除Xmodem标志,保存OTA信息,重启生效
                BootStaFlag &= ~LWIPB_YMODEMD_FLAG;
                OTA_Info.UpData_Flag = UPDATA_FROM_B_FLAG;
                OTA_Info.Firelen[2] = UpDataB.XmodemNB * XMODEM_BLOCK_SIZE;
                WriteOTAInfo();  // 写入内部Flash(替代24C02)
                delay_ms(100);
               u1_printf("APP1 XMODEM接收完毕!固件大小:%lu字节,准备重启\r\n", OTA_Info.Firelen[1]);
                NVIC_SystemReset();  // 系统重启
            }
            else {
                u1_printf("未知XMODEM帧类型,发送NAK\r\n");
                TCP_SendSingleByte(tpcb, es, XMODEM_NAK);
            }
        }
				
				
    }
    else { // 连接已关闭但收到数据
        tcp_recved(tpcb, p->tot_len);
        es->p = NULL;
        pbuf_free(p);
    }

    return ret_err;
}
。。。。。。。。。。。。。。。。。。。

QT上位机:

https://blog.csdn.net/m0_74739916/article/details/154916818?spm=1011.2415.3001.5331https://blog.csdn.net/m0_74739916/article/details/154916818?spm=1011.2415.3001.5331

扩展:

FLASH:

一、FLASH 硬件核心特性(必须掌握)

  1. 存储结构

    • STM32F407ZGT6 的 FLASH 为1MB(1024KB) ,起始地址0x08000000,按扇区划分(0~11 共 12 个扇区),扇区大小不等(16KB/64KB/128KB),且分为 2 个 Bank(各 512KB)。
    • 扇区是擦除操作的最小单位(无法擦除单个字节 / 字,必须整扇区擦除),擦除后所有位为0xFFFFFFFF(空白状态)。
  2. 操作限制

    • 编程(写入) :只能向空白区域(0xFFFFFFFF)写入,且地址必须4 字节对齐(32 位字编程),无法直接覆盖非空白数据(需先擦除)。
    • 擦除 :需指定扇区,且擦除前必须解锁 FLASH(默认锁定,防止误操作);擦除 / 编程需满足电压范围(通常 3.3V,对应FLASH_VOLTAGE_RANGE_3)。
    • 速度:擦除单扇区约 150ms,整片擦除约 1.8s;编程单字约 30us,受系统时钟影响(需配置 FLASH 等待周期)。
  3. 保护机制

    • 读保护(RDP):通过选项字节配置,等级 1 禁止外部读取,等级 2 不可逆(禁止调试,谨慎使用)。

    • 写保护 :可按扇区独立配置,保护后无法擦除 / 写入(需通过选项字节解除)。

      编程(写入)时只能从 1 变为 0,擦除时才能从 0 变回 1,这是 FLASH 的硬件特性决定的。

      核心逻辑拆解

    • FLASH 的初始 / 擦除状态 擦除后的 FLASH,所有存储位都会变成1(对应十六进制0xFFFFFFFF),这是 FLASH 的 "空白状态"。

    • 编程(写入)的位变化 写入数据时,只能将需要的位从1置为0(比如要写0x12345678,就是把对应位的1改成0,未涉及的位保持1)。无法直接将已为0的位改回1,这也是 "不能覆盖写入" 的核心原因。

    • 擦除的位变化 只有执行扇区擦除操作,才能把扇区内所有位从0恢复为1,回到空白状态后才能重新写入新数据。

    • 实际操作影响

    • 想修改某段数据时,必须先擦除整个扇区(让所有位变回1),再重新写入包含修改后内容的完整数据(不能只写修改的部分)。

    • 比如参数区某字节从0x1100010001)改成0x1000010000),可直接写(仅把最后一位10);但要从0x10改回0x11,必须先擦除整个扇区再写。

FLASH和ROM

这是 Keil MDK 开发环境中的存储器配置界面,用于指定程序和数据在芯片存储器中的存储区域,不同区域的用途如下:

一、IROM1(内置 Flash 程序存储区)

  • 地址0x08020000 ~ 0x08020000 + 0x60000 - 1(即0x08080000),总容量384KB
  • 用途 :存放应用程序代码(如主程序、函数、常量数据等)。结合之前的分区规划,这里对应 "运行区 / APP1 区",是程序实际执行的代码存储区域(Bootloader 跳转后,程序从该区域启动)。

二、IRAM1(内置 SRAM 数据存储区)

  • 地址0x20000000 ~ 0x20000000 + 0x1C000 - 1(即0x2001C000),总容量112KB
  • 用途 :存放程序运行时的临时数据 ,包括:
    • 全局变量、局部变量;
    • 函数调用的栈空间(Stack);
    • 动态内存分配(如malloc申请的空间)。

三、IRAM2(内置 SRAM 扩展数据区,部分 STM32 型号支持)

  • 地址0x2001C000 ~ 0x2001C000 + 0x4000 - 1(即0x20020000),总容量16KB
  • 用途
    • 可作为额外的临时数据存储区(如存放高频访问的缓存、大数组);
    • 部分芯片中,IRAM2 可配置为 "CCM RAM"(内核紧密耦合内存),用于存储对速度要求极高的代码或数据(执行效率比普通 SRAM 更高)。

总结

  • IROM1(FALSH):存程序代码,掉电不丢;
  • IRAM1/IRAM2:存运行时的临时数据,掉电丢失。这种配置是为了让程序在 "非默认 Flash 起始地址"(0x08020000)运行,同时合理分配 RAM 资源,确保程序稳定执行。

中断向量表:

默认放在FLASH的起始位置0x8000000但是,我们将FLASH划分为多个区域,需要将中断向量表偏移(除了boot程序其他的全部都要偏移)

SCB->VTOR=FLASH_BASE|偏移量 ;

keil配置:

keil的falsh配置:

Erase Sectors--》擦除扇区

bootloader:

APP程序的FLASH,需要加入中断向量表的偏移:

EGG:运行区域

生成Bin文件:

方法一:

1:将KEIL下面的bin文件加入环境变量

2:fromelf --bin --output ./Run/Run.bin ./Run/Run.axf

./../ 是表示相对路径的符号,

./----->当前目录

../----->上级目录

方法二:

在keil里面加入: D:\keil5\ARM\ARMCC\bin\fromelf.exe --bin -o .\Run\Run.bin .\Run\Run.axf

D:\keil5\ARM\ARMCC\bin\fromelf.exe 你的keil安装的目录也是

.\Run\Run.bin 生成bin文件的目录

\Run\Run.axf axf文件的目录

需要注意:当前打开keil的目录是Run.uvprojx , 所以需要注意.\ 和..\

过程发现的问题:

使用DMA+串口中断使用HAL_DMA_Abort停止DMA传输,使用这个发现问题第一次输入stm32可以正常接收,第二次不能接收,第三次正常接收,第四从不能接收,以此类推 (您描述的"交替成功")

一:串口DMA空闲中断

HAL_DMA_Abort

总之这个函数就是判断DMA的句柄状态不忙的话,清除中断相关的寄存器,关闭DMA数据流, 一次性清除指定 DMA 流的所有中断标志。不对其他外设(USART)操作。

perl 复制代码
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma)
{
  /* calculate DMA base and stream number */
  DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;
  
  uint32_t tickstart = HAL_GetTick();
  
  if(hdma->State != HAL_DMA_STATE_BUSY)
  {
    hdma->ErrorCode = HAL_DMA_ERROR_NO_XFER;
    
    /* Process Unlocked */
    __HAL_UNLOCK(hdma);
    
    return HAL_ERROR;
  }
  else
  {
    /* 清除 传输完成中断使能 传输错误中断使能 直接模式错误中断使能*/
    hdma->Instance->CR  &= ~(DMA_IT_TC | DMA_IT_TE | DMA_IT_DME);
    /*  DMA 流量xFIFO 控制寄存器
    清除 :FIFO 错误中断使能 (FIFO error interrupt enable)*/
    hdma->Instance->FCR &= ~(DMA_IT_FE);
    /*检查普通DMA模式的半传输完成回调函数是否被设置*/
    if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL))
    {
        /*清除   半传输中断使能 (Half transfer interrupt enable) */
      hdma->Instance->CR  &= ~(DMA_IT_HT);
    }
    
    /* 关闭 数据流使能/读作低电平时数据流就绪标志*/
    __HAL_DMA_DISABLE(hdma);
    
    /* EN:数据流使能/读作低电平时数据流就绪标志 */
    while((hdma->Instance->CR & DMA_SxCR_EN) != RESET)
    {
      /* Check for the Timeout */
      if((HAL_GetTick() - tickstart ) > HAL_TIMEOUT_DMA_ABORT)
      {
        /* Update error code */
        hdma->ErrorCode = HAL_DMA_ERROR_TIMEOUT;
        
        /* Change the DMA state */
        hdma->State = HAL_DMA_STATE_TIMEOUT;
        
        /* 结锁 */
        __HAL_UNLOCK(hdma);
        
        return HAL_TIMEOUT;
      }
    }
    
    /* 
    regs 指向 DMA 寄存器组
    IFCR :DMA中断清除寄存器
    hdma->StreamIndex dma的那个数据流
    次性清除指定 DMA 流的所有中断标志,
     */
    regs->IFCR = 0x3FU << hdma->StreamIndex;
    
    /* Change the DMA state*/
    hdma->State = HAL_DMA_STATE_READY;
    
    /* Process Unlocked */
    __HAL_UNLOCK(hdma);
  }
  return HAL_OK;
}

HAL_UART_DMAStop

我们只使用了接收的,所以只看接收的(其实接收和发送写的一样)

使能串口的DMA接收器,判断串口的状态如果为忙,失能DMA接收器。判断串口有没有配置dma接收通道,配置了调HAL_DMA_Abort。再把串口的接收中断清理。

perl 复制代码
HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart)
{
  uint32_t dmarequest = 0x00U;
  /* The Lock is not implemented on this API to allow the user application
     to call the HAL UART API under callbacks HAL_UART_TxCpltCallback() / HAL_UART_RxCpltCallback():
     when calling HAL_DMA_Abort() API the DMA TX/RX Transfer complete interrupt is generated
     and the correspond call back is executed HAL_UART_TxCpltCallback() / HAL_UART_RxCpltCallback()
     */

  /* Stop UART DMA Tx request if ongoing */
  dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAT);
  if ((huart->gState == HAL_UART_STATE_BUSY_TX) && dmarequest)
  {
    ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);

    /* Abort the UART DMA Tx stream */
    if (huart->hdmatx != NULL)
    {
      HAL_DMA_Abort(huart->hdmatx);
    }
    UART_EndTxTransfer(huart);
  }

     /*
    #define USART_CR3_DMAR_Pos            (6U)                                     
    #define USART_CR3_DMAR_Msk            (0x1UL << USART_CR3_DMAR_Pos)    
    #define USART_CR3_DMAR                USART_CR3_DMAR_Msk  
    USART_CR3_DMAR 实际上为 0100 0000
    使能 DMA 使能接收器 (DMA enable receiver)
       */
  dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
    /*
    HAL_UART_STATE_BUSY_RX  串口接收数据状态忙
    
    */
  if ((huart->RxState == HAL_UART_STATE_BUSY_RX) && dmarequest)
  {
    //失能 DMA 使能接收器 (DMA enable receiver)
    ATOMIC_CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    /*判断UART是否配置了DMA接收通道 */
    if (huart->hdmarx != NULL)
    {
      HAL_DMA_Abort(huart->hdmarx);
    }
    /*
    这个是对于串口操作
    禁用接收中断 - 关闭RXNE、奇偶错误和各类错误中断
    清理空闲检测中断 - 如果是"接收至空闲"模式,额外关闭空闲中断
    重置状态标志 - 将接收状态恢复为就绪状态,接收类型重置为标准模式
    核心目的:安全地停止UART接收并清理相关配置。
    */
    UART_EndRxTransfer(huart);
  }

  return HAL_OK;
}

为什么再使用DMA+空闲中断的时候只能使用HAL_UART_DMAStop?

主要区别

HAL_DMA_Abort(&hdma_usart1_rx) - 仅停止DMA

  • ❌ 只中止DMA传输

  • 不处理UART侧的DMA配置

  • ❌ USART_CR3_DMAR 位仍然保持使能状态

  • ❌ UART可能继续产生DMA请求,但DMA已停止

HAL_UART_DMAStop(&huart1) - 完整停止UART+DMA

  • ✅ 中止DMA传输

  • 清除UART的DMA接收使能位 (USART_CR3_DMAR)

  • ✅ 更新UART状态机 (RxState)

  • ✅ 清理相关中断标志

  • ✅ 确保UART和DMA完全同步停止

    为什么直接使用 HAL_DMA_Abort 会导致错误?

  • 问题所在:

  • UART仍在请求DMA :虽然DMA停止了,但UART的 USART_CR3_DMAR 位仍然为1

  • 状态不一致 :UART认为DMA仍在工作 (HAL_UART_STATE_BUSY_RX),但DMA实际已停止

  • 后续操作混乱:重新启动DMA时可能出现状态冲突

  • 可能丢失数据:UART可能继续接收数据并尝试DMA传输,但DMA已不可用

  • 总结

    必须使用 HAL_UART_DMAStop 的原因:

  • 同步操作:确保UART和DMA同时停止

  • 状态一致性:维护正确的状态机状态

  • 简单说: HAL_UART_DMAStop 是一个"完整的套餐",而 HAL_DMA_Abort 只是其中的"一道菜",单独使用会导致系统状态不一致。

  • 安全性:避免硬件状态不一致导致的异常

  • 完整性:清理所有相关的配置和中断

debug:

描述的"交替成功:使用HAL_DMA_Abort 会导致hdma->State 状态异常,第一次为HAL_DMA_STATE_BUSY可以进入else,第二次为HAL_DMA_STATE_READY不能进入依次循环

perl 复制代码
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma)
{
  /* calculate DMA base and stream number */
  DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;
  
  uint32_t tickstart = HAL_GetTick();
  
  if(hdma->State != HAL_DMA_STATE_BUSY)
  {

  }
  else
  {
    /* Disable all the transfer interrupts */
      这个里面是清除dma相关的
  }
  return HAL_OK;
}

DMA这个我自己写的:

https://blog.csdn.net/m0_74739916/article/details/141902135?spm=1011.2415.3001.5331https://blog.csdn.net/m0_74739916/article/details/141902135?spm=1011.2415.3001.5331

这个是别的up的写的不错参考一下:

https://blog.csdn.net/bjun54/article/details/141429926?ops_request_misc=&request_id=&biz_id=102&utm_term=DMA+%E7%A9%BA%E9%97%B2%E4%B8%AD%E6%96%AD&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-141429926.142^v102^pc_search_result_base6&spm=1018.2226.3001.4187https://blog.csdn.net/bjun54/article/details/141429926?ops_request_misc=&request_id=&biz_id=102&utm_term=DMA+%E7%A9%BA%E9%97%B2%E4%B8%AD%E6%96%AD&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-141429926.142^v102^pc_search_result_base6&spm=1018.2226.3001.4187

相关推荐
red watchma2 小时前
向量表偏移寄存器(Vector Table Offset Register,VTOR)
单片机·嵌入式硬件
NEU-UUN2 小时前
3.4.STM32-按键控制LED&光敏传感器控制蜂鸣器
stm32·单片机·嵌入式硬件
点灯小铭3 小时前
基于单片机的程控放大器设计与实现
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
范纹杉想快点毕业3 小时前
《嵌入式硬件从入门到精通:电源 / 模电 / 数电 / 通信核心全解析》
java·开发语言·数据库·单片机·嵌入式硬件
打酱油程序员4 小时前
舵机工作原理与控制详解
单片机·嵌入式硬件
Wave8454 小时前
FreeRTOS的常用函数和剪切
单片机·嵌入式硬件
C.咖.5 小时前
STM32 ——嵌入式 存储系统、时钟系统(F407 系列)
stm32·单片机·嵌入式硬件
llilian_166 小时前
晶振有什么好用的检测仪器?石英晶振测试仪 晶体测试仪
服务器·单片机·嵌入式硬件·其他
FreakStudio7 小时前
串口协议解析实战:以 R60ABD1 雷达为例,详解 MicroPython 驱动中数据与业务逻辑的分离设计
python·单片机·pycharm·嵌入式·面向对象·硬件·电子diy