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

news2025/1/23 0:59:31

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

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

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

相关文章

uniapp生命周期

uniapp生命周期 uniapp生命周期不同于vue生命周期&#xff0c;uniapp生命周期分为&#xff1a; 应用生命周期 页面生命周期 组件生命周期 应用生命周期(官网) 注意 应用生命周期仅可在App.vue中监听&#xff0c;在其它页面监听无效。 onlaunch里进行页面跳转&#xff0c;如遇白…

你还在使用if-else写代码吗,今天带你领略下策略模式的魅力!

1、什么是策略模式 策略模式其实也是在解耦&#xff0c;把策略的定义、创建、使用这三个部分解耦开来&#xff0c;因为本身策略模式也是基于接口编程&#xff0c;这样其实可以简单的理解客户端调用使用接口进行编程&#xff0c;可以通过工厂方法创建对应的策略模式&#xff0c…

Docker 常见操作及部署springboot、Shiro、SpringData脚手架(下)

1、查找jdk容器 docker search jdk 2、查看镜像 docker images 3、启动JDK镜像 docker run -di --namejdk1.8 clarinpl/java 4、查看镜像运行情况 docker ps 5、使用命令行进入容器 docker exec -it 48428f21b6ee /bin/bash 6、查看jdk版本 java -version 7、从宿主机复制…

面向对象 - 继承

Hello , 各位同学朋友大家好啊, 今天给大家分享的技术呢, 是面向对象三大特征之一的继承&#xff0c;我们今天主要按照以下几个点, 展开继承的讲解。目录 :* 继承的介绍* 继承的好处和弊端* 继承中成员访问特点 - 成员变量* 继承中成员访问特点 - 成员方法* 方法重写* 继承中成…

一文认知并发安全的几种解决方案与性能对比

Kotlin协程基本套餐&#xff1a;协程的基本使用协程的上下文理解协程的作用域管理协程的常见进阶使用之前的系列文章我们讲的是一些 Kotlin 协程的基本概念和一些实用与常用的技巧与方法。其实明白之后&#xff0c;基本的使用是没有问题了。那么今天我想探讨一下&#xff0c;没…

用gin写简单的crud后端API接口

提要使用gin框架(go的web框架)来创建简单的几个crud接口)使用技术: gin sqlite3 sqlx创建初始工程新建文件夹,创建三个子文件夹分别初始化工程 go mod如果没有.go文件,执行go mod tidy可能报错(warning: "all" matched no packages), 可以先不弄,只初始化模块就行(…

GreenPlum小结

什么是GreenPlum&#xff1f;GreenPlum是业界最快最高性价比的关系型分布式数据库,它在开源的PostgreSQL的基础上采用MPP架构&#xff08;Massive Parallel Processing&#xff0c;海量并行处理&#xff09;,具有强大的大规模数据分析任务处理能力。GreenPlum作为大数据融合存储…

【UE4 RTS游戏】03-摄像机运动_旋转视角

效果可以通过WASD控制“CameraPawn”的移动&#xff1b;通过鼠标中键旋转视角&#xff1b;通过alt鼠标中键将视角回归默认值&#xff1b;通过shift加速移动。步骤打开“CameraPawnController”&#xff0c;给如下节点添加注释&#xff0c;命名为“MovementX”接下来开始开始编辑…

JDK解压安装及idea开发工具配置

1. 安装JDK 1.1 下载安装包 下载安装包&#xff0c;直接解压&#xff0c;注意&#xff0c;解压的路径不要有中文 1.2 配置环境变量 右键点击我的电脑&#xff0c;选择属性 选择高级系统设置 选择环境变量 选择新建 在变量名中输入JAVA_HOME&#xff0c;变量值就是1.1中压缩包…

Windows环境下实现设计模式——访问者模式(JAVA版)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows环境下如何编程实现访问者模式&#xff08;设计模式&#xff09;。不知道大家有没有这样的感觉&#xff0c;看了一大堆编程和设计模式的书&#xff0c;却还是很难理解设计模式&#xff0c…

【C++】类和对象(收尾)

文章目录成员变量初始化问题初始化列表explicit关键字static成员特性&#xff1a;友元友元函数友元类内部类特性匿名对象成员变量初始化问题 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给了对象中各个成员变量一个合适的初始值。但是这并不能够称为对对象中成…

简单了解蓄电池在直流系统中的使用现状!

一般情况下&#xff0c;由市电通过直流配电屏为变电站的直流系统提供工作电源&#xff0c;包括对蓄电池组进行饱和和充电使蓄电池处于备用状态&#xff0c;当交流失电或系统需要进行大电流供电时&#xff0c;蓄电池需要迅速切入&#xff0c;向事故负荷、自动装置、保护装置以及…

本地套接字

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 本地套接字专栏&#xff1a;《Linux从小白到大神》《网络编程》 本地套接字通信需要一个文件&#xff…

tensorflow【import transformers 报错】

目录 一、安装 安装好了tensorflow,但是import时候报错&#xff1a; import transformers 报错 一、安装 &#xff08;1&#xff09;创建环境&#xff1a; conda create -n [name] python3.3-3.7 &#xff08;2&#xff09;激活环境&#xff1a; conda activate [name] …

Python中赋值、引用、深浅拷贝的区别和联系

文章目录一、对象的唯一id二、赋值三、可变对象和不可变对象四、函数的参数传递五、深拷贝和浅拷贝六、举个栗子6.1 不可变对象的拷贝6.2 可变对象的拷贝6.3 可变对象改变外层元素6.4 可变对象改变内层元素七、总结一、对象的唯一id python中的所有对象都有自己的唯一id&#…

典型回溯题目 - 全排列(一、二)

典型回溯题目 - 全排列&#xff08;一、二&#xff09; 46. 全排列 题目链接&#xff1a;46. 全排列状 题目大意&#xff1a; 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 注意&#xff1a;&#xff08;1&#xf…

Linux命令·which·whereis·locate·find

我们经常在linux要查找某个文件&#xff0c;但不知道放在哪里了&#xff0c;可以使用下面的一些命令来搜索&#xff1a; which 查看可执行文件的位置。whereis 查看文件的位置。 locate 配合数据库查看文件位置。find 实际搜寻硬盘查询文件名称。whichwhich命令的作用是&#x…

DJ1-1 操作系统引论

目录 一、操作系统的概念 二、操作系统的目标 三、操作系统的作用 一、操作系统的概念 定义一 操作系统是一组控制和管理计算机软硬件资源、合理地对各类作业进行调度以及方便用户使用的程序集合。 定义二 操作系统是位于硬件层&#xff08;HAL&#xff09;之上&#xff…

SQL 基础函数,通配符,BETWEEN ,用法复习

使用 SQL _ 通配符 下面的 SQL 语句选取 name 以一个任意字符开始&#xff0c;然后是 “oogle” 的所有客户&#xff1a; SELECT * FROM Websites WHERE name LIKE _oogle;下面的 SQL 语句选取 name 以 “G” 开始&#xff0c;然后是一个任意字符&#xff0c;然后是 “o”&am…

看完这篇我不信你不会二叉树的层序遍历【C语言】

目录 实现思路 代码实现 之前介绍了二叉树的前、中、后序三种遍历&#xff0c;采用的是递归的方式。今天我们来学习另外一种遍历方式——层序遍历。层序遍历不容小觑&#xff0c;虽然实现方法并不难&#xff0c;但是它所采取的思路是很值得学习的&#xff0c;与前三者不同&am…