FreeRTOS任务间通信“IPC”

news2024/12/24 9:48:05

---------------信号量---------------

信号量的定义:

操作系统中一种解决问题的机制,可以实现    “共享资源的访问”

  • 信号:起通知作用
  • 量:还可以用来表示资源的数量
  • 当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
  • 当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)
  • 支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1

give的确切含义是,释放资源“当资源使用完毕后给出资源然后计数值 + 1 ,表示有资源被释放或者是该资源变为可用状态”。

task 确切含义是“占用资源”表示当前的资源处于被使用状态计数值 -1 ,表示该资源被使用,其余任务需要访问时无法获取到该资源,处于阻塞状态。

同步与互斥:

同步的概念:

举一个例子。在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完 成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同事A已经使用会议室了,经 理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒 我。这就是使用"同步"来实现"互斥"。

互斥的概念:

同一时间只能有一个人使用的资源,被称为临界资源。比如任务A、B都要使用串口来打印,串口就是临 界资源。如果A、B同时使用串口,那么打印出来的信息就是A、B混杂,无法分辨。所以使用串口时, 应该是这样:A用完,B再用;B用完,A再用。

PV操作就是实现进程同步于互斥同步的有效方法,,P表示通过的意思,V表示释放的意思

任务之间信息传递(通信或同步)最重要,最基础的一种方法,就是信号量

信号量被设置为1的过程,被称为SemaphoreGive,等待信号量设置为1的过程被称为SemaphoreTake(等待资源)


二值信号量:

本次案例基于GD32使用HAL库实现LED灯每经过一秒钟翻转一次

步骤:

1: 创建二值信号量的句柄

2:创建开始任务

3:由开始任务创建二值信号量和另外两个任务并删除开始任务

创建二值信号量的API函数

使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。

信号量的创建:

使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。 对于二进制信号量、计数型信号量,它们的创建函数不一样:

创建二值信号量:

1:创建句柄:

// 创建二值信号量句柄使用API函数SemaphoreHandle_t    句柄名称(自定义)
SemaphoreHandle_t  Binarysemhandle

2:创建开始任务:

  // 开始任务的任务句柄为
  TaskHandle_t Start_handle
  // 创建一个任务用于创建另外的两个任务
  xTaskCreate(Start_Task,"Start_Task",128,(void *)0,12,&Start_Handle); 

3:由开始任务创建二值信号量和另外两个任务并删除开始任务

void Start_Task(void * p){
	// 进入临界区
	taskENTER_CRITICAL();
	// 创建二值信号量
	Binary = xSemaphoreCreateBinary();
	// 创建两个任务
	xTaskCreate(Give_binarySem,"Give_binarySem",128,(void *)0,10,&BinaryGive_Handle); 
    xTaskCreate(Task_binarySem,"Task_binarySem",128,(void *)0,10,&BinaryTask_Handle); 
    // 删除开始任务 
 	vTaskDelete(Start_Handle);
    // 退出临界区
	taskEXIT_CRITICAL();
	
}

具体完整的代码如下所示

// 创建二值信号量句柄
SemaphoreHandle_t Binary;
// 宏定义创建任务句柄
TaskHandle_t Start_Handle;
TaskHandle_t BinaryGive_Handle;
TaskHandle_t BinaryTask_Handle;


void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

int fputc(int ch,FILE *f)
{
 while((USART1->SR & 0X40) == 0);

 USART1->DR = (uint8_t)ch;
	
 return ch;
}

// 获取二值信号量中的值
void Give_binarySem(void *p){
   
   while(1){
      // 获取二值信号量资源,也就是 +1 操作,参数是信号量的句柄
	  xSemaphoreGive(Binary);
	  // 然后每次间隔1秒钟释放一次
	  vTaskDelay(1000);   
   }

}
void Task_binarySem(void *p){
	  while(1){
			xSemaphoreTake(Binary,portMAX_DELAY);
		    // 调用hal函数实现led每次间隔1秒钟翻转一次
		    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);
	        vTaskDelay(500);
	  }
}

void Start_Task(void * p){
	// 进入临界区
	taskENTER_CRITICAL();
	// 创建二值信号量
	Binary = xSemaphoreCreateBinary();
	// 创建两个任务
	xTaskCreate(Give_binarySem,"Give_binarySem",128,(void *)0,10,&BinaryGive_Handle); 
    xTaskCreate(Task_binarySem,"Task_binarySem",128,(void *)0,10,&BinaryTask_Handle); 
    // 删除开始任务 
 	vTaskDelete(Start_Handle);
    // 退出临界区
	taskEXIT_CRITICAL();
	
}

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  // 创建一个任务用于创建另外的两个任务
  xTaskCreate(Start_Task,"Start_Task",128,(void *)0,12,&Start_Handle); 
  // 开启任务调度
  vTaskStartScheduler();
  while (1)
  {
  }
}

FreeRTOS在使用信号量时可能出现的BUG:

解决方式可以是添加临界区防止程序在运行中被ISR打断,或者是等待资源时(也就是使用资源时先判断资源是否被释放,释放完毕之后再使用资源)。

生产消费问题:

二值信号量模拟生产快消费慢的情况

生产快也就是GIVE释放资源的速度比较快,Task获取资源的速度比较慢,在这样的一种情况下会出现次数丢失的问题,(也就是会损失精度),代码如下所示:释放资源的代码演示100毫秒,等待资源的代码延时500毫秒,初步的实验现象是丢失精度。

void Task_binarySem(void *p){
	  while(1){
			xSemaphoreTake(Binary,portMAX_DELAY);
		    // 调用hal函数实现led每次间隔1秒钟翻转一次
		    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);
		    // 翻转之后延时一段时间,得到的是信号量生产快,然后消费得慢
	        vTaskDelay(500);
	  }
}

// 创建一个任务函数可以比较快速的释放信号量:实现100毫秒释放一次
void Give_Fast_Free(void *p){
	    // 这是一种c99语法
	    uint8_t count = 0;
	    while(1){
		  // 一直释放信号量无法直接看出结果,所以使用for循环每次间隔1毫秒释放一次
		  if(count++ < 30){
		    xSemaphoreGive(Binary);
		  }
		  vTaskDelay(100);
		}
}

总结:

模拟生产快,翻转慢的情况,信号量释放快获取慢

def:如果信号量释放的速度很快的话,我们得到信号量之后LED的翻转速度比较慢,导致出现丢失次数的情况

【CAT1也是一种技术】

生产快,消费慢会产生的问题会导致数据漏发,最终可能导致数据的丢失率很高也就是数据原本计划是上传10次但是实际却只上传了5次的情况【在定时任务和采集通信之间没有缓存机制,不能很好的解决生产和消费能力不匹配的问题】


计数型信号量:

def:有一个较好的缓冲机制确保数据包不会丢失太多

创建计数型信号量的API是xSemaphoreCreateCounting(255,0);,第一个参数是计数的最大值,第二个参数是起始计数的位置。

	// 创建计数型信号量,第一个参数是最大值,第二个参数是起始值
	CountSemaphore = xSemaphoreCreateCounting(255,0);

1:创建计数型信号量的任务句柄,任务句柄是第一个,后面三个是其余任务的任务句柄

// 创建计数信号量句柄
SemaphoreHandle_t CountSemaphore;
// 宏定义创建任务句柄
TaskHandle_t Start_Handle;
TaskHandle_t BinaryGive_Handle;
TaskHandle_t BinaryTask_Handle;

2:创建开始任务函数,开始任务函数的优先级要大于其余创建函数的优先级,不然后出现其余的任务抢占开始任务,导致任务无法创建成功的情况

int main(void)
{

  
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  // 创建一个任务用于创建另外的两个任务
  xTaskCreate(Start_Task,"Start_Task",128,(void *)0,12,&Start_Handle); 
  // 开启任务调度
  vTaskStartScheduler();
  while (1)
  {
  }

}

3:开始任务创建函数

void Start_Task(void * p){
	// 进入临界区
	taskENTER_CRITICAL();
	// 创建计数型信号量,第一个参数是最大值,第二个参数是起始值
	CountSemaphore = xSemaphoreCreateCounting(255,0);
	// 创建两个任务
	xTaskCreate(Give_Fast_Free,"Give_Fast_Free",128,(void *)0,10,&BinaryGive_Handle); 
    xTaskCreate(Task_binarySem,"Task_binarySem",128,(void *)0,10,&BinaryTask_Handle); 
    // 删除开始任务 
 	vTaskDelete(Start_Handle);
    // 退出临界区
	taskEXIT_CRITICAL();
	
}

完整的计数型信号量创建代码

#include "main.h"
#include "cmsis_os.h"

#include "stdio.h"

UART_HandleTypeDef huart1;

// 创建计数信号量句柄
SemaphoreHandle_t CountSemaphore;
// 宏定义创建任务句柄
TaskHandle_t Start_Handle;
TaskHandle_t BinaryGive_Handle;
TaskHandle_t BinaryTask_Handle;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

int fputc(int ch,FILE *f)
{
 while((USART1->SR & 0X40) == 0);

 USART1->DR = (uint8_t)ch;
	
 return ch;
}


void Task_binarySem(void *p){
	  while(1){
			xSemaphoreTake(CountSemaphore,portMAX_DELAY);
		    // 调用hal函数实现led每次间隔1秒钟翻转一次
		    HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);
		    // 翻转之后延时一段时间,得到的是信号量生产快,然后消费得慢
	        vTaskDelay(500);
	  }
}

// 创建一个任务函数可以比较快速的释放信号量:实现100毫秒释放一次
void Give_Fast_Free(void *p){
	    // 这是一种c99语法
	    uint8_t count = 0;
	    while(1){
		  // 一直释放信号量无法直接看出结果,所以使用for循环每次间隔1毫秒释放一次
		  if(count++ < 30){
		    xSemaphoreGive(CountSemaphore);
		  }
		  vTaskDelay(100);
		}
}


void Start_Task(void * p){
	// 进入临界区
	taskENTER_CRITICAL();
	// 创建计数型信号量,第一个参数是最大值,第二个参数是起始值
	CountSemaphore = xSemaphoreCreateCounting(255,0);
	// 创建两个任务
	xTaskCreate(Give_Fast_Free,"Give_Fast_Free",128,(void *)0,10,&BinaryGive_Handle); 
    xTaskCreate(Task_binarySem,"Task_binarySem",128,(void *)0,10,&BinaryTask_Handle); 
    // 删除开始任务 
 	vTaskDelete(Start_Handle);
    // 退出临界区
	taskEXIT_CRITICAL();
	
}



int main(void)
{

  
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  // 创建一个任务用于创建另外的两个任务
  xTaskCreate(Start_Task,"Start_Task",128,(void *)0,12,&Start_Handle); 
  // 开启任务调度
  vTaskStartScheduler();
  while (1)
  {
  }

}

通过计数型信号量解决二值信号量可能带来的数据包丢失的问题

信号量的删除:

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:


-----------------互斥锁-------------

锁的概念:

例如:文件就是典型的临界资源,windows不可能因此暂停整个任务调度,windows操作系统的做法是在第一次打开文件的时候,给文件上一把锁,如果后面还有程序想要操作文件时,会因为锁的存在,提示打开失败。

锁的机制:

例如:资源我正在使用,并且已经上锁,什么时候解锁,你什么时候再使用,RTOS中互斥锁使用Mutex。

在RTOS中引入互斥锁的概念解决临界资源的争抢问题

注:

1:可以使用临界区,防止程序被打断的方式解决临界资源争抢的问题,但是临界区会拖慢整个操作系统的运行

2:建议使用互斥锁的方式解决临界资源争抢问题,可以替代临界区并且防止操作系统卡死

临界资源与临界区的概念

临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。

每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入。不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。多个进程涉及到同一个临界资源的的临界区称为相关临界区。使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。

创建互斥锁:

1:创建互斥锁句柄

// 定义互斥锁句柄
SemaphoreHandle_t MutexSemaphore;

2: 创建开始任务

具体函数代码如下所示:

#include "task_init.h"
#include "cmsis_os.h"
extern void print1(void *p);
extern void print2(void *p);

extern SemaphoreHandle_t MutexSemaphore;



/* 创建任务的函数 */
void task_init( void )
{
 BaseType_t xReturned;
 TaskHandle_t xHandle = NULL;
	
 // 创建互斥锁
 MutexSemaphore = xSemaphoreCreateMutex();

 /* 创建任务,存储句柄 */
 xReturned = xTaskCreate(
                    print1,       /* 执行任务的函数 */
                    "print1Task",          /* 任务名称 */
                    STACK_SIZE,      /* 堆栈大小,单位为字 */
                    ( void * ) 1,    /* 传递给任务的参数 */
                    tskIDLE_PRIORITY,/* 创建任务的优先级 */
                    &xHandle );      /* 用于传递创建的任务句柄 */
										
 xReturned = xTaskCreate(
                    print2,       /* 执行任务的函数 */
                    "print2Task",          /* 任务名称 */
                    STACK_SIZE,      /* 堆栈大小,单位为字 */
                    ( void * ) 1,    /* 传递给任务的参数 */
                    tskIDLE_PRIORITY,/* 创建任务的优先级 */
                    &xHandle );      /* 用于传递创建的任务句柄 */
 
 vTaskStartScheduler();										 
}

主函数代码如下所示:

#include "main.h"
#include "cmsis_os.h"

#include "stdio.h"
#include "task_init.h"

UART_HandleTypeDef huart1;

// 定义互斥锁句柄
SemaphoreHandle_t MutexSemaphore;



void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

int fputc(int ch,FILE *f)
{
 while((USART1->SR & 0X40) == 0);

 USART1->DR = (uint8_t)ch;
	
 return ch;
}

void print1(void *p)
{
 while(1)
 {
	 // 等待互斥锁
	 xSemaphoreTake(MutexSemaphore,portMAX_DELAY);
	 // 通过加入临界区解决临界资源争抢
	printf("HELLO RTOS! HELLO PNXUETANG! HELLO ZNMCU!\r\n");
	 // 释放互斥锁
	xSemaphoreGive(MutexSemaphore);
 }
}

void print2(void *p)
{
 while(1)
 {
	 xSemaphoreTake(MutexSemaphore,portMAX_DELAY);
	 printf("hello rtos! hello pnxuetang! hello znmcu!\r\n");
	 xSemaphoreGive(MutexSemaphore);
 }
}



int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	
  task_init();
  	
	
	
  while (1)
  {
   
  }

}


/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_6, GPIO_PIN_SET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);

  /*Configure GPIO pin : PE6 */
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

  /*Configure GPIO pin : PA8 */
  GPIO_InitStruct.Pin = GPIO_PIN_8;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END 5 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

死锁的概念:

“互斥锁使用不当造成的问题”

例子:

1:面试要求求职的学生有工作经验

2:学生没有工作经验表示如果不入职就没有工作经验

典型的死锁关系图如下所示:

以下有三个进程:进程1占用资源A,并且向资源B申请资源,进程3占用资源B的同时申请资源C,进程2占用资源C并且申请A资源,“问题:当进程2申请资源A的时候进程1正在使用还没有释放这个时候进程2是无法使用A资源的这个时候就出现了死锁,进程之间无法获得有效的资源”

互斥锁的递归和嵌套使用导致出现死锁问题

解决死锁问题:

此处使用递归互斥锁的方式来解决死锁问题

典型的死锁代码案例演示:

使用递归互斥量解决死锁

递归互斥量的API函数:

 xSemaphoreCreateRecursiveMutex();	

1:创建任务句柄

2:创建开始任务函数

3:创建开始任务

4:创建函数模拟资源的递归互斥访问“死锁问题的解决”

具体主函数的代码如下所示

#include "main.h"
#include "cmsis_os.h"

#include "stdio.h"
#include "math.h"
#include "shell.h"

UART_HandleTypeDef huart1;

SemaphoreHandle_t MutexSemaphore;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

int fputc(int ch,FILE *f)
{
 while((USART1->SR & 0X40) == 0);

 USART1->DR = (uint8_t)ch;
	
 return ch;
}

void myfun(void)
{
 //........
 xSemaphoreTakeRecursive(MutexSemaphore,portMAX_DELAY);	
 printf("data process complete!\r\n");
 xSemaphoreGiveRecursive(MutexSemaphore);
 //.......
 xSemaphoreTakeRecursive(MutexSemaphore,portMAX_DELAY);
 printf("result is .....\r\n");
 xSemaphoreGiveRecursive(MutexSemaphore);
}

void print1(void *p)
{
 while(1)
 {
	xSemaphoreTakeRecursive(MutexSemaphore,portMAX_DELAY);
	//printf("HELLO RTOS! HELLO PNXUETANG! HELLO ZNMCU!\r\n");
	myfun(); 
	 
	xSemaphoreGiveRecursive(MutexSemaphore);
 }
}

void print2(void *p)
{
 while(1)
 {
	xSemaphoreTakeRecursive(MutexSemaphore,portMAX_DELAY);
	//printf("hello rtos! hello pnxuetang! hello znmcu!\r\n");
	myfun();
	 
	xSemaphoreGiveRecursive(MutexSemaphore);
 }
}

void task_create_entry(void *p)
{	
 TaskHandle_t tmp_handle;	
 TaskHandle_t xHandle;
 TaskHandle_t xHandle2;
	
 vTaskDelay(5000);
	
 //=====================================
 printf("start to create recursive_mutex\r\n");	
 MutexSemaphore = xSemaphoreCreateRecursiveMutex();	
	
 printf("start to create tasks\r\n");	
	
 printf("create 1st task\r\n");
	
 xTaskCreate(print1,"print1_task",128,(void *)0,10,&xHandle);
	
 printf("create 2nd task\r\n");
	
 xTaskCreate(print1,"print2_task",128,(void *)0,10,&xHandle2);
	
 //=====================================	

 tmp_handle = xTaskGetHandle("task_create_task");
	
 vTaskDelete(tmp_handle);
}




int main(void)
{
  TaskHandle_t xHandle;
	
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	
	shell_init();
	
	xTaskCreate(task_create_entry,"task_create_task",128,(void *)0,12,&xHandle);
	
	vTaskStartScheduler();
	
  while (1)
  {
  }

}

----------优先级翻转问题---------

注:

1:在FreeRTOS中使用中文打印串口数据可能会出现乱码问题,打印串口的时候尽量使用英文

优先级反转:

低优先级的任务干死高优先级的任务,在任务调度的过程中,高优先级的任务总是会优先得到调度

而优先级反转,导致高优先级处于阻塞状态没法得到运行,反而低优先级的任务先于高优先级的任务得到运行。

模拟优先级反转程序如下所示“这个时候高优先级的任务无法得到调度”,使用串口打印输出程序运行的结果。

优先级反转问题:

#include "main.h"
#include "cmsis_os.h"

#include "stdio.h"
#include "math.h"
#include "shell.h"

UART_HandleTypeDef huart1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

// 创建一个二进制信号量
SemaphoreHandle_t BinarySum;

int fputc(int ch,FILE *f)
{
 while((USART1->SR & 0X40) == 0);

 USART1->DR = (uint8_t)ch;
	
 return ch;
}

// 创建一个最低优先级的任务
void Low_Task_Entry(void *p){
  
	 
	 // 低优先级的任务不断的释放信号量
	 while(1){
	    printf("give low task \r\n");
		xSemaphoreGive(BinarySum);
	 }

}
// 配置中等优先级的任务
void Middle_Task_Entry(void *p){
     
	 // 中等优先级的任务独占CPU资源,CPU的资源没法得到释放,导致高优先级的任务没法运行
	 while(1){
	     printf("middle task is run\r\n");
	 }
}

// 创建一个最高优先级的任务
void High_Task_Entry(void *p){
    
	while(1){
		
		// 等待信号量
	    xSemaphoreTake(BinarySum,portMAX_DELAY);
		printf("high task prior is run\r\n");
	}
}

// 创建任务函数
void Task_Create_Init(void *p){
   
	TaskHandle_t tmp_handle;
	TaskHandle_t xHandle1;
	TaskHandle_t xHandle2;
	TaskHandle_t xHandle3;
	vTaskDelay(5000);
	
	// 创建二值信号量
	printf("Create xSemaphoreCreateBinary");
	BinarySum = xSemaphoreCreateBinary();
	
	
	printf("Create task1");
	xTaskCreate(Low_Task_Entry,"Low_Task_Entry",128,(void*)0,7,&xHandle1);
	printf("Create task2");
	xTaskCreate(Middle_Task_Entry,"Middle_Task_Entry",128,(void*)0,9,&xHandle2);
	printf("Create task3");
	xTaskCreate(High_Task_Entry,"High_Task_Entry",128,(void*)0,10,&xHandle3);
	// 获取开始任务的任务句柄
	tmp_handle = xTaskGetHandle("Task_Create_Init");
	
	vTaskDelete(tmp_handle);
	
	while(1){
	
	}
	
}
int main(void)
{
  TaskHandle_t Handle;
	
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	
	shell_init();
	
   xTaskCreate(Task_Create_Init,"Task_Create_Init",128,(void*)0,12,&Handle);	
   // 开启任务调度
   vTaskStartScheduler();
	
  while (1)
  {
  }

}

优先级反转问题解决:

使用FreeRTOS中的API函数vTaskPrioritySet();内部包含两个参数,第一个参数是任务句柄,第二个参数是需要提高或者降低的优先级等级

出现了一个情况,更改任务优先级之后出现仅仅只有低优先级任务在运行,初步判断是由于内存的空间不足导致的,在FreeRTOSCONFIG中提高任务栈的大小“注:分配的内存空间不足会影响任务的创建”。

串口打印输出演示结果

结果显示中等优先级的任务一直占用CPU的资源,导致低优先级和高优先级的任务一直无法得到运行

 main函数代码展示

 

#include "main.h"
#include "cmsis_os.h"

#include "stdio.h"
#include "math.h"
#include "shell.h"

UART_HandleTypeDef huart1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

TaskHandle_t xHandle1;
TaskHandle_t xHandle2;
TaskHandle_t xHandle3;

// 创建一个二进制信号量
SemaphoreHandle_t BinarySum;

int fputc(int ch,FILE *f)
{
 while((USART1->SR & 0X40) == 0);

 USART1->DR = (uint8_t)ch;
	
 return ch;
}

// 创建一个最低优先级的任务
void Low_Task_Entry(void *p){
  
	 // 低优先级的任务不断的释放信号量
	 while(1){
		taskENTER_CRITICAL();
	    printf("Low Task give\r\n");
		taskEXIT_CRITICAL();
		xSemaphoreGive(BinarySum);
	 }

}
// 配置中等优先级的任务
void Middle_Task_Entry(void *p){
     
	 // 中等优先级的任务独占CPU资源,CPU的资源没法得到释放,导致高优先级的任务没法运行
	 while(1){
		 taskENTER_CRITICAL();
	     printf("Mid task Create\r\n");
		 taskEXIT_CRITICAL();
	 }
}

// 创建一个最高优先级的任务
void High_Task_Entry(void *p){
    
	while(1){
		// 打印输出任务被创建
		taskENTER_CRITICAL();
		printf("Task is Create\r\n");
		taskEXIT_CRITICAL();
		// 临时提高低优先级任务的优先级解决优先级反转问题
		vTaskPrioritySet(xHandle1,10);
		// 等待信号量
	    xSemaphoreTake(BinarySum,portMAX_DELAY);
		taskENTER_CRITICAL();
		printf("High Task Create\r\n");
		taskEXIT_CRITICAL();
		// 把低优先级任务的优先级降低回原来的优先级
		vTaskPrioritySet(xHandle1,7);
	}
}

// 创建任务函数
void Task_Create_Init(void *p){
   
	TaskHandle_t tmp_handle;

	vTaskDelay(5000);
	
	// 创建二值信号量
	printf("Create xSemaphoreCreateBinary");
	BinarySum = xSemaphoreCreateBinary();
	
	
	printf("Create task1\r\n");
	xTaskCreate(Low_Task_Entry,"Low_Task_Entry",128,(void*)0,7,&xHandle1);
	printf("Create task2\r\n");
	xTaskCreate(Middle_Task_Entry,"Middle_Task_Entry",128,(void*)0,9,&xHandle2);
	printf("Create task3\r\n");
	xTaskCreate(High_Task_Entry,"High_Task_Entry",128,(void*)0,10,&xHandle3);
	// 获取开始任务的任务句柄
	tmp_handle = xTaskGetHandle("Task_Create_Init");
	
	vTaskDelete(tmp_handle);
	
	while(1){
	
	}
	
}
int main(void)
{
  TaskHandle_t Handle;
	
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	
	shell_init();
	
   xTaskCreate(Task_Create_Init,"Task_Create_Init",128,(void*)0,12,&Handle);	
   // 开启任务调度
   vTaskStartScheduler();
	
  while (1)
  {
  }

}

上面解决优先级反转的问题比较老土,目前主流的解决优先级反转问题的方式是互斥锁解决优先级反转,互斥锁优先级继承解决反转问题。

互斥锁解决优先级反转问题:

xSemaphoreCreateMutex // 创建互斥锁的API函数

#include "main.h"
#include "cmsis_os.h"

#include "stdio.h"
#include "math.h"
#include "shell.h"

UART_HandleTypeDef huart1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

TaskHandle_t xHandle1;
TaskHandle_t xHandle2;
TaskHandle_t xHandle3;

// 创建一个二进制信号量
SemaphoreHandle_t Mutex;

int fputc(int ch,FILE *f)
{
 while((USART1->SR & 0X40) == 0);

 USART1->DR = (uint8_t)ch;
	
 return ch;
}

// 创建一个最低优先级的任务
void Low_Task_Entry(void *p){
  
	 // 低优先级的任务不断的释放信号量
	 while(1){
		taskENTER_CRITICAL();
	    printf("Low Task give\r\n");
		taskEXIT_CRITICAL();
		 
		xSemaphoreGive(Mutex);
	 }

}
// 配置中等优先级的任务
void Middle_Task_Entry(void *p){
     
	 // 中等优先级的任务独占CPU资源,CPU的资源没法得到释放,导致高优先级的任务没法运行
	 while(1){
		 taskENTER_CRITICAL();
	     printf("Mid task Create\r\n");
		 taskEXIT_CRITICAL();
	 }
}

// 创建一个最高优先级的任务
void High_Task_Entry(void *p){
    
	while(1){
		// 等待信号量
	    xSemaphoreTake(Mutex,portMAX_DELAY);
		taskENTER_CRITICAL();
		printf("High Task Create\r\n");
		taskEXIT_CRITICAL();
	}
}

// 创建任务函数
void Task_Create_Init(void *p){
   
	TaskHandle_t tmp_handle;

	vTaskDelay(5000);
	
	// 创建二值信号量
	printf("Create xSemaphoreCreateBinary");
	// 修改为互斥锁
	Mutex = xSemaphoreCreateMutex();
	
	
	printf("Create task1\r\n");
	xTaskCreate(Low_Task_Entry,"Low_Task_Entry",128,(void*)0,7,&xHandle1);
	printf("Create task2\r\n");
	xTaskCreate(Middle_Task_Entry,"Middle_Task_Entry",128,(void*)0,9,&xHandle2);
	printf("Create task3\r\n");
	xTaskCreate(High_Task_Entry,"High_Task_Entry",128,(void*)0,10,&xHandle3);
	// 获取开始任务的任务句柄
	tmp_handle = xTaskGetHandle("Task_Create_Init");
	
	vTaskDelete(tmp_handle);
	
	while(1){
	
	}
	
}
int main(void)
{
  TaskHandle_t Handle;
	
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	
	shell_init();
	
   xTaskCreate(Task_Create_Init,"Task_Create_Init",128,(void*)0,12,&Handle);	
   // 开启任务调度
   vTaskStartScheduler();
	
  while (1)
  {
  }

}


































/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOE, GPIO_PIN_6, GPIO_PIN_SET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);

  /*Configure GPIO pin : PE6 */
  GPIO_InitStruct.Pin = GPIO_PIN_6;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

  /*Configure GPIO pin : PA8 */
  GPIO_InitStruct.Pin = GPIO_PIN_8;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END 5 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

-------------任务主动切换---------

在RTOS中优先级相同的任务使用“时间片轮转”的方式进行任务调度,每个任务之间轮流的执行,不停的进行任务间上下文的切换。

上下文切换的概念:

本质上就是压栈和出栈,就是Push操作和Pop操作,如有两个任务,一个是任务A,一个是任务B

当任务A向任务B进行切换时,任务A的函数(操作)会被pop出栈,放进一个寄存器中进行存储

同时当前执行的位置会被记录下来,任务A此时处于阻塞状态,任务B在寄存器中的函数操作或者是地址会被PUSH进入栈中,任务B等到运行,时间片轮转,上下文的切换往复循环。

时间片轮状slice ---------> 一个切片就是一个Tick,Tick是系统提供的心跳节拍,来自于滴答定时器

注:频繁的切换上下文不利于操作系统的稳定运行

主动任务切换概念:

FreeRTOS中使用taskYeild()函数实现任务的主动切换,【taskYeild】主动任务切换,在多个任务优先级相同的情况下,为了更好的使用和调度CPU资源,通过主动任务切换的方式提高任务的运行效率,调用taskYeild这个API来实现主动任务切换。

例如:

系统中的空闲任务IDLE,当系统重有任务运行时会主动的让出CPU的资源

操作系统的实时性:一般情况下任务的切换是到最终切换完成时有一定的时间间隔的“相当于有了一个缓冲”

注:“两个任务优先级相同的情况下,相互之间不会去抢占做上下文的切换,任务的切换是在一个TICK中断中完成的”

主动山下文切换:

 1: 在两个任务优先级有高有低的时候,B任务是8,A任务是7,在实时抢占式任务的内核下,仍然会为我们快速的切换上下文**(任务之间的切换是非常快的)**

 2:在任务优先级相同的情况下,可以调用taskYIELD()主动上下文切换主动的放弃CPU的资源

主动上下文切换代码main.c

 

#include "main.h"
#include "cmsis_os.h"

#include "stdio.h"
#include "math.h"
#include "shell.h"

UART_HandleTypeDef huart1;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);




// 创建二进制信号量
SemaphoreHandle_t sem;




int fputc(int ch,FILE *f)
{
 while((USART1->SR & 0X40) == 0);

 USART1->DR = (uint8_t)ch;
	
 return ch;
}

void MyDelay(uint32_t num){
   while(num--){
   
   }
}
void taskSem_entry(void *p){
    while(1){
	    // 释放信号量
		xSemaphoreTake(sem,portMAX_DELAY);
		// 翻转第一个LED灯
		HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);
	}
}

// 等待信号量的释放
void giveSem_entry(void *p){
     while(1){
	    xSemaphoreGive(sem);
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_6);
		/*
		 两个任务主动的切换上下文,
		 可以调用taskYIELD();主动让出CPU资源
		 完成任务的上下文切换
		 */ 
		 taskYIELD();
		 
		MyDelay(1000000);
	 }
}
 
void task_create_entry(void *p){
    // 创建任务句柄
	TaskHandle_t tmp_handle;
	TaskHandle_t xHandle;
	TaskHandle_t xHandle2;
	
	vTaskDelay(5000);
	printf("start to create task\r\n");
	sem = xSemaphoreCreateBinary();
	printf("start to create task\r\n");
	
	printf("create 1st task\r\n");
	
	xTaskCreate(taskSem_entry,
	            "taskSem_entry",
				128,
				(void *)0,
				10,
				&xHandle
	            );
	// 创建两个任务优先级相同的情况下,调度器就不会主动的为我们切换上下文			
	xTaskCreate(giveSem_entry,
	            "giveSem_entry",
				128,
				(void *)0,
				10,
				&xHandle2
	            );	
				
	// 通过中间变量获取开始任务的句柄
	tmp_handle = xTaskGetHandle("task_create_entry");
	vTaskDelete(tmp_handle);
}






int main(void)
{
  TaskHandle_t Handle;
	
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
	
	shell_init();
	
   xTaskCreate(task_create_entry,"task_create_entry",128,(void*)0,12,&Handle);	
   // 开启任务调度
   vTaskStartScheduler();
	
  while (1)
  {
  }

}

.........

注:以上内容均基于本人对相关知识的理解进行撰写,如有错误欢迎评论区纠正

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

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

相关文章

pycharm找不到conda可执行文件解决办法

解决办法 1、第一种 按照以下步骤&#xff0c;找到condabin文件下面&#xff0c;conda.bat 文件&#xff0c;把路径给复制下来&#xff0c;粘贴到 Conda 可执行文件&#xff0c;即可。 然后再点击加载环境&#xff0c;我这里是已经汉化了 pycharm &#xff0c;如何汉化&…

提取 Chrome、Firefox 中储存的用户密码用于凭据发现

操作环境 Chrome 浏览器 Version 125.0.6422.112 (Official Build) (64-bit)Firefox 浏览器 Version 126.0 (64 位) Chrome 浏览器储存密钥原理 新的 Chrome 浏览器储存密码的方案是使用 Chrome 生成的 AES 密钥对用户密码进行加密之后储存在 Sqlite 数据库文件中&#xff0c;A…

c++入门的基础知识

c入门 C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想&#xff0c;并增加了许多有用的库&#xff0c;以及编程范式等。熟悉C语言之后&#xff0c;对C学习有一定的帮助&#xff0c;本章节主要目标&#xff1a; 补充C语言语法的不足&#xff0c;以及C是如何对C语言设计…

C++ 数据结构算法 学习笔记(32) -五大排序算法

C 数据结构算法 学习笔记(32) -五大排序算法 选择算法 如下若有多个女生的身高需要做排序: 常规思维: 第一步先找出所有候选美女中身高最高的&#xff0c;与最后一个数交换 第二步再找出除最后一位美女外其它美女中的最高者&#xff0c;与倒数第二个美女交换位置 再找出除最…

ASE60P06-ASEMI场效应MOS管ASE60P06

编辑&#xff1a;ll ASE60P06-ASEMI场效应MOS管ASE60P06 型号&#xff1a;ASE60P06 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220 批号&#xff1a;2024 沟道&#xff1a;N沟道 导通内阻RDS&#xff08;ON&#xff09;Max&#xff1a;19mΩ 启动电压&#xff1a;2V-4…

【启明智显技术分享】SOM2D02-2GW核心板适配ALSA(适用Sigmastar ssd201/202D)

提示&#xff1a;作为Espressif&#xff08;乐鑫科技&#xff09;大中华区合作伙伴及sigmastar&#xff08;厦门星宸&#xff09;VAD合作伙伴&#xff0c;我们不仅用心整理了你在开发过程中可能会遇到的问题以及快速上手的简明教程供开发小伙伴参考。同时也用心整理了乐鑫及星宸…

【代码随想录】二分查找算法总结篇

目录 前言二分查找例题一例题二例题三例题四 前言 本篇文章记录了代码随想录二分查找算法的总结笔记&#xff0c;下面我们一起来学习吧&#xff01;&#xff01; 二分查找 关于二分查找算法&#xff0c;我在之前的这篇博客里面做了非常多的分析&#xff0c;但是后面做题做着…

Leetcode | 5-21| 每日一题

2769. 找出最大的可达成数字 考点: 暴力 数学式子计算 思维 题解 通过式子推导: 第一想法是二分确定区间在区间内进行查找是否符合条件的, 本题最关键的便是 条件确定 , 第二种方法: 一般是通过数学公式推导的,这种题目我称为数学式编程题 代码 条件判断式 class Solution { …

长文处理更高效:一键章节拆分,批量操作轻松搞定,飞速提升工作效率!

在当今信息爆炸的时代&#xff0c;我们时常需要处理大量的文本内容。无论是阅读长篇小说、整理专业资料还是编辑大型文档&#xff0c;TXT文本文件的普遍性不言而喻。然而&#xff0c;当TXT文本内容过于庞大时&#xff0c;阅读、编辑和管理都变得异常繁琐。此时&#xff0c;一款…

齐护K210系列教程(三十一)_视觉小车

视觉小车 齐护编程小车端程序动作说明联系我们 在经常做小车任务项目时会用的K210的视觉与巡线或其它动作结合&#xff0c;这就关系到要将K210的识别结果传送给小车的主控制器&#xff0c;K210为辅助传感器&#xff08;视觉采集&#xff09;。 这节课我们用K210识别图像&#x…

多微信如何高效管理?一台电脑就能搞定!

对于有多个微信号的人来说&#xff0c;管理这些微信无疑是一道难题。 今天&#xff0c;就给大家分享一个能够让你高效管理多个微信号的神器——个微管理系统&#xff0c;下面&#xff0c;就一起来看看它都有哪些功能吧&#xff01; 1、多号同时登录在线 系统支持多个微信号同…

AI助力农田作物智能化激光除草,基于轻量级YOLOv8n开发构建农田作物场景下常见20种杂草检测识别分析系统

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术在各个领域的应用愈发广泛&#xff0c;其中农业领域也不例外。近年来&#xff0c;AI助力农田作物场景下智能激光除草的技术成为了农业领域的一大亮点&#xff0c;它代表着农业智能化、自动化的新趋势。智…

泪目!网络连接中断的原因,终于找到了!

朋友们&#xff0c;出大事了&#xff01; 不知道多少朋友玩过 DNF 这个游戏&#xff0c;这个我从小学玩到大学的 “破” 游戏&#xff0c;昨天竟然出手游了&#xff01; 我都忘了自己曾几何时预约过这个手游通知&#xff0c;昨天给我发了条通知信息说游戏已开服。 老玩家直接…

网络实时安全:构筑数字时代的铜墙铁壁

什么是网络实时安全&#xff1f; 网络实时安全&#xff0c;简而言之&#xff0c;是一种能够在威胁发生的瞬间即刻识别、响应并有效抵御的安全机制。它强调的是速度与效率&#xff0c;确保网络环境能够持续处于安全状态。这背后&#xff0c;离不开高科技的支撑——扩展检测系统…

分类预测 | Matlab实现ZOA-SVM斑马算法优化支持向量机的多变量输入数据分类预测

分类预测 | Matlab实现ZOA-SVM斑马算法优化支持向量机的多变量输入数据分类预测 目录 分类预测 | Matlab实现ZOA-SVM斑马算法优化支持向量机的多变量输入数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现ZOA-SVM斑马算法优化支持向量机的多变量输…

STM32+CubeMX移植SPI协议驱动W25Q16FLash存储器

STM32CubeMX移植SPI协议驱动W25Q16FLash存储器 SPI简介拓扑结构时钟相位&#xff08;CPHA&#xff09;和时钟极性&#xff08; CPOL&#xff09; W25Q16简介什么是Flash&#xff0c;有什么特点&#xff1f;W25Q16内部块、扇区、页的划分引脚定义通讯方式控制指令原理图 CubeMX配…

使用vue3实现右侧瀑布流滑动时左侧菜单的固定与取消固定

实现效果 实现方法 下面展示的为关键代码&#xff0c;想要查看完整流程及代码可参考https://blog.csdn.net/weixin_43312391/article/details/139197550 isMenuBarFixed为控制左侧菜单是否固定的参数 // 监听滚动事件 const handleScroll () > {const scrollTopThreshol…

mac M3芯片 goland 2022.1 断点调试失败(frames are not available)问题,亲测有效

遇到如上问题&#xff0c;解法 步骤1&#xff1a;下载dlv文件 执行 go install github.com/go-delve/delve/cmd/dlvlatest 然后在 $GOPATH/bin里发现多了一个dlv文件 (找不到gopath? 执行 go env 可以看到) 步骤2&#xff1a;配置dlv 将这个dlv文件移到 /Applications/G…

redis中String,Hash类型用法与场景使用

String 用法 1. 设置键值对 &#xff08;1&#xff09;设置键值对使用 set 命令设置 key 的值。 返回值&#xff1a;ok&#xff0c;如果 key 已经存在&#xff0c;set 命令会覆盖旧值。 &#xff08;2&#xff09;使用 setex 命令设置 key 的值并为其设置过期时间&#xff…

MCS-51伪指令

上篇我们讲了汇编指令格式&#xff0c;寻址方式和指令系统分类&#xff0c;这篇我们讲一下单片机伪指令。 伪指令是汇编程序中用于指示汇编程序如何对源程序进行汇编的指令。伪指令不同于指令&#xff0c;在汇编时并不翻译成机器代码&#xff0c;只是会汇编过程进行相应的控制…