手写RTOS-使用PendSV进行压栈与出栈操作

学会使用PendSV中断进行压栈和出栈操作,是实现任务调度的关键。今天我们就来学习一下如何使用不超过20行的汇编实现压栈和出栈操作。

我们现在来实现这么一个例子:先把R4-R11通用寄存器的值保存到一个缓冲区里面,然后再把缓冲区里面的值恢复到R4-R11。

实现这个功能的意义在于,在切换一下个任务之前,要先把当前任务的状态先保存起来,然后把要下面要执行的任务状态从缓冲区里面恢复出来。

下面我们就要上一次创建的工程上进行本次实例的讲解

首先,我们先定义一个结构体:

typedef uint32_t StackType_t;

typedef struct tskTaskControlBlock
{
  StackType_t *pxTopOfStack;
}TCB_t;

然后定义一个结构体变量以及结构体指针:

TCB_t *pxCurrentTCB;
TCB_t CurrentTCB;

接下来,我们要对PendSV相关的寄存器进行定义和定义一个写寄存器操作:

#define NVIC_INT_CTRL                    0xE000ED22
#define portNVIC_INT_CTRL_REG            0xE000ED04
#define portNVIC_PENDSVSET_BIT           0x10000000
#define portNVIC_PENDSVSET_PRI           0xFF

#define REG_WRITE_32(ADDRESS)	         (*(volatile unsigned long *)(ADDRESS))
#define REG_WRITE_8(ADDRESS)	         (*(volatile unsigned char *)(ADDRESS))

下面我们来实现一下触发PendSV异常的函数:

void TrigetPendSVC(void)
{
  REG_WRITE_8(NVIC_INT_CTRL) = portNVIC_PENDSVSET_PRI;
  REG_WRITE_32(portNVIC_INT_CTRL_REG) = portNVIC_PENDSVSET_BIT;
}

这个函数很简单,先把PendSV中断设置为最低优先级,然后再触发发PendSV异常。

在main函数中对结构体进行初始化:

CurrentTCB.pxTopOfStack = &puxStackBuffer[1024];
pxCurrentTCB = &CurrentTCB;

最后我们来实现一下PendSV的中断函数:

__asm void PendSV_Handler(void)
{
  extern  pxCurrentTCB;            /*引用外部变量pxCurrentTCB*/
  
  ldr r1, =pxCurrentTCB;           /*读取结构体指针pxCurrentTCB地址的值*/
  ldr r2, [r1];                    /*读取结构体指针pxCurrentTCB指向的值,即CurrentTCB的地址*/
                                   /*也是CurrentTCB.pxTopOfStack的地址*/
  ldr r0, [r2];                    /*读取CurrentTCB.pxTopOfStack指针指向的地址,并赋给r0*/
  
  stmdb r0!, {r4-r11};             /*依次把r11到r4保存到r0指向的缓冲区中*/
  
  str r0, [r2];                    /*再把此时移位后栈顶指针的值赋给CurrentTCB.pxTopOfStack*/
  
  add r4, r4, #1;                  /*测试代码*/
  add r5, r5, #1;                  /*测试代码*/
  
  ldmia r0!, {r4-r11};             /*把r0指向的缓冲区中的值恢复给r11-r4*/
  bx lr;                           /*返回*/
}

现在我们来测试一下我们写的代码是否有问题,进入仿真模式:

我们停在这个触发PendSV中断的地方:

点击下一步看下能否进行中断,能够正常进入

我们再把相关的变量加入到Watch1中进行观察,看下数据是否正确:

第一步,把pxCurrentTCB指针的地址赋给r0,从下图可以看出,操作正常:

 下一步,读出pxCurrentTCB结构体指针所指针向的结构体地址的值,&pxCurrentTCB的值为0x20000004,&CurrentTCB = pxCurrentTCB = 0x20000008,操作正确:

下一步,把CurrentTCB.pxTopOfStack的值赋给r0,即r0=CurrentTCB.pxTopOfStack=&puxStackBuffer[1024];这里要说一下为什么要取puxStackBuffer[1024]的地址,这样不是数据越界了吗?是不是我写错了?其实没有写错,刚开始的时候我也不理解,看到后面指令我就明白了,因为Cotex-M3是满减栈,即操作SP指针的时候,要先把SP-1,然后再对SP指向的地址进行赋值,所以到后面还是会把第一个数写在puxStackBuffer[1023]上,不会越界。

 下一步,把r11-r4依次保存在缓冲区puxStackBuffer数组中,数据可以一一对应,见下图:

 下一步,把缓冲区压栈完成后指向的地址保存到CurrentTCB.pxTopOfStack中,以便下次恢复的时候用,可以看到CurrentTCB.pxTopOfStack的值已经变成0x20000FF0,与r0的值一致:

下面执行一下测试代码,r4=0,r5=0x20001010;我们执行一下加1运算:

下一步,恢复数据,可以看到r4,r5已经变回原来的数值了:

 到这里,使用PendSV保存上下文和恢复的完成了,下一篇说一下任务的切换。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值