如题,任务创建与上下文切换是跟硬件息息相关的,而这恰恰是RTOS编写的最难点,抛开这些功能,剩下的就是双向链表增删改操作了,本例用最精简的方式实现了任务创建与切换,OS启动等功能,并运用了Cortex-M3的先进特性,非传统关中断实现方式,助你学习CM3与RTOS的精髓
关于RTOS的理论性知识,实在是无力多讲,资料太多了,请自行寻找吧!
关于Cortex-M3,它实在是太优秀了,它的很多特性就是为了RTOS而生的
你可以下载代码,在keil下执行软件仿真调试即可,区区160行代码(不算注释),相信你能搞明白的
本例固定4个任务,main函数是第一个任务,在main函数再创建2个任务,简单轮转调度,任务管理不在本文讨论内容,请见谅
注释偏少,不懂请问!
直接上代码:
OS核心部分
#include "os.h"
typedef struct TCB{
int reserve[3];
void* stack; // 任务栈顶地址 汇编使用,offset 0x0C
int deleteFlag;
//...
}TCB;
struct _OS{
TCB* run; // 正在运行的任务 汇编使用,offset 0x00
TCB* rdy; // 就绪的任务 汇编使用,offset 0x04
// ...
}os;
__asm void SVC_Handler (void){
PRESERVE8
THUMB
PUSH {LR}
MRS R0,PSP // Read PSP
LDM R0,{R0-R3,R12} // Read R0-R3,R12 from stack
BLX R12 // Call SVC Function
MRS R1,PSP // Read PSP
STR R0,[R1,#0x0] // set return values
POP {PC}
ALIGN
}
__asm void PendSV_Handler (void){
PRESERVE8
THUMB
IMPORT os
IMPORT OS_Sched
PUSH {LR}
MRS R0,PSP
STMFD R0!,{R4-R11}
MSR PSP,R0
BL OS_Sched // C语言任务调度
// 切换任务,看C示意代码
// os.run->stack = PSP;
LDR R2,=os
LDR R1,[R2,#0x0]
MRS R3,PSP
STR R3,[R1,#0xC]
// os.run = os.rdy;
LDR R0,[R2,#0x4]
STR R0,[R2,#0x0]
// PSP = os.rdy->stack
LDR R1,[R2,#0x4]
LDR R0,[R1,#0xC]
MSR PSP,R0
MRS R0,PSP
LDMFD R0!,{R4-R11}
MSR PSP,R0
POP {PC}
ALIGN
}
/**
* [url=home.php?mod=space&uid=159083]@brief[/url] 悬起PendSV,由于此中断优先级最低,所以若是在SVC_Handler或更高级别中断函数中设置的,
* PendSV_Handler函数不会立即执行, [url=home.php?mod=space&uid=418085]@see[/url]
*/
__asm void OS_PendSV (void){
PRESERVE8
THUMB
NVIC_INT_CTRL EQU 0xE000ED04
NVIC_PENDSVSET EQU 0x10000000
LDR R0, =NVIC_INT_CTRL
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR
ALIGN
}
void _OS_TaskDeleteSelf(void){
// 本例没有实现 ...
// 一般做法是将该TCB从链表中移除
os.run->deleteFlag = 1;
OS_PendSV();
}
/**
* @brief 当任务函数执行返回时,跳转到此函数, @see OS_TaskStackInit
*/
__asm void ASM_TaskDelete(void){
PRESERVE8
THUMB
IMPORT _OS_TaskDeleteSelf
LDR R12,=_OS_TaskDeleteSelf
SVC 0
ALIGN
}
/**
* @brief 堆栈初始化
*/
__asm void* OS_TaskStackInit(void (*taskFun)(void),void* stk,void* argv){
PRESERVE8
THUMB
MOV R3,#0x01000000 // 初始化PSR的值
STMFD R1!, {R3} // push xPSR
STMFD R1!, {R0} // push PC = taskFun
LDR R0,=ASM_TaskDelete
STMFD R1!,{R0} // push LR = 删除任务函数的地址
MOV R0,#0x0
STMFD R1!,{R0} // push R12
STMFD R1!,{R0} // push R3
STMFD R1!,{R0} // push R2
STMFD R1!,{R0} // push R1
STMFD R1!,{R2} // push R0 = argv
STMFD R1!,{R0} // push R11
STMFD R1!,{R0} // push R10
STMFD R1!,{R0} // push R9
STMFD R1!,{R0} // push R8
STMFD R1!,{R0} // push R7
STMFD R1!,{R0} // push R6
STMFD R1!,{R0} // push R5
STMFD R1!,{R0} // push R4
// 返回新栈顶
MOV R0, R1
BX LR
ALIGN
}
/**
* @brief 更改CPU模式,并设置PendSV的优先级为最低
*/
__asm void OS_ChangeCpuMode (void* stk){
PRESERVE8
THUMB
NVIC_SYSPRI2 EQU 0xE000ED20 // 系统优先级寄存器(2)
NVIC_PENDSV_PRI EQU 0xFFFF0000 // PendSV中断和系统节拍中断 (都为最低,0xff).
MSR PSP,R0
// 设置中断优先级,将PendSV设为最低
LDR R0, =NVIC_SYSPRI2
LDR R1, =NVIC_PENDSV_PRI
STR R1, [R0]
// 更换CPU模式,使线程模式运行在特权级,#0x3则运行在用户级
MOV R0,#0x2
MSR CONTROL,R0
// 开中断
CPSIE I
BX LR
ALIGN
}
/**
* @brief 切换到下一个任务
*/
void _OS_TaskPass(void){
OS_PendSV();
}
/// 引用配置文件中的定义,os是一个模块,是要被编译成lib文件的,所以这里只要声明即可
extern int const IDLE_TASK_TACK_SIZE;
extern uint64_t IdleTaskTackBuff[];
extern int const FIRST_TASK_TACK_SIZE;
extern uint64_t FirstTaskTackBuff[];
/// 本例固定定义4个TCB,实际编写时应采用声明,由os配置文件做具体定义,同上
/// 这样在编译OS时不依赖任何应用相关的配置
TCB tcbArray[2+2];
int tcbIndex = 0;
int osSchedIndex;
// 这是一个简单实现函数
void OS_Sched(void){
// 查找一个没有被删除的任务
do{
osSchedIndex++;
if(osSchedIndex >= 4){
osSchedIndex = 0;
}
}while(tcbArray[osSchedIndex].deleteFlag == 1);
os.rdy = &tcbArray[osSchedIndex];
}
/**
* @brief 这是一个简单实现函数,一般要做链表的插入操作
*/
int _OS_TaskCreate(void (*taskFun)(void),void* stk,int prio_stkSize,void* argv){
if(tcbIndex >= 4){
return -1;
}
// 取优先级参数,此例暂不使用
//int prio = (prio_stkSize & 0xff);
// 取堆栈size
prio_stkSize = prio_stkSize>>8;
// 初始化任务堆栈
stk = (void*)((int)stk + prio_stkSize);
stk = OS_TaskStackInit(taskFun,stk,argv);
// 初始化TCB,此例没有任务管理功能,仅设置栈顶即可
tcbArray[tcbIndex].stack = stk;
tcbArray[tcbIndex].deleteFlag = 0;
// 返回任务ID
return tcbIndex++;
}
// 这是一个简单实现函数
void OS_Start(void (*taskFun)(void)){
extern void OS_IdleTask(void);
// 初始化空闲任务,默认使用tcbArray[0]
// 空闲任务的堆栈不需要初始化,多单步运行几次你就会明白的
tcbArray[0].stack = (void*)((int)IdleTaskTackBuff + IDLE_TASK_TACK_SIZE);
tcbArray[0].deleteFlag = 0;
// 创建第一个任务
tcbIndex = 1;
_OS_TaskCreate(taskFun,FirstTaskTackBuff,0|(FIRST_TASK_TACK_SIZE<<8),0);
// os.run指向空闲任务
osSchedIndex = 0;
os.run = &tcbArray[0];
// 启动任务
OS_ChangeCpuMode(os.run->stack);
_OS_TaskPass();
OS_IdleTask();
}
__weak void OS_IdleTask(void){
// 注意:本列没有实现定时器,所以要主动切换任务,不然就一直运行在这里
// 若要实现定时器,只需要在处理完时间事务后调用OS_PendSV()函数即可切换任务
while(1)
os_pass();
}
复制代码
头文件部分
#include "stdint.h"
// 注意:不能直接使用这个函数,要经过SVC才可以,参考下面的例子,其它函数也一样,请自行举一反三
extern int _OS_TaskCreate(void (*taskFun)(void),void* stk,int prio_stkSize,void* argv);
extern void _OS_TaskPass(void);
/**
* @brief 堆栈定义宏
*/
#define TACK_DEF(pool,size) unsigned long long pool[((size)+7)/8]
#define __SVC_0 __svc_indirect(0)
extern int _os_tsk_create (uint32_t p,void (*task)(void ),void* stk,int prio_stkSize,void* argv) __SVC_0;
extern int _os_tsk_create_ex(uint32_t p,void (*task)(void*),void* stk,int prio_stkSize,void* argv) __SVC_0;
#define os_tsk_create(taskFun,prio,stk,stkSize) \
_os_tsk_create((uint32_t)_OS_TaskCreate,taskFun,stk,prio|(stkSize<<8),(void*)0)
#define os_tsk_create_ex(taskFun,prio,stk,stkSize,argv) \
_os_tsk_create_ex((uint32_t)_OS_TaskCreate,taskFun,stk,prio|(stkSize<<8),argv)
extern void _os_pass (uint32_t p) __SVC_0;
#define os_pass() _os_pass((uint32_t)_OS_TaskPass) // 注意:这只是一个SVC调用的例子,实际上此函数不需要经过SVC
复制代码
应用部分
#include "os.h"
// 配置空闲任务堆栈与第一个任务堆栈的例子,请使用Configuration Wizard窗口,不过此功能仅keil才有
//-------- <<< use configuration wizard in context menu>>> -----------------
//
Idle Task stack size [bytes] <128-4096:128><#/4> // Default: 512
#ifndef OS_STKSIZE
#define OS_STKSIZE 64
#endif
//
First Task stack size [bytes] <128-4096:128><#/4> // Default: 512
#ifndef MAIN_TASK_STKSIZE
#define MAIN_TASK_STKSIZE 256
#endif
//------------- <<< end of configuration section>>> -----------------------
int const IDLE_TASK_TACK_SIZE = OS_STKSIZE*4;
int const FIRST_TASK_TACK_SIZE = MAIN_TASK_STKSIZE*4;
TACK_DEF(IdleTaskTackBuff,IDLE_TASK_TACK_SIZE);
TACK_DEF(FirstTaskTackBuff,FIRST_TASK_TACK_SIZE);
// 以上代码应专门放在一个配置文件里面,随项目一起,
// 以下为应用代码,仅能再创建2个任务,加上空闲任务与第一个任务共4个任务
TACK_DEF(Task1Stk,256);
TACK_DEF(Task2Stk,256);
void Task2(void* argv){
int x = 0;
if((int)argv == 1){
// ...
}else{
// ...
}
while(1){
x++;
os_pass();
}
}
void Task1(void){
int x = 0;
while(1){
x++;
os_pass();
}
}
int main(void){
os_tsk_create(Task1,0,Task1Stk,sizeof(Task1Stk));
os_tsk_create_ex(Task2,0,Task2Stk,sizeof(Task2Stk),(void*)0);
// 此函数退出后,这个任务将会被删除...
}
/**
* @brief 修改lib启动过程,在执行分散加载后由os接管,main函数作为第一个任务
*/
#ifdef __MICROLIB
void _main_init (void) __attribute__((section(".ARM.Collect$00000007")));
__asm void _main_init (void) {
#else
__asm void __rt_entry (void) {
#endif
PRESERVE8
IMPORT main
IMPORT OS_Start
IMPORT __heap_base
IMPORT __heap_limit
#ifdef __MICROLIB
#else
IMPORT __rt_lib_init
LDR R0,=__heap_base
LDR R1,=__heap_limit
BL __rt_lib_init
#endif
LDR R0,=main
BL OS_Start
ALIGN
}
复制代码
以上图文内容均是EEWORLD论坛网友:samos2011 原创,在此感谢。
如有疑问可到EEWORLD论坛或通过微博、微信提出。
欢迎微博@EEWORLD
如果你也写过此类原创干货请关注微信公众号:EEWORLD(电子工程世界)回复“投稿”,也可将你的原创发至:bbs_service@eeworld.com.cn,一经入选,我们将帮你登上头条!
与更多行业内网友进行交流请登陆EEWORLD论坛。