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在系统中被设置为最低优先级,因此只有当没有其他异常或者中断在执行时才会被执行。
执行系统调用 就是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; //用于防止编译器报错
}
- 动态创建一个"IDLE"任务:堆栈
configMINIMAL_STACK_SIZE=128*4byte
;任务优先级为tskIDLE_PRIORITY;任务体 prvIdleTask。 - 如果系统使用 软件定时器;将通过
xTimerCreateTimerTask()
创建定时器服务任务"Tmr Svc"。堆栈2*128*4byte
;任务优先级为configTIMER_TASK_PRIORITY;任务体 prvTimerTask。 - 关闭中断
portDISABLE_INTERRUPTS()
用于屏蔽configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY = 5
中断级别以下的所有中断。(在vPortSVChandle 中断服务函数中会对其开启) - 设置下一任务调度需要的阻塞时间
xNextTaskUnblockTime = portMAX_DELAY = 0xffffffffUL
; - 设置调度器工作标志
xSchedulerRunning = pdTRUE
- 设置初始化时钟节拍计数器
xTickCount = 0
- 系统运行时间统计变量初始化。
runtimeCounter = 0ul。
- 开启进入第一个任务
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 /* 跳转开始执行 */
任务出栈顺序
附录: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(线程模式使用主堆栈):
LR的值在异常期间被设置为EXC_RETURN(线程模式使用进程堆栈):
附录: 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
Freertos学习:01 移植到STM32的更多相关文章
- FreeRTOS学习及移植笔记之一:开始FreeRTOS之旅
1.必要的准备工作 工欲善其事,必先利其器,在开始学习和移植之前,相应的准备工作必不可少.所以在开始我们写要准备如下: 测试环境:我准备在STM32F103平台上移植和测试FreeRTOS系统 准备F ...
- 【FreeRTOS学习01】CubeIDE快速整合FreeRTOS创建第一个任务
整个专栏主要是博主结合自身对FreeRTOS的实战学习以及源码分析,基于STM32F767 Nucleo-144平台,在CubeIDE下进行开发,结合官方的HAL库,将硬件环节的问题减少到最小,将精力 ...
- FreeRTOS学习及移植笔记之二:在IAR和STM32F103VET上移植FreeRTOS
上一次,我们简单的测试了FreeRTOS的基于IAR EWARM v6.4和STM32F103VET6平台的Demo,对其有了一个基本认识.接下来我们开始自己移植FreeRTOS的过程. 1.创建一个 ...
- 【FreeRTOS学习05】深度解剖FreeRTOSConfig.h实现对系统的自定义剪裁
ROM/RAM太小,因此要对系统进行剪裁: 相关文章 [FreeRTOS实战汇总]小白博主的RTOS学习实战快速进阶之路(持续更新) 文章目录 相关文章 1 系统的剪裁 2 FreeRTOSConfi ...
- ucos实时操作系统学习笔记——操作系统在STM32的移植
使用ucos实时操作系统是在上学的时候,导师科研项目中.那时候就是网上找到操作系统移植教程以及应用教程依葫芦画瓢,功能实现也就罢了,没有很深入的去研究过这个东西.后来工作了,闲来无聊就研究了一下这个只 ...
- FreeRTOS移植到STM32上的移植过程
所有的单片机都是顺序执行的,而对于多任务而言就显得力不从心了,虽然在一些小项目中可以通过定时器来实现,但这种实现方式没有实时性,一旦任务需要在规定时间内做出响应,那只能通过实时操作系统来完成了.在很多 ...
- RT-Thread移植到stm32
一.移植RT-Thread准备 RT-Thread源码 源码版本和下载方式,可以参考RT-Thread移植入门学习. keil软件 STM32工程项目模板 因为每一厂家提供的库文件可能有一些区别,在移 ...
- Basic脚本解释器移植到STM32
本文来自http://blog.csdn.net/hellogv/ .引用必须注明出处! 上次讲了LUA移植到STM32.这次讲讲Basic脚本解释器移植到STM32. 在STM32上跑Basic脚本 ...
- 基于Linux的kfifo移植到STM32(支持os的互斥访问)
基于Linux的kfifo移植到STM32(支持os的互斥访问) 关于kfifo kfifo是内核里面的一个First In First Out数据结构,它采用环形循环队列的数据结构来实现:它提供一个 ...
- Python学习--01入门
Python学习--01入门 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.和PHP一样,它是后端开发语言. 如果有C语言.PHP语言.JAVA语言等其中一种语言的基础,学习Py ...
随机推荐
- 微服务 - 作业调度 · Hangfire集成式 · 仪表盘 · DolphinScheduler分布式 · 定义流程
系列目录 微服务 - 1.概念 · 应用 · 架构 · 通讯 · 授权 · 跨域 · 限流 微服务 - 2.IdentityServer4认证授权 · 概念认识 · 运行过程 · 实践应用 微服务 - ...
- Linux下vim的常用命令总结
vim按d表示剪切 按dd剪切一行 vim命令:命令模式 /关键字 n继续向下查找 vim的多行注释: 1.按ctrl + v进入 visual block模式 2.按上下选中要注释的行 3.按大写字 ...
- ansible(8)--ansible的hostname模块
1. hostname模块 功能:管理远程主机的主机名. 示例一:更改192.168.20.22的主机名为nginx01: [root@xuzhichao ~]# ansible 192.168.20 ...
- 零知识证明: Tornado Cash 项目学习
前言 最近在了解零知识证明方面的内容,这方面的内容确实不好入门也不好掌握,在了解了一些基础的概念以后,决定选择一个应用了零知识证明的项目来进行进一步的学习.最终选择了 Tornado Cash 这个项 ...
- nginx中目录浏览配置
root方式配置:(会自动加目录名) #开放本地目录-root server { listen 81; server_name localhost 127.0.0.1 0.0.0.0; charset ...
- uniapp中使用极光推送
1.注册极光账号 2.注册几个主流手机厂商的开发者账号(注册手机厂商,可以保证app进程不在的时候走厂商通道推送消息) 3.配置uniapp极光插件 https://ext.dcloud.net.cn ...
- 基于Python实现MapReduce
一.什么是MapReduce 首先,将这个单词分解为Map.Reduce. Map阶段:在这个阶段,输入数据集被分割成小块,并由多个Map任务处理.每个Map任务将输入数据映射为一系列(key, va ...
- 更改wsl中系统的安装位置
wsl默认安装位置是C盘,众所周知C盘总是不够用的,所以才有了把wsl的系统迁移到其它位置的需求.官网文档 首先查看所有分发版本 wsl -l --all -v 导出分发版为tar文件到D盘 wsl ...
- nc反弹中 &>、0>&1是什么意思
1.简介 本文结合一些参考文章以及作者个人理解解释Linux的bash反弹命令中的 &>.0>&1 观点有误,欢迎指出! 目标讨论命令:bash -i >& ...
- 将.net core api 部署成windows服务
将.net core api 部署成windows服务 参考 https://blog.csdn.net/qq_38762313/article/details/103311944 将NSSM解压 ...