1、什么是IAP?
IAP是In Application Programming的缩写,即在应用编程,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。
可以通过串口、USB、网络、无线等方式进行升级数据的传输。
2、IAP要点
(1)程序分两部分,BOOT和APP分别编写;
(2)flash空间划分(flash空间足够大的情况下,可以分成APP1和APP2进行备份升级)
本文Boot使用0x08000000-0x08003000空间,共12k存放Boot代码(可以根据Boot程序的大小,调整空间),剩下空间存放App代码。
(3)flash可编程(读、写、擦除),本文使用GD32f303,使用以下三个函数完成flash的编程操作。
void FlashWrite(uint16_t len,uint8_t *data,uint32_t addr_start)
{
uint16_t i;
uint16_t temp;
fmc_state_enum fmc_state=FMC_READY;
fmc_unlock();
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
for(i=0;i<len/2;i++)
{
fmc_state=fmc_halfword_program(addr_start, *(uint16_t*)data);
if(fmc_state!=FMC_READY)
{
return;
}
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
data += 2;
addr_start += 2;
}
if(len % 2)
{
temp = *data | 0xff00;
fmc_state=fmc_halfword_program(addr_start,temp);
}
fmc_lock();
}
void FlashRead(uint16_t len,uint8_t *outdata,uint32_t addr_start)
{
uint32_t addr;
uint16_t i;
addr = addr_start;
for(i=0;i<len;i++)
{
*outdata = *(uint8_t*) addr;
addr = addr + 1;
outdata++;
}
}
void FlashErase(uint32_t start, uint32_t end)
{
uint32_t EraseCounter;
fmc_state_enum fmc_state=FMC_READY;
/* unlock the flash program/erase controller */
fmc_unlock();
/* clear all pending flags */
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
/* erase the flash pages */
while(start < end)
{
EraseCounter = start/FLASH_PAGE_SIZE;
fmc_state=fmc_page_erase(EraseCounter*FLASH_PAGE_SIZE);
if(fmc_state!=FMC_READY)
{
return;
}
fmc_flag_clear(FMC_FLAG_BANK0_END);
fmc_flag_clear(FMC_FLAG_BANK0_WPERR);
fmc_flag_clear(FMC_FLAG_BANK0_PGERR);
start += FLASH_PAGE_SIZE;
}
/* lock the main FMC after the erase operation */
fmc_lock();
}
(4)从BOOT跳转到APP的时候需要,关闭中断,判断栈顶地址、设置栈指针。
if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FF30000 ) == 0x20000000)
/*判断栈顶地址是否在合法范围内,*/
{
JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
/* Jump to user application */
Jump_To_Application = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) ApplicationAddress);//设置栈指针
Jump_To_Application();
}
ApplicationAddress
为跳转后App的flash首地址,前面4字节存放的正是栈顶地址。一般arm cortex m内核的cpu,ram起始地址为0x20000000,本文使用的GD32F303VC,ram为48k,范围为0x20000000-0x2000C000,所以要跳转到app之前先检查,确保栈顶地址在0x20000000-0x2000C000范围内。
(5)APP程序编译设置
在编译器里面设置,App程序的flash起始地址,flash空间大小。
(6)App程序设置(中断开和中断向量位置设置)
nvic_vector_table_set(NVIC_VECTTAB_FLASH,0x8003000); //设置向量表起始位置
本文在Boot中没有关闭全局中断,所以不需要开全局中断。
3、YMODEM协议
3.1、YMODEM 帧格式
Ymodem 有两种帧格式,主要区别是信息块长度不一样。
帧头 | 包序号 | 包序号 取反 | 信息块 | 校验 |
---|---|---|---|---|
SOH/STX | PN | XPN | DATA | CRC |
1byte | 1byte | 1byte | 128/1024byte | 2byte |
3.1.1、帧头
帧头表示两种数据帧长度,主要是信息块长度不同。
当帧头为SOH(0x01)时,信息块为128字节;
当帧头为STX(0x02)时,信息块为1024字节。
3.1.2、包序号
数据包序号只有1字节,因此计算范围是0~255;对于数据包大于255的,序号归零重复计算。
3.1.3、帧长度
以SOH(0x01)开始的数据包,信息块是128字节,该类型帧总长度为133字节;
以STX(0x02)开始的数据包,信息块是1024字节,该类型帧总长度为1029字节。
3.1.4、校验
Ymodem采用的是CRC16校验算法,校验值为2字节,传输时CRC高八位在前,低八位在后;CRC计算数据为信息块数据,不包含帧头、包号、包号反码。
3.2、YMODEM起始帧
Ymodem起始帧并不直接传输文件内容,而是先将文件名和文件大小置于数据帧中传输;起始帧是以SOH 133字节长度帧传输,格式如下。
帧头 | 包序号 | 包序号 取反 | 文件名称 | 文件大小 | 填充区 | 校验 |
---|---|---|---|---|---|---|
SOH | 0x00 | 0xFF | filename+0x00 | filesize+0x00 | n字节0x00 | CRC16 |
其中包号为固定为0;
filename为文件名称,文件名称后必须加0x00作为结束;
filesize为文件大小值,文件大小值后必须加0x00作为结束;
余下未满128字节数据区域,则以0x00填充。
可以看出起始帧也是遵守3.1中Ymodem包格式的。
3.3、YMODEM数据帧
Ymodem数据帧传输,在信息块填充有效数据。
传输有效数据时主要考虑的是最后一包数据的是处理,SOH帧和STR帧有不同的处理。
(1)对于SOH帧,若余下数据小于128字节,则以0x1A填充,该帧长度仍为133字节。
(2)对于STX帧需考虑几种情况:
- 余下数据等于1024字节,以1029长度帧发送;
- 余下数据小于1024字节,但大于128字节,以1029字节帧长度发送,无效数据以0x1A填充;
- 余下数据等于128字节,以133字节帧长度发送;
- 余下数据小于128字节,以133字节帧长度发送,无效数据以0x1A填充。
3.4、YMODEM结束帧
Ymodem的结束帧采用SOH 133字节长度帧传输,该帧不携带数据(空包),即数据区、校验都以0x00填充。
帧头 | 包序号 | 包序号 取反 | 信息块 | 校验 |
---|---|---|---|---|
0x01 | 0x00 | 0xff | 128个0x00 | 0x00 0x00 |
3.5、YMODEM握手信号
握手信号由接收方发起,在发送方开始传输文件前,接收方需发送YMODEM_C
(字符C,ASII码为0x43)命令,发送方收到后,开始传输起始帧。
3.6、YMODEM命令
命令 | 命令码 | 说明 |
---|---|---|
SOH | 0x01 | 128字节数据包 |
STX | 0x02 | 1024字节的数据包 |
EOT | 0x04 | 结束传输 |
ACK | 0x06 | 回应 |
NAK | 0x15 | 没回应,需要重传当前数据包 |
CA | 0x18 | 取消传输 |
C | 0x43 | 握手 |
3.7、一个YMODEM传输过程
可以看出YMODEM只有起始帧、数据帧、结束帧的帧长度是133或者1029长度的,除此以外都是一个字节,这也提高了YMODEM的传输效率。
4、YMODEM工具
有些工具是支持YMODEM传输的,比如SecureCRT等。
5、C语言实现YMODEM协议
篇幅有限,本文只列举了核心代码
5.1、YMODEM接收
int32_t Ymodem_Receive (uint8_t *buf)
{
uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD], file_size[FILE_SIZE_LENGTH], *file_ptr, *buf_ptr;
int32_t i, packet_length, session_done, file_done, packets_received, errors, session_begin, size = 0;
/* Initialize FlashDestination variable */
FlashDestination = ApplicationAddress;
for (session_done = 0, errors = 0, session_begin = 0; ;)
{
for (packets_received = 0, file_done = 0, buf_ptr = buf; ;)
{
switch (Receive_Packet(packet_data, &packet_length, NAK_TIMEOUT))
{
case 0:
errors = 0;
switch (packet_length)
{
/* Abort by sender */
case - 1:
Send_Byte(ACK);
return 0;
/* End of transmission */
case 0:
Send_Byte(ACK);
file_done = 1;
break;
/* Normal packet */
default:
if ((packet_data[PACKET_SEQNO_INDEX] & 0xff) != (packets_received & 0xff))
{
Send_Byte(NAK);
}
else
{
if (packets_received == 0) //第一个包
{
/* Filename packet */
if (packet_data[PACKET_HEADER] != 0)
{
/* Filename packet has valid data */
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';
for (i = 0, file_ptr ++; (*file_ptr != ' ') && (i < FILE_SIZE_LENGTH);)
{
file_size[i++] = *file_ptr++;
}
file_size[i++] = '\0';
Str2Int(file_size, &size);
/* Test the size of the image to be sent */
/* Image size is greater than Flash size */
if (size > (FLASH_SIZE - 1)) //文件大小,大于flash容量
{
/* End session */
Send_Byte(CA);
Send_Byte(CA);
return -1;
}
FlashErase(FlashDestination,FlashDestination+size);
Send_Byte(ACK);
Send_Byte(CRC16);
}
/* Filename packet is empty, end session */
else
{
Send_Byte(ACK);
file_done = 1;
session_done = 1;
break;
}
}
/* Data packet */
else
{
memcpy(buf_ptr, packet_data + PACKET_HEADER, packet_length);
RamSource = (uint32_t)buf;
{
FlashWrite(packet_length,buf_ptr,FlashDestination);
FlashDestination+=packet_length;
}
Send_Byte(ACK);
}
packets_received ++;
session_begin = 1;
}
}
break;
case 1:
Send_Byte(CA);
Send_Byte(CA);
return -3;
default:
if (session_begin > 0)
{
errors ++;
}
if (errors > MAX_ERRORS)
{
Send_Byte(CA);
Send_Byte(CA);
return 0;
}
Send_Byte(CRC16);
break;
}
if (file_done != 0)
{
break;
}
}
if (session_done != 0)
{
break;
}
}
return (int32_t)size;
}
5.2、YMODEM发送
uint8_t Ymodem_Transmit (uint8_t *buf, const uint8_t* sendFileName, uint32_t sizeFile)
{
uint8_t packet_data[PACKET_1K_SIZE + PACKET_OVERHEAD];
uint8_t FileName[FILE_NAME_LENGTH];
uint8_t *buf_ptr, tempCheckSum ;
uint16_t tempCRC, blkNumber;
uint8_t receivedC[2], CRC16_F = 0, i;
uint32_t errors, ackReceived, size = 0, pktSize;
errors = 0;
ackReceived = 0;
for (i = 0; i < (FILE_NAME_LENGTH - 1); i++)
{
FileName[i] = sendFileName[i];
}
CRC16_F = 1;
/* Prepare first block */
Ymodem_PrepareIntialPacket(&packet_data[0], FileName, &sizeFile);
do
{
/* Send Packet */
Ymodem_SendPacket(packet_data, PACKET_SIZE + PACKET_HEADER);
/* Send CRC or Check Sum based on CRC16_F */
if (CRC16_F)
{
tempCRC = Cal_CRC16(&packet_data[3], PACKET_SIZE);
Send_Byte(tempCRC >> 8);
Send_Byte(tempCRC & 0xFF);
}
else
{
tempCheckSum = CalChecksum (&packet_data[3], PACKET_SIZE);
Send_Byte(tempCheckSum);
}
/* Wait for Ack and 'C' */
if (Receive_Byte(&receivedC[0], 10000) == 0)
{
if (receivedC[0] == ACK)
{
/* Packet transfered correctly */
ackReceived = 1;
}
}
else
{
errors++;
}
}while (!ackReceived && (errors < 0x0A));
if (errors >= 0x0A)
{
return errors;
}
buf_ptr = buf;
size = sizeFile;
blkNumber = 0x01;
/* Here 1024 bytes package is used to send the packets */
/* Resend packet if NAK for a count of 10 else end of commuincation */
while (size)
{
/* Prepare next packet */
Ymodem_PreparePacket(buf_ptr, &packet_data[0], blkNumber, size);
ackReceived = 0;
receivedC[0]= 0;
errors = 0;
do
{
/* Send next packet */
if (size >= PACKET_1K_SIZE)
{
pktSize = PACKET_1K_SIZE;
}
else
{
pktSize = PACKET_SIZE;
}
Ymodem_SendPacket(packet_data, pktSize + PACKET_HEADER);
/* Send CRC or Check Sum based on CRC16_F */
/* Send CRC or Check Sum based on CRC16_F */
if (CRC16_F)
{
tempCRC = Cal_CRC16(&packet_data[3], pktSize);
Send_Byte(tempCRC >> 8);
Send_Byte(tempCRC & 0xFF);
}
else
{
tempCheckSum = CalChecksum (&packet_data[3], pktSize);
Send_Byte(tempCheckSum);
}
/* Wait for Ack */
if ((Receive_Byte(&receivedC[0], 0xffffff) == 0) && (receivedC[0] == ACK))
{
ackReceived = 1;
if (size > pktSize)
{
buf_ptr += pktSize;
size -= pktSize;
if (blkNumber == (FLASH_IMAGE_SIZE/1024))
{
return 0xFF; /* error */
}
else
{
blkNumber++;
}
}
else
{
buf_ptr += pktSize;
size = 0;
}
}
else
{
errors++;
}
}while(!ackReceived && (errors < 0x0A));
/* Resend packet if NAK for a count of 10 else end of commuincation */
if (errors >= 0x0A)
{
return errors;
}
}
ackReceived = 0;
receivedC[0] = 0x00;
errors = 0;
do
{
Send_Byte(EOT);
/* Send (EOT); */
/* Wait for Ack */
if ((Receive_Byte(&receivedC[0], 0xffffff) == 0) && receivedC[0] == ACK)
{
ackReceived = 1;
}
else
{
errors++;
}
}while (!ackReceived && (errors < 0x0A));
if (errors >= 0x0A)
{
return errors;
}
/* Last packet preparation */
ackReceived = 0;
receivedC[0] = 0x00;
errors = 0;
packet_data[0] = SOH;
packet_data[1] = 0;
packet_data [2] = 0xFF;
for (i = PACKET_HEADER; i < (PACKET_SIZE + PACKET_HEADER); i++)
{
packet_data [i] = 0x00;
}
do
{
/* Send Packet */
Ymodem_SendPacket(packet_data, PACKET_SIZE + PACKET_HEADER);
/* Send CRC or Check Sum based on CRC16_F */
tempCRC = Cal_CRC16(&packet_data[3], PACKET_SIZE);
Send_Byte(tempCRC >> 8);
Send_Byte(tempCRC & 0xFF);
/* Wait for Ack and 'C' */
if (Receive_Byte(&receivedC[0], 0xffffff) == 0)
{
if (receivedC[0] == ACK)
{
/* Packet transfered correctly */
ackReceived = 1;
}
}
else
{
errors++;
}
}while (!ackReceived && (errors < 0x0A));
/* Resend packet if NAK for a count of 10 else end of commuincation */
if (errors >= 0x0A)
{
return errors;
}
do
{
Send_Byte(EOT);
/* Send (EOT); */
/* Wait for Ack */
if ((Receive_Byte(&receivedC[0], 0xffffff) == 0) && receivedC[0] == ACK)
{
ackReceived = 1;
}
else
{
errors++;
}
}while (!ackReceived && (errors < 0x0A));
if (errors >= 0x0A)
{
return errors;
}
return 0; /* file trasmitted successfully */
}
6、升级
6.1、上电复位等待输入操作
可以输入1,2,3选择操作:
1、下载app程序(升级)
2、上传app程序(从mcu读取)
3、执行app程序。
6.2、给mcu下载app程序(升级)并运行app
只需要输入1即可,进入下载(升级)过程,可以看到升级过程由接收方先发送字符“C”与发送发握手,升级成功后,输入3执行app。
6.3、mcu上传app程序(从mcu读取)并另存为文件
注:这里从mcu读取了app所有空间的flash,即244k;可以设置只读取app实际大小空间的flash。
7、说明
YMODEM协议适用于传输文件,如果系统里面有参数设置或读取,YMODEM协议不太适合。
8、GD32F303 YMODEM Boot源码
代码已经在GD32F130、GD32F303上面验证通过,移植到其他mcu上也比较方便;分享GD32F303 YMODEM Boot完整工程源码。