进入OS前的两步之PendSV(任务切换)

先了解下如何使用PendSV异常。(为何要使用PendSV而不是其他的异常,请参考《cortex-M3权威指南》)

1,如何设定PendSV优先级?

NVIC_SYSPRI14 EQU 0xE000ED22

NVIC_PENDSV_PRI EQU 0xFF

LDR R0, =NVIC_SYSPRI14 LDR R1, =NVIC_PENDSV_PRI

STRB R1, [R0]

2,如何触发PendSV异常?

往ICSR第28位写1,即可将PendSV异常挂起。若是当前没有高优先级中断产生,那么程序将会进入PendSV handler

NVIC_INT_CTRL EQU 0xE000ED04

NVIC_PENDSVSET EQU 0x10000000

LDR R0, =NVIC_INT_CTRL

LDR R1, =NVIC_PENDSVSET

STR R1, [R0]

3,编写PendSV异常handler

这里用PendSV_Handler来触发LED点亮,以此证明PendSV异常触发的设置是正确的。

#include "stm32f10x_conf.h"

#define LED0 *((volatile unsigned long *)(0x422101a0)) //PA8

unsigned char flag=0;

void LEDInit(void)

{

RCC->APB2ENR|=1<<2;

GPIOA->CRH&=0XFFFFFFF0;

GPIOA->CRH|=0X00000003;

     GPIOA->ODR|=1<<8;

}

__asm void SetPendSVPro(void)

{

NVIC_SYSPRI14 EQU 0xE000ED22

NVIC_PENDSV_PRI EQU 0xFF

    LDR R1, =NVIC_PENDSV_PRI    

    LDR R0, =NVIC_SYSPRI14

    STRB R1, [R0]

    BX LR

}

__asm void TriggerPendSV(void)

{

NVIC_INT_CTRL EQU 0xE000ED04

NVIC_PENDSVSET EQU 0x10000000

    LDR R0, =NVIC_INT_CTRL

    LDR R1, =NVIC_PENDSVSET

    STR R1, [R0]

    BX LR

}

int main(void)

{

    SetPendSVPro();

    LEDInit();

    TriggerPendSV();

    while(1);

}

void PendSV_Handler(void)

{

    LED0=0;

}

上述代码可以正常点亮LED,说明PendSV异常是正常触发了。

OK,是时候挑战任务切换了。

如何实现任务切换?三个步骤:

步骤一:在进入中断前先设置PSP。

curr_task = 0;

设置任务0为当前任务

__set_PSP((PSP_array[curr_task] + 16*4));

设置PSP指向task0堆栈的栈顶位置

__set_CONTROL(0x3);

设置为用户级,并使用PSP堆栈。

__ISB();

指令同步隔离,暂不知道干啥用

步骤二:将当前寄存器的内容保存到当前任务堆栈中。进入ISR时,cortex-m3会自动保存八个寄存器到PSP中,剩下的几个需要我们手动保存。

步骤三:在Handler中将下一个任务的堆栈中的内容加载到寄存器中,并将PSP指向下一个任务的堆栈。这样就完成了任务切换。

要在PendSV 的ISR中完成这两个步骤,我们先需了解下在进入PendSV ISR时,cortex-M3做了什么?

1,入栈。会有8个寄存器自动入栈。入栈内容及顺序如下:

在步骤一中,我们已经设置了PSP,那这8个寄存器就会自动入栈到PSP所指地址处。

2,取向量。找到PendSV ISR的入口地址,这样就能跳到ISR了。,

3,更新寄存器内容

做完这三步后,程序就进入ISR了。

进入ISR前,我们已经完成了步骤一,cortex-M3已经帮我们完成了步骤二的一部分,剩下的需要我们手动完成。

在ISR中添加代码如下:

MRS R0, PSP

保存PSP到R0。为什么是PSP而不是MSP。因为在OS启动的时候,我们已经把SP设置为PSP了。这样使得用户程序使用任务堆栈,OS使用主堆栈,不会互相干扰。不会因为用户程序导致OS崩溃。

STMDB R0!,{R4-R11}

保存R4-R11到PSP中。C语言表达是*(--R0)={R4-R11},R0中值先自减1,然后将R4-R11的值保存到该值所指向的地址中,即PSP中。

STMDB Rd!,{寄存器列表} 连续存储多个字到Rd中的地址值所指地址处。每次存储前,Rd先自减一次。

若是ISR是从从task0进来,那么此时task0的堆栈中已经保存了该任务的寄存器参数。保存完成后,当前任务堆栈中的内容如下(假设是task0)

左边表格是预期值,右边是keil调试的实际值。可以看出,是一致的。在任务初始化时(步骤一),我们将PSP指向任务0的栈顶0x20000080。在进入PendSV之前,cortex-M3自动入栈八个值,此时PSP指向了0x20000060。然后我们再保存R4-R11到0x20000040~0x2000005C。

这样很容易看明白,如果需要下次再切换到task0,只需恢复R4~R11,再将PSP指向0x20000060即可。

所以切换到另一个任务的代码:

LDR R1,=__cpp(&curr_task)

LDR R3,=__cpp(&PSP_array)

LDR R4,=__cpp(&next_task)

LDR R4,[R4]

获取下一个任务的编号

STR R4,[R1]

Curr_task=next_task

LDR R0,[R3, R4, LSL #2]

获得任务堆栈地址,若是task0,那么R0=0x20000040( R0=R3+R4*4)

LDMIA R0!,{R4-R11}

恢复堆栈中的值到R4~R11。R4=*(R0++)。执行完后,R0中值变为0x20000060

LDMIA Rd! {寄存器列表} 先将Rd中值所指地址处的值送出寄存器中,Rd再自增1.

MSR PSP, R0

PSP=R0。

BX LR

中断返回

完整代码如下:

#include "stm32f10x.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stdio.h"
#include "misc.h"

#define HW32_REG(ADDRESS)  (*((volatile unsigned long  *)(ADDRESS)))
#define LED0 *((volatile unsigned long *)(0x422101a0)) //PA8
void USART1_Init(void);
void task0(void) ;
unsigned char flag=1;

uint32_t  curr_task=0;     // 当前执行任务
uint32_t  next_task=1;     // 下一个任务
uint32_t task0_stack[17];
uint32_t task1_stack[17];
uint32_t  PSP_array[4];

u8 task0_handle=1;
u8 task1_handle=1;

void task0(void)
{
    while(1)
    {
        if(task0_handle==1)
        {
            printf("task0\n");
            task0_handle=0;
            task1_handle=1;
        }
    }
}

void task1(void)
{
    while(1)
    {
        if(task1_handle==1)
        {
            printf("task1\n");
            task1_handle=0;
            task0_handle=1;
        }
    }
}

void LEDInit(void)
{
    RCC->APB2ENR|=1<<2;
    GPIOA->CRH&=0XFFFFFFF0;
    GPIOA->CRH|=0X00000003;
      GPIOA->ODR|=1<<8;
}

__asm void SetPendSVPro(void)
{
NVIC_SYSPRI14   EQU     0xE000ED22
NVIC_PENDSV_PRI EQU           0xFF

    LDR     R1, =NVIC_PENDSV_PRI
    LDR     R0, =NVIC_SYSPRI14
    STRB    R1, [R0]
    BX      LR

}

__asm void TriggerPendSV(void)
{
NVIC_INT_CTRL   EQU     0xE000ED04
NVIC_PENDSVSET  EQU     0x10000000                              

    LDR     R0, =NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR
}

int main(void)
{
    USART1_Init();

    SetPendSVPro();
    LEDInit();

    printf("OS test\n");

    PSP_array[0] = ((unsigned int) task0_stack) + (sizeof task0_stack) - 16*4;
    //PSP_array中存储的为task0_stack数组的尾地址-16*4,即task0_stack[1023-16]地址
    HW32_REG((PSP_array[0] + (14<<2))) = (unsigned long) task0; /* PC */
    //task0的PC存储在task0_stack[1023-16]地址  +14<<2中,即task0_stack[1022]中
    HW32_REG((PSP_array[0] + (15<<2))) = 0x01000000;            /* xPSR */

    PSP_array[1] = ((unsigned int) task1_stack) + (sizeof task1_stack) - 16*4;
    HW32_REG((PSP_array[1] + (14<<2))) = (unsigned long) task1; /* PC */
    HW32_REG((PSP_array[1] + (15<<2))) = 0x01000000;            /* xPSR */    

        /* 任务0先执行 */
    curr_task = 0; 

    /* 设置PSP指向任务0堆栈的栈顶 */
    __set_PSP((PSP_array[curr_task] + 16*4)); 

    SysTick_Config(9000000);
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);//72/8=9MHZ
    /* 使用堆栈指针,非特权级状态 */
    __set_CONTROL(0x3);

    /* 改变CONTROL后执行ISB (architectural recommendation) */
    __ISB();

    /* 启动任务0 */
    task0();
    //LED0=0;
    while(1);
}

__asm void PendSV_Handler(void)
{ 

    // 保存当前任务的寄存器内容
    MRS    R0, PSP     // 得到PSP  R0 = PSP
                       // xPSR, PC, LR, R12, R0-R3已自动保存
    STMDB  R0!,{R4-R11}// 保存R4-R11共8个寄存器得到当前任务堆栈

    // 加载下一个任务的内容
    LDR    R1,=__cpp(&curr_task)
    LDR    R3,=__cpp(&PSP_array)
    LDR    R4,=__cpp(&next_task)
    LDR    R4,[R4]     // 得到下一个任务的ID
    STR    R4,[R1]     // 设置 curr_task = next_task
    LDR    R0,[R3, R4, LSL #2] // 从PSP_array中获取PSP的值
    LDMIA  R0!,{R4-R11}// 将任务堆栈中的数值加载到R4-R11中
  //ADDS   R0, R0, #0x20
    MSR    PSP, R0     // 设置PSP指向此任务
 // ORR     LR, LR, #0x04
    BX     LR          // 返回
                       // xPSR, PC, LR, R12, R0-R3会自动的恢复
    ALIGN  4
}

void SysTick_Handler(void)
{
    flag=~flag;
    LED0=flag;
    if(curr_task==0)
        next_task=1;
    else
        next_task=0;
    TriggerPendSV();
}

void USART1_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    /* config USART1 clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    /* USART1 GPIO config */
    /* Configure USART1 Tx (PA.09) as alternate function push-pull */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    /* Configure USART1 Rx (PA.10) as input floating */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* USART1 mode config */
    USART_InitStructure.USART_BaudRate = 9600;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No ;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}

int fputc(int ch, FILE *f)
{

  USART_SendData(USART1, (unsigned char) ch);
  while (!(USART1->SR & USART_FLAG_TXE));

  return (ch);
}

测试后结果如图:

可以看出,两个任务可以切换了。

上述代码参考《cortex-M3权威指南》和《安富莱_STM32-V5开发板_μCOS-III教程》得来。

时间: 2024-11-03 22:27:55

进入OS前的两步之PendSV(任务切换)的相关文章

进入OS前的两步之System tick

OK,继续向操作系统迈进.由简入繁,先实现两个小功能.第一个是system tick,第二个是任务切换(PendSV).一个是操作系统的心跳,一个是操作系统的并发处理的具体实现. System tick,在裸机程序里面,这个功能一直是用来做延时功能用的,用STM32做项目,基本都会用到.这里不准备再从头去实现这个功能,而是直接用ST官方的库文件.库文件版本为3.5.0. 主要用到两个函数,第一个为 static __INLINE uint32_t SysTick_Config(uint32_t

混血妆容教程 妆前两步排水肿不脱妆

混血妆容教程 妆前两步排水肿不脱 http://baozoumanhua.com/users/13998545/forum_articleshttp://baozoumanhua.com/users/13998544/forum_articleshttp://baozoumanhua.com/users/13998551/forum_articleshttp://baozoumanhua.com/users/13998554/forum_articleshttp://baozoumanhua.c

挑子学习笔记:两步聚类算法(TwoStep Cluster Algorithm)——改进的BIRCH算法

转载请标明出处:http://www.cnblogs.com/tiaozistudy/p/twostep_cluster_algorithm.html 两步聚类算法是在SPSS Modeler中使用的一种聚类算法,是BIRCH层次聚类算法的改进版本.可以应用于混合属性数据集的聚类,同时加入了自动确定最佳簇数量的机制,使得方法更加实用.本文在学习文献[1]和“IBM SPSS Modeler 15 Algorithms Guide”的基础上,融入了自己的理解,更详尽地叙述两步聚类算法的流程和细节.

MongoDB 操作手册CRUD 事务 两步提交

执行两步提交 概述 这部分提供了多记录更新或者多记录事务,使用两步提交来完成多记录写入的模板.另外,可以扩展此方法来提供rollback-like功能. 背景 MongoDB对于单条记录的操作是原子性的:但是涉及多条记录的操作却不是原子性的.由于记录可能是相当复杂,并且有内嵌记录,单记录原子性操作提供了实际中常用的必要支持. 除了单记录的原子性操作,还有许多情况需要多记录操作事务,当执行一个包含一些列操作的事务时,就有以下要求: 原子性:如果一个操作失败,事务中之前的操作需要回滚到之前的状态 一

用模板元实现50个台阶问题,一次走一步或者两步或者3步,用模板元实现求裴波那契额数列

 1.用模板元实现50个台阶问题,一次走一步或者两步或者3步 2.分析 由上面分析可以知道,到达N(N > 3)级台阶时的次数为:目标台阶的前3个台阶分别直接到目标台阶的次数总和. 3.模板元把在运行时消耗的时间,在编译器键优化. 4.通过模板元实现的代码如下: #include <iostream> /*这里是int 类型的,N表示台阶数量*/ template<int N> struct data { enum { res = data<N - 1>::r

基于两步聚类的离群点检测

转载请标明出处:http://www.cnblogs.com/tiaozistudy/p/anomaly_detection.html 本文主要针对IBM SPSS Modeler 18.0中离群点检测算法的原理以及“异常”节点(见图1)使用方法进行说明.SPSS Modeler中的离群点检测算法思想主要基于聚类分析.如图2所示,可先将图中样本点聚成三类,$A$.$B$和$C$三个样本点应分别属于距离他们最近的类,但与相对类内的其他样本点,这三个点又分别远离各自的类,所以可以基于此判定是离群点.

只需两步快速获取微信小程序源码

只需两步快速获取微信小程序源码 第一次在掘金这样高大上的社区写文章,忐忑地敲下我获取小程序源码过程中的经验分享. 最近在学习微信小程序开发,半个月学习下来,很想实战一下踩踩坑,于是就仿写了某个小程序的前端实现,过程一言难尽,差不多两周时间过去了,发现小程序的坑远比想象的要多的多!!在实际练手中,完全是黑盒的,纯靠推测,部分效果在各种尝试后能能做出大致的实现,但是有些细节,实在不知道如何去实现.这种时候,真的很想一窥源码,查看究竟,看看大厂的前端大神们是如何规避了小程序的一些比较奇葩的坑. 于是就

两步快速获取小程序源码

最近在学习微信小程序开发,半个月学习下来,很想实战一下踩踩坑,于是就仿写了某个小程序的前端实现,在实际练手中,完全是黑盒的,纯靠推测,部分效果在各种尝试后能能做出大致的实现,但是有些细节,实在不知道如何去实现.这种时候,真的很想一窥源码,查看究竟,看看大厂的前端大神们是如何规避了小程序的一些比较奇葩的坑. 于是就想到获取到小程序地源文件,然后尝试对其进行反编译还原为源代码,来作为学习参考.我百度了各种关于小程序地反编译教程,但是感觉都不太适合像我这样地初学小白,踩了挺多坑.在这里把我重新简化好的

华为机试题 N阶楼梯的走法,每次走一步或者两步

在Stairs函数中实现该功能: 一个楼梯有N阶,从下往上走,一步可以走一阶,也可以走两阶,有多少种走法? (0<n<=30)<> 例如3阶楼梯有3种走法: 1.1.1 1.2 2.1 输入样例: 3 返回值样例: 3 思路:这是最典型的类似斐波那契数列的变型.N阶楼梯,第一步有两种走法,1.走一步,则剩下N-1级      2,走两步,剩下N-2级      所以f(n)=f(n-1)+f(n-2) public static int ways(int n){ if(n==1)