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

一. 简介

本文主要是对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;//,retif(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));//比较文件的md5if(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.其他空间继续填充0for (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.mzph.cn/news/2385.shtml

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

相关文章

WebRTC不同方案对比

1.功能上会有一些出入&#xff0c;尤其是国内的metaRTC版本迭代很快&#xff0c; 2.后续的ffmpeg也在进行支持webrtc特性&#xff0c;obs新的版本好像已经支持了webrtc&#xff0c; 3.对于webrtc部分缺少的信令部分的标准化也有了对应的标准whip和whep协议 所以&#xff0c;如…

一道SQL题

有个搞数仓的朋友不知道从哪儿弄了个题。。。 做了做体验了一下。。。 记录记录。 分析 要保证每天都要做新题 5天必须都做题&#xff0c;不然GG 最后一天必须做新题&#xff0c;如果最后一天做新题了&#xff0c;前面那几天没做新题&#xff0c;做的是老题 最后一天&#…

QT:问题、解决与原因

在这里记录一些自己遇到的在QT开发上面的小问题和tips 目录 QComboBox 设置qss样式不生效qt按钮设置点击释放效果实现效果 QComboBox 设置qss样式不生效 我设置的样式是&#xff1a; box->setStyleSheet("QComboBox {""border: none;""padding:…

支付宝支付上线准备工作(商家自研接入)

商家自研流程 1.创建应用 登录支付宝开放平台创建 网页/移动应用 说明&#xff1a;生成的应用唯一标识 APPID 可用于调用开放产品接口。 2.配置应用 2.1 应用详情 2.2 产品绑定 2.3 开发设置 在开发 > 开发设置 中配置应用信息。 2.3.1 接口加密方式 按操作步骤进行…

抖音seo源码部署/开源不加密可二开/抖音seo优化开发方案

一、前言 抖音是目前国内非常流行的短视频平台之一&#xff0c;用户数量庞大&#xff0c;更是吸引了许多企业和个人在上面开设账号&#xff0c;通过发布内容来进行流量变现。但是&#xff0c;在一个账号发布内容的同时&#xff0c;管理员又需要同时关注多个账号&#xff0c;对账…

mysql中的行格式之compact格式分析

mysql中的行格式之compact格式分析 mysql行格式 所谓行格式&#xff0c;就是指mysql一行数据的存储格式。 InnoDB 储存引擎支持有四种行储存格式&#xff1a;Compact、Redundant、Dynamic 和 Compressed。 Redundant是很古老的行格式了&#xff0c;因为占用空间最多&#x…

基于深度学习的高精度道路瑕疵检测系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度道路瑕疵&#xff08;裂纹&#xff08;Crack&#xff09;、检查井&#xff08;Manhole&#xff09;、网&#xff08;Net&#xff09;、裂纹块&#xff08;Patch-Crack&#xff09;、网块&#xff08;Patch-Net&#xff09;、坑洼块&#x…

【C++】模板进阶—非类型模板参数、模板特化及模板的分离编译

&#x1f680; 作者简介&#xff1a;一名在后端领域学习&#xff0c;并渴望能够学有所成的追梦人。 &#x1f681; 个人主页&#xff1a;不 良 &#x1f525; 系列专栏&#xff1a;&#x1f6f8;C &#x1f6f9;Linux &#x1f4d5; 学习格言&#xff1a;博观而约取&#xff0…

什么是云应用程序?

应用程序优先的云服务的日益普及导致应用程序与云服务的融合程度比以前更深。应用程序和云之间的运行时边界正在从虚拟机转移到容器和函数。集成边界正在从仅访问数据库和消息代理转向应用程序的机械部分混合并在云中运行的边界。在这个最终架构中&#xff0c;应用程序是“云绑…

pwm呼吸灯

文章目录 一、呼吸灯二、代码实现三、引脚分配 一、呼吸灯 呼吸灯是指灯光在微电脑的控制之下完成由亮到暗的逐渐变化&#xff0c;使用开发板上的四个led灯实现1s间隔的呼吸灯。 二、代码实现 c module pwm_led( input clk ,input rst_n ,output reg [3:0] led ); …

photoshop制作法线和凹凸贴图

做个选区 Ctrlj 法线贴图 生成凹凸贴图

Spring框架的创建和使用

目录 Spring框架概述 什么是Spring框架 什么是容器 什么是IoC容器 Spring的核心功能 IoC容器和普通程序开发的区别 DI Spring项目的创建和使用 Spring项目创建 创建一个maven项目 添加Spring框架支持 添加一个启动类 Spring项目的使用 存储Bean对象 获取并使用B…

应用级监控方案Spring Boot Admin

1.简介 Spring Boot Admin为项目常用的监控方式&#xff0c;可以动态的监控服务是否运行和运行的参数&#xff0c;如类的调用情况、流量等。其中分为server与client&#xff1a; server&#xff1a; 提供展示UI与监控服务。client&#xff1a;加入server&#xff0c;被监控的…

vue3,elementPlus和自己封装,点击 新增添加表单,删除表单,提交数据

ElementPlus下的form也有新增表单 如果你写H5等没找到合适的 自己也可以进行封装 实现3个代码讲解:1&#xff1a;ElementPlus的代码 2&#xff1a;自己书写的代码 3&#xff1a;自己把2的代码进行封装 1&#xff1a;ElementPlus的运行效果 点击提交 1&#xff1a;ElementPlus…

Python教程(3)——python开发工具vscode的下载与安装

Python的开发工具有很多款&#xff0c;很多都是非常好用的&#xff0c;其中vscode作为其中一款Python的开发工具&#xff0c;是非常轻量级的&#xff0c;今天我们来介绍一下vs code的下载与安装。 vscode的下载与安装 首先需要到vscode的官网&#xff0c;这个谷歌或者百度一下…

VSCode 注释后光标快速定位下一行

VSCode默认用 Ctrl / 注释一行时&#xff0c;光标停留在该行中。下面介绍如何注释后&#xff0c;光标会自动移动到下一行。 1.【View】 ->【Extensions】->【查找并安装Multi-command 扩展】 2.【File 】 -> 【Preferences 】->【Keyboard Shortcuts】&#xff08…

【人工智能】xAI——“X宇宙”又增添了一位新成员

个人主页&#xff1a;【&#x1f60a;个人主页】 &#x1f31e;热爱编程&#xff0c;热爱生活&#x1f31e; 文章目录 前言xAI团队成员做解开宇宙本质的AI 前言 有人问他&#xff0c;xAI公司是干啥的&#xff1f;马斯克的回答引用了其偶像、科幻作家道格拉斯・亚当斯的话&…

Python实现将pdf,docx,xls,doc,wps,zip,xlsx,ofd链接下载并将文件保存到本地

前言 本文是该专栏的第31篇,后面会持续分享python的各种干货知识,值得关注。 在工作上,尤其是在处理爬虫项目中,会遇到这样的需求。访问某个网页或者在采集某个页面的时候,正文部分含有docx,或pdf,或xls,或doc,或wps,或ofd,或xlsx,或zip等链接。需要你使用python自…

【运维小知识】(四)——linux常用命令

运维专栏&#xff1a;运维小知识 目录 1.&#x1f341;&#x1f341;用mv命令修改文件名 2.&#x1f343;&#x1f343;创建及删除文件夹即文件夹下所有文件 3.&#x1f342;&#x1f342;移动文件夹并重命名 4.&#x1f33f;&#x1f33f;复制文件 5.&#x1f344;&#x…

Python 算法基础篇之字符串操作:索引、切片、常用方法

Python 算法基础篇之字符串操作&#xff1a;索引、切片、常用方法 引言 1. 字符串的概念和创建2. 字符串的索引3. 字符串的切片4. 字符串的常用方法 a ) 查找子字符串 b ) 替换子字符串 c ) 拆分和连接字符串 总结 引言 字符串是一种常见的数据类型&#xff0c;在 Python 中对…