一、引言
上一次简单的记录了一下我学习用Cortex-M3写一个最小调度系统的过程,但大多是图片的堆砌,所以现在准备写一个基于Cortex-M3的简易操作系统的系列,也算记录一下学习操作系统的过程。
本系列使用基于Cortex-M3内核的STM32F103RCT6作为平台
代码开源在gitee上,大家可以自行下载:https://gitee.com/dwk88/SimpleOS
二、预备知识
本文默认你学习并实际开发过Cortex-M系列的的单片机,例如:STM32,Tiva等,熟悉汇编知识
你还需要了解Cortex-M3包含的寄存器以及它们的功能
首先我们来思考一个问题:如何让多个任务同时进行(并行),例如:算法程序计算(长时间占用MCU)和数据传输、显示、外部控制
要知道我们的单核MCU是无法同时执行两条指令的,要并行运行多个任务必然要多个MCU,但那样协调性和成本都是问题。
我们做不到并行,所以折中的解决方案是使用一个调度程序,周期性的切换执行的任务。
三、时间片轮转调度算法
时间片轮转的意思是,以一个固定的时间周期,每隔一段时间就切换MCU执行的任务。
下面举一个列子:
void task1(void) { while(1) { GPIO_PinWrite(LED_Red.port, LED_Red.num, 1); Delay_ms(500); GPIO_PinWrite(LED_Red.port, LED_Red.num, 0); Delay_ms(500); } } void task2(void) { while(1) { GPIO_PinWrite(LED_Yellow.port, LED_Yellow.num, 1); Delay_ms(500); GPIO_PinWrite(LED_Yellow.port, LED_Yellow.num, 0); Delay_ms(500); } }
这里给出了两个函数,都是控制GPIO口让灯闪烁的功能。但与我们平时写的main里面的while(1)不同,这里出现了两个while(1),按照逻辑,只要调用了其中一个函数,就永远跳不出来了,那么另外一个函数也就没办法执行了,所以最后的效果只能是一个灯在闪烁。那么如何做到让两个任务同时运行呢?
我的思路是:让两个任务交替运行,task1执行5ms,task2也执行5ms。
我们知道寄存器是,存储的值是指令存储的中间临时变量,记录了程序的运行状态。
所以我们可以把task1的状态保存下来,然后跳到task2,执行一段时间后,保存task2的状态,恢复task1的状态,再跳到task1,就可以实现交替运行了。而帮我们保存现场的便是堆栈,所以我们在初始化任务的时候要定义任务的堆栈大小。
三、代码实现
CM3有两个堆栈指针——主堆栈指针(MSP)和 进程堆栈指针(PSP)
主堆栈指针用在任务切换的程序,进程堆栈指针用在用户任务程序上
这里贴出任务初始化的时候,堆栈的初始化代码
uint32_t *OSTaskStackInit(void (*task)(void), uint32_t *stack) { uint32_t *stk = stack; /* Registers stacked as if auto-saved on exception */ *(stk) = (uint32_t)0x01000000L; /* xPSR */ *(--stk) = (uint32_t)task; /* Entry Point */ *(--stk) = (uint32_t)0xFFFFFFFEL; /* R14 (LR) (init value will cause fault if ever used)*/ *(--stk) = (uint32_t)0x12121212L; /* R12 */ *(--stk) = (uint32_t)0x03030303L; /* R3 */ *(--stk) = (uint32_t)0x02020202L; /* R2 */ *(--stk) = (uint32_t)0x01010101L; /* R1 */ *(--stk) = (uint32_t)0x00000000L; /* R0 : argument */ /* Remaining registers saved on process stack */ *(--stk) = (uint32_t)0x11111111L; /* R11 */ *(--stk) = (uint32_t)0x10101010L; /* R10 */ *(--stk) = (uint32_t)0x09090909L; /* R9 */ *(--stk) = (uint32_t)0x08080808L; /* R8 */ *(--stk) = (uint32_t)0x07070707L; /* R7 */ *(--stk) = (uint32_t)0x06060606L; /* R6 */ *(--stk) = (uint32_t)0x05050505L; /* R5 */ *(--stk) = (uint32_t)0x04040404L; /* R4 */ return (stk); }
可以看到这里对R0-R15都进行了初始化,其中最主要的就是初始化程序的入口
*(--stk) = (uint32_t)task; /* Entry Point
这里把PC指针的值指向了函数的首地址,在第一次运行时就可以找到程序运行的入口了。
未完待续。。。
来源:https://www.cnblogs.com/dwk8/p/12274514.html