ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

STM32下完成一个基于FreeRTOS的多任务程序

2020-12-02 14:32:56  阅读:274  来源: 互联网

标签:Task 函数 FreeRTOS 句柄 void STM32 任务 多任务


文章目录

本文章采用的开发板是野火stm32mini版,前面四个部分只是一些介绍内容,重点移植内容在后面。

一、了解FreeRTOS

  1. RTOS
    Real Time Operating System 实时操作系统。
  2. FreeRTOS
    FreeRTOS 是一款 “开源免费”的实时操作系统,遵循的是 GPLv2+的许可协议。
  3. FreeRTOS的编程风格
    ①FreeRTOS 的数据类型
    对标准 C 的数据类型进行了重定义。
    详细内容如下:
    新定义的数据类型实际的数据类型说明
    portCHARchar字符型
    ortSHORTshort短整型
    ortLONGlong长整型
    ortTickTypeunsigned short int或者unsigned int均用于定义系统时基计数器的值和阻塞时间的值。当 FreeRTOSConfig.h 头文件中的宏configUSE_16_BIT_TICKS 为 1 时,unsigned short int则为 16位,unsigned int则为 32位。
    ortBASE_TYPElong根据处理器的架构来决定是多少位的,如果是 32/16/8bit 的处理器则是 32/16/8bit 的数据类型。一般用于定义函数的返回值或者布尔类型。
    ②FreeRTOS的变量名
    定义变量的时候往往会把变量的类型当作前缀加在变量上。
    通常规则是char 型变量的前缀是 c,short 型变量的前缀是 s,long 型变量的缀是 l, portBASE_TYPE 类型,数据结构,任务句柄,队列句柄变量的前缀是 x。如果一个变量是无符号型的那么会有一个前缀 u,如果是一个指针变量则会有一个前缀 p。因此,当我们定义一个无符号的 char 型变量的时候会加一个 uc 前缀,当定义一个char 型的指针变量的时候会有一个 pc 前缀。
    ③FreeRTOS的函数名
    函数名包含了函数返回值的类型、函数所在的文件名和函数的功能,如果是私有的函数则会加一个 prv(private)的前缀。
    ④FreeRTOS的宏
    宏均是由大写字母表示,并配有小写字母的前缀,前缀用于表示该宏在个头文件定义。比如 configUSE_PREEMPTION(config就表示宏定义在FreeRTOSConfig.h中)

    小技巧:编写FreeRTOS代码的时候缩进最好不要采用tab键,使用空格,移植代码不容易出现出现格式问题。

二、使用Keil创建FreeRTOS 工程(不使用Free RTOS源码)

  1. 准备相关文件夹
    电脑上创建一个FreeRTOS(名称可以自己取)文件夹,然后再该文件夹下创建下面文件夹或文件

    件夹名称文件夹用途
    Doc存放工程的说明文件
    Project存放新建的工程文件
    User存放main.c和其他用户编写的程序
    freeRTOS/Demo存放板级支持包
    freeRTOS/License存放FreeRTOS组件
    freeRTOS/Source/include存放头文件
    freeRTOS/Source存放FreeRTOS内核源码
    freeRTOS/Source/protable/RVDS/ARM_CM3存放与处理器相关的接口文件(移植文件)
    freeRTOS/Source/protable/RVDS/ARM_CM4存放与处理器相关的接口文件(移植文件)
    freeRTOS/Source/protable/RVDS/ARM_CM7存放与处理器相关的接口文件(移植文件)
  2. Keil创建工程
    ①点击Project—>New uVision Project,输入工程名称(可以随便取名)
    ②选择处理器,我们选择 ARMCM3(ARMCM4 或 ARMCM7,根据自己的开发板进行选择)
    在这里插入图片描述
    ③Manage Run-Time Environment 选项框中选择CMSIS 栏中 CORE 和 Device 栏中 Startup
    在这里插入图片描述
    ④keil工程里面新建文件组和添加本地文件
    工程里面添加 User、freeRTOS/ports、freeRTOS/source 和 Doc 这几个文件组。
    文件组中添加本地文件,User中添加main.c,Doc中添加readme.txt。
    选择工程,右键选择Manage Project Items
    在这里插入图片描述

三、了解裸机系统与多任务系统

  1. 裸机系统
    裸机系统通常分成轮询系统前后台系统
    ①轮询系统
    在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情。只需要顺序执行代码且不需要外部事件来驱动的就能完成的事情。
    ②前后台系统
    在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断在这里我们称为前台,main 函数里面的无限循环我们称为后台。

  2. 多任务系统
    多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。在多任务系统中,任务跟中断一样,也具有优先级,优先级高的任务会被优先执行。当一个紧急的事件在中断被标记之后,如果事件对应的任务的优先级足够高,就会立马得到响应。多任务系统与前后台系统的区别在于对于事件的处理位置不同。

  3. 三种系统的对比

    模型事件响应事件处理特点
    轮询系统主程序主程序轮询响应事件,轮询处理事件
    前后台系统中断主程序实时响应事件,轮询处理事件
    多任务系统中断任务实时响应事件,实时处理事件

四、FreeRTOS的任务

  1. 任务的定义
    把整个系统分割成一个个独立的且无法返回的函数,这些函数我们称为任务。

  2. 创建任务
    ①定义任务栈
    每个任务都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于 RAM 中。

    #define TASK1_STACK_SIZE 128
    StackType_t Task1Stack[TASK1_STACK_SIZE];  
    #define TASK2_STACK_SIZE 128
    StackType_t Task2Stack[TASK2_STACK_SIZE];
    

    ②定义任务函数

     void delay (uint32_t count)
    {
    	for (; count!=0; count--);
    }
    /* 任务 1 */
    void Task1_Entry( void *p_arg ) 
    {
    	for ( ;; )
    	{
    		flag1 = 1;
    		delay( 100 );
    		flag1 = 0;
    		delay( 100 );
    	}
    }
    /* 任务 2 */
    void Task2_Entry( void *p_arg ) 
    {
    	for ( ;; )
    	{
    		flag2 = 1;
    		delay( 100 );
    		flag2 = 0;
    		delay( 100 );
    	}
    }
    

    ③定义任务控制块
    任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的栈指针,任务名称,任务的形参等内容。

    typedef struct tskTaskControlBlock
    {
    	volatile StackType_t *pxTopOfStack; /* 栈顶 */ 
    	ListItem_t xStateListItem; /* 任务节点 */ 
    	StackType_t *pxStack; /* 任务栈起始地址 */ 
    	/* 任务名称,字符串形式 */
    	char pcTaskName[ configMAX_TASK_NAME_LEN ];
    } tskTCB;
    typedef tskTCB TCB_t;//数据类型重定义
    

    ④实现任务创建函数

    /*
    任务的创建方法:动态创建,静态创建。
    动态创建时,任务控制块和栈的内存是创建任务时动态分配的,任务删除时,内存可以释放。
    静态创建时,任务控制块和栈的内存需要事先定义好,是静态的内存任务删除时,内存不能释放。
    此处是静态创建
    */
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )  
    TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, //任务入口,即任务的函数名称。
    const char * const pcName, //任务名称,字符串形式
    const uint32_t ulStackDepth,//任务栈大小,单位为字
    void * const pvParameters,//任务形参
    StackType_t * const puxStackBuffer,//任务栈起始地址
    TCB_t * const pxTaskBuffer ) //任务控制块指针
    {
    	TCB_t *pxNewTCB;
    	TaskHandle_t xReturn; //定义一个任务句柄 xReturn,任务句柄用于指向任务的 TCB
    	if ( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
    	{
    		pxNewTCB = ( TCB_t * ) pxTaskBuffer;
    		pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;
    		/* 创建新的任务 */
    		/*调用 prvInitialiseNewTask()函数,创建新任务
    		pxTaskCode:任务入口
    		pcName:任务名称,字符串形式
    		ulStackDepth:任务栈大小,单位为字
    		pvParameters:任务形参
    		&xReturn:任务句柄
    		pxNewTCB):任务栈起始地址
    		*/
    		prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, &xReturn,pxNewTCB);
    	 }
    	else
    	{
    		xReturn = NULL;
    	}
    	/* 返回任务句柄,如果任务创建成功,此时 xReturn 应该指向任务控制块 */
    	return xReturn; //返回任务句柄,如果任务创建成功,此时 xReturn 应该指向任务控制块,xReturn 作为形参传入到 prvInitialiseNewTask 函数
    } 
    #endif /* configSUPPORT_STATIC_ALLOCATION */
    
  3. 实现就绪列表
    ①定义就绪列表

    List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
    

    ②就绪列表初始化

    void prvInitialiseTaskLists( void )
    {
    	UBaseType_t uxPriority; 
    	for ( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES;uxPriority++ )
    	{
    		vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
    	}
    }
    

    ③将任务插入到就绪列表

    1 /* 初始化与任务相关的列表,如就绪列表 */ 
    prvInitialiseTaskLists(); 
    Task1_Handle = /* 任务句柄 */
    xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */
    (char *)"Task1", /* 任务名称,字符串形式 */
    (uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */
    (void *) NULL, /* 任务形参 */
    (StackType_t *)Task1Stack, /* 任务栈起始地址 */
    (TCB_t *)&Task1TCB ); /* 任务控制块 */
    /* 将任务添加到就绪列表 */ 
    vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
    
  4. 实现调度器
    调度器是操作系统的核心,其主要功能是用于实现任务的切换,即从就绪列表里面找到优先级最高的任务,然后去执行该任务。
    ①启动调度器

    void vTaskStartScheduler( void )
    {
    	/* 手动指定第一个运行的任务 */
    	pxCurrentTCB = &Task1TCB; 
    	//的全局指针,用于指向当前正在运行或者即将要运行的任务的任务控制块
    	/* 启动调度器 */
    	if ( xPortStartScheduler() != pdFALSE )
    	{
    		/* 调度器启动成功,则不会返回,即不会来到这里 */ 
    	}
    }
    

    ②任务切换

     void vTaskSwitchContext( void )
    {
    	/* 两个任务轮流切换 */
    	if ( pxCurrentTCB == &Task1TCB ) //如果当前任务为任务 1,则把下一个要运行的任务改为任务 2
    	{
    		pxCurrentTCB = &Task2TCB;
    	}
    	else 如果当前任务为任务 2,则把下一个要运行的任务改为任务 1
    	{
    		pxCurrentTCB = &Task1TCB;
    	}
    }
    

    当前任务1和任务2之间不存在优先级的,所以此处任务切换是采用的轮流切换的方式。

五、移植FreeRTOS到STM32

  1. 获取 STM32 的裸机工程模板
    已建好的一个基于固件库的STM32工程。
  2. 下载 FreeRTOS V9.0.0 源码
    FreeRTOS 的源码获取地址:
    https://sourceforge.net/projects/freertos/files/FreeRTOS/
  3. FreeRTOS源码文件的介绍
    在这里插入图片描述
    在这里插入图片描述
  4. 往裸机工程添加 FreeRTOS 源码
    添加最简的FreeRTOS源码方法
    ①在STM32裸机工程模板根目录下新建一个文件夹 ,命名为“FreeRTOS”
    ②在FreeRTOS文件夹下新建两个空文件夹,分别命名为“src”与“port”,src 文件夹用于保存 FreeRTOS 中的核心源文件(‘.c 文件’),port 文件夹用于保存内存管理以及处理器架构相关代码
    ③FreeRTOS V9.0.0 源码的部分文件复制到STM32裸机工程下的FreeRTOS文件
    在这里插入图片描述
    在这里插入图片描述
    “FreeRTOSv9.0.0\ FreeRTOS\Source”目录下找到“include”文件夹,直接复制到FreeRTOS文件夹
    在这里插入图片描述
    直接拷贝整个FreeRTOS源码到STM32裸机工程
  5. 添加 FreeRTOS 源码到工程组文件夹
    选中工程,右键选择Manage Project Items
    在这里插入图片描述
    添加头文件路径
    在这里插入图片描述
    编译出现错误
    在这里插入图片描述
    解决方法
    在D:\FreeRTOSv9.0.0\FreeRTOS\Demo\CORTEX_STM32F103_Keil(该路径是我电脑路径,你需要找到你下载源码所放置的位置)中找到FreeRTOSConfig.h复制到STM32工程中的FreeRTOS文件夹。

六、实现多任务程序

  1. 创建任务句柄

    static TaskHandle_t AppTaskCreate_Handle = NULL;
    /* LED任务句柄 */
    static TaskHandle_t LED_Task_Handle = NULL;
    /* 串口任务句柄 */
    static TaskHandle_t USART_Task_Handle = NULL;
    /* 温度任务句柄 */
    static TaskHandle_t Temperature_Task_Handle = NULL;
    
  2. 创建任务

    /* 创建LED_Task任务 */
      xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
                  (const char*    )"LED_Task",/* 任务名字 */
                  (uint16_t       )512,   /* 任务栈大小 */
                  (void*          )NULL,	/* 任务入口函数参数 */
                  (UBaseType_t    )2,	    /* 任务的优先级 */
                  (TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */
    							
    	/* 创建USART_Task任务 */
      xTaskCreate((TaskFunction_t )USART_Task, /* 任务入口函数 */
                  (const char*    )"USART_Task",/* 任务名字 */
                  (uint16_t       )512,   /* 任务栈大小 */
                  (void*          )NULL,	/* 任务入口函数参数 */
                  (UBaseType_t    )2,	    /* 任务的优先级 */
                  (TaskHandle_t*  )&USART_Task_Handle);/* 任务控制块指针 */
    							
    	/* 创建Temperature_Task任务 */
      xTaskCreate((TaskFunction_t )Temperature_Task, /* 任务入口函数 */
                  (const char*    )"Temperature_Task",/* 任务名字 */
                  (uint16_t       )512,   /* 任务栈大小 */
                  (void*          )NULL,	/* 任务入口函数参数 */
                  (UBaseType_t    )2,	    /* 任务的优先级 */
                  (TaskHandle_t*  )&Temperature_Task_Handle);/* 任务控制块指针 */
    
  3. 任务功能函数

    static void LED_Task(void* parameter)
    {	
        while (1)
        {
    				printf("LED is ON!\n");
            LED1_ON;
            vTaskDelay(500);   /* 延时500个tick */
            
    				printf("LED is OFF!\n");
            LED1_OFF;     
            vTaskDelay(500);   /* 延时500个tick */		 		
        }
    }
    static void USART_Task(void* pvParameters)
    {
    	while(1)
    	{
    		printf("Hello Windows!\n");
    		vTaskDelay(1000);
    	}
    	
    }
    static void Temperature_Task(void* pvParameters)
    {
    	//具体功能还没有实现,此处采用一个输出语句来表明进行该项任务
    	while(1)
    	{
    		printf("检测温度!\n");
    		vTaskDelay(1000);
    	}
    }
    
    
  4. main函数

    /*****************************************************************
      * @brief  主函数
      * @param  无
      * @retval 无
      * @note   第一步:开发板硬件初始化 
                第二步:创建APP应用任务
                第三步:启动FreeRTOS,开始多任务调度
      ****************************************************************/
    int main(void)
    {	
      BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
    
      /* 开发板硬件初始化 */
      BSP_Init();
       /* 创建AppTaskCreate任务 */
      xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                            (const char*    )"AppTaskCreate",/* 任务名字 */
                            (uint16_t       )512,  /* 任务栈大小 */
                            (void*          )NULL,/* 任务入口函数参数 */
                            (UBaseType_t    )1, /* 任务的优先级 */
                            (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
      /* 启动任务调度 */           
      if(pdPASS == xReturn)
        vTaskStartScheduler();   /* 启动任务,开启调度 */
      else
        return -1;  
      
      while(1);   /* 正常不会执行到这里 */    
    }
    
  5. 编译烧录

  6. 使用串口来验证结果
    在这里插入图片描述
    在这里插入图片描述整个工程代码百度网盘链接:
    https://pan.baidu.com/s/1_Vs0Yl2HkydNiAfphZDAwg
    提取码:m63f

七、总结

本文章的重点内容是FreeRTOS的移植和多任务的实现。前面内容只是对整个内容的一些介绍,不想了解也没什么关系。只是可能在后面代码部分,看的不是很懂。不清楚每个部分具体是完成一些什么操作。本过程初始化函数是必要的,一定不要忘记添加。特别是对串口的初始化,如果没有初始化,程序可能不会报错,但是,使用串口调试助手进行数据接收就什么也不会显示。程序中的printf函数并不是指的C程序中的printf函数,而是串口中重新定义的函数,不要混淆了。

八、参考资料

野火FreeRTOS内核实现与应用开发实战.pdf
下载地址:野火官方产品资料下载

标签:Task,函数,FreeRTOS,句柄,void,STM32,任务,多任务
来源: https://blog.csdn.net/qq_43279579/article/details/110391791

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有