上篇文章说了IAP升级的要点,本篇文章通过串口YMODEM实现IAP程序升级。
YMODEM有两种帧格式,主要区别是信息块长度不一样。
帧头 | 包序号 | 包序号 取反 | 信息块 | 校验 |
---|---|---|---|---|
SOH/STX | PN | XPN | DATA | CRC |
1byte | 1byte | 1byte | 128/1024byte | 2byte |
帧头表示两种数据帧长度,主要是信息块长度不同。 当帧头为SOH(0x01)时,信息块为128字节; 当帧头为STX(0x02)时,信息块为1024字节。
数据包序号只有1字节,因此计算范围是0~255;对于数据包大于255的,序号归零重复计算。
以SOH(0x01)开始的数据包,信息块是128字节,该类型帧总长度为133字节; 以STX(0x02)开始的数据包,信息块是1024字节,该类型帧总长度为1029字节。
YMODEM采用的是CRC16校验算法,校验值为2字节,传输时CRC高八位在前,低八位在后;CRC计算数据为信息块数据,不包含帧头、包号、包号反码。
YMODEM起始帧并不直接传输文件内容,而是先将文件名和文件大小置于数据帧中传输;起始帧是以SOH 133字节长度帧传输,格式如下。
帧头 | 包序号 | 包序号 取反 | 文件名称 | 文件大小 | 填充区 | 校验 |
---|---|---|---|---|---|---|
SOH | 0x00 | 0xFF | filename+0x00 | filesize+0x00 | n字节0x00 | CRC16 |
其中包号为固定为0; filename为文件名称,文件名称后必须加0x00作为结束; filesize为文件大小值,文件大小值后必须加0x00作为结束; 余下未满128字节数据区域,则以0x00填充。 可以看出起始帧也是遵守3.1中Ymodem包格式的。
Ymodem数据帧传输,在信息块填充有效数据。 传输有效数据时主要考虑的是最后一包数据的是处理,SOH帧和STR帧有不同的处理。 (1)对于SOH帧,若余下数据小于128字节,则以0x1A填充,该帧长度仍为133字节。 (2)对于STX帧需考虑几种情况:
YMODEM的结束帧采用SOH 133字节长度帧传输,该帧不携带数据(空包),即数据区、校验都以0x00填充。
帧头 | 包序号 | 包序号 取反 | 信息块 | 校验 |
---|---|---|---|---|
0x01 | 0x00 | 0xff | 128个0x00 | 0x00 0x00 |
握手信号由接收方发起,在发送方开始传输文件前,接收方需发送YMODEM_C
(字符C,ASII码为0x43)命令,发送方收到后,开始传输起始帧。
命令 | 命令码 | 说明 |
---|---|---|
SOH | 0x01 | 128字节数据包 |
STX | 0x02 | 1024字节的数据包 |
EOT | 0x04 | 结束传输 |
ACK | 0x06 | 回应 |
NAK | 0x15 | 没回应,需要重传当前数据包 |
CA | 0x18 | 取消传输 |
C | 0x43 | 握手 |
可以看出YMODEM只有起始帧、数据帧、结束帧的帧长度是133或者1029长度的,除此以外都是一个字节,这也提高了YMODEM的传输效率。
有些工具是支持YMODEM传输的,比如SecureCRT等。
篇幅有限,本文只列举了核心代码。
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;
}
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 */
}
可以输入1,2,3选择操作: 1、下载app程序(升级) 2、上传app程序(从mcu读取) 3、执行app程序。
只需要输入1即可,进入下载(升级)过程,可以看到升级过程由接收方先发送字符“C”与发送发握手,升级成功后,输入3执行app。
5.3、mcu上传app程序(从mcu读取)并另存为文件
注:这里从mcu读取了app所有空间的flash,即244k;可以设置只读取app实际大小空间的flash。
6、说明
YMODEM协议适用于传输文件,如果系统里面有参数设置或读取,YMODEM协议不太适合。
代码已经在GD32F130、GD32F303上面验证通过,移植到其他mcu上也比较方便;后台回复“YMODEM”获取GD32F303 YMODEM Boot完整工程源码。