gd32f103vbt6 串口OTA升级3-linux端的部分

news2024/10/7 20:32:51

一. 简介

本文主要是对linux端升级单片机程序的功能部分做一些介绍,包括一些软件流程。

二.硬件部分

2.1 rk3399cpu+gd32f103

2.2 连接方式:串口(115200,8N1)或者iic(本文没有介绍iic)

三、其他需要说明的软件部分

3.1 单片机端分两个部分:iap(用于升级)和app(自己的应用)部分(这两个部分本文不做介绍)。

3.2 linux端做一个升级的app软件,这里称为update_app,本文主要是介绍该软件。

3.3 升级单片机用的bin文件,由iap的bin与app的bin的一个组合文件。由combin.exe在keil编译时调用完成。(本文不介绍该软件)

3.4 单片机flash的分区情况

3.4.1  0 ~(0x5c00-1) : iap程序区,用于存放iap程序

3.4.2  0x5c00~(0x6000-1) : 这个1k用于存放一些标志位,以及程序的md5

3.4.2.1 主要的标志是下载标志,更新标志,结构体定义在单片机程序中

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,表示需要升级

3.4.2.2 md5值也是保存在该(1k)区域,默认我是偏移512字节的位置,相当于写在该区域的中心部分。下载成功之后,该区域也一同被擦除(原来的bin文件对应的md5被删除),然后会重新写入新的md5(对应刚刚下载的app的bin)。

3.4.2.3  firm_size表示app的bin部分的大小,字节数,只是存下来,软件中没有用于判断。

3.4.3  0x6000 ~(0x13000-1): app程序区,单片机的实际功能程序区

3.4.4  0x13000 ~ (0x20000-1) : app程序下载时的缓存区,程序下载时不直接下载到app区,而是先缓存到下载区,下载成功(数据完成后,会进行md5校验)后,才会更新到app区。

 

四、linux端软件的简要分析说明

 这是本文的主要部分啦。

4.1 main函数的流程

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;
}

4.1.1 首先有指定文件名的功能,所以先分析-f的参数,-f指定文件名。

4.1.2 串口初始化,设置波特率这些等,这里不做其他说明了。

4.1.3 xymodem_send,正式进入下载过程,返回0表示下载正常并且成功,其他值表示失败或错误。参数是用于指定bin文件的名称,(可以带路径)。

4.1.4  关闭打开的串口,程序结束

4.2 xymodem_send的流程情况

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;
}

4.2.1 请查看代码中的注释

4.2.2 UART_ReceiveByte 表示从串口读取一个字节,第二个参数用于表示超时,单位ms

4.2.3 file_read_check,看看这个函数的流程

#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文件的源码。

4.2.4 ready_to_update 发命令给单片机准备进入升级状态

4.2.4.1 分两步走,第一步,从单片机读取md5值(保存在单片机的flash的23K后的1K中),用于对比本次bin文件的md5,如果相同,则不进行升级了,如果不同才会进行升级。这里可以防止重复升级。

4.2.4.2 如果不同,则继续发送进入升级状态命令,单片机此时会进入到升级模式。

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;
}

4.2.5 Ymodem_Transmit 函数的流程,这里不多介绍了。

这是一次ymodem的传输过程,参数1表示数据缓存首地址,参数2表示该文件的名字,参数3表示传输的缓存中数据的字节数。

4.2.5.1 但是特别说明一下,第一帧数据进行了一个小修改,默认正常是传输文件名和文件大小的起始帧,我加入了md5值,这个在全局变量md5_readBuf中。

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

5.1 这里从防止出错和重复升级出发,都是通过判断md5来完成的

5.1.1 bin文件内部包含app区的md5值,从而读取的时候,可以判断文件是否正常。

5.1.2  程序本身会从单片机那读回md5值,然后与bin文件的md5进行比较,防止重复升级的问题。

5.2 还有就是会判断bin文件的合法性,这里选择了前8个字节的内容进行判断,基本能够消除误升级的问题。(当然有人恶意要生成的话,还是避免不了了)

5.3 最重要的,就是需要对单片机flash的分区,和bin文件的组成要有一些了解,不然可能不太好理解代码的流程。当然,单片机的程序也是一个配合,所以也要熟悉单片机的流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/766573.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[nlp] GPT

一、联合训练任务 1.1 NTP(Next Token Prediction) gpt预训练的一个目标函数有两个,第一个是基础的下一个词预测任务,选择一个K窗口,将窗口中的K个词的embedding作为条件去预测下一个词。 1.2 TC(Text Classification) 第二个是一个分类任务,一段话给一个标签,然后去预…

Qt 获得QTableview所选中的行的某一列数据

1、点击QtableView控件-》右键-》跳到槽-》选择 2、编写槽函数信息 void XXX::on_tableView_CalTable_clicked(const QModelIndex &index) {int rowindex.row();//获得当前行索引int colindex.column();//获得当前列索引QModelIndex index1 CalViewModel->index(row,2)…

开鸿智谷与华秋达成生态共创合作,共同打造OpenHarmony硬件生态

7月11日&#xff0c;在2023慕尼黑上海电子展现场&#xff0c;开鸿智谷数字产业发展有限公司(以下简称“开鸿智谷”)与深圳华秋电子有限公司(以下简称“华秋”)签署了生态共创战略合作协议&#xff0c;共同推动开源鸿蒙OpenAtom OpenHarmony(以下简称“OpenHarmony”)硬件生态繁…

在线图片处理工具:让您的图片处理更加轻松

在我们日常生活和工作中&#xff0c;图片已经成为了一种非常常见的媒介。然而&#xff0c;有时候我们会遇到一些问题&#xff0c;例如图片过大不能上传或下载等等。在这种情况下&#xff0c;我们需要一个功能强大的图片处理工具&#xff0c;来解决这些问题。今天&#xff0c;我…

Visual Studio 向工程中添加现有文件夹

前言&#xff1a; 在创建C#类库&#xff08;dll&#xff09;工程后&#xff0c;需要把现有的C#文件添加进工程中 步骤1.将所有文件夹复制到工程中 步骤2. 点击这个图标&#xff0c;显示所有文件夹 工程目录下的所有文件夹都会被显示出来 选中需要添加的文件夹&#xff0c;右…

Ceph 应用(CephFS文件存储、块存储、对象存储)

目录 一&#xff1a;创建 CephFS 文件系统 MDS 接口 1、服务端操作 &#xff08;1&#xff09;在管理节点创建 mds 服务 &#xff08;2&#xff09;查看各个节点的 mds 服务 &#xff08;3&#xff09;创建存储池&#xff0c;启用 ceph 文件系统 &#xff08;4&#xff09;…

C#(六十二)之泛型的约束

类型约束 基类约束有两个重要的目的。 1&#xff1a;它允许在泛型类中使用有约束指定的基类成员。 2&#xff1a;确保只能使用支持指定基类或派生类的类型实例。 约束是使用 where 上下文关键字指定的。 下表列出了五种类型的约束&#xff1a; 约束 说明 T&#xff1a;str…

C++初阶 - 2.类和对象(上)

目录 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 4.类的访问限定符及封装 4.1 访问限定符 4.2 封装 5.类的作用域 6. 类的实例化 7.类对象模型 7.1 如何计算类对象的大小 7.2 类对象的存储方式猜测 7.3 结构体内存对齐规则 8. this指针 8.1 this指针的…

网络--练习错题笔记

1、SNMP是简单网络管理协议&#xff0c;与邮件发送无关 发送邮件&#xff1a;SMTP协议&#xff0c;简单邮件管理协议 用户发送邮件是利用SMTP协议将编辑好的邮件送往发送端的邮件服务器 2、mac地址前24位是厂商编号 来自IEEE&#xff0c;后24位来自厂商&#xff0c;区别每一个…

5.0蓝牙模块助力车联网实现简便、安全连接-车载蓝牙模块SKB501参考设计

车载电子系统正向智能化、信息化和网络化方向发展&#xff0c;无线通信技术在汽车等移动系统中有着广泛的应用前景。车联网通过蓝牙技术可以实现与各种设备进行无缝、快速的连接&#xff0c;针对这部分应用需求&#xff0c;SKYLAB研发推出的5.0蓝牙模块SKB501&#xff0c;该模块…

Linux调试器------gdb的使用【Linux系统编程】

目录 背景&#xff1a; 指令&#xff1a; 背景&#xff1a; &#xff08;1&#xff09;debug和release&#xff1a;debug可以调试&#xff08;形成可执行程序时会有调试信息&#xff09;&#xff0c;release不可以。Linux下默认是release版本。如果要以debug版本发布&#…

Spring Boot 3.x 自动配置详解

基于Spring Boot 3.1.0 系列文章 Spring Boot 源码阅读初始化环境搭建Spring Boot 框架整体启动流程详解Spring Boot 系统初始化器详解Spring Boot 监听器详解Spring Boot banner详解Spring Boot 属性配置解析Spring Boot 属性加载原理解析Spring Boot 异常报告器解析Spring Bo…

《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(3)-再识Charles

1.简介 上一篇通过宏哥的介绍想必各位小伙伴或者童鞋们对Charles已经有了一个理性地认识&#xff0c;今天宏哥在从Charles的外貌介绍和分享一下&#xff0c;让小伙伴们或者童鞋们再对Charles有一个感性的认识&#xff0c;今天主要是对Charles的界面进行一个详细的介绍。 2.Ch…

CSS:给子元素设置了浮动,页面缩放的时候,子元素往下掉

前言 给子元素设置了浮动&#xff0c;页面缩放的时候&#xff0c;子元素往下掉 html代码&#xff1a; <div class"father"><div class"child1"></div><div class"child2"></div> </div>css代码 .child1…

SSD 读写擦相关知识

1. 简述闪存的工作原理及存储和记录数据 每个闪存芯片中有海量的存储单元&#xff08;Cell&#xff09;&#xff0c;下图是一个闪存存储单元的示意图&#xff0c;从上到下包括控制栅极、氧化层、浮栅层、隧道氧化层和衬底&#xff1b;左侧是源极&#xff0c;右侧是漏极。电流只…

【如何将无序知识库构建为结构化的语义知识库?《知识图谱:方法、工具与案例》将带你进入崭新的世界】

知识图谱开创了人工智能的新范式&#xff0c;以数据驱动和知识驱动相结合&#xff0c;开启了下一代人工智能&#xff0c;实现了人与人、人与机器、机器与机器的协同协作。此外&#xff0c;知识图谱突破了传统的人工智能研究领域&#xff0c;从广泛的文本、结构化、视觉和时序等…

WMS仓储管理系统项目实施,该如何调研

随着企业业务的不断发展&#xff0c;仓储管理逐渐成为企业竞争力的重要因素之一。为了提高仓储管理的效率和准确性&#xff0c;越来越多的企业选择引入WMS仓储管理系统解决方案。本文将探讨在WMS系统项目实施过程中&#xff0c;如何进行调研以确保项目的成功实施。 一、项目调研…

微信小程序上传图片报错:ReferenceError:that is not defined

微信小程序上传图片报错 问题背景 最近在开发一个微信小程序短视频项目&#xff0c;目前开发到用户中心模块&#xff0c;但是在实现头像上传功能时&#xff0c;头像上传成功&#xff0c;但是不能成功展示 报错ReferenceError:that is not defined 问题原因&#xff1a; 这个…

什么时间是投简历、面试的最佳时间?

什么时间投简历最好呢&#xff1f;面试是在上午好还是下午呢&#xff1f;相信大家在求职的过程中一定都思考过这些问题吧&#xff01;那么&#xff0c;投简历和面试是不是有最佳的时间呢&#xff1f;接下来&#xff0c;小编来告诉你投简历以及面试的最佳时间。 首先&#xff0…

Redis可视化工具(Redis Desktop Manager)

redis是我们平时开发工作中经常用到的非关系型数据库&#xff0c;常用于做数据缓存&#xff0c;分布式锁等。 为了更方便的使用redi&#xff0c;这里给大家推荐一款可视化工具&#xff1a;Redis Desktop Manager。 1.下载与安装 直接到gihub下载&#xff0c;地址 Release 0.…