一、引言

上一次简单的记录了一下我学习用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)
    {
        LedBling(LED_Red);
        OSTickDelay(500);
    }
}

void task2(void)
{
    OSTickDelay(125);
    while(1)
    {
        LedBling(LED_Yellow);
        OSTickDelay(500);
    }
}

这里给出了两个函数,都是控制GPIO口让灯闪烁的功能。但与我们平时写的main里面的while(1)不同,这里出现了两个while(1),按照逻辑,只要调用了其中一个函数,就永远跳不出来了,那么另外一个函数也就没办法执行了,所以最后的效果只能是一个灯在闪烁。那么如何做到让两个任务同时运行呢?

我的思路是:让两个任务交替运行,task1执行5ms,task2也执行5ms。

我们知道寄存器是,存储的值是指令存储的中间临时变量,记录了程序的运行状态。

所以我们可以把task1的状态保存下来,然后跳到task2,执行一段时间后,保存task2的状态,恢复task1的状态,再跳到task1,就可以实现交替运行了。而帮我们保存现场的便是堆栈,所以我们在初始化任务的时候要定义任务的堆栈大小。

四、代码实现

首先贴出最终代码示例,这里创建了4个任务,预先为四个任务定义了堆栈数组,然后进行堆栈初始化

#include "os_type.h"
#include "os_tick.h"
#include "os_task.h"
#include "os_cpu.h"
#include "led.h"

#define task1_stack_size 100
#define task2_stack_size 100
#define task3_stack_size 100
#define task4_stack_size 100

uint32_t task1_stack[task1_stack_size];
uint32_t task2_stack[task2_stack_size];
uint32_t task3_stack[task3_stack_size];
uint32_t task4_stack[task4_stack_size];

void task1(void)
{
    while(1)
    {
        LedBling(LED_Red);
        OSTickDelay(500);
    }
}

void task2(void)
{
    OSTickDelay(125);
    while(1)
    {
        LedBling(LED_Yellow);
        OSTickDelay(500);
    }
}

void task3(void)
{
    OSTickDelay(250);
    while(1)
    {
        LedBling(LED_Blue);
        OSTickDelay(500);
    }
}

void task4(void)
{
    OSTickDelay(375);
    while(1)
    {
        LedBling(LED_Green);
        OSTickDelay(500);
    }
}

int main()
{
    LedInit();

    OSTickInit();

    OSTaskInit(task1, &task1_stack[task1_stack_size - 1]);
    OSTaskInit(task2, &task2_stack[task2_stack_size - 1]);
    OSTaskInit(task3, &task3_stack[task3_stack_size - 1]);
    OSTaskInit(task4, &task4_stack[task4_stack_size - 1]);

    OSTaskStart();
}

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指针的值指向了函数的首地址,在第一次运行时就可以找到程序运行的入口了。

未完待续。。。

最后修改:2022 年 04 月 28 日
如果觉得我的文章对你有用,请随意赞赏