如果不想看的可以直接使用git把我的代码下载出来,里面工程挺全的,后期会慢慢的补注释之类的

码云地址:stm32学习笔记: stm32学习笔记源码

如果不会使用git快速下载可以选择直接下载压缩包或者去看看git的使用

Git入门教程-CSDN博客

一  调度机制

1.1 任务调度介绍

在创建任务之前,我们先熟悉下调度机制

在freertos中,我们一般使用的就是抢占式+时间片

在不同优先级的情况下默认为抢占式,就是谁优先级高,优先级高的任务进入就绪态后可以直接执行。

相同优先级下:使用时间片调度,在时间片调度下每个任务会固定执行一个时间片

携程式调度:这个官方自己不更新了,并且不管是什么优先级的任务,如果任务不释放CPU任务就会一直继续运行。所以我感觉没那么方便控制,这边就不写了,有兴趣了的可以自己去试试。

1.1.1 抢占式调度

抢占式调度,需要任务在不同优先级下才能体现出来。

1 高优先级会先执行   2高优先级任务不停止,低优先级任务就不会执行   3被抢占的任务会进入就绪态(什么是就绪态后面会写,可以直接跳目录到任务状态那里去)

这里有个示例:如大家可以看看上面的图。

这边我们写个代码测试一下:(先看看效果,具体实现后续代码会细讲


/* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO         1
#define START_TASK_STACK_SIZE   128
TaskHandle_t    start_task_handler;
void start_task( void * pvParameters );/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO         3
#define TASK2_STACK_SIZE   128
TaskHandle_t    task2_handler;
void task2( void * pvParameters );void freertos_demo(void)
{    xTaskCreate((TaskFunction_t         )   start_task,(char *                 )   "start_task",(configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   START_TASK_PRIO,(TaskHandle_t *         )   &start_task_handler );vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL();               /* 进入临界区 */xTaskCreate((TaskFunction_t         )   task1,(char *                 )   "task1",(configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   TASK1_PRIO,(TaskHandle_t *         )   &task1_handler );xTaskCreate((TaskFunction_t         )   task2,(char *                 )   "task2",(configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   TASK2_PRIO,(TaskHandle_t *         )   &task2_handler );								vTaskDelete(NULL);taskEXIT_CRITICAL();                /* 退出临界区 */
}void task1( void * pvParameters )
{while (1){printf("task 1\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");for(int i=0;i<10000000;i++){}}
}

这里我们使用xTaskCreate() 创建了3个任务,参数看不懂没关系,后面会细讲,先了解下调度机制和任务状态

分别是一个启动任务:这个启动任务在去创建了两个任务,创建完成之后删除自身,防止任务被重复创建

两个任务:

task1:优先级为2  打印一个task 1 ,之后释放CPU500ms

task1:优先级为2  打印一个task 1 ,之后释放CPU500ms

 taskENTER_CRITICAL();               /* 进入临界区 */

taskEXIT_CRITICAL();                /* 退出临界区 */

这两个函数是防止创建任务之后任务直接被启动,比如task1创建之后,不管后面的task2优先级有多高,task都会先运行一下,直到task2创建完成,抢占他的cpu,我们等会也会对其关闭测试。

1 高优先级任务不释放CPU(开启临界区了的)

这个就是我们上面的代码跑出来的结果,抢占式调度:高优先级的任务如果不释放CPU那么低优先级的任务永远也执行不了

2 高优先级的任务不释放CPU(关闭临界区了的)
void start_task( void * pvParameters )
{//taskENTER_CRITICAL();               /* 进入临界区 */xTaskCreate((TaskFunction_t         )   task1,(char *                 )   "task1",(configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   TASK1_PRIO,(TaskHandle_t *         )   &task1_handler );xTaskCreate((TaskFunction_t         )   task2,(char *                 )   "task2",(configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   TASK2_PRIO,(TaskHandle_t *         )   &task2_handler );								vTaskDelete(NULL);//taskEXIT_CRITICAL();                /* 退出临界区 */
}

我们上面屏蔽临界区代码之后产生的效果,如上所说,不管优先级怎么样,在task2创建出来之前task1会直接执行。

3 高优先级任务加入CPU释放(开启临界区了的)
void task2( void * pvParameters )
{while (1){printf("task 2\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}

可以明显的看见,我们这里task2运行之后释放掉了CPU,之后task运行。

4 高优先级任务不释放cpu(开启临界区了的)

这里大家根据1.1.1的抢占式调度的任务图,大家觉得会怎么样?是task1一直运行,还是说task进入就绪态后直接抢占task1?

void task1( void * pvParameters )
{while (1){printf("task 1\r\n");for(int i=0;i<10000000;i++){}}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}

这里很明显,在task2执行完成后,释放掉cpu,这时候task1运行结论就是低优先级的就算不释放cpu高优先级的一样占用。在经过500ms之后,task2又进入了就绪态,直接抢占了task1,这时候task1的for循环都还没有执行完,等task1的for循环执行完成后,又能执行打印task1了。

这个就是抢占式调度的测试了。

1.1.2 时间片调度

同等优先级的任务会轮流使用CPU(一般是1个时间片)

task1 task2 task3只使用一个时间片,如果task1第一次只使用了0.1个时间片,第二次并不会给他补0.9(这样做会让cpu等待的时间累积的越来越多,很不合理)

这里我们可以再次测试

首先将优先级改为一致

/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO         2
#define TASK2_STACK_SIZE   128
TaskHandle_t    task2_handler;
void task2( void * pvParameters );

之后测试:

1.不释放CPU
void task1( void * pvParameters )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}//vTaskDelay(pdMS_TO_TICKS(500));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}//vTaskDelay(pdMS_TO_TICKS(500));}
}

这里会怎么样呢?我的一个时间片是1ms,先运行task1之后,这里会一直循环打印task1,在运行1ms之后,运行task2

这里可以看见,时间戳都给我干卡死了,并且已经全乱码了,在一个时间片来回切换串口资源导致串口已经乱掉了。

2 释放CPU

这里释放了1ms cpu但是还是肉眼可见的乱码了

之后直接释放了10个时间片打印才正常(这里就引出了一个问题,过快的访问外设资源导致的乱码问题,这种时候我们需要添加一个互斥锁这个后面互斥锁章节说)

这里两种调度模式就说完了,时间片调度就是这个效果了,如果不释放cpu就是1ms内循环执行(这里上面的任务图说的是1ms内没执行完就丢掉,但是我们这里是在一个while循环中,并不会说执行完就完了,所以就会循环执行)

1.2 任务状态

1.2.1 任务状态转换图介绍

在freertos中任务有四种状态:挂起、就绪、运行、阻塞

所有状态想进入运行态都需要先进入就绪态

挂起态:运行态的时候或者就绪态的时候调用函数vtasksuspend

就绪态:cpu释放的时间到了(阻塞态的时间结束了),就会进入就绪态。挂起态直接使用函数也能进入就绪态

阻塞态:就是上面的vTaskDelay(pdMS_TO_TICKS(500));这种类型的函数

运行态:就绪态列表的第一个任务就会进入运行态(会按优先级排序)

二 创建任务的函数与整体框架

2.1 整体框架

2.1.1main函数

main函数:单片机最先进入的c语言部分

int main(void)
{	//LCD 初始化ILI9341_Init ();         /* USART config */USART_Config();  //其中0、3、5、6 模式适合从左至右显示文字,//不推荐使用其它模式显示文字	其它模式显示文字会有镜像效果			//其中 6 模式为大部分液晶例程的默认显示方向  ILI9341_GramScan ( 6 );freertos_demo();
}

这里有个freertos_demo函数,就是创建任务的函数,上面的这些都是外设初始化,其实这些外设初始化也可以使用一个单独的函数来初始化,但是需要注意各个外设的开启·时间,防止硬件不执行。

2.1.2 freertos_demo函数

freertos_demo函数内容一个创建任务函数和启动任务调度器

启动任务调度器必须在所有初始化工作(如硬件初始化、任务创建、信号量创建等)完成后调用,且整个系统中只能调用一次。这个start_task就是一个典型的启动任务了。里面包含所有任务,在创建完任务后,就执行任务调度器即可。

void freertos_demo(void)
{    xTaskCreate((TaskFunction_t         )   start_task,(char *                 )   "start_task",(configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   START_TASK_PRIO,(TaskHandle_t *         )   &start_task_handler );vTaskStartScheduler();
}

2.1.3 start_task函数

start_task:创建所有的任务,方便统一管理

start_task 是典型的 “启动任务”(或叫初始化任务),它的唯一职责是:

  1. 集中创建其他业务任务(如 task1task2)。
  2. 完成初始化后立即删除自己,释放系统资源(栈空间、任务控制块等)。
void start_task( void * pvParameters )
{taskENTER_CRITICAL();               /* 进入临界区 */xTaskCreate((TaskFunction_t         )   task1,(char *                 )   "task1",(configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   TASK1_PRIO,(TaskHandle_t *         )   &task1_handler );xTaskCreate((TaskFunction_t         )   task2,(char *                 )   "task2",(configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   TASK2_PRIO,(TaskHandle_t *         )   &task2_handler );								vTaskDelete(NULL);taskEXIT_CRITICAL();                /* 退出临界区 */
}

2.1.4 task函数

task:具体任务的处理,你需要处理数据在其内部处理即可

void task1( void * pvParameters )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}

这里就是整个代码的大概框架了

2.2 本章使用到的函数讲解

2.2.1 动态创建任务函数

xTaskCreate:函数原型如下

动态与静态的区别:

动态:任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配

静态:任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供

这里来说一下函数原型需要传入的参数:PS文章为使用讲解哈,不涉及函数的实现,这个可以在后面的系统设计讲解

1 pxTaskCode 指向任务函数的指针

就是把你函数的名字传进去即可

2 pcName

给任务起一个名称,主要用于调试和可视化分析。这个名称可以帮助开发者在调试过程中,通过调试工具(如 FreeRTOS 的可视化调试插件)快速识别不同的任务

3 uxStackDepth

指定任务栈的大小:单位是字,不是字节写128的话就是128 * 4 字节

4 pvParameters

用于向任务函数传递参数。当任务函数需要接收外部数据时,可以通过这个参数传入:我们这里没有传入东西,还有后面用到的时候理解

uxPriority

优先级:越大越高理论上软件可以开任意优先级数量,但是由于硬件等限制,在配置文件中一般都写的32

6 pxCreatedTask

是一个指向任务句柄的指针。用于获取新创建任务的句柄。任务句柄是任务的唯一标识,通过任务句柄可以在运行时对任务进行管理,比如挂起、恢复、删除任务等操作。

类型定义为TaskHandle_t即可

2.2.2 vTaskDelete()函数

就是删除任务:传入句柄即可,本文使用:创建完task1和taks2后删除start任务,如果是删除自身,函数内田NULL即可删除自己,如果是在自己函数内需要删除其他任务,就需要填写对应的句柄,句柄保存了任务的内存地址。

2.2.3 vTaskStartScheduler()函数

启动任务调度器函数

启动任务调度器必须在所有初始化工作(如硬件初始化、任务创建、信号量创建等)完成后调用,且整个系统中只能调用一次。这个start_task就是一个典型的启动任务了。里面包含所有任务,在创建完任务后,就执行任务调度器即可。

2.2.4 临界区函数

taskENTER_CRITICAL();               /* 进入临界区 */

taskEXIT_CRITICAL();                /* 退出临界区 */

进入临界区后会停止调度,这时候低优先级的任务就算先创建了也不会先运行了。

附上本文代码(同步上传gitee了)

不想下载的可以直接复制

#include "FreeDome.h"/* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO         1
#define START_TASK_STACK_SIZE   128
TaskHandle_t    start_task_handler;
void start_task( void * pvParameters );/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO         2
#define TASK1_STACK_SIZE   128
TaskHandle_t    task1_handler;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO         2
#define TASK2_STACK_SIZE   128
TaskHandle_t    task2_handler;
void task2( void * pvParameters );// 全局共享常量(所有任务共用)
#define CHAR_WIDTH     WIDTH_CH_CHAR  // 中文字符宽度
#define SCREEN_WIDTH   LCD_X_LENGTH   // 屏幕宽度
#define FONT_HEIGHT    LCD_GetFont()->Height  // 字体高度void freertos_demo(void)
{    xTaskCreate((TaskFunction_t         )   start_task,(char *                 )   "start_task",(configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   START_TASK_PRIO,(TaskHandle_t *         )   &start_task_handler );vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL();               /* 进入临界区 */xTaskCreate((TaskFunction_t         )   task1,(char *                 )   "task1",(configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   TASK1_PRIO,(TaskHandle_t *         )   &task1_handler );xTaskCreate((TaskFunction_t         )   task2,(char *                 )   "task2",(configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,(void *                 )   NULL,(UBaseType_t            )   TASK2_PRIO,(TaskHandle_t *         )   &task2_handler );								vTaskDelete(NULL);taskEXIT_CRITICAL();                /* 退出临界区 */
}void task1( void * pvParameters )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"	
#include "./lcd/bsp_ili9341_lcd.h"
#include "./flash/bsp_spi_flash.h"
#include "FreeDome.h"int main(void)
{	//LCD 初始化ILI9341_Init ();         /* USART config */USART_Config();  //其中0、3、5、6 模式适合从左至右显示文字,//不推荐使用其它模式显示文字	其它模式显示文字会有镜像效果			//其中 6 模式为大部分液晶例程的默认显示方向  ILI9341_GramScan ( 6 );freertos_demo();
}

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

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

    相关文章

    C++中std::vector Vs std::deque VS std::list对比详解

    1) 核心差异速览 std::vector&#xff1a;连续内存、随机访问 O(1)、尾部 push_back 摊还 O(1)、中间插入/删除 O(n)&#xff0c;非常缓存友好。std::deque&#xff1a;分段&#xff08;block&#xff09;存储&#xff0c;不是整体连续&#xff1b;随机访问 O(1)&#xff08;但…

    【js】js实现日期转大写:

    文章目录一、方法&#xff1a;二、使用效果&#xff1a;一、方法&#xff1a; export function dateToChnese(strDate) {let dateMap {year: "",month: "",day: ""}if (!strDate || strDate.length 0) return dateMap;const chineseDigit [&…

    逆向 js

    参考地址&#xff1a;https://blog.csdn.net/2302_80243887/article/details/146349209 注意事项 1. crypto-js 安装 需要你的.js文件同级目录执行npm install crypto-js 才能让js文件引入包 注意事项2&#xff1a; crypto-js 执行js 报错_external_runtime.py" A…

    FFmpeg的安装及简单使用

    简介 FFmpeg 是一个跨平台的音视频处理工具库/命令行工具&#xff0c;其核心作用是&#xff1a;对音视频文件或流进行解码、转换&#xff08;编码&#xff09;、封装/解封装等处理。 友情提示 本次安装以Windows64位操作系统为例 一、下载及安装 1、前往FFmpeg官网&#xff0…

    Science Advances--3D打印生物启发扭曲双曲超材料,用于无人机冲击缓冲和自供电实时传感

    湍流引起的振动会对飞机的结构完整性及飞行稳定性造成巨大威胁&#xff0c;尤其是在无人驾驶飞行器&#xff08;UAV&#xff09;中&#xff0c;实时的冲击监测和轻质防护尤为重要。该研究基于生物启发&#xff0c;通过3D 打印尼龙PA12 制备了一种扭转-双曲面超材料&#xff08;…

    AI大模型的研发流程

    开发一个大模型是一个庞大、复杂且资源密集的系统工程&#xff0c;涉及算法研究、工程实现、数据管理和算力基础设施等多个层面。下面我将为您提供一个从零开始开发大模型的全景式路线图&#xff0c;涵盖了从概念到部署的全过程。请注意&#xff0c;完全从零开始训练一个类似GP…

    Docker desktop安装Redis Cluster集群

    本文章将介绍如何在 Windows 系统的 Docker Desktop 环境中搭建 Redis 集群。将创建一个包含 6 个节点&#xff08;3 主 3 从&#xff09;的 Redis 集群。 环境准备 Windows 10/11 操作系统Docker Desktop 已安装并运行 步骤 清理环境&#xff08;如之前有尝试&#xff09; 如果…

    Zynq开发实践(SDK之第一个纯PS工程)

    【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】学编程的时候&#xff0c;大家一般都比较重视第一个项目的创建和执行。第一个fpga程序一般是led闪烁&#xff0c;第一个c程序一般就是hello world程…

    EJS(Embedded JavaScript)(一个基于JavaScript的模板引擎,用于在HTML中嵌入动态内容)

    文章目录**1. 什么是 EJS&#xff1f;****2. 核心特点**- **接近原生 HTML**- **动态渲染**- **轻量高效**- **与 Express 深度集成****3. EJS 的基本语法****4. 示例代码****HTML 模板&#xff08;views/user.ejs&#xff09;****Express 中渲染模板****5. 使用场景**1. **服务…

    Linux:基于阻塞队列的生产者消费模型

    文章目录一、生产者消费者模型的基本原则&#x1f495;&#x1f495;生产者-消费者模型的 321 原则&#x1f495;&#x1f495;二、为何要使用生产者消费者模型1. 解耦2. 支持并发 &#xff08;提高效率&#xff09;3. 忙闲不均的支持三、基于 BlockingQueue 的生产者消费者模型…

    ensp启动路由器报错40

    1. 先关闭 eNSP 模拟器、关闭 Virtualbox2. 在everything里面搜索 .VirtualBox文件夹&#xff0c;然后删掉3. 再打开 eNSP&#xff0c;不添加任何模拟设备&#xff0c;单击“菜单-工具-注册设备”&#xff0c;将 AR_Base 重新注册。4. 关闭 eNSP 模拟器

    代码随想录二刷之“图论”~GO

    A.深搜与广搜&#xff08;重点掌握&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; 深搜类似于回溯法 搜索方向&#xff0c;是认准一个方向搜&#xff0c;直到碰壁之后再换方向换方向是撤销原路径&#xff0c;改为节点链接的下一个路径&#xff0c;回溯的过程…

    基于Echarts+HTML5可视化数据大屏展示-白茶大数据溯源平台V2

    效果展示&#xff1a;代码结构&#xff1a;主要代码实现 index.html布局 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta n…

    Linux 系统网络配置及 IP 地址相关知识汇总

    Linux 系统网络配置及 IP 地址相关知识汇总 一、IP地址基础 IP地址&#xff1a;在计算机网络中用来唯一标识一台设备的一组数字。 二、IPv4相关知识 1. IPv4的表示方法 采用点分十进制表示&#xff0c;即由4个0-255的十进制数通过点分隔组成&#xff08;如192.168.1.1&#xff…

    百度股价突破120美元创年内新高,AI云成为增长新引擎

    美东时间9月16日&#xff0c;百度&#xff08;NASDAQ: BIDU&#xff09;美股大涨近8%&#xff0c;收盘价突破120美元&#xff0c;站上124美元高位&#xff0c;创2023年10月以来新高。北京时间9月17日港股开盘&#xff0c;百度&#xff08;09888.HK&#xff09;港股再次暴涨&…

    《彩虹六号:围攻》“Siege X”发布会3月14日举行!

    使用jQuery的常用方法与返回值分析 jQuery是一个轻量级的JavaScript库&#xff0c;旨在简化HTML文档遍历和操作、事件处理以及动画效果的创建。本文将介绍一些常用的jQuery方法及其返回值&#xff0c;帮助开发者更好地理解和运用这一强大的库。 1. 选择器方法 jQuery提供了多种…

    [从青铜到王者] Spring Boot+Redis+Kafka电商场景面试全解析

    互联网大厂Java开发岗技术面试实录&#xff1a;严肃面试官VS搞笑程序员谢飞机 文章内容 第一轮&#xff1a;基础框架与并发控制&#xff08;电商系统基础能力&#xff09; 面试官&#xff08;严肃&#xff09;&#xff1a;欢迎进入面试环节&#xff0c;首先请用3句话总结Spring…

    【DMA】DMA架构解析

    目录 1 DMA架构 1. 芯片架构图一览 2. AHB总线矩阵挂载 3. AHB1/APB1的桥和AHB1/APB2的桥 4. DMA1 和 DMA2 的区别 2 AHB总线矩阵 1 DMA架构 1. 芯片架构图一览 2. AHB总线矩阵挂载 stm32F411 芯片的 AHB 总线矩阵上共挂载了 6 主 5 从 六主&#xff1a; Icode-bus、D…

    GPS 定位器:精准追踪的“隐形守护者”

    GPS 定位器&#xff1a;精准追踪的“隐形守护者” 一、什么是 GPS 定位器&#xff1f; GPS 定位器是一种基于 全球定位系统&#xff08;Global Positioning System, GPS&#xff09; 的智能追踪设备。 通过接收卫星信号并结合通信模块&#xff08;如 4G、NB-IoT&#xff09;&am…

    前端拖拽排序实现

    1. 使用 HTML5 事件 触发时机 核心任务 dragstart 开始拖拽时 准备数据&#xff0c;贴上标签 dragover 经过目标上方时 必须 preventDefault()&#xff0c;发出“允许放置”的信号 dragleave 离开目标上方时 清理高亮等临时视觉效果 drop 在目标上松手时 接收数据…