基于Cortex-m3实现简易实时操作系统

一、实验环境

硬件平台:stm32f103c8t6

硬件内核:cortex-m3

开发环境:mdk5.25

 

二、功能描述

       这里要实现的简易操作系统其实就是一个多任务系统,让MCU能够在同时运行多个任务!下面先列出对这个操作系统功能需求:

1、任务优先级(8): 这个是必须的,任务总有个轻重缓急,具体多少个看实际需要;

2、同优先级任务数目(不限): 同优先级的任务个数,这里不做限制;

3、任务时间切片(1ms): 即每1ms做一次任务调度;

4、任务创建:创建一个任务;

5、任务延时启动: 即当成功创建一个任务后,延时一定的时间再去运行;

6、任务睡眠: 类似延时,但是会交出CPU使用权限,直到延时结束;

 

三、初始定义

3.1 任务优先级和切片时间定义

定义时钟心跳为1ms, 最大优先级数目为8个

/* 定义每次任务调度时间间隔为1ms */
#define OS_TIME_PER_TICK        1
/* 定义优先级数量为8 */
#define OS_MAX_PRIO             8

  3.2  数据类型定义

主要是为了方便后期修改或者移植

#define STATIC                  static

/* 数据类型定义 */
typedef	signed int              OS_INT32;
typedef unsigned int            OS_INT32_U;
typedef signed short            OS_INT16;
typedef unsigned short          OS_INT16_U;
typedef signed char             OS_INT8;
typedef unsigned char           OS_INT8_U;

/* 任务栈相关的数据类型定义 */
typedef OS_INT32_U              OS_STK;
typedef OS_INT32_U              OS_STK_SIZE;
typedef OS_INT32_U              OS_TASK_PRIO;
typedef OS_INT32_U              OS_DATA;
typedef OS_INT32_U              OS_TICK;

3.3 全局变量定义

/* 任务优先级表,每一位代表一个优先级,最大支持32个优先级 */
extern OS_DATA os_prio_table;
/* 就绪任务列表 */
extern OS_TASK_TABLE os_rdy_list[OS_MAX_PRIO];
/* 当前任务指针 */
extern OS_TCB *os_tcb_cur_ptr;
/* 下一个任务指针 */
extern OS_TCB *os_tcb_next_ptr;

上面涉及到两个数据类型(OS_TCB, OS_TASK_TABLE),定义如下:

/* 任务状态定义 */
typedef enum{
    OS_TASK_STATE_NONE          = 0u,
    OS_TASK_STATE_OK            = 1u,
    OS_TASK_STATE_READY         = 2u,
    OS_TASK_STATE_SLEEP         = 4u,
}OS_TASK_STATE;

/* 错误码定义(其实没用到)*/
typedef enum os_err {
    OS_ERR_NONE                 = 0u,
} OS_ERR;


/* 任务控制块结构体定义 */
typedef struct os_tcb           OS_TCB;
struct os_tcb
{
    OS_STK          *stack_ptr;   // 任务栈指针 
    OS_STK_SIZE     stack_size;   // 任务栈大小 
    
    OS_TASK_PRIO    task_prio;    // 任务优先级 
    OS_TASK_STATE   task_state;   // 任务状态
    OS_TICK         task_delay;   // 任务延时
    
    OS_TCB*         next_ptr;     // 任务控制块指针, 指向当前任务的上一个任务 
    OS_TCB*         prev_ptr;     // 任务控制块指针,指向当前任务的下一个任务 
};

/* 任务列表结构体定义 */
typedef struct os_task_table
{
    OS_TCB* head_ptr;             // 列表头指针  
    OS_TCB* tail_ptr;             // 列表尾指针  
    OS_DATA task_count;           // 任务数量  
}OS_TASK_TABLE;

3.2 全局函数定义

根据前面写的功能需求,可以先声明相应的函数(具体参数下面会做详细说明):

/* 任务初始化函数,用于创建任务 */
void  os_task_init( OS_TCB          *p_tcb,
                    OS_TASK_PTR     p_task,
                    void            *p_arg,
                    OS_STK          *p_stk_base,
                    OS_STK_SIZE     stk_size,
                    OS_TASK_PRIO    task_prio,
                    OS_DATA         task_delay,
                    OS_ERR          *p_err);

/* 任务睡眠函数 */
void os_task_sleep(OS_TICK time);
/* 任务睡眠时间递减函数 */
void os_task_tick_dec(void);
/* 任务调度函数,根据调度规则,切换不同的任务 */
void os_sched(void);
/* 操作系统初始化函数 */
void os_init(void);
/* 启动操作系统 */
void os_start(void);

 

四、功能实现

4.1 创建任务

4.1.1  任务入口地址

在创建任务之前,我们要明白一个任务实际上指的是什么?假设有如下任务:

/* 任务1 */
void task1(void)
{
    int i=0;
    while(1)
    {
        i++;
    }
}

       对MCU来说,所谓的任务就是一段代码。要MCU执行这段代码,就要找到这段代码的在内存中的地址,然后把地址给到CPU,那么CPU就会执行这段代码。

      所以对于一个任务,我们首先需要知道这个任务的地址,也就是任务入口地址。对于上面的例子来说,函数名就是这个任务的入口地址。

4.1.2  任务栈

假设有如下两个任务:

/* 任务1 */
void task1(void)
{
    int a1=0;
    int b1=0;
    while(1)
    {
        a1++;
        b1++;
    }
}

/* 任务2 */
void task2(void)
{
    int a2=0;
    int b2=0;
    while(1)
    {
        a2++;
        b2++;
    }
}

       假设当前运行的任务是任务一,n此循环之后,运行到a1++这条语句。此时要进行任务切换,这就需要将当前程序执行的位置和a1,b1这的值存下来,这就需要一段内存,也就是任务栈。当重新运行到任务一的时候,就把这些数据从任务栈重新加载回来,程序就可以从原来的位置继续执行。

      现在再重新看一下任务创建函数的参数就一目了然了。

void  os_task_init( OS_TCB          *p_tcb,  //任务控制块指针
                    OS_TASK_PTR     p_task,  //任务入口地址
                    void            *p_arg,  //任务函数的参数
                    OS_STK          *p_stk_base, //任务栈地址
                    OS_STK_SIZE     stk_size, //任务栈大小
                    OS_TASK_PRIO    task_prio, //任务优先级
                    OS_DATA         task_delay, //任务延时启动时间
                    OS_ERR          *p_err);   //错误码

   下面来看一下这个函数的具体实现:

void  os_task_init( OS_TCB          *p_tcb,
                    OS_TASK_PTR     p_task,
                    void            *p_arg,
                    OS_STK          *p_stk_base,
                    OS_STK_SIZE     stk_size,
                    OS_TASK_PRIO    task_prio,
                    OS_DATA         task_delay,
                    OS_ERR          *p_err)
{
    OS_DATA task_count;
    OS_DATA prio;

    /* 初始化任务栈 */
    p_tcb->stack_ptr = os_stack_init(p_task,
                         p_arg,
                         p_stk_base,
                         stk_size);
    p_tcb->stack_size = stk_size;
    /* 任务优先级 */
    prio = task_prio%OS_MAX_PRIO;
    p_tcb->task_prio = prio;
    /* 延时启动时间 */
    p_tcb->task_delay = task_delay + OS_TIME_PER_TICK;
    p_tcb->task_state = OS_TASK_STATE_OK;
    
    task_count = os_rdy_list[prio].task_count ;

    /* 将任务添加到就绪列表中 */
    if(task_count)
    {
        p_tcb->prev_ptr = os_rdy_list[prio].tail_ptr;
        os_rdy_list[prio].tail_ptr->next_ptr = p_tcb;
        
        p_tcb->next_ptr = (OS_TCB*) 0;
        os_rdy_list[prio].tail_ptr = p_tcb;
    }
    else
    {
        p_tcb->prev_ptr = (OS_TCB*) 0;
        p_tcb->next_ptr = (OS_TCB*) 0;
        os_rdy_list[prio].head_ptr = p_tcb;
        os_rdy_list[prio].tail_ptr = p_tcb;
    }
    os_set_prio(prio);
    os_rdy_list[prio].task_count++;
    *p_err = OS_ERR_NONE;

}

/* 设置任务优先级 */
void os_reset_prio(OS_TASK_PRIO prio)
{
    OS_DATA res;
    res = prio%OS_MAX_PRIO;
    os_prio_table &= ~((0x01)<< res);
}

   再看一下任务栈初始化函数:

OS_STK  *os_stack_init( OS_TASK_PTR     p_task,
                        void            *p_arg,
                        OS_STK          *p_stk_base,
                        OS_STK_SIZE     stk_size)
{
    OS_STK  *p_stk;
    /* from top to bottom */
    p_stk = &p_stk_base[stk_size];

    /* xPSR */
    *--p_stk = (OS_STK)0x01000000u;
    /* R15(PC) */
    *--p_stk = (OS_STK)p_task;
    /* R14(LR) */
    *--p_stk = (OS_STK)0x14141414u;
    /* R12 */
    *--p_stk = (OS_STK)0x12121212u;
    /* R3 */
    *--p_stk = (OS_STK)0x03030303u;
    /* R2 */
    *--p_stk = (OS_STK)0x02020202u;
    /* R1 */
    *--p_stk = (OS_STK)0x01010101u;

    /* R0(argument) */
    *--p_stk = (OS_STK)p_arg;

    *--p_stk = (OS_STK)0x11111111u;
    *--p_stk = (OS_STK)0x10101010u;
    *--p_stk = (OS_STK)0x09090909u;
    *--p_stk = (OS_STK)0x08080808u;
    *--p_stk = (OS_STK)0x07070707u;
    *--p_stk = (OS_STK)0x06060606u;
    *--p_stk = (OS_STK)0x05050505u;
    *--p_stk = (OS_STK)0x04040404u;

    return (p_stk);	
}

       p_stk就是任务栈指针,开始指向的是数组的之后一个位置(因为m3内核的栈是向下生长的,也就是从大地址到小地址)。那存储的顺序为什么是上面这样的?因为在进入中断的时候,m3内核会自动将XPSR到R0寄存按照上面的顺序压入栈中,退出中断的时候也会自动的按照相反的顺序出栈,所以要按照上面的顺序来。置于后面的寄存器是要手动保存的,顺序可以自己定,但是一定要保证入栈的顺序和出栈的顺序是相反的!

4.2  任务睡眠

/* 设置睡眠时间 */
void os_task_sleep(OS_TICK time)
{
    os_tcb_cur_ptr->task_delay = time;
    os_tcb_cur_ptr->task_state = OS_TASK_STATE_SLEEP;
    
    os_sched();
}

/* 用在定时器中断函数里,进行睡眠时间递减 */
void os_task_tick_dec(void)
{
    OS_DATA i;
    OS_DATA prio;
    OS_TCB* tcb_ptr;
    
    i = 0;
    
    while(i>i)&(0x01);
        if(prio)
        {
            tcb_ptr = os_rdy_list[i].head_ptr;
            while(tcb_ptr)
            {
                if(tcb_ptr->task_state & (OS_TASK_STATE_OK | OS_TASK_STATE_SLEEP))
                {
                    if(tcb_ptr->task_delay > OS_TIME_PER_TICK)
                    {
                        tcb_ptr->task_delay -= OS_TIME_PER_TICK;
                    }
                    else
                    {
                        tcb_ptr->task_delay = 0;
                        tcb_ptr->task_state = OS_TASK_STATE_READY;
                    }
                }
                tcb_ptr = tcb_ptr->next_ptr;
            }
        }
        i++;
    }
}

4.3  任务调度

/* 出发PendSV中断,在中断处理函数里进行任务切换 */
__asm void os_task_sw(void)
{
    LDR  R0, = 0xE000ED04
    LDR  R1, = 0x10000000
    STR  R1, [R0]
    NOP
    
    BX LR
}


/* 任务调度函数 */
void os_sched(void)
{
    OS_DATA i, j;
    OS_DATA prio;
    OS_TCB* tcb_ptr;
    
    i = 0;
    os_tcb_next_ptr = (OS_TCB*)0;
    
    while(i < OS_MAX_PRIO)
    {
        prio = (os_prio_table>>i)&(0x01);
        if(prio)
        {
            j=0;
            tcb_ptr = (i == os_tcb_cur_ptr->task_prio)? os_tcb_cur_ptr : os_rdy_list[i].head_ptr;
            
            while(j < os_rdy_list[i].task_count)
            {
                if(tcb_ptr->task_state == OS_TASK_STATE_READY)
                {
                    os_tcb_next_ptr = tcb_ptr;
                    if(tcb_ptr!=os_tcb_cur_ptr)
                    {
                        break;
                    }
                }
                j++;
                tcb_ptr = tcb_ptr->next_ptr? tcb_ptr->next_ptr : os_rdy_list[i].head_ptr;
            }
            if(os_tcb_next_ptr)
            {
                os_task_sw();
                break;
            }
        }
        i++;
    }
    
}



/* 定时器处理函数,进行任务调度和休眠时间递减 */
void SysTick_Handler(void)
{
    os_task_tick_dec();
    os_sched();
}

4.4 任务切换

__asm void PendSV_Handler(void)
{
    IMPORT  os_tcb_cur_ptr
    IMPORT  os_tcb_next_ptr
    
    CPSID I
    
    MRS  R0, PSP
    CBZ  R0, OS_TASK_SWITCH
    STMDB  R0!, {R4-R11}       //寄存器入栈
    LDR  R1, = os_tcb_cur_ptr
    LDR  R1, [R1]
    STR  R0, [R1]
OS_TASK_SWITCH
    LDR  R0, = os_tcb_cur_ptr
    LDR  R1, = os_tcb_next_ptr
    LDR  R2, [R1]
    STR  R2, [R0]
    
    LDR  R0, [R2]
    LDMIA R0!, {R4-R11}        //寄存器出栈
    MSR  PSP, R0
    ORR  LR, LR, #0x04   //从MSP切换到PSP
    
    CPSIE I
    
    BX LR
}

4.5 系统初始化

#define NVIC_INT_CTRL_REG *((OS_DATA *)0xE000ED04)
#define NVIC_INT_CTRL           0xE000ED04
#define NVIC_SYSPRI14           0xE000ED22
#define NVIC_PENDSV_PRI         0xFF
#define NVIC_PENDSVSET          0x10000000

#define  IDLE_STACK_SIZE        128

STATIC OS_STK task_stk_idle[IDLE_STACK_SIZE];
STATIC OS_TCB task_tcb_idle;
/**
  * @brief  os idle task
  * @param
  * @retval
  */
void os_task_idle(void *p_arg)
{
    OS_DATA idle_count=0;
    while(1)
    {
        idle_count++;
    }
}


/**
  * @brief 创建一个空闲任务,当没有任务就绪的时候就自动运行这个任务
  * @param
  * @retval
  */
void os_init(void)
{
    OS_ERR err;
    
    //todo disable interrupt
    __asm{
        CPSID  I
    }
    
    os_task_init(&task_tcb_idle,
                os_task_idle,
                (void*)0,
                task_stk_idle,
                IDLE_STACK_SIZE,
                (OS_TASK_PRIO) OS_MAX_PRIO-1,
                (OS_TICK) 0,
                &err);
                
    os_tcb_cur_ptr = &task_tcb_idle;
    os_tcb_next_ptr = &task_tcb_idle;
}


/**
  * @brief
  * @param
  * @retval
  */
__asm void os_start_high_ready(void)
{
   /* 将PendSV设置为最低优先级 */
    LDR  R0, = NVIC_SYSPRI14
    LDR  R1, = NVIC_PENDSV_PRI
    STRB  R1, [R0]
    
    MOVS  R0, #0
    MSR  PSP, R0
    
    /* 出发PendSV */
    LDR  R0, = NVIC_INT_CTRL
    LDR  R1, = NVIC_PENDSVSET
    STR  R1, [R0]
    
    CPSIE  I
    NOP
    
    BX LR
}




/**
  * @brief 将PendSV设置为最低优先级,并触发中断, 开始第一次上下文切换
  * @param
  * @retval
  */
void os_start(void)
{
    os_start_high_ready();
}

 

五、运行系统

主要就是做了个定时器的初始化和LED的初始化,直接上代码:


#include "main.h"
#include "os.h"


#define STACK_SIZE              128

void system_init(void);

STATIC OS_STK task1_stack[STACK_SIZE];
STATIC OS_STK task2_stack[STACK_SIZE];
STATIC OS_STK task3_stack[STACK_SIZE];

STATIC OS_TCB tcb_task1, tcb_task2, tcb_task3;



void os_task1( void *p_arg )
{
    while(1)
    {
        LED_ON;
        os_task_sleep(1000);
    }
}

void os_task2( void *p_arg )
{
    while(1)
    {
        LED_OFF;
        os_task_sleep(1000);
    }
}

void os_task3( void *p_arg )
{
    while(1)
    {
        LED_ON;
        os_task_sleep(1000);
    }
}

/* Program Entry */
int main(void)
{
    OS_ERR err1, err2;
    
    os_init();
    
    system_init();

    /* 创建任务1 */
    os_task_init(&tcb_task1,
                os_task1,
                (void*)0,
                task1_stack,
                STACK_SIZE,
                1,
                0,
                &err1);
    /* 创建任务2, 延时500ms启动 */
    os_task_init(&tcb_task2,
                os_task2,
                (void*)0,
                task2_stack,
                STACK_SIZE,
                1,
                500,
                &err2);
    /* 创建任务3, 延时800ms启动 */
    os_task_init(&tcb_task3,
                os_task3,
                (void*)0,
                task3_stack,
                STACK_SIZE,
                1,
                800,
                &err2);
    os_start();
}

void system_init(void)
{
    systick_ms_init(72000000*OS_TIME_PER_TICK);
    led_init();
}

六、总结

        一个多任务系统其实没有很难,只要理解了内核的运行原理,一切就都不是问题了。工程就不贴了,要收积分的^_^,有需要的可以私信我!

你可能感兴趣的:(嵌入式,C编程)