先了解下如何使用PendSV異常。(為何要使用PendSV而不是其他的異常,請參考《cortex-M3權威指南》)
1,如何設定PendSV優先級?
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_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教程》得來。