当前位置: 首页 > news >正文

STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式1)

STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式1)

目录

  • STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式1)
    • 前言
    • 1 环境搭建
    • 2 功能描述
    • 3 程序编写
      • 3.1 BootLoader部分
      • 3.2 APP的制作
    • 4 修改工程中的内存配置
      • 4.1 Bootloader工程内存配置
      • 4.2 APP工程内存配置
    • 5 烧录相关配置
      • 5.1 BootLoader部分
      • 5.2 APP部分
    • 6 运行测试
    • 结束语

前言

什么是OTA?

百度百科:空中下载技术(Over-the-Air Technology; OTA),是通过移动通信的空中接口实现对移动终端设备及SIM卡数据进行远程管理的技术。经过公网多年的应用与发展,已十分成熟,网络运营商通过OTA技术实现SIM卡远程管理,还能提供移动化的新业务下载功能。

实际上,现在我们所说的OTA比百度百科的定义还要更广泛,OTA的形式已经不再局限于手机和SIM卡,只要涉及到远程下载升级程序的方式我们都可以称之为OTA。例如通过4G,5G,WiFI,蓝牙等无线通讯进行下载升级的可以称为OTA,通过U盘,RS485等串行接口进行升级的也可以称之为OTA。

OTA的作用?
OTA的意义在于它在一定程度上突破了距离的限制,在不借助烧录器的情况下完成固件的下载升级,极大的方便了产品的升级和维护,降低售后成本。

什么是BootLoader?

百度百科:在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。

实际上,BootLoader不仅仅在操作系统上使用,在一些内存小,功能应用较为简单的单片机设备上面也可以通过BootLoader来完成OTA升级。

我之前也有发过一些关于STM32远程OTA的文章,实现的方式有很多种,感兴趣的同学可以去看一下。
OTA应用开发系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047

那么这一期我来介绍一下如何自己制作一个BootLoader程序,并且通过串口或者RS485实现OTA升级。

1 环境搭建

关于STM32以及Keil的环境这里就不具体介绍了,网上教程也很多,不懂的同学自行查阅资料。

2 功能描述

在做bootloader之前一定要先想好升级的途径和方式,这样才好规划分区以及制作bootloader。
关于bootloader详细的讲解,可以看下我之前发的博客:
STM32 OTA应用开发——自制BootLoader

分区介绍:
我用的是STM32F407,内存是512K的(想用内存更小的MCU也是可以的,改下各个分区的内存分配就行了)。
注:F4系列的MCU不像F1那样,内存扇区都很大(最少也是16K),而且同一块扇区只能一起擦除,所以就没办法分的那么细了。详细的内存分布可以参考下面的两个图。
STM32F4x扇区分布图如下:
请添加图片描述
STM32F1x扇区分布图如下:
请添加图片描述
那么我这里呢,就用一个512k的内存,分成4个区域,来实现一个OTA的功能。
分区表如下:

nameoffsetsizefunction
boot0x080000000x00004000存放boot程序
setting0x080040000x00004000存放升级相关的配置参数
app0x080080000x00018000存放应用程序
download0x080200000x00020000存放需要升级的新固件

请添加图片描述
方案介绍:
1)bootloader部分:
运行时从setting分区里面读取升级相关的数据,确定是否需要升级,如果需要,则把download分区的固件搬运到app分区,如果不需要升级则直接跳转到app分区。另外,使用串口1来打印运行的一些信息。
2)APP部分:
通过串口2或者RS485连接到PC端,然后等待上位机发送特定的升级命令,如果MCU收到命令,则进入下载模式,然后通过串口2或者RS485传输新固件到download分区,并且在下载完成后把升级标志写入到setting分区里面。
我这里图方便,串口传输固件的方式我采用的是Ymodem协议,因为这个协议很多tool都可以用,就不用专门做一个上位机了。如果你想用其他的协议或者自定义协议其实都是可以的,稍做修改就行。

在这里插入图片描述

3 程序编写

3.1 BootLoader部分

不管用的是什么MCU,要使用OTA都离不开BootLoader,BootLoader是一个统称,它其实只是一段引导程序,在MCU启动的时候会先运行这段代码,判断是否需要升级,如果不需要升级就跳转到APP分区运行用户代码,如果需要升级则先通过一些硬件接口接收和搬运要升级的新固件,然后再跳转到APP分区运行新固件,从而实现OTA升级。
BootLoader的制作需要根据实际的需求来做,不同的运行方式或者升级方式在做法上都是有区别的,包括BootLoader所需要的内存空间也不尽相同。
不过不管是用什么方式,Bootloader都应该尽可能做的更小更简洁,这样的话内存的开销就更小,对于内存较小的MCU来说压力就没那么大了。

示例代码如下:
分区定义:

#define FLASH_SECTOR_SIZE           1024
#define FLASH_SECTOR_NUM            512    // 512K
#define FLASH_START_ADDR            ((uint32_t)0x8000000)
#define FLASH_END_ADDR              ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))//flash sector addr
#define ADDR_FLASH_SECTOR_0         ((uint32_t)0x08000000) 	//sector0 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_1         ((uint32_t)0x08004000) 	//sector1 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_2         ((uint32_t)0x08008000) 	//sector2 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_3         ((uint32_t)0x0800C000) 	//sector3 addr, 16 Kbytes  
#define ADDR_FLASH_SECTOR_4         ((uint32_t)0x08010000) 	//sector4 addr, 64 Kbytes  
#define ADDR_FLASH_SECTOR_5         ((uint32_t)0x08020000) 	//sector5 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_6         ((uint32_t)0x08040000) 	//sector6 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_7         ((uint32_t)0x08060000) 	//sector7 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_8         ((uint32_t)0x08080000) 	//sector8 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_9         ((uint32_t)0x080A0000) 	//sector9 addr, 128 Kbytes  
#define ADDR_FLASH_SECTOR_10        ((uint32_t)0x080C0000) 	//sector10 addr,128 Kbytes  
#define ADDR_FLASH_SECTOR_11        ((uint32_t)0x080E0000) 	//sector11 addr,128 Kbytes  #define BOOT_SECTOR_ADDR            0x08000000
#define BOOT_SECTOR_SIZE            0x4000
#define SETTING_SECTOR_ADDR         0x08004000
#define SETTING_SECTOR_SIZE         0x4000
#define APP_SECTOR_ADDR             0x08008000     // APP sector start address  
#define APP_SECTOR_SIZE             0x18000        // APP sector size    
#define DOWNLOAD_SECTOR_ADDR        0x08020000     // Download sector start address
#define DOWNLOAD_SECTOR_SIZE        0x20000        // Download sector size 

程序跳转:

uint8_t jump_app(uint32_t app_addr) 
{uint32_t jump_addr;jump_callback cb;if (((*(__IO uint32_t*)app_addr) & 0x2FFE0000 ) == 0x20000000) {  jump_addr = *(__IO uint32_t*) (app_addr + 4);  cb = (jump_callback)jump_addr;  __set_MSP(*(__IO uint32_t*)app_addr);  cb();return 1;} return 0;
}

主函数:

void print_boot_message(void)
{uart_log("---------- Enter BootLoader ----------\r\n");uart_log("\r\n");uart_log("======== flash pration table =========\r\n");uart_log("| name     | offset     | size       |\r\n");uart_log("--------------------------------------\r\n");uart_log("| boot     | 0x08000000 | 0x00004000 |\r\n");uart_log("| setting  | 0x08004000 | 0x00004000 |\r\n");uart_log("| app      | 0x08008000 | 0x00018000 |\r\n");uart_log("| download | 0x08020000 | 0x00020000 |\r\n");uart_log("======================================\r\n");
}int main() 
{process_status process;uint16_t i;uint8_t boot_state;uint8_t down_buf[128];uint32_t down_addr;uint32_t app_addr;delay_init(168);uart_init(115200);print_boot_message();boot_parameter.process = read_setting_boot_state();boot_parameter.addr = APP_SECTOR_ADDR;while (1) {process = get_boot_state();switch (process) {case START_PROGRAM:uart_log("start app...\r\n");delay_ms(50);if (!jump_app(boot_parameter.addr)) {uart_log("no program\r\n");delay_ms(1000);}uart_log("start app failed\r\n");break;case UPDATE_PROGRAM:uart_log("update app program...\r\n");app_addr = APP_SECTOR_ADDR;down_addr = DOWNLOAD_SECTOR_ADDR;uart_log("app addr: 0x%08X \r\n", app_addr);uart_log("down addr: 0x%08X \r\n", down_addr);uart_log("erase mcu flash...\r\n");mcu_flash_erase(app_addr, APP_ERASE_SECTORS_NUM);  uart_log("mcu flash erase success\r\n");uart_log("write mcu flash...\r\n");// memset(down_buf, 0, sizeof(down_buf));for (i = 0; i < (APP_SECTOR_SIZE / 1024) * 8; i++){mcu_flash_read(down_addr, &down_buf[0], 128);delay_ms(5);mcu_flash_write(app_addr, &down_buf[0], 128);delay_ms(5);down_addr += 128;app_addr += 128;// uart_log("mcu_flash_write: %d\r\n", i);}uart_log("mcu flash write success\r\n");set_boot_state(UPDATE_SUCCESS);break;case UPDATE_SUCCESS:uart_log("update success\r\n");boot_state = UPDATE_SUCCESS_STATE;write_setting_boot_state(boot_state);set_boot_state(START_PROGRAM);break;default:break;}}
}

关于bootloader详细的讲解,可以看下我之前发的博客:
STM32 OTA应用开发——自制BootLoader
完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87546126

3.2 APP的制作

APP部分根据自己实际的功能来做,我这里用的是串口或者RS485连接PC端,然后传输固件的协议用的是Ymodem。
当然了,协议也是可以自定义,只要能正确的把固件从PC端搬运到MCU的flash就行了。

示例代码如下:
Ymodem协议部分:
注:详细的协议解析这里就不讲解了,不懂的同学自行查阅资料。

void ymodem_ack(void) 
{uint8_t buf[3];buf[0] = YMODEM_ACK;buf[1] = 0x0D;buf[2] = 0x0A;RS485_Send_Data(buf, 3);
}void ymodem_nack(void) 
{uint8_t buf[3];buf[0] = YMODEM_NAK;buf[1] = 0x0D;buf[2] = 0x0A;RS485_Send_Data(buf, 3);
}void ymodem_c(void) 
{uint8_t buf[3];buf[0] = YMODEM_C;buf[1] = 0x0D;buf[2] = 0x0A;RS485_Send_Data(buf, 3);
}void set_ymodem_status(process_status process) 
{ymodem.process = process;
}process_status get_ymodem_status(void) 
{process_status process = ymodem.process;return process;
}void ymodem_start(ymodem_callback cb) 
{if (ymodem.status == 0) {ymodem.cb = cb;}
}void ymodem_recv(download_buf_t *p) 
{uint8_t type = p->data[0];switch (ymodem.status) {case 0:if (type == YMODEM_SOH) {ymodem.process = BUSY;ymodem.addr = DOWNLOAD_SECTOR_ADDR;mcu_flash_erase(ymodem.addr, ERASE_SECTORS);ymodem_ack();ymodem_c();ymodem.status++;}else if (type == '1') {uart_log("enter update mode\r\n");ymodem.process = UPDATE_PROGRAM;}break;case 1:if (type == YMODEM_SOH || type == YMODEM_STX) {if (type == YMODEM_SOH) {mcu_flash_write(ymodem.addr, &p->data[3], 128);ymodem.addr += 128;}else {mcu_flash_write(ymodem.addr, &p->data[3], 1024);ymodem.addr += 1024;}ymodem_ack();}else if (type == YMODEM_EOT) {ymodem_nack();ymodem.status++;}else {ymodem.status = 0;}break;case 2:if (type == YMODEM_EOT) {ymodem_ack();ymodem_c();ymodem.status++;}break;case 3:if (type == YMODEM_SOH) {ymodem_ack();ymodem.status = 0;ymodem.process = UPDATE_SUCCESS;}}p->len = 0;
}void ymodem_handle(void)
{uint8_t boot_state;process_status process;process = get_ymodem_status();switch (process) {case START_PROGRAM:break;case UPDATE_PROGRAM:ymodem_c();delay_ms(1000);break;case UPDATE_SUCCESS:boot_state = UPDATE_PROGRAM_STATE;mcu_flash_erase(SETTING_BOOT_STATE, 1);mcu_flash_write(SETTING_BOOT_STATE, &boot_state, 1);// mcu_flash_read(SETTING_BOOT_STATE, &boot_state, 1);// uart_log("boot_state:%d\r\n", boot_state);uart_log("firmware download success\r\n");uart_log("system reboot...\r\n");delay_ms(2000);system_reboot();break;default:break;}
}void ymodem_init(void)
{RS485_Init(115200);timer_init();queue_initiate(&rx_queue);
}

主函数:

#define APP_VERSION   "V100"void print_boot_message(void)
{uart_log("======================================\r\n");uart_log("-------------- Enter APP -------------\r\n");uart_log ("app version is: %s\r\n", APP_VERSION);uart_log("======================================\r\n");
}int main(void)
{delay_init(168);uart_init(115200);ymodem_init();print_boot_message();uart_log ("app init success\r\n");while (1){ymodem_handle();}
}

修改中断向量:
bootloader的运行地址是在起始地址上的,所以中断向量是0,不用改。
但是app的运行地址是在起始地址上做了偏移的,所以中断向量也要改,不然会运行会出问题。

#define VECT_TAB_OFFSET  0x8000

注:这个变量定义在system_stm32f4xx.c中可以找到。

完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87546126

4 修改工程中的内存配置

因为我们对stm32的内存进行了分区,不同的代码要存放在不同的区域,因此,我们在编译工程之前需要先定义好各自的区域,以免出现内存越界。

4.1 Bootloader工程内存配置

Bootloader的起始地址不需要改,按flash默认地址即可,size需要改成实际分区大小。

请添加图片描述

4.2 APP工程内存配置

APP的起始地址和size都需要根据实际的分区来改。
请添加图片描述

5 烧录相关配置

我们的Bootloader做好以后需要烧录到MCU里面,可以直接用Keil uVison来下载,也可以用J-Flash或者其他,这个都没关系,但是要注意内存的分配,要把固件烧到对应的内存地址上。

5.1 BootLoader部分

1)使用Keil uVision下载
如果是用keil下载的话,需要注意flash的配置,具体如下:
请添加图片描述
2)使用其他下载工具
如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08000000就好了。

5.2 APP部分

1)使用Keil uVision下载
跟BootLoader一样,我们按照前面分配好的空间配置APP的参数即可。
请添加图片描述
2)使用其他下载工具
如果是用J-Flash或者STlink的工具烧录的话注意烧录的起始地址是0x08008000就好了。

6 运行测试

用串口助手查看运行log(我这里用的是XShell,用其他的也是可以的)。

1)开始运行代码
不需要升级时直接跳转到App区,如下图:
请添加图片描述

2)进入烧录模式
进入APP之后,往串口2/RS485发送一个字符"1",进入升级模式,然后通过调试工具发送新固件的bin文件。
注:为了方便调试才用了一个字符"1",实际使用的话最好改一下,太简单的话容易出现误操作。
串口调试窗口log如下图:
请添加图片描述

3)通过Ymodem传输新固件
调试工具我用的是XShell,实际上用其他工具也行,只要支持Ymodem方式传输文件即可。
请添加图片描述请添加图片描述
4)升级固件
固件搬运完成后自动重启,重新运行Bootloader,然后进行固件的升级。
请添加图片描述
至此,整个升级流程就走完了。

结束语

好了,关于自制BootLoader并实现串口以及RS485 OTA升级的介绍就讲到这里,本文列举的例子其实只是升级的其中一种方式,只是提供一个思路,不是唯一的方法,实际上最好还是根据自己实际的需求来做。
需要源码的同学可以在下面的链接下载,我把BootLoader和APP都上传了。
如果你有什么问题或者有更好的方法,欢迎在评论区留言。

完整代码下载地址:https://download.csdn.net/download/ShenZhen_zixian/87546126
更多相关文章:
OTA应用开发系列合集:https://blog.csdn.net/ShenZhen_zixian/article/details/129074047

相关文章:

STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式1)

STM32 OTA应用开发——通过串口/RS485实现OTA升级&#xff08;方式1&#xff09; 目录STM32 OTA应用开发——通过串口/RS485实现OTA升级&#xff08;方式1&#xff09;前言1 环境搭建2 功能描述3 程序编写3.1 BootLoader部分3.2 APP的制作4 修改工程中的内存配置4.1 Bootloader…...

在教学中常被问到的几个vue3.x与typescript的问题,统一解答

在教学当中&#xff0c;学生在学习vue3.x时&#xff0c;常常会问到typescript和vue3.x之间的关系&#xff0c;感觉这两个技术总是绑在一起的&#xff0c;下面老赵来统一解答一下&#xff1a; 那学vue3.x&#xff0c;为什么要求也要掌握typescript Vue 3.x是一个使用TypeScript编…...

纯css实现超炫酷的星空背景按钮

也是在制作项目时发现的&#xff0c;找了很多demo&#xff0c;一点一点测试&#xff0c;发现这个按钮也是非常的炫酷 用到了几个属性&#xff0c;keyframes&#xff0c;::after,::before 先了解一下他们分别都是干嘛的 keyframes 关键帧 keyframes at-rule 规则通过在动画序…...

openpnp - 贴片前, 放入一块新板子后, 对板子的坐标矫正

文章目录openpnp - 贴片前, 放入一块新板子后, 对板子的坐标矫正概述笔记实验前置条件实验开始建立自己板子上的Mark点封装, 用于自己人工圈定判断Mark点位置是否正确建立mark点封装根据多个mark点, 来精确定位板子左下角原点坐标ENDopenpnp - 贴片前, 放入一块新板子后, 对板子…...

计及需求响应的改进灰狼优化算法求解风、光、柴、储容量优化配置(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…...

Elasticsearch使用——高级篇

1.数据聚合**聚合&#xff08;aggregations&#xff09;**可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a;什么品牌的手机最受欢迎&#xff1f;这些手机的平均价格、最高价格、最低价格&#xff1f;这些手机每月的销售情况如何&#xff1f;实现这些统计功…...

Java网络爬虫-HttpClient工具类

关于用Java进行爬虫的资料网上实在少之又少&#xff0c;但作为以一名对Java刚刚初窥门径建立好兴趣的学生怎么能静得下心用新学的Python去写&#xff0c;毕竟Java是世界上最好的语言嘛 (狗头)关于Java爬虫最受欢迎的一个框架Jsoup常常搭配HttpClient来使用&#xff0c;因为Jsou…...

LeetCode203_203. 移除链表元素

LeetCode203_203. 移除链表元素 一、描述 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2…...

【洛谷 P1443】马的遍历 题解(广度优先搜索)

马的遍历 题目描述 有一个 nmn \times mnm 的棋盘&#xff0c;在某个点 (x,y)(x, y)(x,y) 上有一个马&#xff0c;要求你计算出马到达棋盘上任意一个点最少要走几步。 输入格式 输入只有一行四个整数&#xff0c;分别为 n,m,x,yn, m, x, yn,m,x,y。 输出格式 一个 nmn \t…...

为什么gpt输出有随机性?

以下答案由chatGPT产生&#xff01; 为什么gpt输出有随机性&#xff1f; GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种基于Transformer架构的神经语言模型&#xff0c;它是一个深度学习模型&#xff0c;通过在大规模文本数据上进行预训练&#xff0…...

配置Clion用于STM23开发(Makefile)

前言 对于Clion配置STM32开发环境的教程在网上一搜一大堆&#xff0c;但是大部分都是22年之前的&#xff0c;使用的方法都是在STM32CubeMX生成SW4STM32工程。但是在22年不知道哪个版本后&#xff0c;CubeMX已经不再支持生成SW4STM32工程了&#xff0c;这也是我本人遇到的问题。…...

如何在 Istio 中使用 SkyWalking 进行分布式追踪

在云原生应用中&#xff0c;一次请求往往需要经过一系列的 API 或后台服务处理才能完成&#xff0c;这些服务有些是并行的&#xff0c;有些是串行的&#xff0c;而且位于不同的平台或节点。那么如何确定一次调用的经过的服务路径和节点以帮助我们进行问题排查&#xff1f;这时候…...

HBase高手之路1-Hbase简介

文章目录HBase高手之路1-Hbase简介一、什么是HBase1. HBase简介2. HBase的发展过程二、HBase特点1. 海量存储2. 列式存储3. 极易扩展4. 高并发5. 稀疏6. 强一致性读/写7. 自动分块8. 自动RegionServer故障转移9. Hadoop/HDFS集成10. MapReduce11. Java Client API12. Thrift/RE…...

计算机视觉手指甲标注案例

关键点标注是指识别和标注图像或视频中特定的相关点或区域的过程。在机器学习行业&#xff0c;它经常被用来训练计算机视觉模型&#xff0c;以执行诸如物体检测、分割和跟踪等任务。 关键点注释可用于以下应用&#xff1a; 面部关键点检测&#xff1a;识别图像中人脸上的眼睛…...

linux 字符串截取(cut)

-b &#xff1a;以字节为单位进行分割。这些字节位置将忽略多字节字符边界&#xff0c;除非也指定了 -n 标志。 -c &#xff1a;以字符为单位进行分割。 -d &#xff1a;自定义分隔符&#xff0c;默认为制表符。 -f &#xff1a;与-d一起使用&#xff0c;指定显示哪个区域。 -n…...

003+limou+HTML——(3)HTML列表

000、前言 列表是网页常见的一种数据排列方式&#xff0c;在HTMl中列表一共有三种&#xff1a;有序列表、无序列表、定义列表&#xff08;另外“目录列表dir”和“菜单列表menu”已经在HTML5中被废除了&#xff0c;现在都是使用无序列表ul来替代&#xff09; 001、有序列表&a…...

设计模式---工厂模式

目录 1. 简单工厂模式 2. 工厂方法模式 1. 简单工厂模式 简单工厂模式(Simple Factory Patterm)又称为静态工厂方法模式(Static Factory Model)&#xff0c;它属于类创建型模式。在简单工厂模式中&#xff0c;可以根据参数的不同返回不同类的实例。简单工厂模式专门定义了一…...

C++基础了解-13-C++ 数组

C 数组 一、C 数组 C 支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量&#xff0c;比如 number0、number1、…、number9…...

ICC2:限制LVT比例

1) 禁用VT 在优化过程用&#xff0c;如果要禁用某种VT可以直接对其使用dont use&#xff0c;如下示例: set_attribute -objects [get_lib_cells *_lvt*/*] -name dont_use -value true 在dont use lib cell的基础上还可以对某些模块放开lvt的使用。 set_app_options -name …...

Kettle工具通过JNDI连接Oracle集群

我们在用Kettle ETL工具的时候&#xff0c;可能会遇到数据库为Oracle集群的模式&#xff0c;或者有时候目标库为oracle&#xff0c;在持续的循环调度中&#xff0c;经常发现oracle的数据库连接中断的情况&#xff0c;此时&#xff0c;在Kettle中有一个JNDI的连接方式能很好的解…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...

【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!

【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...