FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列

1. 课程视频(375.1)

  • 此教程不会讲过多理论,以动手实操为主,解决大伙学了半天 FreeRTOS 操作系统不知道干什么的问题。

为什么要学 FreeRTOS ?

  • 更有钱途!!
    • 只会祼机开发的单片机工程师,薪资注定不会高于会 FreeRTOS 的工程师;
    • 有了 FreeRTOS 基础,对于将来学习 Linux 操作系统会更加有帮助;

如何学好 FreeRTOS ?

  • 无它,多写代码,多做项目!
  • 一定要把本课程里所有项目全部自己动手做一遍,加深理解。光看不练假把式!!

学习本课程前置要求

  • C 语言熟练;
  • 上官二号课程一定要好好学一遍,本课程依然基于上官二号。

2. FreeRTOS介绍(376.2)

什么是 FreeRTOS ?

  • Free 即免费的,RTOS 的全称是 Real time operating system,中文就是实时操作系统。
  • 注意:RTOS 不是指某一个确定的系统,而是指一类操作系统。比如:uc/OS,FreeRTOS,RTX,RT-Thread 等这些都是 RTOS 类操作系统。

FreeRTOS 是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满
足较小系统的需要。 由于 RTOS 需占用一定的系统资源(尤其是 RAM 资源),只有 μC/OS-II、
embOS、salvo、FreeRTOS 等少数实时操作系统能在小 RAM 单片机上运行。相对 μC/OS-II、embOS 等商业操作系统,FreeRTOS 操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为 10.4.4 版。

为什么选择 FreeRTOS ?

  • FreeRTOS 是免费的;
  • 很多半导体厂商产品的 SDK(Software Development Kit)软件开发工具包,就使用 FreeRTOS 作为其操作系统,尤其是 WIFI、蓝牙这些带有协议栈的芯片或模块。
  • 简单,因为 FreeRTOS 的文件数量很少。

FreeRTOS 资料与源码下载

  • 最好的资料就是官网提供的资料!点击直达官网

祼机开发与 FreeRTOS

  • 众所周知,游戏与女朋友不可兼得!
  • 祼机开发:
    • 玩游戏,结果女朋友生气,分手!
    • 陪女朋友,无法玩游戏,抑郁症!
  • FreeRTOS:
    • 玩1秒游戏 – 陪1秒女朋友 – 玩1秒游戏 – 陪1秒女朋友 – 玩1秒游戏 – 陪1秒女朋友 …(累死……)
    • 但 CPU 是个无情的战斗机器,可以快速在两个乃至多个任务间快速切换,并且不觉得劳累,实现二者兼顾。
  • FreeRTOS 实现多任务的原理
    • 严格来说 FreeRTOS 并不是实时操作系统,因为它是分时复用的。
    • 系统将时间分割成很多时间片,然后轮流执行各个任务。
    • 每个任务都是独立运行的,互不影响,由于切换的频率很快,就感觉像是同时运行的一样。
      FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第1张图片

3. 移植FreeRTOS到上官一号平台(377.3)

手动移植

  • 过程复杂且繁琐,对新手不友好。如有需要手动移植,可参照以下文章:FreeRTOS移植到STM32教程

使用CubeMX快速移植

快速移植流程

  1. 在 SYS 选项里,将 Debug 设为 Serial Wire ,并且将 Timebase Source 设为 TIM2 (其它定时器也行)。为何要如此配置?下文解说。
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第2张图片
  2. 将 RCC 里的 HSE 设置为 Crystal/Ceramic Resonator 。
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第3张图片
  3. 时钟按下图配置
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第4张图片
  4. 选择 FREERTOS 选项,并将 Interface 改为 CMSIS_V1 。V1 和 V2 有啥区别?下文解释。
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第5张图片
  5. 配置项目信息,并导出代码。
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第6张图片
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第7张图片

一些常见问题

  1. Timebase Source 为什么不能设置为 SysTick ?
  • 裸机的时钟源默认是 SysTick,但是开启 FreeRTOS 后,FreeRTOS 会占用 SysTick (用来生成 1ms 定时,用于任务调度),所以需要需要为其他总线提供另外的时钟源。
  1. FreeRTOS 版本问题
  • V2 的内核版本更高,功能更多,在大多数情况下 V1 版本的内核完全够用。
  1. FreeRTOS 各配置选项卡的解释
  • Events:事件相关的创建
  • Task and Queues: 任务与队列的创建
  • Timers and Semaphores: 定时器和信号量的创建
  • Mutexes: 互斥量的创建
  • FreeRTOS Heap Usage: 用于查看堆使用情况
  • config parameters: 内核参数设置,用户根据自己的实际应用来裁剪定制 FreeRTOS 内核
  • Include parameters: FreeRTOS 部分函数的使能
  • User Constants: 相关宏的定义,可以自建一些常量在工程中使用
  • Advanced settings:高级设置
  1. 内核配置、函数使能的一些翻译
  • 内核参数的理解内容非常多,可以参考以下文章:FreeRTOS 内核配置说明

4. (补充)模板工程打开串口(378.3)

  • 打开串口1
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第8张图片
  • 打开 “Use MicroLIB”
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第9张图片
  • 添加代码(1.muban)
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第10张图片
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第11张图片
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第12张图片

5. 任务的创建与删除(理论)(379.4)

1. 什么是任务?

  • 任务可以理解为进程/线程,创建一个任务,就会在内存开辟一个空间。
  • 比如:
    • 玩游戏、陪女朋友,都可以视为任务
    • Windows 系统中的 MarkText 、谷歌浏览器、记事本,都是任务。
  • 任务通常都含有 while(1) 死循环。

2. 任务创建与删除相关函数

  • 任务创建与删除相关函数有如下三个:
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第13张图片
  • 任务动态创建与静态创建的区别:
    • 动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。
    • 通常情况下使用动态方式创建任务。
  • xTaskCreate 函数原型
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第14张图片
  1. pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);
  2. pcName:任务的名字,主要是用来调试,默认情况下最大长度是16;
  3. pvParameters:指定的任务栈的大小;
  4. uxPriority:任务优先级,数值越大,优先级越大;
  5. pxCreatedTask:用于返回已创建任务的句柄可以被引用。
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第15张图片
  • 官方案例:
/* Task to be created. */
void vTaskCode( void * pvParameters )
{
	/* The parameter value is expected to be 1 as 1 is passed in the
	pvParameters value in the call to xTaskCreate() below.
	configASSERT( ( ( uint32_t ) pvParameters ) == 1 );
	for( ;; )
	{
		/* Task code goes here. */
	}
}
/* Function that creates a task. */
void vOtherFunction( void )
{
	BaseType_t xReturned;
	TaskHandle_t xHandle = NULL;
	/* Create the task, storing the handle. */
	xReturned = xTaskCreate(
			vTaskCode, /* Function that implements the task. */
			"NAME", /* Text name for the task. */
			STACK_SIZE, /* Stack size in words, not bytes. */
			( void * ) 1, /* Parameter passed into the task. */
			tskIDLE_PRIORITY,/* Priority at which the task is created. */
			&xHandle ); /* Used to pass out the created task's handle. */
	if( xReturned == pdPASS )
	{
		/* The task was created. Use the task's handle to delete the task. */
		vTaskDelete( xHandle );
	}
}
  • vTaskDelete 函数原型
void vTaskDelete(TaskHandle_t xTaskToDelete);
- 只需将待删除的任务句柄传入该函数,即可将该任务删除。
- 当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。

6. 任务的创建与删除(实操)(380.5)

  • 基于1.muban
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第16张图片
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第17张图片
  • 代码(2.task_test)(实现一闪一闪,且LED1闪的更快)
//freertos.c
/* USER CODE END Header_StartTaskLED1 */
void StartTaskLED1(void const * argument)
{
  /* USER CODE BEGIN StartTaskLED1 */
  /* Infinite loop */
  for(;;)
  {	
	HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
    osDelay(500);
  }
  /* USER CODE END StartTaskLED1 */
}
/* USER CODE END Header_StartTaskLED2 */
void StartTaskLED2(void const * argument)
{
  /* USER CODE BEGIN StartTaskLED2 */
  /* Infinite loop */
  for(;;)
  {
    HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
    osDelay(1000);
  }
  /* USER CODE END StartTaskLED2 */
}

FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第18张图片

7. 任务调度简介(381.6)

什么是任务调度?

  • 调度器就是使用相关的调度算法来决定当前需要执行的哪个任务。
  • FreeRTOS 中开启任务调度的函数是 vTaskStartScheduler() ,但在 CubeMX 中被封装为
    osKernelStart() 。

FreeRTOS的任务调度规则是怎样的?

  • FreeRTOS 是一个实时操作系统,它所奉行的调度规则:
  1. 高优先级抢占低优先级任务,系统永远执行最高优先级的任务(即抢占式调度)
  2. 同等优先级的任务轮转调度(即时间片调度)
  3. 还有一种调度规则是协程式调度,但官方已明确表示不更新,主要是用在小容量的芯片上,用得也不多。

抢占式调度运行过程

FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第19张图片

  • Task 1:玩游戏
  • Task 2:老妈喊你吃饭
  • Task 3:女朋友喊你看电视
  • 总结:
    1. 高优先级任务,优先执行;
    2. 高优先级任务不停止,低优先级任务无法执行;
    3. 被抢占的任务将会进入就绪态

时间片调度运行过程FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第20张图片

  • 总结:
  1. 同等优先级任务,轮流执行,时间片流转;
  2. 一个时间片大小,取决为滴答定时器中断周期(1ms);
  3. 注意没有用完的时间片不会再使用,下次任务 Task3 得到执行,还是按照一个时间片的时钟节拍运行

8. 任务状态及互相转换(382.7)

FreeRTOS中任务共存在4种状态:

  • Running 运行态
    • 当任务处于实际运行状态称之为运行态,即 CPU 的使用权被这个任务占用(同一时间仅一个任务处于运行态)。
  • Ready 就绪态
    • 处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因为同优先级或更高优先级的任务正在运行。
  • Blocked 阻塞态
    • 如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞态。
  • Suspended 挂起态
    • 类似暂停,通过调用函数 vTaskSuspend() 对指定任务进行挂起,挂起后这个任务将不被执行,只有调用函数 xTaskResume() 才可以将这个任务从挂起态恢复。
      FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第21张图片
  • 总结:
  1. 仅就绪态可转变成运行态
  2. 其他状态的任务想运行,必须先转变成就绪态

9. 任务综合小实验(383.8)

实验需求

  • 创建 4 个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:
    • taskLED1:间隔 500ms 闪烁 LED1;
    • taskLED2:间隔 1000ms 闪烁 LED2;
    • taskKEY1:如果 taskLED1 存在,则按下 KEY1 后删除 taskLED1 ,否则创建 taskLED1;
    • taskKEY2:如果 taskLED2 正常运行,则按下 KEY2 后挂起 taskLED2 ,否则恢复 taskLED2

cubemx配置

FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第22张图片

代码实现(基于2.task_test)

  • 代码(3.tasks_test)
//freertos.c
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不存在,准备创建任务1\r\n");
					osThreadDef(taskLED1, StartTaskLED1, osPriorityNormal, 0, 128);
					taskLED1Handle = osThreadCreate(osThread(taskLED1), NULL);
					if(taskLED1Handle != NULL)
						printf("任务1创建完成\r\n");
				}
				else
				{
					printf("删除任务1\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 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(10);
  }
  /* USER CODE END StartTaskKEY2 */
}

FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第23张图片

10. 队列简介(384.9)

什么是队列?

  • 队列又称消息队列,是一种常用于任务间通信的数据结构,队列可以在任务与任务间、中断和任务间传递信息。
  • 为什么不使用全局变量?
    • 如果使用全局变量,兔子(任务1)修改了变量 a ,等待树獭(任务3)处理,但树獭处理速度很慢,在处理
      数据的过程中,狐狸(任务2)有可能又修改了变量 a ,导致树獭有可能得到的不是正确的数据。
      FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第24张图片
  • 在这种情况下,就可以使用队列。兔子和狐狸产生的数据放在流水线上,树獭可以慢慢一个个依次处理。
  • 关于队列的几个名词:
    • 队列项目: 队列中的每一个数据;
    • 队列长度: 队列能够存储队列项目的最大数量;
  • 创建队列时,需要指定队列长度及队列项目大小。

队列特点

  1. 数据入队出队方式
  • 通常采用先进先出(FIFO)的数据存储缓冲机制,即先入队的数据会先从队列中被读取。
  • 也可以配置为后进先出(LIFO)方式,但用得比较少。
  1. 数据传递方式
  • 采用实际值传递,即将数据拷贝到队列中进行传递,也可以传递指针,在传递较大的数据的时候采用指针传
    递。
  1. 多任务访问
  • 队列不属于某个任务,任何任务和中断都可以向队列发送/读取消息
  1. 出队、入队阻塞
  • 当任务向一个队列发送消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队。
  • 阻塞时间如果设置为:
    • 0:直接返回不会等待;
    • 0~port_MAX_DELAY:等待设定的阻塞时间,若在该时间内还无法入队,超时后直接返回不再等待;
    • port_MAX_DELAY:死等,一直等到可以入队为止。出队阻塞与入队阻塞类似;

队列相关 API 函数

  1. 创建队列
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
  • 参数:
    • uxQueueLength:队列可同时容纳的最大项目数 。
    • uxItemSize:存储队列中的每个数据项所需的大小(以字节为单位)。
  • 返回值:
    • 如果队列创建成功,则返回所创建队列的句柄 。 如果创建队列所需的内存无法分配 ,则返回 NULL。
  1. 写队列
  • 写队列总共有以下几个函数:
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第25张图片
BaseType_t xQueueSend(
              QueueHandle_t xQueue,
              const void * pvItemToQueue,
              TickType_t xTicksToWait
            );
  • 参数:
    • xQueue:队列的句柄,数据项将发送到此队列。
    • pvItemToQueue:待写入数据
    • xTicksToWait:阻塞超时时间
  • 返回值:
    • 如果成功写入数据,返回 pdTRUE,否则返回 errQUEUE_FULL。
  1. 读队列
  • 读队列总共有以下几个函数:
    FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第26张图片
BaseType_t xQueueReceive(
               QueueHandle_t xQueue,
               void *pvBuffer,
               TickType_t xTicksToWait
             );
  • 参数:
    • xQueue:待读取的队列
    • pvItemToQueue:数据读取缓冲区
    • xTicksToWait:阻塞超时时间
  • 返回值:
    • 成功返回 pdTRUE,否则返回 pdFALSE。

11. 队列实操(385.10)

实验需求

  • 创建一个队列,按下 KEY1 向队列发送数据,按下 KEY2 向队列读取数据。

cubeMX配置

FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第27张图片
FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第28张图片

代码实现

  • 代码(4.queue_test)(基于3.tasks_test)
//freertos.c
void StartTaskSend(void const * argument)
{
  /* USER CODE BEGIN StartTaskSend */
	uint16_t buf = 99;
	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);
				else
					printf("写入队列失败\r\n");
			}
			while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET);//防止按下时多次一直打印
		}
    osDelay(10);
  }
  /* USER CODE END StartTaskSend */
}
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);
				else
					printf("读取队列失败\r\n");
			}
			while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_RESET);//防止按下时多次一直打印
		}
    osDelay(10);
  }
  /* USER CODE END StartTaskReceive */
}

FreeRTOS第1天:freertos介绍及移植、任务的创建与删除、队列_第29张图片

你可能感兴趣的:(数据库)