如果不想看的可以直接使用git把我的代码下载出来,里面工程挺全的,后期会慢慢的补注释之类的
码云地址:stm32学习笔记: stm32学习笔记源码
如果不会使用git快速下载可以选择直接下载压缩包或者去看看git的使用
Git入门教程-CSDN博客
一 调度机制
1.1 任务调度介绍
在创建任务之前,我们先熟悉下调度机制
在freertos中,我们一般使用的就是抢占式+时间片
在不同优先级的情况下:默认为抢占式,就是谁优先级高,优先级高的任务进入就绪态后可以直接执行。
相同优先级下:使用时间片调度,在时间片调度下每个任务会固定执行一个时间片
携程式调度:这个官方自己不更新了,并且不管是什么优先级的任务,如果任务不释放CPU任务就会一直继续运行。所以我感觉没那么方便控制,这边就不写了,有兴趣了的可以自己去试试。
1.1.1 抢占式调度
抢占式调度,需要任务在不同优先级下才能体现出来。
1 高优先级会先执行 2高优先级任务不停止,低优先级任务就不会执行 3被抢占的任务会进入就绪态(什么是就绪态后面会写,可以直接跳目录到任务状态那里去)
这里有个示例:如大家可以看看上面的图。
这边我们写个代码测试一下:(先看看效果,具体实现后续代码会细讲)
/* 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 );void freertos_demo(void)
{ 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 )
{while (1){printf("task 1\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");for(int i=0;i<10000000;i++){}}
}
这里我们使用xTaskCreate() 创建了3个任务,参数看不懂没关系,后面会细讲,先了解下调度机制和任务状态
分别是一个启动任务:这个启动任务在去创建了两个任务,创建完成之后删除自身,防止任务被重复创建
两个任务:
task1:优先级为2 打印一个task 1 ,之后释放CPU500ms
task1:优先级为2 打印一个task 1 ,之后释放CPU500ms
taskENTER_CRITICAL(); /* 进入临界区 */
taskEXIT_CRITICAL(); /* 退出临界区 */
这两个函数是防止创建任务之后任务直接被启动,比如task1创建之后,不管后面的task2优先级有多高,task都会先运行一下,直到task2创建完成,抢占他的cpu,我们等会也会对其关闭测试。
1 高优先级任务不释放CPU(开启临界区了的)
这个就是我们上面的代码跑出来的结果,抢占式调度:高优先级的任务如果不释放CPU那么低优先级的任务永远也执行不了
2 高优先级的任务不释放CPU(关闭临界区了的)
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(); /* 退出临界区 */
}
我们上面屏蔽临界区代码之后产生的效果,如上所说,不管优先级怎么样,在task2创建出来之前task1会直接执行。
3 高优先级任务加入CPU释放(开启临界区了的)
void task2( void * pvParameters )
{while (1){printf("task 2\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}
可以明显的看见,我们这里task2运行之后释放掉了CPU,之后task运行。
4 高优先级任务不释放cpu(开启临界区了的)
这里大家根据1.1.1的抢占式调度的任务图,大家觉得会怎么样?是task1一直运行,还是说task进入就绪态后直接抢占task1?
void task1( void * pvParameters )
{while (1){printf("task 1\r\n");for(int i=0;i<10000000;i++){}}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}
这里很明显,在task2执行完成后,释放掉cpu,这时候task1运行,结论就是低优先级的就算不释放cpu高优先级的一样占用。在经过500ms之后,task2又进入了就绪态,直接抢占了task1,这时候task1的for循环都还没有执行完,等task1的for循环执行完成后,又能执行打印task1了。
这个就是抢占式调度的测试了。
1.1.2 时间片调度
同等优先级的任务会轮流使用CPU(一般是1个时间片)
task1 task2 task3只使用一个时间片,如果task1第一次只使用了0.1个时间片,第二次并不会给他补0.9(这样做会让cpu等待的时间累积的越来越多,很不合理)
这里我们可以再次测试
首先将优先级改为一致
/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define TASK2_PRIO 2
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
之后测试:
1.不释放CPU
void task1( void * pvParameters )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}//vTaskDelay(pdMS_TO_TICKS(500));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}//vTaskDelay(pdMS_TO_TICKS(500));}
}
这里会怎么样呢?我的一个时间片是1ms,先运行task1之后,这里会一直循环打印task1,在运行1ms之后,运行task2
这里可以看见,时间戳都给我干卡死了,并且已经全乱码了,在一个时间片来回切换串口资源导致串口已经乱掉了。
2 释放CPU
这里释放了1ms cpu但是还是肉眼可见的乱码了
之后直接释放了10个时间片打印才正常(这里就引出了一个问题,过快的访问外设资源导致的乱码问题,这种时候我们需要添加一个互斥锁这个后面互斥锁章节说)
这里两种调度模式就说完了,时间片调度就是这个效果了,如果不释放cpu就是1ms内循环执行(这里上面的任务图说的是1ms内没执行完就丢掉,但是我们这里是在一个while循环中,并不会说执行完就完了,所以就会循环执行)
1.2 任务状态
1.2.1 任务状态转换图介绍
在freertos中任务有四种状态:挂起、就绪、运行、阻塞
所有状态想进入运行态都需要先进入就绪态
挂起态:运行态的时候或者就绪态的时候调用函数vtasksuspend
就绪态:cpu释放的时间到了(阻塞态的时间结束了),就会进入就绪态。挂起态直接使用函数也能进入就绪态
阻塞态:就是上面的vTaskDelay(pdMS_TO_TICKS(500));这种类型的函数
运行态:就绪态列表的第一个任务就会进入运行态(会按优先级排序)
二 创建任务的函数与整体框架
2.1 整体框架
2.1.1main函数
main函数:单片机最先进入的c语言部分
int main(void)
{ //LCD 初始化ILI9341_Init (); /* USART config */USART_Config(); //其中0、3、5、6 模式适合从左至右显示文字,//不推荐使用其它模式显示文字 其它模式显示文字会有镜像效果 //其中 6 模式为大部分液晶例程的默认显示方向 ILI9341_GramScan ( 6 );freertos_demo();
}
这里有个freertos_demo函数,就是创建任务的函数,上面的这些都是外设初始化,其实这些外设初始化也可以使用一个单独的函数来初始化,但是需要注意各个外设的开启·时间,防止硬件不执行。
2.1.2 freertos_demo函数
freertos_demo函数内容一个创建任务函数和启动任务调度器
启动任务调度器必须在所有初始化工作(如硬件初始化、任务创建、信号量创建等)完成后调用,且整个系统中只能调用一次。这个start_task就是一个典型的启动任务了。里面包含所有任务,在创建完任务后,就执行任务调度器即可。
void freertos_demo(void)
{ 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();
}
2.1.3 start_task函数
start_task:创建所有的任务,方便统一管理
start_task
是典型的 “启动任务”(或叫初始化任务),它的唯一职责是:
- 集中创建其他业务任务(如
task1
、task2
)。 - 完成初始化后立即删除自己,释放系统资源(栈空间、任务控制块等)。
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(); /* 退出临界区 */
}
2.1.4 task函数
task:具体任务的处理,你需要处理数据在其内部处理即可
void task1( void * pvParameters )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}
这里就是整个代码的大概框架了
2.2 本章使用到的函数讲解
2.2.1 动态创建任务函数
xTaskCreate:函数原型如下
动态与静态的区别:
动态:任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配
静态:任务的任务控制块以及任务的栈空间所需的内存,需用户分配提供
这里来说一下函数原型需要传入的参数:PS文章为使用讲解哈,不涉及函数的实现,这个可以在后面的系统设计讲解
1 pxTaskCode 指向任务函数的指针
就是把你函数的名字传进去即可
2 pcName
给任务起一个名称,主要用于调试和可视化分析。这个名称可以帮助开发者在调试过程中,通过调试工具(如 FreeRTOS 的可视化调试插件)快速识别不同的任务
3 uxStackDepth
指定任务栈的大小:单位是字,不是字节写128的话就是128 * 4
字节
4 pvParameters
用于向任务函数传递参数。当任务函数需要接收外部数据时,可以通过这个参数传入:我们这里没有传入东西,还有后面用到的时候理解
5 uxPriority
优先级:越大越高理论上软件可以开任意优先级数量,但是由于硬件等限制,在配置文件中一般都写的32
6 pxCreatedTask
是一个指向任务句柄的指针。用于获取新创建任务的句柄。任务句柄是任务的唯一标识,通过任务句柄可以在运行时对任务进行管理,比如挂起、恢复、删除任务等操作。
类型定义为TaskHandle_t即可
2.2.2 vTaskDelete()函数
就是删除任务:传入句柄即可,本文使用:创建完task1和taks2后删除start任务,如果是删除自身,函数内田NULL即可删除自己,如果是在自己函数内需要删除其他任务,就需要填写对应的句柄,句柄保存了任务的内存地址。
2.2.3 vTaskStartScheduler()函数
启动任务调度器函数
启动任务调度器必须在所有初始化工作(如硬件初始化、任务创建、信号量创建等)完成后调用,且整个系统中只能调用一次。这个start_task就是一个典型的启动任务了。里面包含所有任务,在创建完任务后,就执行任务调度器即可。
2.2.4 临界区函数
taskENTER_CRITICAL(); /* 进入临界区 */
taskEXIT_CRITICAL(); /* 退出临界区 */
进入临界区后会停止调度,这时候低优先级的任务就算先创建了也不会先运行了。
三 附上本文代码(同步上传gitee了)
不想下载的可以直接复制
#include "FreeDome.h"/* 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 2
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );// 全局共享常量(所有任务共用)
#define CHAR_WIDTH WIDTH_CH_CHAR // 中文字符宽度
#define SCREEN_WIDTH LCD_X_LENGTH // 屏幕宽度
#define FONT_HEIGHT LCD_GetFont()->Height // 字体高度void freertos_demo(void)
{ 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 )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./lcd/bsp_ili9341_lcd.h"
#include "./flash/bsp_spi_flash.h"
#include "FreeDome.h"int main(void)
{ //LCD 初始化ILI9341_Init (); /* USART config */USART_Config(); //其中0、3、5、6 模式适合从左至右显示文字,//不推荐使用其它模式显示文字 其它模式显示文字会有镜像效果 //其中 6 模式为大部分液晶例程的默认显示方向 ILI9341_GramScan ( 6 );freertos_demo();
}