硬件使用江科大stm32套件,写一个采用Xmodem-1k协议通过串口升级的bootloader,并预留W25Q接口用于后续的远程升级
功能概述
要实现Xmodem-1k协议通过串口升级的bootloader,需实现以下部分
实现flash分区、操作及状态管理,即实现对flash的boot和app分区,flash的读写和状态管理
实现串口中断接收状态切换,即实现升级命令到接收升级包的状态切换
实现Xmodem-k协议完成升级包的校验与接收
实现接收完升级包后进行新程序位置的跳转执行
升级流程
升级流程主函数如下
上电进行优先级划分与串口初始化
重置IAP状态,即初始化接收模式,清空数据缓冲计数,清除内部Flash擦除标志
做W25Q初始化,并根据是否初始化成功选择升级或直接跳转应用,即升级依赖W25Q初始化(用于后续远程升级)
进入升级流程,根据主机发送开始升级标志进入Xmodem-1k下载模式
接收升级包校验完成后,跳转app地址,执行新程序
int main(void)
{
uint8_t ret = 0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断优先级分组2
Usart1_Init(115200);
Iap_Up_Init(); // 重置Iap全局状态,清除内部Flash擦除标志缓存
ret = W25QXX_Init(); // 初始化W25QXX SPI FLASH,读取ID并重试判断是否为指定型号,依赖W25Q正常工作,本地或者之后支持远程升级都是将数据下载到W25Q中
if(ret == 1)
{
Check_Iap_Up_Data(); // W25Q初始化成功,进行本地或之后支持远程升级检查
}
else
{
Boot_Jump(APP_START_ADDR); // W25Q初始化失败,直接跳转应用
}
Boot_Jump(APP_START_ADDR); // 最终跳转应用
for(;;)
{
}
}
Flash分区、操作及状态管理
Flash的分区划分
c8t6的Falsh为64KB,一页为1KB,即64页。这里使用前16页作为boot占用,后48页用于app使用。
// C8T6 Flash 参数
#define STM32_FLASH_BASE ((uint32_t)0x08000000)
#define FLASH_PAGE_SIZE (1024U)
#define TOTAL_FLASH_PAGES (64U)
#define FLASH_END_ADDR (STM32_FLASH_BASE + 64*1024 - 1)
// Bootloader占用前16KB(16页)
#define BOOTLOADER_SIZE_KB (16U)
#define APP_START_ADDR (STM32_FLASH_BASE + BOOTLOADER_SIZE_KB * 1024)
// 获取某页起始地址
#define ADDR_FLASH_PAGE(n) (STM32_FLASH_BASE + (n) * FLASH_PAGE_SIZE)
实现对于Flash的操作与状态管理
Flash_Erase_Page_Manage
管理Flash页擦除标志,避免重复擦除同一页
维护一个静态数据用于记录每一页是否进行擦除
Get_Flash_PageNumber
通过传入的地址判断其在那一页,通过地址偏移量和Flash页大小得到页数
STMFLASH_Write
将数据写入到内部Flash(需擦除相关页),writeAddr写入的起始地址(word对齐),pBuffer指向写入的内容,numToWrite需要写入的word数量(word即4字节)
通过写入地址和需写入的word数量,得到写入结束地址
通过Get_Flash_PageNumber得到起始和结束的Flash页码
得到每一页的起始地址,对页进行擦除,首先解锁Flash
Flash擦除完成后进行按word写,写完成后对Flash进行上锁
STMFLASH_ReadWord
从Flash中读取一个word,即根据传入的地址,读取32位数据
STM32FLASH_Read
从Flash读取任意长度字节数据,addr即起始地址,buf接收缓冲区,len要读取的字节数
通过len判断要读取多少个word,使用STMFLASH_ReadWord从地址读取,并写入到buf中
IAP_Write_AppBin
将升级固件写入到app区域,appAddr目标写入地址,appBuf指向固件数据,appSize固件总字节数
将需要写入的字节组成word
创建一个全局静态缓冲区,将word写入其中,每次缓冲区满的时候,进行一次STMFLASH_Write
最后将不足一个缓冲区的数组写入Flash
// 函数声明
uint32_t Get_Flash_PageNumber(uint32_t addr);
void STMFLASH_Write(uint32_t writeAddr, uint32_t *pBuffer, uint32_t numToWrite);
uint32_t STMFLASH_ReadWord(uint32_t addr);
void STM32FLASH_Read(uint32_t addr, uint8_t *buf, uint32_t len);
void IAP_Write_AppBin(uint32_t appAddr, uint8_t *appBuf, uint32_t appSize);
int Flash_Erase_Page_Manage(uint8_t flag, uint32_t addr);
串口数据接收与状态切换
实现上电启动时,当串口接收到发送的download字符串时,切换文件下载模式,根据Xmodem-1K协议头判断是否出错
void USART1_IRQHandler(void)
{
uint8_t res;
int i = 0;
unsigned int r_buf_2048_cnt = 0;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
res = USART_ReceiveData(USART1); // 读取串口接收到数据
r_buf_2048_cnt = g_com_iap.r_buf_2048_cnt;
g_com_iap.r_buf_2048[r_buf_2048_cnt] = res;
r_buf_2048_cnt = r_buf_2048_cnt + 1;
if(r_buf_2048_cnt >= 2047)
{
r_buf_2048_cnt = 0;
}
if(g_com_iap.recvfileMod == 1) // 文件下载状态
{
if((g_com_iap.r_buf_2048[0] != XM_STX))
{
r_buf_2048_cnt = 0;
}
}
else if(g_com_iap.recvfileMod == 0)
{
if(r_buf_2048_cnt >= 10)
{
if((g_com_iap.r_buf_2048[r_buf_2048_cnt - 2] == 0x0d) && (g_com_iap.r_buf_2048[r_buf_2048_cnt - 1] == 0x0a))
{
for(i = 0; i < 10; i++)
{
g_com_iap.r_cmdBuf[i] = g_com_iap.r_buf_2048[i];
}
if(com_strncmp(g_com_iap.r_cmdBuf, "download\r\n", 10))
{
g_com_iap.recvfileMod = 1;
}
}
r_buf_2048_cnt = 0;
}
}
g_com_iap.r_buf_2048_cnt = r_buf_2048_cnt;
}
}
实现Xmodem-k协议完成升级包的校验与接收
即IAP的核心,对接受的数据进行接收、校验与跳转(涉及到Xmode-1k协议相关内容,参考上一篇Xmodem-1k内容)
这里定义Xmodem-1k的相关标志,以及核心的com_iap全局结构体
Iap_Up_Init
初始化IAP状态,即初始化接收模式,清空数据缓冲计数,清除内部Flash擦除标志
Check_Iap_Up_Data
上电判断是否需要进行本地下载升级包
上电一段时间内轮询recvfileMod标志,判断是否进入升级路程,若轮询结束未收到升级标志,则直接跳转app地址执行
recvfileMod为1则进入本地Xmodem-1k升级路程,向上位机打印输出升级提醒,并进入down_load_file,根据返回值判断跳转
#define PAR_READ (1) //读
#define PAR_WRITE (2) //写
#define PAR_BACKUP (3) //备份
#define PAR_RECOVERY (4) //恢复
//1K-Xmode 协议头
#define XM_DLY_1S 65536 //在1S内xm_timer被调用的次数,小于65536
#define XM_RETRY 15 //retry次数
#define XM_SOH 0x01 //Xmodem数据头
#define XM_STX 0x02 //1K-xmodem数据头
#define XM_EOT 0x04 //发送结束
#define XM_ACK 0x06 //认可应答
#define XM_NAK 0x15 //不认可应答
#define XM_CAN 0x18 //丛机撤销传输
#define XM_EOF 0x1A //数据包填充
#define XM_OK 0
#define XM_ERR -1
#define MAXRETRANS 25
#define W25Q_PAGE_SIZE 4096 //存储器页大小定义
#define W25Q_ADDR_START 0x000000 //存储器存储起始大小
// iap全局结构体
struct com_iap
{
unsigned int uiHaveNewRemotePack; //0x00 无新固件 0x12345678 有新固件 需存储;远程升级标志
unsigned int uiFileSize; //文件总大小 4字节 需存储;固件总大小
uint8_t recvfileMod; //接收模式,0 为命令模式,1 为文件下载模式
char r_cmdBuf[24]; //文本命令缓冲区 用于检测download\r\n
char r_buf_2048[2048];
unsigned int r_buf_2048_cnt; //串口接收与XModem数据缓冲
unsigned int uiPackedID; //包ID Xmodem数据包编号
char t_buf_2048[2048]; //临时数据缓冲,用于 Flash 读回校验和远程拷贝
};
extern struct com_iap g_com_iap;
void Boot_Jump(u32 new_Code_Addr); // 从指定地址读取MSP和Reset_Handler,设置向量偏移表、MSP并跳转执行应用程序
int Check_Iap_Up_Data(void); // 上电后统一处理本地升级和远程升级逻辑,并在需要时跳转应用
void Iap_Up_Init(void); // 初始化IAP相关状态
down_load_file
实现接收校验并写入Flash逻辑
根据Xmodem-1k协议,给平台发送C,提示平台开始发送升级包,接收到Xmodem-1k的XM_STX数据头,则开始接收
根据Xmodem-k协议,在start_recv进行接收
int down_load_File(void)
{
uint8_t i = 0;
uint8_t getChar;
unsigned char ucStartBuf[1] = {'C'};
g_com_iap.uiPackedID = 1;
for(i = 0; i < XM_RETRY; i++) // 根据Xmodem-1K协议,每间隔1s给平台发送启动字符'C',尝试XM_RETRY此,直到接收到第一个字符
{
Usart1_SendArray(ucStartBuf, 1); // 给调试串口发送数据
Delay_Ms(200);
getChar = g_com_iap.r_buf_2048[0];
if(getChar == XM_STX)
{
break;
}else
{
Delay_Ms(1000);
}
}
while(1)
{
getChar = g_com_iap.r_buf_2048[0];
if(XM_STX == getChar)
{
start_recv();
}
else if(XM_EOT == getChar)
{
com_xmodem_finish();
Boot_Jump(APP_START_ADDR);
break;
}
else if(XM_CAN == getChar)
{
// 从机撤销传输
}
Delay_Ms(200);
}
return 1;
}
start_recv
完成升级包的接收、校验与写入
若数据包头为XM_STX,则正常接收,对收到的数据包进行长度、标志头、CRC、包id和包序进行校验
校验通过,则清空数据缓冲计数,用于下一包接收,并将接收到的数据包使用IAP_Write_AppBin写入到Flash中
写入Flash后,在进行一次crc校验,即对写入的数据计算crc,并再次读取写入的数据包,计算crc。二者进行校验
成功则向平台发送XM_ACK,并更新包id和下一次写入Flash位置,失败则关闭中断并复位
平台根据Xmodem-1k发送XM_EOT,即升级包发生接收完毕,此时返回应答,并跳转Boot_Jump
void start_recv(void)
{
#define FILE_SIZE_1K 1024
#define PacHeadLen 3
static uint32_t curFileSize = 0;
uint16_t crcFlag = 0;
int iErr = 0;
int iRight = 0;
uint16_t crc16ValueR = 0;
uint16_t crc16ValueW = 0;
unsigned char ucSendBuf[1] = {'C'};
unsigned char *pData = (unsigned char *)g_com_iap.r_buf_2048;
crcFlag = xm_crc16_ccitt((unsigned char *)(pData+3), 1024); // 详Xmodem-1k数据包格式
iRight = 1;
iRight = iRight && (g_com_iap.r_buf_2048_cnt == 1029);
iRight = iRight && (pData[0] == XM_STX);
iRight = iRight && (((crcFlag >> 8) == pData[1027])&&(((uint8_t)(crcFlag) == pData[1028])));
iRight = iRight && (((unsigned char)g_com_iap.uiPackedID) == pData[1]);
iRight = iRight && (((unsigned char)pData[1] == (unsigned char)(~pData[2])));
if(1 == iRight)
{
g_com_iap.r_buf_2048_cnt = 0;
IAP_Write_AppBin((APP_START_ADDR + curFileSize*FILE_SIZE_1K), (unsigned char *)(pData + PacHeadLen), FILE_SIZE_1K); // 更新Flash代码
crc16ValueW = xm_crc16_ccitt((unsigned char *)(pData + PacHeadLen), FILE_SIZE_1K);
STM32FLASH_Read((APP_START_ADDR + curFileSize*FILE_SIZE_1K), (unsigned char *)g_com_iap.t_buf_2048, FILE_SIZE_1K); // 读取Flash
crc16ValueR = xm_crc16_ccitt((unsigned char *)g_com_iap.t_buf_2048, FILE_SIZE_1K);
if(crc16ValueW != crc16ValueR)
{
g_com_iap.r_buf_2048_cnt = 0;
printf("*************************Write memory failed**************************\r\n");
printf("***************** ******Device restart attempt************************\r\n");
__set_FAULTMASK(1); // 关闭所有中断
NVIC_SystemReset(); // 复位
}
else
{
g_com_iap.r_buf_2048_cnt = 0;
ucSendBuf[0] = XM_ACK;
Usart1_SendArray(ucSendBuf, 1);
g_com_iap.uiPackedID = g_com_iap.uiPackedID + 1;
curFileSize = curFileSize + 1;
}
}else
{
g_com_iap.r_buf_2048_cnt = 0;
iErr = 1;
}
if(1 == iErr)
{
g_com_iap.r_buf_2048_cnt = 0;
ucSendBuf[0] = XM_NAK; // 发送NAK,通知平台重发当前数据包
Usart1_SendArray(ucSendBuf, 1);
}
g_com_iap.r_buf_2048_cnt = 0;
}
实现接收完升级包后进行新程序位置的跳转执行
Boot_Jump
从指定地址读取MSP和Reset_Handler,设置向量偏移表、MSP并跳转执行应用程序
接收完升级包后,读出栈顶指针和复位向量。
设置中断向量表,设置主堆栈指针
跳转新地址执行程序
void Boot_Jump(u32 new_Code_Addr)
{
uint8_t ReadBuf[8] = {0};
uint32_t msp;
uint32_t reset;
#define IROM_ADDR ((uint32_t)0x08000000)
uint32_t base;
uint32_t offset;
STM32FLASH_Read(new_Code_Addr,ReadBuf,8); //1.读取新固件栈顶指针,及其复位向量
msp = ((uint32_t)ReadBuf[0]) + (((uint32_t)ReadBuf[1])<< 8) + (((uint32_t)ReadBuf[2]) << 16) + (((uint32_t)ReadBuf[3]) << 24);
reset = ((uint32_t)ReadBuf[4]) + (((uint32_t)ReadBuf[5])<< 8) + (((uint32_t)ReadBuf[6]) << 16) + (((uint32_t)ReadBuf[7]) << 24);
base = (new_Code_Addr > IROM_ADDR) ?(NVIC_VectTab_FLASH):(NVIC_VectTab_RAM);
offset = new_Code_Addr - base;
NVIC_SetVectorTable(base,offset); //2.设置中断向量表
__set_MSP(msp); //3.设置主堆栈指针
((void(*)())(reset))(); //4.跳转至内部FLASH特定地址处执行新程序
}
至此,采用Xmodem-1k协议通过串口升级的bootloader编写完毕。