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

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

3天内不再提示

如何设定PendSV优先级?

技术让梦想更伟大 来源:技术让梦想更伟大 作者:技术让梦想更伟大 2022-12-05 11:38 次阅读

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

1,如何设定PendSV优先级?

37b73aaa-7432-11ed-8abf-dac502259ad0.png
NVIC_SYSPRI14EQU0xE000ED22
NVIC_PENDSV_PRIEQU0xFF
LDRR0,=NVIC_SYSPRI14LDRR1,=NVIC_PENDSV_PRI
STRBR1,[R0]

2,如何触发PendSV异常?

37c38d64-7432-11ed-8abf-dac502259ad0.png

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

NVIC_INT_CTRLEQU0xE000ED04
NVIC_PENDSVSETEQU0x10000000

LDRR0,=NVIC_INT_CTRL
LDRR1,=NVIC_PENDSVSET
STRR1,[R0]

3,编写PendSV异常handler

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

#include"stm32f10x_conf.h"

#defineLED0*((volatileunsignedlong*)(0x422101a0))//PA8

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

__asmvoidSetPendSVPro(void)
{
NVIC_SYSPRI14EQU0xE000ED22
NVIC_PENDSV_PRIEQU0xFF
LDRR1,=NVIC_PENDSV_PRI
LDRR0,=NVIC_SYSPRI14
STRBR1,[R0]
BXLR
}

__asmvoidTriggerPendSV(void)
{
NVIC_INT_CTRLEQU0xE000ED04
NVIC_PENDSVSETEQU0x10000000
LDRR0,=NVIC_INT_CTRL
LDRR1,=NVIC_PENDSVSET
STRR1,[R0]
BXLR
}

intmain(void)
{
SetPendSVPro();
LEDInit();
TriggerPendSV();
while(1);
}

voidPendSV_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个寄存器自动入栈。入栈内容及顺序如下:

37caed20-7432-11ed-8abf-dac502259ad0.pngimg

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

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

3,更新寄存器内容

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

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

在ISR中添加代码如下:

MRSR0,PSP

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

STMDBR0!,{R4-R11}

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

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

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

37d21abe-7432-11ed-8abf-dac502259ad0.png

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

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

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

LDRR1,=__cpp(&curr_task)
LDRR3,=__cpp(&PSP_array)
LDRR4,=__cpp(&next_task)
LDRR4,[R4]

获取下一个任务的编号:

STRR4,[R1]
Curr_task=next_task
LDRR0,[R3,R4,LSL#2]

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

LDMIAR0!,{R4-R11}

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

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

MSRPSP,R0
PSP=R0。
BXLR

中断返回。

完整代码

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

#defineHW32_REG(ADDRESS)(*((volatileunsignedlong*)(ADDRESS)))
#defineLED0*((volatileunsignedlong*)(0x422101a0))//PA8
voidUSART1_Init(void);
voidtask0(void);
unsignedcharflag=1;

uint32_tcurr_task=0;//当前执行任务
uint32_tnext_task=1;//下一个任务
uint32_ttask0_stack[17];
uint32_ttask1_stack[17];
uint32_tPSP_array[4];

u8task0_handle=1;
u8task1_handle=1;

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

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

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

__asmvoidSetPendSVPro(void)
{
NVIC_SYSPRI14EQU0xE000ED22
NVIC_PENDSV_PRIEQU0xFF

LDRR1,=NVIC_PENDSV_PRI
LDRR0,=NVIC_SYSPRI14
STRBR1,[R0]
BXLR
}

__asmvoidTriggerPendSV(void)
{
NVIC_INT_CTRLEQU0xE000ED04
NVIC_PENDSVSETEQU0x10000000

LDRR0,=NVIC_INT_CTRL
LDRR1,=NVIC_PENDSVSET
STRR1,[R0]
BXLR
}

intmain(void)
{
USART1_Init();

SetPendSVPro();
LEDInit();

printf("OStest
");

PSP_array[0]=((unsignedint)task0_stack)+(sizeoftask0_stack)-16*4;
//PSP_array中存储的为task0_stack数组的尾地址-16*4,即task0_stack[1023-16]地址
HW32_REG((PSP_array[0]+(14<<2)))=(unsignedlong)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]=((unsignedint)task1_stack)+(sizeoftask1_stack)-16*4;
HW32_REG((PSP_array[1]+(14<<2)))=(unsignedlong)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(architecturalrecommendation)*/
__ISB();

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

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

//加载下一个任务的内容
LDRR1,=__cpp(&curr_task)
LDRR3,=__cpp(&PSP_array)
LDRR4,=__cpp(&next_task)
LDRR4,[R4]//得到下一个任务的ID
STRR4,[R1]//设置curr_task=next_task
LDRR0,[R3,R4,LSL#2]//从PSP_array中获取PSP的值
LDMIAR0!,{R4-R11}//将任务堆栈中的数值加载到R4-R11中
//ADDSR0,R0,#0x20
MSRPSP,R0//设置PSP指向此任务
//ORRLR,LR,#0x04
BXLR//返回
//xPSR,PC,LR,R12,R0-R3会自动的恢复
ALIGN4
}

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

voidUSART1_Init(void)
{
GPIO_InitTypeDefGPIO_InitStructure;
USART_InitTypeDefUSART_InitStructure;

/*configUSART1clock*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE);

/*USART1GPIOconfig*/
/*ConfigureUSART1Tx(PA.09)asalternatefunctionpush-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);
/*ConfigureUSART1Rx(PA.10)asinputfloating*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);

/*USART1modeconfig*/
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);
}

intfputc(intch,FILE*f)
{
USART_SendData(USART1,(unsignedchar)ch);
while(!(USART1->SR&USART_FLAG_TXE));

return(ch);
}

测试后结果如图:

37e621e4-7432-11ed-8abf-dac502259ad0.png

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

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

审核编辑 :李倩


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

    关注

    241

    文章

    23136

    浏览量

    658472
  • 程序
    +关注

    关注

    116

    文章

    3777

    浏览量

    80851

原文标题:例说OS前的任务切换(附代码)

文章出处:【微信号:技术让梦想更伟大,微信公众号:技术让梦想更伟大】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    freertos中断优先级在哪设置

    FreeRTOS是一个流行的实时操作系统,它广泛应用于嵌入式系统开发。在FreeRTOS中,中断优先级是一个重要的概念,因为它决定了中断处理的顺序和响应时间。 1. 理解中断优先级 在讨论如何设置
    的头像 发表于 09-02 14:17 554次阅读

    APS智能优化排产软件的优先级应用

    资源、订单需求和约束条件,自动计算出最优的生产计划。在这个过程中,订单排产的优先规则起着至关重要的作用。1.交期优先级交期优先级是订单排产中最基本的优先规则。根据
    的头像 发表于 06-26 17:43 337次阅读
    APS智能优化排产软件的<b class='flag-5'>优先级</b>应用

    ESP32S2如何设置中断优先级

    ESP32S2有六中断, 如何设置中断优先级? 比如在GPIO例程里 ,这种中断优先级是如何设置的呢? 中断寄存器列表里 ,只有简称,哪里能获取这个名称所对应的功能??
    发表于 06-21 07:59

    请问有什么方法降低WIFI中断的优先级

    因为项目对时序输出要求比较严格,WIFI中断优先级比硬件定时器中断优先级要高,在定时器中断里面对GPIO管脚控制被延长执行,请问有什么方法降低WIFI中断的优先级
    发表于 06-19 07:37

    如何在不同优先级安装相同的处理程序?

    在不同的优先级中安装相同的处理程序? 我需要一个解决方案,在不同的优先级中安装相同的处理程序(ISR),以便在我的项目中处理 CAT2 中断,但我不能上传项目,因为我签署了 NDA。
    发表于 05-28 08:13

    systick的中断优先级是否应该设置为最高优先级的嵌套中断?

    最近在学习ucosii有这样一个疑问,关于stm32的systick作为ucosii的基准时钟,systick的中断优先级是否应该设置为最高优先级的嵌套中断。理由是什么?各位大侠求指教!!!
    发表于 05-16 06:44

    创建了两个task如果一个优先级高 一个优先级低,高优先级的那个一直在做,什么时候会调度到低优先级的呢?

    创建了两个task如果一个优先级高一个优先级低, 我看到任务的函数都是用死循环做的,那个高优先级的那个一直在做,什么时候会调度到低优先级的呢?
    发表于 05-13 07:13

    求助,关于stm32F4中断优先级0无法抢占的问题求解

    使用F407遇到一个抢占优先级的问题: 现设置优先级分组 HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);也就是15个优先级分组。按照手册说明0
    发表于 04-23 07:26

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

    freeRTOS的任务切换我的理解是:通过SysTick中断去触发pendSV中断去做任务切换,因为pendSV中断优先级最低,所以任务切换只会发生在没有中断的时候产生。那是不是也可以不用pe
    发表于 04-16 07:59

    STM32F103的寄存器NVIC_IPRx抢占优先级和子优先级是怎么设置的?

    STM32F103的寄存器NVIC_IPRx有些不明白,抢占优先级和子优先级是怎么设置的? 如果,中断分组2,抢占优先级3,子优先级3 那下面这样写对吗? SCB->AIR
    发表于 03-27 07:48

    Hightec对芯片中断优先级分配时,中断号越高中断优先级越高吗?

    请问大佬,Hightec对芯片中断优先级分配时,中断号越高中断优先级越高吗?看网上的帖子说中断号越低优先级越高。
    发表于 02-27 06:13

    RTTnano的用户main函数中的优先级是多少?

    RTTnano 的用户main函数中的优先级是多少?为什么会出现创建新的task之后,main函数会抢占掉低优先级的task,例如RT_THREAD_PRIORITY_MAX设置为8 ,低于3
    发表于 02-26 08:01

    华为和思科默认路由优先级

    优先级值不同,则优先级值最小的为最优路由(无论开销值是否相同,另一种理解就是对不同路由来源或路由协议之间的比较)。
    的头像 发表于 01-11 10:47 1074次阅读

    GD32如何配置中断优先级分组以及中断优先级

    使用GD32 MCU的过程中,大家可能会有以下疑问:中断优先级如何配置和使用?
    的头像 发表于 01-10 10:30 2937次阅读
    GD32如何配置中断<b class='flag-5'>优先级</b>分组以及中断<b class='flag-5'>优先级</b>

    python中运算符的优先级大小

    Python中运算符的优先级决定了表达式中各个运算符的计算顺序。了解运算符的优先级对于正确理解和编写复杂的表达式非常重要。本文将详细介绍Python中运算符的优先级,并给出一些示例来帮助读者更好地理
    的头像 发表于 11-29 16:21 3247次阅读