百问网FreeRTOS复习笔记第22到28讲

/*

源代码以及其他一切形式的知识转述均出自百问网课程,仅供参考,请勿用作商业用途。

  【FreeRTOS入门与工程实践 --由浅入深带你学习FreeRTOS(FreeRTOS教程 基于STM32,以实际项目为导向)】 https://www.bilibili.com/video/BV1Jw411i7Fz/?p=8&share_source=copy_web&vd_source=bab35cd72a6b7a3ffd3c77e664d802f1 

*/

第22讲 任务状态_改进播放控制

任务的调度:

(1)相同优先级的任务轮流运行

(2)更高优先级的任务优先运行

任务的最高优先级(configMAX_PRIORITIES)是56,这个宏被引用在了优先级管理链表中。如下所示:

PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ] = {0};	/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1 = {0};								/*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2 = {0};								/*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList = NULL;					/*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList = NULL;			/*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList = {0};								/*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */

List_t结构体的定义是:

typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	volatile UBaseType_t uxNumberOfItems;
	ListItem_t * configLIST_VOLATILE pxIndex;			/*< Used to walk through the list.  Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
	MiniListItem_t xListEnd;							/*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
	listSECOND_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;

由此可知,我们最多可以设置0到55种优先级,把这些任务句柄存储在这一个链表之中:

pxReadyTasksLists[0]到pxReadyTasksLists[55] 。这些任务可能处于Ready态或者Running态。咱们之前的声光色影任务中使用的osPriorityNormal优先级为24,即在链表pxReadyTasksLists[osPriorityNormal] 中存放着光色影这三个任务(孤勇者的播放为了提高质量加1了)。每创建一个任务系统都会创建一个TCB结构体,按照建立顺序链接,注意24这个优先级的头节点是默认任务(default task)。程序在启动任务调度器(osKernelStart)时,会自动建立一个优先级为0的空闲任务。创建第一个任务时,操作系统会生成一个全局指针pxCurrentTCB:

PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL;

pxCurrentTCB会自动指向当前最高优先级的就绪任务,同优先级的任务被依次创建之后它也会跟着改变指向,虽然启用任务调度器之后系统会创建一个空闲任务,但是这个默认的空闲任务优先级较低,pxCurrentTCB依然会指向程序员最后创建的高优先级新任务。程序在开始运行的时候会从pxCurrentTCB指向的就绪态任务开始,这就是之前06_Task_Create工程中OLED会先从Task3开始打印的原因。

FreeRTOS会为我们初始化一个时钟节拍(Tick),如下图所示:

它定义了一个频率为1000Hz的时钟(振荡源就是系统滴答计时器),每1ms产生一次时钟中断。这个中断中会发生计数累加(cnt++作为时钟基准);Tick中断也会遍历延时态链表,检查是否存在完成延时可以恢复就绪的任务;并且会发起一次调度从上至下遍历(55到0)pxReadyTasksLists[osPriorityNormal] 链表,发现非空链表之后改变pxCurrentTCB的指向,从而找到下一个需要运行的任务并且启动它。

vTaskDelay()函数可以将处于任意状态的任务从它所属的状态链表之中删除,并将他移入Delay链表(DelayedTaskList)。

就绪链表之中存在一个记录项指针index,指向上一个运行的任务。

处于阻塞态链表的任务不会被Tick中断所唤醒,只能被vTaskDelay从阻塞态“拉入”就绪态。

第23讲 空闲任务

我们在FreeRTOS中创造任务时调用的C函数必须最终处于死循环,如下所示的任务中:

/*创建任务:声*/
  do{
	  ret1=xTaskCreate(PlayMusic,"SoundTask",128,NULL,osPriorityNormal,&xSoundTaskHandle);
	}while(ret1==pdFAIL);
	/*创建任务:光*/
	do{
	  ret2=xTaskCreateStatic(Led_Test,"lightTask",128,NULL,osPriorityNormal,g_pucStackofLightTask,&g_TCBofLightTask);
	}while(ret2==pdFAIL);
	/*创建任务:色*/
	do{
	  ret3=xTaskCreateStatic(ColorLED_Test,"ColorLEDTask",128,NULL,osPriorityNormal,g_pucStackofColourTask,&g_TCBofColourTask);
	}while(ret3==pdFAIL);

例如PC13电平翻转点灯的函数:

void Led_Test(void *argument)
{
    Led_Init();

    while (1)
    {
        Led_Control(LED_GREEN, 1);
        mdelay(500);

        Led_Control(LED_GREEN, 0);
        mdelay(500);
    }
}

如果改成这样,是不能传递给xCreateTask的:

void Led_Test(void *argument)
{
    Led_Init();

    for(int i=0;i<10;i++)
    {
        Led_Control(LED_GREEN, 1);
        mdelay(500);

        Led_Control(LED_GREEN, 0);
        mdelay(500);
    }
}

原因很简单,MCU程序中一个函数在执行完之后要返回到LR寄存器所指示的位置,同时系统在创建任务时会初始化栈,并且在栈中伪造好LR寄存器的值,即prvTaskExitError。

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* Simulate the stack frame as it would be created by a context switch
	interrupt. */
	pxTopOfStack--; /* Offset added to account for the way the MCU uses the stack on entry/exit of interrupts. */
	*pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
	pxTopOfStack--;
	*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;	/* PC */
	pxTopOfStack--;
	*pxTopOfStack = ( StackType_t ) prvTaskExitError;	/* LR */

	pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
	*pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */
	pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

	return pxTopOfStack;
}

static void prvTaskExitError( void )
{
	/* A function that implements a task must not exit or attempt to return to
	its caller as there is nothing to return to.  If a task wants to exit it
	should instead call vTaskDelete( NULL ).

	Artificially force an assert() to be triggered if configASSERT() is
	defined, then stop here so application writers can catch the error. */
	configASSERT( uxCriticalNesting == ~0UL );
	portDISABLE_INTERRUPTS();
	for( ;; );
}

在这个函数中先是关闭了中断,同时创建了一个死循环。如果xCreateTask接口导入的任务函数中没有死循环,就会被导入prvTaskExitError()之中。这是一个关闭中断的死循环,会导致所有任务都无法执行。

任务退出状态链表的方式只能是主动或者被动调用了vTaskDelete。

void Led_Test(void *argument)
{
    Led_Init();

    for(int i=0;i<10;i++)
    {
        Led_Control(LED_GREEN, 1);
        mdelay(500);

        Led_Control(LED_GREEN, 0);
        mdelay(500);
    }
		vTaskDelete(NULL);
}

空闲任务负责释放自我删除的任务占用的内存空间。注意空闲任务的默认优先级极低(位于pxReadyTasksLists[0]),没有主动和其他任务抢占CPU资源的能力。所以建议遵循以下编程习惯:①事件驱动任务卸载;②延时内部不要使用死循环(例如我们使用的mdelay()函数内部就包含死循环),最好使用阻塞式延时(例如vTaskDelay)。

空闲任务永远处于就绪态,不会处于阻塞。空闲任务可以配置一个钩子函数用来打印调试信息。

第24讲 两个Delay函数

FreeRTOS提供的延时函数可以让目标任务放弃调度,让其他任务有更多的运行机会。

辨析:

 vTaskDelay:至少等待指定个数的 Tick Interrupt 才能变为就绪状态 。
 vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。
void vTaskDelay( const TickType_t xTicksToDelay );

vTaskDelay需要传递的参数是需要推延的tick数;

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )

vTaskDelayUntil需要传递两个参数,一个是起始时间,一个是增量时间。

获得起始时间需要这个函数:

TickType_t xTaskGetTickCount( void )
{
TickType_t xTicks;

	/* Critical section required if running on a 16 bit processor. */
	portTICK_TYPE_ENTER_CRITICAL();
	{
		xTicks = xTickCount;
	}
	portTICK_TYPE_EXIT_CRITICAL();

	return xTicks;
}

老规矩贴一下0错误0警告的代码:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * File Name          : freertos.c
  * Description        : Code for freertos applications
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "Music.h"
#include "driver_led.h"
#include "driver_lcd.h"
#include "driver_mpu6050.h"
#include "driver_timer.h"
#include "driver_ds18b20.h"
#include "driver_dht11.h"
#include "driver_active_buzzer.h"
#include "driver_passive_buzzer.h"
#include "driver_color_led.h"
#include "driver_ir_receiver.h"
#include "driver_ir_sender.h"
#include "driver_light_sensor.h"
#include "driver_ir_obstacle.h"
#include "driver_ultrasonic_sr04.h"
#include "driver_spiflash_w25q64.h"
#include "driver_rotary_encoder.h"
#include "driver_motor.h"
#include "driver_key.h"
#include "driver_uart.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */

/* USER CODE END Variables */
/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
  .name = "defaultTask",
  .stack_size = 128 * 4,
  .priority = (osPriority_t) osPriorityNormal,
};

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
struct TaskPrintInfo{
    uint8_t x;
	  uint8_t y;
    char name[16];
};

struct TaskPrintInfo g_Task1Info={0,0,"Task1"};
//struct TaskPrintInfo g_Task2Info={0,3,"Task2"};
//struct TaskPrintInfo g_Task3Info={0,6,"Task3"};

int g_LCDCanUse=1;

void LcdPrintfTask(void * params)
{
	 struct TaskPrintInfo *pInfo=params;
   uint32_t count=0;
   int len;
	 TickType_t preTime;
	 uint64_t t1,t2;
	 preTime=xTaskGetTickCount();
	
   for(;;)
	{
		 if (g_LCDCanUse)
		{
			g_LCDCanUse = 0;
			len = LCD_PrintString(pInfo->x, pInfo->y, pInfo->name);
			len += LCD_PrintString(len, pInfo->y, ":");
			LCD_PrintSignedVal(len, pInfo->y, count++);
			g_LCDCanUse = 1;
			mdelay(count&0x3);
		}
			t1=system_get_ns();
//			vTaskDelay(500);
		vTaskDelayUntil(&preTime,500);
			
			t2=system_get_ns();
			LCD_ClearLine(pInfo->x, pInfo->y+2);
			LCD_PrintSignedVal(pInfo->x, pInfo->y+2,t2-t1);
	}
}
/* USER CODE END FunctionPrototypes */

void StartDefaultTask(void *argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
  /* USER CODE BEGIN Init */
  LCD_Init();
	LCD_Clear();
  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */

  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */
  /* creation of defaultTask */
//  defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
	/*使用同一个函数常见不同的任务*/
	xTaskCreate(LcdPrintfTask,"Task1",128,&g_Task1Info,osPriorityNormal,NULL);
//	xTaskCreate(LcdPrintfTask,"Task2",128,&g_Task2Info,osPriorityNormal,NULL);
//	xTaskCreate(LcdPrintfTask,"Task3",128,&g_Task3Info,osPriorityNormal,NULL);
	
  /* USER CODE END RTOS_THREADS */

  /* USER CODE BEGIN RTOS_EVENTS */
  /* add events, ... */
  /* USER CODE END RTOS_EVENTS */

}

/* 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 *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
	LCD_Init();
	LCD_Clear();
	
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END StartDefaultTask */
}

/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */

/* USER CODE END Application */

第25讲 同步与互斥通信_有缺陷的同步实例

概念:

同步(Synchronization)

在FreeRTOS中,同步是一种控制任务执行顺序和时间的机制。想象一下,当多个任务需要按照特定的顺序执行,或者当一个任务需要等待另一个任务完成某项工作后才能继续执行时,就需要用到同步。

同步的主要目的是确保任务之间按照预期的顺序进行协调执行,避免出现混乱或竞态条件(race condition)。例如,当两个任务都需要访问同一个资源时,通过同步机制可以确保这两个任务不会同时访问该资源,从而避免数据错误或冲突。

在FreeRTOS中,实现同步的机制包括信号量(Semaphore)、事件标志(Event Flags)等。这些机制可以让任务之间进行合理的协作,确保任务按照预期的顺序执行。

互斥(Mutual Exclusion)

互斥是指在多任务环境中,当运行特定代码段时,确保数据的一致性和完整性,避免多个任务同时访问和修改共享资源导致错误的发生。

互斥的主要目的是保护共享资源,如全局变量、内存缓冲区或设备。由于多个任务可能同时请求访问这些资源,如果不加以控制,就可能导致数据错误或资源冲突。通过互斥机制,可以确保在一个时间点只有一个任务能够访问共享资源,其他任务必须等待或互斥地使用资源。

这一讲老师计划用一个计算数值任务和一个屏幕打印任务讲解同步与互斥,但并没有使用RTOS提供的机制,而是使用全局变量。数值计算任务就是从0加到100000000,在此期间这个任务占用OLED屏幕打印“Waiting...”,计算完毕后另外一个任务打印出计算结果和所用的时间。

使用全局变量等待死循环的方式往往会带来效率问题,1.2s内完成的计算最后需要2.5s。

struct TaskPrintInfo{
    uint8_t x;
	  uint8_t y;
    char name[16];
};

//struct TaskPrintInfo g_Task1Info={0,0,"Task1"};
struct TaskPrintInfo g_Task2Info={0,3,"Task2"};
//struct TaskPrintInfo g_Task3Info={0,6,"Task3"};

int g_LCDCanUse=1;
volatile int g_calc_end=0;
uint64_t g_time=0;
uint32_t g_sum=0;

void CalcTask(void * params)
{
	uint32_t i = 0;
	
	LCD_PrintString(0, 0, "Waiting");

	g_time = system_get_ns();
	for (i = 0; i < 10000000; i++)
	{
		g_sum += i;
	}
	g_calc_end = 1;
	g_time = system_get_ns() - g_time;

	vTaskDelete(NULL);
}

void LcdPrintfTask(void * params)
{
  int len;
	while (1)
	{
		LCD_PrintString(0, 0, "Waiting");
		
		vTaskDelay(3000);
		
		while (g_calc_end == 0);
		
		/*打印信息*/
		if (g_LCDCanUse)
		{
			g_LCDCanUse = 0;
			
			LCD_ClearLine(0, 0);
			len = LCD_PrintString(0, 0, "Sum: ");
			LCD_PrintHex(len, 0, g_sum, 1);
			
			LCD_ClearLine(0, 2);
			len = LCD_PrintString(0, 2, "Time(ms): ");
			LCD_PrintSignedVal(len, 2, g_time/1000000);
			
			g_LCDCanUse = 1;
		}
		vTaskDelete(NULL);
	}
}

第26讲 同步与互斥通信_有缺陷的互斥实例 

如果程序员使用全局变量互斥地访问临界资源,那么程序在自动修改变量时可能会被中断(不如Tick,如果访问临界资源耗时过长)打断。但这个方法依然会存在不足,但如果任务A与任务B的执行函数是同一个,可能会导致任务B一直进行无效访问。

第27讲 同步与互斥通信_FreeRTOS提供的方法

任务与任务之间也存在通信方式。
能实现同步、互斥的内核方法有:任务通知(task notification)、队列(queue)、事件组
(event group)、信号量(semaphoe)、互斥量(mutex)。

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值