学习之路主要为FreeRTOS操作系统在STM32F103(STM32F103C8T6)上的运用,采用的是标准库编程的方式,使用的IDE为KEIL5。
注意!!!本学习之路可以通过购买STM32最小系统板以及部分配件的方式进行学习,也可以通过Proteus仿真的方式进行学习。
后续文章会同时发表在个人博客(jason1016.club)、CSDN;
视频会发布在bilibili(UID:399951374)
理论
1、实时操作系统为什么要使用任务概念
首先我们得先清楚一个概念,那就是单片机同一时间永远只能一个任务。像人一样,单片机对一心一用这个概念发挥到了机制,无论单片机当前执行的任务有多简单,单片机永远只能执行一个进程。那怎么切换到下个进程呢?或者说单片机如何从当前执行的操作跳转到其他的操作呢?,裸机单片机给出的方案有两个,第一个最简单的就是等当前任务执行完后就自然而然地顺着你编写的代码逻辑跳转到下一个进程了,第二个方法就是单片机的中断操作了。上述两个方法就可以使单片机完成简单的功能了。
但是这时我们又不得不思考一下以下这几个问题了:1、当我编写的轮询任务执行顺序为 task1-->task2-->task3 ,当我执行完task1后想执行task3怎么办?2、我进中断后执行完后是不是还得重新返还之前的任务呀,可以直接停止中断进入前的进程吗?3、我由于中断优先级的问题,不停嵌套进入中断会不会导致中断冗余或者导致中断保护现场栈堆满了呀?、
而上述问题的解决方案就可以重新回到实时操作系统的任务概念了,我们把单片机要执行的各类进程按照任务划分,将task1、task2、task3划分为不同的三个任务,并且赋予其对应的优先级123。当满足task1的执行条件后才进行任务的执行,当task1和task2的条件都满足则按照优先级先执行任务优先级高的操作。这个就是软实时操作系统。个人认为呢这个执行方式呢就是轮询语句加入了标志位的判断执行,但是又删除了判断操作,听着有点绕啊,这个我写一个用轮询方法简单实现操作系统任务概念的划分的操作伪代码,大家应该就理解了。
while(1)
{
int t1=0,t2=0,t3=0;
t1优先级>t2优先级>t3优先级
if(t1满足执行条件&&t2不满足执行条件&&t3不满足执行条件)
t1;
if(t1不满足执行条件&&t2满足执行条件&&t3不满足执行条件)
t2;
if(t1不满足执行条件&&t2不满足执行条件&&t3满足执行条件)
t3;
if(t1满足执行条件&&t2满足执行条件&&t3不满足执行条件)
t1;
t2;
if(t1满足执行条件&&t2不满足执行条件&&t3满足执行条件)
t1;
t3;
if(t1满足执行条件&&t2满足执行条件&&t3满足执行条件)
t1;
t2;
t3;
.
.
.
}
2、任务的概念
所谓FreeRTOS的任务概念,就是将要是执行的操作分为一个一个任务,然后通过任务调度器让任务按照优先级进行分配,实现多任务的调配管理。与裸机系统所有操作都堆积在main函数运行,会导致系统冗余,虽然可以通过进入中断的方法实现紧急操作的分流,但是频繁进中断可以会影响系统的正常运行。所以让FreeRTOS将任务进行分流执行。
freertos低优先级任务执行到一半可以强制跳转到高优先级任务吗?
如果一个低优先级任务执行到一半,而此时有一个高优先级任务就绪,调度器会立即切换到高优先级任务执行,而不会等待低优先级任务执行完毕。
需要注意的是,任务的切换是由调度器自动完成的,开发者无法手动控制任务的切换时机。如果您希望在低优先级任务执行到一半时主动切换到高优先级任务,可以考虑在低优先级任务中主动调用任务切换函数,例如taskYIELD(),来让出CPU给其他任务执行。这样,调度器会根据任务的优先级和调度策略来决定下一个执行的任务。
3、任务状态
上述有说到任务得满足条件才会进行执行操作嘛,那我们怎么去查看以及改变任务的条件呢,这里我们就引入了任务状态的概念了。
就绪态:已经达成运行条件,但是因为有一个同优先级或者更高优先级的任务正在运行,所以运行不了
阻塞态:任务在等某个外部触发条件,当条件满足才会转换状态(比如vTaskDelay()函数,就要等待延时完毕)【任务进入阻塞态会有一个超时时间,当超过这个超时时间任务就会退出阻塞态,即使所等待的事件还没有来临】
挂起态:任务进入挂起态以后也不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间
3、任务优先级
每个任务都可以分配一个从0~(configMAX_PRIORITIES-1)的优先级
优先级数字越低表示任务的优先级越低,0 的优先级最低,configMAX_PRIORITIES-1 的优先级最高。空闲任务的优先级最低,为 0。
FreeRTOS 调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,换句话说就是处于就绪态的最高优先级的任务才会运行。
处于就绪态的优先级相同的任务就会使用时间片轮转调度器获取运行时间。(轮转调度器后面会讲到)
具体方法
1、任务的创建与删除
任务创建分为动态创建以及静态创建(区别在于动态创建是自动分配RAM任务栈的,而静态是手动分配的)【下面我们任务创建主要用动态的方法】 并且在动态方法中用于储存任务属性的任务控制块地址也会自动分配
2、pcName: 任务名字,一般用于追踪和调试,任务名字长度不能超过:configMAX_TASK_NAME_LEN
3、usStackDepth: 任务堆栈大小,注意实际申请到的堆栈是 usStackDepth 的 4 倍。其中空闲任务的任务堆栈大小为 configMINIMAL_STACK_SIZE 任务堆栈作用当任务被中断时可以保护(恢复)现场
5、uxPriotiry: 任务优先级,范围 0~ configMAX_PRIORITIES-1
6、pxCreatedTask: 任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。其他 API 函数可能会使用到这个句柄。
//创建开始任务
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); //任务句柄
vTaskDelete( TaskHandle_t xTaskToDelete ) //参数为需要删除的任务函数
2、任务的挂起与恢复
任务挂起:将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态
void vTaskSuspend( TaskHandle_t xTaskToSuspend) //参数为需要挂起的任务函数
void vTaskResume( TaskHandle_t xTaskToResume) //参数为需要恢复的任务函数
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume) //此函数是 vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务
实训代码
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "sys.h"
#include "timer.h"
#include "OLED.h"
#define START_TASK_PRIO 1
#define START_STK_SIZE 128
TaskHandle_t StartTask_Handler;
void start_task(void *pvParameters);
#define TASK1_TASK_PRIO 2
#define TASK1_STK_SIZE 128
TaskHandle_t Task1Task_Handler;
void task1_task(void *pvParameters);
#define TASK2_TASK_PRIO 3
#define TASK2_STK_SIZE 128
TaskHandle_t Task2Task_Handler;
void task2_task(void *pvParameters);
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
delay_init();
uart_init(115200);
LED_Init();
OLED_Init();
OLED_ShowString(1, 1, "TASK Test");
//创建开始任务
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(); //开启任务调度
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t ) task1_task,
(const char* ) "task1_task",
(uint16_t ) TASK1_STK_SIZE,
(void* ) NULL,
(UBaseType_t ) TASK1_TASK_PRIO,
(TaskHandle_t* ) &Task1Task_Handler);
xTaskCreate((TaskFunction_t )task2_task,
(const char* ) "led1_task",
(uint16_t ) TASK2_STK_SIZE,
(void* ) NULL,
(UBaseType_t ) TASK2_TASK_PRIO,
(TaskHandle_t* ) &Task2Task_Handler);
vTaskDelete(StartTask_Handler);
taskEXIT_CRITICAL();
}
void task1_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务执 1 行次数加 1 注意 task1_num1 加到 255 的时候会清零!!
LED0=!LED0;
printf("任务 1 已经执行:%d 次\r\n",task1_num);
if(task1_num==5)
{
vTaskDelete(Task2Task_Handler);//任务 1 执行 5 次删除任务 2
printf("任务 1 删除了任务 2!\r\n");
}
OLED_ShowHexNum(3, 1, task1_num, 3);
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
//task2 任务函数
void task2_task(void *pvParameters)
{
u8 task2_num=0;
while(1)
{
task2_num++; //任务 2 执行次数加 1 注意 task1_num2 加到 255 的时候会清零!!
LED1=!LED1;
printf("任务 2 已经执行:%d 次\r\n",task2_num);
OLED_ShowHexNum(2, 1, task2_num, 3);
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}
本文为作者独立编写,本BLOG上所有的原创文章未经本人许可,不得用于商业用途及传统媒体。网络媒体转载请注明出处,否则属于侵权行为。