本文主要是对linux端升级单片机程序的功能部分做一些介绍,包括一些软件流程。
2.1 rk3399cpu+gd32f103
2.2 连接方式:串口(115200,8N1)或者iic(本文没有介绍iic)
typedef struct update_flag
{
uint16_t need_update; //需要升级(从back区拷贝到app区)吗?0xffff是需要升级(同时表示升级不成功),0x00ff表示升级成功
uint16_t need_download; //需要下载吗?0xffff是不需要下载(同时表示下载成功),0x00ff表示需要下载,
uint32_t firm_size; //固件大小,下载的值
}update_flag_t;
因为单片机访问flash最小就是16位,这里就定义变量大小就是16位的大小。
flash擦除后的值是0xffff(16位),一般重新烧写(使用调试器烧录bin文件)后,该值一般都是0xffff的默认值。
need_download:表示上位机(linux端或者调试串口的ymodem)发起了升级命令,此时,该标志设置为0xff,iap启动的时候检查该值,如果不是0xffff,则表示进入下载模式。如果是0xffff,则启动app程序,这就是正常使用的模式。
app程序下载成功之后,该1k区域全部被擦除(因为单片机最小擦除是1k),need_download变为0xffff,表示不需要下载,同时把下载下来的md5值会保存到该区域的指定位置。
need_update:(0xffff)表示需要升级操作,就是把download分区的内容拷贝到app分区中,拷贝成功后,修改该值为0xff,表示不再需要升级。
app程序下载成功之后(注意,是完成了download过程),1k区域被擦除,则need_update值是0xffff,表示需要升级
这是本文的主要部分啦。
int main(int argc,char* argv[])
{
char* filename = "./app.bin";
int get_name = 0,c;
int serverflag = 0;//,ret
if(argc != 1)
{
while(1)
{
c = getopt(argc, argv, my_opt);
if (c < 0)
{
break;
}
switch(c)
{
case 'f':
filename = optarg;
get_name = 1;
printf("filename = %s\n",filename);
break;
default:
break;
}
if(get_name)
break;
}
}
uart_init(argc, argv);
if(0 == xymodem_send(filename))
printf("%s is done!\n",argv[0]);
uart_exit() ; //关闭打开的串口
return 0;
}
int xymodem_send(const char *filename)
{
int ret;
int skip_payload = 0;
int timeout = 0;
char data[2] = {0};
int size = 0;
int recv_0x43 = 0;//adcount = 0, remain = 0;
char *buf;
//1.读取bin文件,并且判断md5是否正确,返回bin文件内容的首地址(正常返回时,bin文件已经被读出来了,存在在buf指定的控件)
//md5正确时,对应的md5存放在全局变量md5_readBuf中。
buf = file_read_check(filename,&size);
if(buf == NULL)
{
printf("error : bin_file_read_check\n");
return -1;
}
//2.读取串口缓存中的所有数据,以免被缓存的数据干扰,等待新的数据过来
do
{
ret = UART_ReceiveByte (data, 500); //这是超时读取,500ms的超时
if(!ret && data[0] == 0x43)
recv_0x43 = 1;
}while(ret == 0); //如果有数据就一直读,直到返回-1,表示没有读到数据了。
//3.收到的不是0x43,表示单片机此时没有进入到下载模式,需要发送下载命令,让单片机进入到下载模式
if(!recv_0x43) //没有收到数据,或者收到的不是0x43,表示
{
printf("enter ready_to_update\n");
//4.
if(ready_to_update()) //不等于0就是退出
{
free(buf);
printf("error return : ready_to_update()\n");
return -1;
}
//5.单片机进入到下载模式时,会不断给上位机发送0x43的数据。等待这个数据,用于单片机进入下载模式
do
{
printf("wait for mcu ready ... ... timeout = %d \n",timeout++);
if(timeout >= 600) //10·等待超时退出
{
printf("wait for mcu ready timeout,abort now \n");
free(buf);
return -1;
}
ret = UART_ReceiveByte (data, 1000);
if(ret == 0)
{
printf("3.data[0] = %#x\n",data[0]);
if(data[0] == 0x43)
{
printf("recive 0x43 ----2\n");
break;
}
}
}while(1);
}
else//6.表示读到了0x43,表示单片机此时已经进入到下载模式,在不断给上位机发送0x43的数据。
printf("recive 0x43 ----1\n");
printf("go to update now!!!\n");
//7.一切准备就绪,开始发送流程
Ymodem_Transmit(buf, filename, size);
//8.释放空间
free(buf);
return 0;
}
#define ApplicationAddress 0x8006000
//成功返回buf的地址,否则返回NULL
char* file_read_check(const char *filename,int *filesize)
{
size_t len;
int ret;// fd;
FILE *fin;// *fout;
int size = 0;
int bw = 0;
int readcount = 0;
char md5_value[64] = {0};
int file_offset = 0;
//1.判断文件名的长度,太长了缓存不够,其实意义不大,这里主要考虑就是绝对路径的时候,可能会比较长
len = strlen(filename);
if(len < 5 || len > 63)
{
printf("ERROR: filename length = %ld <5-63>\n",len);
return NULL;
}
//2.只读方式打开
fin = fopen(filename, "rb");
if (fin != NULL)
{
/* 文件打开成功*/
printf("open %s success\r\n",filename);
}
else
{
printf("open %s error\r\n",filename);
return NULL;
}
//3.计算iap的长度,用区分不同单片机的bin文件,所以偏移不一定相同
file_offset = ApplicationAddress & 0x7f00; //iap的偏移全部去掉
fseek(fin, 0, SEEK_END);
size = ftell(fin); //得到文件长度
//4.从bin文件读取md5值
fseek(fin, file_offset-512, SEEK_SET); //读出md5,2023-06-12 增加一个偏移
bw = fread(md5_value, 1, 32, fin);
if(bw != 32)
{
fclose(fin);
printf("ERROR: read bin md5 failed ! ret = %d\n",bw);
return NULL;
}
printf("read bin md5 success! md5_value = %s\n",md5_value);
//5.计算去掉iap+1k的文件长度,这iap+1k部分不要了
fseek(fin, file_offset, SEEK_SET); //读取的位置也是不从0开始
size -= (file_offset); //去掉偏移的字节
printf("file size = %d\r\n", size);
//6.分配缓存,用于把文件的内容全部读出来
char* buf = malloc(size);
if(buf == NULL)
{
printf("error: malloc %d\n",size);
fclose(fin);
return NULL;
}
//7.把文件读出来
do
{
bw = fread(&buf[readcount], 1, size, fin);
readcount += bw;
} while (readcount < size);
printf("file readcount = %d\r\n", readcount);
fclose(fin);
//8.比较md5,对缓存中的内容计算md5,再与刚刚从bin文件中读取的md5进行比较
ret = get_file_md5sum2(buf,size);
if(ret == 0)
{
printf("get_file_md5sum = %s,strlen = %lu\n",md5_readBuf,strlen(md5_readBuf));
//比较文件的md5
if(strcmp(md5_readBuf,md5_value)) //md5异常,为无效bin文件,不能进行升级。
{
printf("md5 compare failed ! please check bin file!!!!\n");
free(buf);
return NULL;
}
}
else //9. md5 获取失败,不进行升级
{
printf("error : get_file_md5sum ret = %d\n",ret);
free(buf);
return NULL;
}
//10.对bin文件内容进行判断,单片机的bin文件0-3这4个字节一定是0x20000000开头
if (((*(uint32_t*)buf) & 0xfFFE0000 ) != 0x20000000)
{
printf("image addr 0 != 0x20000000\n");
printf("ERROR: bad image(bin)!!!!! update cancle!!!,please check bin file!!!");
free(buf);
return NULL;
}
else if(((*(uint32_t*)(buf+4)) & 0xfFFffc00 ) != ApplicationAddress)//单片机的bin文件4-7这4个字节一定是与镜像的偏移有关
{
printf("image addr %#x != ApplicationAddress %#x\n",((*(uint32_t*)(buf+4)) & 0xfFFffc00 ),ApplicationAddress);
printf("ERROR: bad image(bin)!!!!! update cancle!!!,please check again!!!");
free(buf);
return NULL;
}
*filesize = size; //返回文件大小
return buf; //返回文件的内容的首地址
}
这里要说明一下,bin文件的组成部分:
所以实际的app的读取只要读24k之后的部分,这个文件的组合可以参考combin.exe文件的源码。
static int ready_to_update(void)
{
char data[40] = {0};
int offset=0;
uint8_t csum = 0,c,rsum = 0;
int ret;
uint8_t i = 0;
//1.发送请求md5单片机命令,单片机返回32个字节的md5值(实际不止,还有帧头帧尾)
ret = send_update_cmd_tomcu(data,0);
if(ret == 0)
{
rsum = data[34];
data[34] = 0;
printf("Receive mcu checksum: %s\n",data+2);
csum = checksum(data, 34); //帧尾包含校验和,计算这一帧的校验和
if(csum == rsum) //校验和正常
{
if(memcmp(data+2,md5_readBuf,32)==0) //md5 对比,发现与bin文件相同,则不进行升级
{
printf("md5sum memcmp ret = 0,is the same\n");
printf("not need update!!!\n");
return 1;
}
else //md5不同,则继续
{
printf("md5sum different , readyto update\n");
return send_update_cmd_tomcu(NULL,1); //发送准备升级命令,正常返回0,其他返回-1
//return 0;
}
}
else //收到单片机的数据,但是校验和不正常,应该数据有问题,结束升级
{
printf("checksum error csum = %d,rsum = %d\n",csum,rsum);
//uart_exit();
return -1;
}
}
printf("ready to update!\n");
return 0;
}
这是一次ymodem的传输过程,参数1表示数据缓存首地址,参数2表示该文件的名字,参数3表示传输的缓存中数据的字节数。
void Ymodem_PrepareIntialPacket(uint8_t *data, const uint8_t* fileName, uint32_t *length)
{
uint16_t i, j;
uint8_t file_ptr[16];
data[0] = SOH; /* soh表示该帧128字节*/
data[1] = 0x00;
data[2] = 0xff;
//1.填充文件名,这里FILE_NAME_LENGTH限制为64字节
for (i = 0; (fileName[i] != '\0') && (i < FILE_NAME_LENGTH); i++)
{
data[i + PACKET_HEADER] = fileName[i];
}
data[i + PACKET_HEADER] = 0x00; //填入字符串结尾符
//2.填充文件大小,先把int转为字符串
snprintf(file_ptr,sizeof file_ptr-1,"%d ",*length); //增加一个空格
for (j =0, i = i + PACKET_HEADER + 1; file_ptr[j] != '\0' ; )
{
data[i++] = file_ptr[j++];
}
data[i] = 0x00;
//3.继续填入md5值,这里是32字节的字符串
for (j =0, i = i + 1; (md5_readBuf[j] != '\0') && (j < FILE_MD5_LENGTH) ;i++,j++ )
{
data[i] = md5_readBuf[j];
}
//4.其他空间继续填充0
for (j = i; j < PACKET_SIZE + PACKET_HEADER; j++)
{
data[j] = 0;
}
}
所有源代码请参考链接:github