STM32-FreeRTOS快速学习

1、FreeRTOS定义

FreeRTOS 满足实时系统对任务响应时间的要求。
实时操作系统、轻量级(内核小,只需要几KB的ROM和RAM)、
提供了一些内核功能,如任务管理、时间管理、内存管理和通信机制等。

RTOS核心就是任务切换,任务是在中断里面切换的,切换任务的最基本的目的起始就是切换PC指针,所有的动作都是围绕着这个
PC指针切换来的。简单来说,进入中断的目的就是为了切换pc指针,然后退出,退出之后就进入到了切换之后的pc指针指向的程序
代码处,也就是任务2

2、和裸机的区别

裸机:无操作系统,缺乏任务调度难以管理多任务。只能顺序执行任务
多任务管理:可以创建多个任务,通过时间片轮转算法进行任务切换。
任务管理

# 裸机
void music()
void movie()
void main(){
	while(1){
		music();
		movie();
	}
}
# freeRTOS通过时间片轮转算法不断切换任务,实现了音画基本同步,提高了用户体验
void task1(){
	music();
}
void task2(){
	movie();
}
void main(){
	while(1){
		task1();
		taks2();
	}
}

提供内存管理、任务间通信机制

3、利用CUBMX快速配置FreeRTOS

源码结构/数据类型/命名规则总结
freeRTOS需要使用systick滴答时钟作为系统提供时基,因此需要更换stm32内核的时基为其他定时器
在这里插入图片描述
在这里插入图片描述
生成多出的代码
在这里插入图片描述

osThreadDef 为FreeRTOS用于定于任务的宏
osThreadDef(name, thread, priority, instances, stacksz)

name为任务的名称,thread为任务的处理函数(函数指针,传入函数名即可),priority为任务的优先级,instances为任务的实例数,stacksz为任务的堆大小 栈

osThreadCreate用于创建任务的函数
osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)
thread_def为任务定义的指针,argument为传递给任务处理函数的参数

CUBMEX创建了一个默认的任务
在这里插入图片描述
在FreeRTOS.init中创建了任务

  /* Create the thread(s) */
  /* definition and creation of defaultTask */
  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

也创建了对应的任务处理函数

/* 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 StartDefaultTask */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END StartDefaultTask */
}
# 上面defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL)中
# 传递参数为NULL,所以这里StartDefaultTask()里的argument也为NULL

4、手动创建第一个自己的任务

仿照CUBMEX创建的默认任务,在main.c中创建自己的任务,实现每 500ms led闪烁一次
osThreadId myTaskHandle 创建任务句柄
void myTask1(void const * argument){for(;;) {}} 任务功能函数

#define osThreadDef(name, thread, priority, instances, stacksz)  

name:任务的名称;
thread:任务的函数名称;
priority:任务的优先级,与任务调度有关;
instances:能够被实例化的最大数量,好像没啥作用;
stacksz:线程函数的堆栈大小要求(以字节为单位),也就是每个线程分配了固定大小的栈空间,这个大小与线程局部变量和调用深度有关(如果调用其他函数则需要将CPU中的LS寄存器压入栈中)。

osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)

thread_def:定义的线程(任务);
argument:为开始参数传递给线程函数的指针。
实例

  osThreadDef(myTask, myTask1, osPriorityNormal, 0, 128);
  myTaskHandle = osThreadCreate(osThread(myTask), NULL);
#导入头文件
#include "FreeRTOS.h"
#include "task.h"

osThreadId myTaskHandle;//任务句柄ID

void myTask1(void const * argument)
{
  for(;;)//相当于main函数中的while
  {
	  HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
	  osDelay(500);//每 500ms LED闪烁一次
  }
}

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  osThreadDef(myTask, myTask1, osPriorityNormal, 0, 128);
  myTaskHandle = osThreadCreate(osThread(myTask), NULL);

5、堆、栈、FreeRTOS里的任务栈

:操作系统里非常大的一块内存,比如malloc函数就是申请堆里的一块区域使用,不过不free释放,那么申请一块就少一块。

:存储局部变量和函数调用消息的数据结构。栈的大小在程序编译时确定,通常较小。

void func(){
}
int main(void){
	int a=0;
	int b=0;
	func();
	return 0;
}

执行完func()函数如何跳回主函数,这就使用到了栈。
下图中LR为Link Register,SP是堆栈指针,用来指示当前要出栈或入栈的数据,保存数据时SP总指向最后一个压入堆栈的数据所在的数据单元——栈顶,栈的数据是先入后出。

在这里插入图片描述
堆的分配是动态的,由人决定;栈的分配是静态的,由编译器自动控制,编译时确定,运行时无法改变。
堆的分配效率低,需要运行时动态分配和释放。栈的分配效率高,由编译器编译时确定分配大小。

FreeRTOS里的任务栈:创建每一个任务都分配了一块栈空间,任务栈的大小在创建任务时指定,任务栈无需我们管理。

6、FreeRTOSCongfig.h(FreeRTOS的大总管)

该文件主要包含了各种宏定义,有功能宏、API宏…,可以对FreeRTOS灵活配置。详细可参考各种宏的说明
包含任务调度器、内存管理、时间管理、中断处理等方面的配置。
一部分如下图
在这里插入图片描述
常用比较重要的6个宏:
configUSE_PREEMPTION定义任务调度器的抢占式调度或者协同式调度。

为 1 时 RTOS 使用抢占式调度器,即当进程位于内核空间时,有一个更高优先级的任务出现时,如果当前内核允许抢占,则可以将当前任务挂起,执行优先级更高的进程;为 0 时 RTOS 使用协作式调度器(时间片)高优先级的进程不能中止正在内核中运行的低优先级的进程而抢占 CPU 运行。

configUSE_IDLE_HOOK 定义是否使用空闲任务钩子函数。

configUSE_TICK_HOOK 定义是否使用系统滴答钩子函数。c

configTICK_RATE_HZ 定义是否使用系统滴答频率。

configCPU_CLOCK_HZ 定义系统时钟频率。

configMAX_PRIORITIES 定义系统支持的最大任务优先级。

7、任务调度算法

多个任务的执行顺序、时间如何确定。
实时系统的调度需求:响应时间要要求、任务优先级、资源利用率。
FreeRTOS的任务调度算法分为抢占式调度算法(优先级抢占式调度算法、时间片轮转调度算法)、非抢占式调度算法(优先级调度算法、先来先服务调度算法)。

优先级抢占式调度算法:任务优先级越高执行的机会越大,相同时执行时间片轮转调度算法。 满足实时操作系统的响应要求、有优先级管理,缺点是优先级低的任务可能被长时间阻塞。

时间片轮转调度算法:每个任务被分配一个时间片,时间片用完后任务被挂起,等待下一次调度。 公平分配CPU时间片、避免优先级低的任务被长时间阻塞。 缺点无法满足实时操作系统的响应要求。
FreeRTOS默认开启优先级抢占式调度算法、时间片轮转调度算法结合使用。

8、利用CUBMX创建任务

在这里插入图片描述
可以看到生成的代码和手动配置的代码一样
在这里插入图片描述

在这里插入图片描述

9、任务优先级实验

7个优先级,数字越小优先等级越低,可以在利用CUBMX创建任务时指定或者手动修改

/* definition and creation of defaultTask */
  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

  /* definition and creation of myTask02 */
  osThreadDef(myTask02, StartTask02, osPriorityIdle, 0, 128);
  myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

  /* definition and creation of myTask03 */
  osThreadDef(myTask03, StartTask03, osPriorityHigh, 0, 128);
  myTask03Handle = osThreadCreate(osThread(myTask03), NULL);

在这里插入图片描述
在这里插入图片描述
设置task3优先级高于task2,实验现象就是
Task3 Task2 Task3 Task2交替打印,Task3先,Task2后

10、时间片调度

概念:时间片调度是指将CPU的执行时间划分为固定长度的时间片,每个任务在一个时间片内执行一定的指令
原理:当多个具有相同优先级的任务处于就绪状态时,他们将通过时间片调度轮流执行。
默认情况下时间片调度和优先级调度是同时开启的。较高优先级的任务处于就绪状态时,它会优先执行,而不受时间片调度的限制。

实验:时间片调度/ 优先级调度

11、静态创建任务和动态创建任务

//c语言中
int a[10];//静态创建,执行完后自动释放
int* p=(int*)malloc(sizeof(int)*10);//动态创建
free(p);

静态创建任务不需要进行内存动态分配和释放操作,运行时性能开销更低,任务响应时间更低。缺点就是静态创建任务在编译时需要确定任务的数量和属性,这意味着任务的数量和属性在运行时不可变的。如果任务的资源需求在运行时发生变化,可能会导致资源浪费或不足。

动态创建任务在运行时创建任务,任务的内存空间在运行时动态分配。可以按实际需求分配内存避免浪费。缺点:动态创建任务涉及到内存动态分配和释放,需要额外的内存管理机制。这会增加系统的复杂性和开销,包括内存碎片问题的处理。
在这里插入图片描述

  /* definition and creation of myTask02 */
  //静态创建任务的task2多出来两个参数
  //myTask02Buffer stack栈的大小
  osThreadStaticDef(myTask02, StartTask02, osPriorityIdle, 0, 128, myTask02Buffer, &myTask02ControlBlock);
  myTask02Handle = osThreadCreate(osThread(myTask02), NULL);

  /* definition and creation of myTask03 */
  osThreadDef(myTask03, StartTask03, osPriorityHigh, 0, 128);
  myTask03Handle = osThreadCreate(osThread(myTask03), NULL);

12、任务的状态

  • 就绪态:任务已创建,还没被调度器执行
  • 运行态:被调度器选中执行的任务处于运行态,正在使用CPU资源执行任务代码
  • 阻塞态:任务因为某些原因无法继续执行而被阻塞。
  • 暂停 / 挂起态:任务被暂停,可以恢复。
    不管哪个状态的任务,要想进入运行态,都得先进入就绪态。
    假如有A B C 3个任务,都进入了就绪态,A先执行,进入运行态,运行一段时间切换为B,A进入就绪态;到A再次进入运行态时,A可以调用vTAskSuspend()主动休息,进入挂起态;或者A希望等待某些事情发生,于是进入阻塞态。

在这里插入图片描述
任务状态切换的基础:tick滴答中断,每次发生时判断是否要切换任务,FreeRTOS的时间基准是一个tick间隔,即每个任务执行的时间片。
不同任务的切换依靠了链表

13、空闲任务和钩子函数

对任务的清理是在空闲任务中,优先级最低。如果任务自己删除自己,就无法清理自己。
可以添加一个空闲任务钩子函数(Idle task hook functions),空闲任务每执行一次,就会调用一次钩子函数。钩子函数的作用有这些:

  • 执行一些低优先级、后台的、需要连续执行的函数
  • 测量系统的空闲时间:空闲任务能执行说明高级任务停止了,测量空闲任务占据的时间可以算出处理器占有率。
  • 让系统进入省电模式:空闲任务能执行说明说明没有重要的事情,可以进入省电模式
    注意钩子函数的限制:
  • 不能让空闲任务进入到阻塞、暂停状态
  • 钩子函数要实现得越快越好

任务通信和同步

FreeRTOS提供了多种任务通信和同步机制,如队列、信号量、互斥锁、事件组等

14、消息队列

任务之间的同步(同步就是任务之间做数据交互、通讯),任务和中断之间的同步都可以依靠消息队列,从而实现异步处理,FreeRTOS的队列采用FIFO(先进先出)缓冲区,具体如下图所示;

在这里插入图片描述
消息队列的读取/写入,消息队列里的数据一般是多写一读
读取
任务读取数据时,设置堵塞超时时间内,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程
往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。

写入:如果队列被多个任务写入,那么将导致多个任务堵塞以等待队列有效,当队列有效的时候,这些任务中的优先级最高的任务优先进入就绪态。
FreeRTOS的消息队列函数封装在了queue.h中
以下为常用函数

//创建与删除队列
//这个函数可以创建一个队列,创建成功则会返回一个队列句柄
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength,UBaseType_t uxItemSize);
void vQueueDelete( QueueHandle_t xQueue ); //传入待删除队列的句柄


//任务与任务之间同步
xQueueSendToFront //发送数据到队首
xQueueSendToBack //发送数据到队尾
xQueueSend  //与xQueueSendToBack一样
xQueueOverwrite
//任务与中断之间同步
xQueueSendToFrontFromISR
xQueueSendToBackFromISR
xQueueOverwriteFromISR
xQueueSendFromISR

//接收
xQueueReceive //接收到的单元同时会从队列中删除。(出队)
xQueuePeek //接收数据,但接收到数据后,不会删除队列中的数据。

//
uxQueueSpacesAvailable //查询队列中可用的空闲空间数量
uxQueueMessagesWaiting //查询队列中当前有效数据单元个数

15、信号量

二值信号量是一种用于同步任务之间的机制。它有两种状态,可用和不可用,通常用于实现互斥访问共享资源。
xSemaphoreCreateBinary():创建一个信号量,返回信号量句柄
xSemaphoreTake() :占用信号量
xSemaphoreGive() :释放信号量

// 创建一个初始状态为不可用的二值信号量
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
xSemaphoreTake(sem, 0);
 
// 发送任务释放二值信号量
xSemaphoreGive(sem);
 
// 接收任务获取二值信号量
xSemaphoreTake(sem, portMAX_DELAY);

注意:如果二值信号量不可用,调用 xSemaphoreTake() 函数的任务将被挂起,直到二值信号量变为可用状态。

16、计数信号量

计数信号量是一种用于同步任务之间的机制。它可以记录一个整数计数器的值,通常用于控制任务执行的次数
调用xSemaphoreGive() 计数信号量+1,xSemaphoreTake()计数信号量-1。

// 创建一个初始值为 0 的计数信号量
SemaphoreHandle_t sem = xSemaphoreCreateCounting(10, 0);
 
// 发送任务将计数信号量的值加 1
xSemaphoreGive(sem);
 
// 接收任务将计数信号量的值减 1
xSemaphoreTake(sem, portMAX_DELAY);

注意:如果计数信号量的值已经为 0,调用 xSemaphoreTake() 函数的任务将被挂起,直到计数信号量的值不为 0。

17、事件标志组

通过时间标志组来实现定时任务的调度。时间标志组是一种可以在指定时间后自动通知任务的机制,通知可以激活等待时间标志组的任务。

/* 创建一个周期为 1000ms 的时间标志组,时间标志组到期时调用 vTimerCallback 函数 */
TimerHandle_t xTimer = xTimerCreate("Timer", pdMS_TO_TICKS(1000), pdTRUE, NULL, vTimerCallback);
 
/* 启动时间标志组 */
xTimerStart(xTimer, 0);
 
/* 回调函数 */
void vTimerCallback(TimerHandle_t xTimer)
{
    /* 处理时间标志组到期事件 */
}

18、中断处理与任务管理的关系

  • 18
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值