先了解下如何使用PendSV異常。(為何要使用PendSV而不是其他的異常,請(qǐng)參考《cortex-M3權(quán)威指南》)
1,如何設(shè)定PendSV優(yōu)先級(jí)?

NVIC_SYSPRI14EQU0xE000ED22
NVIC_PENDSV_PRIEQU0xFF
LDRR0,=NVIC_SYSPRI14LDRR1,=NVIC_PENDSV_PRI
STRBR1,[R0]
2,如何觸發(fā)PendSV異常?

往ICSR第28位寫1,即可將PendSV異常掛起。若是當(dāng)前沒有高優(yōu)先級(jí)中斷產(chǎn)生,那么程序?qū)?huì)進(jìn)入PendSV handler
NVIC_INT_CTRLEQU0xE000ED04
NVIC_PENDSVSETEQU0x10000000
LDRR0,=NVIC_INT_CTRL
LDRR1,=NVIC_PENDSVSET
STRR1,[R0]
3,編寫PendSV異常handler
這里用PendSV_Handler來觸發(fā)LED點(diǎn)亮,以此證明PendSV異常觸發(fā)的設(shè)置是正確的。
#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;
}
上述代碼可以正常點(diǎn)亮LED,說明PendSV異常是正常觸發(fā)了。
OK,是時(shí)候挑戰(zhàn)任務(wù)切換了。
如何實(shí)現(xiàn)任務(wù)切換?三個(gè)步驟:
步驟一:在進(jìn)入中斷前先設(shè)置PSP。
curr_task=0;
設(shè)置任務(wù)0為當(dāng)前任務(wù):
__set_PSP((PSP_array[curr_task]+16*4));
設(shè)置PSP指向task0堆棧的棧頂位置:
__set_CONTROL(0x3);
設(shè)置為用戶級(jí),并使用PSP堆棧:
__ISB();
指令同步隔離。
步驟二:將當(dāng)前寄存器的內(nèi)容保存到當(dāng)前任務(wù)堆棧中。進(jìn)入ISR時(shí),cortex-m3會(huì)自動(dòng)保存八個(gè)寄存器到PSP中,剩下的幾個(gè)需要我們手動(dòng)保存。
步驟三:在Handler中將下一個(gè)任務(wù)的堆棧中的內(nèi)容加載到寄存器中,并將PSP指向下一個(gè)任務(wù)的堆棧。這樣就完成了任務(wù)切換。
要在PendSV 的ISR中完成這兩個(gè)步驟,我們先需了解下在進(jìn)入PendSV ISR時(shí),cortex-M3做了什么?
1,入棧。會(huì)有8個(gè)寄存器自動(dòng)入棧。入棧內(nèi)容及順序如下:

在步驟一中,我們已經(jīng)設(shè)置了PSP,那這8個(gè)寄存器就會(huì)自動(dòng)入棧到PSP所指地址處。
2,取向量。找到PendSV ISR的入口地址,這樣就能跳到ISR了。,
3,更新寄存器內(nèi)容。
做完這三步后,程序就進(jìn)入ISR了。
進(jìn)入ISR前,我們已經(jīng)完成了步驟一,cortex-M3已經(jīng)幫我們完成了步驟二的一部分,剩下的需要我們手動(dòng)完成。
在ISR中添加代碼如下:
MRSR0,PSP
保存PSP到R0。為什么是PSP而不是MSP。因?yàn)樵贠S啟動(dòng)的時(shí)候,我們已經(jīng)把SP設(shè)置為PSP了。這樣使得用戶程序使用任務(wù)堆棧,OS使用主堆棧,不會(huì)互相干擾。不會(huì)因?yàn)橛脩舫绦驅(qū)е翺S崩潰。
STMDBR0!,{R4-R11}
保存R4-R11到PSP中。C語言表達(dá)是*(--R0)={R4-R11},R0中值先自減1,然后將R4-R11的值保存到該值所指向的地址中,即PSP中。
STMDB Rd!,{寄存器列表} 連續(xù)存儲(chǔ)多個(gè)字到Rd中的地址值所指地址處。每次存儲(chǔ)前,Rd先自減一次。
若是ISR是從從task0進(jìn)來,那么此時(shí)task0的堆棧中已經(jīng)保存了該任務(wù)的寄存器參數(shù)。保存完成后,當(dāng)前任務(wù)堆棧中的內(nèi)容如下(假設(shè)是task0)

左邊表格是預(yù)期值,右邊是keil調(diào)試的實(shí)際值??梢钥闯?,是一致的。在任務(wù)初始化時(shí)(步驟一),我們將PSP指向任務(wù)0的棧頂0x20000080。在進(jìn)入PendSV之前,cortex-M3自動(dòng)入棧八個(gè)值,此時(shí)PSP指向了0x20000060。然后我們?cè)俦4鍾4-R11到0x20000040~0x2000005C。
這樣很容易看明白,如果需要下次再切換到task0,只需恢復(fù)R4~R11,再將PSP指向0x20000060即可。
所以切換到另一個(gè)任務(wù)的代碼:
LDRR1,=__cpp(&curr_task)
LDRR3,=__cpp(&PSP_array)
LDRR4,=__cpp(&next_task)
LDRR4,[R4]
獲取下一個(gè)任務(wù)的編號(hào):
STRR4,[R1]
Curr_task=next_task
LDRR0,[R3,R4,LSL#2]
獲得任務(wù)堆棧地址,若是task0,那么R0=0x20000040( R0=R3+R4*4)
LDMIAR0!,{R4-R11}
恢復(fù)堆棧中的值到R4~R11。R4=*(R0++)。執(zhí)行完后,R0中值變?yōu)?x20000060
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;//當(dāng)前執(zhí)行任務(wù)
uint32_tnext_task=1;//下一個(gè)任務(wù)
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中存儲(chǔ)的為task0_stack數(shù)組的尾地址-16*4,即task0_stack[1023-16]地址
HW32_REG((PSP_array[0]+(14<<2)))=(unsignedlong)task0;/*PC*/
//task0的PC存儲(chǔ)在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*/
/*任務(wù)0先執(zhí)行*/
curr_task=0;
/*設(shè)置PSP指向任務(wù)0堆棧的棧頂*/
__set_PSP((PSP_array[curr_task]+16*4));
SysTick_Config(9000000);
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);//72/8=9MHZ
/*使用堆棧指針,非特權(quán)級(jí)狀態(tài)*/
__set_CONTROL(0x3);
/*改變CONTROL后執(zhí)行ISB(architecturalrecommendation)*/
__ISB();
/*啟動(dòng)任務(wù)0*/
task0();
//LED0=0;
while(1);
}
__asmvoidPendSV_Handler(void)
{
//保存當(dāng)前任務(wù)的寄存器內(nèi)容
MRSR0,PSP//得到PSPR0=PSP
//xPSR,PC,LR,R12,R0-R3已自動(dòng)保存
STMDBR0!,{R4-R11}//保存R4-R11共8個(gè)寄存器得到當(dāng)前任務(wù)堆棧
//加載下一個(gè)任務(wù)的內(nèi)容
LDRR1,=__cpp(&curr_task)
LDRR3,=__cpp(&PSP_array)
LDRR4,=__cpp(&next_task)
LDRR4,[R4]//得到下一個(gè)任務(wù)的ID
STRR4,[R1]//設(shè)置curr_task=next_task
LDRR0,[R3,R4,LSL#2]//從PSP_array中獲取PSP的值
LDMIAR0!,{R4-R11}//將任務(wù)堆棧中的數(shù)值加載到R4-R11中
//ADDSR0,R0,#0x20
MSRPSP,R0//設(shè)置PSP指向此任務(wù)
//ORRLR,LR,#0x04
BXLR//返回
//xPSR,PC,LR,R12,R0-R3會(huì)自動(dòng)的恢復(fù)
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);
}
測(cè)試后結(jié)果如圖:

可以看出,兩個(gè)任務(wù)可以切換了。
上述代碼參考《cortex-M3權(quán)威指南》和《安富萊_STM32-V5開發(fā)板_μCOS-III教程》得來。
審核編輯 :李倩
-
led
+關(guān)注
關(guān)注
242文章
23617瀏覽量
669335 -
程序
+關(guān)注
關(guān)注
117文章
3817瀏覽量
82208
原文標(biāo)題:例說OS前的任務(wù)切換(附代碼)
文章出處:【微信號(hào):技術(shù)讓夢(mèng)想更偉大,微信公眾號(hào):技術(shù)讓夢(mèng)想更偉大】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
配電柜—斷電危機(jī)?配電柜故障排查優(yōu)先級(jí)指南

使用CH32V103C8TC設(shè)置中斷優(yōu)先級(jí)分組時(shí),編譯報(bào)錯(cuò)的原因?
CH32V103 使用中斷優(yōu)先級(jí)分組0時(shí)報(bào)錯(cuò)
車載以太網(wǎng)交換機(jī)入門基本功(4)—優(yōu)先級(jí)設(shè)計(jì)與VLAN測(cè)試

在汽車區(qū)域模塊中使用理想二極管的優(yōu)先級(jí)電源多路復(fù)用器

freertos中斷優(yōu)先級(jí)在哪設(shè)置
APS智能優(yōu)化排產(chǎn)軟件的優(yōu)先級(jí)應(yīng)用

評(píng)論