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

FreeRTOS的简单介绍

一、FreeRTOS介绍

FreeRTOS并不是实时操作系统,因为它是分时复用的

利用CubeMX快速移植

二、快速移植流程

1. 在 SYS 选项里,将 Debug 设为 Serial Wire ,并且将 Timebase Source 设为 TIM2 (其它定时器也行)。为何要如此配置?下文解说。

裸机的时钟源默认是 SysTick ,但是开启 FreeRTOS 后, FreeRTOS 会占用 SysTick (用来生成 1ms定时,用于任务调度),所以需要需要为其他总线提供另外的时钟源。

2. RCC 里的 HSE 设置为 Crystal/Ceramic Resonator

3. 打开串口

4. 时钟按下图配置

5. 选择 FREERTOS 选项,并将 Interface 改为 CMSIS_V1

6. 配置项目信息,并导出代码。

三、任务的创建与删除

实操

四、任务的调度

void StarttaskKEY1(void const * argument)
{/* USER CODE BEGIN StarttaskKEY1 *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){printf("KEY1被按下来\r\n");if(taskLED1Handle  ==  NULL){printf("任务不存在,准备创建任务1!\r\n");osThreadDef(taskLED1, StarttaskLED1, osPriorityNormal, 0, 128);taskLED1Handle = osThreadCreate(osThread(taskLED1), NULL);if(taskLED1Handle != NULL )printf("任务创建完成!\r\n");}else{printf("删除任务!\r\n");osThreadTerminate(taskLED1Handle);taskLED1Handle = NULL;}}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StarttaskKEY1 */
}/* USER CODE BEGIN Header_StarttaskKEY2 */
/**
* @brief Function implementing the taskKEY2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StarttaskKEY2 */
void StarttaskKEY2(void const * argument)
{/* USER CODE BEGIN StarttaskKEY2 */static int flag =0;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){printf("KEY2被按下来\r\n");if(flag == 0){osThreadSuspend(taskLED2Handle);printf("任务2已暂停!\r\n");flag=1;}else{osThreadResume(taskLED2Handle);printf("任务2已恢复\r\n");flag=0;}}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(1);}/* USER CODE END StarttaskKEY2 */
}

六、队列

队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息。

队列项目:队列中的每一个数据;

队列长度:队列能够存储队列项目的最大数量;

创建队列时,需要指定队列长度及队列项目大小

代码如下:

void StarttaskSend(void const * argument)
{/* USER CODE BEGIN StarttaskSend */uint16_t buf =100;BaseType_t status ;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){status=xQueueSend(myQueueHandle,&buf,0);    //第一个数据是句柄  第二个是到队列中的数据 第三个是超时if(status == pdTRUE)printf("写入队列成功,写入值为%d\r\n",buf);elseprintf("写入队列失败\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(1);}/* USER CODE END StarttaskSend */
}/* USER CODE BEGIN Header_StarttaskReceive */
/**
* @brief Function implementing the taskReceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StarttaskReceive */
void StarttaskReceive(void const * argument)
{/* USER CODE BEGIN StarttaskReceive */uint16_t buf;BaseType_t status ;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){status=xQueueReceive(myQueueHandle,&buf,0);    //第一个数据是句柄  第二个是接收到的队列中的数据 第三个是超时if(status == pdTRUE)printf("读取队列成功,读取值为%d\r\n",buf);elseprintf("读取队列失败\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(1);}/* USER CODE END StarttaskReceive */
}

七、二值信号量

八、计数型信号量

void StarttaskGive(void const * argument)
{/* USER CODE BEGIN StarttaskGive *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){if(xSemaphoreGive(myCountingSemHandle)  == pdTRUE)printf("计数型信号量放入成功\r\n");elseprintf("计数型信号量放入失败\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(1);}/* USER CODE END StarttaskGive */
}/* USER CODE BEGIN Header_StarttaskTake */
/**
* @brief Function implementing the taskTake thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StarttaskTake */
void StarttaskTake(void const * argument)
{/* USER CODE BEGIN StarttaskTake *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){if(xSemaphoreTake(myCountingSemHandle,0)  == pdTRUE)    //portMAX_DELAY  死等printf("计数型信号量获取成功\r\n");elseprintf("计数型信号量获取失败\r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(1);}/* USER CODE END StarttaskTake */
}

九、互斥量

在多数情况下,互斥型信号量和二值型信号量非常的相似,但是从功能上二值型信号量用于同步,而互斥型信号量用于资源保护。

互斥型信号量和二值型信号量还有一个最大的区别,互斥型信号量可以有效地解决优先级反转的现象。

互斥量的特点
唯一访问:即互斥量保证在任意时间段,只有一个任务能够持有互斥量,即独占访问共享资源
优先级继承:当一个高优先级任务因为等待互斥量而被阻塞时,此时正持有互斥量的低优先级任务会临时继承高优先级任务的优先级,以防止优先级反转问题。
递归锁定:同一个任务可以多次获得同一个互斥量(递归互斥),每次成功获取后需要对应次数的释放操作。

任务优先级反转示例:
设有三个任务:

任务A:高优先级

任务B:中优先级

任务C:低优先级

步骤:

任务C获取资源:低优先级任务C获取一个共享资源并开始执行;
任务A被阻塞:高优先级任务需要访问该资源,但由于任务C持有资源,任务A被阻塞并等待资源释放;
任务B运行:此时,中等优先级任务B开始运行,因为它不需要访问任务C持有的资源,因此它不会被阻塞。由于任务B的优先级高于任务C,任务B持续运行,占用了CPU时间。
任务C无法运行:任务C被任务B抢占,无法继续执行,也就无法释放高优先级任务A所需的资源。
任务A优先级被反转:结果是高优先级任务A被中等优先级任务B间接阻塞,直到任务B完成执行并且任务C重新获得CPU时间并释放资源。

十、事件标志组

事件标志位:用一个位,来表示事件是否发生

事件标志组:一组事件标志位的集合,可以简单的理解时间标志组,就是一个整体。

事件标志租的特点:

        它的每一个位表示一个时间(高8位不算);

        每一个事件的含义,由用户自己决定,如:bit0表示按键是否按下,bit1表示是否接收到消息...(这些位的值为1:表示事件发生了;值为0,表示事件未发生)

        任意任务或中断都可以读写这些位;

        可以等待某一位成立,或者等待多位同时成立;

1. 创建事件标志组
EventGroupHandle_t xEventGroupCreate ( void );
参数:
返回值:
成功,返回对应事件标志组的句柄; 失败,返回 NULL
2. 设置事件标志位
EventBits_t xEventGroupSetBits ( EventGroupHandle_t xEventGroup , const EventBits_t uxBitsToSet );
参数:
xEventGroup :对应事件组句柄。 uxBitsToSet :指定要在事件组中设置的一个或多个位的按位
值。
返回值:
设置之后事件组中的事件标志位值。
3. 清除事件标志位
EventBits_t xEventGroupClearBits ( EventGroupHandle_t xEventGroup , const EventBits_t uxBitsToClear );
参数:
xEventGroup :对应事件组句柄。 uxBitsToClear :指定要在事件组中清除的一个或多个位的按位
值。
返回值:
清零之前事件组中事件标志位的值。
4. 等待事件标志位
EventBits_t xEventGroupWaitBits ( const EventGroupHandle_t xEventGroup , const EventBits_t uxBitsToWaitFor , const BaseType_t xClearOnExit , const BaseType_t xWaitForAllBits , TickType_t xTicksToWait );
参数: xEventGroup :对应的事件标志组句柄 uxBitsToWaitFor :指定事件组中要等待的一个或多个事件
位的按位值 xClearOnExit pdTRUE—— 清除对应事件位, pdFALSE—— 不清除 xWaitForAllBits
pdTRUE—— 所有等待事件位全为 1 (逻辑与), pdFALSE—— 等待的事件位有一个为 1 (逻辑或)
xTicksToWait :超时
返回值:
等待的事件标志位值:等待事件标志位成功,返回等待到的事件标志位 其他值:等待事件标志位
失败,返回事件组中的事件标志位

十一、任务通知

模拟二值信号量
void StartTasksend(void const * argument)
{/* USER CODE BEGIN StartTasksend *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){xTaskNotifyGive(TaskreceiveHandle);      //任务通知,不带通知值printf("任务通知,模拟二值信号量发送成功 ! \r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTasksend */
}/* USER CODE BEGIN Header_StartTaskreceive */
/**
* @brief Function implementing the Taskreceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskreceive */
void StartTaskreceive(void const * argument)
{/* USER CODE BEGIN StartTaskreceive */uint32_t rev = 0;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){rev = ulTaskNotifyTake(pdTRUE , portMAX_DELAY);      //任务通知,不带通知值if(rev !=0)     //非0:接收成功,返回任务通知的通知值printf("任务通知,模拟二值信号量接收成功 ! \r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(1);}/* USER CODE END StartTaskreceive */
}
模拟计数型信号量
void StartTasksend(void const * argument)
{/* USER CODE BEGIN StartTasksend *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){xTaskNotifyGive(TaskreceiveHandle);      //任务通知,不带通知值printf("任务通知,模拟计数型信号量发送成功 ! \r\n");}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTasksend */
}/* USER CODE BEGIN Header_StartTaskreceive */
/**
* @brief Function implementing the Taskreceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskreceive */
void StartTaskreceive(void const * argument)
{/* USER CODE BEGIN StartTaskreceive */uint32_t rev = 0;/* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){rev = ulTaskNotifyTake(pdFALSE , portMAX_DELAY);      //任务通知,不带通知值   特别注意这里pdTRUE是二值  pdFALSE 是计数型if(rev !=0)     //非0:接收成功,返回任务通知的通知值printf("任务通知,模拟计数型信号量接收成功 ! rev=  %d \r\n" , rev);}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(1);}/* USER CODE END StartTaskreceive */
}
模拟事件标志组
void StartTasksend(void const * argument)
{/* USER CODE BEGIN StartTasksend *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){printf("将bit0位置置1\r\n");xTaskNotify(TaskreceiveHandle,0x01,eSetBits);}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){printf("将bit1位置置1\r\n");xTaskNotify(TaskreceiveHandle,0x02,eSetBits);}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTasksend */
}/* USER CODE BEGIN Header_StartTaskreceive */
/**
* @brief Function implementing the Taskreceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskreceive */
void StartTaskreceive(void const * argument)
{/* USER CODE BEGIN StartTaskreceive */uint32_t notify_val = 0;uint32_t event_bit =0;/* Infinite loop */for(;;){xTaskNotifyWait(0,0xFFFFFFFF,&notify_val,portMAX_DELAY);if(notify_val & 0x01)     //判断bit0位是否为0event_bit |= 0x01;if(notify_val & 0x02)     //判断bit1位是否为1event_bit |=0x02;if(event_bit ==(0x03))     //只有两个位都置为1时才可以{printf("任务通知模拟事件标志组接收成功! \r\n");event_bit =0;}}/* USER CODE END StartTaskreceive */
}

模拟邮箱
void StartTasksend(void const * argument)
{/* USER CODE BEGIN StartTasksend *//* Infinite loop */for(;;){if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){printf("按键1按下\r\n");xTaskNotify(TaskreceiveHandle,1,eSetValueWithOverwrite);     //eSetValueWithOverwrite 带覆写  eSetValueWithoutOverwrite  不带覆写}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);}if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){osDelay(20);if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){printf("按键2按下\r\n");xTaskNotify(TaskreceiveHandle,2,eSetValueWithOverwrite);}while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);}osDelay(10);}/* USER CODE END StartTasksend */
}/* USER CODE BEGIN Header_StartTaskreceive */
/**
* @brief Function implementing the Taskreceive thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskreceive */
void StartTaskreceive(void const * argument)
{/* USER CODE BEGIN StartTaskreceive */uint32_t notify_val = 0;uint32_t event_bit =0;/* Infinite loop */for(;;){xTaskNotifyWait(0,0xFFFFFFFF,&notify_val,portMAX_DELAY);printf("接收到的通知值为: %d\r\n",notify_val);osDelay(1);}/* USER CODE END StartTaskreceive */
}

十二、延时函数

十三、软件定时器

void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask *///osTimerStart(myTimer01Handle,1000);          //周期xTimerChangePeriod(myTimer01Handle,pdMS_TO_TICKS(1000),0);        //官方的定时函数      osTimerStart(myTimer02Handle,2000);          //单次/* Infinite loop */for(;;){osDelay(1);}/* USER CODE END StartDefaultTask */
}/* Callback01 function */
void Callback01(void const * argument)
{/* USER CODE BEGIN Callback01 */printf("lvshiyan shuai\r\n");/* USER CODE END Callback01 */
}/* Callback02 function */
void Callback02(void const * argument)
{/* USER CODE BEGIN Callback02 */printf("lujingjing mei\r\n");/* USER CODE END Callback02 */
}

十四、中断

在stm32f1xx_it.c 文件中

void EXTI0_IRQHandler(void)
{/* USER CODE BEGIN EXTI0_IRQn 0 *//* USER CODE END EXTI0_IRQn 0 */HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);/* USER CODE BEGIN EXTI0_IRQn 1 */
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{uint32_t snd=1;xQueueSendFromISR(myQueue01Handle,&snd,NULL);       //在中断中往队列的尾部写入信息/* USER CODE END EXTI0_IRQn 1 */
}

在freertos.c中

void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN StartDefaultTask */uint32_t rev =0;/* Infinite loop */for(;;){if( xQueueReceive(myQueue01Handle,&rev,portMAX_DELAY) == pdTRUE)printf("rev = %d \r\n",rev);osDelay(1);}/* USER CODE END StartDefaultTask */
}

相关文章:

FreeRTOS的简单介绍

一、FreeRTOS介绍 FreeRTOS并不是实时操作系统,因为它是分时复用的 利用CubeMX快速移植 二、快速移植流程 1. 在 SYS 选项里,将 Debug 设为 Serial Wire ,并且将 Timebase Source 设为 TIM2 (其它定时器也行)。为何…...

DeepSeek模型安全部署与对抗防御全攻略

引言 随着DeepSeek模型在企业关键业务中的深入应用,模型安全已成为不可忽视的重要议题。本文将从实际攻防对抗经验出发,系统剖析DeepSeek模型面临的安全威胁,提供覆盖输入过滤、输出净化、权限控制等环节的立体防御方案,并分享红蓝对抗中的最佳实践,助力企业构建安全可靠…...

Docker容器使用手册

Docker是一种轻量级、可移植、自给自足的软件运行环境,用于打包和运行应用程序。它允许开发者将应用及其所有依赖打包成一个镜像(Image),然后基于这个镜像创建出容器(Container)来运行。与虚拟机相比不需要…...

深入解析C++引用:从别名机制到函数特性实践

1.C引用 1.1引用的概念和定义 引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同⼀块内存空间。比如四大名著中林冲,他有一个外号叫豹子头,类比到C里就…...

Fuse.js:打造极致模糊搜索体验

Fuse.js 完全学习指南:JavaScript模糊搜索库 🎯 什么是 Fuse.js? Fuse.js 是一个轻量、强大且无依赖的JavaScript模糊搜索库。它提供了简单而强大的模糊搜索功能,可以在任何 JavaScript 环境中使用,包括浏览器和 Nod…...

MyBatis分页插件(以PageHelper为例)与MySQL分页语法的关系

MyBatis分页插件(以PageHelper为例)与MySQL分页语法关系总结 MyBatis的分页插件(如PageHelper)底层实现依赖于数据库的分页语法。对于MySQL数据库来说,其分页逻辑最终会转化为LIMIT语句,下面展开详细说明&…...

CentOS 7.9 安装 宝塔面板

在 CentOS 7.9 上安装 宝塔面板(BT Panel) 的完整步骤如下: 1. 准备工作 系统要求: CentOS 7.x(推荐 7.9)内存 ≥ 1GB(建议 2GB)硬盘 ≥ 20GBroot 权限(需使用 root 用户…...

使用Redis作为缓存优化ElasticSearch读写性能

在现代数据密集型应用中,ElasticSearch凭借其强大的全文搜索能力成为许多系统的首选搜索引擎。然而,随着数据量和查询量的增长,ElasticSearch的读写性能可能会成为瓶颈。本文将详细介绍如何使用Redis作为缓存层来显著提升ElasticSearch的读写…...

项目交付后缺乏回顾和改进,如何持续优化

项目交付后缺乏回顾和改进可通过建立定期回顾机制、实施反馈闭环流程、开展持续学习和培训、运用数据驱动分析、培养持续改进文化来持续优化。 其中,实施反馈闭环流程尤其重要,它能够确保反馈信息得到有效传递、处理与追踪,形成良好的改进生态…...

从0开始学习R语言--Day15--非参数检验

非参数检验 如果在进行T检验去比较两组数据差异时,假如数据里存在异常值,会把数据之间的差异拉的很大,影响正常的判断。那么这个时候,我们可以尝试用非参数检验的方式来比较数据。 假设我们有A,B两筐苹果&#xff0c…...

Linux或者Windows下PHP版本查看方法总结

确定当前服务器或本地环境中 PHP 的版本,可以通过以下几种方法进行操作: 1. 通过命令行检查 这是最直接且常用的方法,适用于本地开发环境或有 SSH 访问权限的服务器。 方法一:php -v 命令 php -v输出示例:PHP 8.1.12 (cli) (built: Oct 12 2023 12:34:56) (NTS) Copyri…...

EC2 实例详解:AWS 的云服务器怎么玩?☁️

弹性计算、灵活计费、全球可用,AWS EC2 全攻略 在 AWS 生态中,有两个核心服务是非常关键的,一个是 S3(对象存储),另一个就是我们今天的主角 —— Amazon EC2(Elastic Compute Cloud&#xff09…...

第三发 DSP 点击控制系统

背景 ​ 在第三方 DSP 上投放广告,需要根据 DP Link 的点击次数进行控制。比如当 DP Link 达到 5000 后,后续的点击将不能带来收益,但是后续的广告却要付出成本。因此需要建立一个 DP Link 池,当 DP Link 到达限制后,…...

saveOrUpdate 有个缺点,不会把值赋值为null,解决办法

针对 MyBatis-Plus 的 saveOrUpdate 方法无法将字段更新为 null 的问题,这是因为 MyBatis-Plus 默认会忽略 null 值字段。以下是几种解决方案: 方案 1:使用 update(entity, wrapper) 手动指定更新条件 原理:通过 UpdateWrapper …...

Java面试:企业协同SaaS中的技术挑战与解决方案

Java面试:企业协同SaaS中的技术挑战与解决方案 面试场景 在一家知名互联网大厂,面试官老王正在对一位应聘企业协同SaaS开发职位的程序员谢飞机进行技术面试。 第一轮提问:基础技术 老王:谢飞机,你好。首先&#xf…...

【笔记】在 MSYS2 MINGW64 环境中降级 NumPy 2.2.6 到 2.2.4

📝 在 MSYS2 MINGW64 环境中降级 NumPy 到 2.2.4 ✅ 目标说明 在 MSYS2 的 MINGW64 工具链环境中,将 NumPy 从 2.2.6 成功降级到 2.2.4。 🧰 环境信息 项目内容操作系统Windows 11MSYS2 终端类型MINGW64(默认终端)Py…...

前端限流如何实现,如何防止服务器过载

前端限流是一种控制请求频率的技术,旨在防止过多的请求在同一时间段内发送到服务器,避免造成服务器过载或触发反爬虫机制。实现前端限流的方法有很多,下面介绍几种常见的策略和技术: 1. 时间窗口算法 时间窗口算法是最简单的限流…...

基于大模型的慢性硬脑膜下血肿预测与诊疗系统技术方案

目录 一、术前阶段二、并发症风险预测三、手术方案制定四、麻醉方案生成五、术后护理与康复六、系统集成方案七、实验验证与统计分析八、健康教育与随访一、术前阶段 1. 数据预处理与特征提取 伪代码: # 输入:患者多模态影像数据(CT/MRI)、病史、生理指标 def preproce…...

vue入门环境搭建及demo运行

提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 vue简介:第一步:安装node.jsnode简介第二步:安装vue.js第三步:安装vue-cli工具第四步 :安装webpack第五步…...

git checkout C1解释

git checkout C1 的意思是: 让 Git 切换到某个提交(commit)ID 为 C1 的状态。 🔍 更具体地说: C1 通常是一个 commit 的哈希值(可以是前几位,比如 6a3f9d2) git checkout C1 会让你…...

原始数据去哪找?分享15个免费官方网站

目录 一、找数据的免费官方网站 (一)国家级数据宝库:权威且全面 1.中国国家统计局 2.香港政府数据中心 3.OECD数据库 (二)企业情报中心:洞察商业本质 4.巨潮资讯 5.EDGAR数据库 6.天眼查/企查查&a…...

宝塔部署 Vue + NestJS 全栈项目

宝塔部署 Vue NestJS 全栈项目 前言一、Node.js版本管理器1、安装2、配置 二、NestJS项目管理(等同Node项目)1、Git安装2、拉取项目代码3、无法自动认证4、添加Node项目5、配置防火墙(两道) 三、Vue项目管理1、项目上传2、Nginx安…...

# [特殊字符] Unity UI 性能优化终极指南 — LayoutGroup篇

🎯 Unity UI 性能优化终极指南 — LayoutGroup篇 🧩 什么是 LayoutGroup? LayoutGroup 是一类用于 自动排列子节点 的UI组件。 代表组件: HorizontalLayoutGroupVerticalLayoutGroupGridLayoutGroup 可以搭配: Conte…...

Apache Iceberg 如何实现分布式 ACID 事务:深度解析大数据时代的可靠数据管理

引言:大数据时代的事务挑战 在大数据时代,传统数据库的 ACID 事务模型面临前所未有的挑战: 海量数据:PB 级数据难以使用传统事务机制管理多并发写入:数十甚至上百个作业同时写入同一数据集复杂分析:长时间运行的查询需要一致性视图混合负载:批处理和流处理同时访问相同…...

计算A图片所有颜色占B图片红色区域的百分比

import cv2 import numpy as npdef calculate_overlap_percentage(a_image_path, b_image_path):# 读取A组和B组图像a_image cv2.imread(a_image_path)b_image cv2.imread(b_image_path)# 将图像从BGR转为HSV色彩空间,便于颜色筛选a_hsv cv2.cvtColor(a_image, c…...

2024-2025-2-《移动机器人设计与实践》-复习资料-8……

2024-2025-2-《移动机器人设计与实践》-复习资料-1-7-CSDN博客 08 移动机器人基础编程 单选题(6题) 在ROS中,用于移动机器人速度控制的消息类型通常是? A. std_msgs/StringB. geometry_msgs/TwistC. sensor_msgs/ImageD. nav_ms…...

如何监测光伏系统中的电能质量问题?分布式光伏电能质量解决方案

根据光伏相关技术规范要求,通过10(6)kV~35kV电压等级并网的变流器类型分布式电源应在公共连接点装设满足GB/T 19862要求的A级电能质量监测装置。用于监测分布式光伏发出的电能的质量,指标包括谐波、电压偏差、电压不平衡度、电压波动和闪变等。 CET中电…...

电子电路:全面深入了解晶振的定义、作用及应用

本次了解重点: 1.压电效应的数学描述 2.生产工艺以及关键工序 3.电路设计部分如负阻原理和匹配电容计算 4.失效案例比如冷启动问题 5.新形态晶振技术引入5G和量子计算 6.温补晶振的补偿机制 7故障案例讲解-更换负载电池或增加预热电路 蓝牙音频断续-频偏导致 工控机死机-起振电…...

Day-15【选择与循环】选择结构-if语句

目录 一、if语句 (1)单分支选择结构 (2)双分支选择结构 (3)多分支选择结构 (4)if-else的嵌套使用 二、开关分支语句(switch) (1&#xff09…...

定时器时钟来源可以从输入捕获引脚输入

外部时钟模式 和 输入捕获。 核心结论: 外部时钟模式的输入引脚 ≠ 输入捕获功能的输入引脚(通常情况): 外部时钟模式有专用的输入引脚 (ETR) 和可选的替代输入通道(如TI1, TI2)。 输入捕获功能有自己的专…...