基于Cortex-M3的简易操作系统——简易任务调度(一)

亡梦爱人 提交于 2020-02-07 23:27:26

一、引言

上一次简单的记录了一下我学习用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指针的值指向了函数的首地址,在第一次运行时就可以找到程序运行的入口了。

 

未完待续。。。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!