文章目录
- 一.概要
- 二.什么是实时操作系统
- 三.FreeRTOS的特性
- 四.FreeRTOS的任务详解
- 1.任务函数定义
- 2.任务的创建
- 3.任务的调度原理
- 五.CubeMX配置一个FreeRTOS例程
- 1.硬件准备
- 2.创建工程
- 3.调试FreeRTOS任务调度
- 六.CubeMX工程源代码下载
- 七.讲解视频链接地址
- 八.小结
一.概要
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函数创建了两个任务LED_Thread1(驱动LED灯灭)和LED_Thread2(驱动LED灯亮)。在main函数中,通过调用osKernelStart函数启动了实时系统,使得任务可以被调度执行,由于两个任务都能不断运行,所以能驱动LED灯闪烁。
//定义任务函数1
void LED_Thread1(void const * argument)
{
(void) argument;
for (;;)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);//PC13输出高,LED灭
osDelay(100);//等待100ms
}
}
//定义任务函数2
void LED_Thread2(void const * argument)
{
(void) argument;
for (;;)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);//PC13输出低,LED亮
osDelay(250);//等待250ms
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();//外部8M晶振,系统72M主频
MX_GPIO_Init();//PC13配置成推挽输出
MX_FREERTOS_Init();//实时系统初始化
/* Start scheduler */
osThreadDef(THREAD1, LED_Thread1, osPriorityNormal, 0, 128);
THREAD1Handle = osThreadCreate(osThread(THREAD1), NULL);//建立任务1
/* definition and creation of THREAD2 */
osThreadDef(THREAD2, LED_Thread2, osPriorityNormal, 0, 128);
THREAD2Handle = osThreadCreate(osThread(THREAD2), NULL);//建立任务2
osKernelStart();//实时系统启动
while (1)
{
}
}
1.任务函数定义
无论采用何种方法创建任务,均需要用到任务函数。FreeRTOS 规定任务函数的返回值必须为void,而且带有一个void型指针参数。
void LED_Thread1(void const * argument)
{
(void) argument;
for (;;)
{
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);//PC13输出高,LED灭
osDelay(100);//等待100ms
}
}
STM32F103C8T6单片机只有一个内核,那怎么让多个人同时干活呢?其实每个子任务虽然都是死循环,但并不是每个子任务一直都在执行,每个子任务在执行期间,可能需要延时,这边就通过 osDelay(100),等待100ms,单片机就可以停止此任务,然后切换到其它任务执行,这样看起来就是多个人在同时干活了。
2.任务的创建
osThreadDef(THREAD1, LED_Thread1, osPriorityNormal, 0, 128);//定义一个任务,任务名,任务函数,优先级,堆栈大小
THREAD1Handle = osThreadCreate(osThread(THREAD1), NULL); //根据上面定义的任务,创建一个任务
osThreadDef函数中的128是任务栈大小定义:
任务栈大小指定了任务可以使用多少RAM来存储局部变量和其他临时数据。这个大小应该足够大,能够容纳任务在执行期间可能出现的最大需求。
osThreadDef函数中的LED_Thread1是任务函数:
任务函数是任务执行的代码,它应该是一个无返回值的函数,其参数是一个可选的指针,可以用来传递任何需要的数据,我们代码中的任务就是控制GPIO输出高或者低电平。
osThreadDef函数中的osPriorityNormal任务优先级:
任务优先级决定了任务在操作系统调度下的执行顺序。数值越大,优先级越高。
osThreadCreate函数的返回值THREAD1Handle是任务句柄:
任务句柄是一个指针,可以用来引用已经创建的任务,以便可以在任务创建后对其进行操作,例如删除、挂起、恢复等。
3.任务的调度原理
FreeRTOS默认使用抢占式调度策略,对同等优先级的任务使用时间片轮询调度,时间片轮询就是可轮流享有相同的单片机时间(可设置),一个时间片等于SysTick中断周期。
抢占式调度是指调度器始终运行优先级最高且处于可运行状态的任务,无论任务何时可以运行。如在中断服务函数中更改了优先级最高且可运行的任务,调度器会停止当前执行的低优先级任务,并启动高优先级任务。
刚才程序中的osDelay 是通过将当前任务加入到延时列表中,并设置一个定时器来在指定的延时时间,延时时间过去之后将任务从延时列表中移除并将其设置为就绪状态。这样当定时器触发时,任务重新被加入到就绪列表中,等待被调度器再次调度执行。
上面代码中的LED_Thread1(驱动LED灯灭)和LED_Thread2(驱动LED灯亮)两个任务的调度顺序以及时间,如下图所示,横坐标是时间,箭头代表运行顺序
下面代码截图就是操作系统底层寻找就绪任务的最高优先级,获取优先级最高的就绪任务的 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 值会被恢复为即将运行的任务的任务函数,新的任务开始运行!至此,任务切换成功。
}
五.CubeMX配置一个FreeRTOS例程
1.硬件准备
STLINK接STM32F103C8T6小系统板,STLINK接电脑USB口。
2.创建工程
打开STM32CubeMX软件,新建工程
Part Number处输入STM32F103C8,再双击就创建新的工程
配置下载口引脚
配置外部晶振引脚
可以查看STM32F103C8T6小系统板原理图,PC13连接LED灯,所以配置PC13为GPIO输出
配置FreeRTOS
配置系统主频
配置工程文件名,保存路径,KEIL5工程输出方式
生成工程
用Keil5打开工程
添加代码
3.调试FreeRTOS任务调度
调试代码,能更好理解任务调度。
下载调试,在LED_Thread1打断点,程序能运行到断点处停止
在LED_Thread2打断点,程序也能运行到断点处停止
在xPortPendSVHandler调度函数中打断点,可以看到操作系统将要跳转到任务1运行
单步执行,运行指针就跳转到任务1开始运行
xPortPendSVHandler调度函数中打断点,可以看到操作系统将要跳转到任务2运行
单步执行,运行指针就跳转到任务2开始运行
六.CubeMX工程源代码下载
链接:https://pan.baidu.com/s/1f50X8w2UXnDtbM6U6zwYqw
提取码:0sys
如果链接失效,可以联系博主给最新链接
程序下载下来之后解压就行
七.讲解视频链接地址
FreeRTOS实验
八.小结
FreeRTOS实时操作系统能使在STM32软件开发中,程序结构清晰,单片机执行效率提升许多,在多个任务模块的代码中,可以考虑使用FreeRTOS。