FreeRTOS

1. 任务切换:

使用vTaskDelayUntil、vTaskDelay、xQueueSend函数可以引起任务切换从中断函数中退出后,执行高优先级任务:

//如果 xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);      

栈溢出钩子函数方式一: 用来检测任务栈溢出
说明: 如果栈溢出了,在任务切换的时候会自动调用触发栈溢出钩子函数.
做法:

#define configCHECK_FOR_STACK_OVERFLOW     1
void vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName )
{
	//打印出栈溢出的任务名 和 栈溢出信息
    printf("任务:%s 发现栈溢出\r\n", pcTaskName);                 
}

2. 调度锁:

调度锁开启函数

 vTaskSuspendAll ;

调度锁关闭函数

xTaskResumeAll,

他们之间不能使用引起任务切换的函数;
作用:禁止任务调度,防止被被高优先级的任务抢占

3. 时间延迟:

作用:(1).阻塞引起不同优先级任务的切换;(2).实现时间片调度,引起同一优先级任务的切换
void vTaskDelay(const TickType_t xTicksToDelay ); /* 延迟时间长度 */ ————–相对延迟(2 + 10)

void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, /* 存储任务上次处于非阻塞状态时刻的变量地址 /
const TickType_t xTimeIncrement ) /
周期性延迟时间 */ ————–绝对延迟 (10, 函数执行时间2包含在10内)

volatile TickType_t xTaskGetTickCount( void ); ——–在任务函数中获取系统当前运行的时钟节拍数
volatile TickType_t xTaskGetTickCountFromISR( void ); ————-在中断函数中获取系统当前运行的时钟节拍数

4. 事件标志组:

xEventGroupCreate()
作用:类似于全局变量,实现多任务同步,相比之下,能够防止多任务的访问冲突,让RTOS 内核有效地管理任务

xEventGroupCreate()                                                     
vEventGroupDelete()
xEventGroupWaitBits()                                                                			xEventGroupClearBits()  // 在中断函数中需添加FromISR()
xEventGroupSetBits()                                                                  xEventGroupGetBits()     //  在中断函数中需添加FromISR()
xEventGroupSetBitsFromISR()                                                     xEventGroupSync()

EventGroupHandle_t xEventGroupCreate( void ); 成功返回事件标志组的句柄,由heap空间不足,失败返回NULL

EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup, /* 事件标志组句柄 /
const EventBits_t uxBitsToSet ); /
事件标志位设置 */ 例如: BIT_1 (1 << 1) BIT_ALL (BIT_0 | BIT_1)

BaseType_t xEventGroupSetBitsFromISR(
EventGroupHandle_t xEventGroup, /* 事件标志组句柄 /
const EventBits_t uxBitsToSet, /
事件标志位设置 */
BaseType_t pxHigherPriorityTaskWoken ); / 高优先级任务是否被唤醒的状态保存 */ ———-此参数的数值是pdTRUE,说明有高优先级任务要执行
例如:
BaseType_t xHigherPriorityTaskWoken = pdFALSE; &xHigherPriorityTaskWoken

EventBits_t xEventGroupWaitBits(
const EventGroupHandle_t xEventGroup, /* 事件标志组句柄 /
const EventBits_t uxBitsToWaitFor, /
等待被设置的事件标志位 / 例如:
BIT_1 BIT_ALL
const BaseType_t xClearOnExit, /
选择是否清零被置位的事件标志位 /—–一般设为 pdTRUE,其设置的溢出时间内返回,被设置的事件标志位会被清零
const BaseType_t xWaitForAllBits, /
选择是否等待所有标志位都被设置 /—–一般设为 pdTRUE,其等待2个参数所指定的标志位全部被置 1,函数才可以返回
TickType_t xTicksToWait ); /
设置等待时间 */——–设置等待时间,如果设置为 portMAX_DELAY,表示永久等待

注意: 不能在中断函数中使用; 通过返回值用户可以检测是哪个事件标志位被置 1 ,但如果超时,函数的返回值可会有部分事件标志位被置 1

5. 定时器组:xTimerCreate( )

作用: 用户可以创建多个定时器(软件定时器),实现定时调用函数功能
重点:

xTimerCreate()
xTimerStart ()
pvTimerGetTimerID ()

//成功返回定时器句柄,失败返回NULL
TimerHandle_t xTimerCreate
( const char * const pcTimerName, /* 定时器名字 / 例如: “Timer”
const TickType_t xTimerPeriod, /
定时器周期,单位系统时钟节拍 / 例:const TickType_t xTimerPer = 100; ==》 xTimerPer
const UBaseType_t uxAutoReload, /
选择单次模式或者周期模式 / ———若参数为 pdTRUE,则表示选择周期模式;pdFALSE,则表示选择单次模式
void * const pvTimerID, /
定时器 ID */ 例如: (void )1
TimerCallbackFunction_t pxCallbackFunction ); /
定时器回调函数 */ 时间到要执行的函数,例如:vTimerCallback

//pdPASS 表示此函数向消息队列发送消息成功, 作用; 激活定时器
BaseType_t xTimerStart
( TimerHandle_t xTimer, /* 定时器句柄 /
TickType_t xBlockTime ); /
成功启动定时器前的最大等待时间设置,单位系统时钟节拍 */ 例如: 100

void pvTimerGetTimerID( TimerHandle_t xTimer ); / 定时器句柄 */ 作用: 获取定时器ID,一般用于定时器回调函数

它们的常用组合方法如下

static void AppObjCreate (void)
{
	const TickType_t xTimerPer = 100;
	/*
	创建定时器,如果在 RTOS 调度开始前初始化定时器,那么系统启动后才会执行。
	*/
	xTimers = xTimerCreate("Timer", /* 定时器名字 */
	xTimerPer, /* 定时器周期,单位时钟节拍 */
	pdTRUE, /* 周期性 */
	(void *) i, /* 定时器 ID */
	vTimerCallback); /* 定时器回调函数 */
	if(xTimers == NULL)
	{
		/* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
	}
	else
	{
		/* 启动定时器,系统启动后才开始工作 */
		if(xTimerStart(xTimers, 100) != pdPASS)
		{
			/* 定时器还没有进入激活状态 */
		}
	}
}

static void vTimerCallback(xTimerHandle pxTimer)              //  定时器回调函数
{
	uint32_t ulTimerID;
	configASSERT(pxTimer);
	/* 获取那个定时器时间到 */
	ulTimerID = (uint32_t)pvTimerGetTimerID(pxTimer);
	/* 处理定时器 0 任务 */
	if(ulTimerID == 0)
	{
		bsp_LedToggle(1);
	}
	/* 处理定时器 1 任务 */
	if(ulTimerID == 1)
	{
		bsp_LedToggle(2);
	}
}

6. 消息队列

作用:消息队列传递的是实际数据,并不是数据地址

xQueueCreate()
vQueueDelete()
xQueueSend()

//成功会返回消息队列的句柄
QueueHandle_t xQueueCreate
( UBaseType_t uxQueueLength, /* 消息个数 / 例如 10
UBaseType_t uxItemSize ); /
每个消息大小,单位字节 */ 例如: sizeof(uint8_t) sizeof(struct Msg *)

//消息成功发送返回 pdTRUE
BaseType_t xQueueSend(
QueueHandle_t xQueue, /* 消息队列句柄 /
const void * pvItemToQueue, /
要传递数据地址 /
TickType_t xTicksToWait /
等待消息队列有空间的最大等待时间 */
);

BaseType_t xQueueSendFromISR
(
QueueHandle_t xQueue, /* 消息队列句柄 */
const void pvItemToQueue, / 要传递数据地址 */
BaseType_t pxHigherPriorityTaskWoken / 高优先级任务是否被唤醒的状态保存 */ 此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有
);

//接到到消息返回 pdTRUE
BaseType_t xQueueReceive(
QueueHandle_t xQueue, /* 消息队列句柄 */
void pvBuffer, / 接收消息队列数据的缓冲地址 /
TickType_t xTicksToWait /
等待消息队列有数据的最大等待时间 */
)

7. 看门狗

作用; 实现复位功能
实现模型
uxBits = xEventGroupWaitBits(xCreatedEventGroup,
TASK_BIT_ALL,
pdTRUE,
pdTRUE,
xTicksToWait); 我们设置每个任务 10s 内必须发一次事件标志以此来表示任务在执行。如果 10s 内有一个任务没有发来消息,系统会被复位
其它四个发送事件标志的任务:

xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_0);
xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_1);
xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_2);
xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_3);

8. 多任务系统:

作用:不需要采用超级循环,避免了软件开销的增加和中断函数的使用.
注意: 多任务系统同一时刻只能有一个任务可以运行,只是通过调度器的决策,看起来像所有任务同时运行一样。
(1) 启动 RTOS,首先执行高优先级任务(vTaskStartScheduler)。
(2) 高优先级任务等待事件标志( xEventGroupWaitBits )被阻塞,低优先级任务得到执行。
(3) 低优先级任务执行的过程中产生 USB 中断,进入 USB 中断服务程序。
(4) 退出 USB 中断复位程序,回到低优先级任务继续执行。
(5) 低优先级任务执行过程中产生串口接收中断,进入串口接收中断服务程序。
(6) 退出串口接收中断复位程序,并发送事件标志设置消息( xEventGroupSetBitsFromISR ),被阻塞的高优先级任务就会重新进入就绪状态,这个时候高优先级任务和低优先级任务都在就绪态,抢占式调度器就会让高优先级的任务先执行,所以此时就会进入高优先级任务。
(7) 高优先级任务由于等待事件标志( xEventGroupWaitBits )会再次被阻塞,低优先级任务开始继续执行。
(8) 低优先级任务调用函数 vTaskDelay ,低优先级任务被挂起,从而空闲任务得到执行。
(9) 空闲任务执行期间发生滴答定时器中断,进入滴答定时器中断服务程序。
(10) 退出滴答定时器中断,由于低优先级任务延时时间到,低优先级任务继续执行。
(11) 低优先级任务再次调用延迟函数 vTaskDelay ,低优先级任务被挂起,从而切换到空闲任务。
空闲任务得到执行。

//用于启动 FreeRTOS 调度器,即启动 FreeRTOS 的多任务执行
void vTaskStartScheduler( void );
创建失败主要是堆空间不足,此时需要加大 FreeRTOSConfig.h 文件中定义的 heap 大小
if(xHandleTaskStart!= NULL) 判断任务是否执行,执行则不为NULL

//创建操作系统的任务
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode, /* 任务函数 / 例如 vTaskStart
const char * const pcName, /
任务名 / 例如 “vTaskStart”
unsigned short usStackDepth, /
任务栈大小,单位 word,也就是 4 字节 */ 例如: 512
void pvParameters, / 任务参数 / 例如; NULL
UBaseType_t uxPriority, /
任务优先级 */ 例如: 3 ;需在宏 #define configMAX_PRIORITIES ( 5 )的范围0-4中选择,但不能选择空闲任务0,即选择1-4中数字
TaskHandle_t pvCreatedTask / 任务句柄 */ 例如: static TaskHandle_t xHandleTaskStart = NULL; //&xHandleTaskStart
);

//-删除操作系统的任务
void vTaskDelete( if(xHandleTaskStart!= NULL) {vTaskDelete(xHandleTaskStart )}
TaskHandle_t xTask ); /* 任务句柄 */ 例如 static TaskHandle_t xHandleTaskStart = NULL; ==》 xHandleTaskStart
(1) 如果任务句柄为NULL,即数值 0 的话,那么删除的就是当前正在执行的任务,此任务被删除后,FreeRTOS 会切换到任务就绪列表里面下一个要执行的最高优先级任务。
(2)调用这个函数的话,一定要让空闲任务有执行的机会(创建任务所需的内存需要在空闲任务中释放),否则这块内存是无法释放的。另外,创建的这个任务在使用中申请了动态内存,这个内存不会因为此任务被删除而删除,这一点要注意,一定要在删除前将此内存释放。

//挂起操作系统的任务
void vTaskSuspend(
TaskHandle_t xTaskToSuspend); /* 任务句柄 */ 例如 static TaskHandle_t xHandleTaskStart = NULL; ==》 xHandleTaskStart
(1)如果用往此函数里面填的任务句柄 是 NULL,即数值 0 的话,那么挂起的就是当前正在执行的任务,此任务被挂起后,FreeRTOS 会切换到任务就绪列表里面下一个要执行的高优先级任务。

//恢复挂起的任务
void vTaskResume(
TaskHandle_t xTaskToResume); /* 任务句柄 */
此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的xTaskResumeFromISR(),以后缀 FromISR 结尾。

空闲任务的作用
(1)用户不能让系统一直在执行各个应用任务,这样的话系统利用率就是 100%,系统就会一直超负荷运行,所以空闲任务很有必要
(2)为了更好的实现低功耗,空闲任务也很有必要,用户可以在空闲任务中实现睡眠,停机等低功耗措施。

9.中断优先级配置NVIC

中断优先级分组: STM32 支持 5 种优先级分组 抢占优先级支持嵌套;子(响应)优先级不支持嵌套 一般配置为NVIC_PriorityGroup_4
补充: 无论是抢占优先级还是子优先级,都是数字越小,优先级越高
NVIC_PriorityGroup_0 0 级抢占优先级 0-15 级子优先级
NVIC_PriorityGroup_1 0-1 级抢占优先级 0-7 级子优先级
NVIC_PriorityGroup_2 0-3 级抢占优先级 0-3 级子优先级
NVIC_PriorityGroup_3 0-7 级抢占优先级 0-1 级子优先级
NVIC_PriorityGroup_4 0-15 级抢占优先级 0 级子优先级
(1)具有高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行。
(2)在抢占式优先级相同的情况下,有几个子优先级不同的中断同时到来,那么高子优先级的中断优先被响应。
(3)在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被响应的低子优先级中断执行结束后才能得到响应,即子优先级不支持中断嵌套
PendSV———>任务切换的中断,SVC——>仅执行一次,用于启动第一个要执行的任务,SysTick———>系统时钟节拍中断
注意; 数字的数值越小,优先级越高

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); =========>把优先级分组设置为 4,
注意: 一旦初始化好 NVIC 的优先级分组后,切不可以在应用中再次更改
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x3000); //重映射中断向量表, 如果不配置默认从0的ROM开始读取,此处不放代码
其配置好的主要用法如下,
例如:

static void TIM_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;        /* 抢占优先级设置,优先级分组为 4 的情况下,抢占优先级可设置范围 0-15 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;      /* 子优先级设置,优先级分组为 4 的情况下,子优先级无效,取数值 0 即可 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x0f // 用于配置SysTick 中断和 PendSV 中断的优先级,这里配置为最低优先级15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01 // 配置允许用户在这个中断服务程序里面调用 FreeRTOS 的 API 的最高优先级,这里设置最高优先级为1,即抢占式优先级为 0 的中断里面是不允许调用的。

中断开关机制
// __set_PRIMASK(1) 禁止所有可关闭的中断, __set_PRIMASK(0) 不关中断

primask:
primask这是个只有 1 个 bit 的寄存器。 在它被置 1 后,就关掉所有可屏蔽的异常,只剩下NMI 和硬 fault 可以响应。它的缺省值是 0,表示没有关中断

basepri
basepri 这个寄存器最多有 9 位(由表达优先级的位数决定)。它定义了被屏蔽优先级的阈值。当它被设成某个值后,所有优先级号大于等于此值的中断都被关(优先级号越大,优先级越低)。但若被设成 0,则不关闭任何中断, 0 也是缺省值。

basepri 16   则关闭优先级大于16的所有中断,   
basepri  0        不关闭任何中断

10. 调度器:

使用相关的调度算法来决定当前需要执行的任务
所有调度器的共同特性:

  1. 调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而使得任务被挂起)
  2. 调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。当前正在执行的任务是运行态的任务
    区别: 如何分配就绪态任务间的完成时间
    补充: 嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在不同的嵌入式实时操作系统中区别不大,基本相同的硬件内核架构。

抢占式调度器
用途:用于不同优先级任务
原理: 当一个运行着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU 的控制权并运行。如果中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的低优先级任务被挂起,优先级高的那个任务开始运行。
总结:每个任务都被分配了不同的优先级,抢占式调度器会获得就绪列表中优先级最高的任务,并运行这个任务

时间片调度器
用途:用于相同优先级任务,时间片到了或调用阻塞函数则切换到下一个任务.
作用: 调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)
通过 #define configUSE_TIME_SLICING 1 实现时间片调度

11. 临界段和开关中断

临界段: 也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
用途: 一般在读取或者修改变量(特别是用于任务间通信的全局变量)的代码中使用
注意: 对于临界段要做到执行时间越短越好,否则会影响系统的实时性
原理: 任务代码中临界段的进入和退出主要是通过操作寄存器 basepri 实现的,进入临界段前操作寄存器 basepri 关闭了所有小于等于宏定义的中断,退出临界段时重新操作 basepri 寄存器。

#define taskENTER_CRITICAL()     portENTER_CRITICAL()
#define taskEXIT_CRITICAL()         portEXIT_CRITICAL()

(1) 允许嵌套临界区的进入和退出
函数:
taskENTER_CRITICAL() 进入临界区
taskEXIT_CRITICAL() 退出临界区
例如:

void FunctionA()
{
taskENTER_CRITICAL();      关闭中断
FunctionB(); 调用函数  B
taskEXIT_CRITICAL();         打开中断
}

(2) 不支持嵌套临界区的进入和退出
函数:
taskDISABLE_INTERRUPTS() 进入临界区
taskENABLE_INTERRUPTS() 退出临界区

void FunctionB()
{
	taskDISABLE_INTERRUPTS(); 关闭中断
	printf("任务 vTaskLED 正在运行\r\n");
	taskENABLE_INTERRUPTS(); 打开中断
}

12. 调度锁、任务锁和中断锁

调度锁: 调用了调度锁开关函数,处于调度锁开和调度锁关之间的代码在执行期间是不会被高优先级的任务抢占的
注意: 调度锁只是禁止了任务调度,并没有关闭任何中断,中断还是正常执行的
void vTaskSuspendAll( void ); 开启调度锁 BaseType_t xTaskResumeAll(void) 关闭调度锁
注意事项: 1. 两个函数一定要成对使用 2. 两个函数之间不能调用引起任务切换的API,比如 vTaskDelayUntil、vTaskDelay、xQueueSend 等

中断锁: 即使用进入临界区的函数,中断锁开和中断锁关之间的代码在执行期间是不会被中断打断的
注意: 要做到执行时间越短越好,否则会影响系统的实时性
taskENTER_CRITICAL(); 关闭中断 taskEXIT_CRITICAL(); 打开中断

任务锁: 使用调度锁功能给调度器加锁,将关闭任务切换功能,从而高优先级任务也就无法抢占低优先级任务的执行,同时高优先级任务也是无法向低优先级任务切换的。
注意: 调度锁只是禁止了任务调度,并没有关闭任何中断,中断还是正常执行的

FreeRTOS中的信号量:
计数信号量
二值信号量
互斥信号量

作用: 用于任务间的同步和资源共享机制,它们是基于消息队列实现的

13. 计数信号量

//如果创建成功会返回句柄, 失败返回NULL
SemaphoreHandle_t xSemaphoreCreateCounting(
UBaseType_t uxMaxCount, //支持的最大计数值, 设置此计数信号量支持的最大计数值
UBaseType_t uxInitialCount); // 初始计数值 , 设置计数信号量的初始值

//用于在任务代码中释放信号量,信号量释放成功返回 pdTRUE,否则返回 pdFALSE,返回失败的主要原因是消息队列已经满了
xSemaphoreGive(
SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */
注意: 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者xSemaphoreCreateCounting()创建了信号量

//用于中断服务程序中调用
xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
signed BaseType_t pxHigherPriorityTaskWoken / 高优先级任务是否被唤醒的状态保存 */ 此参数的数值是pdTRUE,说明有高优先级任务要执行,否则没有
)

//用于在任务代码中获取信号量, 返回 pdTRUE,否则返回 pdFALSE
xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 /
TickType_t xTicksToWait ); /
等待信号量可用的最大等待时间 */ 没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍
注意:

  1. 第二个参数为 0,那么此函数会立即返回, 设置为portMAX_DELAY,永久等待直到信号量可用
    2. 如果在中断服务程序中,则使用xSemaphoreTakeFromISR
    例如:
//设置最大等待时间为 300ms
const TickType_t xMaxBlockTime = pdMS_TO_TICKS(300); 
BaseType_t  Result = xSemaphoreTake(xSemaphore(TickType_t)xMaxBlockTime);

14. 二值信号量

是计数信号量的一种特殊形式,即共享资源为 1 的情况

xSemaphoreCreateBinary ()
xSemaphoreGive ()
xSemaphoreGiveFromISR ()
xSemaphoreTake ()

//创建二值信号量,成功返回句柄,失败返回NULL
SemaphoreHandle_t xSemaphoreCreateBinary(void)

//在任务代码中释放信号量
xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

//在中断代码中释放信号量
xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
signed BaseType_t pxHigherPriorityTaskWoken / 高优先级任务是否被唤醒的状态保存 */
)

//在任务代码中获取信号量
xSemaphoreTake( SemaphoreHandle_t xSemaphore, //信号量句柄
TickType_t xTicksToWait //等待信号量可用的最大等待时间 );

15. 互斥信号量

只能用在 FreeRTOS 的任务中,中断函数中不可使用.

xSemaphoreCreateMutex ()
xSemaphoreGive ()
xSemaphoreTake ()

//创建成功会返回互斥信号量的句柄
SemaphoreHandle_t xSemaphoreCreateMutex( void )

xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */

xSemaphoreTake( SemaphoreHandle_t xSemaphore, /* 信号量句柄 */
TickType_t xTicksToWait //等待信号量可用的最大等待时间);

16. 任务通知

每个已经创建的任务都有一个结构体变量, 用于记录任务的相关信息,其中有一个 32 位的变量成员 ulNotifiedValue 是专门用于任务通知的

用途: 通过任务通知方式可以实现 计数信号量,二值信号量,事件标志组 和 消息邮箱 (消息邮箱就是消息队列长度为 1 的情况)。
实现: 任务通知方式实现的计数信号量,二值信号量,事件标志组和消息邮箱是通过修改变量ulNotifiedValue 实现.
优点: 速度快,而且这种方式需要的 RAM 空间更小
缺点: 任务通知方式仅可以用在只有一个任务等待信号量,消息邮箱或者事件标志组的情况;并且发送消息的任务不支持超时等待

任务计数信号量:

  xTaskNotifyGive
  vTaskNotifyGiveFromISR
  ulTaskNotifyTake

BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); /* 任务句柄 */ 用于释放信号量(含任务二值信号量,任务计数信号量)
注意:

  1. 任务信号量的初始计数值是 0
  2. 宏#define configUSE_TASK_NOTIFICATIONS 1 如果不需要使用任务通知功能相关的函数, 需把宏置为 0

//用于中断函数中释放信号量
void vTaskNotifyGiveFromISR(
TaskHandle_t xTaskToNotify, /* 任务句柄 */
BaseType_t pxHigherPriorityTaskWoken ); / 高优先级任务是否被唤醒的状态保存 */

//用于中断函数中接收信号量, 返回值大于0则表示接收到消息
uint32_t ulTaskNotifyTake(
BaseType_t xClearCountOnExit, /* 选择是否清零用于任务通知的 ulNotifiedValue /
TickType_t xTicksToWait ); /
等待信号量可用的最大等待时间 */
补充:
第1个参数配置为 pdFALSE, ulNotifiedValue数值减一,这种方式用于任务计数信号量
第1个参数配置为 pdTRUE,ulNotifiedValue数值被清零,这种方式用于任务二值信号量
第2个参数表示等待信号量可用的最大等待时间
例如:

const TickType_t xBlockTime = pdMS_TO_TICKS(500);
ulNotifiedValue = ulTaskNotifyTake(pdFALSE, xBlockTime); 
if( ulNotifiedValue > 0 )
{
     printf("任务 vTaskMsgPro 接收到消息,ulNotifiedValue = %d\r\n", ulNotifiedValue);            /* 接收到消息 */
}

任务消息邮箱:
即将消息队列的长度设置为 1 的情况

xTaskNotify
xTaskNotifyFromISR
xTaskNotifyWait

//除eSetValueWithoutOverwrite模式外,成功返回 pdPASS, 在任务函数中发送消息邮箱数据
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify, /* 任务句柄 / 例如: xHandleTaskMsgPro 则把消息邮箱数据发给任务 vTaskMsgPro
uint32_t ulValue, /
更新任务控制块中的变量 ulNotifiedValue / 例如 count++
eNotifyAction eAction ); /
任务通知模式设置/ 支持 5 个参数,其中例如: eSetValueWithOverwrite 如果目标任务上次的数据还没有处理,会被覆盖

// 除eSetValueWithoutOverwrite模式外,成功返回 pdPASS , 在中断函数中发送消息邮箱数据
BaseType_t xTaskNotifyFromISR(
TaskHandle_t xTaskToNotify, /* 任务句柄 /
uint32_t ulValue, /
更新任务控制块中的变量 ulNotifiedValue /
eNotifyAction eAction, /
任务通知模式设置 */
BaseType_t pxHigherPriorityTaskWoken ); / 高优先级任务是否被唤醒的状态保存 *

//保存消息邮箱接收到的数据
BaseType_t xTaskNotifyWait(
/* 设置函数执行前清零任务控制块中变量 ulNotifiedValue 那些位 */
uint32_t ulBitsToClearOnEntry, 例如: 0x00000000 // 函数执行前保留所有位
/*设置函数退出前清零任务控制块中变量 ulNotifiedValue 那些位 /
uint32_t ulBitsToClearOnExit, 例如:0xFFFFFFFF //函数退出前清楚所有位
/
保存任务控制块中的变量 ulNotifiedValue 到指针变量 pulNotifiedValue 所指向的存储单元 */
uint32_t pulNotificationValue, 例如: &ulValue //保存 ulNotifiedValue 到变量 ulValue 中
/
等待消息通知的最大等待时间 */
TickType_t xTicksToWait ); 例如: 500

17. 动态内存管理

FreeRTOS 支持 5 种动态内存管理方案,分别通过文件 heap_1,heap_2,heap_3,heap_4 和 heap_5实现;
xPortGetFreeHeapSize 就能获得 FreeRTOS 动态内存的剩余。

动态内存管理方式一 heap_1:
特点: 5种动态内存管理最简单的,这种方式的动态内存管理一旦申请了相应内存后,是不允许被释放的
1. 项目应用不需要删除任务、信号量、消息队列等已经创建的资源。
2. 具有时间确定性,即申请动态内存的时间是固定的并且不会产生内存碎片。
3. 确切的说这是一种静态内存分配,因为申请的内存是不允许被释放掉的。
评价: 能够满足大部分嵌入式应用的需求,因为像任务创建、事件标志组这类的需要一直使用,不需要删除

动态内存管理方式二 heap_2:
特点: 利用了最适应算法,并且支持内存释放。但是 heap_2 不支持内存碎片整理
1. 不考虑内存碎片的情况下,这种方式支持重复的任务、信号量、事件标志组、软件定时器等内部资源的创建和删除
2. 如果用户申请和释放的动态内存大小是随机的,不建议采用这种动态内存管理方式
3. 如果用户随机的创建和删除任务、消息队列、事件标志组、信号量等内部资源也容易出现内存碎片
4. heap_2 方式实现的动态内存申请不具有时间确定性,但是比 C 库中的 malloc 函数效率要高
评价: 大部分需要动态内存申请和释放的小型实时系统项目可以使用 heap_2

动态内存管理方式三 heap_3:
特点
1. 需要调用编译器标准库提供 malloc 和 free 函数实现
2. 不具有时间确定性,即申请动态内存的时间不是固定的
3. 增加 RTOS 内核的代码量
注意: 这种方式的动态内存申请和释放不是用的FreeRTOSConfig.h 文件中宏定义的heap空间,而是启动文件.s中设置的heap空间大小

动态内存管理方式四 heap_4:
pvPortMalloc() 分配
vPortFree() 释放
特点: heap_4 动态内存管理利用了最适应算法,且支持内存碎片的回收并将其整理为一个大的内存块。
1. 可以用于需要重复的创建和删任务、信号量、事件标志组、软件定时器等内部资源的场合;
2. 随机的调用 pvPortMalloc() 和 vPortFree(),且每次申请的大小都不同,也不会像 heap_2 那样产生很多的内存碎片;
3. 不具有时间确定性,即申请动态内存的时间不是确定的,但是比 C 库中的 malloc 函数要高效。
评价: 比较实用,用户的代码也可以直接调用函数 pvPortMalloc() 和 vPortFree()进行动态内存的申请和释放。

动态内存管理方式五 heap_5:
特点可以采用不连续的内存区,比如定义在内部 SRAM 一部分,外部 SRAM 一部分,是在heap_4 的基础上实现的
创建任务 之前需对vPortDefineHeapRegions 进行初始化
注意: 一个是内存段结束时要定义 NULL。另一个是内存段的地址是从低地址到高地址排列。

总结:
heap_1:五种方式里面最简单的,但是申请的内存不允许释放。
heap_2:支持动态内存的申请和释放,但是不支持内存碎片的处理,并将其合并成一个大的内存块。
heap_3:将编译器自带的 malloc 和 free 函数进行简单的封装,以支持线程安全,即支持多任务调用。
heap_4:支持动态内存的申请和释放,支持内存碎片处理,支持将动态内存设置在个固定的地址。
heap_5:在 heap_4 的基础上支持将动态内存设置在不连续的区域上。

18. freertos低功耗模式:

     1.  睡眠模式(Cortex?-M3 内核停止,所有外设包括 Cortex-M3 核心的外设,如 NVIC、系统滴答定时器 Systick 等仍在运行)。
     2.  停机模式(所有的时钟都已停止)。
     3.  待机模式(1.8V 电源关闭)。

睡眠模式:

进入
通过执行 WFI(等待中断)或 WFE(等待事件)指令进入睡眠状态,但有以下两种机制选择:
1. SLEEP-NOW:如果 SLEEPONEXIT 位被清除,当 WFI 或 WFE 被执行时,微控制器立即进入睡眠模式。
2. SLEEP-ON-EXIT:如果 SLEEPONEXIT 位被置位,系统从最低优先级的中断处理程序中退出时,微控制器就立即进入睡眠模式。
评价: 实际应用中我们采用 WFI 指令进入睡眠模式,睡眠模式的进入机制是采用的 SLEEP-NOW。
具体情况:在 FreeRTOS 系统上,不使用 tickless 低功耗模式的话,我们可以将 WFI 指令放到空闲任务里面实现。不过,既然有了 tickless 模式,基本就 不需要采用这种方法了。

唤醒
任意中断也可以将其从睡眠态唤醒 .
由于我们是采用指令 WFI 进入睡眠模式,那么任意一个被嵌套向量中断控制器 NVIC 响应的外设中断都能将系统从睡眠模式唤醒。并且该模式唤醒所需的时间最短,因为没有时间损失在中断的进入或退出上。在 FreeRTOS 系统上,主要是周期性执行的系统滴答定时器中断会将系统从睡眠态唤醒,当然,其它的任意中断也可以将其从睡眠态唤醒。
低功耗模式下调试软件: 不能关闭 FCLK 或 HCLK
睡眠模式下
调用库函数:DBGMCU_Config(DBGMCU_SLEEP, ENABLE);初始化即可->为 HCLK 提供与FCLK(由代码配置的系统时钟)相同的时钟

停机模式:

进入
调用固件库函数 PWR_EnterSTOPMode
注意:所有的外部中断的请求位(挂起寄存器(EXTI_PR))和 RTC 的闹钟标志都必须被清除,否则停止模式的进入流程将会被跳过,程序继续运行。
唤醒
设置任一外部中断线 EXTI 为中断模式并且在 NVIC中必须使能相应的外部中断向量
停机模式下
调用库函数:DBGMCU_Config(DBGMCU_STOP, ENABLE);初始化即可->为 FCLK和 HCLK 提供时钟
例如:

 SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; /* 关闭滴答定时器 */
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFE);
portENTER_CRITICAL();
/*
1、当一个中断或唤醒事件导致退出停止模式时, HSI RC 振荡器被选为系统时钟。
2、退出低功耗的停机模式后,需要重新配置使用 HSE。
*/
RCC_HSEConfig(RCC_HSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET){}
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET){}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while (RCC_GetSYSCLKSource() != 0x08){}
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; /* 使能滴答定时器 */
portEXIT_CRITICAL();

待机模式:

进入:
调用固件库函数PWR_EnterSTANDBYMode 即可
退出:
可以通过外部复位(NRST 引脚)、IWDG 复位、WKUP 引脚上的上升沿或RTC 闹钟事件的上升沿。从待机唤醒后,除了电源控制/状寄存器,所有寄存器被复位。

  1. 初始化一个定时器中断,精度高于滴答定时器中断,这样才可以获得准确的系统信息 仅供调试目的,实际项目中不要使用,因为这个功能比较影响系统实时性
  2. 为了正确获取 FreeRTOS 的调试信息,可以考虑将上面的关闭中断指令__set_PRIMASK(1); 注释掉。
    vSetupSysInfoTest();
    printf(“K3 按键按下,系统进入待机模式,按 K2 按键将唤醒\r\n”);
    PWR_EnterSTANDBYMode();
    break;

19. tickless低功耗模式:

原理: 直接把睡眠或者停机模式直接放在空闲任务,进入空闲任务后,就是把低功耗的唤醒时间设置为从空闲模式切换到高优先级任务的时间,到时间后系统会从低功耗模式被唤醒,继续执行多任务。这个就是所谓的 tickless 模式。
现状: tickless 低功耗机制是当前小型 RTOS 所采用的通用低功耗方法。
实现:
把宏定义 configUSE_TICKLESS_IDLE 为 1 即可。如果配置此参数为 2,那么用户可以自定义 tickless 低功耗模式的实现

DBGMCU_Config(DBGMCU_SLEEP, ENABLE); //初始化 保证睡眠模式下调试器继续可以连接使用

如果使用 FreeRTOS 自带的 tickless 模式使用比较简单,只需用户使能宏配置:

#define configUSE_TICKLESS_IDLE 1

物联沃分享整理
物联沃-IOTWORD物联网 » FreeRTOS深入剖析

发表评论