datasheet

STM32串口IAP(YModem)

2016-10-10来源: eefocus关键字: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();//获取键值
IAP_SerialSendByte(c);//串口返回键值
switch(c)
{
case '1'://FWUPDATA固件升级
if(IAP_GetKey() == '\r')//检测回车键
{
IAP_DisableFlashWPR();//关闭写保护
DownloadFirmware();//开始升级
Delay_ms(500);
NVIC_SystemReset();//重启
}
break;
case '2'://FWDWLOAD上传当前固件
if(IAP_GetKey() == '\r') //获取键值
{
UploadFirmware();//开始上传
return;//退出循环
}
break;
case '3'://FWERASE固件擦除
if(IAP_GetKey() == '\r')//检测回车键
{
IAP_SerialSendStr("\r\nErasing...");
IAP_FlashEease(FLASH_SIZE + FLASH_BASE_ADDR - IAP_ADDR);//擦除Flash,升级处后面的flash空间
IAP_SerialSendStr("\r\nErase done!\r\n");
return;//退出循环
}
break;
case '4'://BOOT执行升级程序
if(IAP_GetKey() == '\r')//获取键值
{
IAP_SerialSendStr("\r\nBotting...\r\n");
if(((*(__IO u32 *)IAP_ADDR) & 0x2FFE0000) != 0x20000000)//判断是否有应用程序
{
IAP_SerialSendStr("No user program! Please download a firmware!\r\n");
}
Delay_ms(500);
NVIC_SystemReset();
return;//退出循环
}
break;
case '5'://REBOOT系统重启
if(IAP_GetKey() == '\r')//检测回车键
{
IAP_SerialSendStr("\r\nRebooting...\r\n");
return;//退出循环
}
break;
case '?'://HELP帮助
if(IAP_GetKey() == '\r')
{
ShwHelpInfo();//显示帮助信息
return;//退出循环
}
break;
default:
IAP_SerialSendStr("\r\nInvalid Number! The number should be either 1、2、3、4or5\r\n");
return;//退出循环
}
}
}

具体的实现代码在《STM32串口IAP》已经介绍过了,自己对比理解吧。但需要补充说明的是:上面的DownloadFirmware()和UploadFirmware()分别Download.c与Upload.c文件中实现,后面马上就会讲到。
上面,IAP.c的代码就齐全了,接下去给出IAP.h的代码,如下:

#ifndef __IAP_H__
#define __IAP_H__
#include "stm32f10x.h"

#define FLASH_BASE_ADDR 0x8000000 //Flash基地址
#define IAP_ADDR 0x8005000 //升级代码地址

#define IAP_PARAM_SIZE 1
#define IAP_PARAM_ADDR (FLASH_BASE_ADDR + FLASH_SIZE - PAGE_SIZE) //Flash空间最后1页地址开始处存放参数

#if defined (STM32F10X_MD) || defined (STM32F10X_MD_VL)
#define PAGE_SIZE (0x400) //页的大小1K
#define FLASH_SIZE (0x20000) //Flash空间128K
#elif defined STM32F10X_CL
#define PAGE_SIZE (0x800) //页的大小2K
#define FLASH_SIZE (0x40000) //Flash空间256K
#elif defined STM32F10X_HD || defined (STM32F10X_HD_VL)
#define PAGE_SIZE (0x800) //页的大小2K
#define FLASH_SIZE (0x80000) //Flash空间512K
#elif defined STM32F10X_XL
#define PAGE_SIZE (0x800) //页的大小2K
#define FLASH_SIZE (0x100000) //Flash空间1M
#else
#error "Please select first the STM32 device to be used (in stm32f10x.h)"
#endif


void IAP_Init(void);
void IAP_SerialSendByte(u8 c);
void IAP_SerialSendStr(u8 *s);
u8 IAP_SerialGetByte(u8 *c);
u8 IAP_GetKey(void);
s8 IAP_UpdataParam(s32 *param);
s8 IAP_UpdataProgram(u32 addr, u32 size);
void IAP_FlashEease(u32 size);
void IAP_ShowMenu(void);
void IAP_WiatForChoose(void);
void IAP_JumpToApplication(void);

#endif

IAP_ADDR是目标升级地址;IAP_PARAM_ADDR是保存参数的地址,它的地址在FLash空间最后一页的起始地址处。
 
3、Download.c与Download.h代码的编写
Download.c当然将如何从上位机那里获取升级数据,并将升级数据固化到STM32的内部flash中,它的代码如下:

extern u8 file_name[FILE_NAME_LENGTH];
u8 tab_1024[1024] = {0};
/*************************************************************
Function : DownloadFirmware
Description: 下载升级固件
Input : none
return : none
*************************************************************/
void DownloadFirmware(void)
{
u8 number[10]= " "; //文件的大小字符串
s32 size = 0;

IAP_SerialSendStr("\r\nWaiting for the file to be send...(press 'a' or 'A' to abort)\r\n");
size = YModem_Receive(&tab_1024[0]);//开始接收升级程序
Delay_ms(1000);//延时1s,让secureCRT有足够时间关闭ymodem对话,而不影响下面的信息打印
if(size > 0)
{
IAP_SerialSendStr("+-----------------------------------+\r\n");
IAP_SerialSendStr("Proramming completed successfully!\r\nName: ");
IAP_SerialSendStr(file_name);//显示文件名
YModem_Int2Str(number, size);
IAP_SerialSendStr("\r\nSize:");
IAP_SerialSendStr(number);//显示文件大小
IAP_SerialSendStr("Bytes\r\n");
IAP_SerialSendStr("+-----------------------------------+\r\n");
}
else if(size == -1)//固件的大小超出处理器的flash空间
{
IAP_SerialSendStr("The image size is higher than the allowed space memory!\r\n");
}
else if(size == -2)//程序烧写不成功
{
IAP_SerialSendStr("Verification failed!\r\n");
}
else if(size == -3)//用户终止
{
IAP_SerialSendStr("Aborted by user!\r\n");
}
else //其他错误
{
IAP_SerialSendStr("Failed to receive the file!\r\n");
}
}

首先需要定义一个大小为1024字节的数组tab_1024[1024]用来暂存接收到升级数据。接着在DownloadFirmware()它调用YModem_Receive()函数来完成对升级文件的接胡搜与固化工作,当讲到YModem.c的时候才会讲到它,这里暂不多讲。DownloadFirmware()这个函数会根据YModem_Receive()返回值判断是否正确接收,并根据结构打印相应的消息:返回值大于0,说明正确接收,则打印收到的升级文件的文件名与文件大小;如果返回值等于-1,则说明升级文件的大小太大,没有足够的flash空间可以容纳它;如果返回值等于-2,则说明在接收到数据,但没有烧写成功,如果出现这部分错误,就要检查flash操作相关的代码了;如果返回值为-3,则说明发送端中止了升级文件的传输;其他返回值就表示其他未知原因的错误了。上面的代码中的延时1s放在这里是有目的的,为的是等待YModem完全端来连接,在打印消息,如果没有延时,则串口软件端是不不会打印后面的消息的。
至于Download.h文件非常简单,直接给出代码:

#ifndef __DOWNLOAD_H__
#define __DOWNLOAD_H__
#include "Download.h"

void DownloadFirmware(void);

#endif

4、Upload.c与Upload.h的代码编写
Upload.c实现的是如何将STM32内部的升级代码传送给上位机,它的代码如下:

/*************************************************************
Function : UploadFirmware
Description: 向上位机上传固件
Input : none
return : none
*************************************************************/
void UploadFirmware(void)
{
u32 status = 0;
u32 imageSize = 0;

IAP_SerialSendStr("\r\nBeginning to receive file...(press any key to abort)\r\n");
if(IAP_GetKey() == CRC16)//收到字符'C',便是ymodem询问数据
{
imageSize = *(u32 *)IAP_PARAM_ADDR;//向参数IAP_PARAM_ADDR地址处读取固件的大小
status = YModem_Transmit((u8 *)IAP_ADDR, (u8 *)"Firmware.bin", imageSize);
if(status != 0) //接收错误
{
IAP_SerialSendStr("\r\nError Occured while transmitting file!\r\n");
}
else//接收正确
{
IAP_SerialSendStr("\r\nFile Transmitted successfully!\r\n");
}
}
else//终止接收
{
IAP_SerialSendStr("\r\nAbort by user!\r\n");
}
}

在代码的组开始出,给出了一个判断语句:if(IAP_GetKey() == CRC16),即判断等待接收到字符'C',在《YModem协议简介》中曾经提过,接收端会最先发送一个字符'C'想发送端寻求数据,所以当开始上传时,需要等到接收到这个字符'C',如果在这期间,收到用户的按键输入,则终止传输。UploadFirmware()函数也是调用YModem.c文件中的YModem_Transmit()函数发送数据,这个函数的参数包括固件的读取地址、固件的文件名、固件的大小。这里的固件读取地址就是我们升级地址处IAP_ADDR,文件名我统一定为Firmware.bin文件,固件的大小则需要到之前烧尽flash参数区IAP_PARAM_ADDR区域去读取了。根据YModem_Transmit()函数返回的参数可以判断是否传输成功,如果返回值等于0,则说明传输成功,否则传输错误。
Upload.h代码也很简单,直接给出:

#ifndef __UPLOAD_H__
#define __UPLOAD_H__
#include "stm32f10x.h"

void UploadFirmware(void);

#endif

5、YModem.c与YModem.h代码的编写
在YModem.c的最开始先编写一个个整型转字符串的函数,后面会用到,如下:

/*************************************************************
Function : Int2Str
Description: 整型转化成字符串
Input : str-字符串指针 intnum-转换值
return : none
*************************************************************/
void YModem_Int2Str(u8* str, s32 intnum)
{
u32 i, Div = 1000000000, j = 0, Status = 0;

for (i = 0; i < 10; i++)
{
str[j++] = (intnum / Div) + '0';//数字转化成字符

intnum = intnum % Div;
Div /= 10;
if ((str[j-1] == '0') & (Status == 0))//忽略最前面的'0'
{
j = 0;
}
else
{
Status++;
}
}
}

这个函数功能本来应该是可以用C语言的库函数中itoa()来说实现的(包含stdlib.h头文件),但是对于编译器的这个stdlib头文件没有能实现这个函数,所以只能字节编写上面这个转换函数。
接下去对串口的收发函数进行一层封装(起始其实算不上封装,只是改个名字而已)。如下:

/*************************************************************
Function : YModem_RecvByte
Description: ymodem接收一个字节
Input : c-存放接收到的字节 timeout-超时时间
return : none
*************************************************************/
static s8 YModem_RecvByte(u8 *c, u32 timeout)
{
while(timeout-- > 0)
{
if(IAP_SerialGetByte(c) == 1)
{
return 0;
}
}
return -1;
}

/*************************************************************
Function : YModem_SendByte
Description: 发送一个字节
Input : c-要发送的字节
return : none
*************************************************************/
static void YModem_SendByte(u8 c)
{
IAP_SerialSendByte(c);
}

接下去就可以编写接收一个数据包的函数,代码如下:

/*************************************************************
Function : YModem_RecvPacket
Description: 接收一个数据包
Input : data-存放接收到的数据
length-数据包的长度
timeout-超时时间
return : 0 -正常接收 -1 -接收错误
*************************************************************/
s8 YModem_RecvPacket(u8 *data, s32 *length, u32 timeout)
{
u16 i, packet_size;
u8 c;

*length = 0;
if(YModem_RecvByte(&c, timeout) != 0)//接收数据包的第一个字节
{
return -1;
}
switch(c)
{
case SOH: //128字节数据包
packet_size = PACKET_SIZE; /记录数据包的长度
break;
case STX: //1024字节数据包
packet_size = PACKET_1K_SIZE; //记录数据包的长度
break;
case EOT: //数据接收结束字符
return 0; //接收结束
case CA: //接收中止标志
if((YModem_RecvByte(&c, timeout) == 0) && (c == CA))//等待接收中止字符
{
*length = -1; //接收到中止字符
return 0;
}
else //接收超时
{
return -1;
}
case ABORT1: //用户终止,用户按下'A'
case ABORT2: //用户终止,用户按下'a'
return 1; //接收终止
default:
return -1; //接收错误
}
*data = c; //保存第一个字节
for(i = 1; i < (packet_size + PACKET_OVERHEAD); i++)//接收数据
{
if(YModem_RecvByte(data + i, timeout) != 0)
{
return -1;
}
}
if(data[PACKET_SEQNO_INDEX] != ((data[PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff))
{
return -1; //接收错误
}
*length = packet_size; //保存接收到的数据长度
return 0; //正常接收
}

首先值得一看的是这个函数的参数:u8 *data, s32 *length,对于第一个是指针结构的data,当让是用来保存收到的数据的啦,但是这里的length也使用指针让人难以理解?实际上length设计成指针跟data的效果一样,是为了保存长度值,如果不使用指针,就相当一个局部变量,函数执行完后,就失去了数值,使用指针就是为了保存函数执行到最后的数值。在这个函数中,先接收一个字节,在判断这个字节的信息,这里的情况也分很多种:SOH、STX、EOT、CA、ABORT1、ABORT2。当接收到SOH信号,则表示后面接收到的是128字节的数据包;当收到STX信号,则表示后面接收到的1024字节的数据包;当收到CA,表示中止传输,则直接退出这个函数;当收到的
ABORT1或者ABORT2信号,也是取消传输,函数直接返回。接收到第一个字节后,根据是SOH还是STX决定后面是后128字节数据还是接胡搜1024字节数据。接收数据的过程中,如果接收超时,则直接返回。接收完毕后,还要判断下数据的第二个字节与第三个字节是否为取反关系,他们的取法关系就是YModem数据协议决定的,如果不为取反关系,则表示接受错误,直接返回。如果,最后接收到的数据是正确的,则直接数据长度。还有一点需要说明的是,照YModem传输协议,最后两个字节是CRC检验码,应该在检查下CRC检验码是否正确,这里为了提高接收速度和减少CPU负担,直接省略的CRC的校验,如果有兴趣的话,可以自行添加校验代码。
讲完了接收一个数据包的函数,下面就正式编写YModem接收文件的函数了,先给出代码:

/*************************************************************
Function : YModem_Receive
Description: ymodem接收
Input : buf-存放接收到的数据
return : 0 -发送端传输中止
-1 -固件过大
-2 -flash烧写错误
-3 -用户终止
*************************************************************/
s32 YModem_Receive(u8 *buf)
{
u8 packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD], file_size[FILE_SIZE_LENGTH];
u8 session_done, file_done, session_begin, packets_received, errors;
u8 *file_ptr, *buf_ptr;
s32 packet_length = 0, size = 0;
u32 i = 0,RamSource = 0;

for (session_done = 0, errors = 0, session_begin = 0; ;)//死循环,一个ymodem连接
{
for (packets_received = 0, file_done = 0, buf_ptr = buf; ; )//死循环,不断接收数据
{
switch(YModem_RecvPacket(packet_data, &packet_length, NAK_TIMEOUT))//接收数据包
{
case 0:
errors = 0;
switch(packet_length)
{
case -1: //发送端中止传输
YModem_SendByte(ACK);//回复ACK
return 0;
case 0: //接收结束或接收错误
YModem_SendByte(ACK);
file_done = 1;//接收完成
break;
default: //接收数据中
if((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))
{
YModem_SendByte(NAK);//接收错误的数据,回复NAK
}
else//接收到正确的数据
{
if(packets_received == 0)//接收第一帧数据
{
if(packet_data[PACKET_HEADER] != 0)//包含文件信息:文件名,文件长度等
{
for(i = 0, file_ptr = packet_data + PACKET_HEADER; (*file_ptr != 0) && (i < FILE_NAME_LENGTH); )
{
file_name[i++] = *file_ptr++;//保存文件名
}
file_name[i++] = '\0';//文件名以'\0'结束

for(i = 0, file_ptr++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH); )
{
file_size[i++] = *file_ptr++;//保存文件大小
}
file_size[i++] = '\0';//文件大小以'\0'结束
size = atoi((const char *)file_size);//将文件大小字符串转换成整型

if(size > (FLASH_SIZE -1))//升级固件过大
{
YModem_SendByte(CA);
YModem_SendByte(CA);//连续发送2次中止符CA
return -1;//返回
}
IAP_FlashEease(size);//擦除相应的flash空间
IAP_UpdataParam(&size);//将size大小烧写进Flash中Parameter区
YModem_SendByte(ACK);//回复ACk
YModem_SendByte(CRC16);//发送'C',询问数据
}
else//文件名数据包为空,结束传输
{
YModem_SendByte(ACK);//回复ACK
file_done = 1;/停止接收
session_done = 1;//结束对话
break;
}
}
else //收到数据包
{
memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);//拷贝数据
RamSource = (u32)buf;//8位强制转化成32为数据
if(IAP_UpdataProgram(RamSource, packet_length) != 0) //烧写升级数据
{
YModem_SendByte(CA);
YModem_SendByte(CA);//flash烧写错误,连续发送2次中止符CA
return -2;//烧写错误
}
YModem_SendByte(ACK);//flash烧写成功,回复ACK
}
packets_received++;//收到数据包的个数
session_begin = 1;//设置接收中标志
}
}
break;
case 1: //用户中止
YModem_SendByte(CA);
YModem_SendByte(CA); //连续发送2次中止符CA
return -3; //烧写中止
default:
if(session_begin > 0) //传输过程中发生错误
{
errors++;
}
if(errors > MAX_ERRORS) //错误超过上限
{
YModem_SendByte(CA);
YModem_SendByte(CA);//连续发送2次中止符CA
return 0; //传输过程发生过多错误
}
YModem_SendByte(CRC16); //发送'C',继续接收
break;
}
if(file_done != 0)//文件接收完毕,退出循环
{
break;
}
}
if(session_done != 0)//对话结束,跳出循环
{
break;
}
}
return (s32)size;//返回接收到文件的大小
}

哎,由于网页的code阅读器的问题,导致上面的代码度起来很别扭,千万体谅,你可以将这段代码复制到自己电脑里的C编译器中阅读。这段代码的最开始就有两个死循环,外面一层死循环是一个数据传输的连接的循环,传输链路断开之前,会一直执行这个循环;内一层的死循环是数据接收的循环,数据接收完成之前,会一直执行这个循环。在内循环中,调用YModem_RecvPacket()接收一个数据包,在根据这个函数的返回值进行判断。具体不细讲,否者不知又要打多少字了,读者自己联系上文理解,我只粗略的讲述下。每次接收一个数据包,如果接收的是第一个数据包,即包含了升级文件的信息的那个数据帧,则升级文件的名字、文件大小数据部分拷贝出来,文件大小的数据段部分需要先调用atoi()函数将字符串转换成整型值,然后将这个表示升级文件大小的整型值固化到flash参数存储区IAP_PARAM_ADDR。接收完一帧数据帧后,则发送'C'字符据需所要剩下的数据。后面每收到一帧数据就发送ACK响应字符,然后将数据固化到STM32的flash升级去,同时计算剩余的数据。知道接收到EOT结束传输字符,才结束结束接收,然后分两次跳出内外循环。最后返回接收到的文件大小(整型值)。
上面就将Ymodem下载并固化升级数据的功能实现了,下面就轮到了上传芯片内部升级数据的时候了。
在《YModem协议简介》一文讲过输出的过程,为了完成一文件的传输,需要最先准备YModem标准的数据帧格式包:起始数据帧、传输数据帧、结束数据帧。下面分别讲述。
先是准备起始帧数据包格式,代码如下:

/*************************************************************
Function : YModem_PrepareFirstPacket
Description: 准备第一个数据包,包括文件名与大小
Input : data-要发送的数据包
fileName-文件名
length-文件的大小
return : none
*************************************************************/
void YModem_PrepareFirstPacket(u8 *data, const u8 *fileName, u32 *length)
{
u16 i, j;
u8 file_size[10];

data[0] = SOH; //128字节数据包
data[1] = 0x00; //第一个数据包
data[2] = 0xFF; //data[2] = ~data[1]
for(i = 0; (fileName[i] != '\0') && (i < FILE_NAME_LENGTH); i++)
{
data[i + PACKET_HEADER] = fileName[i];//拷贝文件名
}
data[i + PACKET_HEADER] = '\0';//文件名以'\0'结束

YModem_Int2Str (file_size, *length);//将文件长度转化成字符串
for (j =0, i = i + PACKET_HEADER + 1; file_size[j] != '\0' ; )
{
data[i++] = file_size[j++];//拷贝文件长度
}
for(j = i; j < PACKET_SIZE + PACKET_HEADER; j++)
{
data[j] = 0; //0填充
}
}

参照YModem协议的起始帧数据格式,首先首部为SOH,表示后面接着的是128字节的数据段,接下去的两个位是帧序以及帧序的取反,对于起始帧,帧序自然是0x00,它的取反则为0xFF。这之后,开始拷贝文件名的到数据段中,接着在将要传输文件的大小的数值转换成字符串,再拷贝进文件名后面的数据段中。其他剩余的数据段统一用0x00填充。这样就准备好了第一帧数据帧。
再来准备下第二帧数据帧,它的代码如下:

/*************************************************************
Function : YModem_PrepareDataPacket
Description: 准备数据包
Input : sourceBuf-要发送的数据
data-要发送的数据包
ptkNo-数据包的序号
sizeBlk-要发送的数据长度
return : none
*************************************************************/
void YModem_PrepareDataPacket(u8 *sourceBuf, u8 *data, u8 pktNo, u32 sizeBlk)
{
u16 i, size, packetSize;
u8 *file_ptr;

packetSize = sizeBlk >= PACKET_1K_SIZE ? PACKET_1K_SIZE : PACKET_SIZE;//决定发送128字节数据包还是发送1024字节数据包
size = sizeBlk < packetSize ? sizeBlk : packetSize; //计算数据包中数据的长度
if(packetSize == PACKET_1K_SIZE)//1K字节数据
{
data[0] = STX; //设置数据包首部STX,1024字节数据包
}
else //128字节数据
{
data[0] = SOH; //设置数据包首部SOH,128字节数据
}
data[1] = pktNo; //数据包序号
data[2] = (~pktNo);
file_ptr = sourceBuf; //指向需要发送的数据

for(i = PACKET_HEADER; i < size + PACKET_HEADER; i++)
{
data[i] = *file_ptr++; //拷贝要发送的数据
}
if(size <= packetSize) //数据长度小于128字节
{
for(i = size + PACKET_HEADER; i < packetSize + PACKET_HEADER; i++)
{
data[i] = 0x1A; //数据不够,以0x1A填充
}
}
}

数据帧的数据结构则有两种形式,一种是以STX开头的后面跟着1024字节数据的数据帧,另外一种是以SOH开头的后面跟128字节数据的数据帧。如何判断判断使用何种数据帧?当然是根据剩余数据的长度了,但剩余数据的长度大于128字节的时候,则使用STX开头的数据帧,否者使用SOH开头的数据帧。数据的第二字节与第三字节仍然分别是数据帧的帧序以及帧序的取反。接下去在根据是SOH开头还是STX开头分别拷贝128字节数据和1024字节数据,如果数据无法填充整个数据段,则用0x1A来填充。
最后需要准备的是结束帧的数据结构,它比起上面两种数据结构比较简单,代码如下:

/*************************************************************
Function : YModem_PrepareLastPacket
Description: 准备最后一个数据包
Input : data-要发送搞得数据包
return : none
*************************************************************/
void YModem_PrepareLastPacket(u8 *data)
{
u8 i = 0;

data[0] = SOH; //128字节数据包
data[1] = 0; //序号
data[2] = 0xFF;

for(i = PACKET_HEADER; i < (PACKET_SIZE + PACKET_HEADER); i++)
{
data[i] = 0x00;//数据以0填充,即空数据包
}
}

结束帧的数据的第一个字节仍然以SOH开头,规定它的帧序跟起始帧一样为0x00,帧序的取反当然是0xFF,但是它后面的128字节数据段不存放任何信息,以0x00填充。
其实上面的数据帧还不算完整,为什么?因为还缺了CRC检验,所以下面还需要编写CRC16的检验代码,YModem使用的CRC16-CCITT欧洲标准的生成多项式(想具体了解CRC16检验,参照我的《CRCj检验的C的C代码实现》一文),代码如下:

/*************************************************************
Function : UpdateCRC16
Description: 计算一个字节的CRC16校验码(CRC16-CCITT欧洲标准)
Input : crcIn-上一次的CRC码
byte-一个字节
return : 返回crc码
*************************************************************/
u16 UpdateCRC16(uint16_t crcIn, uint8_t byte)
{
uint32_t crc = crcIn;
uint32_t in = byte|0x100;
do
{
crc <<= 1;
in <<= 1;
if (in&0x100)
++crc; //crc |= 0x01
if (crc&0x10000)
crc ^= 0x1021;
}
while (!(in&0x10000));
return crc&0xffffu;
}

/*************************************************************
Function : Cal_CRC16
Description: 计算数据的CRC码
Input : data-要计算的数据
size-数据的大小
return : 返回计算出的CRC码
*************************************************************/
u16 Cal_CRC16(const uint8_t* data, uint32_t size)
{
uint32_t crc = 0;
const uint8_t* dataEnd = data+size;
while (data crc = UpdateCRC16(crc,*data++);

crc = UpdateCRC16(crc,0);
crc = UpdateCRC16(crc,0);
return crc&0xffffu;
}

接下去需要编写以发送数据包的函数YModem_SendPacket(),代码如下:

/*************************************************************
Function : YModem_SendPacket
Description: 发送数据包
Input : data-要发送的数据
length-发送的数据长度
return : none
*************************************************************/
void YModem_SendPacket(u8 *data, u16 length)
{
u16 i = 0;

while(i < length)
{
YModem_SendByte(data[i]);
i++;
}
}

有了发送数据包的函数,下面就可以开始编写上面准备的三种数据帧。
首先是发送第一帧数据帧,它的代码如下:

/*************************************************************
Function : YModem_TransmitFirstPacket
Description: 传输第一个数据包
Input : sendFileName-文件名
fileSize-文件的大小
return : 0-successed -1 -failed
*************************************************************/
s8 YModem_TransmitFirstPacket(u8 *sendFileName, u32 fileSize)
{
u8 i, ackReceived = 0, errors = 0, receiveChar = 0;
u8 firstPacket[PACKET_SIZE + PACKET_HEADER];
u8 fileName[FILE_NAME_LENGTH];
u16 tempCRC = 0;

for(i = 0; i < (FILE_NAME_LENGTH - 1); i++)
{
fileName[i] = sendFileName[i]; //拷贝文件名
}
YModem_PrepareFirstPacket(firstPacket, fileName, &fileSize);//准备第一个数据包
do
{
YModem_SendPacket(firstPacket, PACKET_SIZE + PACKET_HEADER);//发送第一个数据包
tempCRC = Cal_CRC16(&firstPacket[3], PACKET_SIZE);//计算校验码
YModem_SendByte(tempCRC >> 8); //发送CRC高位
YModem_SendByte(tempCRC & 0xFF); //发送CRC低位

if(((YModem_RecvByte(&receiveChar, NAK_TIMEOUT) == 0) && (receiveChar == ACK))
&& ((YModem_RecvByte(&receiveChar, NAK_TIMEOUT) == 0) && (receiveChar == CRC16)))
{
ackReceived = 1; //先后收到ACK与C标志,才表示接收方接收成功
}
else
{
errors++;
}
}while(!ackReceived && (errors < 0x0A));
if(errors >= 0x0A)
{
return -1;
}
return 0;
}

要发送这一帧数据,需要先将文件名以文件大小通过调用YModem_PrepareFirstPacket()函数拷贝到之前准备好的起始帧的数据段中。然后就可以将这个准备好的起始帧发送出去了,接下去在计算CRC检验码,然后再以高字节在前低字节在后的顺序发送出去。这样就完成了一个完整的数据帧的发送。当发送完毕后,等待先后接收到接收端返回的ACK和C信号,如果这两个字节全部接收到,表示接受端接收成功。如果没有接收成功,errors就会自增,错误的次数大于10次时,则返回-1,提示错误。
接下去是发送数据帧的数据。代码如下:

/*************************************************************
Function : YModem_TransmitDataPacket
Description: 开始传输数据包
Input : buf-要传输的数据
fileSize-要发送文件的大小
return : 0-successed -1 -failed
*************************************************************/
s8 YModem_TransmitDataPacket(u8 *buf, u32 size)
{
u8 *buf_ptr = buf;
u8 blkNumber = 0x01;
u8 ackReceived, errors, receiveChar;
u8 packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
u16 tempCRC = 0;
u32 pktSize = 0;
u32 fileSize = size;

while(fileSize) //数据没有发送完
{
ackReceived = 0;
receiveChar = 0;
errors = 0;
YModem_PrepareDataPacket(buf_ptr, packet_data, blkNumber, fileSize);//准备数据包
do
{
if(fileSize >= PACKET_1K_SIZE) //1024字节数据包
{
pktSize = PACKET_1K_SIZE;
}
else //128字节数据包
{
pktSize = PACKET_SIZE;
}
YModem_SendPacket(packet_data, pktSize + PACKET_HEADER);//发送数据包

tempCRC = Cal_CRC16(&packet_data[3], pktSize);//计算校验码
YModem_SendByte(tempCRC >> 8); //发送CRC高位
YModem_SendByte(tempCRC & 0xFF); //发送CRC低位

if((YModem_RecvByte(&receiveChar, NAK_TIMEOUT) == 0) && (receiveChar == ACK))
{
ackReceived = 1;//收到ACK
if(fileSize > pktSize)
{
buf_ptr += pktSize;//偏移要发送数据的位置
fileSize -= pktSize;//计算剩余的数据
if(blkNumber == (size/1024 + 3))//数据包是否还需发送
{
return -1;
}
else
{
blkNumber++;
}
}
else
{
buf_ptr += pktSize;
fileSize = 0;
}
}
else
{
errors++;
}
}while (!ackReceived && (errors < 0x0A));
if(errors >= 0x0A)
{
return -1;
}
}
return 0;
}

这个函数的绝大部分被while(fileSize)包围了起来,它的作用是一直发送,直到数据发送完毕。先根据文件的剩余大小调用YModem_PrepareDataPacket()函数准备数据帧的数据结构,然后开始发送,接着再发送CRC检验码,这样就完成了一个完整数据帧的发送。发送完毕后,等待接收到ACK回应信号,如果接收到ACK信号,则开始准备下一帧数据,否则errors自增,当错误达到上限,直接返回退出。
然后是结束帧的发送,代码如下:

/*************************************************************
Function : YModem_TransmitLastPacket
Description: 传输最后一个数据包
Input : none
return : 0-successed -1 -failed
*************************************************************/
s8 YModem_TransmitLastPacket(void)
{
u8 ackReceived = 0, receiveChar = 0, errors = 0;
u8 lastPacket[PACKET_SIZE + PACKET_OVERHEAD];
u16 tempCRC = 0;

YModem_PrepareLastPacket(lastPacket);
do
{
YModem_SendPacket(lastPacket, PACKET_SIZE + PACKET_HEADER);//发送数据包
tempCRC = Cal_CRC16(&lastPacket[3], PACKET_SIZE);//计算CRC检验位
YModem_SendByte(tempCRC >> 8); //发送CRC高位
YModem_SendByte(tempCRC & 0xFF);//发送CRC低位

if((YModem_RecvByte(&receiveChar, NAK_TIMEOUT) == 0) && (receiveChar == ACK))
{
ackReceived = 1; //收到ACK
}
else
{
errors++;
}
}while(!ackReceived && (errors < 0x0A));
if(errors >= 0x0A)
{
return -1;
}
return 0;
}

结束帧的发送跟起始帧的过程差不多,这里就不赘述了。
在《YModem协议简介》中可以看到YModem传过程在传输接收后,发送端连续发送了两个EOT信号,所以这里需要编写发送这两个EOT信号的函数。
先是发送以一个EOT结束信号,它的代码如下:

/*************************************************************
Function : YModem_TransmitFirstEOT
Description: 发送第一个EOT结束标志
Input : none
return : 0-successed -1 -failed
*************************************************************/
s8 YModem_TransmitFirstEOT(void)
{
u8 ackReceived = 0, receiveChar = 0, errors = 0;
do
{
YModem_SendByte(EOT);//发送结束标志
if((YModem_RecvByte(&receiveChar, NAK_TIMEOUT) == 0) && (receiveChar == NAK))
{
ackReceived = 1;//收到ACK, 表示对方收到EOT标志
}
else
{
errors++;
}
}while(!ackReceived && (errors < 0x0A));
if(errors >= 0x0A)
{
return -1;
}
return 0;
}

要注意的是第一个EOT信号发送后,需要等待接收NAK信号。如果接收到这个信号,则表示接收端已经明白了之后不再数据包了。
第二EOT与第一个EOT的稍有不同,它的代码如下:

/*************************************************************
Function : YModem_TransmitSecondEOT
Description: 发送第二个EOT结束标志
Input :
return : 0-successed -1 -failed
*************************************************************/
s8 YModem_TransmitSecondEOT(void)
{
u8 ackReceived = 0, receiveChar = 0, errors = 0;
do
{
YModem_SendByte(EOT); //发送结束标志
if(((YModem_RecvByte(&receiveChar, NAK_TIMEOUT) == 0) && (receiveChar == ACK))
&& ((YModem_RecvByte(&receiveChar, NAK_TIMEOUT) == 0) && (receiveChar == CRC16)))
{
ackReceived = 1;//分别收到ACK与C标志,才表示对方同意结束
}
else
{
errors++;
}
}while(!ackReceived && (errors < 0x0A));
if(errors >= 0x0A)
{
return -1;
}
return 0;
}

第二个EOT发送出去后,如果正常的话应该会先后接收到ACK信号与C信号。ACK信号用来回应EOT信号,而C信号则向发送端询问结束帧数据。
终于到最重要的函数了:YModem_Transmit(),用这个函数来发送一个文件,代码如下:

/*************************************************************
Function : YModem_Transmit
Description: ymodem传输文件
Input : buf-要传输的数据
sendFileName-传传输的文件名
filesize-传输的数据大小
return : 0-successed -1 -failed
*************************************************************/
s8 YModem_Transmit(u8 *buf, u8 *sendFileName, u32 fileSize)
{
s8 result = 0;
result = YModem_TransmitFirstPacket(sendFileName, fileSize);//发送一个数据包
if(result != 0) return result;
result = YModem_TransmitDataPacket(buf, fileSize);//发送文件数据
if(result != 0) return result;
result = YModem_TransmitFirstEOT();//发送第一个结束标志
if(result != 0) return result;
result = YModem_TransmitSecondEOT();//发送第二个结束标志
if(result != 0) return result;
result = YModem_TransmitLastPacket();//发送最后一个数据包
if(result != 0) return result;
return 0;;
}

这里千万要注意输出的顺序,尤其是发送两个EOT信号后,在发送结束帧数据。
上面就将YModem.c的代码全部讲完了,下面直接给出YModem.h的代码:

#ifndef __YMODEM_H__
#define __YMODEM_H__
#include "stm32f10x.h"

#define PACKET_SEQNO_INDEX (1) //数据包序号
#define PACKET_SEQNO_COMP_INDEX (2) //包序取反

#define PACKET_HEADER (3) //首部3位
#define PACKET_TRAILER (2) //CRC检验的2位
#define PACKET_OVERHEAD (PACKET_HEADER + PACKET_TRAILER)//3位首部+2位CRC
#define PACKET_SIZE (128) //128字节
#define PACKET_1K_SIZE (1024) //1024字节

#define FILE_NAME_LENGTH (256) //文件最大长度
#define FILE_SIZE_LENGTH (16) //文件大小

#define SOH (0x01) //128字节数据包开始
#define STX (0x02) //1024字节的数据包开始
#define EOT (0x04) //结束传输
#define ACK (0x06) //回应
#define NAK (0x15) //没回应
#define CA (0x18) //这两个相继中止转移
#define CRC16 (0x43) //'C'即0x43, 需要 16-bit CRC

#define ABORT1 (0x41) //'A'即0x41, 用户终止
#define ABORT2 (0x61) //'a'即0x61, 用户终止

#define NAK_TIMEOUT (0x100000)//最大超时时间
#define MAX_ERRORS (5) //错误上限


void YModem_Int2Str(uint8_t* str, int32_t intnum);
s32 YModem_Receive(u8 *buf);
s8 YModem_Transmit(u8 *buf, u8 *sendFileName, u32 fileSize);

#endif

6、main函数的编写
main.c中的代跟《STM32串口IAP》一文中,一模一样,所以这里只贴出代码,就不做详细讲解了。

/*************************************************************
Function : KeyInit
Description: 初始化按键
Input : none
return : none
*************************************************************/
void KeyInit (void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA , &GPIO_InitStructure);
}

/*************************************************************
Function : GetKey
Description: 获取按键状态
Input : none
return : none
*************************************************************/
u8 GetKey (void)
{
return (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8));
}

/*************************************************************
Function : main
Description: main入口
Input : none
return : none
*************************************************************/
int main(void)
{
BSP_Init(); //板子初始化
KeyInit(); //初始化按键
if(!GetKey ()) //按键按下,进入升级界面
{
set: IAP_Init(); //初始化串口
shw: IAP_ShowMenu(); //显示功能菜单
IAP_WiatForChoose(); //等待选择界面
goto shw; //重新显示界面
}
else
{
IAP_JumpToApplication();//跳转到升级出代码执行
goto set;//没有升级程序或者升级程序错误才会执行到这句,然后天转到升级界面
}
}

7、测试
将程序下载进开发板中,然后用串口线连接电脑,打开电脑上的SecureCRT软件。
当系统上电的时候,应为没有升级程序,所以会自动计入bootlader才当,如下:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
  接下去开始下载升级的程序,在SecureCRT输入1 <回车> ,界面上就会提醒发送文件,在等待期间,打印字符‘C’显示:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
然后在菜单栏的Transfer中选择Send YModem,如下:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
  选择好升级文件后,就开始出传输了,当传输完毕后,界面就会显示传输的文件名与文件大小,然后自动执行升级后的程序:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
  接下再实验下上传的功能,重启系统,同时按下按键,再次进入菜单,然后输入2<回车>,就会提示开始接收操作:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
  这时候,如果键盘按下一个键,就会马上退出传输,然后重启界面:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
  重新输入2,然后在SecureCRT的菜单栏点击Transfer下的Receive YModem,则开始往上位机上传升级的文件:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
传输成功后,会提示传输成功信息,然后会重启下界面:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
   这时候,可以自SecureCRT的指定目录下找到一个名为Firmware.bin的文件,它的文件大小于之前的升级文件相同:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
  再来试验下功能3:FWERASE,在界面中输入3<回车>,就开始擦出flash区域:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
  重新下载升级文件后,进入菜单界面,输入4<回车>,然后系统就开始运行升级后的代码了:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
下面再来实验下功能4:REBOOT,在界面中输入5<回车>,就会重启菜单界面了:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
  最后再检测下功能6:HELP,输入?<回车>,就开始显示帮助信号,然后界面就会重启:
STM32串口IAP(YModem) - ziye334 - ziye334的博客
以上,就是使用YModem协议实现串IAP的全部内容!

关键字:STM32  串口  IAP

编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/article_2016101030308.html
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:MSP430 PWM
下一篇:基于射频技术和单片机技术的公交车自动报站系统设计

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

STM32解决:st-link连接下载程序的问题

STM32解决:Error: Flash Download failed - "Cortex-M3"本人由于使用普中科技的stm32 的开发板的 USB的下载的地方坏了,所以不得不使用arm仿真器 st-link 进行下载。鼓捣了半天下面总结一下几个问题:1、st-link的驱动下载首先你插上st-link的时候,电脑的设备管理器这个地方是有感叹号的,说明还没有装好驱动,所以我就在网上找啊找。终于根据:win8【笔者没这个系统,无法测试,请大家测试后报告】:http://pan.baidu.com/s/1sjJQxZn(转载来自:https://blog.csdn.net/imxiangzi/article
发表于 2019-07-19
STM32解决:st-link连接下载程序的问题

解决stm32f103通过stlink不能烧录程序问题

问题:   stm32(stm32f103c8T6)开发板只能通过串口烧录程序,而st—link居然不行描述:解决:st-link固件升级用stm32cubemx快速开发时没有配置好调试模式重新生成代码就可以了如果还是不行的话,就得升级一下stlink固件了,具体升级方法可百度
发表于 2019-07-19
解决stm32f103通过stlink不能烧录程序问题

STM32下载不成功问题汇总

在某宝上买了五个最小系统核心板是STM32F103C8T6的芯片,刚拿到手准备下载程序调试,上电后板子自带LED闪烁,这是商家自己下载的示例程序,说明芯片工作着,用KEIL4进行下载自己程序,把自己编译好的程序下载。用的JLINK的四线下载调试下载口,SW的调试接口,点击下载后发现擦除成功,下载失败,提示:Load "..\Output\STM32-DEMO.axf" Set JLink Project File to "F:文件RFID程序电机USERJLinkSettings.ini"* JLink Info: Device "STM32
发表于 2019-07-19
STM32下载不成功问题汇总

STM32高级开发(11)-使用GDB调试你的工程

/scripts/target/stm32f4x_stlink.cfg在执行完此条指令后该终端就会一直执行OpenOCD的程序了,不要关闭它,我们再打开一个终端界面,进入我们的工程目录,比如我这里进入的就是我的libopencm3样例工程下的blink子工程目录。$ cd '/home/yangliu/workspace/libopencm3-my-example/blink'然后我们使用指令输入调试文件并打开GDB程序。$ arm-none-eabi-gdb blink.elf 然后我们在GDB的指令界面中,输入连接指令,连接本地的3333端口。(gdb)target remote localhost:3333此时
发表于 2019-07-19
STM32高级开发(11)-使用GDB调试你的工程

STM32F4标准外设库模板工程建立与使用

SW4STM32安装其实固件库安装过程很简单,在第一次新建工程时会提示选择使用Stdperiph 驱动还是Cube HAL,由于Stm32官方大力推行Cube HAL固件库,所以Cube HAL的固件库直接可以从网上直接一键下载安装。然而对于老的StdPeriph固件库不能一键式下载安装,会提示出错。所以,我们需要自己下载一个.zip固件包,放在C:UsersLYAppDataRoamingAc6SW4STM32firmwares文件夹下,其中的LY就是计算机的用户名。然后新建工程时在选择Stdperiph固件时会自动解压缩,这样就能使用该库进行编译了。界面如下所示:工程配置器件与时钟或者,修改晶振与时钟,根据注释可以算得
发表于 2019-07-19
STM32F4标准外设库模板工程建立与使用

基于STM32的外设的GPIO外设设置总结

1、背景外设驱动的寄存器设置对于外设功能正常运行异常重要。现在对GPIO的配置进行总结。2、GPIO的配置总结复用GPIO配置GPIO设置为输出或者是复用模式时,需要设置输出速度;而无论设置为什么模式,都要对GPIO的内部上下拉进行设置。注意:在输入模式(普通输入/模拟输入)下,OTYPE和OSPEED参数无效!!
发表于 2019-07-19
基于STM32的外设的GPIO外设设置总结

小广播

何立民专栏

单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved