zoukankan      html  css  js  c++  java
  • Freertos学习:01 移植到STM32

    --- title: rtos-freertos-01-移植到STM32 EntryName: rtos-freertos-01-porting-on-stm32 date: 2020-06-17 13:33:14 categories: tags: - FreeRTOS - stm32 - porting ---

    章节概述:

    介绍如何在STM32(F103)移植FreeRTOS。

    STM32-LIB:HAL

    IDE:MDK5.30

    移植步骤

    1、下载FreeRTOS

    2、在项目中建立目录freeRTOS

    3、将FreeRTOS/Source文件夹转移到项目中的freeRTOS

    4、根据平台的不同复制portable文件,拷贝到freeRTOS

    • 源文件port.c拷贝到freeRTOS
    • 头文件portmacro.h拷贝到freeRTOS/include

    STM32F103(Cortex-M3)对应的是在portable/RVDS/ARM_CM3

    5、工程中添加头文件对应的路径、添加源文件

    6、编译,报错说:不能打开FreeRTOSConfig.h头文件。

    我们需要到Demo文件夹处找到与我们单片机型号相同或相似的Demo,这里在FreeRTOS/Demo/CORTEX_STM32F103_Keil文件夹内可找到FreeRTOSConfig.h文件

    7、编译,提示:

    Error: L6218E: Undefined symbol xTaskGetCurrentTaskHandle (referred from stream_buffer.o).
    

    实际上TaskHandle_t xTaskGetCurrentTaskHandle是用于获取当前任务句柄。

    在文件FreeRTOSConfig.h中,宏INCLUDE_xTaskGetCurrentTaskHandle必须设置为1,此函数才有效。

    所以只要在FreeRTOSConfig.h中加入这句话即可:

    #define INCLUDE_xTaskGetCurrentTaskHandle 1
    

    8、重新编译,提示:

    Error: L6218E: Undefined symbol pvPortMalloc (referred from event_groups.o).
    Error: L6218E: Undefined symbol vPortFree (referred from event_groups.o).
    

    这是因为我们没有选择堆内存管理方式,portable/MemMang中的每一个文件对应一种方式,拷贝出来,这里选择heap_4.c

    9、仿真在线运行,发现调用vTaskStartScheduler进入了HardFault_Handler

    需要:修改中断向量指向RTOS-port.c文件中定义函数入口

    打开startup_stm32f10x_hd.s文件

    __heap_base
    Heap_Mem        SPACE   Heap_Size
    __heap_limit
    				;添加这3行
                    IMPORT xPortPendSVHandler
                    IMPORT xPortSysTickHandler
                    IMPORT vPortSVCHandler
                    ; 结束
                    PRESERVE8
                    THUMB
                    
    ; 将 SVC_Handler 改为 vPortSVHandler
    				;DCD     SVC_Handler                ; SVCall Handler
    				DCD     vPortSVCHandler                ; SVCall Handler
    
    ; 将 PendSV_Handler 改为 xPortPendSVHandler
    				;DCD     PendSV_Handler                ; PendSV Handler
    				DCD     xPortPendSVHandler                ; PendSV Handler
    
    ; 将 SysTick_Handler 改为 xPortSysTickHandler
    				;DCD     SysTick_Handler                ; SysTick Handler
    				DCD     xPortSysTickHandler                ; SysTick Handler
    

    简单的例程

    下面的代码实现了创建一个任务,并定期打印。

    添加下列代码

    #if 1 // 使用printf
    #include "stdio.h"
    
    #ifdef __GNUC__
    #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
    #else
    #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
    #endif
    /* 重定向printf*/
    PUTCHAR_PROTOTYPE
    {
    	HAL_UART_Transmit(&huart2, (uint8_t*)&ch,1,HAL_MAX_DELAY);
        return ch;
    }
    /* 重定向scanf */
    int fgetc(FILE *f)
    {
      uint8_t ch = 0;
      HAL_UART_Receive(&huart2, &ch, 1, 0xffff);
      return ch;
    }
    #endif
    
    #include "FreeRTOS.h"
    #include "task.h"
    
    //任务优先级
    #define START_TASK_PRIO     1
    //任务堆栈大小    
    #define START_STK_SIZE      128  
    //任务句柄
    TaskHandle_t StartTask_Handler;
    //任务函数
    void start_task(void *pvParameters)
    {
        while(1)
        {
            vTaskDelay(200);
            printf("start_task
    ");
        }
    }
    
    int main(void)
    {
        // HAL_Init(); // 如果Systick没有改为其他时钟源,则运行异常。需要注释掉。
        SystemClock_Config();
        MX_USART2_UART_Init();
        
        xTaskCreate((TaskFunction_t )start_task,            //任务函数
                    (const char*    )"start_task",          //任务名称
                    (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                    (void*          )NULL,                  //传递给任务函数的参数
                    (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                    (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
        vTaskStartScheduler();          //开启任务调度
    }
    

    附录:为什么需要修改中断向量表

    我们首先需要明白:FreeRTOS任务调度的原理。

    PendSV异常:可挂起的系统调用,其优先级可通过编程设置,在FreeRTOS中,一般将其设置为最低优先级。FreeRTOS系统的任务切换都是在PendSV中断服务函数中完成的。

    SVC:系统服务调用,用于产生系统函数的调用请求。

    任务切换场合

    认识这两个概念以后熟悉一下,FreeRTOS在什么情况下会进行任务的切换:

    • 执行系统调用 SVC(Supervisor Call);
    • 系统嘀嗒定时器中断;

    SVC(Supervisor Call)指令用于产生一个SVC异常。它是用户模式代码中的主进程,用于创造对特权操作系统代码的调用。SVC是用于呼叫操作系统所提供API的正道。用户程序只需知道传递给操作系统的参数,而不必知道各API函数的地址。

    SVC指令带一个8位的立即数,可以视为是它的参数,被封装在指令自身,如:

    SVC    3; 呼叫3号系统服务
    

    因此在SVC服务例程中,需要读取本次触发SVC异常的SVC指令,并提取出8位立即数所在的位段,从而判断系统调用号。

    PendSV是为系统级服务提供的中断驱动。在一个操作系统环境中,当没有其他异常正在执行时,可以使用PendSV来进行上下文的切换。

    在进入PendSV处理函数时:

    (1)xPSR、PC、LR、R12、R0~R3已经在处理栈中被保存。

    (2)处理模式切换到线程模式。

    (3)栈是主堆栈。

    由于PendSV在系统中被设置为最低优先级,因此只有当没有其他异常或者中断在执行时才会被执行。

    https://blog.csdn.net/findaway123/article/details/18148513

    执行系统调用 就是FreeRTOS系统提供的相关 API函数,比如 函数,比如 任务切换函数 taskYIELD(), FreeRTOS有些 API函数 也会调用taskYIELD(),这些 ,这些 API函数都会导致任务切换,统称为系统调用;

    #define 	taskYIELD() 	portYIELD()
    
    #define portYIELD() 
    { 
    	//通过向中断控制和壮态寄存器ICSR的bit28写入1挂起PendSV来启动PendSV中断,进行任务切换
    	portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; 
    	__dsb( portSY_FULL_READ_WRITE );
    	__isb( portSY_FULL_READ_WRITE ); 
    }
    
    #define 	portEND_SWITCHING_ISR( xSwitchRequired )	
    			if( xSwitchRequired != pdFALSE ) portYIELD()
    #define portYIELD_FROM_ISR( x ) portEND_SWITCHING_ISR( x )
    

    系统嘀嗒定时器中断 也会进行任务切换:

    void SysTick_Handler(void)
    {
    	if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)      //系统已经运行
    	{
    		xPortSysTickHandler();
    	}
    	
    	HAL_IncTick();
    }
    
    void xPortSysTickHandler( void )
    {
    	vPortRaiseBASEPRI();										//关闭中断
    	
    	if( xTaskIncrementTick() != pdFALSE )						//进入PendSV中断
    	{
    		portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
    	}
    
    	vPortClearBASEPRIFromISR();									//打开中断
    }
    

    PendSV解析

    FreeRTOS在PendSV中完成任务切换,具体不具体展开,因为是汇编语言,核心信息是利用vTaskSwitchContext() 来获取下一个要运行的任务,并将pxCurrentTCB更新为这个要运行的任务:

    void vTaskSwitchContext( void )
    {
    	if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )   	//挂起状态不能进行任务切换
    	{
    		xYieldPending = pdTRUE;									
    	}
    	else
    	{
    		xYieldPending = pdFALSE;								
    		traceTASK_SWITCHED_OUT();								
    		taskCHECK_FOR_STACK_OVERFLOW();
    		taskSELECT_HIGHEST_PRIORITY_TASK();                     //切换至就绪状态下的优先级最高的任务
    		traceTASK_SWITCHED_IN();
    	}
    }
    
    #define taskSELECT_HIGHEST_PRIORITY_TASK()
    {
    	UBaseType_t uxTopPriority = uxTopReadyPriority;
    	
    	//pxReadyTasksLists[]为就绪任务列表数组,一个优先级一个列表,同优先级都挂到相对应的列表
    	while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )
    	{
    		configASSERT( uxTopPriority );
    		--uxTopPriority;
    	}
    	
    	listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB,&( pxReadyTasksLists[ uxTopPriority ] ) );
    	uxTopReadyPriority = uxTopPriority;
    }
    

    内核控制函数概览

    这些函数应用层是不使用的,请注意!!!

    函数 描述
    taskYIELD() 任务切换
    taskENTER_CRITICAL()/taskEXIT_CRITICAL() 进入/退出临界区(用于任务中)
    taskENTER_CRITICAL_FROM_ISR()/taskEXIT_CRITICAL_FROM_ISR() 进入/退出临界区(用于中断服务函数中)
    taskDISABLE_INTERRUPTS()/taskENABLE_INTERRUPTS() 关闭/打开中断
    vTaskStartScheduler()/vTaskEndScheduler() 开启/关闭任务调度器
    vTaskSpendAll()/vTaskResumeAll() 挂起/恢复任务调度器
    vTaskStepTick() 设置系统节拍值

    附录:调用vTaskStartScheduler的注意事项

    为了调度任务,必须调用vTaskStartScheduler,且在初始化调度器之前,除了创建任务/队列等,不要做多余动作。

    vTaskStartScheduler()主要完成以下工作:

    • 调用xTaskCreate() 创建空闲任务,其优先级为最低(0)

    • 关闭中断功能,使能任务调度功能;

    • 初始化全局变量3. 设置SysTick、PendSV、FPU

    • 设置系统节拍定时器

    • 触发SVC异常,运行第一个任务

    • 返回空闲任务句柄。

    像你的情况,有可能是:

    如果你使用的是HAL库,则HAL_Init()会初始化SysTick,SysTick会挂起PendSV运行调度器导致任务运行。
    不要在HAL_Init()中初始化SysTick,注释掉相关初始化代码(最好不要修改库函数,直接复制出来使用)

    附录:vTaskStartScheduler()解析

    vTaskStartScheduler 执行过程

    创建任务以后,调用vTaskStartScheduler开始调度任务。那么到底发生了什么呢?

    void vTaskStartScheduler( void )
    {
        BaseType_t xReturn;
    
        #if( configSUPPORT_STATIC_ALLOCATION == 1 )     //条件编译,如果是静态创建方式
        {
            StaticTask_t *pxIdleTaskTCBBuffer = NULL;
            StackType_t *pxIdleTaskStackBuffer = NULL;
            uint32_t ulIdleTaskStackSize;
    
            /* The Idle task is created using user provided RAM - obtain the
    		address of the RAM then create the idle task. */
            vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer, &ulIdleTaskStackSize );
            xIdleTaskHandle = xTaskCreateStatic(	prvIdleTask,
                                                "IDLE",
                                                ulIdleTaskStackSize,
                                                ( void * ) NULL,
                                                ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
                                                pxIdleTaskStackBuffer,
                                                pxIdleTaskTCBBuffer ); 
    
            if( xIdleTaskHandle != NULL )
            {
                xReturn = pdPASS;
            }
            else
            {
                xReturn = pdFAIL;
            }
        }
        #else                                    //动态方式创建,分配RAM
        {
            xReturn = xTaskCreate(	prvIdleTask,
                                  "IDLE", configMINIMAL_STACK_SIZE,
                                  ( void * ) NULL,
                                  ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
                                  &xIdleTaskHandle );
        }
        #endif 
    
        #if ( configUSE_TIMERS == 1 )           //如果使用软件定时器,使能软件定时器
        {
            if( xReturn == pdPASS )
            {
                xReturn = xTimerCreateTimerTask();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* configUSE_TIMERS */
    
        if( xReturn == pdPASS )                           //空闲任务和定时器任务创建成功
        {
            portDISABLE_INTERRUPTS();                     //关闭中断
    
            #if ( configUSE_NEWLIB_REENTRANT == 1 )       //使能NEWLIB-一个面向嵌入式系统的C运行库
            {
                _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
            }
            #endif 
    
            xNextTaskUnblockTime = portMAX_DELAY;
            xSchedulerRunning = pdTRUE;                  //调度器开始运行
            xTickCount = ( TickType_t ) 0U;   			 //设置软件定时器初始值
    
            portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();    //配置定时器,用户需要操作portCONFIGURE_TIMER_FOR_RUN_TIME_STATS这个宏
    
            //调用函数xPortStartScheduler()来初始化跟调度器有关的硬件
            if( xPortStartScheduler() != pdFALSE )
            {       
            }
            else
            {
            }
        }
        else
        {
            //内核未创建成功,返回原因:内存不够
            configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
        }
    
        ( void ) xIdleTaskHandle;                         //用于防止编译器报错
    }
    
    1. 动态创建一个"IDLE"任务:堆栈configMINIMAL_STACK_SIZE=128*4byte;任务优先级为tskIDLE_PRIORITY;任务体 prvIdleTask。
    2. 如果系统使用 软件定时器;将通过xTimerCreateTimerTask()创建定时器服务任务"Tmr Svc"。堆栈2*128*4byte;任务优先级为configTIMER_TASK_PRIORITY;任务体 prvTimerTask。
    3. 关闭中断portDISABLE_INTERRUPTS()用于屏蔽configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY = 5中断级别以下的所有中断。(在vPortSVChandle 中断服务函数中会对其开启)
    4. 设置下一任务调度需要的阻塞时间xNextTaskUnblockTime = portMAX_DELAY = 0xffffffffUL
    5. 设置调度器工作标志 xSchedulerRunning = pdTRUE
    6. 设置初始化时钟节拍计数器xTickCount = 0
    7. 系统运行时间统计变量初始化。runtimeCounter = 0ul。
    8. 开启进入第一个任务 xPortStartScheduler()
    • 设置PendSV、SysTick为最低优先级中断并开启SysTick。
    • 初始化任务临界区嵌套计数 uxCriticalNesting = 0;
    • 设置systick计时器来生成所需的tick中断频率vPortSetupTimerInterrupt()
    • 使能FPU(CP10&CP11),确保在VFP启用vPortEnableVFP()。
    • 开启第一个任务。vPortStartFirstTask()

    有关定义

    全局状态量

    语句 意义
    static volatile TickType_t xTickCount = ( TickType_t ) 0U; 系统时钟节拍计数器tick。
    static volatile TickType_t xNextTaskUnblockTime = ( TickType_t ) 0U; 全局下一任务调度需要的阻塞时间,用于及其唤醒任务
    TCB_t * volatile pxCurrentTCB = NULL; 全局当前任务pcb。
    static volatile BaseType_t xSchedulerRunning = pdFALSE; 全局调度器工作标志。
    static volatile UBaseType_t uxSchedulerSuspended = ( UBaseType_t ) pdFALSE; 全局调度器挂起标志。

    调度器运行状态

    (与调度器挂起标志、调度器运行标志有关)。

     #define taskSCHEDULER_SUSPENDED      ( ( BaseType_t ) 0 ) // 挂起
     #define taskSCHEDULER_NOT_STARTED    ( ( BaseType_t ) 1 ) // 未开启
     #define taskSCHEDULER_RUNNING        ( ( BaseType_t ) 2 ) // 运行
    

    获取调度器运行状态。

    BaseType_t xTaskGetSchedulerState( void )
    {
        BaseType_t xReturn;
    
        if( xSchedulerRunning == pdFALSE ){
            xReturn = taskSCHEDULER_NOT_STARTED;
        } else {
            if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ){
                xReturn = taskSCHEDULER_RUNNING;
            }else{
                xReturn = taskSCHEDULER_SUSPENDED; 
    
           }
        }return xReturn;
    }
    

    任务分析

    prvIdleTask()任务分析、prvTimerTask()任务分析

    略。
    

    vPortStartFirstTask() 分析

       /* Use the NVIC offset register to locate the stack. */
        ldr r0, =0xE000ED08   /* 中断向量表重定位(偏移)寄存器 VTOR.(systemInit中已经重定位到0x80000000) */
        ldr r0, [r0]   /*  读取VTOR中的值到R0,即 R0=  0x80000000 ,*/
        ldr r0, [r0]   /* 向量表起始地址存储的是MSP(系统堆栈指针)的初始值,即本处获得了MSP中的初始值(CSTACK)*/
        /* Set the msp back to the start of the stack. */
        msr msp, r0  /* 将MSP中的初始值赋值给MSP,即复位MSP ,这个初始值子在哪里赋予的呢?*/
        /* Call SVC to start the first task. */
        cpsie i
        cpsie f    /*操作 寄存器PRIMASK、FAULTMASK,使能全局中断 */
        dsb        /*数据同步隔离*/
        isb         /*指令同步隔离*/
        svc 0     /*触发SVC中断,系统调用代号为 0 ,整个freeRtos中唯一使用的地方*/
    

    vPortSVCHandler 异常服务处理

    vPortSVCHandler:
        /* Get the location of the current TCB. */
        ldr    r3, =pxCurrentTCB       /*  获取第一个任务pcb指针 到 R3*/
        ldr r1, [r3]                       /* 获取任务pcb指针的值(即PCB的存储地址) 到 R1*/
        ldr r0, [r1]                      /*  获取任务pcb 结构的第一个数据,即任务堆栈指针 到 R0 */
        /* Pop the core registers. */
        ldmia r0!, {r4-r11, r14}   /* 需要手动出栈,恢复任务的现场 ,R0~R3、xPSR(任务状态)、R12(IP)、R15(PC)中断退出后硬件自动出栈恢复;注意:R14(LR)=EXC_RETURN,在进入本中断时自动赋值为特殊含义*/
        msr psp, r0                    /*  将目前的任务栈指针赋值给进程栈指针(R13-PSP) */
        isb                                 /*指令同步隔离*/
        mov r0, #0                   /* 设置R0的的 值为 0 */
        msr    basepri, r0         /* 打开中断  */
        bx r14                           /*   跳转开始执行  */
    

    任务出栈顺序

    img

    附录:CORTEX-M3 异常/中断响应与返回

    响应

    Cortex-M3的异常/中断响应序列包括:

    • 入栈:把8个寄存器的值压入栈。
    • 取向量:从向量表中找出对应的服务程序入口地址。
    • 更新寄存器:更新堆栈指针SP,更新连接寄存器LR,更新程序计数器PC

    入栈

    响应异常的第一个行动,就是自动保存现场的必要部分:依次把xPSR、PC、LR、R12以及R3~R0由硬件自动压入适当的堆栈中:

    • 如果当响应异常时,当前的代码正在使用PSP,则压入PSP,也就是使用进程堆栈;
    • 否则就压入MSP,使用主堆栈。

    在自动入栈的过程中,把寄存器写入堆栈内存的时间顺序,并不是与写入的空间顺序相对应的。但是,Cortex-M3内核会保证:正确的寄存器将被保存到正确的位置。

    一旦进入了异常中断服务程序,就将一直使用主堆栈。

    取向量

    在数据总线执行入栈操作的时候,指令总线正在执行取向量操作:即从向量表中找出正确的中断向量,然后在服务例程入口处预取指令。取向量和入栈是同时进行的;

    更新寄存器

    在入栈和取向量操作完成之后,执行异常中断服务程序之前,还要更新一系列的寄存器:

    • 堆栈指针SP:在入栈后,会把堆栈指针(PSP或MSP)更新到新的位置。在执行异常中断服务程序时,将由MSP负责对堆栈的访问。
    • 程序状态寄存器PSR:更新IPSR位段的值为新响应的异常编号。
    • 程序计数寄存器PC:在取向量完成后,PC将指向异常中断服务程序的入口地址。
    • 连接寄存器LR:在出入ISR的时候,LR的值将得到重新的诠释,这种特殊的值称为“EXC_RETURN”。在异常进入时由系统计算并赋给LR,并在异常返回时使用它。

    另外,在NVIC中,也会更新若干个相关寄存器。例如,新响应异常的悬起位将被清除,同时其活动位将被置位。

    返回

    当异常/中断服务程序执行完毕之后,需要一个“异常返回”动作序列,从而恢复先前的系统状态,使被中断的程序继续执行。

    从形式上看,有3种途径可以触发异常返回序列,如:

    返回指令 工作原理
    BX <reg> 当LR储存了EXC_RETURN,调用即可返回
    POP {PC}POP{..., PC} 在服务例程,LR的值常常会被压入栈。此时即可使用POP指令把LR储存的EXC_RETURN往PC里弹,从而启动处理器的中断返回序列
    LDR与LDM 把PC作为目的寄存器,亦可启动中断返回序列。

    不管使用哪一种返回指令,都需要用到先前存储到LR中的EXX_RETURN,把EXC_RETURN送往PC。
    在启动了中断返回序列后,将执行以下操作:

    • 出栈:恢复先前压入堆栈的寄存器的值。内部的出栈顺序与入栈时的向对应。堆栈指针的值也恢复更新。
    • 更新NVIC寄存器:异常返回,其将于的活动位将被硬件清除。对于外部中断,如果中断输入再次被置为有效,悬起位也将再次置位,新的中断响应序列也随之再次执行。

    附录:Cortex-M3 异常返回值EXC_RETURN

    进入异常服务程序以后,LR的值被自动更新为特殊的EXC_RETURN(只有[3:0]位有意义,其他位都为1)。

    当程序从异常服务程序返回,把这个EXC_RETURN值送往PC时,就会启动处理器的异常中断返回序列。
    因为LR的值EXC_RETURN是由硬件自动设置的,所以只要没有特殊需求,就不要改动它。

    RETURN的高28位全为1,只有bit[3:0]的值有特殊含义。

    位段 意义
    3 0:返回后进入Handler模式

    1:返回后进入线程模式
    2 0:从主堆栈中执行出栈操作,返回后使用MSP

    1:从进程栈中执行出栈操作,返回后使用PSP
    1 保留,必须为0
    0 0:返回ARM状态

    1:返回Thumb状态(在CM3中必须为1)

    显然,合法的EXC_RETURN值共有3个,如下:

    EXC_RETURN值 功能
    0xFFFF FFF1 返回Handler模式
    0xFFFF FFF9 返回thread(线程)模式,并且使用主堆栈(SP=MSP)
    0xFFFF FFFD 返回thread模式,并且使用线程栈(SP=PSP)

    如果主程序在线程模式下运行,并且在使用MSP时被中断,则在服务程序中LR=0xFFFFFFF9(主程序被打断前LR已被自动入栈)。

    如果主程序在线程模式下运行,并且在使用PSP时被中断,则在服务程序中LR=0xFFFFFFFD(主程序被打断前LR已被自动入栈)。

    如果主程序在Handler模式下运行,则服务程序中LR=0xFFFFFFF1(主程序被打断前LR已自动入栈)。这是所谓的“主程序”,其实更可能时被抢占的中断服务程序。事实上,在嵌套时,更深层ISR所看到的LR总是0xFFFFFFF1。

    LR的值在异常期间被设置为EXC_RETURN(线程模式使用主堆栈)

    img

    LR的值在异常期间被设置为EXC_RETURN(线程模式使用进程堆栈)

    img

    附录: Cortex-M3的有关机制

    2种操作模式

    Cortex-M3支持两种操作模式(handler模式和thread模式),这两种模式是为了区别正在执行代码的类型:

    • handler模式为异常处理程序的代码
    • 线程模式为普通应用程序的代码

    2个栈指针

    Cortex-M3内核有两个堆栈指针:MSP-主堆栈指针和PSP-进程堆栈指针,在任何一个时刻只能有一个堆栈指针起作用,也就是说任何一个时刻只能使用一个堆栈指针,要么使用MSP,要么使用PSP。何为堆栈指针,其实就是普通的指针,只是他们指向两个不同的堆栈。

    MSP:主堆栈指针,当程序复位后(开始运行后),一直到第一次任务切换完成前,使用的都是MSP,即:main函数运行时用的是MSP,运行OSStartHighRdy,运行PendSV程序,用的都是MSP。当main函数开始运行前,启动文件会给这个函数分配一个堆栈空间,像ucos给任务分配堆栈空间一样,用于保存main函数运行过程中变量的保存。此时MSP就指向了该堆栈的首地址。

    PSP:进程堆栈指针,切换任务之后PendSV服务程序中有ORR LR, LR, #0x04这句,意思就是PendSV中断返回后使用的PSP指针,此时PSP已经指向了所运行任务的堆栈,所以返回后就可以就接着该任务继续运行下去了。

    由于任何一个时刻都只能使用一个堆栈指针(SP),所以,如果在某一个时刻,需要读取或者改变另外一个堆栈指针的内容就得使用特定的指令:MSR和MRS

  • 相关阅读:
    [BZOJ 4034][HAOI2015]树上操作(欧拉序列+线段树)
    [BZOJ 4318]OSU!(期望dp)
    [Codeforces Round #146 (Div. 1) B]Let's Play Osu!(期望Dp)
    [Codeforces Round #261 (Div. 2) E]Pashmak and Graph(Dp)
    [Codeforces Round #301 (Div. 2) D]Bad Luck Island(概率Dp)
    [Codeforces Round #284 (Div. 1) B]Name That Tune(概率Dp)
    [UVALive 7143]Room Assignment(Dp)
    [BZOJ 1076][SCOI2008]奖励关(期望+状压Dp)
    【DBMS HKUST slides8】范式及分解算法 知识点总结
    【DBMS HKUST slides1~6】数据库管理系统 知识点总结
  • 原文地址:https://www.cnblogs.com/schips/p/rtos-freertos-01-porting-on-stm32.html
Copyright © 2011-2022 走看看