作为无人机方面做嵌入式编写的飞控总结9.1-固件更新的实现(STM32串口IAP)

    在之前的《STM32串口IAP》一文中,通过传输数据流来升级程序,但是这种"裸"数据的传输方式存在这许多的问题,比如它没有容错机制,不能保证数据的正确传输,还比如说它无法获知升级文件的信息,导致它在判断何时停止接收数据上“犹豫不决”。正式为了解决上面的问题,才引进了YModem协议。
在《YModem协议简介》一文中,已经详细介绍了YModem的协议,这里就不再赘述,这篇文章就来讲讲如何将YModem协议转换成代码,并应用到串口升级的功能中。
还是以我自己的规范工程为例,讲讲走YModem协议的IAP工程的的实现。
1、工程的修改
1)串口升级当然需要用到USART与FLASH了,我的原工程已经添加了串口的库文件stm32f10x_usart.c与stm32f10x_flash.c,所以就不需要在添加这两个文件了。
2) 新建IAP.c和IAP.h 两个文件分别保存到BSP文件夹下的src与inc两个文件中。并将IAP.c文件添加到BSP工程组中。
3) 新建YMoem.c和YMoem.h 两个文件分别保存到BSP文件夹下的src与inc两个文件中。并将YModem.c文件添加到BSP工程组中。
4) 新建Download.c和Download.h 两个文件分别保存到BSP文件夹下的src与inc两个文件中。并将Download.c文件添加到BSP工程组中。
5) 新建Upload.c和Upload.h 两个文件分别保存到BSP文件夹下的src与inc两个文件中。并将Upload.c文件添加到BSP工程组中。
也就是说在BSP的工程组中有:BSP.c、IAP.c、YModem.c、Download.c、Upload.c这几个文件,如下:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
 
2、IAP.c与IAP.h的编写
这个文件与之前在《STM32串口IAP》一文中的IAP.c与IAP.h文件代码相似,只是做了细微的一些调整,不过这里还是仔细讲述下。
同样的 考虑到开发板资源,我采用串口1作为升级的通道,所以原先在规范工程中作为调试接口的串口1的相关代码需要从BSP.c与BSP.h两个文件中完全删除掉,出现此之外还要打开stm32f10x_it.c文件中将串口中断服务程序的相关代码删除掉。
与完成一样,IAP.c中第一个函数就是IAP_Init(), 在这个函数中,配置串口相关的代码,如配置串口引脚,串口时钟,串口属性,串口中断等,具体的代码如下:
 
  

/*************************************************************
Function : IAP_Init
Description: IAP初始化函数,初始化串口1
Input : none
return : none
*************************************************************/
void IAP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;

RCC_APB2PeriphClockCmd(COM1_RCC, ENABLE);//使能 USART2 时钟
RCC_APB2PeriphClockCmd(COM1_GPIO_RCC, ENABLE);//使能串口2引脚时钟

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//配置 USART2 的Tx 引脚类型为推挽式的
GPIO_InitStructure.GPIO_Pin = COM1_TX_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(COM1_GPIO_PORT, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//配置 USART2 的Rx 为输入悬空
GPIO_InitStructure.GPIO_Pin = COM1_RX_PIN;
GPIO_Init(COM1_GPIO_PORT, &GPIO_InitStructure);

USART_InitStructure.USART_BaudRate = 115200;//设置波特率为115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//设置数据位为8位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//设置停止位为1位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //没有硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//发送与接收

USART_Init(COM1,&USART_InitStructure);//串口2相关寄存器的配置
USART_Cmd(COM1,ENABLE);//使能串口2
}

上面的代码中可以看出,这次我没有打开串口中断,而是使用查询法来接收串口数据。
接下去在编写串口的发送接收程序,代码如下:
 
  

/*************************************************************
Function : IAP_SerialSendByte
Description: 串口发送字节
Input : c-要发送的字节
return : none
*************************************************************/
void IAP_SerialSendByte(u8 c)
{
USART_SendData(COM1, c);
while (USART_GetFlagStatus(COM1, USART_FLAG_TXE) == RESET) {}
}

/*************************************************************
Function : IAP_SerialSendStr
Description: 串口发送字符串
Input : none
return : none
*************************************************************/
void IAP_SerialSendStr(u8 *s)
{
while(*s != '\0')
{
IAP_SerialSendByte(*s);
s++;
}
}

/*************************************************************
Function : IAP_SerialGetByte
Description: 接收一个字节数据
Input : none
return : 返回结果值,0-没有接收到数据;1-接收到数据
*************************************************************/
u8 IAP_SerialGetByte(u8 *c)
{
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET)
{
*c = USART_ReceiveData(USART1);
return 1;
}
return 0;
}

可以看到串口接收函数 IAP_SerialGetByte()仅仅是查询下是否有数据接收到,但无论是否接收到数据,程序都不会在这个函数上阻塞。所以为了实现阻塞的效果,还需要稍微做下文章,下面就提前讲讲如何在 IAP_SerialGetByte()的基础上 修改成阻塞的功能,如下获取一个用户输入键值的函数:
 
  

/*************************************************************
Function : IAP_GetKey
Description: 获取键入值
Input : none
return : 返回键值
*************************************************************/
u8 IAP_GetKey(void)
{
u8 data;
while(!IAP_SerialGetByte(&data)){ }
return data;
}

然后再设计Bootload的界面,更《STM32串口IAP》一文给出的界面基本上一样,如下:
 
  

/*************************************************************
Function : IAP_ShowMenu
Description: 显示菜单界面
Input : none
return : none
*************************************************************/
void IAP_ShowMenu(void)
{
IAP_SerialSendStr("\r\n+================(C) COPYRIGHT 2014 Ziye334 ================+");
IAP_SerialSendStr("\r\n| In-Application Programing Application (Version 1.0) |");
IAP_SerialSendStr("\r\n+----command----+-----------------function------------------+");
IAP_SerialSendStr("\r\n| 1: FWUPDATA | Update the firmware to flash by YModem |");
IAP_SerialSendStr("\r\n| 2: FWDWLOAD | Download the firmware from Flash by YModem|");
IAP_SerialSendStr("\r\n| 3: FWERASE | Erase the current firmware |");
IAP_SerialSendStr("\r\n| 4: BOOT | Excute the current firmware |");
IAP_SerialSendStr("\r\n| 5:REBOOT | Reboot |");
IAP_SerialSendStr("\r\n| ?: HELP | Display this help |");
IAP_SerialSendStr("\r\n+===========================================================+");
IAP_SerialSendStr("\r\n\r\n");
IAP_SerialSendStr("STM32-IAP>>");
}

界面与之前相比,唯一的区别在于多了一个FWDWLOAD的选项,提供用户从STM32上下载升级文件的功能。接下去讲讲这些功能的实现。
第一个功能FWUPDATA,更新芯片的升级程序。这部分需要用到YModem接收协议的支持,YModem部分这里暂时不讲,只讲将升级需要的一些基本函数,如下:
 
  

/*************************************************************
Function : IAP_DisableFlashWPR
Description: 关闭flash的写保护
Input : none
return : none
*************************************************************/
void IAP_DisableFlashWPR(void)
{
u32 blockNum = 0, UserMemoryMask = 0;

blockNum = (IAP_ADDR - FLASH_BASE_ADDR) >> 12; //计算flash块
UserMemoryMask = ((u32)(~((1 << blockNum) - 1)));//计算掩码

if((FLASH_GetWriteProtectionOptionByte() & UserMemoryMask) != UserMemoryMask)//查看块所在区域是否写保护
{
FLASH_EraseOptionBytes ();//擦除选择位
}
}

s8 IAP_UpdataParam(s32 *param)
{
u32 i;
u32 flashptr = IAP_PARAM_ADDR;

FLASH_Unlock();//flash上锁
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);//清除flash相关标志位
for(i = 0; i < IAP_PARAM_SIZE; i++)
{
FLASH_ProgramWord(flashptr + 4 * i, *param);
if(*(u32 *)(flashptr + 4 * i) != *param)
{
return -1;
}
param++;
}
FLASH_Lock();//flash解锁
return 0;
}

/*************************************************************
Function : IAP_UpdataProgram
Description: 升级程序
Input : addr-烧写的地址 size-大小
return : 0-OK 1-error
*************************************************************/
s8 IAP_UpdataProgram(u32 addr, u32 size)
{
u32 i;
static u32 flashptr = IAP_ADDR;

FLASH_Unlock();//flash上锁
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);//清除flash相关标志位
for(i = 0; i < size; i += 4)
{
FLASH_ProgramWord(flashptr, *(u32 *)addr);//烧写1个字
if(*(u32 *)flashptr != *(u32 *)addr)//判断是否烧写成功
{
return -1;
}
flashptr += 4;
addr += 4;
}
FLASH_Lock();//flash解锁
return 0;
}

上面给出的三个函数,都是跟flash相关的。第一个函数IAP_DisableFlashWPR(),它的功能是关闭flash写保护。第二个函数IAP_UpdataParam(),它的功能是更新参数,这里的参数实际上值的是文件的大小,每次收到升级文件后,就可以获得升级文件的大小,然后将文件的大小转化成32为的十六进制数,保存在指定的FLASH参数区IAP_PARAM_ADDR,这个地址在IAP.h中定义,之所以要开出这个区域,为的是方便YModem的传输,当要从芯片上下载升级的程序,可读取这块区域获取当前升级程序的大小,然后再发送指定大小的代码,这样就不用当心不知道如何结束发送了。第三个函数IAP_UpdataProgram(),将接收到的数据固化到Flash中,跟《STM2串口IAP》一文中的这个函数相比,它支持了以字为单位的flash烧写,进一步提高了效率。
第二个功能FWDWLOAD,从芯片上下载升级的程序。这不分涉及YModem传输协议,也留在后面再讲。
第三个功能是FWERASE,擦除升级代码区的代码。它的代码实现如下:
 
  

/*************************************************************
Function : IAP_FlashEease
Description: 擦除Flash
Input : size-擦除的大小
return : none
*************************************************************/
void IAP_FlashEease(u32 size)
{
u16 eraseCounter = 0;
u16 nbrOfPage = 0;
FLASH_Status FLASHStatus = FLASH_COMPLETE;

if(size % PAGE_SIZE != 0)//计算需要擦写的页数
{
nbrOfPage = size / PAGE_SIZE + 1;
}
else
{
nbrOfPage = size / PAGE_SIZE;
}

FLASH_Unlock();//解除flash擦写锁定
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);//清除flash相关标志位
for(eraseCounter = 0; (eraseCounter < nbrOfPage) && ((FLASHStatus == FLASH_COMPLETE)); eraseCounter++)//开始擦除
{
FLASHStatus = FLASH_ErasePage(IAP_ADDR + (eraseCounter * PAGE_SIZE));//擦除
IAP_SerialSendStr(".");//打印'.'以显示进度
}
FLASH_ErasePage(IAP_PARAM_ADDR);//擦除参数所在的flash页
FLASH_Lock();//flash擦写锁定
}

可以看到,它与《STM32串口IAP》中擦除程序相比,多一个参数size,即允许擦写指定大小的区域。这样做的好处是,当知道了升级文件文件的大小后,可以擦除升级文件大小相对应的页数,而不像之前那样,擦除升级代码起始位置后面所有的flash空间数据,这样就显著提高了效率。
第四个功能BOOT,即执行升级后的代码。相关代码如下:
 
  

typedef void (*pFunction)(void);
pFunction Jump_To_Application;

/*************************************************************
Function : IAP_JumpToApplication
Description: 跳转到升级程序处
Input : none
return : none
*************************************************************/
void IAP_JumpToApplication(void)
{
u32 JumpAddress;//跳转地址

if(((*(__IO u32 *)IAP_ADDR) & 0x2FFE0000) == 0x20000000)//有升级代码,IAP_ADDR地址处理应指向主堆栈区,即0x20000000
{
JumpAddress = *(__IO u32 *)(IAP_ADDR + 4);//获取复位地址
Jump_To_Application = (pFunction)JumpAddress;//函数指针指向复位地址
__set_MSP(*(__IO u32*)IAP_ADDR);//设置主堆栈指针MSP指向升级机制IAP_ADDR
Jump_To_Application();//跳转到升级代码处
}
}

这段代码的工作原理在《STM32串口IAP》一文已经详细分析过了,这里就不在赘述了。
第五个功能REBOOT,功能是重新显示下菜单界面,这里 给出指定函数来实现,后面会讲到这个功能的实现。
最后一个功能是HELP,显示帮助信息,显示完帮助信息后,重新显示下菜单界面,代码如下:
 
  

/*************************************************************
Function : ShwHelpInfo
Description: 显示帮助信息
Input : none
return : none
*************************************************************/
static void ShwHelpInfo(void)
{
IAP_SerialSendStr("\r\nEnter '1' to updtate you apllication code!");
IAP_SerialSendStr("\r\nRnter '2' to download the firmware from interal flash!");
IAP_SerialSendStr("\r\nRnter '3' to erase the current application code!");
IAP_SerialSendStr("\r\nEnter '4' to go to excute the current application code!");
IAP_SerialSendStr("\r\nEnter '5' to restart the system!");
IAP_SerialSendStr("\r\nEnter '?' to show the help infomation!\r\n");
}

IAP.c的最后一个函数是IAP_WiatForChoose(),实现一个与用户交互的一个过程,代码如下:
 
  

/*************************************************************
Function : IAP_WiatForChoose
Description: 功能选择
Input : none
return : none
*************************************************************/
void IAP_WiatForChoose(void)
{
u8 c = 0;

while (1)
{
c = IAP_GetKey();//获取键值

转自:

STM32串口IAP(YModem)  


猜你喜欢

转载自blog.csdn.net/xiaoxilang/article/details/80664531