Loading

从0开始的FreeRTOS(4)

“从0开始的FreeRTOS”系列教程第四讲

作者:satori

这一次我们来进行基于FreeRTOS的任务管理实验。
在开讲之前,推荐一下Zou Changjun翻译的FreeRTOS实时内核使用指南(官方网站上的英文原名是Mastering_the_FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide),在后面讲解API时我们力求精简易懂,所以不会说太多详细的内容,具体可以参见该文档和官方API手册(文末有本文档的下载链接)。

回顾一下上次我们介绍的有关FreeRTOS的进程的知识:
主要有:进程的概念,进程的调度机制
对于FreeRTOS而言,不同优先级的进程之间采用优先级调度算法,对于同优先级的进程之间采用时间片轮转调度算法+FCFS算法。

本次实验我们主要的实验内容为
任务的创建
同优先级进程之间的时间片轮转调度算法
不同优先级进程之间的优先级调度算法
任务的删除
任务的挂起(延时函数)

首先我们先来学习一下和FreeRTOS的任务创建有关的API:

对于初学者而言,比较需要关心的参数有以下几个:pvTaskCode,pcName和uxPriority
pvTaskCode是任务函数名
pcName是用户起的函数名
uxPriority是任务优先级
在cude建立的工程中,我们不会直接使用freertos的API,而是会使用CMSIS-RTOS的API:

#define osThreadDef	(	 	
	   name,                             //进程名
 	priority,                          //进程优先级
 	instances,                       //进程函数
 	stacksz                           //栈大小
)

其中priority一般使用的是CMISIS-RTOS自己定义的七个优先级(详见上一讲介绍的结构体)

osThreadId osThreadCreate	(	
	const osThreadDef_t * 	thread_def,         //传递在osThreadDef中输入的参数
	void * 	argument                                             //参数
)

这里我们通过一个实例来学习进程的创建:
实验1:
使用CMSIS-RTOS创建一个进程:
仿照第二讲中的方式新建一个工程
并启用串口1(后续的教程中串口会非常常用)

参数设置如下

将默认的任务的名字改成task_1

创建工程,在freertos.c上加入

#include "stm32f1xx_hal.h"

在usart.c下加入以下代码,就可以在32中使用printf功能了

#ifdef __GNUC__
  /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
     set to 'Yes') calls __io_putchar() */
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**

  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
    */
    PUTCHAR_PROTOTYPE
    {
    /* Place your implementation of fputc here */
    /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);          //具体根据自己启用的串口修改这个函数

  	return ch;

	}

在freertos.c中添加如下代码

/* USER CODE BEGIN FunctionPrototypes */
void delay(int x);
/* USER CODE END FunctionPrototypes */

/* task1_handler function */
void task1_handler(void const * argument)
{

  /* USER CODE BEGIN task1_handler */
  /* Infinite loop */
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);
		printf("this is task_1 running\r\n");
		delay(500);
  }
  /* USER CODE END task1_handler */
}

/* USER CODE BEGIN Application */
void delay(int x)
{
	for(int i=0;i<x;i++)
	{
		for(int j=0;j<1000;j++);
	}
}
/* USER CODE END Application */


编译后烧录

可以发现板子上的绿灯闪烁+串口发送数据

然后我们注释掉所有和task_1有关的代码,来尝试不借助cubemx自己生成一个系统任务
(实际上关于cubemx,我的个人意见是,创建工程时这是一个非常方便的工具,但是带来的后续开发的麻烦也很多,比如要修改外设配置的时候,要经历cube中修改------>重新生成工程---->继续写代码这样一个过程,如果你只是要做修改串口波特率这类很简单的参数修改的话,何苦非要经过上面这个繁琐的过程而非直接在代码中修改呢?)
需要添加的内容如下

/声明任务句柄
osThreadId task_2Handle;

//声明任务函数
void task2_handler(void const * argument);

//利用osTreadDef和osThreadCreate两个函数定义任务参数+创建任务
osThreadDef(task_2,                            //任务名
		task2_handler,                    //任务函数
		 osPriorityNormal,               //任务优先级
		 0,                                     // 子进程数
		 128);                                //任务栈
task_2Handle = osThreadCreate(osThread(task_2),                  //任务名要和上面的函数中一致
					NULL);

/关于两个函数更具体的讲解可以看官方API手册
http://www.keil.com/pack/doc/CMSIS_Dev/RTOS/html/group__CMSIS__RTOS__ThreadMgmt.html#gac59b5713cb083702dce759c73fd90dff

最后添加任务函数

void task2_handler(void const * argument)
{

  /* USER CODE BEGIN task1_handler */
  /* Infinite loop */
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
		printf("this is task_2 running\r\n");
		delay(500);
  }
  /* USER CODE END task1_handler */
}

编译下载,查看实验效果
红灯闪烁,串口发送数据如下

实验2:
时间片轮转调度实验
取消task_1有关代码的注释,让task_1和task_2同时开始运行,观察实验结果——

1.红灯和绿灯同时闪烁

2.串口发送数据如下

可以发现串口发送的数据存在错位的情况,思考一下这是为什么?

实验3:
优先级调度实验
在创建task2的函数中修改task2的优先级

osThreadDef(task_2, task2_handler, osPriorityAboveNormal, 0, 128);
task_2Handle = osThreadCreate(osThread(task_2), NULL);

编译下载后发现只有红灯闪烁
串口发送数据如下

这个现象的原理是只有在同优先级的任务间才存在时间片轮转调度,当task2优先级高于task1时,系统会始终执行task2

再修改代码如下

void task2_handler(void const * argument)
{

  /* USER CODE BEGIN task1_handler */
  /* Infinite loop */
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);
		printf("this is task_2 running\r\n");
		osDelay(500);
  }
  /* USER CODE END task1_handler */
}

会发现任务交替运行
这就体现出了osDelay和普通的延时之间的区别
osDelay的原理是将一个任务修改到最低优先级(IDLE优先级),使任意一个就绪进程都可以抢占cpu

通过以上三个实验,我们学习了任务创建,时间片轮转调度和优先级调度的实践
实际上在rtos中有相当丰富的进程管理函数,包括获取进程id,设置进程优先级,设置进程优先级等(可以用来实现动态优先级算法
这里就不多加介绍了,有兴趣的可以自己去了解

最后附上推荐教材(官方文档的中文翻译版)

《FreeRTOS实时内核使用指南》:
链接:https://pan.baidu.com/s/1qouYjnNSsa0u6KxI-0jMRQ
提取码:al12

posted @ 2020-01-24 11:26  sasasatori  阅读(1177)  评论(3编辑  收藏  举报