FreeRTOSFreeRTOS学习笔记— 开始创建任务并测试任务代码
Posted 果果小师弟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FreeRTOSFreeRTOS学习笔记— 开始创建任务并测试任务代码相关的知识,希望对你有一定的参考价值。
现在开始使用FreeRTOS,对于代码的编写很简单,FreeRTOS是可裁剪的,想要什么不想要什么直接在FreeRTOSConfig.h文件中取消和打开相关的宏定义就可以了。
下面是小编的FreeRTOSConfig.h文件配置,你可以作为参考。
FreeRTOSConfig.h
/*
* FreeRTOS V202107.00
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. *
* http://www.FreeRTOS.org
* http://aws.amazon.com/freertos
* See http://www.freertos.org/a00110.html
* 1 tab == 4 spaces!
*/
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* 确保stdint仅由编译器使用,而不是由汇编程序使用.针对不同的编译器调用不同的stdint.h文件 */
#if defined (__ICCARM__)|| defined(__CC_ARM)|| defined(__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
/***************************************************************************************************************/
/* FreeRTOS基础配置配置选项 */
/***************************************************************************************************************/
#define configUSE_TIME_SLICING 1 //1使能时间片调度(默认式使能的)
/*
某些运行 FreeRTos的硬件有两种方法选择下一个要执行的任务:
*通用方法和特定于硬件的方法(以下简称“特殊方法〃)。
*通用方法
1.configUSE_PORT_OPTIMISED_TASK_SELECTION0或者硬件不支持这种特殊方法
2.可以用于所有FreeRTOS支持的硬件
3.完全用C实现,效率略低于特殊方法
4.不强制要求限制最大可用优先级数目
*特殊方法
1.必须将 configUSE PORT OPTIMISED TASK SELECTIoN设置为1。
2.依赖一个或多个特定架构的汇编指令(一般是类似计算前导零[CL2]指令)
3.比通用方法更高效
4.一般强制限定最大可用优先级数目为32般是硬件计算前导零指令,如果所使用的,MCU没有这些硬件指令的话此宏应该设置为0!
*/
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 /*1启用特殊方法来选择下一个要运行的任务
一般是硬件计算前导零指令,如果所使用的
MCU没有这些硬件指令的话此宏应该设置为0!*/
#define configUSE_TICKLESS_IDLE 0 //置 1:使能低功耗 tickless 模式;置 0:保持系统节拍(tick)中断一直运行
#define configUSE_QUEUE_SETS 1 //为1时启用队列
#define configUSE_PREEMPTION 1 //1使用抢占式内核,0使用协程
/*
* 写入实际的CPU内核时钟频率,也就是CPU指令执行频率,通常称为Fc1k Fc1k
* 为供给CPU内核的时钟信号,我们所说的CPU主频为 XX MHZ,
* 就是指的这个时钟信号,相应的,1/Fc1k即为cpu时钟周期;
*/
#define configCPU_CLOCK_HZ ( SystemCoreClock ) //设置CPU 时钟频率
//RTOS 系统节拍中断的频率。即一秒中断的次数,每次中断 RTOS 都会进行任务调度,这里设置为1000,周期就是1ms
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES ( 32 ) //最大任务优先级默认为5,最大支持256个优先级
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 ) //空闲任务使用的堆栈大小
#define configMAX_TASK_NAME_LEN ( 16 ) //任务名字字符串长度,这里定义的长度包括字符串结束符’\\0’
#define configUSE_TRACE_FACILITY 1
#define configUSE_16_BIT_TICKS 0 //系统节拍计数器变量数据类型,1 表示为 16 位无符号整形,0 表示为 32 位无符号整形
#define configIDLE_SHOULD_YIELD 1 //为1时 空闲任务放弃CPU使用权给其他同优先级的用户任务
#define configUSE_MUTEXES 1 //为1时使用互斥信号量
#define configQUEUE_REGISTRY_SIZE 8 //不为0时表示启用队列记录,具体的值是可以
#define configCHECK_FOR_STACK_OVERFLOW 0 /*大于0时启用堆栈溢出检测功能,如果使用此功能
用户必须提供一个栈溢出钩子函数,如果使用的话
此值可以为1或者2,因为有两种栈溢出检测方法。*/
#define configUSE_RECURSIVE_MUTEXES 1 //为1时使用递归互斥信号量
#define configUSE_MALLOC_FAILED_HOOK 0 //1使用内存申请失败钩子函数
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 1 //为1时使用计数信号量
#define configGENERATE_RUN_TIME_STATS 0
/***************************************************************************************************************/
/* FreeRTOS与内存申请有关配置选项 */
/**************************************************************************************************************/
#define configSUPPORT_DYNAMIC_ALLOCATION 1 /*支持动态内存申请,一般在系统中采用的内存分配都是动态内存分配。
FreeRTOS同时也支持静态分配内存,但是常用的就是动态分配了。*/
#define configSUPPORT_STATIC_ALLOCATION 0 /* 1支持静态内存
如果使用静态方法的话需要自己实现两个函数vApplicationGetIdleTaskMemory()和
vApplicationGetTimerTaskMemory()。通过这两个函数来给空闲任务和定时器服务任务的任务堆
栈和任务控制块分配内存,*/
#define configTOTAL_HEAP_SIZE ((size_t)(20*1024)) //系统所有总的堆大小
/***************************************************************************************************************/
/* FreeRTOS与钩子函数有关的配置选项 */
/***************************************************************************************************************/
/*
* 置1:使用空闲钩子(Idle hook类似于回调函数);置0:忽略空闲钩子
* 空闲任务钩子是一个函数,这个函数由用户来实现,
* FreeRTOS规定了函数的名字和参数:void vApplicationIdleHook(void),
* 这个函数在每个空闲任务周期都会被调用
* 对于已经删除的RTOS任务,空闲任务可以释放分配给它们的堆栈内存。
* 因此必须保证空闲任务可以被CPU执行
* 使用空闲钩子函数设置CPU进入省电模式是很常见的
* 不可以调用会引起空闲任务阻塞的API函数
*/
#define configUSE_IDLE_HOOK 0 //空闲钩子
/*
* 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子
*
* 时间片钩子是一个函数,这个函数由用户来实现,
* FreerToS规定了函数的名字和参数:void vApplicationTickHook(void)
* 时间片中断可以周期性的调用
* 函数必须非常短小,不能大量使用堆栈,
* 不能调用以〃FromIsR"或"FROM ISR"结尾的API函数
*/
#define configUSE_TICK_HOOK 0 //滴答定时器钩子
/***************************************************************************************************************/
/* FreeRTOS与运行时间和任务状态收集有关的配置选项 */
/***************************************************************************************************************/
#define configGENERATE_RUN_TIME_STATS 0 //为1时 启用运行时间统计功能
#define configUSE_TRACE_FACILITY 1 //为1时 启用可视化跟踪调试
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 /* 宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数
* prvWriteNameToBuffer(),
* vTaskList(),
* vTaskGetRunTimeStats()
*/
/***************************************************************************************************************/
/* FreeRTOS与协程有关的配置选项 */
/***************************************************************************************************************/
#define configUSE_CO_ROUTINES 0 //为1时启用协程,启用协程以后必须添加文件croutine.c
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) //协程的有效优先级数目
/***************************************************************************************************************/
/* FreeRTOS与软件定时器有关的配置选项 */
/***************************************************************************************************************/
#define configUSE_TIMERS 1 //为1时启用软件定时器
#define configTIMER_TASK_PRIORITY ( 2 ) //软件定时器优先级
#define configTIMER_QUEUE_LENGTH 10 //软件定时器队列长度
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 ) //软件定时器任务堆栈大小
/***************************************************************************************************************/
/* FreeRTOS可选函数配置选项 */
/***************************************************************************************************************/
#define INCLUDE_vTaskPrioritySet 1 //如果要使用函数 vTaskPrioritySet() 的话需要将宏 vTaskPrioritySet 定义为1。
#define INCLUDE_uxTaskPriorityGet 1 //如果要使用函数 uxTaskPriorityGet() 的话需要将宏 uxTaskPriorityGet 定义为1。
#define INCLUDE_vTaskDelete 1 //如果要使用函数 vTaskDelete() 的话需要将宏 vTaskDelete 定义为1。
#define INCLUDE_vTaskCleanUpResources 1 //如果要使用函数 vTaskCleanUpResources() 的话需要将宏 vTaskCleanUpResources 定义为1。
#define INCLUDE_vTaskSuspend 1 //如果要使用函数 vTaskSuspend() 的话需要将宏 vTaskSuspend 定义为1。
#define INCLUDE_vTaskDelayUntil 1 //如果要使用函数 vTaskDelayUntil() 的话需要将宏 vTaskDelayUntil 定义为1
#define INCLUDE_vTaskDelay 1 //如果要使用函数 vTaskDelay() 的话需要将宏 INCLUDE_vTaskDelay 定义为1
#define INCLUDE_xTaskGetSchedulerState 1 //如果要使用函数 xTaskGetSchedulerState() 的话需要将宏 xTaskGetSchedulerState 定义为1
/***************************************************************************************************************/
/* FreeRTOS与中断有关的配置选项 */
/***************************************************************************************************************/
#ifdef __NVIC_PRIO_BITS
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4 /* 15 priority levels */
#endif
/* 中断最低优先级.而 FreeRTOS 的任务优先级是,任务优先级数值越小,任务优先级越低。*/
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
/* 系统可管理的最高中断优先级。优先级越高,数值越低。 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
/* 内核端口层本身使用的中断优先级。这些是所有Cortex-M端口的通用端口,不依赖于任何特定的库函数。 */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!! See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* 断言 */
#define vAssertCalled(char,int) printf("Error:%s,%d\\r\\n",char,int)
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
/****************************************************************
FreeRTOS 与中断服务函数有关的配置选项
****************************************************************/
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
//#define xPortSysTickHandler SysTick_Handler
#endif
在main()
函数中,我们直接可以对FreeRTOS
进行创建任务操作,因为FreeRTOS
会自动帮我们做初始化的事情,比如初始化堆内存。FreeRTOS
的简单方便是在别的实时操作系统上都没有的,像RT-Thread
,需要做很多事情,具体可以看野火出版的一本书《RT_Thread
内核实现与应用开发实战一基于STM32》;华为 LiteS也需要我们用户进行初始化内核,具体可以看野火出版的另一本书籍华为 LiteOS
《华为 LiteOS
内核实现与应用开发实战一基于STM32》。
这种简单的特点使得FreeRTOS
在初学的时候变得很简单,我们自己在main();
函数中直接初始化我们的板级外设,然后进行任务的创建即可—xTaskCreate()
,在任务创建中,FreeRTOS
会帮我们进行一系列的系统初始化,在创建任务的时候,会帮我们初始化堆内存。
main.c
1、使用动态任务创建函数xTaskCreate();
创建一个开始任务start_task();
然后再开始任务中创建任务一task2_task();
和任务二task2_task();
2、开始任务start_task();
在执行完创建两个后就马上删除自己这个开始任务,自己删自己,铁锅炖自己。
3、之后系统就开始不断地执行task2_task();
和任务二task2_task();
4、在任务一中我们写了一个按键扫描函数key_Scan()
,不断检测按下的键值赋值给kValue
,因为kValue
定义的是一个全局变量,所以可以在任务二函数中使用这个Value
值。
5、在任务二中通过判断kValue
值来执行点灯关灯的操作。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "lcd.h"
#include "SEGGER_RTT.h"
#include "math.h"
#include "FreeRTOS.h"
#include "task.h"
#define START_TASK_PRIO 1 //任务优先级
#define TASK1_TASK_PRIO 2 //任务优先级
#define TASK2_TASK_PRIO 3 //任务优先级
#define START_STK_SIZE 128 //任务堆栈大小
#define TASK1_STK_SIZE 128 //任务堆栈大小
#define TASK2_STK_SIZE 128 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
TaskHandle_t Task1Task_Handler; //任务句柄
TaskHandle_t Task2Task_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数声明
void task1_task(void *pvParameters); //任务函数声明
void task2_task(void *pvParameters); //任务函数声明
int main(void)
{
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(8, 336, 2, 7); //设置时钟,168Mhz
delay_init(168); //初始化延时函数
KEY_Init();
LED_Init(); //初始化LED端口
//动态创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
//开启任务调度
/* 启动RTOS,其实就是启动“任务管理器”,启动之后任务管理器就开始调度线程,
* 此时pc(程序计数器)就会指向某线程的指令,开始多线程并发运行。
* 如果没有创建多线程的话,那就只有一个线程。*/
vTaskStartScheduler();
/* 由于调用了vTaskStartScheduler之后,PC就指向了线程中的指令,因此vTaskStartScheduler后面代码
* 并不会被CPU执行,所以vTaskStartScheduler后的代码没有意义。 */
while(1)
{
//这里的代码不会被执行,写了也没用
}
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建TASK1任务
xTaskCreate((TaskFunction_t )task1_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&Task1Task_Handler);
//创建TASK2任务
xTaskCreate((TaskFunction_t )task2_task,
(const char* )"task2_task",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_TASK_PRIO,
(TaskHandle_t* )&Task2Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/* 检测k1、k2按键是否按下,并返回各自的键值,这里将k1的键值设定为1,k2的设定为2 */
uint8_t key_Scan(void)
{
//没有key_up,会导致按下按键再松开之前,多次调用KEY_Scan时,每次都会检测到按键
//被按下了,有了key_up,第一次调用KEY_Scan时返回键值,后面几次调用时会通过
//key_up检测到已经返回过一次键值了,不再返回键值
static uint8_t key_up = 0;
int key1 = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4);
int key2 = HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3);
if((key_up == 0) && (key1 == 0 || key2 == 0)) //检测到刚按下进入,如果是按住不放不会进入
{
delay_ms(10); //去抖动
key_up = 1; //设置标志位,表示按下
if(key1 == 0)return 1; //如果k1按下就返回1
else if(key2 == 0)return 2; //如果k2按下就返回2
}
else if(key1 == 1 && key2 == 1) key_up = 0; //按键松开,清标志位
return 0; //无按键按下或松开了时就返回0
}
uint8_t kValue = 0;
//task1任务函数
void task1_task(void *pvParameters)
{
for(;;)
{
kValue = key_Scan();
vTaskDelay(200);
}
}
//task2任务函数
void task2_task(void *pvParameters)
{
static uint8_t flag1 = 0;
static uint8_t flag2 = 0;
for(;;)
{
if(kValue == 1)
{
SEGGER_RTT_printf(0, "k1 被按下了\\r\\n");
if(flag1 == 0) //灯关着就打开
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);
flag1 = 1;
}
else if(flag1 == 1) //开着就关闭
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);
flag1 = 0;
}
}
else if(kValue == 2)
{
SEGGER_RTT_printf(0, "k2 被按下了\\r\\n");
if(flag2 == 0) //灯关着就打开
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_SET);
flag2 = 1;
}
else if(flag2 == 1) //开着就关闭
{
HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_RESET);
flag2 = 0;
}
}
vTaskDelay(200);
}
}
试验现象
当我们拿到一个移植好FreeRTOS
的例程的时候,不出意外,你首先看到的是main
函数,当你认真一看main
函数里面只是创建并启动一些任务和硬件初始化。而系统初始化这些工作不需要我们实现,因为FreeRTOS
在我们使用创建与开启调度的时候就已经偷偷帮我们做完了,如果只是使用FreeRTOS
的话,无需关注FreeRTOS
API函数里面的实现过程,但是为了避免出现问题,还是建议需要深入了解FreeRTOS
然后再去使用。
总结:
一旦开启任务调度。启动RTOS
,其实就是启动“任务管理器”,启动之后任务管理器就开始调度线程,此时PC(程序计数器)就会指向某线程的指令,开始多线程并发运行。
如果没有创建多线程的话,那就只有一个线程。
vTaskStartScheduler();
由于调用了vTaskStartScheduler
之后,PC
就指向了线程中的指令,因此vTaskStartScheduler
后面代码并不会被CPU执行,所以vTaskStartScheduler
后的代码没有意义。
.....................;
.....................;
vTaskStartSche以上是关于FreeRTOSFreeRTOS学习笔记— 开始创建任务并测试任务代码的主要内容,如果未能解决你的问题,请参考以下文章
FreeRTOSFreeRTOS学习笔记(11)— FreeRTOS的线程管理定时器管理(CMSIS_API)
FreeRTOSFreeRTOS学习笔记— 中断+临界区的保护
FreeRTOSFreeRTOS学习笔记— 列表和列表项(链表和节点)
FreeRTOSFreeRTOS学习笔记— 手写FreeRTOS双向链表/源码分析