解剖8051内核如何进行多任务切换

最近在玩新唐单片机,这个跟我之前用的51内核是一样的,然后今天觉得跑下多任务,自己研究了下,跟几个同学还讨论了,发现有些人对切换过程还不是十分明白,所以发个文章出来。

直接上代码

#include "MS51_16K.h"

/*
 * UART0 initial setting
 * include sys.c in Library for modify HIRC value to 24MHz
 * include uart.c in Library for UART initial setting
 */
void initialize_UART0(void)
{
    MODIFY_HIRC(HIRC_24);
    P06_PUSHPULL_MODE;
    UART_Open(24000000,UART0_Timer3,115200);
    ENABLE_UART0_PRINTF;
}

void delay_ms(unsigned int n)
{
    unsigned int i;
    for(i=0;i<n;i++)
        _delay_();
}

void Timer_ISR (void)   interrupt 17     //ISR for self wake-up timer
{
    _push_(SFRS);
    clr_WKTF;                                   //clear interrupt flag
    _pop_(SFRS);
}

void initialize_Timer(void)
{
    WKCON = 0x00;                     //timer base 10k, Pre-scale = 1/16
    //RWK = 0XFF;                     //  if prescale is 0x00, never set RWK = 0xff
    RWK = 0X00;
    ENABLE_WKT_INTERRUPT;             // enable WKT interrupt
    ENABLE_GLOBAL_INTERRUPT;
    set_EIPH1_PWKTH;
    set_WKCON_WKTR;
}


#define MAX_TASKS 2       /*任务槽个数.必须和实际任务数一至*/
#define MAX_TASK_DEP 100   /*最大栈深.最低不得少于2个,保守值为12*/
unsigned char idata task_stack[MAX_TASKS][MAX_TASK_DEP];/*任务堆栈.*/
unsigned int task_id;    /*当前活动任务号*/
unsigned int max_task = 0;

unsigned char idata task_sp[MAX_TASKS];

void task_switch()
{
    task_sp[task_id] = SP;
    if(++task_id == max_task)
            task_id = 0;
    SP = task_sp[task_id];
}

void task_load(unsigned int fn, int tid)
{
    task_sp[tid] = task_stack[tid]+1;
    task_stack[tid][0] = (unsigned int)fn & 0xff;
    task_stack[tid][1] = (unsigned int)fn >> 8;
    ++max_task;
}

void task1()
{
    static unsigned char i;
    printf("task,SP:%x\n",(int)SP);
    while(1)
    {
        i++;
        printf("task#1\n");
        delay_ms(100);
        task_switch();
    }
}

void task2()
{
    static unsigned char j;
    while(1)
    {
        j+=2;
        printf("task#2\n");
        delay_ms(100);
        task_switch();
    }
}

void switch_to(unsigned int tid)
{
    task_id = tid;
    SP = task_sp[tid];
    return;
}

void main(void)
{
    initialize_UART0();
    Disable_WDT_Reset_Config();
    printf("~~~~~~~~~~~~~~~~MainStart...\n");

    task_load(task1, 0);//将task1函数装入0号槽
    task_load(task2, 1);//将task2函数装入1号槽
    switch_to(0);

    printf("~~~~~~~~~~~~~~~~MainEnd...\n"); 
}

单片机运行输出

代码很简单,就是两个任务进行不断的切换,每个任务进行相应时间的延迟。

先说下第一个函数

把任务函数转载到二维数组保存起来,而且任务函数地址和任务的tid编号要对应。

void task_load(unsigned int fn, int tid)
{
    task_sp[tid] = task_stack[tid]+1;
    task_stack[tid][0] = (unsigned int)fn & 0xff;
    task_stack[tid][1] = (unsigned int)fn >> 8;
    ++max_task;
}

task_sp 用来表示任务的数组

task_stack 用来保存任务函数的地址

说下这行代码

task_sp[tid] = task_stack[tid]+1;

后面的 + 1 ,不知道大家有没有疑惑。

+1 简单理解就是指向了下一个位置

再解剖第二个函数,等下你就知道这个作用的奇特

用来做任务的切换,先保存之前运行的任务函数地址,再改变任务id,把对应任务id的函数地址赋值给SP。

void task_switch()
{
    task_sp[task_id] = SP;
    if(++task_id == max_task)
            task_id = 0;
    SP = task_sp[task_id];
}

它是妙处不是在这个函数的本身,而是只有比较深入的了解函数调用的过程,才明白其中的奥妙。

SP 是堆栈指针,用来保存当前堆栈的位置

上面的函数是在进入函数的时候,把当前堆栈的值保存在 stak_sp 中,然后更改stak_sp 的值,再赋值给SP。

说如何切换吧

void switch_to(unsigned int tid)
{
    task_id = tid;
    SP = task_sp[tid];
    return;
}

调用函数 switch_to(0) 之前 堆栈和PC指针是这样的

调用函数 switch_to(0) 之后

我们需要把PC之前的值,保存在SP里面,然后呢,PC就开始执行switch_to函数体里面的内容。

然后,改变SP的值,让SP的值等于需要执行函数的地址

函数退出的时候,PC指针又会从SP堆栈位置拿到之前保存的那个地址「实际上已经被我们修改了」,去继续执行。

就是通过这样不断的切换,完成了多个函数交换执行。

这是最基本的多任务系统,代码也不是非常完整,喜欢研究的同学,可以再看看网上的例程。

我这次用的是芯唐 MS51FB9AE 芯片。

有做这方便的同学,欢迎一起讨论~


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值