一步步写STM32 OS【三】PendSV与堆栈操作

2024-01-25 16:58

本文主要是介绍一步步写STM32 OS【三】PendSV与堆栈操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

一、什么是PendSV

PendSV是可悬起异常,如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。更详细的内容在《Cortex-M3 权威指南》里有介绍,下面我摘抄了一段。


OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动 作。悬起 PendSV 的方法是:手工往 NVIC的 PendSV悬起寄存器中写 1。悬起后,如果优先级不够 高,则将缓期等待执行。

PendSV的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:
1、执行一个系统调用
2、系统滴答定时器(SYSTICK)中断,(轮转调度中需要)

让我们举个简单的例子来辅助理解。假设有这么一个系统,里面有两个就绪的任务,并且通过SysTick异常启动上下文切换。但若在产生 SysTick 异常时正在响应一个中断,则 SysTick异常会抢占其 ISR。在这种情况下,OS是不能执行上下文切换的,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这 种事。因此,在 CM3 中也是严禁没商量——如果 OS 在某中断活跃时尝试切入线程模式,将触犯用法fault异常。

为解决此问题,早期的 OS 大多会检测当前是否有中断在活跃中,只有在无任何中断需要响应 时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切 换动作拖延很久(因为如果抢占了 IRQ,则本次 SysTick在执行后不得作上下文切换,只能等待下 一次SysTick异常),尤其是当某中断源的频率和SysTick异常的频率比较接近时,会发生“共振”, 使上下文切换迟迟不能进行。现在好了,PendSV来完美解决这个问题了。PendSV异常会自动延迟上下文切换的请求,直到 其它的 ISR都完成了处理后才放行。为实现这个机制,需要把 PendSV编程为最低优先级的异常。如果 OS检测到某 IRQ正在活动并且被 SysTick抢占,它将悬起一个 PendSV异常,以便缓期执行 上下文切换。

使用 PendSV 控制上下文切换个中事件的流水账记录如下:

1. 任务 A呼叫 SVC来请求任务切换(例如,等待某些工作完成)

2. OS接收到请求,做好上下文切换的准备,并且悬起一个 PendSV异常。

3. 当 CPU退出 SVC后,它立即进入 PendSV,从而执行上下文切换。

4. 当 PendSV执行完毕后,将返回到任务 B,同时进入线程模式。

5. 发生了一个中断,并且中断服务程序开始执行

6. 在 ISR执行过程中,发生 SysTick异常,并且抢占了该 ISR。

7. OS执行必要的操作,然后悬起 PendSV异常以作好上下文切换的准备。

8. 当 SysTick退出后,回到先前被抢占的 ISR中,ISR继续执行

9. ISR执行完毕并退出后,PendSV服务例程开始执行,并且在里面执行上下文切换

10. 当 PendSV执行完毕后,回到任务 A,同时系统再次进入线程模式。


我们在uCOS的PendSV的处理代码中可以看到:

 

复制代码

OS_CPU_PendSVHandlerCPSID I ; 关中断;保存上文 ;....................... ;切换下文 CPSIE I ;开中断BX LR ;异常返回

复制代码

 

它在异常一开始就关闭了中端,结束时开启中断,中间的代码为临界区代码,即不可被中断的操作。PendSV异常是任务切换的堆栈部分的核心,由他来完成上下文切换。PendSV的操作也很简单,主要有设置优先级和触发异常两部分:

复制代码

NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器
NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(优先级14). 
NVIC_PENDSV_PRI EQU 0xFF ; PendSV优先级(最低). 
NVIC_PENDSVSET EQU 0x10000000 ; PendSV触发值; 设置PendSV的异常中断优先级LDR R0, =NVIC_SYSPRI14 
LDR R1, =NVIC_PENDSV_PRI 
STRB R1, [R0] ; 触发PendSV异常
LDR R0, =NVIC_INT_CTRL 
LDR R1, =NVIC_PENDSVSET 
STR R1, [R0]

复制代码

 二、堆栈操作

Cortex M4有两个堆栈寄存器,主堆栈指针(MSP)与进程堆栈指针(PSP),而且任一时刻只能使用其中的一个。MSP为复位后缺省使用的堆栈指针,异常永远使用MSP,如果手动开启PSP,那么线程使用PSP,否则也使用MSP。怎么开启PSP?

MSR     PSP, R0                                             ; Load PSP with new process SPORR     LR, LR, #0x04                                   ; Ensure exception return uses process stack

很容易就看出来了,置LR的位2为1,那么异常返回后,线程使用PSP。

写OS首先要将内存分配搞明白,单片机内存本来就很小,所以我们当然要斤斤计较一下。在OS运行之前,我们首先要初始化MSP和PSP,OS_CPU_ExceptStkBase是外部变量,假如我们给主堆栈分配1KB(256*4)的内存即OS_CPU_ExceptStk[256],则OS_CPU_ExceptStkBase=&OS_CPU_ExceptStk[256-1]。

复制代码

EXTERN  OS_CPU_ExceptStkBase;PSP清零,作为首次上下文切换的标志MOVS    R0, #0 MSR     PSP, R0;将MSP设为我们为其分配的内存地址LDR     R0, =OS_CPU_ExceptStkBaseLDR     R1, [R0]MSR     MSP, R1

复制代码

然后就是PendSV上下文切换中的堆栈操作了,如果不使用FPU,则进入异常自动压栈xPSR,PC,LR,R12,R0-R3,我们还要把R4-R11入栈。如果开启了FPU,自动压栈的寄存器还有S0-S15,还需吧S16-S31压栈。

复制代码

MRS     R0, PSPSUBS   R0, R0, #0x20        ;压入R4-R11STM     R0, {R4-R11}LDR     R1, =Cur_TCB_Point    ;当前任务的指针LDR     R1, [R1]STR     R0, [R1]            ; 更新任务堆栈指针

复制代码

出栈类似,但要注意顺序

复制代码

LDR     R1, =TCB_Point    ;要切换的任务指针LDR     R2, [R1]LDR     R0, [R2]          ; R0为要切换的任务堆栈地址LDM     R0, {R4-R11}     ; 弹出R4-R11ADDS    R0, R0, #0x20MSR     PSP, R0        ;更新PSP

复制代码

三、OS实战

新建os_port.asm文件,内容如下:

复制代码

NVIC_INT_CTRL   EQU     0xE000ED04                              ; Interrupt control state register.
NVIC_SYSPRI14   EQU     0xE000ED22                              ; System priority register (priority 14).
NVIC_PENDSV_PRI EQU           0xFF                              ; PendSV priority value (lowest).
NVIC_PENDSVSET  EQU     0x10000000                              ; Value to trigger PendSV exception.RSEG CODE:CODE:NOROOT(2)THUMBEXTERN  g_OS_CPU_ExceptStkBaseEXTERN  g_OS_Tcb_CurPEXTERN  g_OS_Tcb_HighRdyPPUBLIC OSStart_AsmPUBLIC PendSV_HandlerPUBLIC OSCtxSwOSCtxSwLDR     R0, =NVIC_INT_CTRLLDR     R1, =NVIC_PENDSVSETSTR     R1, [R0]BX      LR                                                ; Enable interrupts at processor levelOSStart_AsmLDR     R0, =NVIC_SYSPRI14                                  ; Set the PendSV exception priorityLDR     R1, =NVIC_PENDSV_PRISTRB    R1, [R0]MOVS    R0, #0                                              ; Set the PSP to 0 for initial context switch callMSR     PSP, R0LDR     R0, =g_OS_CPU_ExceptStkBase                           ; Initialize the MSP to the OS_CPU_ExceptStkBaseLDR     R1, [R0]MSR     MSP, R1    LDR     R0, =NVIC_INT_CTRL                                  ; Trigger the PendSV exception (causes context switch)LDR     R1, =NVIC_PENDSVSETSTR     R1, [R0]CPSIE   I                                                   ; Enable interrupts at processor levelOSStartHangB       OSStartHang                                         ; Should never get herePendSV_HandlerCPSID   I                                                   ; Prevent interruption during context switchMRS     R0, PSP                                             ; PSP is process stack pointerCBZ     R0, OS_CPU_PendSVHandler_nosave                     ; Skip register save the first timeSUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stackSTM     R0, {R4-R11}LDR     R1, =g_OS_Tcb_CurP                                       ; OSTCBCur->OSTCBStkPtr = SP;LDR     R1, [R1]STR     R0, [R1]                                            ; R0 is SP of process being switched out; At this point, entire context of process has been saved
OS_CPU_PendSVHandler_nosaveLDR     R0, =g_OS_Tcb_CurP                                       ; OSTCBCur  = OSTCBHighRdy;LDR     R1, =g_OS_Tcb_HighRdyPLDR     R2, [R1]STR     R2, [R0]LDR     R0, [R2]                                       ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;LDM     R0, {R4-R11}                                        ; Restore r4-11 from new process stackADDS    R0, R0, #0x20MSR     PSP, R0                                             ; Load PSP with new process SPORR     LR, LR, #0x04                                       ; Ensure exception return uses process stackCPSIE   IBX      LR                                                  ; Exception return will restore remaining contextEND

复制代码

main.c内容如下:

复制代码

#include "stdio.h"
#define OS_EXCEPT_STK_SIZE 1024
#define TASK_1_STK_SIZE 1024
#define TASK_2_STK_SIZE 1024typedef unsigned int OS_STK;
typedef void (*OS_TASK)(void);typedef struct OS_TCB
{OS_STK *StkAddr;
}OS_TCB,*OS_TCBP;OS_TCBP g_OS_Tcb_CurP; 
OS_TCBP g_OS_Tcb_HighRdyP;static OS_STK OS_CPU_ExceptStk[OS_EXCEPT_STK_SIZE];
OS_STK *g_OS_CPU_ExceptStkBase;static OS_TCB TCB_1;
static OS_TCB TCB_2;
static OS_STK TASK_1_STK[TASK_1_STK_SIZE];
static OS_STK TASK_2_STK[TASK_2_STK_SIZE];extern void OSStart_Asm(void);
extern void OSCtxSw(void);void Task_Switch()
{if(g_OS_Tcb_CurP == &TCB_1)g_OS_Tcb_HighRdyP=&TCB_2;elseg_OS_Tcb_HighRdyP=&TCB_1;OSCtxSw();
}void task_1()
{printf("Task 1 Running!!!\n");Task_Switch();printf("Task 1 Running!!!\n");Task_Switch();
}void task_2()
{printf("Task 2 Running!!!\n");Task_Switch();printf("Task 2 Running!!!\n");Task_Switch();
}void Task_End(void)
{printf("Task End\n");while(1){}
}void Task_Create(OS_TCB *tcb,OS_TASK task,OS_STK *stk)
{OS_STK  *p_stk;p_stk      = stk;p_stk      = (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);*(--p_stk) = (OS_STK)0x01000000uL;                          //xPSR*(--p_stk) = (OS_STK)task;                                  // Entry Point*(--p_stk) = (OS_STK)Task_End;                                     // R14 (LR)*(--p_stk) = (OS_STK)0x12121212uL;                          // R12*(--p_stk) = (OS_STK)0x03030303uL;                          // R3*(--p_stk) = (OS_STK)0x02020202uL;                          // R2*(--p_stk) = (OS_STK)0x01010101uL;                          // R1*(--p_stk) = (OS_STK)0x00000000u;                           // R0*(--p_stk) = (OS_STK)0x11111111uL;                          // R11*(--p_stk) = (OS_STK)0x10101010uL;                          // R10*(--p_stk) = (OS_STK)0x09090909uL;                          // R9*(--p_stk) = (OS_STK)0x08080808uL;                          // R8*(--p_stk) = (OS_STK)0x07070707uL;                          // R7*(--p_stk) = (OS_STK)0x06060606uL;                          // R6*(--p_stk) = (OS_STK)0x05050505uL;                          // R5*(--p_stk) = (OS_STK)0x04040404uL;                          // R4tcb->StkAddr=p_stk;
}int main()
{g_OS_CPU_ExceptStkBase = OS_CPU_ExceptStk + OS_EXCEPT_STK_SIZE - 1;Task_Create(&TCB_1,task_1,&TASK_1_STK[TASK_1_STK_SIZE-1]);Task_Create(&TCB_2,task_2,&TASK_2_STK[TASK_1_STK_SIZE-1]);g_OS_Tcb_HighRdyP=&TCB_1;OSStart_Asm();return 0;
}

复制代码

编译下载并调试:

在此处设置断点

QQ图片20131102142647

此时寄存器的值,可以看到R4-R11正是我们给的值,单步运行几次,可以看到进入了我们的任务task_1或task_2,任务里打印信息,然后调用Task_Switch进行切换,OSCtxSw触发PendSV异常。

QQ截图20131102142731

IO输出如下:

QQ图片20131102142802

至此我们成功实现了使用PenSV进行两个任务的互相切换。之后,我们使用使用SysTick实现比较完整的多任务切换。

[源码下载] stepbystep_stm32_os_PendSV.rar

这篇关于一步步写STM32 OS【三】PendSV与堆栈操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/643953

相关文章

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

STM32(十一):ADC数模转换器实验

AD单通道: 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO,把GPIO配置成模拟输入的模式。 3.配置多路开关,把左面通道接入到右面规则组列表里。 4.配置ADC转换器, 包括AD转换器和AD数据寄存器。单次转换,连续转换;扫描、非扫描;有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。 5.ADC_CMD 开启ADC。 void RCC_AD

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介  1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 (OTA) 3在线编程(ICP把整个程序都更新掉) 1 系统的Bootloader写死了,只能用串口下载到指定的位置,启动方式也不方便需要配置BOOT引脚触发启动  4 IAP(自己写的Bootloader,实现程序升级) 1 比如蓝牙转串口,

FreeRTOS-基本介绍和移植STM32

FreeRTOS-基本介绍和STM32移植 一、裸机开发和操作系统开发介绍二、任务调度和任务状态介绍2.1 任务调度2.1.1 抢占式调度2.1.2 时间片调度 2.2 任务状态 三、FreeRTOS源码和移植STM323.1 FreeRTOS源码3.2 FreeRTOS移植STM323.2.1 代码移植3.2.2 时钟中断配置 一、裸机开发和操作系统开发介绍 裸机:前后台系

动手学深度学习【数据操作+数据预处理】

import osos.makedirs(os.path.join('.', 'data'), exist_ok=True)data_file = os.path.join('.', 'data', 'house_tiny.csv')with open(data_file, 'w') as f:f.write('NumRooms,Alley,Price\n') # 列名f.write('NA

寻迹模块TCRT5000的应用原理和功能实现(基于STM32)

目录 概述 1 认识TCRT5000 1.1 模块介绍 1.2 电气特性 2 系统应用 2.1 系统架构 2.2 STM32Cube创建工程 3 功能实现 3.1 代码实现 3.2 源代码文件 4 功能测试 4.1 检测黑线状态 4.2 未检测黑线状态 概述 本文主要介绍TCRT5000模块的使用原理,包括该模块的硬件实现方式,电路实现原理,还使用STM32类

线程的四种操作

所属专栏:Java学习        1. 线程的开启 start和run的区别: run:描述了线程要执行的任务,也可以称为线程的入口 start:调用系统函数,真正的在系统内核中创建线程(创建PCB,加入到链表中),此处的start会根据不同的系统,分别调用不同的api,创建好之后的线程,再单独去执行run(所以说,start的本质是调用系统api,系统的api

Java IO 操作——个人理解

之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字

MySQL——表操作

目录 一、创建表 二、查看表 2.1 查看表中某成员的数据 2.2 查看整个表中的表成员 2.3 查看创建表时的句柄 三、修改表 alter 3.1 重命名 rename 3.2 新增一列 add 3.3 更改列属性 modify 3.4 更改列名称 change 3.5 删除某列 上一篇博客介绍了库的操作,接下来来看一下表的相关操作。 一、创建表 create

STM32 ADC+DMA导致写FLASH失败

最近用STM32G070系列的ADC+DMA采样时,遇到了一些小坑记录一下; 一、ADC+DMA采样时进入死循环; 解决方法:ADC-dma死循环问题_stm32 adc dma死机-CSDN博客 将ADC的DMA中断调整为最高,且增大ADCHAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_Buffer_Size); 的ADC_Bu