STM32CubeMX学习笔记22---FreeRTOS(任务创建和删除)

 一、FreeRTOS简介

FreeRTOS 是一个可裁剪、可剥夺型的多任务内核,而且没有任务数限制。FreeRTOS 提供了实时操作系统所需的所有功能,包括资源管理、同步、任务通信等。

FreeRTOS 是用 C 和汇编来写的,其中绝大部分都是用 C 语言编写的,只有极少数的与处理器密切相关的部分代码才是用汇编写的,FreeRTOS 结构简洁,可读性很强!最主要的是非常适合初次接触嵌入式实时操作系统学生、嵌入式系统开发人员和爱好者学习。

最新版本 V9.0.0(2016年),尽管现在 FreeRTOS 的版本已经更新到 V10.4.1 了,但是我们还是选择 V9.0.0,因为内核很稳定,并且网上资料很多,因为 V10.0.0 版本之后是亚马逊收购了FreeRTOS之后才出来的版本,主要添加了一些云端组件,一般采用 V9.0.0 版本足以。

FreeRTOS官网:http://www.freertos.org/
代码托管网站:https://sourceforge.net/projects/freertos/files/FreeRTOS/

RTOS的多个工作流如下图示:

RTOS工作原理如下图示:

RTOS通用组件如下图示:

RTOS的特点:

  • 更好的事件实时处理机制
  • 更高效利用CPU资源
  • 通用的任务管理框架

FreeRTOS介绍

FreeRTOS是RTOS的一种,尺寸非常小,可运行于微控制器上。微控制器是尺寸小,资源受限的处理器,它在单个芯片上包含了处理器本身、用于保存要执行的程序的只读存储器(ROM或Flash)、所执行程序需要的随机存取存储器(RAM),一般情况下程序直接从只读存储器执行

微控制器用于深度嵌入式应用,一般都有非常明确、专门的工作。尺寸的限制以及专用的终端应用等性质,令其很少能使用完整的RTOS实现。因此FreeRTOS仅为内核提供了实时调度功能、任务间通信、时序和同步原语。更准确地说,它是一个实时内核,或实时执行器。命令控制台界面、网络栈等额外的功能可作为附加组件

在实际使用FreeRTOS的时候我们需要根据自已的需求来配置FreeRTOS,不同架构的MCU在使用的时候配置也不同,下面介绍FreeRTOS配置文件详解

  • 内核配置一

  • 内核配置二

  • 内存管理

  • 任务运行信息获取配置

  • 软件定时器

  • 中断优先级

  • 函数Include配置

二、硬件设计

本实验通过freertos创建两个任务来分别控制LED2和LED3的亮灭,需要用到的硬件资源

  • LED2和LED3指示灯
  • 串口

三、STM32CubeMX设置

1、基础设置

  • RCC设置外接HSE,时钟设置为72MHz;TIM3的时钟挂载在APB1 Time Clocks上为72MHz。
  • 打开串口中断,波特率设置为115200. 
  • PB5/PE1设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平。

2、FreeRtos设置

 (1)、在 System Core 中选择 SYS ,对 Timebase Source 进行设置,选择 TIM1 作为HAL库的时基(除了 SysTick 外都可以)。

在基于STM32 HAL的项目中,一般需要维护的 “时基” 主要有2个:

  1. HAL的时基,SYS Timebase Source
  2. OS的时基(仅在使用OS的情况下才考虑)

而这些 “时基” 该去如何维护,主要分为两种情况考虑:

裸机运行:
可以通过 SysTick(滴答定时器)或 (TIMx)定时器 的方式来维护 SYS Timebase Source,也就是HAL库中的 uwTick,这是HAL库中维护的一个全局变量。在裸机运行的情况下,我们一般选择默认的 SysTick(滴答定时器) 方式即可,也就是直接放在 SysTick_Handler() 中断服务函数中来维护。

带OS运行:
前面提到的 SYS Timebase Source 是STM32的HAL库中的新增部分,主要用于实现 HAL_Delay() 以及作为各种 timeout 的时钟基准。

在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个 时基 一般也都是通过 SysTick(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用 SysTick(滴答定时器) 了。

如果共用SysTick,当我们在CubeMX中选择启用FreeRTOS之后,在生成代码时,CubeMX一定会报如下提示:

强烈建议用户在使用FreeRTOS的时候,不要使用 SysTick(滴答定时器)作为 “HAL的时基”,因为FreeRTOS要用,最好是要换一个!!!如果共用,潜在一定风险。 

 (2)、在 Middleware 中选择 FREERTOS 设置,并选择 CMSIS_V1 接口版本

CMSIS是一种接口标准,目的是屏蔽软硬件差异以提高软件的兼容性。RTOS v1使得软件能够在不同的实时操作系统下运行(屏蔽不同RTOS提供的API的差别),而RTOS v2则是拓展了RTOS v1,兼容更多的CPU架构和实时操作系统。因此我们在使用时可以根据实际情况选择,如果学习过程中使用STM32F1、F4等单片机时没必要选择RTOS v2,更高的兼容性背后时更加冗余的代码,理解起来比较困难。

(3)、在 Config parameters 进行具体参数配置。

Kernel settings:

  • USE_PREEMPTION Enabled:RTOS使用抢占式调度器;Disabled:RTOS使用协作式调度器(时间片)。
  • TICK_RATE_HZ: 值设置为1000,即周期就是1ms。RTOS系统节拍中断的频率,单位为HZ。
  • MAX_PRIORITIES: (默认):可使用的最大优先级数量。设置好以后任务就可以使用从0到(MAX_PRIORITIES - 1)的优先级,其中0位最低优先级,(MAX_PRIORITIES - 1)为最高优先级。
  • MINIMAL_STACK_SIZE:(默认128): 设置空闲任务的最小任务堆栈大小,以字为单位,而不是字节。如该值设置为128 Words,那么真正的堆栈大小就是 128*4 = 512 Byte。
  • MAX_TASK_NAME_LEN:(默认16): 设置任务名称最大长度。
  • IDLE_SHOULD_YIELD: 默认Enabled 空闲任务放弃CPU使用权给其他同优先级的用户任务。
  • USE_MUTEXES:默认Enabled 为1时使用互斥信号量,相关的API函数会被编译。
  • USE_RECURSIVE_MUTEXES默认Enabled 为1时使用递归互斥信号量,相关的API函数会被编译。
  • USE_COUNTING_SEMAPHORES默认Enabled 为1时启用计数型信号量, 相关的API函数会被编译。
  • QUEUE_REGISTRY_SIZE(默认8) 设置可以注册的队列和信号量的最大数量,在使用内核调试器查看信号量和队列的时候需要设置此宏,而且要先将消息队列和信号量进行注册,只有注册了的队列和信号量才会在内核调试器中看到,如果不使用内核调试器的话此宏设置为0即可。
  • USE_APPLICATION_TASK_TAG(默认Disable) 为1时可以使用vTaskSetApplicationTaskTag函数。
  • ENABLE_BACKWARD_COMPATIBILITY(默认Enabled ) 为1时可以使V8.0.0之前的FreeRTOS用户代码直接升级到V8.0.0之后,而不需要做任何修改。
  • USE_PORT_OPTIMISED_TASK_SELECTION(默认Enabled ) FreeRTOS有两种方法来选择下一个要运行的任务,一个是通用的方法,另外一个是特殊的方法,也就是硬件方法,使用MCU自带的硬件指令来实现。STM32有计算前导零指令吗,所以这里强制置1。
  • USE_TICKLESS_IDLE 置1(Built in functionality enabled):使能低功耗tickless模式;置0:保持系统节拍(tick)中断一直运行。假设开启低功耗的话可能会导致下载出现问题,因为程序在睡眠中,可用ISP下载办法解决。
  • USE_TASK_NOTIFICATIONS(默认Enabled )为1时使用任务通知功能,相关的API函数会被编译。开启了此功能,每个任务会多消耗8个字节。
  • RECORD_STACK_HIGH_ADDRESS(默认Disable) 为1时栈开始地址会被保存到每个任务的TCB中(假如栈是向下生长的)。

Memory management settings:

  • Memory Allocation: Dynamic/Static 支持动态/静态内存申请
  • TOTAL_HEAP_SIZE默认,设置堆大小,如果使用了动态内存管理,FreeRTOS在创建 task, queue, mutex, software timer or semaphore的时候就会使用heap_x.c(x为1~5)中的内存申请函数来申请内存。这些内存就是从堆ucHeap[configTOTAL_HEAP_SIZE]中申请的。
  • Memory Management scheme: 内存管理策略 heap_4

Hook function related definitions:

  • USE_IDLE_HOOK: 默认, 置1:使用空闲钩子(Idle Hook类似于回调函数);置0:忽略空闲钩子。
  • USE_TICK_HOOK默认, 置1:使用时间片钩子(Tick Hook);置0:忽略时间片钩子。
  • USE_MALLOC_FAILED_HOOK默认, 使用内存申请失败钩子函数。
  • CHECK_FOR_STACK_OVERFLOW默认, 大于0时启用堆栈溢出检测功能,如果使用此功能用户必须提供一个栈溢出钩子函数,如果使用的话此值可以为1或者2,因为有两种栈溢出检测方法。

Run time and task stats gathering related definitions:

  • GENERATE_RUN_TIME_STATS: 启用运行时间统计功能。
  • USE_TRACE_FACILITY: 启用可视化跟踪调试。
  • USE_STATS_FORMATTING_FUNCTIONS: 与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数prvWriteNameToBuffer()、vTaskList()、vTaskGetRunTimeStats()。

Co-routine related definitions:

  • USE_CO_ROUTINES: 启用协程Enabled
  • MAX_CO_ROUTINE_PRIORITIES: 协程的有效优先级数目2

Software timer definitions:

  • USE_TIMERS: 启用软件定时器Enabled

Interrupt nesting behaviour configuration:

  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 中断最低优先级。
  • LIBRARY_LOWEST_INTERRUPT_PRIORITY: 系统可管理的最高中断优先级。
 (4)、创建任务Task,在 Tasks and Queues 进行配置。

 默认空闲任务是在系统无其它任务执行时执行。

 然后我们创建两个LED任务。

  • Task Name: 任务名称
  • Priority: 优先级,在 FreeRTOS 中,数值越大优先级越高,0 代表最低优先级
  • Stack Size (Words): 堆栈大小,单位为字,在32位处理器(STM32),一个字等于4字节,如果传入512那么任务大小为512*4字节
  • Entry Function: 入口函数
  • Code Generation Option: 代码生成选项
  • Parameter: 任务入口函数形参,不用的时候配置为0或NULL即可
  • Allocation: 分配方式:Dynamic 动态内存创建
  • Buffer Name: 缓冲区名称
  • Conrol Block Name: 控制块名称
(5)、输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码。

四、程序编程

生成的程序中的freertos.c文件下就可以看到STM32CubeMX已经创建了LED1和LED2两个任务的创建和调度,直接在任务中编写代码即可运行。以下为freertos.c原始文件:

void MX_FREERTOS_Init(void) {
 /* definition and creation of defaultTask */
  osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
  defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);

  /* definition and creation of LED1 */
  osThreadDef(LED1, LED1_Task02, osPriorityIdle, 0, 128);
  LED1Handle = osThreadCreate(osThread(LED1), NULL);

  /* definition and creation of LED2 */
  osThreadDef(LED2, LED2_Task03, osPriorityIdle, 0, 128);
  LED2Handle = osThreadCreate(osThread(LED2), NULL);

  /* USER CODE BEGIN RTOS_THREADS */
  /* add threads, ... */
  /* USER CODE END RTOS_THREADS */

}

/* USER CODE END Header_LED1_Task02 */
void LED1_Task02(void const * argument)
{
  /* USER CODE BEGIN LED1_Task02 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END LED1_Task02 */
}

/* USER CODE END Header_LED2_Task03 */
void LED2_Task03(void const * argument)
{
  /* USER CODE BEGIN LED2_Task03 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END LED2_Task03 */
}

该文件的顶端定义了任务的ID,任务的调用都和这个ID相关。

/* USER CODE END Variables */
osThreadId defaultTaskHandle;
osThreadId LED1Handle;
osThreadId LED2Handle;

如果用了time1作为时基,在main.c文件下可以看到调用了定时器中断回调函数,如果需要用其他定时器可以在这里添加定时器程序

(1)、任务创建 

任务创建的方式分为静态创建和动态创建,不同之处是利用不同的运行内存。

在创建之前需要引用osThreadDef定义一下任务

osThreadDef(name, thread, priority, instances, stacksz)
/// 创建一个具有函数、优先级和堆栈要求的线程定义。
/// \param         name         线程函数的名称。
/// \param         thread       任务函数名称。
/// \param         priority     线程函数的初始优先级。
/// \param         instances    可能的线程实例数。
/// \param         stacksz      线程函数的堆栈大小(以字节为单位)。

//例:
osThreadDef(LED1, LED1_Task02, osPriorityIdle, 0, 128);
//即创建了一个名为LED1的线程定义,调用函数为LED1_Task02。

使用动态/静态内存的方法创建一个任务。

函数osThreadCreate (const osThreadDef_t *thread_def, void *argument)
参数thread_def: 引用由osThreadDef定义的任务

argument: 任务入口函数形参
返回值成功返回任务ID,失败返回0

例:创建任务3(LED3_Task04)

//1,先宏定义任务ID
osThreadId LED3Handle;

//2、定义任务函数
void LED3_Task04(void const * argument);

//3、在void MX_FREERTOS_Init(void)中创建任务线程
void MX_FREERTOS_Init(void)
{
 //****省略*****//
//创建任务LED3
  osThreadDef(LED3, LED3_Task04, osPriorityIdle, 0, 128);
  LED3Handle = osThreadCreate(osThread(LED3), NULL);

}

//4、编写任务函数
void LED3_Task04(void const * argument)
{
  for(;;)
  {
	
		printf("this LED3 run \r\n");
        osDelay(800);
  }
}

//注,此任务仅作举例使用。
(2)、任务删除

删除任务。任务被删除后就不复存在,也不会再进入运行态。

函数 osThreadTerminate (osThreadId thread_id)
参数thread_id: 被删除任务的ID
返回值错误码(osStatus)

 例:

osThreadTerminate (LED1Handle); //彻底删除任务LED1Handle
(3)、任务延时函数
(1).osDelay

相对延时函数。用于阻塞延时,调用该函数后,任务将进入阻塞状态,进入阻塞态的任务将让出 CPU 资源。

函数osStatus osDelay (uint32_t millisec)
参数millisec: 毫秒数
返回值错误码

要想使用该函数必须在 Include parameters 中把 vTaskDelay 选择 Enabled 来使能。

例:

void vTaskA( void * pvParameters )
{
    while (1) 
    {
        // ...
        // 这里为任务主体代码
        // ...

        /* 调用相对延时函数,阻塞 1000 个 tick */ 
        osDelay( 1000 ); 
    }
}

(2)、osThreadTerminate

绝对延时函数。常用于较精确的周期运行任务,比如我有一个任务,希望它以固定频率定期执行,而不受外部的影响,任务从上一次运行开始到下一次运行开始的时间间隔是绝对的,而不是相对的。

函数osStatus osDelayUntil (uint32_t *PreviousWakeTime, uint32_t millisec)
参数PreviousWakeTime: 任务上一次离开阻塞态(被唤醒)的时刻。这个时刻被用作一个参考点来计算该任务下一次离开阻塞态的时刻

millisec: 毫秒数
返回值错误码

 要想使用该函数必须在 Include parameters 中把 vTaskDelayUntil 选择 Enabled 来使能。

例:

//注意:在使用的时候要将延时时间转化为系统节拍,在任务主体之前要调用延时函数。
void vTaskA( void * pvParameters ) 
{
    /* 用于保存上次时间。调用后系统自动更新 */ 
    static portTickType PreviousWakeTime; 
    /* 设置延时时间,将时间转为节拍数 */ 
    const portTickType TimeIncrement = pdMS_TO_TICKS(1000); 

    /* 获取当前系统时间 */ 
    PreviousWakeTime = osKernelSysTick();

    while (1)
    {
        /* 调用绝对延时函数,任务时间间隔为 1000 个 tick */ 
        osDelayUntil( &PreviousWakeTime,TimeIncrement ); 
 
        // ...
        // 这里为任务主体代码 
        // ...
 
    }
}

 在生成的两个LED任务中编写运行函数程序。任务1计数到10 时删除任务2

//任务LED1
void LED1_Task02(void const * argument)
{
  /* USER CODE BEGIN LED1_Task02 */
  /* Infinite loop */
	int i=0;
	
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5);   //LED1状态每500s翻转一次
		printf("this LED1 run %d\r\n",i);
		i++;
		if(i==10)
		{
			if(osThreadTerminate (LED2Handle)==0); //彻底删除任务LED2Handle
				printf("删除 LED2Handle\r\n");
		}
		

    osDelay(1000);
  }
  /* USER CODE END LED1_Task02 */
}

//任务LED2
void LED2_Task03(void const * argument)
{
  /* USER CODE BEGIN LED2_Task03 */
  /* Infinite loop */
  for(;;)
  {
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5);   //LED1状态每500s翻转一次
		printf("this LED2 run \r\n");
    osDelay(600);
  }
  /* USER CODE END LED2_Task03 */
}

五、下载验证

编译无误后下载到板子上,可以看到三个任务都能够正常运行,当任务1计数到10 时将任务2删除了之后,任务2不再继续运行

 六、参考文献

STM32CubeMX学习笔记(28)——FreeRTOS实时操作系统使用(任务管理)_stm32 rtos 任务固定时间运行-CSDN博客

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值