• 从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
    ");
    		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
    ");
    		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
    ");
    		osDelay(500);
      }
      /* USER CODE END task1_handler */
    }
    
    

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

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

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

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

  • 相关阅读:
    配置对即时负载的优化
    通过重组索引提高性能
    使用索引视图提高性能
    sqlcmd
    (转)使用SQLCMD在SQLServer执行多个脚本
    在SQLServer处理中的一些问题及解决方法 NEWSEQUENTIALID()
    java反射机制与动态代理
    天天用的开发环境,你真的了解吗?
    通过IP获取对应所在地的地址
    unity3d KeyCode各键值说明
  • 原文地址:https://www.cnblogs.com/sasasatori/p/12231964.html
Copyright © 2020-2023  润新知