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

相关推荐
沉在嵌入式的鱼1 小时前
linux串口对0X0D、0X0A等特殊字符的处理
linux·stm32·单片机·特殊字符·串口配置
学习路上_write1 小时前
AD5293驱动学习
c语言·单片机·嵌入式硬件·学习
影阴2 小时前
存储器和寄存器
stm32·单片机·嵌入式硬件
吃西瓜的年年3 小时前
3. C语言核心语法2
c语言·嵌入式硬件·改行学it
李洛克073 小时前
RDMA CM UDP 通信完整指南
单片机·网络协议·udp
思茂信息3 小时前
CST电动车EMC仿真——电机控制器MCU滤波仿真
javascript·单片机·嵌入式硬件·cst·电磁仿真
小曹要微笑3 小时前
I2C总线技术解析(纯文字版)
单片机·嵌入式硬件·esp32·iic
我送炭你添花4 小时前
可编程逻辑器件(PLD)的发展历程、原理、开发与应用详解
嵌入式硬件·fpga开发
袖手蹲4 小时前
Arduino UNO Q 从 Arduino Cloud 远程控制闪烁 LED
人工智能·单片机·嵌入式硬件·电脑
平凡灵感码头5 小时前
第一次做蓝牙产品,从零开发 嵌入式开发日志(2)AC63NSDK 完整合并版目录说明
stm32·单片机·嵌入式硬件