IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> FreeRTOS一些常识笔记之快速上手 -> 正文阅读

[嵌入式]FreeRTOS一些常识笔记之快速上手

一、为啥要用实时多任务操作系统

real-time Operate System 简称有:RTOS,有如下的好处:

  1. 用户无需关心时间信息
    内核负责计时,并由相关的API完成,从而使得用户的应用程序代码结构更简单。
  2. 模块化、可拓展性强
    也正是由于第一点的原因,程序性能不易受底层硬件更改的影响。姐,各个任务是独立的模块,每个模块都有明确的目
    的,降低了代码的耦合性。
  3. 效率高
    内核可以让软件完全由事件驱动,因次,轮询未发生的事件是不浪费时间的。相当于用中断来进行任务切换。
  4. 中断进程更短
    通过把中断的处理推迟到用户创建的任务中,可以使得中断处理程序非常短。

二、核心的C文件和头文件

  • C文件
    在这里插入图片描述
  • 头文件
    在这里插入图片描述

三、两个数据类型和变量的定义方法

TickType_t

  • FreeRTOS配置了一个周期性的时钟中断:Tick Interrupt
  • 每发生一次中断,中断次数累加,这被称为tick count
  • tick count这个变量的类型就是TickType_t
  • TickType_t可以是16位的,也可以是32位的
  • FreeRTOSConfig.h中定义configUSE_16_BIT_TICKS时,TickType_t就是uint16_t
  • 否则TickType_t就是uint32_t
  • 对于32位架构,建议把TickType_t配置为uint32_t

BaseType_t

  • 这是该架构最高效的数据类型
  • 32位架构中,它就是uint32_t
  • 16位架构中,它就是uint16_t
  • 8位架构中,它就是uint8_t
  • BaseType_t通常用作简单的返回值的类型,还有逻辑值,比如pdTRUE/pdFALSE

变量名
每个变量的前缀表示的含义
在这里插入图片描述
函数名
函数名前缀有有2部分:返回值类型、在哪个文件定义。
在这里插入图片描述
宏的名
宏的名字是大小,可以添加小写的前缀。前缀是用来表示:宏在哪个文件中定义
在这里插入图片描述
通用的宏定义如下:
在这里插入图片描述

四、内存管理中的几个API

提及内存管理就必须说一下堆和栈
堆,heap,就是一块空闲的内存,需要提供管理函数

  • malloc:从堆里划出一块空间给程序使用
  • free:用完后,再把它标记为"空闲"的,可以再次使用
    在这里插入图片描述

栈,stack,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中

  • 可以从堆中分配一块空间用作栈
    在这里插入图片描述

在FreeRTOS中内存管理的接口函数(API)为:
1、pvPortMalloc 、vPortFree,对应于C库的malloc、free。

void * pvPortMalloc( size_t xWantedSize ); //分配内存,如果分配内存不成功,则返回值为NULL。
void vPortFree( void * pv );//释放内存

2、当前还有多少空闲内存,这函数可以用来优化内存的使用情况。比如当所有内核对象都分配好后,执行此函数返回2000,那么configTOTAL_HEAP_SIZE就可减小2000。

size_t xPortGetFreeHeapSize( void );

3、空闲内存的最小值

size_t xPortGetMinimumEverFreeHeapSize( void );//程序运行过程中,空闲内存容量的最小值。

五、创建任务和删除任务

啥叫任务?
任务就一个函数,但要注意的是
在这里插入图片描述示例

void ATaskFunction( void *pvParameters ) 
{ 
    /* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
    int32_t lVariableExample = 0;
    /* 任务函数通常实现为一个无限循环 */
    for( ;; ) 
    { 
        /* 任务的代码 */ 
    }
    
    /* 如果程序从循环中退出,一定要使用vTaskDelete删除自己 * NULL表示删除的是自己 */
    vTaskDelete( NULL ); 
    /* 程序不会执行到这里, 如果执行到这里就出错了 */ 
}

创建任务API

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
                        const char * const pcName, // 任务的名字 
                        const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位 为word,10表示40字节 
                        void * const pvParameters, // 调用任务函数时传入的参数 
                        UBaseType_t uxPriority, // 优先级 
                        TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用 它来操作这个任务

里面的参数说明如下:
在这里插入图片描述
示例:
任务1代码:

void vTask1( void *pvParameters ) 
{ 
    const char *pcTaskName = "T1 run\r\n";
    volatile uint32_t ul; /* volatile用来避免被优化掉 */ 
    
    /* 任务函数的主体一般都是无限循环 */ 
    for( ;; ) 
    { 
        /* 打印任务1的信息 */
        printf( pcTaskName );
        
         /* 延迟一会(比较简单粗暴) */ 
         for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) 
         {
         } 
    } 
}

任务2代码:

void vTask2( void *pvParameters )
{ 
    const char *pcTaskName = "T2 run\r\n"; 
    volatile uint32_t ul; /* volatile用来避免被优化掉 */ 
    
    /* 任务函数的主体一般都是无限循环 */ 
    for( ;; ) 
    { 
        /* 打印任务1的信息 */
        printf( pcTaskName ); 
        
        /* 延迟一会(比较简单粗暴) */ 
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ ) 
        {
        } 
    } 
}

main函数:

int main( void )
{ 
    prvSetupHardware(); 
    
    xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL); 
    xTaskCreate(vTask2, "Task 2", 1000, NULL, 1, NULL); 
    
    /* 启动调度器 */ 
    vTaskStartScheduler(); 
    
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */ 
    return 0; 
}

运行结果:
在这里插入图片描述
注:

  • task 2先运行!
  • 要分析xTaskCreate的代码才能知道原因:更高优先级的、或者后面创建的任务先运行。

删除任务API

void vTaskDelete( TaskHandle_t xTaskToDelete );

参数说明
在这里插入图片描述
举个栗子

  • 自杀: vTaskDelete(NULL)
  • 被杀:别的任务执行 vTaskDelete(pvTaskCode) ,pvTaskCode是自己的句柄
  • 杀人:执行 vTaskDelete(pvTaskCode) ,pvTaskCode是别的任务的句柄

上示例: 大体的意思是:

  1. 创建任务1:任务1的大循环里,创建任务2,然后休眠一段时间
  2. 任务2:打印一句话,然后就删除自己

任务1代码:

void vTask1( void *pvParameters ) 
{ 
    const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL ); 
    BaseType_t ret; 
    
    /* 任务函数的主体一般都是无限循环 */ 
    for( ;; ) 
    { 
        /* 打印任务的信息 */ 
        printf("Task1 is running\r\n"); 
        ret = xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle ); 
        if (ret != pdPASS) 
           printf("Create Task2 Failed\r\n"); 
           
        // 如果不休眠的话, Idle任务无法得到执行 
        // Idel任务会清理任务2使用的内存 
        // 如果不休眠则Idle任务无法执行, 最后内存耗尽 vTaskDelay( xDelay100ms ); 
    }
}

任务2代码

void vTask2( void *pvParameters ) 
{ 
    /* 打印任务的信息 */ 
    printf("Task2 is running and about to delete itself\r\n");
    // 可以直接传入参数NULL, 这里只是为了演示函数用法 
    vTaskDelete(xTask2Handle); 
}

main函数

int main( void ) 
{ 
    prvSetupHardware(); 
    xTaskCreate(vTask1, "Task 1", 1000, NULL, 1, NULL);
    /* 启动调度器 */ 
    vTaskStartScheduler(); 
    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */ 
    return 0;
}

运行结果

在这里插入图片描述

解释:

  • main函数中创建任务1,优先级为1。任务1运行时,它创建任务2,任务2的优先级是2。

  • 任务2的优先级最高,它马上执行。

  • 任务2打印一句话后,就删除了自己。

  • 任务2被删除后,任务1的优先级最高,轮到任务1继续运行,它调用
    vTaskDelay() 进入Block状态

  • 任务1 Block期间,轮到Idle任务执行:它释放任务2的内存(TCB、栈)

  • 时间到后,任务1变为最高优先级的任务继续执行。

  • 如此循环。

六、任务优先级和Tick

任务优先级

  • 高优先级的任务先运行。
  • 优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。

使用uxTaskPriorityGet来获得任务的优先级,
使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

使用vTaskPrioritySet 来设置任务的优先级,
使用参数xTask来指定任务,设置为NULL表示设置自己的优先级;
参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。

void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );

Tick

对于相同优先级的任务的话,它们“轮流”执行。怎么轮流?你执行一会,我执行一会。那么这个一会就是使用Tick定义的

vTaskDelay(2); // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms 

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick 
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms

七、任务的几种状态

  1. 阻塞状态(Blocked)
  2. 暂停状态(Suspended)
  3. 就绪状态(Ready)

任务转换图
在这里插入图片描述

七、两个Delay函数

  • vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪状态
  • vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态
void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给 Tick */ 
/* pxPreviousWakeTime: 上一次被唤醒的时间 
 * xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement) 
 * 单位都是Tick Count 
 */ 
 BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );

详细介绍:

  • 使用vTaskDelay(n)时,进入、退出vTaskDelay的时间间隔至少是n个Tick中断
  • 使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间至少是n个Tick中断
    1、退出xTaskDelayUntil时任务就进入的就绪状态,一般都能得到执行机会
    2、所以可以使用xTaskDelayUntil来让任务周期性地运行

在这里插入图片描述
示例:
本程序会创建2个任务:

  • Task1:
    1、高优先级
    2、设置变量flag为1,然后调用 vTaskDelay(xDelay50ms); 或vTaskDelayUntil(&xLastWakeTime, xDelay50ms);

  • Task2:
    1、低优先级
    2、设置变量flag为0

main函数

int main( void )
{
    prvSetupHardware();
    
    /* Task1的优先级更高, Task1先执行 */ 
    xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL ); 
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL ); 

    /* 启动调度器 */ 
    vTaskStartScheduler(); 

    /* 如果程序运行到了这里就表示出错了, 一般是内存不足 */ 
    return 0; 
}

Task1的代码中使用条件开关来选择Delay函数,把 #if 1 改为 #if 0 就可以使用 vTaskDelayUntil

void vTask1( void *pvParameters ) 
{ 
    const TickType_t xDelay50ms = pdMS_TO_TICKS( 50UL ); 
    TickType_t xLastWakeTime; int i; 
    
    /* 获得当前的Tick Count */ 
    xLastWakeTime = xTaskGetTickCount(); 
    for( ;; ) 
    { 
        flag = 1;
        
        /* 故意加入多个循环,让程序运行时间长一点 */ 
        for (i = 0; i <5; i++)
            printf( "Task 1 is running\r\n" );
##if 1 
    vTaskDelay(xDelay50ms);
##else 
    vTaskDelayUntil(&xLastWakeTime, xDelay50ms); 
##endif 
    }
}

Task2的代码

void vTask2( void *pvParameters ) 
{ 
    for( ;; )
    { 
        flag = 0; 
        printf( "Task 2 is running\r\n" ); 
    } 
}

使用Keil的逻辑分析观察flag变量的bit波形,如下:

  • flag为1时表示Task1在运行,flag为0时表示Task2在运行,也就是Task1处于阻塞状态
  • vTaskDelay:指定的是阻塞的时间
  • vTaskDelayUntil:指定的是任务执行的间隔、周期
    在这里插入图片描述
    在这里插入图片描述

八、空闲任务和钩子函数

空闲任务
为什么要有空闲函数呢?

因为一个良好的程序,它的任务都是事件驱动的:平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行,但是调度器必须能找到一个可以运行的任务:所以,我们要提供空闲任务。

在使用 vTaskStartScheduler() 函数来创建、启动调度器时,这个函数内部会创建空闲任务:

  • 空闲任务优先级为0:它不能阻碍用户任务运行
  • 空闲任务要么处于就绪态,要么处于运行态,永远不会阻塞

空闲任务的优先级为0,这意为着一旦某个用户的任务变为就绪态,那么空闲任务马上被切换出去,让这个用户任务运行。在这种情况下,我们说用户任务"抢占"(pre-empt)了空闲任务,这是由调度器实现的。
要注意的是:如果使用 vTaskDelete() 来删除任务,就要确保空闲任务有机会执行,否则就无法释放被删除任务的内存。

钩子函数

钩子函数在空闲任务中添加,空闲任务每执行一次,钩子函数就会被调用一次,那么钩子函数能干些什么事呢?

  • 执行一些低优先级的、后台的、需要连续执行的函数
  • 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
  • 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式 了。

钩子函数使用过程中应该注意:

  • 不能导致空闲任务进入阻塞状态、暂停状态
  • 如果你会使用 vTaskDelete()来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植 卡在钩子函数里的话,它就无法释放内存。

如果想使用钩子函数

  • 在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定义为1
  • 提供vApplicationMallocFailedHook函数
  • pvPortMalloc失败时,才会调用此函数
    在这里插入图片描述

九、调度算法

因为只能一个任务处在运行状态中,所以需要调度来实现不同任务进入运行状态。

调度算法的行为主要体现为:

  • 高优先级的任务先运行
  • 同优先级的就绪态任务如何被选中

从3个角度统一理解多种调度算法:
在这里插入图片描述
在这里插入图片描述
上表解释:

  • A:可抢占+时间片轮转+空闲任务让步
  • B:可抢占+时间片轮转+空闲任务不让步
  • C:可抢占+非时间片轮转+空闲任务让步
  • D:可抢占+非时间片轮转+空闲任务不让步
  • E:合作调度

示例:

注:

  • 任务1优先级0
  • 任务2优先级0
  • 任务3优先级2(最高优先级)

是否抢占对比
在 FreeRTOSConfig.h 中,定义这样的宏,对比逻辑分析仪的效果:

// 实验1:抢占
#define configUSE_PREEMPTION 1 
#define configUSE_TIME_SLICING 1 
#define configIDLE_SHOULD_YIELD 1 
// 实验2:不抢占 
#define configUSE_PREEMPTION 0 
#define configUSE_TIME_SLICING 1 
#define configIDLE_SHOULD_YIELD 1
  • 抢占时:高优先级任务就绪时,就可以马上执行
  • 不抢占时:优先级失去意义了,既然不能抢占就只能协商了,图中任务1一直在运行(一点都没有协商精神),其他任务都无法执行。即使任务3的 vTaskDelay 已经超时、即使它的优先级更高,都没办法执行。
    在这里插入图片描述
    在这里插入图片描述
    是否时间片轮转对比
    在 FreeRTOSConfig.h 中,定义这样的宏,对比逻辑分析仪的效果:
// 实验1:时间片轮转 
#define configUSE_PREEMPTION 1 
#define configUSE_TIME_SLICING 1 
#define configIDLE_SHOULD_YIELD 1 
// 实验2:时间片不轮转 
#define configUSE_PREEMPTION 1 
#define configUSE_TIME_SLICING 0 
#define configIDLE_SHOULD_YIELD 1
  • 时间片轮转:在Tick中断中会引起任务切换
  • 时间片不轮转:高优先级任务就绪时会引起任务切换,高优先级任务不再运行时也会引起任务切换。可以看到任务3就绪后可以马上执行,它运行完毕后导致任务切换。其他时间没有任务切换, 可以看到任务1、任务2都运行了很长时间。
    在这里插入图片描述
    在这里插入图片描述
    空闲任务是否让步对比
    在 FreeRTOSConfig.h 中,定义这样的宏,对比逻辑分析仪的效果:
// 实验1:空闲任务让步 
#define configUSE_PREEMPTION 1 
#define configUSE_TIME_SLICING 1 
#define configIDLE_SHOULD_YIELD 1 
// 实验2:空闲任务不让步 
#define configUSE_PREEMPTION 1 
#define configUSE_TIME_SLICING 1 
#define configIDLE_SHOULD_YIELD 0
  • 让步时:在空闲任务的每个循环中,会主动让出处理器,从图中可以看到flagIdelTaskrun的波形很小
  • 不让步时:空闲任务跟任务1、任务2同等待遇,它们的波形宽度是差不多的
    在这里插入图片描述
    在这里插入图片描述

十、同步互斥与通信

关于RTOS的其他内容后续更新,已是凌晨1点,先睡会

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2022-06-04 00:04:22  更:2022-06-04 00:04:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/29 7:45:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计