软件综合
1
 
自己动手写STM32多任务调度器
张静茹 2015-6-4 19:37:41
首先介绍几个寄存器,这几个寄存器只能在汇编或内联汇编时才能访问,C语言是访问不到的
R0-R12,都可作为临时变量存储,跟C语言的变量差不多,不过汇编中的存储变量是用寄存器,而且不用声明,全局可见,不分全局和局部,而且是32位的
比如想计算1+1,结果放在r0中
mov r0,0x01
add r0,0x01
244054

图片来自互联网,互联网来自<<Cortex-M3权威指南>>


R13(MSP/PSP)堆栈寄存器,汇编指令PUSH,POP会影响R13的值,
PUSH {R0} //R0的值将被压入栈内 R13-4
POP{R1}    //将SP所指向的内存赋值给R1 SP+4
244056



R14 :是连接寄存器(LR),用于在调用子程序时存储返回地址,应该是不能用mov mrs msr访问的,只能用pop push保存
R15 是程序计数器(PC),每条指令前都对应一个地址,把这个地址赋值给R15,程序就立即跳过去执行地址对应的程序
xRPS特殊功能寄存器组 详细内容请参考<<Cortex-M3权威指南>>

之前用过UCOS,可是他每个死循环任务下面必须有一个延时函数,才可以把调度权限交还给系统,自然这个延时函数上面也不可以有死循环,
不然将永远卡在死循环上,别的任务也就不能再被调用了


int main(void)
{       
    GPIO_InitTypeDef  GPIO_InitStructure;
                     
    Stm32_Clock_Init(9); //系统时钟设置
    delay_init(72);      //延时初始化 
    uart_init(72,9600);  //串口初始化为9600
    LED_Init();          //初始化与LED连接的硬件接口  
    SysTick_Configuration();
    OSInit();
    OSTaskCreate( TaskStart,    //task pointer
                    (void *)0,  //parameter
                    (OS_STK *)&TASK_START_STK[START_STK_SIZE-1],    //task stack top pointer
                    START_TASK_Prio );  //task priority
                         
    OSStart();
    return 0;      
}
//开始任务
void TaskStart(void * pdata)
{
    pdata = pdata; 
    OS_ENTER_CRITICAL();   
    OSTaskCreate(TaskLed, (void * )0, (OS_STK *)&TASK_LED_STK[LED_STK_SIZE-1], LED_TASK_Prio);
    OSTaskCreate(TaskLed1, (void * )0, (OS_STK *)&TASK_LED1_STK[LED1_STK_SIZE-1], LED1_TASK_Prio);
    OSTaskSuspend(START_TASK_Prio); //suspend but not delete
    OS_EXIT_CRITICAL();
}
//任务1
//控制DS0的亮灭.
void TaskLed(void *pdata)
{
    while(1)
    {
        LED0 = !LED0;
        OSTimeDlyHMSM(0,0,1,100);   
    }
}
//任务2
//控制DS1的亮灭.
void TaskLed1(void *pdata)
{
    while(1)
    {
        LED1 = !LED1;
        OSTimeDlyHMSM(0,0,1,300);   
    }
}








正文开始:
我写的这个调度系统是用stm32 定时器中断调度任务的,任务中不需要ucos那种延时函数,定时器每隔一段时间中断当前任务,保存现场,恢复第二个任务的现场,这时PC寄存器被改为第二个任务上次中断前执行的位置然后退出中断,,从任务二上次中断前的位置继续执行
调度原理:
参考<<Cortex-M3权威指南>>中 第九章 中断的具体行为
当 C M 3开始响应一个中断时内核会自动 把 8个寄存器(R0-R3,R12,LR,PC,xPSR)的值压入栈,最关键的是PC,他直接决定了中断退出以后开始执行的位置,R0-R3,R12则保存了一些中间变量,保证了恢复现场以后程序正确执行


程序流程:                                                                       标志:↓↓↓
进入main() ----> 初始化GPIO,时钟,定时器,开中断 ---->进入任务0 ---->定时器中断时间到 ---->开始进入中断 ---->系统自动8个寄存器(R0-R3,R12,LR,PC,xPSR)的值压入栈 ---->进入中断函数TIM3_IRQHandler(此时SP堆栈指针正指向R0,R0+4后,指向R1)
TIM3_IRQHandler PROC
        PUSH     {r4,lr}
        MRS      r4,MSP
        MOV      r0,r4
        ADD      r0,r0,#8
        MOV      r4,r0
        LDR      r0,|L0.640|
        STR      r4,[r0,#0]
        BL       IRQHandler
        POP      {r4,pc}
        ENDP
  ----->生成汇编文件后可以看到,进入TIM3_IRQHandler函数先把R4和LR压栈(这是编译器自动做的),MSP堆栈指针 - 8   ----->MRS      r4,MSP 保存栈指针   ----->指针+8(对前面R4和LR的补偿)对准 8个被自动压栈的寄存器的R0   ----->保存指针到全局变量Address_BASE   ----->bl    IRQHandler  调用任务调度程序   ----->清除中断待处理位   ----->根据之前保存的栈地址,加载8个寄存器保存当前任务现场   ----->   调用任务二的地址----->  task->Start_Next(); 更新任务标志----->   退出中断 并 恢复被修改了返回地址的8个寄存器 ----->  执行任务1----->   定时器中断时间到 ---->进入中断  -----> 保存任务1现场----->   恢复任务0  ----->退出中断  ----->goto 标志;


#include "../h/main.h"
                
extern Task *task;
int main(void)
{
                
    task = new Task(); //创建任务管理对象 这是一个C++类对象
                    
    Init();//初始化 时钟 串口 GPIO
    Timerx_Init(20,7199);//初始化TIM3,开中断,每2ms进一次中断
    TIM3->CNT = 0x01;//意义不明 不要也行
    __asm  //內联汇编
    {
        bl Task0 //跳转到任务1 
    }
//  while (true);
//  delete task;
}
int temp = 0;
void Task0(void) //任务1
{
    while (true)
    {
        LED0 = !LED0;
    }
}
                
void Task1(void) //任务2
{
    while (true)
    {
        LED1 = !LED1;
    }
}

class Task //任务管理类
{
public:Task(void) //构造函数,初始化时会自动调用
    {
        Reg_Buff[0][6]=((unsigned int)&Task0) ; //初始化任务0的指针
        Reg_Buff[1][6]=((unsigned int)&Task1) ; //初始化任务1的指针
        Reg_Buff[0][6]=Reg_Buff[1][7]=0x61000000 ; //初始化xRSP
                
        Current = 0; 
        Next = 1;
    }
    public: static const unsigned char Count = Task_Count;
    public: unsigned char Current; //当前任务
    public: unsigned char Next ;    //下一个任务
    public: volatile unsigned int Reg_Buff[Task_Count][8];
    public: void    Start_Next() //更新至下一个任务
    {
        (Current + 1 < Count) ? Current++ : Current = 0;
        (Next + 1 < Count) ? Next++ : Next = 0;
                
//      if (Next != 0 && (Next - Current) != 1)
//      {
//          while (true)
//          {
                
//          }
//      }
    }
                
};
Task *task ;
unsigned int Address_BASE = 0;
                
void IRQHandler(void) 
{
                    
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源 
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);  //清除TIMx的中断待处理位:TIM 中断源
        __asm
        {
            ldr r5, [Address_BASE]  
            str r5, [&task->Reg_Buff[task->Current][0]]//R0
                
            ldr r5, [Address_BASE , 0x4] 
            str r5, [&task->Reg_Buff[task->Current][1]]//R1
                
            ldr r5, [Address_BASE , 0x8] 
            str r5, [&task->Reg_Buff[task->Current][2]]//R2
                
            ldr r5, [Address_BASE , 0xc] 
            str r5, [&task->Reg_Buff[task->Current][3]]//R3
                
            ldr r5, [Address_BASE , 0x10]
            str r5, [&task->Reg_Buff[task->Current][4]]//R12
                
//          ldr r5, [Address_BASE , 0x14]
//          str r5, [&task->Reg_Buff[task->Current][5]]//R13 LR
                
            ldr r5, [Address_BASE , 0x18]
                
            str r5, [&task->Reg_Buff[task->Current][6]]//R14 PC
                
            ldr r5, [Address_BASE , 0x1c]
            str r5, [&task->Reg_Buff[task->Current][7]]//R15 xRSP
                            
            /*↑↑↑保存当然运行中的任务现场↑↑↑*/
                
            ldr r5, [&task->Reg_Buff[task->Next][0]]//R0
            str r5, [Address_BASE]
                
            ldr r5, [&task->Reg_Buff[task->Next][1]]//R1
            str r5, [Address_BASE, 0x4]
                
            ldr r5, [&task->Reg_Buff[task->Next][2]]//R2
            str r5, [Address_BASE, 0x8]
                
            ldr r5, [&task->Reg_Buff[task->Next][3]]//R3
            str r5, [Address_BASE, 0xc]
                
            ldr r5, [&task->Reg_Buff[task->Next][4]]//R12
            str r5, [Address_BASE, 0x10]
                
//          ldr r5, [&task->Reg_Buff[task->Next][5]]//R13 LR
//          str r5, [Address_BASE, 0x14]
                
            ldr r5, [&task->Reg_Buff[task->Next][6]]//R14 PC
            //orr r5, 0x01
            str r5, [Address_BASE, 0x18]
                
            ldr r5, [&task->Reg_Buff[task->Next][7]]//R15 xRSP
            str r5, [Address_BASE, 0x1c]
            /*↑↑↑恢复上一个任务的现场↑↑↑*/
        }
        task->Start_Next(); //下一个任务
    }
}
extern "C"
{
    void TIM3_IRQHandler(void)   //TIM3中断  中断中不能有太多东西,否则进中断时压栈太多 MSP不容易计算
    {
        __ASM
        {
            mrs r4, msp
            add r4, 0x08
            str r4, [&Address_BASE]
            bl    IRQHandler
        }
    }
                
}

[修改于 4 年前 - 2015-06-05 14:26:47]

2015-6-4 20:38:16
1楼
0
矣,沙发是我dee
张静茹(作者)
2楼
0
本文关于调度程序基本属于原创,多个高手帮我人肉DEBUG,谢谢啦

本文有任何错误或程序有BUG请指正
3楼
0
妹子是个搞科研的料
4楼
0
C++中public一个就够了,想全部public的话也可以直接把class改成struct。
张静茹(作者)
5楼
0
引用 acmilan:
C++中public一个就够了,想全部public的话也可以直接把class改成struct。
C#写习惯了
6楼
0
非常好的文章, 单CPU的多任务调度和实现讲解得很详细.
7楼
0
妹子代码写得不错,话说现在老板让我写都不一定写得出。。。。。特别是这种涉及到汇编的代码
8楼
0
搞技术的好料子,现在在忙什么具体的项目?
9楼
0
堆得一手好代码
10楼
0
写代码的时候要统筹这么多东西,大脑的内存需求一定挺高。
11楼
0
模拟中断返回法
在dos中也可以实现,在win32下由于不能直接挂接中断,timer调用堆栈又过长,所以不大可行
win32下可以用这种方法实现异函数返回,但是要把编译器优化关掉,不然函数搞成内联或不使用ebp就爽了

[修改于 4 年前 - 2015-06-05 16:01:37]

12楼
0
引用 acmilan:
模拟中断返回法
在dos中也可以实现,在win32下由于不能直接挂接中断,timer调用堆栈又过长,所以不大可行
win32下可以用这种方法实现异函数返回,但是要把编译器优化关掉,不然函数搞成内联或不使用ebp就爽了
Win32下中断是可以接管的。IDT hook或者hook KiTrapXX
13楼
0
引用 phpskycn:
Win32下中断是可以接管的。IDT hook或者hook KiTrapXX
怎么做?
14楼
0
引用 acmilan:
怎么做?
R0下直接改IDT或者KiTrapXX头部。
自己的逻辑中注意同步问题即可
15楼
0
引用 phpskycn:
R0下直接改IDT或者KiTrapXX头部。
自己的逻辑中注意同步问题即可
还是R3下没法做→_→
16楼
0
引用 acmilan:
还是R3下没法做→_→
……R3下当然不可能了
17楼
0
作为美术科班出身,看见汇编就蛋疼。
张静茹(作者)
18楼
0
引用 acmilan:
模拟中断返回法
在dos中也可以实现,在win32下由于不能直接挂接中断,timer调用堆栈又过长,所以不大可行
win32下可以用这种方法实现异函数返回,但是要把编译器优化关掉,不然函数搞成内联或不使用ebp就爽了
我以为我的办法是歪门邪道呢,原来还有名字呀,还有什么嵌入式OS是这种办法吗,UCOS那样的调度觉得不好玩
19楼
0
很巧,今天工作解了一个问题发现也是和CPU调度算法有关的。囧[s::L]

今天遇到一个3D游戏只能跑30多fps,我和同事这边使用软件分析了,发现性能的瓶颈不在GPU、居然是在CPU!这还是头一次见到这种现象。
这个3D游戏在运行的时候被android系统的SurfaceFlinger服务抢占,运行的时候帧率就会降低,而且不但如此,还会因为SMP策略的改变
切换到其他CPU去运行,这样搞来搞去,导致这个游戏运行的帧率低下。

后面我和同事调整了一下cpu热插拔的命令,让这个游戏固定在一个CPU上去跑,帧率一下子就上升到50fps去了。。。

CPU调度和SMP还是有很多学问在里面的。。。。。。244087

20楼
0
看见汇编就傻眼了唉。。。
21楼
0
我还是要好好学
22楼
0
真厉害啊,隔行如隔山啊,每行都研究很深。
2015-6-9 22:49:36
23楼
0
大赞。。。。。。。表示准备用一个暑假拿下嵌入式linux呵呵SOC FPGA了,敬请期待~~~
张静茹(作者)
24楼
0
引用 yanli12321:
大赞。。。。。。。表示准备用一个暑假拿下嵌入式linux呵呵SOC FPGA了,敬请期待~~~
期待大作
25楼
0
ucos是基于抢占式的调度算法,我认为作为实时系统这是优势
张静茹(作者)
26楼
0
引用 hackerboygn:
ucos是基于抢占式的调度算法,我认为作为实时系统这是优势
我倒是没看过ucos的源码,觉得实时性应该差不多吧,都没啥实时性,想要有实时性都可以依靠中断
27楼
0
楼主辛苦了。头像很可爱。
2015-6-12 23:16:46
28楼
0
钻研精神不错!不过你这个就没有任务优先级可言了,每个任务从头到尾来一遍再从头到尾往复,如果任务中有时序要求严格的代码就必须关中断调度,ucos的方式就好一些,其实ucos也可以不用delay直接死循环的,高优先级的可以抢占的优先级的任务
张静茹(作者)
29楼
0
引用 超级玛丽:
钻研精神不错!不过你这个就没有任务优先级可言了,每个任务从头到尾来一遍再从头到尾往复,如果任务中有时序要求严格的代码就必须关中断调度,ucos的方式就好一些,其实ucos也可以不用delay直接死循环的,高优先级的可以抢占的优先级的任务
中断给予每个中断相同的CPU时间,如果需要优先级 也可以设置,或者分配不同的权重

想参与大家的讨论?现在就 登录 或者 注册

千古风流
专栏收藏夹发私信
学术分 1科创币 55.56总主题 121 帖总回复 1627 楼拥有证书:会员 学者 机友 笔友
注册于 2010-12-30 03:32最后登录 2019-04-24 17:02

个人简介

暂未填写

Github  https://github.com/kccd/nkc.git

科创研究院 (c)2001-2019

蜀ICP备11004945号-2 川公网安备51010802000058号