前言
对于大部分的项目来讲,STM32基本都是处于“裸奔”的状态,将程序全部放在一个while(1)中顺序执行,但也会有部分任务要求几个任务“同时进行”,这个时候就需要一个RTOS了,但是有的时候就是这样,总会感觉为了一个小小的目的而在小容量的单片机中放一个OS,有些小题大做,我们需要的无非就是OS中的一个任务调度的功能,所以我只需要把这个功能给提取出来。
多任务
单片机只有一个处理核心,所以并不能同多核的PC机一样几个核心同时处理多个线程,我叫它“线程并行
”。而单片机中所谓的多任务就是“线程并发
”,就是多个线程看似一起发生,实际上还是单核心的顺序执行,只不过是每个线程都被分成了不同的时间片轮转执行。有关单片机多任务实现原理可以百度搜索一番,我在这里就不过多叭叭了,直接搞核心东西。
扩展知识
首先我们要知道,STM32有两个栈指针:MSP
(主栈指针)和PSP
(进程栈指针)有两种处理器模式:Thread Mode
和 Handler Mode
,有两种特权等级:特权模式
和非特权模式
。
STM32共有两个栈,主栈和进程栈,在裸机开发中全程使用主栈,而如果涉及到了多任务,就会用到进程栈,每个任务(其实就是函数)都会有一个属于任务本身的栈,这就是进程栈,这个栈的指针就要使用PSP,而这个时候到底是使用MSP还是PSP则是由 CONTROL 寄存器决定的, CONTROL[bit1] 若为0,则使用MSP,为 1 则使用PSP。中断或是异常的服务函数(例如 Systick_Handler 和 PendSV_Handler )一定是使用MSP(不需要人为控制,进入中断函数后 CONTROL[bit1] 位自动清零,函数退出后自动置 1 ),否则HardFault。
在 Thread Mode 中通过 CONTROL[bit0] 位可以选择处于特权模式或是非特权模式(0 特权模式,1 非特权模式),Handler Mode 一定是特权模式。(详情请自行搜索:任务运行在特权级或非特权级模式
)
功能实现
关于实现方式主要参考于《ARM Cortex-M3 Cortex-M4权威指南》第八章和第十章,主要用到了 PendSV 异常
和 Systick 定时器中断
,以及一些汇编语言
的知识。
其中PendSV用于在PendSV_Handler中实现上下文切换(所谓上下文切换就是在多个线程之间反复横跳),Systick定时器则是将多个任务切分成时间片,每次进入 Systick_Handler 就会挂起一个PendSV异常,在 Systick_Handler以及其他高优先级异常任务处理完成之后 再进入PendSV_Handler。
在PendSV_Handler中其实只是做了两件工作:保存现场和进程栈指针重定向。(这里需要用到汇编语言和寄存器的知识,如r0-r12,sp,lr,pc,msp,psp等,请自行搜索)
接下来分析代码。
这是使用STM32CubeIDE写的F407的代码,基于HAL库函数。
//宏定义 直接访问某个地址空间(1个字大小)
#define HW32_REG(ADDRESS) (*((volatile unsigned int *)(ADDRESS)))
//定义任务栈,每个2KB
long long task0Stack[256], task1Stack[256];
uint32_t currTask = 0; //当前任务号
uint32_t nextTask = 1; //下一个任务号
uint32_t PSP_Array[2]; //存放 进程栈指针 的数组
uint8_t initStatus = 0; //初始化完成标志,
//HAL库的Systick初始化在HAL_Init()中就完成了,不想更改,只能加一个status
void Task0()
{
initStatus = 1; //进入Task0之后,任务调度开始
while(1)
{
HAL_GPIO_TogglePin(LED_YELLOW_GPIO_Port, LED_YELLOW_Pin);
HAL_Delay(300);
}
}
void Task1()
{
while