一.概要

FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。
由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行。

二.什么是实时操作系统

操作系统是一个控制程序,作为硬件和应用程序之间的桥梁,主要是和硬件打交道,负责协调分配计算资源和内存资源给不同的应用程序使用,并防止系统出现故障。面对来自不同应用程序的大量且互相竞争的资源请求,操作系统通过一个调度算法和内存管理算法尽可能把资源公平且有效率地分配给不同的程序。应用程序则通过调用操作系统提供的API接口获得相应资源完成指定的任务。

实时操作系统(RTOS-Real Time Operating System)中实时(Real Time)指的是任务(Task)或者说实现一个功能的线程(Thread)必须在给定的时间(Deadline)内完成。

三.FreeRTOS的特性

具有抢占式或者合作式的实时操作系统内核
功能可裁剪,最小占用10kB左右rom空间,0.5kB ram空间
灵活的任务优先级分配
具有低功耗模式
有互斥锁、信号量、消息队列等功能
运行过程可追踪
支持中断嵌套

四.FreeRTOS的任务详解

FreeRTOS的核心是任务调度器(Task Scheduler),它负责按照一定的调度策略将任务分配给处理器执行。每个任务都是一个独立的函数,可以有不同的优先级和堆栈大小。任务调度器根据任务的优先级和调度策略决定哪个任务被执行。

下图就是任务调度简单介绍
在这里插入图片描述

下面代码就是个简单的示例代码,通过调用osThreadCreate函数创建了两个任务vTaskLED(驱动LED灯灭)和vTaskLEDFlash(驱动LED灯亮)。在main函数中,通过调用vTaskStartScheduler函数启动了实时系统,使得任务可以被调度执行,由于两个任务都能不断运行,所以能驱动LED灯闪烁。

//LED熄灭
void vTaskLED(void *pvParameters)
{
    while (1)
    {
        /* LED OFF */
        gpio_bit_set(GPIOB, GPIO_PIN_4);
        vTaskDelay(150/ portTICK_RATE_MS);
    }
}
//LED点亮
void vTaskLEDFlash(void *pvParameters)
{
    while (1)
    {
         /* LED ON */
        gpio_bit_reset(GPIOB, GPIO_PIN_4);
        vTaskDelay(100/ portTICK_RATE_MS);
    }
}

1.任务函数定义

无论采用何种方法创建任务,均需要用到任务函数。FreeRTOS 规定任务函数的返回值必须为void,而且带有一个void型指针参数。

void vTaskLED(void *pvParameters)
{
    while (1)
    {
        /* LED OFF */
        gpio_bit_set(GPIOB, GPIO_PIN_4);
        vTaskDelay(150/ portTICK_RATE_MS);
    }
}

GD32F103C8T6单片机只有一个内核,那怎么让多个人同时干活呢?其实每个子任务虽然都是死循环,但并不是每个子任务一直都在执行,每个子任务在执行期间,可能需要延时,这边就通过 vTaskDelay(100/ portTICK_RATE_MS),等待100ms,单片机就可以停止此任务,然后切换到其它任务执行,这样看起来就是多个人在同时干活了。

2.任务的创建

xTaskCreate(vTaskLED, "Task LED", 10, NULL, 1, NULL);//创建一个任务,任务名,任务函数,优先级,堆栈大小

xTaskCreate函数中的第1个参数(vTaskLED)是任务函数:
任务函数是任务执行的代码,它应该是一个无返回值的函数,其参数是一个可选的指针,可以用来传递任何需要的数据,我们代码中的任务就是控制GPIO输出高或者低电平。

xTaskCreate函数中的第2个参数(“Task LED”)是任务名称:
这是一个字符串。

xTaskCreate函数中的第3个参数(10)是任务栈大小定义:
任务栈大小指定了任务可以使用多少RAM来存储局部变量和其他临时数据。这个大小应该足够大,能够容纳任务在执行期间可能出现的最大需求,一般代表多个字,如果是10,代表40字节,一般我们可以填100。

xTaskCreate函数中的第4个参数(NULL)是指向任务参数的指针:
这个参数可以是任何类型,它将传递给任务。

xTaskCreate函数中的第5个参数(1)是任务优先级:
任务优先级决定了任务在操作系统调度下的执行顺序。数值越大,优先级越高。

xTaskCreate函数的第6个参数(NULL)是任务句柄:
任务句柄是一个指针,可以用来引用已经创建的任务,以便可以在任务创建后对其进行操作,例如删除、挂起、恢复等。

3.任务的调度原理

FreeRTOS默认使用抢占式调度策略,对同等优先级的任务使用时间片轮询调度,时间片轮询就是可轮流享有相同的单片机时间(可设置),一个时间片等于SysTick中断周期。
抢占式调度是指调度器始终运行优先级最高且处于可运行状态的任务,无论任务何时可以运行。如在中断服务函数中更改了优先级最高且可运行的任务,调度器会停止当前执行的低优先级任务,并启动高优先级任务。
刚才程序中的vTaskDelay是通过将当前任务加入到延时列表中,并设置一个定时器来在指定的延时时间,延时时间过去之后将任务从延时列表中移除并将其设置为就绪状态。这样当定时器触发时,任务重新被加入到就绪列表中,等待被调度器再次调度执行。

下面代码截图就是操作系统底层寻找就绪任务的最高优先级,获取优先级最高的就绪任务的 TCB(任务控制块),然后更新到 pxCurrentTCB ,当前运行的任务只可能有一个,因此pxCurrentTCB只是单个TCB_t指针,它始终指向当前运行的任务。通过xPortPendSVHandler(PendSV_Handler)函数实现调度。
PendSV_Handler 是 ARM Cortex-M 处理器中的一个特殊的中断处理函数,用于处理挂起 PendSV(Pending Supervisor Call)中断。PendSV 中断是 Cortex-M 架构中的一种特殊的软件中断,它可以用来实现任务切换或者其他与系统调度相关的操作。
PendSV_Handler 在中断向量表的位置
在这里插入图片描述

__asm void xPortPendSVHandler( void )

{

extern uxCriticalNesting;

extern pxCurrentTCB;

extern vTaskSwitchContext;

PRESERVE8

mrs r0, psp //(1)        读取进程栈指针,保存在寄存器 R0 里面。

isb

ldr r3, =pxCurrentTCB//(2)   获取当前任务的任务控制块,并将任务控制块的地址保存在寄存器 R2 里面。

ldr r2, [r3] //(3)

tst r14, #0x10 //(4)        判断任务是否使用了 FPU

it eq //(5)

vstmdbeq r0!, {s16-s31}// (6)

stmdb r0!, {r4-r11, r14} //(7)

str r0, [r2] //(8)

stmdb sp!, {r3} //(9)

mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY //(10)       

msr basepri, r0 //(11)           关闭中断,进入临界区

dsb

isb

bl vTaskSwitchContext //(12)    调用函数 vTaskSwitchContext(),此函数用来获取下一个要运行的任务,并将pxCurrentTCB 更新为这个要运行的任务。

mov r0, #0 //(13)

msr basepri, r0 //(14)        打开中断,退出临界区。

ldmia sp!, {r3} //(15)

ldr r1, [r3] //(16)

ldr r0, [r1] //(17)

ldmia r0!, {r4-r11, r14} //(18)

tst r14, #0x10 //(19)

it eq //(20)

vldmiaeq r0!, {s16-s31} //(21)

msr psp, r0 //(22)           更新进程栈指针 PSP 的值

isb

bx r14 //(23)     执行此行代码以后硬件自动恢复寄存器 R0~R3、 R12、 LR、 PC 和 xPSR 的值,确定异常返回以后应该进入处理器模式还是进程模式,使用主栈指针(MSP)还是进程栈指针(PSP)。很明显这里会进入进程模式,并且使用进程栈指针(PSP),寄存器 PC 值会被恢复为即将运行的任务的任务函数,新的任务开始运行!至此,任务切换成功。

}

五.FreeRTOS系统移植到GD32F103C8T6单片机

1.硬件准备

STLINK接GD32F103C8T6开发板,STLINK接电脑USB口。

在这里插入图片描述

2.程序移植

FreeRTOS主要文件介绍:

port.c:这是一个示例端口文件,用于 Cortex-M3 微控制器。你需要根据你的处理器架构修改这个文件。
portmacro.h:这个文件包含硬件抽象层代码,也需要根据你的处理器架构修改。
FreeRTOSConfig.h:这个文件包含了 FreeRTOS 的配置选项,你可能需要修改这个文件来满足你的项目需求。
tasks.c, queue.c, list.c:这些是(kernel核心)文件,它们包含了FreeRTOS核心的任务管理和同步机制实现。
heap_2.c:这个是内存管理方法,它是一个小型的内存分配器,用于分配和释放固定大小的块内存。
timers.c:实现了定时器的所有功能,包括定时器的创建、删除、启动和停止等。
routine.c:这个文件包含了FreeRTOS的协程(或者称为微任务)的实现。

解压GD32F103C8T6的第一个例程:
1.GPIO输出实验
在这里插入图片描述

添加Freertos相关文件

添加FreeRTOS文件夹下面的所有文件到工程文件目录下
在这里插入图片描述
FreeRTOS文件夹下面的所有文件可以官网下载,也可以从我们例程上拷贝
在这里插入图片描述

添加FreeRTOSConfig.h文件
在这里插入图片描述

打开Keil5工程,创建FreeRTOS文件夹,添加下述文件到工程
在这里插入图片描述

添加头文件路径
在这里插入图片描述

删除中断文件中的三个中断服务程序,操作系统中已经对这三个文件重新定义了
在这里插入图片描述

main.c中添加包含头文件,并定义两个任务
在这里插入图片描述

main函数中创建这两个任务,并执行启动系统,完成系统移植
在这里插入图片描述

3.调试FreeRTOS任务调度

调试代码,能更好理解任务调度。

打断点,能跑进任务1
在这里插入图片描述

打断点,能跑进任务2
在这里插入图片描述

当前运行任务
在这里插入图片描述
任务切换后将要运行的任务
在这里插入图片描述

六.工程源代码下载

通过网盘分享的文件:13.FREERTOS实验.zip
链接: https://pan.baidu.com/s/1jR-GL6W_IbUX6Y7UKkdrMA 提取码: rrnb
如果链接失效,可以联系博主给最新链接
程序下载下来之后解压就行

CSDN下载

七.小结

FreeRTOS实时操作系统能使在GD32单片机软件开发中,程序结构清晰,单片机执行效率提升许多,在多个任务模块的代码中,可以考虑使用FreeRTOS。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐