0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

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

STM32嵌入式开发 来源:博客园-Weyne 2023-02-14 16:01 次阅读
先了解下如何使用PendSV异常。为何要使用PendSV而不是其他的异常,请参考《cortex-M3权威指南》。

1 如何设定PendSV优先级?

5016a00a-a9ab-11ed-bfe3-dac502259ad0.png

		
NVIC_SYSPRI14 EQU 0xE000ED22
NVIC_PENDSV_PRI EQU 0xFF
    LDR R0, =NVIC_SYSPRI14 LDR R1, =NVIC_PENDSV_PRI
    STRB R1, [R0]

2 如何触发PendSV异常?

503ae686-a9ab-11ed-bfe3-dac502259ad0.pngICSR第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个寄存器自动入栈。入栈内容及顺序如下:504d85c0-a9ab-11ed-bfe3-dac502259ad0.png在步骤一中,我们已经设置了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)50715004-a9ab-11ed-bfe3-dac502259ad0.png左边表格是预期值,右边是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中值变为0x20000060LDMIA 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
");
            task0_handle=0;
            task1_handle=1;
        }
    }
}


void task1(void)
{
    while(1)
    {
        if(task1_handle==1)
        {
            printf("task1
");
            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
");
    
    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);
}
测试后结果如图:509ecdb8-a9ab-11ed-bfe3-dac502259ad0.png可以看出,两个任务可以切换了。 审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • led
    led
    +关注

    关注

    242

    文章

    23252

    浏览量

    660575
  • 寄存器
    +关注

    关注

    31

    文章

    5336

    浏览量

    120230
  • 中断
    +关注

    关注

    5

    文章

    898

    浏览量

    41470
  • Cortex-M3
    +关注

    关注

    9

    文章

    269

    浏览量

    59463
  • 任务切换
    +关注

    关注

    0

    文章

    4

    浏览量

    6809

原文标题:进入OS前的两步:PendSV(任务切换)

文章出处:【微信号:c-stm32,微信公众号:STM32嵌入式开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    求助,是否可以不用pendSV中断做任务切换

    freeRTOS的任务切换我的理解是:通过SysTick中断去触发pendSV中断去做任务切换,因为pe
    发表于 04-16 07:59

    请问UCOSIII如何切换到新任务

    UCOS/III 是在PendSV中断里切换任务的,最后BXLR(见红色字体),LR是旧任务返回地址, 所以应该跳到旧任务继续执行,为什么会
    发表于 05-09 06:35

    【安富莱】【μCOS-III教程】第5章 任务切换设计

    大家理解,下面是上图的执行流程:1.任务A呼叫SVC来请求任务切换(例如,等待某些工作完成)2.OS接收到请求,做好上下文切换的准备,并且悬
    发表于 01-08 17:40

    stm32单片机移植μc/os时,任务切换函数和中断级切换函数过程不是一样的吗?

    移植μc/os时,任务切换函数即OSCtrSw,中断级任务切换函数为OSIntCtrSw,
    发表于 03-13 07:26

    PC里VC下移植的uc/os-II任务切换切换不回来

    菜鸟一个,想先在PC上熟悉一下uc/os-II ,但是遇到了些问题了,希望熟悉uc/os-II的高手们帮帮忙吧。任务切换什么的完全不会,OSTimeDly()函数貌似在PC上面有很大的
    发表于 06-10 04:36

    请问uc/os任务切换问题该怎么解决?

    uc/os任务切换是通过 堆栈和软中断来实现。假设有如下的任务优先级56 PC ->任务1 PC ->
    发表于 06-12 04:35

    【设计技巧】从单片机到操作系统(6)-FreeRTOS任务切换机制详解

    上下文切换。过程如图所示任务切换的源码实现过程差不多了解了,那看看FreeRTOS中怎么实现吧!!FreeRTOS有种方法触发任务
    发表于 07-31 08:30

    ucos上下文该怎么切换

    的值,那么在pendSV处理结束的时刻我们看到有条 BXLR 指令, 那岂不是又回到了任务A的这个地方呀,怎么能切换到别的任务上去啊??????---->问题2:参见附件:图示中上下文
    发表于 08-26 03:21

    OSCtxSw函数是怎么触发PendSV_Handler函数的?

    , =NVIC_PENDSVSET STRR1, [R0] BXLR[/mw_shl_code]在ucos中,这个汇编代码应该是起到切换任务的作用吧!听说他是通过调用PendSV_Handler这个汇编函数实现
    发表于 09-02 20:27

    SVC和PendSV异常有什么用途?

    等待下一次的OS节拍中断产生时才能再次切换,特别是当中断源与OS节拍中断频率接近时,发生“共振”导致任务无法切换
    发表于 05-12 10:58

    基于两步映射的轮胎花纹曲面造型方法张胜男

    基于两步映射的轮胎花纹曲面造型方法_张胜男
    发表于 03-16 08:00 0次下载

    浅谈RTOS中的多任务切换(基于UC/OS iii)

    浅谈RTOS中的多任务切换(基于UC/OS iii)文章目录浅谈RTOS中的多任务切换(基于UC/OS
    发表于 11-23 18:06 25次下载
    浅谈RTOS中的多<b class='flag-5'>任务</b><b class='flag-5'>切换</b>(基于UC/<b class='flag-5'>OS</b> iii)

    UC/OS-III学习——触发PendSV中断

    文档UC/OS-III学习——触发PendSV中断系列文章目录前言一、关于PendSV的基础知识二、代码1.c语言2.汇编语言前言PendSV典型使用场合是在上下文
    发表于 12-01 14:51 8次下载
    UC/<b class='flag-5'>OS</b>-III学习——触发<b class='flag-5'>PendSV</b>中断

    freeRTOS的任务

    freeRTOS系统的任务切换的具体过程最终都是在pendSV中断服务函数里面完成的。
    的头像 发表于 02-10 11:02 1625次阅读
    freeRTOS的<b class='flag-5'>任务</b>

    两步走 解决开关电源输入过压的烦恼!

    两步走 解决开关电源输入过压的烦恼!
    的头像 发表于 09-27 16:00 932次阅读
    <b class='flag-5'>两步</b>走 解决开关电源输入过压的烦恼!