stm32 freertos多任务状态迁移,中断临界段,任务延时

这篇具有很好参考价值的文章主要介绍了stm32 freertos多任务状态迁移,中断临界段,任务延时。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

freertos的作用

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

处理器运行模式与程序特权级别

arm中SP,LR,PC寄存器以及其它所有寄存器以及处理器运行模式介绍
特权级与用户级的区别主要是某些寄存器能不能访问与修改:
cortex M3/M4内核 特权级与用户级详解
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

Cortex-M3双堆栈MSP和PSP

Cortex-M3双堆栈MSP和PSP
M3内核何时使用MSP何时使用PSP?

特权级可以使用MSP 和PSP指针
用户级可以使用PSP,能不能使用MSP不太清楚。
在异常处理函数中只能使用MSP

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
用在裸机开发中
【线程模式特权级 与 Handler模式特权级 相互切换】
线程模式特权级,在异常处理的始末,只发生了处理器模式的切换,如:
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
在RTOS多任务切换
【线程模式非特权级 与 Handler模式特权级 相互切换】
当CONTROL[0]=1时,线程模式非特权级(用户级),则在异常处理的始末,处理器模式和特权等级都发生变化,如:
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

用在带有MTU的RTOS开发中,线程模式非特权级 与 线程模式特权级相当于Linux开发中的进程的用户态到内核态
【线程模式非特权级 与 线程模式特权级&Handler模式特权级 切换】
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

芯片复位后,进入线程模式特权级。 线程模式特权级与Handler模式特权级之间通过异常/中断的进出来切换。
在特权级下的代码可以通过置位CONTROL[0]来进入非特权级。
Handler模式永远都是特权级的。不管是任何原因产生了任何异常,处理器都将以特权级来运行其服务例程,异常返回后,系统将回到产生异常时所处的级别。
Handler模式通过异常/中断进入,退出即返回线程模式。从Handler模式异常返回时,也可以通过置位CONTROL[0](非特权级的线程模式)或清零CONTROL[0](特权级的线程模式)来改变返回线程模式的级别。
线程模式非特权级下的代码不能再试图修改CONTROL[0]来回到线程模式特权级。它必须通过一个异常进入到Handler模式,由那个异常Handler处理来修改,清零CONTROL[0]为线程模式特权级,才能在返回到线程模式后拿到特权级。

在异常Handler异常返回时,除了清零CONTROL[0],还可以修改LR寄存器(在中断函数中不能修改CONTROL[1]),使得返回线程模式变为特权级,且使用psp栈指针

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

一、xTaskCreate()

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
1、prvInitialiseNewTask()

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
2、prvAddNewTaskToReadyList()
(1):全局任务计时器uxCurrentNumberOfTasks加一操作。uxCurrentNumberOfTasks 是一个在task.c中定义的静态变量,默认初始化为0
(2):如果pxCurrentTCB为空,则将pxCurrentTCB指向新创建的任务。pxCurrentTCB 是一个在task.c定义的全局指针,用于指向当前正在运行或者即将要运行的任务的任务控制块,默认初始化为NULL。
(3):如果是第一次创建任务,则需要调用函数prvInitialiseTaskLists()初始化任 务相关的列表,目前只有就绪列表需要初始化,该函数在task.c中定义,

/* 初始化任务相关的列表 */
void prvInitialiseTaskLists( void )
{
    UBaseType_t uxPriority;

    for ( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
    {
        vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
    }
}

vListInitialise()

里pxList->xListEnd.xItemValue = portMAX_DELAY;

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

列表、列表项

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

xListEnd的xItemValue是在初始化List时被初始化:void vListInitialise( List_t * const pxList )
ListItem2的xItemValue是在延时函数中prvAddCurrentTaskToDelayedList(),listSET_LIST_ITEM_VALUE被初始化

vListInsertEnd()插入方式:

pxIndex指向的是列表的头,整个列表是一个双向环形结构,从头这里断开拉直列表,最后的列表项就是尾部,该插入方式就是插入在尾部最后的位置

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
vListInsert()方式插入:

根据xItemValue的值进行排序,然后插入

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

详见如下链接分析:
从单片机到操作系统⑤——FreeRTOS列表&列表项的源码解读

优先级位图表uxTopReadyPriority
支持多优先级

FreeRTOS任务调度器

1、启动任务调度器的初始化和调用第一个任务

各函数的详细代码分析可以参考如下链接:
09_FreeRTOS任务调度器

【FreeRTOS】2. SVC系统调用

**vTaskStartScheduler ##
该函数内部实现,如下步骤:

1.创建空闲任务

2.如果使能软件定时器,则创建定时器任务

3.关闭中断,防止调度器开启之前或过程中,受中断干扰,会在运行第一个任务时打开中断

4.初始化全局变量,并将任务调度器的运行标志设置为已运行

5.初始化任务运行时间统计功能的时基定时器

6.调用函数xPortStartScheduler()

xPortStartScheduler
作用:该函数用于完成启动任务调度器中与硬件架构相关的配置部分,以及启动第一个任务

该函数内部实现,如下:

1.检测用户在FreeRTOSConfig.h文件中对中断的相关配置是否有误

2.配置PendSV和SysTick的中断优先级为最低优先级

3.调用函数vPortSetupTimerlnterrupt()配置SysTick

4.初始化临界区嵌套计数器为0

5.调用函数prvEnableVFP()使能FPU6(M3没有)、调用函数prvStartFirstTask()启动第一个任务

void prvStartFirstTask
prvStartFirstTask开启第一个任务函数

用于初始化启动第一个任务前的环境,主要是重新设置MSP指针,并使能全局中断。

1.什么是MSP指针?

程序在运行过程中需要一定的栈空间来保存局部变量等一些信息。当有信息保存到栈中时,MCU会自动更新SP(R13)指针, ARM
Cortex-M内核提供了两个栈空间:

主堆栈指针(MSP):它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码(复位后的应用程序代码)来使用。

进程堆栈指针(PSP):用于常规的应用程序代码(不处于异常服务例程中时)。

在FreeRTOS中,中断handle模式使用MSP (主堆栈) ,复位后处于线程模式特权级,默认使用MSP,以外使用PSP (进程堆栈)
MSP指针赋值的是什么?参考如下链接:
FreeRTOS启动第一个任务接口prvPortStartFirstTask实现分析

vPortSVCHandler

注意:SVC中断只在启动第一次任务时会调用一次,以后均不调用

当使能了全局中断,并且手动触发SVC中断后,就会进入到SVC的中断服务函数中

1.通过 pxCurrentTCB 获取优先级最高的就绪态任务的任务栈地址,优先级最高的就绪态任务是系统将要运行的任务。

2.通过任务的栈顶指针,将任务栈中的内容出栈到CPU寄存器中,任务栈中的内容在调用任务创建函数的时候,已初始化,然后设置 PSP 指针。

3.通过往 BASEPRI 寄存器中写 0,允许中断。

  1. R14是链接寄存器LR,在ISR中(此刻我们在SVC的ISR中) ,它记录了异常返回值EXC_RETURN而EXC_RETURN只有6个合法的值(M4、M7),如下表所示:
    stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

在vPortSVCHandler函数里使用如下代码的就是上图最后一行最后一列的情况
以下代码的详细分析:FreeRTOS ------ prvStartFirstTask 和 vPortSVCHandler

orr r14, #0xd		//R14|=0X0D使得硬件在退出时使用PSP完成出栈操作并返回后进入任务模式Thumb状态,在SVC中断服务里面使用的是MSP,ARM状态 
    bx r14		//异常返回,由于上一句指令切换到PSP,返回时会自动出栈。因此PSP指针自动将栈中剩下的内容加载到CPU寄存器:xPSR, PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务形参)。出栈操作完成后,PSP指向任务栈的栈顶。

2、任务切换:主动和被动

被动:系统节拍时钟中断(SysTick定时器),在中断里调用pendSVC
主动:执行系统调用代码。普通任务使用taskYIELD()强制任务切换;中断服务程序使用portYIELD_FROM_ISR()强制任务切换;在应用程序里也可以通过设置xYieldPending的值来通知调度器进行任务切换。

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
可以发现不论是主动还是被动,最终都是通过设置NVIC的pendSVC中断寄存器产生pendSVC中断

参考链接如下:
FreeRTOS原理剖析:任务切换过程
任务调度原理 通俗详解(FreeRTOS)

3. 任务切换过程

void xPortPendSVHandler( void )

说明:

在Cortex-M处理器中有两个栈指针,一个是主栈指针(Main Stack Pointer,即MSP),它可用于线程模式,在中断模式下只能用MSP;另一个是进程堆栈指针(Processor Stack Pointer,即PSP),PSP总是用于线程模式。在任何时刻只能使用到其中一个。
复位后处于线程模式特权级,默认使用MSP。在FreeRTOS中,MSP用于OS内核和异常处理,PSP用于应用任务。
通过设置CONTROL寄存器的bit[1]选择使用哪个堆栈指针。CONTROL[1]=0选择主堆栈指针;CONTROL[1]=1选择进程堆栈指针。

vTaskSwitchContext()

该函数会更新当前任务运行时间,检查任务堆栈使用是否溢出,然后调用宏
taskSELECT_HIGHEST_PRIORITY_TASK()获取更高优先级的任务。

寻找下一个任务的方式
PendSV中会调用vTaskSwitchContext(),最后调用函数taskSELECT_HIGHEST_PRIORITY_TASK()寻找优先级最高的任务。
对于FreeRTOS的调度器,它有两种方式寻找下一个最高优先级的任务,分别为特殊方式和常用方式,在FreeRTOSConfig.h中可通过宏定义设置,如下:

/* 0:使用常用方式来选择下一个要运行的任务;1:使用特殊方法来选择下一个要运行的任务 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION	1

在FreeRTOS中,通用方法不依赖某些硬件等限制,适用于多种MCU中。特殊方式是使用了某些硬件的特性,只针对部分MCU而使用。

常用方法
uxTopReadyPriority 记录就绪态中最高优先级值,创建任务时会更新值,有任务添加到就绪表时也会更新值。这种方法对任务的数量无限制。

#define taskSELECT_HIGHEST_PRIORITY_TASK()												\
{																						\
	UBaseType_t uxTopPriority = uxTopReadyPriority;										\
																						\
	while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) )				\
	{																					\
		configASSERT( uxTopPriority );													\
		--uxTopPriority;																\
	}																					\
																						\
	/* 获取优先级最高任务的任务控制块 */														\
	listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );\
	
	uxTopReadyPriority = uxTopPriority;	 												\
}

特殊方式
使用此方法,uxTopReadyPriority 每个bit位表示一个优先级,bit0表示优先级0,bit31表示优先级31,使用此方式优先级最大只能是32个。

#define taskSELECT_HIGHEST_PRIORITY_TASK()														\
{																								\
	UBaseType_t uxTopPriority;																		\
																								\
	/* 获取优先级最高的任务 */								\
	portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );								\
	configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 );		\
	
	/* 获取优先级最高任务的任务控制块 */
	listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );		\
} 


其中:

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

__clz( ( uxReadyPriorities ) 是计算uxReadyPriorities 的前导零个数,如:
二进制0001 1010 0101 1111的前导零个数为3,可以知道,最高优先级uxTopPriority 等于 31减去前导零个数。
知道最高优先级的优先级,则通过listGET_OWNER_OF_NEXT_ENTRY()对应最高优先级的列表项,将pxCurrentTCB指向对应的控制块。

寻找下一个任务的方式详细参考链接如下:
FreeRTOS原理剖析:任务切换过程
支持多优先级

FreeRTOS时间片调度

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
详细参考链接:FreeRTOS任务切换

STM32的PendSV和SVC

xPSR寄存器

参考:《Cortex-M3 权威指南》

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

LR寄存器

1、调用子函数时,LR存放的是返回地址
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

2、当异常中断发生时,存放的时异常中断码

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

因为中断发生时会进行现场保护,中断出的PC,LR会自动入栈,所以不需要再在LR里写入返回地址
而是在LR写入了异常码,不同的异常码有不同的用处

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

vTaskDelay()、vTaskDelayUntil() 任务延时

FreeRTOS原理剖析:任务延时

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

手把手教你FreeRTOS源码详解(二)——任务管理
关于任务调度器的一些解释:
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

FreeRTOS高级篇9—FreeRTOS系统延时分析
相对延时:
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
绝对延时:
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

任务延时列表的实现对延时中断节拍数有详解:

/* 计算任务延时到期时,系统时基计数器xTickCount的值是多少 */(3)
    xTimeToWake = xConstTickCount + xTicksToWait;

vPortSetupTimerInterrupt()设置systick节拍中断

延时函数中有一个重要的变量:xTickCount
该变量每次到达systick设置的节拍中断就会加1,在函数xTaskIncrementTick()中实现(任务延时列表的实现)

FreeRTOS按照教程移植好之后就可以使用了。需要注意的是从官网下载的针对keil环境下STM32F103的FreeRTOS文件,使用systick时钟作为rtos的时钟,而这个systick的时钟源会被默认选择为STM32的内核时钟,而不能选择外部时钟。原因就是当rtos启动时(vTaskStartScheduler();这个函数开始被调用的时候),systick就会被按默认的参数配置好,而不是需要用户自己去配置,这些参数在FreeRTOSconfig.h文件里面
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
88,89,90行,这几行的宏定义决定了systick的初始化参数,其中configCPU_CLOCK_HZ是cpu时钟频率,也是systic的时钟源,如果不打算自己从头到尾来配置systick,一定不要修改这个参数。configTICK_RATE_HZ是systick中断的频率,上图里面是1000HZ。而第88行是我自己加的,它的原型隐藏在port.c文件里面
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
如果你不去额外定义它一下,它就会被定义成0,这个参数在什么地方用到了呢?

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
同样是port.c文件,有上面这么一段代码,如果configOVERRIDE_DEFAULT_TICK_CONFIGURATION 被定义成0,就去定义一个名为vPortSetupTimerInterrupt()的函数,这个函数的作用是初始化systick定时器。注意这个函数里面的最后2行,首先它的reload值是直接根据stm32的内核时钟去计算的。另外,在这个函数里面的最后一行,portNVIC_SYSTICK_CTRL_REG与stm32库里面的SysTick->CTRL是一个意思,表示的systick的CTRL控制寄存器。CM3参考手册里面,描述里这个CTRL寄存器的各个位的作用
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
可以看出,最后一行的目的有3个:选择时钟源为内核时钟;选择计数值到达0时会引起中断;开启计数器的连拍模式(使能计数器) 现在终于可以看出来configOVERRIDE_DEFAULT_TICK_CONFIGURATION到底是干什么的了:如果用户没有把这个东西定义成一个非0的量,它就会被默认定义为0,这样的结果就是默认把systick的时钟源设置为stm32的内核时钟。 一种很可能的误会就是用户在启动RTOS之前已经配置好了systick,时钟源设置为系统时钟的1/8,并把它用于软件延时。结果并没有发现FreeRTOS启动的时候把systick又重新配置了一遍,导致延时不准了。然后用户去freeRTOS留给用户的配置文件里去找,发现了这里有个东西还没改呢#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )就自作聪明的改成这样(我自己开始就是这么干的)#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000/8 )那就踩到坑了。如果用户使用不依赖中断的查询式的软件延时,还是会出现延时不准的后果。因为FreeRTOS启动的时候还是帮你把systick的时钟源改成了内核时钟(72M),而不是预期的9M,所以计数器的频率是72M,但是计算reload值是用的9M时钟源,导致中断的周期却是正确的。 正确的配置并使用systick的方案有2个:一.就使用内核时钟作为时钟源。延时什么的也都按照这个计算。二.如果确实想使用1/8的时钟源,就要这样配置1.在FreeRTOSconfig.h文件里面加上这个宏定义,定义成非0的数#define configOVERRIDE_DEFAULT_TICK_CONFIGURATION 1 [size=10.5000pt]2.自己在需要的地方定义好systick的初始化函数,并把它命名为 void vPortSetupTimerInterrupt( void )这样会带来一个问题就是tickless idle的功能不能用了,也希望有前辈指点一下这个问题怎么解决 3.至于这个宏定义就不需要改动了因为它已经不会被使用了#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )

taskSELECT_HIGHEST_PRIORITY_TASK()

pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList )

listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

heap堆内存分配

详情参考如下链接:
FreeRTOS的内存分配heap_1/2/3/4
freeRTOS中heap_1/2/3/4/5.c的对比
下面只对heap_2.c再做一遍梳理

2、heap_2.c

1、初始化链表

static void prvHeapInit( void )
{
    BlockLink_t * pxFirstFreeBlock;
    uint8_t * pucAlignedHeap;

    /* Ensure the heap starts on a correctly aligned boundary. */
    pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) & ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
//获取到地址对齐后的内存堆首地址
    /* xStart is used to hold a pointer to the first item in the list of free
     * blocks.  The void cast is used to prevent compiler warnings. */
//对空闲块链表进行初始化。留意,xStart是链表头,并不表示一个空闲块,xEnd是链表尾,也不表示一个空闲块,但记录着整个堆的大小。
    xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
    xStart.xBlockSize = ( size_t ) 0;

    /* xEnd is used to mark the end of the list of free blocks. */
    xEnd.xBlockSize = configADJUSTED_HEAP_SIZE;
    xEnd.pxNextFreeBlock = NULL;

    /* To start with there is a single free block that is sized to take up the
     * entire heap space. */
    pxFirstFreeBlock = ( void * ) pucAlignedHeap;
    pxFirstFreeBlock->xBlockSize = configADJUSTED_HEAP_SIZE;
    pxFirstFreeBlock->pxNextFreeBlock = &xEnd;
}

经过上面的初始化流程后,整个链表如下图所示。注意,xStart和xEnd是存在于静态存储区中,并不在FreeRTOS申请的内存堆数组中,但初始时第一个节点却在内存堆数组中。我用的编译器是Keil MDK 5.11,并且将FreeRTOS移植到STM32上,因此一个A_BLOCK_LINK的大小为8个字节。
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

2、确定申请内存大小并对齐

/* The wanted size is increased so it can contain a BlockLink_t 
structure in addition to the requested amount of bytes. */  
if( xWantedSize > 0 )  
{  
 xWantedSize += heapSTRUCT_SIZE;  
 //这里用到的空闲块头部并不是直接sizeof(BlockLink_t),而是heapSTRUCT_SIZE,这个常量也定义在Heap_2.c中,这是对空闲块头部的大小再进行了一次大小对齐。
  
 /* Ensure that blocks are always aligned to the required number of bytes. */  
 if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0 )  
 {  
  /* Byte alignment required. */  
  xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );  
 }  
}  

在真正开始分配内存时,用vTaskSuspendAll()挂起所有任务,防止分配内存的过程被中断,确保操作的原子性。紧接着,如果是第一次调用pvPortMalloc(),则调用prvHeapInit()对内存堆和空闲块链表进行初始化。由于在Heap_2中将内存堆用空闲块处理,因此用户每申请一次内存,FreeRTOS都会在申请的空间前加上空闲块头部BlockLink_t,用于记录分配出去的空间的大小。因此,真正要分配的内存空间大小就等于用户申请的内存空间大小加上空闲块头部的大小。加上头部之后,还要对整个大小进行对齐。因此,在真正分配空间之前,FreeRTOS都对用户申请的空间大小进行了调整。如上面的代码所示。

if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
//初始化的时候xFreeBytesRemaining  = configADJUSTED_HEAP_SIZE,后续等于剩下空闲的内存大小
        {
            /* Blocks are stored in byte order - traverse the list from the start
             * (smallest) block until one of adequate size is found. */
            pxPreviousBlock = &xStart; //1.
            pxBlock = xStart.pxNextFreeBlock;//2.

            while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
            {
            /*best fit algorithm的实现了。在这里,空闲块的大小是按从小到大的顺序排列的。因此,遍历链表,
找到第一块比申请空间大的空闲块即为最合适的空闲块。然后返回这个空闲块头后的首地址。
*/
                pxPreviousBlock = pxBlock;
                pxBlock = pxBlock->pxNextFreeBlock;
            }

            /* If we found the end marker then a block of adequate size was not found. */
            if( pxBlock != &xEnd )
            {
                /* Return the memory space - jumping over the BlockLink_t structure
                 * at its start. */
         //注意,一定是空闲块头后的首地址哦,要是直接将空闲块的首地址返回的话,那用户就会将空闲块头修改了。
                pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + heapSTRUCT_SIZE );//3.

                /* This block is being returned for use so must be taken out of the
                 * list of free blocks. */
			//当前空闲的内存块分了出去,那么当前的空闲内存块的链表节点就不在该链表
                pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;//4.

                /* If the block is larger than required it can be split into two. */
                if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
                {
                /*分配出去的空闲块的剩余空间要是比两倍的空闲块头还要大,则将分配出去的这个空闲块分割剩余
                的空间出来,重新放到空闲块链表中。例如,初始化后只有一个空闲块,这个空闲块大小为17KB。
                经过调整后的用户申请空间大小为1KB,则FreeRTOS就从这个空闲块靠近块首的地方分割出1KB出
                来分配出去,剩余的16KB则重新放回空闲块链表里。以便下一次继续分配。
                */
                    /* This block is to be split into two.  Create a new block
                     * following the number of bytes requested. The void cast is
                     * used to prevent byte alignment warnings from the compiler. */
                    pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );//5.

                    /* Calculate the sizes of two blocks split from the single
                     * block. */
                    pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;//6.
                    pxBlock->xBlockSize = xWantedSize;//7.

                    /* Insert the new block into the list of free blocks. */
                    prvInsertBlockIntoFreeList( ( pxNewBlockLink ) );
                }

                xFreeBytesRemaining -= pxBlock->xBlockSize;
            }
        }

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

#define prvInsertBlockIntoFreeList( pxBlockToInsert )                                                                               \
    {                                                                                                                               \
        BlockLink_t * pxIterator;                                                                                                   \
        size_t xBlockSize;                                                                                                          \
                                                                                                                                    \
        xBlockSize = pxBlockToInsert->xBlockSize;                                                                                   \
                                                                                                                                    \
        /* Iterate through the list until a block is found that has a larger size */                                                \
        /* than the block we are inserting. */                                                                                      \       //1.
        for( pxIterator = &xStart; pxIterator->pxNextFreeBlock->xBlockSize < xBlockSize; pxIterator = pxIterator->pxNextFreeBlock ) \
        {                                                                                                                           \
            /* There is nothing to do here - just iterate to the correct position. */                                               \
        }                                                                                                                           \
                                                                                                                                    \
        /* Update the list to include the block being inserted in the correct */                                                    \
        /* position. */                                                                                                             \
        pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;        //2.                                                     \
        pxIterator->pxNextFreeBlock = pxBlockToInsert;                   //3.                                                           \
    }

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

多态

中断

详细参考链接:
临界段的保护

#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )

/* 不带中断保护的关/开中断函数 */

进入后屏蔽掉内核层调度器相关的中断,systick、pendsvc,调度器不再调度,任务不再切换 退出后,所有中断都开启

当在中断函数里处理边界代码段时,因为已经进入到中断函数了,所以肯定有某些中断没有被屏蔽,那么在退出该边界代码段时就需要将basepri寄存器恢复到进入该中断前的状态(某部分中断不会被屏蔽,我们也不需要知道具体屏蔽的细节),使用的宏如下:

/* 带返回值的关中断函数,可以嵌套,可以在中断里面使用 */(2)
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

进入后屏蔽掉内核层调度器相关的中断,systick、pendsvc,调度器不再调度,任务不再切换 退出后,恢复到进入中断前的中断屏蔽状态

这两种方法的更上层封装如下:

#define taskENTER_CRITICAL()                portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL()         portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

上述方法最终都是把寄存器basepri值设置为 configMAX_SYSCALL_INTERRUPT_PRIORITY

#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( 5 << 4 ) 

configMAX_SYSCALL_INTERRUPT_PRIORITY的时候(该宏在F reeRTOSConfig.h中定义,现在配置为5),会让系统不响应比该优先级低的中断,而优先级比之更高的中断则不受影响。就是说当这个宏定义配置为5的时候,中断优先级数值在0、1、2、3、4的这些中断是不受FreeRTOS管理的,不可被屏蔽,而中断优先级在5到15的中断是受到系统管理,可用被屏蔽的。
当调度器启用时,systick和pendsvc的优先级都被置为最低240,240大于11,当优先级值大于5的,中断会被屏蔽
优先级值小于5的中断会被响应

任务多态

stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件
stm32 freertos多任务状态迁移,中断临界段,任务延时,stm32,C,stm32,单片机,嵌入式硬件

临界段与互斥量

临界段用一句话概括就是一段在执行的时候不能被中断的代码段。(系统中断pendSV不行,外部中断可以)

互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不
同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资
源的独占式处理。

两者有相似之处:

都是用来保护一段代码,形式上也就是都在一段代码的前后

不同之处:

1、 临界段不会被系统调用打断,完整执行这段段代码

2、任务获取到互斥信号量后执行共享资源代码时,是会被系统调用打断从而执行其它任务的,如果其它任务同级,且没有获取互斥信号量也就是其它任务不争抢该资源的话,该任务是可以执行时间片时间
只有当其它任务也要获取该互斥信号量,也就是争抢该资源,其它任务就会先阻塞等待另一个任务使用完共享资源并释放互斥信号量后才会唤醒文章来源地址https://www.toymoban.com/news/detail-526189.html

到了这里,关于stm32 freertos多任务状态迁移,中断临界段,任务延时的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【FreeRTOS】——中断优先级设置&中断相关寄存器&临界段代码保护&调度器挂起与恢复

    目录 前言: 一、中断优先级设置 二、中断相关寄存器(STM32-Cortex M3) 三、临界段代码保护 四、任务调度器的挂起和恢复 总结: 博客笔记根据正点原子视频教程编辑,仅供学习交流使用! ①中断概念回顾 让CPU打断正常运行的程序,转而去处理紧急的事件(程序),就叫中

    2024年02月10日
    浏览(52)
  • 解决STM32在延时时无法进入中断的问题

    问题: 在使用zigbee模块时,使用串口作为无线收发的载体,与stm32通信,使用DHT11作为简单的受控源,出现了bug: 当在while(1)里使用delay函数,或者使用DHT11_Read_Data函数时,程序无法进入串口接收中断,无法使用远程控制 其中的while函数为: 解决思路: 检查接线之后,我首

    2024年02月12日
    浏览(40)
  • STM32不使用中断实现定时器微秒级精确延时

    我们在写代码的时候避免不了要使用延时函数,很多延时函数都是使用中断或者tick来实现的,tick的方式最大到毫秒ms级别,通过中断方式的通用定时器来实现,如果实现1us的延时那么每1us就来一次中断,很影响cpu的效率。 本文提供一种 不使用中断 ,并且很简单的方式实现的

    2024年04月11日
    浏览(49)
  • STM32用FreeRTOS串口中断接收卡死问题

    现在意法半导体的配套软件做得很全面了,简直可以说是保姆式的服务。从芯片选型,引脚定义,到代码模板生成,一条龙服务,很方便。但是方便也有方便的坏处,那就是有些细节的规则会造成天然的bug。 比如,在stm32cubeIDE里新建工程的时候,如果勾选了FREERTOS 那么,程序

    2024年02月15日
    浏览(43)
  • STM32 CubeMX (第三步Freertos中断管理和软件定时)

    学习使用Freertos第三步 在 FreeRTOS 中,中断管理和软件定时: · taskENTER_CRITICAL() ·; 是一个函数在 FreeRTOS 中使用的,用于进入临界区(critical section)。在临界区内,中断会被禁用,这样可以确保在多任务环境下共享资源的安全性。你可以在需要保护共享资源的代码段中使用 ·

    2024年02月12日
    浏览(67)
  • STM32+FREERTOS任务堆栈大小

    在FREERTOS任务开发过程中,由于不知道具体需要分配多大的任务堆栈大小,就需要在开始开发阶段尽可能的多分配一些,不然在调试过程中会出现程序卡死或者数据通信异常的现象。 如何评估任务堆栈的分配大小问题,可以根据任务的规模以及所任务所需的数据空间大概进行

    2024年02月16日
    浏览(43)
  • STM32FreeRTOS任务通知(STM32cube高效开发)

    1、任务通知可模拟队列和信号量 任务通知是FreeRTOS另外一种进程间通信技术。不需要创建任何中间对象,可以直接从任务向任务或ISR向任务发送通知,传递一个通知值任务通知可以模拟二值信号量、计数信号量、或长度为1的消息队列,使用任务通知,一般效率更高,消耗内

    2024年03月13日
    浏览(49)
  • STM32 HAL库 中断处理中使用延时函数(HAL_Delay)

    今天在学习STM32 HAL库外部中断,使用的是按键触发外部中断,想在外部中断里面写一个按键消抖,也就是在HAL库外部中断处理函数中使用了HAL_Delay() 函数,后来经过实验,在外部中断处理函数(void EXTI1_IRQHandler(void))和中断回调函数(void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin))里面都

    2024年03月16日
    浏览(46)
  • FreeRTOS如何解决访问冲突/线程不安全(临界段、互斥锁、挂起调度、看门人任务)

    在多任务(多线程)系统中,存在一个隐患,那就是多线程的访问(在FreeRTOS中就是任务)。当一个任务A开始访问一个资源(外设、一块内存等),但是A还没有完成访问,B任务运行了,也开始访问,这就会造成数据破坏、错误等问题。 例如: 两个任务试图写入一个液晶显示

    2024年02月07日
    浏览(166)
  • 【HAL库】STM32CubeMX开发----非阻塞延时实验----SysTick(滴答定时器)中断

    STM32CubeMX 下载和安装 详细教程 【HAL库】STM32CubeMX开发----STM32F103/F207/F407----目录 HAL库 有自带的 ms级 延时函数: HAL_Delay(); 缺点: 这是 阻塞延时 方式,就是延时期间,什么都不能干,这样很浪费资源。 这篇文章主要介绍,利用 SysTick(滴答定时器)中断 实现 非阻塞延时 的实验

    2024年02月16日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包