手把手教你基于STM32的BootLoader的OTA远程升级

2023-05-16

----本文系21ic论坛蓝V作者小叶三千原创撰写

上次发过SD卡的Bootloader离线升级后,应大家的要求,这次就讲一下STM32的OTA远程升级。

OTA又叫空中下载技术,是通过移动通信的空中接口实现对移动终端设备数据进行远程管理的技术,还能提供移动化的新业务下载功能。

要实现OTA功能,至少需要两块设备,分别是服务器与客户端。服务器只有一个,客户端可有多个。服务器通过串口与PC机连接,需要下载的镜像文件存放于PC机,命令执行器给服务器发命令及镜像文件。首先命令执行器控制服务器广播当前可用的镜像文件信息,客户端收到信息后进行对比,若有与自身相匹配的镜像,则向服务器请求数据。服务器收到请求后向命令执行器索取固定大小的块,再点对点传送给客户端。镜像传输完毕后,客户端进行校验,完成后发送终止信号。

一. 升级方式的对比

OTA升级与平时用到的SD卡升级、串口升级等等大体原理上是一样的,都是对MCU的Flash进行操作而已。

收到升级指令——>MCU复位或者跳转到Boot程序区——>擦除对应的Flash区域——>获取APP数据——>写入FLASH数据——>校验——>跳转到APP应用程序区

OTA与其他本地升级的区别就是:获取数据的方式不同。比如串口升级,就是通过上位机传输到MCU串口上的数据;SD卡升级,就是通过读取SD卡,把程序通过SPI传输到MCU上;而OTA升级,就是通过带无线传输的模块,把程序传输到MCU上。例如:蓝牙、Wifi、GSM等等。不过大部分的无线模块,通过串口把数据传输到MCU上的,只是服务端不再是PC端了,而是网络服务器。

二. 硬件选择

MCU我这里选用的是STM32F030F4P6的芯片,16K的Flash,应该是ST产品中Flash空间比较小的一种,为的就是体现一下小容量的单片机也可以进行OTA升级。

无线模块我使用的是ESP-8266,WIfi传输方式,应该也是比较大众化的一款模组。(TTL串口连接MCU)

OTA相关的硬件没有了,剩下的无所谓,都是其他功能的,最好有个LED灯,可以明显的看出是否升级成功。

三. 网络服务器的选择

网络服务器多种多样,常用的有阿里云、百度云、腾讯云、移动云等等,有条件的,还可以使用自己的服务器。总之需要实现:网络服务器可以与我们的无线模块进行大数据通信。

我这里选用的是OneNet移动云(OTA服务之前是免费,现在是前100个设备免费,之后每增加一个设备1元钱永久),我感觉OneNet相对于阿里云较为简单,没有阿里云那么繁琐,不过阿里云还是比OneNet更专业一点(个人见解),其他的没有用过,大家都可以去试试。

四. 网络服务器的传输方式

我这里使用的是OneNet的服务器,它的OTA服务是通过Http协议进行传输的,有对应的API,我们可以通过OneNet释放的API去访问OTA服务。

五. OTA升级流程

OneNet的OTA升级流程主要为6步:

1. 上报版本号---客户端(MCU)上报当前的一个版本号

2. 检测升级任务---检查服务器是否有待升级的版本

3. 检测Token有效性---检查Token密钥,可省略

4. 下载固件---应用程序传输

5. 上报升级状态---上报服务端升级是否成功,不成功有对应的响应码

六. OneNet服务端配置

1.首先注册OneNet的账号,进入开发者中心,在导航栏选择全部产品->远程升级OTA板块。

2.进入远程升级OTA界面,选择需要升级的模块;然后点击右上角的添加升级包按钮。FOTA升级:对设备中的模组进行升级。SOTA升级:对设备中的应用程序进行升级,我这里选用的是SOTA,因为我要对MCU的应用程序升级。

3.在添加升级包对话框中,输入固件信息,上传固件包文件。产品选你要升级的设备,全部设备也可以;厂商名称选其他,主要是与之后发的对应上即可;模组型号同理;目标版本是你要更新到的版本号,比如你现在是V01,你这里添加的固件是V02的,这个版本号就要填V02;然后上传升级包,只支持Bin和压缩包格式的。

4.点击验证升级按钮,选择验证类型(完整包或者差分包),选择进行测试升级的设备,进行验证。一般跳过验证就行,我这里选的是整包,差分包原理一样。

5.单击升级设备列表,进入升级队列模块,在右上角单击添加升级设备按钮,新增设备升级任务。在添加待升级设备对话框中输入对应参数值。初始版本:就是升级前的版本,也是上次升级的版本;升级范围就是你需要给哪些设备升级;升级时机:就是立即升级或是定时在什么时段升级;重试策略:不重试就是如果升级失败就完事了,重试那就失败了还能重试;信号强度和剩余电量只是一个信息的接口,有需要的可以读取来用。

6.上述完成后,会出现“待升级”的设备,服务器这边就算配置完了,后续要我们M客户端进行操作了。

七.客户端(MCU)API访问服务端进行OTA升级

无线模组用的是ESP8266,由于OneNet的OTA服务用的是HTTP协议,但是ESP8266没有HTTP协议,所以我使用TCP协议,封装成HTTP的报文格式。

1.ESP8266初始化;连接Wifi,AP_SSID,AP_PASS是WiFi的账号和密码;SERVER_IP和SERVER_PORT是OneNet的Ip和端口号。

# defineSERVER_IP "183.230.40.50" # defineSERVER_PORT 80 uint8_tpro = 0; uint8_tESP8266_Init( void) { switch(pro) { case0: //printf("+++"); Uart2_Send( "+++"); Delay_S( 2); if(ESP8266_SoftReset( 50) == 0) pro = 1; break; case1: if(ESP8266_AT_Send( "ATE0\r\n", 10) == 0) pro = 2; break; case2: if(ESP8266_AT_Send( "AT+CWMODE=1\r\n", 50) == 0) //设置8266为STA模式 pro = 3; break; case3: if(ESP8266_ConnectionAP(AP_SSID,AP_PASS, 200) == 0) //8266连接AP pro = 4; break; case4: if(ESP8266_AT_Send( "AT+CIPMODE=1\r\n", 50) == 0) //8266开启透传模式 pro = 5; break; case5: if(ESP8266_Connect_Server(SERVER_IP,SERVER_PORT, 50) == 0) //8266连接TCP服务器 { pro = 0; //USART1_Clear; //清除串口数据 return1; } break; } return0; }

2.上报版本号;dev_id是设备ID,authorization是鉴权参数,ver要上报的版本号,timeout发送超时时间。

//上报版本号uint8_tReport_Version( char*dev_id, char*authorization, char*ver, uint16_ttimeout) {uint16_ttime= 0; charsend_buf[ 296]; USART1_Clear; //清除串口数据 snprintf(send_buf, sizeof(send_buf), "POST /ota/device/version?dev_id=%s HTTP/1.1\r\n""Authorization:%s\r\n""Host:ota.heclouds.com\r\n""Content-Type:application/json\r\n""Content-Length:%d\r\n\r\n""{\"s_version\":\"%s\"}", dev_id, authorization, strlen(ver) + 16, ver); Uart2_Send(send_buf); while(time<timeout) {if( strstr( ( constchar*)usart_info.buf , ( constchar*) "\"errno\":0")) break; Delay_Ms( 100); time++; }if(time>=timeout) return1; elsereturn0; }

3.检查升级任务;dev_id是设备ID,authorization是鉴权参数,cur_version是当前的版本号,timeout发送超时时间

//检查升级任务uint8_tDetect_Task( char*dev_id, char*cur_version, char*authorization, uint16_ttimeout) {uint16_ttime= 0; charsend_buf[ 280]; USART1_Clear; //清除串口数据 snprintf(send_buf, sizeof(send_buf), "GET /ota/south/check?""dev_id=%s&manuf=100&model=10001&type=2&version=%s&cdn=false HTTP/1.1\r\n""Authorization:%s\r\n""Host:ota.heclouds.com\r\n\r\n", dev_id, cur_version,authorization); Uart2_Send(send_buf);while(time<timeout) {if( strstr( ( constchar*)usart_info.buf , ( constchar*) "\"errno\":0")) break; Delay_Ms( 100); time++; }if(time>=timeout) return1; elsereturn0; }

3.下载资源(我省略了"检查token有效"步骤);ctoken是上一步“检查升级任务”返回的Token,这个每次请求都不一样,所以注意要记录;size:平台返回的固件大小(字节);bytes_range:分片大小(字节)

/************************************************************** 函数名称: OTA_Download_Range** 函数功能: 分片下载固件** 入口参数: token:平台返回的Token* size:平台返回的固件大小(字节)* bytes_range:分片大小(字节)** 返回参数: 0-成功 其他-失败** 说明: *************************************************************/uint8_tDownload_Task( char*ctoken, unsignedintsize, constunsignedshortbytes_range, uint16_ttimeout) {MD5_CTX md5_ctx; //MD5相关变量unsignedcharmd5_t[ 16]; charmd5_t1[ 16]; charmd5_result[ 40]; uint16_ttime= 0; char*data_ptr = NULL; charsend_buf[ 256]; unsignedcharflash_buf[OTA_BUFFER_SIZE]; //flash读写缓存unsignedintbytes = 0; MD5_Init(&md5_ctx);Flash_cashu;while(bytes < size) {time = 0; memset(send_buf, 0, sizeof(send_buf)); USART1_Clear; //清除串口数据 snprintf(send_buf, sizeof(send_buf), "GET /ota/south/download/""%s HTTP/1.1\r\n""Range:bytes=%d-%d\r\n""Host:ota.heclouds.com\r\n\r\n", ctoken, bytes, bytes + bytes_range - 1); Uart2_Send(send_buf);//----------------------------------------------------等待数据---------------------------------------------------------------------while(time < 30) {if(usart_info.buf[ 0] != 0) break; Delay_Ms( 100); time++;}if(time <= 29) {Delay_Ms( 500); //----------------------------------------------------跳过HTTP报文头、找到固件数据--------------------------------------------------data_ptr = strstr( ( constchar*)usart_info.buf, "Range"); data_ptr = strstr(data_ptr, "\r\n"); data_ptr += 4; //----------------------------------------------------将固件数据写入缓存和闪存-----------------------------------------------------if(data_ptr != NULL) {if((size - bytes) >= OTA_BUFFER_SIZE) {memcpy(flash_buf + (bytes % OTA_BUFFER_SIZE), data_ptr, bytes_range); STMFLASH_Write_NoCheck(FLASH_APP1_ADDR + bytes,( uint16_t*)flash_buf,OTA_BUFFER_SIZE / 2); bytes = bytes + OTA_BUFFER_SIZE;MD5_Update(&md5_ctx, ( unsignedchar*)data_ptr, bytes_range); }else{memcpy(flash_buf + (bytes % OTA_BUFFER_SIZE), data_ptr, size - bytes); STMFLASH_Write_NoCheck(FLASH_APP1_ADDR + bytes , ( uint16_t*)flash_buf , (size % OTA_BUFFER_SIZE) / 2); MD5_Update(&md5_ctx, ( unsignedchar*)data_ptr, size - bytes); bytes = size;}}}}//----------------------------------------------------MD校验比对------------------------------------------------------------------memset(md5_result, 0, sizeof(md5_result)); MD5_Final(&md5_ctx, md5_t); for( inti = 0; i < 16; i++) {if( md5_t[i] <= 0x0f) sprintf(md5_t1, "0%x", md5_t[i]); elsesprintf(md5_t1, "%x", md5_t[i]); strcat(md5_result, md5_t1); }if( strcmp(md5_result, ota_info.md5) == 0) return0; elsereturn1; }

4.上报升级状态;这一步由于时间问题,我也省略了,总之程序已经下载到MCU上了,只是没有通知服务器而已,大家最好还是加上这一步。

5.main函数循环;

char rrr;char dev_id[] = { "640600857"}; char Authorization[] = { "version=2018-10-31&res=products%2F378414&et=1735660800&method=sha1&sign=9EgY%2Bk4r%2BlvCooIGf1ghtQFC0%2Bc%3D"};

char Version[] = { "V10"};

while( 1) {switch(pro) {case1: //上报版本if(Report_Version(dev_id,Authorization,Version, 10) == 0) pro++;break; case2: //检查任务if(Detect_Task(dev_id,Version,Authorization, 50) == 0) pro++;break; case3: //接收token、size、md5信息rrr = json_get_value(( char*)usart_info.buf, "token",ota_info.token); rrr = json_get_value(( char*)usart_info.buf, "size",ota_info.csize); rrr = json_get_value(( char*)usart_info.buf, "md5",ota_info.md5); ota_info.size = atoi(ota_info.csize);pro++;break; case4: //进行下载res = Download_Task(ota_info.token,ota_info.size,OTA_BUFFER_SIZE, 10); if(res == 0) //校验成功{pro++;}elseif(res == 1) //校验失败{pro = 1; } break; case5: //Flash写入升级完成的标志位USART1_Clear;STMFLASH_Unlock;STMFLASH_WriteHalfWord(FLASH_APP1_ADDR - 0x64, 0xFF02); //写入数据STMFLASH_Lock;pro++;break; case6: //复位或者跳转到APPSys_Soft_Reset;//iap_load_app(FLASH_APP1_ADDR);break; }}

下图是我升级的历史

八.注意事项

2.由于用的是STM32F030F4P6,RAM也非常小,所以局部变量和全局变量的数组不要超过4K,堆栈大小有改动。当前用内存管理的话就不用了。

3.OTA校验用的是MD5,需要把MD5的算法移植一下。

4.别的想不到了,太长时间了。

总结:

OTA的方法只是我个人的理解,可能有的地方不正确,欢迎大家指点。BootLoader代码也是很早之前写过的一个Demo,最简化的,传输协议、加密、升级失败的操作、回滚等等都没有涉及,只是一个OTA演示的例子,代码水平有点差,大家将就的看,参考一下就可以了哈,感谢!

END

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手把手教你基于STM32的BootLoader的OTA远程升级 的相关文章

  • HAL库学习

    CMSIS简介 CMSIS Cortex Microcontroller Software Interface Standard 微控制器软件接口标准 由ARM和其合作的芯片厂商 ST NXP 软件工具厂商 KEIL IAR 共同制定的标准
  • 硬件基础-电容

    电容 本质 电容两端电压不能激变 所以可以起到稳定电压作用 充放电 电容量的大小 想使电容容量大 使用介电常数高的介质 增大极板间的面积 减小极板间的距离 品牌 国外 村田 muRata 松下 PANASONIC 三星 SAMSUNG 太诱
  • 在 Atollic TrueStudio、STM32CubeMX 中导入 C 库

    我目前正在开发 STM32F767ZI Nucleo 板和一个小安全芯片 microchip atecc508a 通过 i2c 连接进行连接 该芯片有一个可用的库加密验证库 https github com MicrochipTech cr
  • 加载引导加载程序的第二阶段和/或将控制权转移给它时出现问题

    我的主引导记录代码 bit16 16bit by default org 0x7c00 jmp short start nop bsOEM db OS423 v 0 1 OEM String start cls mov ah 06h Fun
  • 中断 0x15 函数 0x86(BIOS WAIT)在真实硬件上的运行速度比在虚拟机上慢得多?

    我一直在汇编 游戏 中编写引导加载程序 引导加载程序使用 BIOS WAIT 函数 int 0x15 ah 0x86 来实现帧之间的延迟 我正在使用 BOCHS 进行调试 一切都运行良好 时机非常完美 我还制作了一个可启动的isoisoge
  • Sparkfun Edge 引导加载程序问题

    今天终于到了 Sparkfun 板边板 遵循这个写得很好的指南 https codelabs developers google com codelabs sparkfun tensorflow 3 https codelabs devel
  • 与 CMPSB 指令混淆

    我一直在看这段代码 我对代表 cmpsb line LOOP push cx mov cx 0x000B eleven character name mov si ImageName image name to find push di r
  • 串口通讯第一次发送数据多了一字节

    先初始化IO再初始化串口 导致第一次发送时 多出一个字节数据 优化方案 先初始化串口再初始化IO 即可正常通讯
  • 将bootloader和内核制作成iso? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 如何创建将内核加载到 iso 的简单引导加载程序 已经过去 5 天了 我在 google 中搜索并进行
  • 为什么引导加载程序中的字节“0xea 0000 ffff”会导致计算机重新启动?

    我正在研究引导加载程序 发现了这个有趣的组件 Sends us to the end of the memory causing reboot db 0x0ea dw 0x0000 dw 0xffff 通过评论我知道它的作用 将计算机发送到
  • 库函数点亮Led

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 提示 这里可以添加本文要记录的大概内容 例如 随着人工智能的不断发展 机器学习这门
  • 特殊寄存器

    特殊寄存器 文章目录 前言 一 背景 二 2 1 2 2 总结 前言 前期疑问 STM32特殊寄存器到底是什么 特殊寄存器怎么查看和调试代码 本文目标 记录和理解特殊寄存器 一 背景 最近在看ucosIII文章是 里面提到特殊寄存器 这就进
  • 从没有中断引脚并且在测量准备好之前需要一些时间的传感器读取数据的最佳方法

    我正在尝试将压力传感器 MS5803 14BA 与我的板 NUCLEO STM32L073RZ 连接 根据 第 3 页 压力传感器需要几毫秒才能准备好读取测量值 对于我的项目 我对需要大约 10 毫秒来转换原始数据的最高分辨率感兴趣 不幸的
  • 嵌入式 C++11 代码 — 我需要 volatile 吗?

    采用 Cortex M3 MCU STM32F1 的嵌入式设备 它具有嵌入式闪存 64K MCU固件可以在运行时重新编程闪存扇区 这是由闪存控制器 FMC 寄存器完成的 所以它不像a b那么简单 FMC 获取缓冲区指针并将数据刻录到某个闪存
  • STM32 上的 ADC 单次转换

    我正在研究 STM32 F103x 上的 ADC 编程 并从最简单的情况 单次转换开始 测量内部温度传感器 连接到 ADC1 的值 并使用 USART 将其发送到 COM 端口 目标似乎很明确 但是当我尝试将源代码下载到闪存时 它不会向 C
  • STM32内部时钟

    我对 STM32F7 设备 意法半导体的 Cortex M7 微控制器 上的时钟系统感到困惑 参考手册没有充分阐明这些时钟之间的差异 SYSCLK HCLK FCLK 参考手册中阅读章节 gt RCC 为 Cortex 系统定时器 SysT
  • ARM 的启动过程是怎样的?

    我们知道 对于X86架构 按下电源按钮后 机器开始执行0xFFFFFFF0处的代码 然后开始执行BIOS中的代码以进行硬件初始化 BIOS 执行后 它使用引导加载程序将操作系统映像加载到内存中 最后 操作系统代码开始运行 对于ARM架构 使
  • HAL_Delay() 陷入无限循环

    我被 HAL Delay 函数困住了 当我调用此函数 HAL Delay 时 控制陷入无限循环 在寻找问题的过程中 我发现了这个 http www openstm32 org forumthread2145 threadId2146 htt
  • 使用 STM32 USB 设备库将闪存作为大容量存储设备

    我的板上有这个闪存IC 它连接到我的STM32F04 ARM处理器 处理器的USB端口可供用户使用 我希望我的闪存在通过 USB 连接到 PC 时被检测为存储设备 作为第一步 我在程序中将 USB 类定义为 MSC 效果很好 因为当我将主板
  • 当端点和 PMA 地址均更改时,CubeMX 生成的 USB HID 设备发送错误数据

    我正在调试我正在创建的复合设备的问题 并在新生成的仅 CubeMX 代码中重新创建了该问题 以使其更容易解决 我添加了少量代码main 让我发送 USB HID 鼠标点击 并在按下蓝色按钮时使 LED 闪烁 uint8 t click re

随机推荐

  • 【VPN(虚拟专用网)攻略大全】

    在 VPN 出现之前 xff0c 企业分支之间的数据传输只能依靠现有物理网络 xff08 例如 Internet xff09 由于 Internet 中存在多种不安全因素 xff0c 报文容易被网络中的黑客窃取或篡改 xff0c 最终造成数
  • Linux 如何检测硬盘坏道?

    在 Mac 和 Windows 下检测硬盘坏道有专门的工具 xff0c 或自带 或三方的都挺好用 xff0c 但是如何在 Linux 下检测硬盘坏道呢 xff1f 首先 xff0c 用 lsblk 命令查看下待检测硬盘的名字 xff1a 然
  • 图论-路径优化算法总结

    知乎主页 https www zhihu com people shuang shou cha dai 53 目录 1 xff1a Dijkstra算法 1 1 xff1a 算法思想 1 2 xff1a 算法步骤 1 3 xff1a 代码演
  • uORB发布订阅实例

    PX4SITL仿真 uORB实例 飞控串口读取外部传感器数据 xff1a 飞控开启一个进程读取外部传感器数据 xff0c 发布一个uORB主题 xff1b 另一个进程订阅前一个进程发布的主题 xff0c 订阅到的主题通过mavlink消息发
  • PX4仿真环境搭建

    PX4 SITL Simulation 前提准备 xff1a Ubuntu16 04 LTS 安装ROS kinetic 题外话 xff1a 如果连的是有IPV6的校园网 xff0c 在update时可能会访问IPV6地址出错 xff0c
  • PX4-Gazebo仿真学习笔记

    PX4 Gazebo仿真 xff1a http bbs amovauto com forum php mod 61 viewthread amp tid 61 486 amp extra 61 page 3D1 Simulator仿真器 x
  • C语言strtok函数

    1 strtok 语法 include lt string h gt char strtok char str const char delimiters 参数 xff1a str xff0c 待分割的字符串 xff08 c string
  • 终于把大数据类产品全流程解释清楚了

    你点开这文章 xff0c 说明你清晰知道了数据才是一切的基础 人工智能 机器学习 大数据等应用的基础都是基于这样的一个流程 xff0c 只是说运用领域不同 xff0c 那么偏重点不同 本文从数据采集到数据报告 xff0c 详细说明了大数据运
  • 关于slam

    什么是SLAM 机器人在未知环境中 xff0c 要实现智能化需要完成三个任务 xff0c 第一个是定位 Localization xff0c 第二个是建图 Mapping xff0c 第三个则是随后的路径规划 Navigation 之前地平
  • Linux(Ubuntu系统)同网段SSH连接不上,网络能ping通

    问题描述 测试以下命令同样连接不上 span class token function ssh span localhost 问题原因 Ubuntu系统自带 openssh client xff0c 但是没有自带 openssh serve
  • 本地进程间通信(二)--套接字socket

    目录 一 什么是Socket xff1f 二 socket通信流程 Server端 一 创建socket 二 命名socket 三 绑定 四 监听 五 关闭 Client端 一 创建socket 二 connect 三 发送数据 四 关闭s
  • debain服务器搭建之虚拟机安装(一)

    debain服务器虚拟机搭建系列 xff08 一 xff09 xff08 一 xff09 下载debain系统 xff08 二 xff09 搜索下载安装 vmware xff08 三 xff09 开始安装debain系统 xff08 四 x
  • 企业私有云技术设计方案

    1 概述 1 1 文档内容 本文档为某企业私有云技术路线设计文档 1 2 背景描述 1 2 1 某企业私有云业务线规划 近些年由于国内IDC市场发展迅速 xff0c 某企业从战略层面考虑 xff0c 建造了自己的高等级数据中心 xff0c
  • Qt的主窗口背景设置

    主界面设置背景一般有设置背景图片和背景颜色的需求 xff0c 其实二者之间设置方法类似 目录 主界面设置背景一般有设置背景图片和背景颜色的需求 xff0c 其实二者之间设置方法类似 方法一 xff1a 最简单的方式是通过ui界面来设置 xf
  • 7.使用码云

    使用GitHub时 xff0c 国内的用户经常遇到的问题是访问速度太慢 xff0c 有时候还会出现无法连接的情况 xff08 原因你懂的 xff09 如果我们希望体验Git飞一般的速度 xff0c 可以使用国内的Git托管服务 码云 xff
  • git diff命令之后,如何退出

    git diff命令是对比两次文件修改了什么 但如何退出呢 xff1f 输入q 按enter键盘
  • Float类型出现舍入误差的原因

    首先是float累加产生误差的原因 xff0c 该部分转自 xff1a http blog csdn net zhrh0096 article details 38589067 1 浮点数IEEE 754表示方法 要搞清楚float累加为什
  • React之antd Form回显数据

    转自 xff1a https blog csdn net welkin qing article details 110004969 文章目录 一 antd4如何回显数据 1 定义变量2 保存接口数据到form变量中3 form显示数据4
  • equals()方法和hashCode()方法

    1 hashCode 简介 该方法主要是利用一定的规则生成对象的哈希码 xff0c 也称散列码 它是是由对象导出的一个整数值 xff0c 是没有规律的 关于hashCode 使用的哈希算法 xff0c 越糟糕的哈希算法越容易产生哈希碰撞 产
  • 手把手教你基于STM32的BootLoader的OTA远程升级

    本文系21ic论坛蓝V作者小叶三千原创撰写 上次发过SD卡的Bootloader离线升级后 xff0c 应大家的要求 xff0c 这次就讲一下STM32的OTA远程升级 OTA又叫空中下载技术 xff0c 是通过移动通信的空中接口实现对移动