STM32串口IAP(OTA)升级过程解析
总结
我们可以通过串口使用Xmodem协议将.bin文件发送到外部norlash,再读取norlash数据到A区实现IAP固件更新。
也可通过基于串口的如蓝牙 WIFI模块 4G模块 CAN设备实现空中升级OTA。
基于此思想,可以扩展阿里云带版本推送的双AB区的OTA无感升级。
目录
1.STM32内存分布和启动流程
2.设计逻辑–AB分区理念
3.串口空闲中断+DMA传输
4.读写外部flash 写内部flahs
5.Xmodem协议
6.Bootloader命令行
7.命令对应的API
8.跳转A区,执行应用程序
9.MDK生成.bin文件
1.STM32启动方式和内存分布

1.1重映射与从FLASH启动
我们可以发现每次我们不论在那学习关于STM32相关的知识都避免不了要接触到0X08000000这个关键的地址。
这个地址使我们把BOOT = 0 BOOT1 = X时代码下载到的位置,但是我们的STM32不是上电之后从0开始运行的吗?这是怎么回事呢
接下来我们先看一下STM32的自举方式

无论是从内部flash启动,还是系统存储器,亦或者是SRAM都无一例外提到了映射,那什么是映射,又为什么要映射呢?

我们来看这句话,会发现改变BOOT引脚会将0X00000000 - 0X001FFFFF的映射也给一起改变。
我们可以通俗的理解 此时的0X00000000-0x001 F FFFF 被 0X08000000-0X08FFFFFF替代了(前提是*BOOT = 0 BOOT1 = X)。

1.2SRAM启动使用场景
映射到SRAM 也就是0X20000000 (SRAM的起始地址)
那就很清晰的认识到,的确是从0开始运行,只不过通过一些手段(y映射),把FLASH或者SRAM的代码给“搬运”倒了0X00000000。

而下载到SRAM如上面图片所说,RAM有易失性,仅在调试时代码改动较小,代码量大时相对有很大的效果提升。
1.3系统存储器启动
那不是还有一个系统存储器(BOOT = 1 BOOT1 = 0)启动吗,又是怎么回事?
其实soc厂商在生产时给内部弄了一个Bootloader,我们下载时使用下载器如STLINK JLINK ,芯片内部Bootloader ,会将代码加载到目标芯片的FLAH。
所以我们在下载到系统存储器之后,还要将BOOT引脚恢复到BOOT = 0 BOOT1 = X,即还是从FLASH启动
1.4main函数开始前STM32干了什么
1.4.1开辟栈和堆
打开startup启动文件
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM 的大小。不然可能会出现的HardFault的错误。代码量过大时需手动更改栈大小。
Stack_Size EQU 0x00000400
||
Stack_Size EQU 0x00000800
Stack_Size EQU 0x00000200表示开辟栈的大小为 0X00002000,EQU==C 中的 define。
ARER 后面的接段属性。段名为STACK;NOINIT 表示不初始化在(后续__main启动);READWRITE 表示可读可写,ALIGN=3,表示按照 8 字节对齐。
SPACE 用于分配大小等于 Stack_Size连续内存空间,单位为字节。
堆主要用来动态内存的分配,像 malloc()函数申请的内存就在堆中(我们常常使用自定义的malloc,因为c库的malloc存在线程安全问题,此处不讨论,否则文章太长了)。
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
开辟堆的大小为 0X00000200(512 字节),名字为 HEAP,NOINIT 即不初始化(后续__main启动),可读可写,8字节对齐。__heap_base 表示堆的起始地址,__heap_limit 表示堆的结束地址。
1.4.2设置中断向量表
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
……
我们发现0开始首先居然是 __initial_sp栈顶指针 这是我们后续IAP一个判断依据。前4B为栈顶指针,0X00000004才是中断向量表起始。
1.4.3中断服务函数注册
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
除了复位中断服务函数Reset_Handler被实现了
其他的都未被实现函数体 (weak若定义),仅仅实现了注册(牵扯到内部函数指针与回调函数的定义,此处不涉及)。
1.4.4Reset_Handler复位中断
前面其实还有特权级(M3内核)NVIC清零设置,主堆栈指针设置,内核堆栈指针设置,PC (PC = Reset_Handlerr,SP = *(0X80000000),其中、 *(0X80000000)指向的是SRAM 0x2000 0000,即栈在0X2000 0000 (左右))设置我们不过多赘述,直接查看Reset_Handler复位中断。
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
定义了一个服务程序,PROC表示程序的开始。
使用EXPORT将Reset_Handler申明为可被外部引用。
使用IMPORT表示该标号来自外部文件,**SystemInit()**是一个库函数,在system_stm32f1xx.c中定义的。
SystemInit ()初始化系统时钟
__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,这个是由编译器完成的
__mian 会对开辟的堆和栈进行初始化 并且调用main函数

当我们在Options for Target 使用微库 Use MocroLIB设置
该函数最终会调用我们自己写的main函数,执行我们自己的代码。
LDR R0, =SystemInit
这是一条汇编指令,表示从存储器中加载SystemInit到一个寄存器R0的地址中。
BLX R0
表示跳转到寄存器R0的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。
LDR R0, =__main
表示从存储器中加载__main到一个寄存器R0的地址中。
BX R0
跳转到至指定寄存器的地址后,不会返回。
与PROC是对应的,表示程序的结束。
2.设计逻辑–AB分区理念

以F103C8T6 (64KB的FLASH) 为例:将FLASH分为两个部分 B区:Bototloader区 A区:APP区
相关宏定义设置
//以下单位均为B字节
//flash起始地址
#define C8T6_FLASH_START_ADDR 0X08000000
//flash页page个数
#define C8T6_FLASH_NUM 64
//flash页page大小 1k
#define C8T6_FLASH_PAGE_SIZE 1024
//b区页数
#define C8T6_B_PAGE_NUM 20
//a区页数
#define C8T6_A_PAGE_NUM C8T6_FLASH_NUM - C8T6_B_PAGE_NUM
//a区起始页号
#define C8T6_A_START_PAGE C8T6_B_PAGE_NUM
//a区起始地址
#define C8T6_A_START_ADDR C8T6_FLASH_START_ADDR + C8T6_A_START_PAGE * C8T6_FLASH_PAGE_SIZE
前20KB为Bootloader引导程序区此区域代码功能:
①擦除A区代码数据;
②串口IAP下载A区;
③向外部FLASH下载程序;
④读取外部FLSAH到内部FLASH使用;
⑤重启
3.串口空闲中断+DMA传输
3.1环形接收区建立
#define NUM 10
typedef struct
{
uint8_t* start;
uint8_t* end;
}UCB_URXBufferptr;
typedef struct
{
uint16_t URxcounter;
UCB_URXBufferptr URxDataptr[NUM];
UCB_URXBufferptr *URxDataIN;
UCB_URXBufferptr *URxDataOUT;
UCB_URXBufferptr *URxDateEND;
}UCB_CB;
缓冲区指针初始化
void URx1_ptrInit(void)
{
U1CB.URxcounter = 0;
U1CB.URxDataIN = &U1CB.URxDataptr[0];
U1CB.URxDataOUT = &U1CB.URxDataptr[0];
U1CB.URxDateEND = &U1CB.URxDataptr[NUM-1];
U1CB.URxDataIN->start = rx1_buffer;
}

其实我们还是定义了一整个大数组 但是被我们分成了NUM个部分 后续称为块 使用IN OUT END来界定。
IN与OUT一旦不相等说明接受了多少个数据包,也就是进入了多少次空闲中断
块指针有start 和 end指针
start表示下次接受起始位置
end一直指向当前块的起始
原理:如果end != start表示当前这个块有数据接收 可以进行处理
每接收数据一次IN后移 IN与END重合表明当前接收已满,IN再次指向开头,实现循环
OUT指针就是来指示上次接收完毕的块地址,每次接收IN会后移,那我怎么知道这次接收从缓冲区哪开始的?
那就通过OUT指向进入中断管服务函数前的IN,即上次的接收完的末尾,然后对OUT的start与end进行判断
具体详情可参考中断服务函数
3.2串口与DMA配置
串口初始化配置
注意开启串口空闲中断以及调用URx1_ptrInit函数。
void Usart1_Init(uint32_t baurd)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_initstruct;
GPIO_initstruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_initstruct.GPIO_Pin = GPIO_Pin_10;
GPIO_Init(GPIOA,&GPIO_initstruct);
GPIO_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_initstruct.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOA,&GPIO_initstruct);
USART_InitTypeDef usart_initstruct;
usart_initstruct.USART_BaudRate = baurd;
usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
usart_initstruct.USART_Parity = USART_Parity_No;
usart_initstruct.USART_StopBits = USART_StopBits_1;
usart_initstruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&usart_initstruct);
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);//!!!!!!!!!!!!!!!!!!!!!!!!!!USART_IT_IDLE
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);
NVIC_InitTypeDef nvic_initstruct;
nvic_initstruct.NVIC_IRQChannel = USART1_IRQn;
nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;
nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 5;
NVIC_Init(&nvic_initstruct);
URx1_ptrInit();
USART_Cmd(USART1,ENABLE);
}
DMA配置
串口1接收通道对应DMA1_Channel5
void Dma_init(uint32_t dma_PeripheralBaseAddr1,uint32_t dma_MemoryBaseAddr1,
uint32_t dma_PeripheralBaseAddr2,uint32_t dma_MemoryBaseAddr2)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//串口dma to rx1_buffer
dam_initstruct.DMA_BufferSize = USART1_RX_MAX + 1; //不让其产生完成中断
dam_initstruct.DMA_DIR = DMA_DIR_PeripheralSRC;
dam_initstruct.DMA_M2M = DMA_M2M_Disable;
dam_initstruct.DMA_MemoryBaseAddr = dma_MemoryBaseAddr1; //usart1_rx_buf
dam_initstruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
dam_initstruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
dam_initstruct.DMA_Mode = DMA_Mode_Normal;
dam_initstruct.DMA_PeripheralBaseAddr = dma_PeripheralBaseAddr1; //&(USART1->DR)
dam_initstruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
dam_initstruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
dam_initstruct.DMA_Priority = DMA_Priority_High;
DMA_Init(DMA1_Channel5,&dam_initstruct);
DMA_Cmd(DMA1_Channel5,ENABLE);//usart1 rx channel5
}
注意以下代码
dam_initstruct.DMA_BufferSize = USART1_RX_MAX + 1; //不让其产生完成中断
我们这样子设置的目的是不让DMA产生完成中断(虽然我们也没有使能),这样子就算DMA模式是DMA_Mode_Normal,我们也可以将传输次数恢复在串口空闲中断实现。
串口中断服务函数
uint8_t length = 0;
void USART1_IRQHandler(void)
{
uint16_t clear_idle = 0;
if(USART_GetITStatus(USART1,USART_IT_IDLE) == SET)
{
clear_idle = USART1->SR;
clear_idle = USART1->DR;
//清楚USART_IT_IDLE的参数
//因此清楚这个标志位 先读sr 再读dr
U1CB.URxcounter += (USART1_RX_MAX + 1) - DMA_GetCurrDataCounter(DMA1_Channel5);
U1CB.URxDataIN->end = &rx1_buffer[U1CB.URxcounter - 1];
U1CB.URxDataIN++;
if(U1CB.URxDataIN == U1CB.URxDateEND)
{
U1CB.URxDataIN = &U1CB.URxDataptr[0];
}
if(USART1_RX_BUFSIZE - U1CB.URxcounter >= USART1_RX_MAX)
{
U1CB.URxDataIN->start = &rx1_buffer[U1CB.URxcounter - 1 + 1];
}
else
{
U1CB.URxDataIN->start = rx1_buffer;
U1CB.URxcounter = 0;
}
DMA_Cmd(DMA1_Channel5,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel5,NUM + 1);
dam_initstruct.DMA_MemoryBaseAddr = (uint32_t)U1CB.URxDataIN->start;
DMA_Init(DMA1_Channel5,&dam_initstruct); //次数会再次在这配置
DMA_Cmd(DMA1_Channel5,ENABLE);
}
}
注意IDLE空闲中断标志位的清除方式,以及
DMA_Init(DMA1_Channel5,&dam_initstruct);
实现再次复制DMA的传输次数。
HAL库版本
void URx1_ptrInit(void)
{
U1CB.URxcounter = 0;
U1CB.URxDataIN = &U1CB.URxDataptr[0];
U1CB.URxDataOUT = &U1CB.URxDataptr[0];
U1CB.URxDateEND = &U1CB.URxDataptr[NUM-1];
U1CB.URxDataIN->start = rx1_buffer;
}
void usart_init(uint32_t baudrate)
{
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
huart1.Instance = USART1;
huart1.Init.BaudRate = baudrate;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(&huart1);
// 启用空闲中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
// 初始化缓冲区指针
URx1_ptrInit();
// NVIC配置
HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
void USART1_IRQHandler(void)
{
uint16_t clear_idle = 0;
clear_idle = USART1->SR;
clear_idle = USART1->DR;
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
HAL_UART_IdleCallback(&huart1);
}
void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_AbortReceive(huart);
uint32_t temp;
temp = huart->Instance->SR; // 清除空闲中断标志
temp = huart->Instance->DR;
// 计算接收数据长度
U1CB.URxcounter += USART1_RX_MAX - __HAL_DMA_GET_COUNTER(huart->hdmarx);
// 更新缓冲区指针
U1CB.URxDataIN->end = &rx1_buffer[U1CB.URxcounter - 1];
U1CB.URxDataIN++;
if(U1CB.URxDataIN > U1CB.URxDateEND)
{
U1CB.URxDataIN = &U1CB.URxDataptr[0];
}
// 处理缓冲区回绕
if(USART1_RX_BUFSIZE - U1CB.URxcounter >= USART1_RX_MAX)
{
U1CB.URxDataIN->start = &rx1_buffer[U1CB.URxcounter];
}
else
{
U1CB.URxDataIN->start = rx1_buffer;
U1CB.URxcounter = 0;
}
__HAL_DMA_ENABLE(huart->hdmarx);
HAL_UART_Receive_DMA(&huart1, (uint8_t*)U1CB.URxDataIN->start, USART1_RX_MAX);
}
}
数据回显测试
main.c
while(1)
{
if(U1CB.URxDataIN != U1CB.URxDataOUT)
{
BootLoader_Event(U1CB.URxDataOUT->start,U1CB.URxDataOUT->end - U1CB.URxDataOUT->start + 1);
U1CB.URxDataOUT++;
if(U1CB.URxDataOUT == U1CB.URxDateEND)
{
U1CB.URxDataOUT = &U1CB.URxDataptr[0];
}
}
…………
判断OUT与IN是否重合来判断数据包接收个数 判断OUT的start与end来判断当前包接收数据个数
4.读写外部flash 写内部flahs
首先完成norflash(W25Q64)操作API,包括底层SPI驱动。
SPI驱动
void My_SPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_Initstructure;
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //MISO配置为浮空输入
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_6 ;
GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Initstructure);
GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF_PP; //MOSI配置为推完服用输出
GPIO_Initstructure.GPIO_Pin = GPIO_Pin_7 | GPIO_Pin_5;
GPIO_Init(GPIOA,&GPIO_Initstructure);
SPI_InitTypeDef SPI_Initstructure;
SPI_Initstructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率分频128
SPI_Initstructure.SPI_CPHA = SPI_CPHA_1Edge; //空闲电平为低
SPI_Initstructure.SPI_CPOL = SPI_CPOL_Low; //第二个边沿采样
SPI_Initstructure.SPI_CRCPolynomial = 7; //crc默认配置为7
SPI_Initstructure.SPI_DataSize = SPI_DataSize_8b; //数据长度8为
SPI_Initstructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //全双工
SPI_Initstructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行
SPI_Initstructure.SPI_Mode = SPI_Mode_Master; //主机模式
SPI_Initstructure.SPI_NSS = SPI_NSS_Soft; //软件片选
SPI_Init(SPI1,&SPI_Initstructure);
SPI_Cmd(SPI1,ENABLE);
}
/*函数功能:spi读取一个字节------------------------*/
/*参数:要发送的空白无用字节0xff-------------------*/
/*返回值:读取到的字节----------------------------*/
uint8_t SPI1_ReadBytedata(uint8_t txd)
{
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET); //判断TXE是否成立 成立就跳转
SPI_I2S_SendData(SPI1,txd); //0XFF发送成功
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET); //等待RXNE成立 即接收完成
return SPI_I2S_ReceiveData(SPI1); //返回读取
}
/*函数功能:spi写------------------------*/
/*参数:wdata要写的数据地址-------------------*/
/*参数:datalen要写的数据个数-------------------*/
/*返回值:无----------------------------*/
void SPI1_Write(uint8_t *wdata,uint16_t datalen)
{
uint16_t i ;
for(i = 0;i < datalen; i++)
{
SPI1_ReadBytedata(wdata[i]);
}
}
/*函数功能:spi读取------------------------*/
/*参数:rdata读取到的数据放的位置-------------------*/
/*参数:datalen要读的数据个数-------------------*/
/*返回值:无----------------------------*/
void SPI1_Read(uint8_t *rdata,uint16_t datalen)
{
uint16_t i ;
for(i = 0;i < datalen; i++)
{
rdata[i] = SPI1_ReadBytedata(0xff);
}
}
norflash操作API
#define CS_ENABLE GPIO_ResetBits(GPIOA,GPIO_Pin_4)
#define CS_DISABLE GPIO_SetBits(GPIOA,GPIO_Pin_4)
#define RD_SR_CMD 0X05
#define NOUSE_MES 0XFF
#define WR_ENABLE 0X06
#define BLOCK_ERASE 0XD8
#define PAGE_PROGRAM 0X02
#define RRAD_DATA 0X03
…………
void W25Q64_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef gpio_initstructure;
gpio_initstructure.GPIO_Mode = GPIO_Mode_Out_PP;
gpio_initstructure.GPIO_Pin = GPIO_Pin_4;
gpio_initstructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&gpio_initstructure);
CS_DISABLE; //初始化先不选中
My_SPI_Init();
}
/*函数功能:w25q64等待忙------------------------*/
/*参数:无-------------------*/
/*返回值:无----------------------------*/
void W25Q64_WaitBusy(void)
{
uint8_t res;
do
{
CS_ENABLE; //片选选中
SPI1_ReadBytedata(RD_SR_CMD); //发送读状态寄存器的命令
res = SPI1_ReadBytedata(NOUSE_MES); //读取状态寄存器 参数为OXFF
CS_DISABLE; //片选取消
}while((res & 0x01) == 1);//sr.0 busy //判断忙
}
/*函数功能:w25q64写使能------------------------*/
/*参数:无-------------------*/
/*返回值:无----------------------------*/
void W25Q64_WR_ENABLE(void)
{
W25Q64_WaitBusy(); //等待忙
CS_ENABLE; //片选
SPI1_ReadBytedata(WR_ENABLE); //发送写使能
CS_DISABLE; //取消片选
}
/*函数功能:w25q64块擦除 64k------------------------*/
/*参数:块的序号 num*1024*64 具体地址-------------------*/
/*返回值:无----------------------------*/
void W25Q64_64K_ERASE(uint8_t blocknum)//8m = 8*1024 = 8192k 8192k/64k = 128块 1块64kB
{
uint8_t wdata[4] = {0};
wdata[0] = BLOCK_ERASE; //块擦除命令
wdata[1] = (blocknum * 64 * 1024) >> 16; //擦除地址高8位
wdata[2] = (blocknum * 64 * 1024) >> 8; //擦除地址中八位
wdata[3] = (blocknum * 64 * 1024); //擦除地址第八位
W25Q64_WaitBusy(); //等待忙
W25Q64_WR_ENABLE(); //写使能
CS_ENABLE; //片选
SPI1_Write(wdata,4); //发送擦除
CS_DISABLE; //取消片选
W25Q64_WaitBusy(); //等待忙
}
/*函数功能:w25q64页写256B------------------------*/
/*参数:写的数据的地址 wbuf-------------------*/
/*参数:页的序号 -------------------*/
/*返回值:无----------------------------*/
void W25Q64_Page_write(uint8_t* wbuf,uint16_t pagenum) //256B一页
{
uint8_t wdata[4] = {0};
wdata[0] = PAGE_PROGRAM; //页编程命令
wdata[1] = (pagenum * 256) >> 16; //页高八位地址
wdata[2] = (pagenum * 256) >> 8; //页中八位地址
wdata[3] = pagenum * 256; //页第八位地址
W25Q64_WaitBusy(); //等待忙
W25Q64_WR_ENABLE(); //写使能
CS_ENABLE; //片选
SPI1_Write(wdata,4); //发命令
SPI1_Write(wbuf,256); //写字节
CS_DISABLE; //取消片选
}
/*函数功能:w25q64读------------------------*/
/*参数:读的数据的地址 rbuf-------------------*/
/*参数:读取的地址addr -------------------*/
/*参数:读取的数据长度址datalen -------------------*/
/*返回值:无----------------------------*/
void W25Q64_Readdata(uint8_t* rbuf,uint32_t addr,uint32_t datalen)
{
uint8_t wdata[4] = {0};
wdata[0] = RRAD_DATA;
wdata[1] = addr >> 16;
wdata[2] = addr >> 8;
wdata[3] = addr ;
W25Q64_WaitBusy();
CS_ENABLE;
SPI1_Write(wdata,4);
SPI1_Read(rbuf,datalen);
CS_DISABLE;
}
因为是要进行IAP(OTA)固件升级,整个新版固件.bin文件很大,所以提供页写W25Q64_Page_write与块擦除W25Q64_WR_ENABLE
为什么弄一个页写却弄一个块擦除呢
是因为我们把norlash的每一个块都用来保存一个版本的固件(固件大小 <= 块大小)\
注意我们设置第0个块存放最新OTA的固件,其他存放IAP或者低版本的OTA的固件
每次版本覆盖,使用最小擦除单位扇区擦除,会比较耗时。
而写入采用页(1KB)写是为了和后续接收协议Xmodem协议适配,而且F1内部的flash有写入要求,即一次操作必须为4个字节,即字写入
内部FLASH操作API
/*函数功能:flash擦除------------------------*/
/*参数:start_num 起始块的序号 一块1KB-------------------*/
/*参数:num 擦除个数-------------------*/
/*返回值:无----------------------------*/
void Erase_Flash(uint16_t start_num,uint16_t num)
{
uint16_t i;
FLASH_Unlock(); //整片flash解锁
for(i = 0;i < num;i++)
{
FLASH_ErasePage( (start_num * 1024 + 0x08000000) + (1024 * i) ); //擦除
}
FLASH_Lock(); //上锁
}
/*函数功能:flash写 一次写一个字------------------------*/
/*参数:addr写的地址 -------------------*/
/*参数:wdata 写的数据存放的地址-------------------*/
/*参数:datalen 写的数据个数-------------------*/
/*返回值:无----------------------------*/
void Write_Flash(uint32_t addr,uint32_t* wdata,uint32_t datalen)
{
FLASH_Unlock();
while(datalen)
{
FLASH_ProgramWord(addr,*wdata); //一次写入一个字
datalen -= 4; //长度1表示一个B 减4
addr += 4; //地址一位一字节
wdata++; //wdata形式位uint32_t
}
FLASH_Lock();
}
5.Xmodem协议
前面提到了页写为了匹配Xmodem协议,接下来看看Xmodem协议到底几斤几两。

Xmodem传输数据单位为信息包,信息包格式如下:
---------------------------------------------------------------------------
| Byte1 | Byte2 | Byte3 |Byte4~Byte131| Byte132 |
|-------------------------------------------------------------------------|
|Start Of Header|Packet Number|~(Packet Number)| Packet Data | Check Sum |
---------------------------------------------------------------------------
数据传输流程
#<SOH> 01H
#<EOT> 04H
#<ACK> 06H
#<NAK> 15H
#<CAN> 18H
------------------------------------------------------------------------------
| SENDER | | RECIEVER |
| | <--- | NAK |
| | | Time out after 3 second |
| | <--- | NAK |
| SOH|0x01|0xFE|Data[0~127]|CheckSum| | ---> | |
| | <--- | ACK |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| | ---> | |
| | <--- | NAK |
| SOH|0x02|0xFD|Data[0~127]|CheckSum| | ---> | |
| | <--- | ACK |
| SOH|0x03|0xFC|Data[0~127]|CheckSum| | ---> | |
| | <--- | ACK |
| . | | . |
| . | | . |
| . | | . |
| | <--- | ACK |
| EOT | ---> | |
| | <--- | ACK |
------------------------------------------------------------------------------
持续发’C’ 然后以SOH开头 CRC16结尾发送数据包 开始发数据停止发送’C’
目标每收到一个数据包 且校验CRC16正确发送ACK应答
发送EOT表示结束发送 再次发送最后的ACK应答
我们仅使用Xmodem协议 也就是完成持续发’C’以及CRC校验
while(1)
{
……
Delay_ms(10); //实现间隔1s发送C
if(BootStateFlag & IAP_XMODE_C_FLAG)
{
if(updata_a.XmodemTime >= 100)
{
printf("C");
updata_a.XmodemTime = 0;
}
updata_a.XmodemTime ++;
}
……
}
CRC校验
/*函数功能:生成crc16 ------------------------*/
/*参数:帧 帧长度------------------*/
/*返回值:无----------------------------*/
uint16_t Xmodem_CRC16(uint8_t* data,uint16_t datalen)
{
uint8_t i;
uint16_t CRC_init = 0x0000;
uint16_t CRC_poly = 0x1021;//多项式
while(datalen--)
{
CRC_init = ( *data << 8 ) ^ CRC_init;
for(i = 0;i < 8;i++)
{
if(CRC_init & 0x8000)//1000 0000 0000 0000
{
CRC_init = ( CRC_init << 1 ) ^ CRC_poly;
}
else
{
CRC_init = ( CRC_init << 1 );
}
}
data++;
}
return CRC_init;
}
为了方便管理我们采用结构体来表示。
typedef struct
{
uint8_t UpdataBuf[C8T6_FLASH_PAGE_SIZE]; //读取buffer大小
uint32_t W25Q64_BlockNum; //外部flash的块号
uint32_t XmodemTime; //xmode协议数据发送周期
uint32_t XmodemDa_num; //数据包接收数量
uint32_t XmodemCRC; //程序自己计算的CRC校验 和发来的比较
}UpDataA_CB;
//Xmodem只能一次接收128B
单独使用上述就可以满足要求了。
我们可以直接使用现成的软件SecureCRTPortable


直接发送我们的.bin文件
拓展:
1.将.bin文件直接生成.xmodem文件
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define XMODEM_BLOCK_SIZE 128
#define PAD_CHAR 0x1A // 填充字符
// XMODEM控制字符定义
#define SOH 0x01
#define EOT 0x04
// CRC-16-CCITT计算(XMODEM标准)
uint16_t calculate_crc(const uint8_t *data, size_t length) {
uint16_t crc = 0x0000;
for (size_t i = 0; i < length; ++i) {
crc ^= (uint16_t)data[i] << 8;
for (uint8_t j = 0; j < 8; ++j) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
}
}
return crc;
}
// 写入单个XMODEM数据包
int write_xmodem_block(FILE *out, uint8_t block_num, const uint8_t *data) {
uint8_t header[3] = {
SOH, // 起始字符
block_num, // 块编号
0xFF - block_num // 块编号补码
};
// 写入包头
if (fwrite(header, 1, sizeof(header), out) != sizeof(header)) {
return -1;
}
// 写入数据块
if (fwrite(data, 1, XMODEM_BLOCK_SIZE, out) != XMODEM_BLOCK_SIZE) {
return -1;
}
// 计算并写入CRC
uint16_t crc = calculate_crc(data, XMODEM_BLOCK_SIZE);
uint8_t crc_bytes[2] = { (uint8_t)(crc >> 8), (uint8_t)crc };
if (fwrite(crc_bytes, 1, sizeof(crc_bytes), out) != sizeof(crc_bytes)) {
return -1;
}
return 0;
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <input.bin> <output.xmodem>\n", argv[0]);
return EXIT_FAILURE;
}
FILE *in_file = fopen(argv[1], "rb");
if (!in_file) {
perror("Error opening input file");
return EXIT_FAILURE;
}
FILE *out_file = fopen(argv[2], "wb");
if (!out_file) {
perror("Error opening output file");
fclose(in_file);
return EXIT_FAILURE;
}
uint8_t block[XMODEM_BLOCK_SIZE] = {0};
uint8_t block_num = 1;
size_t bytes_read;
while ((bytes_read = fread(block, 1, XMODEM_BLOCK_SIZE, in_file)) > 0) {
// 填充不足的块
if (bytes_read < XMODEM_BLOCK_SIZE) {
memset(block + bytes_read, PAD_CHAR, XMODEM_BLOCK_SIZE - bytes_read);
}
if (write_xmodem_block(out_file, block_num, block) != 0) {
fprintf(stderr, "Failed to write block %d\n", block_num);
break;
}
// 处理块编号回绕(XMODEM规范允许)
if (++block_num == 0) block_num = 1;
}
// 写入传输结束标记
fputc(EOT, out_file);
fclose(in_file);
fclose(out_file);
printf("Successfully created XMODEM package: %s\n", argv[2]);
return EXIT_SUCCESS;
}
使用gcc编译出可执行文件
gcc xmodem.c -o xmodem_pack
给予文件可执行权限
chmod 777 xmodem_pack
编译.bin文件直接生成.xmodem文件
./xmodem_pack Project.bin project.xmodem

然后我们就可以使用串口助手直接发送文件了。
6.Bootloader命令行
首先我们定义一些标志位(bit标志,个数多,空间小)
//为a区更新flag
#define UPDATA_A_FLAG 0X00000001//0000 0001
//xmode发送C的flag
#define IAP_XMODE_C_FLAG 0X00000002//0000 0010
//xmode发送data的flag
#define IAP_XMODE_DATA_FLAG 0X00000004//0000 0100
//CMD外部flash下载程序
#define BOOT_CMD_WB_FLASH_DOWN 0X00000010//0000 1000
//外部flash通过Xmodem下载程序flag
#define WRITE_WB_FLASH_XMODEM_FLAG 0X00000020//0001 0000
//CMD使用外部flash程序
#define BOOT_CMD_RED_WB_FLASH 0X00000040//0010 0000
倒计时等待
/*函数功能:输入M进入Bootloder命令行 ------------------------*/
/*参数:超时等待时间s------------------*/
/*返回值:1进入 0未进入----------------------------*/
uint8_t BootLoader_Enter(uint8_t Timeout)
{
printf("%ds内,输入大写字母M,进入 BootLoader命令行\r\n",Timeout);
while(Timeout--)
{
Delay_ms(1000);
if(rx1_buffer[0] == 'M')
{
return 1;
}
}
return 0;
}
倒计时超时执行
Load_A(C8T6_A_START_ADDR);
跳转A区,否则进入bootloader命令行
BootLoader_Info();
typedef struct
{
uint32_t OTA_Flag;
//25q64每块真正含多少B 一一对应 其他快有其他用途 每一个值对应对应w25q64的块的大小
uint32_t FileLen[11];//但成员0为表示OTA的大小
uint8_t OTA_ver[32]; //OTA版本号
}OTA_InfoCB;
OTA_InfoCB OTA_Info;
UpDataA_CB updata_a;
//标志位
uint32_t BootStateFlag;
/*函数功能:判断OTA RUN_a ------------------------*/
/*参数:无------------------*/
/*返回值:无----------------------------*/
void BootLoader_Brance(void)
{
if( BootLoader_Enter(5) == 0 )
{
if(OTA_Info.OTA_Flag == OTA_FLAG_SET)
{
printf("OTA更新\r\n");
BootStateFlag |= UPDATA_A_FLAG; //OTA更新 置BootStateFlag第0位为1
updata_a.W25Q64_BlockNum = 0; //OTA事件 使用W25Q64的块0 其他块有其他作用
}
else
{
printf("跳转A分区\r\n"); //不更新 执行a区
Load_A(C8T6_A_START_ADDR); //跳转 a区
}
}
printf("进入 BootLoader命令行\r\n");
BootLoader_Info();
}
命令行
/*函数功能Bootloder命令行 ------------------------*/
/*参数:无------------------*/
/*返回值:无----------------------------*/
void BootLoader_Info(void)
{
printf("(1)擦除A区\r\n");
printf("(2)串口IAP下载A区\r\n");
printf("(3)向外部flash下载程序\r\n");
printf("(4)使用外部flash程序\r\n");
printf("(5)重启\r\n");
}
当我们串口输入对应编号BootStateFlag 就会被&上对应标志,如
#define UPDATA_A_FLAG 0X00000001//0000 0001
…………
BootStateFlag &= UPDATA_A_FLAG;
7.命令对应的API
/*函数功能:Bootloder命令动作 置标志位 while循环才具体实现 一切命令来自串口信息 ------------------------*/
/*参数:串口接收命令数据和长度------------------*/
/*返回值:无----------------------------*/
void BootLoader_Event(uint8_t* data,uint16_t datalen)
{
//顶层命令 选择性调用后续4个函数
BOOT_CMD_process(data,&datalen);
//处理串口IAP事件
IAP_process(data,datalen);
//向外部flash块写程序
WRITE_WB_FLAH_process(data,datalen);
//使用外部flash程序
READ_WB_FLAH_process(data,datalen);
}
API具体实现
void BOOT_CMD_process(uint8_t* data,uint16_t *datalen)
{
if(BootStateFlag == 0) //顶层命令 即初始化时串口最先执行的
{
if(*datalen == 1 && data[0] == '1') //命令1擦除a区
{
printf("擦除A区ing\r\n");
Delay_ms(100);
Erase_Flash(C8T6_A_START_PAGE,C8T6_A_PAGE_NUM); //擦除A区
printf("擦除A区成功\r\n");
BootLoader_Info();
}
else if(*datalen == 1 && data[0] == '2') //命令2IAP下载
{
printf("通过Xmodem协议,串口IAP下载A区程序,请使用.bin文件\r\n");
Delay_ms(100);
Erase_Flash(C8T6_A_START_PAGE,C8T6_A_PAGE_NUM); //擦除A区
BootStateFlag |= ( IAP_XMODE_C_FLAG | IAP_XMODE_DATA_FLAG ); //置位BootStateFlag
updata_a.XmodemTime = 0; //清除发送间隔
updata_a.XmodemDa_num = 0; //接收数据包数量清零
}
else if(*datalen == 1 && data[0] == '3') //命令3向外部flash下载程序
{
printf("向外部flash下载程序,输入需要使用的块编号(1-9)\r\n");
BootStateFlag |= BOOT_CMD_WB_FLASH_DOWN; //置位BootStateFlag
}
else if(*datalen == 1 && data[0] == '4') //命令4使用外部flash程序
{
printf("使用外部flash程序,输入需要读取的块编号(1-9)\r\n");
BootStateFlag |= BOOT_CMD_RED_WB_FLASH; //置位BootStateFlag
}
else if(*datalen == 1 && data[0] == '5') //命令5重启
{
printf("重启ing\r\n");
Delay_ms(100);
printf("重启成功\r\n");
NVIC_SystemReset(); //重启
}
*datalen = 0;
}
}
//串口IAP事件 写A区或者写外部FLASH
void IAP_process(uint8_t* data,uint16_t datalen)
{
uint8_t i;
if(BootStateFlag & IAP_XMODE_DATA_FLAG)
{
//SOH(0X01) 数据序号(0x0?,1开始) 0XFE-0XF0 数据0-127 crc16 1+1+1+128+2=133
if( (datalen == 133) &&(data[0] ==0X01) )
{
BootStateFlag &= ~IAP_XMODE_C_FLAG; //开始接收数据 关闭发送C的功能
updata_a.XmodemCRC = Xmodem_CRC16(&data[3],128);//程序自己求crc 和发来的比对
if(updata_a.XmodemCRC == (( data[131]<<8) | data[132]) )
{
//此处的减一 是因为第一个的偏移量为0 所以减1
updata_a.XmodemDa_num++;
memcpy(&updata_a.UpdataBuf[((updata_a.XmodemDa_num-1) % 8) *128],&data[3],128);
if(updata_a.XmodemDa_num % 8 == 0) //凑满了8个包 即1KB可以写a区flash
{
if(BootStateFlag & WRITE_WB_FLASH_XMODEM_FLAG)
{
//一页256B 分四次写
for(i = 0;i < 4;i++)
{
//updata_a.W25Q64_BlockNum * 64 * 4 块的偏移量 一块64KB
// 64 * 1024 /256 一页是256B
//(updata_a.XmodemDa_num / 8 + 1) * 4 + i xmodem收到的数据包对于页的偏移量
/*-------------------------------------------------------
i | 0 | 1 | 2 | 3 |
8包 | 0页 | 1页 | 2页 | 3 页 |
16包 | 4页 | 5页 | 6 页 | 7页 |
24包 | 8页 | 9页 | 10页 | 11页 |
页数 = (包数 / 8 - 1) * 4 + i
-------------------------------------------------------------*/
W25Q64_Page_write(&updata_a.UpdataBuf[i*256],
updata_a.W25Q64_BlockNum * (64 * 4) + (updata_a.XmodemDa_num / 8 - 1) * 4 + i);
}
}
else
{
Write_Flash( (updata_a.XmodemDa_num/(C8T6_FLASH_PAGE_SIZE / 128) - 1) * C8T6_FLASH_PAGE_SIZE + C8T6_A_START_ADDR,
(uint32_t *)updata_a.UpdataBuf,
C8T6_FLASH_PAGE_SIZE);
}
}
printf("\x06"); //发送ACK
}
else
{
printf("\x15"); //发送NACK
}
}
}
//EOT 结束信号 0x04
if( (datalen == 1) &&(data[0] ==0X04) )
{
printf("\x06"); //发送ACK
if(updata_a.XmodemDa_num % (C8T6_FLASH_PAGE_SIZE / 128) != 0) //有剩余未满八个包的
{
if(BootStateFlag & WRITE_WB_FLASH_XMODEM_FLAG)
{
for(i = 0;i < 4;i++)
{
//此处不减一是因为 这是对于已经写好了的下一篇了
W25Q64_Page_write(&updata_a.UpdataBuf[i*256],
updata_a.W25Q64_BlockNum * 64 * 4 + (updata_a.XmodemDa_num / 8) * 4 + i);
}
}
else
{
//此处不减一是因为 这是对于已经写好了的下一篇了
Write_Flash( (updata_a.XmodemDa_num/(C8T6_FLASH_PAGE_SIZE / 128)) * C8T6_FLASH_PAGE_SIZE + C8T6_A_START_ADDR,
(uint32_t *)updata_a.UpdataBuf,
(updata_a.XmodemDa_num % (C8T6_FLASH_PAGE_SIZE / 128) *128));
}
BootStateFlag &= ~IAP_XMODE_DATA_FLAG; //关闭接收数据功能
if(BootStateFlag & WRITE_WB_FLASH_XMODEM_FLAG)
{
BootStateFlag &= ~ WRITE_WB_FLASH_XMODEM_FLAG;
OTA_Info.FileLen[updata_a.W25Q64_BlockNum] = updata_a.XmodemDa_num * 128;
AT24C02_WriteOTAInfo();
Delay_ms(100);
BootLoader_Info();
}
else
{
Delay_ms(100);
NVIC_SystemReset(); //重启
}
}
}
}
//使能向外部FLASH写
void WRITE_WB_FLAH_process(uint8_t* data,uint16_t datalen)
{
if(BootStateFlag & BOOT_CMD_WB_FLASH_DOWN)
{
if(datalen != 1) return;
if(datalen == 1)
{
//0X31=='1' 0X39=='9'
if(data[0] >= 0X31 && data[0] <= 0X39)
{
updata_a.W25Q64_BlockNum = data[0] - 0X30;
BootStateFlag |= WRITE_WB_FLASH_XMODEM_FLAG | IAP_XMODE_C_FLAG | IAP_XMODE_DATA_FLAG;
updata_a.XmodemTime = 0; //清除发送间隔
updata_a.XmodemDa_num = 0; //接收数据包数量清零
OTA_Info.FileLen[updata_a.W25Q64_BlockNum] = 0;
W25Q64_64K_ERASE(updata_a.W25Q64_BlockNum);
printf("通过Xmodem协议,向外部flash第%d个块下载a区程序,请使用.bin文件\r\n",updata_a.W25Q64_BlockNum);
BootStateFlag &= ~BOOT_CMD_WB_FLASH_DOWN;
}
else
{
printf("块编号数据输入错误\r\n");
}
}
else
{
printf("块编号长度输入错误\r\n");
}
}
}
void READ_WB_FLAH_process(uint8_t* data,uint16_t datalen)
{
if(BootStateFlag & BOOT_CMD_RED_WB_FLASH)
{
if(datalen != 1) return;
if(datalen == 1)
{
//0X31=='1' 0X39=='9'
if(data[0] >= 0X31 && data[0] <= 0X39)
{
updata_a.W25Q64_BlockNum = data[0] - 0X30;
BootStateFlag |= UPDATA_A_FLAG;
BootStateFlag &= ~BOOT_CMD_RED_WB_FLASH;
}
else
{
printf("块编号数据输入错误\r\n");
}
}
else
{
printf("块编号长度输入错误\r\n");
}
}
}
void UPDATA_A_process(void)
{
uint8_t i;
if(BootStateFlag & UPDATA_A_FLAG) //判断BootStateFlag 第一位置1否? 即判断OTA
{
//更新a区
printf("长度%d字节\r\n",OTA_Info.FileLen[updata_a.W25Q64_BlockNum]);
if(OTA_Info.FileLen[updata_a.W25Q64_BlockNum] % 4 == 0) //Flash设置好了只能写4个字节 强制写会进hardfalut
{
Erase_Flash(C8T6_A_START_PAGE,C8T6_A_PAGE_NUM); //先擦除 擦除a区
for(i = 0;i<OTA_Info.FileLen[updata_a.W25Q64_BlockNum] / C8T6_FLASH_PAGE_SIZE;i++)
{
//一个block写 i*1024 写64次满一个block 64k
//updata_a.W25Q64_BlockNum*64*1024 是多块bolck偏移
W25Q64_Readdata(updata_a.UpdataBuf,i*1024 + updata_a.W25Q64_BlockNum*64*1024,C8T6_FLASH_PAGE_SIZE); //读取整页 1k
//i * C8T6_FLASH_PAGE_SIZE + C8T6_A_START_ADDR 起始地址加偏移
Write_Flash(i * C8T6_FLASH_PAGE_SIZE + C8T6_A_START_ADDR,(uint32_t *)updata_a.UpdataBuf,C8T6_FLASH_PAGE_SIZE); //写入内部flash
}
//此处i是上面退出的 是加一了的 直接使用
if(OTA_Info.FileLen[updata_a.W25Q64_BlockNum] % 1024 != 0) //mod1k之后剩余不满1k的读取
{
W25Q64_Readdata(updata_a.UpdataBuf,i*1024 + updata_a.W25Q64_BlockNum*64*1024,OTA_Info.FileLen[updata_a.W25Q64_BlockNum] % 1024);
//***************************************************************************
Write_Flash(i * C8T6_FLASH_PAGE_SIZE + C8T6_A_START_ADDR,(uint32_t *)updata_a.UpdataBuf,OTA_Info.FileLen[updata_a.W25Q64_BlockNum] % 1024);//写入内部flash
//***************************************************************************
}
if(updata_a.W25Q64_BlockNum == 0) //是块0 说明是OTA事件
{
OTA_Info.OTA_Flag = 0; //OTA完毕 更改标志
AT24C02_WriteOTAInfo(); //更改24c02OTA相关信息
}
printf("A区更新完毕\r\n");
NVIC_SystemReset();
}
else
{
printf("长度错误\r\n");
BootStateFlag &= ~UPDATA_A_FLAG; //退出 清楚标志位
}
}
}
上述API就是实现写norlash,读norflash,擦除内部flash,写内部flash的APP区全过程。
代码逻辑并不难,不再讲解代码。
但要注意每次进入其中一个API,要对BootStateFlag清除标志位,如
BootStateFlag &= ~IAP_XMODE_C_FLAG; //开始接收数据 关闭发送C的功能
以及对于写入对于多余不满足一页的数据,为了保障norflash耐用性与内部flash写入规则,应该单独再一次写入。
8.跳转A区,执行应用程序
我们已经将我们的新固件给下载到了A区,那该如何启动呢?
我们定义一个函数指针(其实可以定义在结构体的内部,来实现面对对象的思想,类似于回调函数)
typedef void (*Load_A_Point)(void); //函数指针
让这个函数指针指向我们要执行的地址,或者说代码 然后调用此函数指针,跳转执行
//Load_a 指向了复位向量指针
load_ap = (Load_A_Point)*(uint32_t*)(addr + 4); //设置pc addr要为0X00000004
load_ap(); //取地址指向的函数
跳转A区API
/*函数功能:设置pc 即跳转 ------------------------*/
/*参数:pc指向的目标------------------*/
/*返回值:无----------------------------*/
void Load_A(uint32_t addr)
{
if( (*(uint32_t*)addr >= 0x20000000) && (*(uint32_t*)addr <= 0x20004FFF) )//判断设置sp是否合理 即是否在堆栈
{
MSR_SP(*(uint32_t*)addr);
//Load_a 指向了复位向量指针
load_ap = (Load_A_Point)*(uint32_t*)(addr + 4); //设置pc addr要为0X00000004
load_ap(); //取地址指向的函数
}
else
{
printf("跳转A分区失败\r\n");
}
}
上述代码为什么会有这个判断呢?
if( (*(uint32_t*)addr >= 0x20000000) && (*(uint32_t*)addr <= 0x20004FFF) )//判断设置sp是否合理 即是否在堆栈
9.MDK生成.bin文件
使用指令
D:\keli5\ARM\ARMCC\bin\fromelf.exe --bin --output .\Objects\project.bin .\Objects\project.axf
根据.axf文件编译出.bin文件,注意换成自己的文件路径。
A区不也是一个完整的工程吗,那为什么我没看见启动过程中的中断向量表和堆栈设置呢
其实,我们是对他进行了一个偏移


首先是ROM程序存储区也就是内部flash 偏移了0X00005000
对应A区起始位置(0X5000 = 20480 = 20k)
然后就是中断向量表的设置,当然也包括咱们的栈顶指针设置。
在system_stm32f0x.c找到如下语句
/*!< Uncomment the following line if you need to relocate your vector Table in
Internal SRAM. */
/* #define VECT_TAB_SRAM */
#define VECT_TAB_OFFSET 0x0 /*!< Vector Table base offset field.
This value must be a multiple of 0x200. */
此处就是设置中断向量表起始位置,只不过是偏移量,将其改为如下数值:
#define VECT_TAB_OFFSET (uint32_t)0X5000 /*!< Vector Table base offset field.
This value must be a multiple of 0x200. */
也就正对应与20k
设置了中断向量表偏移,在前面知道中断向量表前4B为栈顶指针,
而栈顶在Bootloader里面是SRAM0X2000 0000也就对应着跳转A区时的验证了,即验证栈的位置。
if( (*(uint32_t*)addr >= 0x20000000) && (*(uint32_t*)addr <= 0x20004FFF) )//判断设置sp是否合理 即是否在堆栈
点击编译就会看到生成了bin文件了:

至此 IAP升级也算是完成了,至于OTA,我们可以构建单纯的MQTT报文,也可以使用WIFI模块如ESP8266的透传模式,至于双AB区无感OTA升级,有兴趣的可在此基础上拓展。

1672

被折叠的 条评论
为什么被折叠?



