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\r\n");
    }
}

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM