文章目录
🔴🟡🟢其他文章链接,独家吐血整理
【吐血总结】FreeRTOS难点、Systick中断-滴答定时器、PendSV中断-任务切换、SVC中断-系统底层、时间片调度-时钟节拍【已完结】
(第1-8讲)STM32F4单片机,FreeRTOS基础知识总结【视频笔记、代码讲解】【正点原子】【原创】
(第9-10讲)STM32F4单片机,FreeRTOS任务创建和删除(动态方法)【视频笔记、代码讲解】【正点原子】【原创】
(第12讲)STM32F4单片机,FreeRTOS任务创建和删除(静态方法)【视频笔记、代码讲解】【正点原子】【原创】
(第13-14讲)STM32F4单片机,FreeRTOS任务挂起和恢复【视频笔记、代码讲解】【正点原子】【原创】
(第16-17讲)STM32F4单片机,FreeRTOS中断管理简介【视频笔记、代码讲解】【正点原子】【原创】
(第18-19讲)32单片机,FreeRTOS临界段代码保护、任务调度器的挂起和恢复【视频笔记、代码讲解】【原创】
(第20-22讲)STM32F4单片机,FreeRTOS列表和列表项API函数讲解【视频笔记、代码讲解、正点原子】【原创】
(第34-36讲)FreeRTOS消息队列知识汇总【B站UP、硬件家园、普中科技、正点原子】【视频笔记】【原创】
(第40-44讲)STM32F4单片机,FreeRTOS信号量【二值、计数、翻转、互斥】【代码讲解】【正点原子】【原创】
1、2024温故知新(纯文字 + 图片)
队列类似于数组,信号量类似于bool型标志变量
信号量本质上也是由队列实现,只不过要简单些
释放信号量(就是写二值信号量)不可阻塞(读空时依然会阻塞),队列写满读满都会阻塞任务
二值信号量=队列长度为1的队列
二值信号量=互斥访问=任务同步(不是同时,是等待一个完成后另一个瞬间继续接上即同步)=不存在优先级翻转
创建二值信号量也是由动态和静态方式(建议还是动态方式)
计数型信号量=队列长度>1的队列
计数型信号量=事件计数
释放一个计数信号量=写一个信号量=资源+1
获取一个计数信号量=读一个信号量=资源-1
/*
char g_i = 0;
task//1ms的时间片
{
if (读信号量)
{
//code
}
vTaskDelay(10); //10ms
g_i++;
if (g_i > 200) //2s
{
g_i = 0;
}
}//如果读信号量发现为空,则task被阻塞(从运行态变阻塞态),则g_i不会再++
*/
优先级翻转:高优先级的任务慢执行,低优先级的任务先执行(实时性操作系统不允许这种现象出现)
但实际上二值信号量会导致这种情况的发生(如图所示,我们不喜欢这种现象,如何避免?只能使用互斥信号量!!!)
注:下图中,如果不开启任务调度,只用死延时,那就是时间片调度,此时只会永远执行高优先的任务,低优先级的任务永远执行不了,除非三个任务都是同等优先级才会轮转执行
注:上图中,如果不开启任务调度,只用死延时,那就是时间片调度,此时只会永远执行高优先的任务,低优先级的任务永远执行不了,除非三个任务都是同等优先级才会轮转执行
互斥信号量=带优先级继承的二值信号量=简单来说就是改善了上面优先级翻转的问题(并不是完美解决,只能说把危害程度尽可能降低,会影响比互斥信号量那个任务优先级低的任务,具体可看视频)
互斥信号量只能用于任务中,不能用于中断中(不解释了)
使用互斥信号量前要将一个宏打开
2、二值信号量
1、队列与信号量(B站硬件家园)
队列是数据是结构体,信号量是状态是flag
这种特殊的队列,大小为0(所以不能存数据),长度为1
即信号量初始值为0
即串口接收到数据时,相当于flag为1
task1得到了1,task1知道串口接收到了数据
然后flag变为0,等待下一次判断(cpu只判断一次省算力)
我们很少用这个函数,因为中断要求实时性
2、demo.c
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 F407电机开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/******************************************************************************************************/
QueueHandle_t semphore_handle;//定义了一个二值信号量变量
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
semphore_handle = xSemaphoreCreateBinary();//定义二值信号量
if(semphore_handle != NULL)
{
printf("二值信号量创建成功!!!\r\n");
}
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,释放二值信号量 */
void task1( void * pvParameters )
{
uint8_t key = 0;
BaseType_t err;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
if(semphore_handle != NULL)
{
err = xSemaphoreGive(semphore_handle);
if(err == pdPASS)
{
printf("信号量释放成功!!\r\n");
}else printf("信号量释放失败!!\r\n");
}
}
vTaskDelay(10);
}
}
/* 任务二,获取二值信号量 */
void task2( void * pvParameters )
{
uint32_t i = 0;
BaseType_t err;
while(1)
{
err = xSemaphoreTake(semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
if(err == pdTRUE)
{
printf("获取信号量成功\r\n");
}else printf("已超时%d\r\n",++i);
}
}
3、代码讲解
void freertos_demo(void)
{
semphore_handle = xSemaphoreCreateBinary();
if(semphore_handle != NULL)
{
printf("二值信号量创建成功!!!\r\n");
}
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
QueueHandle_t semphore_handle;
semphore_handle 是结构体类型指针变量=它的内容是结构体地址=结构体指针,QueueHandle_t是用typedef封装起来的结构体类型指针,xSemaphoreCreateBinary函数的返回值类型就是QueueHandle_t
vTaskDelete(NULL);
如果没有这个删除自己的语句,则start任务会一直创建task1与task2,则久而久之堆栈就会消耗完所有单片机内存,
/* 任务一,释放二值信号量 */
void task1( void * pvParameters )
{
uint8_t key = 0;
BaseType_t err;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
if(semphore_handle != NULL)
{
err = xSemaphoreGive(semphore_handle);
if(err == pdPASS)
{
printf("信号量释放成功!!\r\n");
}else printf("信号量释放失败!!\r\n");
}
}
vTaskDelay(10);
}
}
err没啥好说的,是long int长整型,一般是64或者32位
/* 任务二,获取二值信号量 */
void task2( void * pvParameters )
{
uint32_t i = 0;
BaseType_t err;
while(1)
{
err = xSemaphoreTake(semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
if(err == pdTRUE)
{
printf("获取信号量成功\r\n");
}else printf("已超时%d\r\n",++i);
}
}
portMAX_DELAY=0xffffffffUL,portMAX_DELAY的作用看上图,虽然task2的任务优先级高于task1,但是这个获取信号量函数会使task2被阻塞,因此此时task1会优先执行(并且task2随时准备机会抢占task1),i用来记录阻塞时间,每次+1大约是一个时间片
如图,这里很奇怪,就是说按键key0按下的一瞬间,竟然没有进入if判断,反而先进入的是task2的printf,这是因为task2的任务优先级高,本来就应该是task2先执行,在你按下的瞬间信号在task1中释放了,还没来得及进入if打印,就被task2抢占了(优先级高),这个抢占就是上面所说的task2在找的机会
err = xSemaphoreGive(semphore_handle);
这个释放函数没有超时机制,没有就不释放(即释放失败),有就释放(即成功)
❤注:硬件家园&&正点原子的区别
硬件家园里面,这个是void类型=空类型
这个是正点原子,这个是结构体类型
3、计数型信号量
1、PPT
没啥好说的,就是二值信号量的延伸
2、demo.c
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 F407电机开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
/******************************************************************************************************/
QueueHandle_t count_semphore_handle;
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
count_semphore_handle = xSemaphoreCreateCounting(100 , 0); /* 创建计数型信号量 */
if(count_semphore_handle != NULL)
{
printf("计数型信号量创建成功!!!\r\n");
}
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &task2_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,释放计数型信号量 */
void task1( void * pvParameters )
{
uint8_t key = 0;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
if(count_semphore_handle != NULL)
{
xSemaphoreGive(count_semphore_handle); /* 释放信号量 */
}
}
vTaskDelay(10);
}
}
/* 任务二,获取计数型信号量 */
void task2( void * pvParameters )
{
BaseType_t err = 0;
while(1)
{
err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
if(err == pdTRUE)
{
printf("信号量的计数值为:%d\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));
}
vTaskDelay(1000);
}
}
3、代码讲解
count_semphore_handle = xSemaphoreCreateCounting(100 , 0); /* 创建计数型信号量 */
//100是上限,0是初始值
xSemaphoreGive(count_semphore_handle); /* 释放信号量 */
这个函数每执行一次,计数就会+1,至于+1的时间间隔由task的延时来决定,比如正点的这个就是10ms进入一次(前提是按键按下了)
err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
这个函数每执行一次,计数就会-1,同样时间间隔也由task的延时来决定,比如正点的这个就是1000ms=1s
printf("信号量的计数值为:%d\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));
获取计数值