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

【学习日记】【FreeRTOS】空闲任务与阻塞延时

写在前面

本文是基于野火 RTOS 教程对空闲任务和阻塞延时的详解。

一、什么是任务中的阻塞延时

  • 说到阻塞延时,笔者的第一反应就是在单片机的 while 循环中,使用一个 for 循环不断递减一个大数,通过 CPU 不断执行一条指令的耗时进行延时。这种延时会占用 CPU 资源执行指令,在延时的时候 CPU 不能执行其他的指令。
  • 但是注意,我们现在是想在 RTOS 中的任务实现阻塞延时,RTOS 可以有多个任务,所有所谓任务中的阻塞延时虽然也是阻塞其后的代码运行,但是只阻塞了他所在的那个任务中阻塞延时函数后面的代码。
  • 也就是说,RTOS 中,任务中的阻塞延时就是先阻塞一下这个任务,然后把 CPU 使用权交给其他代码,虽然也是阻塞下文的代码执行,但是只阻塞这个任务的下文,CPU 在这个过程中可以执行其他任务中的指令,大大提高 CPU 利用率,和笔者印象中的阻塞延时并不一样。

二、空闲任务有什么用

  • 空闲任务的优先级是所有任务中优先级最低的,当其他任务都在阻塞延时中,CPU 就会切换到空闲任务运行。
  • 一般来说在空闲任务里面运行一些系统内存的清理工作,或者在空闲任务中让单片机休眠或者进入低功耗模式。

三、空闲任务的实现

  1. 定义空闲任务的任务栈
  2. 定义空闲任务的 TCB
  3. 空闲任务的创建

注意,空闲任务的任务栈和 TCB 变量我们都在 main.c 中声明为全局变量,但是同时,我们想在开启任务调度器的时候自动创建一个空闲任务,而 RTOS 的开发人员不用显式地去创建空闲任务,所以我们把空闲任务的创建集成在 void vTaskStartScheduler( void ) 这个函数中。这样,我们在启动调度器的同时就会自动创建一个空闲任务。代码如下:

void vTaskStartScheduler( void )
{
/*======================================创建空闲任务start==============================================*/     TCB_t *pxIdleTaskTCBBuffer = NULL;               /* 用于指向空闲任务控制块 */StackType_t *pxIdleTaskStackBuffer = NULL;       /* 用于空闲任务栈起始地址 */uint32_t ulIdleTaskStackSize;/* 获取空闲任务的内存:任务栈和任务TCB */vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );    xIdleTaskHandle = xTaskCreateStatic( (TaskFunction_t)prvIdleTask,              /* 任务入口 */(char *)"IDLE",                           /* 任务名称,字符串形式 */(uint32_t)ulIdleTaskStackSize ,           /* 任务栈大小,单位为字 */(void *) NULL,                            /* 任务形参 */(StackType_t *)pxIdleTaskStackBuffer,     /* 任务栈起始地址 */(TCB_t *)pxIdleTaskTCBBuffer );           /* 任务控制块 *//* 将任务添加到就绪列表 */                                 vListInsertEnd( &( pxReadyTasksLists[0] ), &( ((TCB_t *)pxIdleTaskTCBBuffer)->xStateListItem ) );
/*======================================创建空闲任务end================================================*//* 手动指定第一个运行的任务 */pxCurrentTCB = &Task1TCB;/* 初始化系统时基计数器 */xTickCount = ( TickType_t ) 0U;/* 启动调度器 */if( xPortStartScheduler() != pdFALSE ){/* 调度器启动成功,则不会返回,即不会来到这里 */}
}

上面这段代码调用了 xTaskCreateStatic() 这个函数进行空闲任务的创建,但是这个函数需要传入空闲任务的任务栈和 TCB 变量,而我们把这些变量定义在了 main.c 中,所以需要使用 vApplicationGetIdleTaskMemory() 这个函数来使 vTaskStartScheduler() 函数中的任务指针等等变量指向定义在 main.c 中的任务栈和 TCB,然后再把这些任务指针等传入 xTaskCreateStatic() 中。vApplicationGetIdleTaskMemory() 的具体代码如下:

void vApplicationGetIdleTaskMemory( TCB_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{*ppxIdleTaskTCBBuffer=&IdleTaskTCB;*ppxIdleTaskStackBuffer=IdleTaskStack; *pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;
}

四、任务中的阻塞延时怎么实现

具体想法如下:

  1. 为 TCB 添加记录延时时间的参数
  2. 在任务中调用阻塞延时函数时,会给 TCB 记录延时时间的参数进行赋值,然后调用任务切换函数
  3. 调用任务切换函数会产生 PendSV 中断,在 PendSV中断服务函数中会调用上下文切换函数 vTaskSwitchContext()
  4. 在上下文切换函数中,我们更新当前执行任务的指针。现在我们的思想是,如果当前任务是空闲任务,那么查看其他任务的延时是否结束,如果没有结束就继续执行空闲任务;如果当前执行的不是空闲任务,那么检查一下其他任务是否在延时中,如果不在延时中,就不忘初心进行任务切换,如果在延时中,就判断现在这个任务是否要延时,如果要延时就切换到空闲任务,否则就不进行任何切换。
  5. 上面检查任务是否在延时状态都是通过检查 TCB 的延时参数是否为 0 来实现的,我们使用 SysTick 中断来对 TCB 的延时参数进行定时修改
  6. 在每次 SysTick 中断触发时,我们更新一下系统时基计数器(以后有用),然后扫描一下就绪列表中所有 TCB 的延时参数,不为 0 就减 1,最后尝试任务切换

1. 为 TCB 添加记录延时时间的参数

typedef struct tskTaskControlBlock
{volatile StackType_t    *pxTopOfStack;    /* 栈顶 */ListItem_t			    xStateListItem;   /* 任务节点 */StackType_t             *pxStack;         /* 任务栈起始地址 *//* 任务名称,字符串形式 */char                    pcTaskName[ configMAX_TASK_NAME_LEN ];  TickType_t xTicksToDelay; /* 用于延时 */
} tskTCB;
typedef tskTCB TCB_t;

2. 阻塞延时函数 vTaskDelay()

给 TCB 记录延时时间的参数进行赋值,然后调用任务切换函数。

void vTaskDelay( const TickType_t xTicksToDelay )
{TCB_t *pxTCB = NULL;/* 获取当前任务的TCB */pxTCB = pxCurrentTCB;/* 设置延时时间 */pxTCB->xTicksToDelay = xTicksToDelay;/* 任务切换 */taskYIELD();
}

3. 上下文切换函数 vTaskSwitchContext()

  • 如果当前任务是空闲任务
    • 查看其他任务的延时是否结束
      • 没有结束 -> 继续执行空闲任务
      • 结束 -> 跳转到其他任务
  • 如果当前执行的不是空闲任务
    • 检查一下其他任务是否在延时中
      • 不在延时中 -> 进行任务切换
      • 在延时中 -> 判断现在这个任务是否要延时
        • 要延时就切换到空闲任务
        • 否则就不进行任何切换
void vTaskSwitchContext( void )
{/* 如果当前线程是空闲线程,那么就去尝试执行线程1或者线程2,看看他们的延时时间是否结束,如果线程的延时时间均没有到期,那就返回继续执行空闲线程 */if( pxCurrentTCB == &IdleTaskTCB ){if(Task1TCB.xTicksToDelay == 0){            pxCurrentTCB =&Task1TCB;}else if(Task2TCB.xTicksToDelay == 0){pxCurrentTCB =&Task2TCB;}else{return;		/* 线程延时均没有到期则返回,继续执行空闲线程 */} }else{/*如果当前线程是线程1或者线程2的话,检查下另外一个线程,如果另外的线程不在延时中,就切换到该线程否则,判断下当前线程是否应该进入延时状态,如果是的话,就切换到空闲线程。否则就不进行任何切换 */if(pxCurrentTCB == &Task1TCB){if(Task2TCB.xTicksToDelay == 0){pxCurrentTCB =&Task2TCB;}else if(pxCurrentTCB->xTicksToDelay != 0){pxCurrentTCB = &IdleTaskTCB;}else {return;		/* 返回,不进行切换,因为两个线程都处于延时中 */}}else if(pxCurrentTCB == &Task2TCB){if(Task1TCB.xTicksToDelay == 0){pxCurrentTCB =&Task1TCB;}else if(pxCurrentTCB->xTicksToDelay != 0){pxCurrentTCB = &IdleTaskTCB;}else {return;		/* 返回,不进行切换,因为两个线程都处于延时中 */}}}
}

4. SysTick 中断对 TCB 的延时参数进行定时修改

/*
*************************************************************************
*                             SysTick中断服务函数
*************************************************************************
*/
void xPortSysTickHandler( void )
{/* 关中断 */vPortRaiseBASEPRI();/* 更新系统时基 */xTaskIncrementTick();/* 开中断 */vPortClearBASEPRIFromISR();
}

每次 SysTick 中断触发时,我们更新一下系统时基计数器(以后有用),然后扫描一下就绪列表中所有 TCB 的延时参数,不为 0 就减 1,最后尝试任务切换:

void xTaskIncrementTick( void )
{TCB_t *pxTCB = NULL;BaseType_t i = 0;/* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;/* 扫描就绪列表中所有线程的xTicksToDelay,如果不为0,则减1 */for(i=0; i<configMAX_PRIORITIES; i++){pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &pxReadyTasksLists[i] ) );if(pxTCB->xTicksToDelay > 0){pxTCB->xTicksToDelay --;}}/* 任务切换 */portYIELD();
}

关于上面这段代码,有一段写得很奇怪:

    /* 更新系统时基计数器xTickCount,xTickCount是一个在port.c中定义的全局变量 */const TickType_t xConstTickCount = xTickCount + 1;xTickCount = xConstTickCount;

笔者刚开始看到的时候想问:直接递增xTickCount不行吗,为什么要写成
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
这样不是画蛇添足吗?使代码更复杂。

其实不然,在任务调度器中,xTickCount 变量用于记录系统的时基计数器。它的目的是跟踪系统运行的时间,并且根据需要递增。

直接递增 xTickCount 可能会导致并发问题。在多线程或多任务的情况下,如果有多个任务同时尝试递增 xTickCount,并且中间存在竞争条件,可能会导致计数不准确或不一致。

为了避免这种并发问题,代码中将递增操作分解为两个步骤:

首先,通过 const TickType_t xConstTickCount = xTickCount + 1; 将 xTickCount 的值复制到一个中间变量 xConstTickCount 中,并递增这个中间变量。

然后,将中间变量 xConstTickCount 的值赋回给 xTickCount,完成递增操作。

这样做的好处是,无论何时进行递增操作,代码都使用了一个稳定的中间值 xConstTickCount 来执行计算和更新。这确保了计数器 xTickCount 在整个递增过程中保持一致,并且不会受到其他任务的干扰。这样可以避免并发问题,提高代码的可靠性和正确性。

5. 最后是 SysTick 的相关初始化代码

在调度器启动函数 xPortStartScheduler() 函数中调用 vPortSetupTimerInterrupt():

/*
*************************************************************************
*                              调度器启动函数
*************************************************************************
*/BaseType_t xPortStartScheduler( void )
{/*PendSV是一个用于低优先级任务切换的软件中断。通过触发PendSV中断,可以请求处理器在合适的时间切换到更高优先级的任务。PendSV中断具有最低的中断优先级,因此可以在其他中断处理完成后立即执行。*//* 配置PendSV 和 SysTick 的中断优先级为最低 */portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;//初始化SysTick中断vPortSetupTimerInterrupt();/* 启动第一个任务,不再返回 */prvStartFirstTask();/* 不应该运行到这里 */return 0;
}

初始化 SysTick 的函数 vPortSetupTimerInterrupt():

/*
*************************************************************************
*                             初始化SysTick
*************************************************************************
*/
void vPortSetupTimerInterrupt( void )
{/* 设置重装载寄存器的值 */portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;/* 设置系统定时器的时钟等于内核时钟使能SysTick 定时器中断使能SysTick 定时器 */portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT |portNVIC_SYSTICK_ENABLE_BIT ); 
}

这里解释一下重装载寄存器的值怎么设置。计时器实际上是一个计数器,当接收到设定数量的脉冲后进行一次中断,而这个设定的数量就是重装载寄存器的值。

我们把计时器接入到 CPU 晶振后,由于晶振每隔一段固定时间发出一个脉冲信号,此时计时器就将重装载寄存器的值减 1,当重装载寄存器的值减到 0 后,就触发一次中断,由此完成了对晶振的高频率信号的分频。

注意,重装载寄存器的值是从 0 开始减的,所以设置时要减 1。

可以看到,我们使用 configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL 进行设置,configSYSTICK_CLOCK_HZ 实际上就是 CPU 的晶振频率,而 configTICK_RATE_HZ 就是我们设置 SysTick 的中断频率。

其中的宏定义为:

#define configCPU_CLOCK_HZ			( ( unsigned long ) 25000000 )	
#define configTICK_RATE_HZ			( ( TickType_t ) 100 )/* SysTick 配置寄存器 */
#define portNVIC_SYSTICK_CTRL_REG			( * ( ( volatile uint32_t * ) 0xe000e010 ) )
#define portNVIC_SYSTICK_LOAD_REG			( * ( ( volatile uint32_t * ) 0xe000e014 ) )#ifndef configSYSTICK_CLOCK_HZ#define configSYSTICK_CLOCK_HZ configCPU_CLOCK_HZ/* 确保SysTick的时钟与内核时钟一致 */#define portNVIC_SYSTICK_CLK_BIT	( 1UL << 2UL )
#else#define portNVIC_SYSTICK_CLK_BIT	( 0 )
#endif#define portNVIC_SYSTICK_INT_BIT			( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT			( 1UL << 0UL )

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

相关文章:

【学习日记】【FreeRTOS】空闲任务与阻塞延时

写在前面 本文是基于野火 RTOS 教程对空闲任务和阻塞延时的详解。 一、什么是任务中的阻塞延时 说到阻塞延时&#xff0c;笔者的第一反应就是在单片机的 while 循环中&#xff0c;使用一个 for 循环不断递减一个大数&#xff0c;通过 CPU 不断执行一条指令的耗时进行延时。这…...

衣服材质等整理(时常更新)

参考文章&图片来源 https://zhuanlan.zhihu.com/p/390341736 00. 天然纤维 01. 化学纤维 02. 聚酯纤维&#xff08;即&#xff0c;涤纶&#xff09; 一种由有机二元酸和二元醇通过化学缩聚制成的合成纤维。具有出色的抗皱性和保形性&#xff0c;所制衣物在穿着过程中不容…...

电子商务环境下旅游价值链

迈克尔 ・ 波特(Michael E. Porter)在其《竞争优势》一书中提出了“价值链” 的概念&#xff0c;并认为一家企业最核心的竞争优势在于对价值链的设计。虽然迈克尔 ・ 波 特提出的价值链主要是针对企业内部的价值链&#xff0c;但他视价值链为一系列连续完成的 活动&#xff…...

spring源码分析bean的生命周期(下)

doGetBean()执行过程 createBean()执行过程 一、DependsOn注解 spring创建对象之前会判断类上是否加了DependsOn注解&#xff0c;加了会遍历然后会添加到一个map中&#xff0c;spring会先创建DependsOn注解指定的类 二、spring类加载器 在合并BeanDefinition&#xff0c;确定…...

完美解决Github提交PR后报错:File is not gofumpt-ed (gofumpt)

问题阐述 最近在Github上提交PR后&#xff0c;遇到了这么一个问题&#xff1a;golangci-lint运行失败&#xff0c;具体原因是File is not gofumpt-ed (gofumpt)。 名词解释 golangci-lint&#xff1a; golangci-lint 是Go语言社区中常用的代码质量检查工具&#xff0c;它可以…...

pytorch3d成功安装

一、pytorch3d是什么&#xff1f; PyTorch3D的目标是帮助加速深度学习和3D交叉点的研究。3D数据比2D图像更复杂&#xff0c;在从事Mesh R-CNN和C3DPO等项目时&#xff0c;我们遇到了一些挑战&#xff0c;包括3D数据表示、批处理和速度。我们开发了许多有用的算子和抽象&#xf…...

【vue3】同个页面引入多个图表组件实现自适应的方法

首先说明&#xff0c;此方案仅针对vue3项目在同一个页面引入了多个图表组件&#xff0c;因为我发现不能框架不同的引入&#xff0c;resize的写法还不同 window.addEventListener("resize", function() {...// 在此处重新调用即可 }以下是具体写法&#xff1a; 循环…...

一文了解汽车芯片的分类及用途介绍

汽车芯片按其功能可分为控制类&#xff08;MCU和AI芯片&#xff09;、功率类、传感器和其他&#xff08;如存储器&#xff09;四种类型。市场基本被国际巨头所垄断。人们常说的汽车芯片是指汽车里的计算芯片&#xff0c;按集成规模可分为MCU芯片和AI芯片&#xff08;SoC芯片&am…...

Linux0.11内核源码解析-truncate.c

truncate文件只要实现释放指定i节点在设备上占用的所有逻辑块&#xff0c;包括直接块、一次间接块、二次间接块。从而将文件节点对应的文件长度截为0&#xff0c;并释放占用的设备空间。 索引节点的逻辑块连接方式 释放一次间接块 static void free_ind(int dev,int block) {…...

LED驱动型IC芯片的原理介绍

一、LED驱动器是什么 LED驱动器&#xff08;LED Driver&#xff09;&#xff0c;是指驱动LED发光或LED模块组件正常工作的电源调整电子器件。由于LED PN结的导通特性决定&#xff0c;它能适应的电源电压和电流变动范围十分狭窄&#xff0c;稍许偏离就可能无法点亮LED或者发光效…...

VLAN实验

实验题目如下&#xff1a; 实验拓扑如下&#xff1a; 实验要求如下&#xff1a; 【1】PC1/3的接口均为access模式&#xff0c;且属于van2&#xff0c;在同一网段 【2】PC2/4/5/6的IP地址在同一网段&#xff0c;与PC1/3不在同一网段 【3】PC2可以访问4/5/6&#xff0c;PC4不能…...

Qt应用开发(基础篇)——高级纯文本窗口 QPlainTextEdit

一、前言 QPlainTextEdit类继承于QAbstractScrollArea&#xff0c;QAbstractScrollArea继承于QFrame&#xff0c;是Qt用来显示和编辑纯文本的窗口。 滚屏区域基类https://blog.csdn.net/u014491932/article/details/132245486?spm1001.2014.3001.5501框架类QFramehttps://blo…...

三维可视化平台有哪些?Sovit3D可视化平台怎么样?

随着社会经济的发展和数字技术的进步&#xff0c;互联网行业发展迅速。为了适应新时代社会发展的需要&#xff0c;大数据在这个社会经济发展过程中随着技术的进步而显得尤为重要。同时&#xff0c;大数据技术的快速发展进程也推动了可视化技术的飞速发展&#xff0c;国内外各类…...

Xxl-job安装部署以及SpringBoot集成Xxl-job使用

1、安装Xxl-job&#xff1a; 可以使用docker拉取镜像部署和源码编译两种方式&#xff0c;这里选择源码编译安装。 代码拉取地址&#xff1a; https://github.com/xuxueli/xxl-job/tree/2.1.2 官方开发文档&#xff1a; https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%…...

【【超声波避障小车代码】】

超声波避障小车代码 #include <reg51.h> //通用51头文件 #include <intrins.h> //使用了_nop()_函数#define uchar unsigned char //用 uchar 表示 unsigned char 类型 #define uint unsigned int //用 uint 表示 unsigned int 类型sbit EN…...

TDI(Time Delay Integration)

TDI&#xff08;Time Delay Integration&#xff09;是一种特殊的图像采集技术&#xff0c;常用于线阵CCD&#xff08;Charge-Coupled Device&#xff09;相机。TDI技术可以在保持高分辨率的同时增强图像的信噪比&#xff08;Signal-to-Noise Ratio, SNR&#xff09;&#xff0…...

RHCE——一、安装部署及例行性工作

RHCE 一、网络服务1、准备工作2、RHEL9操作系统的安装部署3、配置并优化RHEL9操作系统4、网络配置5、修改网络连接 二、例行性工作1、单一执行的例行性工作2、循环执行的例行性工作 三、书写定时任务的注意事项四、系统级别的计划任务五、实验1、实验一&#xff1a;编写脚本tes…...

服务器数据库中了360后缀勒索病毒怎么办?360后缀勒索病毒的加密形式

随着信息技术的发展&#xff0c;企业的计算机服务器数据库变得越来越重要。然而&#xff0c;在数字时代&#xff0c;网络上的威胁也日益增多。近期&#xff0c;我们收到很多企业的求助&#xff0c;企业的计算机服务器遭到了360后缀勒索病毒的攻击&#xff0c;导致服务器内的所有…...

期权就是股指期货吗,哪个好做一点?

近年来&#xff0c;场内ETF期权产品不断扩大&#xff0c;越来越多的投资者有投资期权的想法。当我们看到期权时&#xff0c;我们会不知不觉地想到期货&#xff0c;虽然期货与期权只有一个字的区别&#xff0c;但实际上有很大的不同&#xff0c;那么期权就是股指期货吗&#xff…...

week32

本周目标&#xff1a; Belady现象的解释 操作系统 计组IO/MM chapter 刷力扣 ubuntu磁盘/网络/命令行进阶*1 tarball之类的使用 Question 大数据系统实验要学吗&#xff1f; 据说课讲得不好这是一门类似数据库的课程——大数据之hadoop / hive / hbase 的区别是什么&a…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

[Java恶补day16] 238.除自身以外数组的乘积

给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&#xff0c;且在 O(n) 时间复杂度…...

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…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...