先了解下如何使用PendSV异常。(为何要使用PendSV而不是其他的异常,请参考《cortex-M3权威指南》)
NVIC_SYSPRI14 EQU 0xE000ED22
NVIC_PENDSV_PRI EQU 0xFF
LDR R0, =NVIC_SYSPRI14 LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
往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]
这里用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教程》得来。
来源:https://www.cnblogs.com/WeyneChen/p/4891885.html
版权声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。
注意
由于微信公众号近期改变了推送规则,为了防止找不到,可以星标置顶,这样每次推送的文章才会出现在您的订阅列表里。
猜你喜欢:
嵌入式设备AP配网实例分享
嵌入式Linux单板连接飞燕物联网平台
分享一种灵活性很高的协议格式(附代码例子)
嵌入式大杂烩周记 | 第 16 期
嵌入式大杂烩周记 | 第 15 期
访问非法内存为什么不会出错?
嵌入式大杂烩周记 | 第 14 期
分享几个实用的代码片段(第二弹)
分享一种你可能不知道的bug定位方法
分享一种修改配置文件的方法
《嵌入式大杂烩周记第 13 期:lz4》
《嵌入式并行多线程处理器,了解一下!》
《分享一种修改配置文件的方法》
《分享几个实用的代码片段(附代码例子)》
《废旧板子再利用:搭建无线调试环境!》
《嵌入式段错误的3种调试方法汇总!》
《简说TCP通信非阻塞接收(附代码例子)》
《TCP通信常用接口的使用封装》
《嵌入式软件中,总线错误的坑?替大家先踩一步》
《分享嵌入式软件调试方法及几个有用的工具!》
《分享两点提高编程能力的建议!》
在公众号聊天界面回复1024,可获取嵌入式资源;回复 m ,可查看文章汇总