硬件平台:stm32f103c8t6
硬件内核:cortex-m3
开发环境:mdk5.25
这里要实现的简易操作系统其实就是一个多任务系统,让MCU能够在同时运行多个任务!下面先列出对这个操作系统功能需求:
1、任务优先级(8): 这个是必须的,任务总有个轻重缓急,具体多少个看实际需要;
2、同优先级任务数目(不限): 同优先级的任务个数,这里不做限制;
3、任务时间切片(1ms): 即每1ms做一次任务调度;
4、任务创建:创建一个任务;
5、任务延时启动: 即当成功创建一个任务后,延时一定的时间再去运行;
6、任务睡眠: 类似延时,但是会交出CPU使用权限,直到延时结束;
定义时钟心跳为1ms, 最大优先级数目为8个
/* 定义每次任务调度时间间隔为1ms */
#define OS_TIME_PER_TICK 1
/* 定义优先级数量为8 */
#define OS_MAX_PRIO 8
主要是为了方便后期修改或者移植
#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;
/* 任务优先级表,每一位代表一个优先级,最大支持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;
根据前面写的功能需求,可以先声明相应的函数(具体参数下面会做详细说明):
/* 任务初始化函数,用于创建任务 */
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);
在创建任务之前,我们要明白一个任务实际上指的是什么?假设有如下任务:
/* 任务1 */
void task1(void)
{
int i=0;
while(1)
{
i++;
}
}
对MCU来说,所谓的任务就是一段代码。要MCU执行这段代码,就要找到这段代码的在内存中的地址,然后把地址给到CPU,那么CPU就会执行这段代码。
所以对于一个任务,我们首先需要知道这个任务的地址,也就是任务入口地址。对于上面的例子来说,函数名就是这个任务的入口地址。
假设有如下两个任务:
/* 任务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寄存按照上面的顺序压入栈中,退出中断的时候也会自动的按照相反的顺序出栈,所以要按照上面的顺序来。置于后面的寄存器是要手动保存的,顺序可以自己定,但是一定要保证入栈的顺序和出栈的顺序是相反的!
/* 设置睡眠时间 */
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++;
}
}
/* 出发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();
}
__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
}
#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();
}
一个多任务系统其实没有很难,只要理解了内核的运行原理,一切就都不是问题了。工程就不贴了,要收积分的^_^,有需要的可以私信我!