网站主题模板下载安装,室内设计效果图马克笔,wordpress镶嵌网页,阜沙网站建设嵌入式系统不只是ARMLinux#xff0c;不是只有安卓#xff0c;凡是电子产品都可称为嵌入式系统。物联网行业的兴起#xff0c;也提升了FreeRTOS市场占有率。本文就是介绍FreeRTOS基础及其应用#xff0c;只是个人整理#xff0c;可能存在问题#xff0c;其目的只是简要介… 嵌入式系统不只是ARMLinux不是只有安卓凡是电子产品都可称为嵌入式系统。物联网行业的兴起也提升了FreeRTOS市场占有率。本文就是介绍FreeRTOS基础及其应用只是个人整理可能存在问题其目的只是简要介绍系统的基础只能作为入门资料。一、 为什么要学习 RTOS进入嵌入式这个领域入门首先接触的是单片机编程尤其是C51 单片机来基础的单片机编程通常都是指裸机编程即不加入任何 RTOSReal Time Operating System 实时操作系统。常用的有国外的FreeRTOS、μC/OS、RTX 和国内的 RT-thread、Huawei LiteOS 和 AliOS-Things 等其中开源且免费的 FreeRTOS 的市场占有率较高。1.1 前后台系统在裸机系统中所有的操作都是在一个无限的大循环里面实现,支持中断检测。外部中断紧急事件在中断里面标记或者响应中断服务称为前台main 函数里面的while(1)无限循环称为后台按顺序处理业务功能以及中断标记的可执行的事件。小型的电子产品用的都是裸机系统而且也能够满足需求。1.2 多任务系统多任务系统的事件响应也是在中断中完成的但是事件的处理是在任务中完成的。如果事件对应的任务的优先级足够高中断对应的事件会立刻执行。相比前后台系统多任务系统的实时性又被提高了。在多任务系统中根据程序的功能把这个程序主体分割成一个个独立的无限循环且不能返回的子程序称之为任务。每个任务都是独立的互不干扰的且具备自身的优先级它由操作系统调度管理。加入操作系统后开发人员不需要关注每个功能模块之间的冲突重心放在子程序的实现。缺点是整个系统随之带来的额外RAM开销但对目前的单片机的来影响不大。1.3 学习RTOS的意义学习 RTOS一是项目需要随着产品要实现的功能越来越多单纯的裸机系统已经不能完美地解决问题反而会使编程变得更加复杂如果想降低编程的难度就必须引入 RTOS实现多任务管理。二是技能需要掌握操作系统和基于RTOS的编程实现更好的职业规划对个人发展尤其是钱途是必不可少的。以前一直觉得学操作系统就必须是linux实际每个系统都有其应用场景对于物联网行业杀鸡焉用牛刀小而美且应用广泛的FreeRTOS 是首选。有一个操作系统的基础即使后续基于其他系统开发软件也可触类旁通对新技术快速入门。目前接触的几款芯片都是基于FreeRTOS。如何学习RTOS最简单的就是在别人移植好的系统之上看看 RTOS 里面的 API 使用说明然后调用这些 API 实现自己想要的功能即可。完全不用关心底层的移植这是最简单快速的入门方法。这种学习方式如果是做产品可以快速的实现功能弊端是当程序出现问题的时候如果对RTOS不够了解会导致调试困难无从下手。各种RTOS内核实现方式都差不多我们只需要深入学习其中一款就行。万变不离其宗正如掌握了C51基础后续换其他型号或者更高级的ARM单片机在原理和方法上都是有借鉴意义可以比较快的熟悉并掌握新单片机的使用。二、 操作系统基础2.1 链表链表作为 C 语言中一种基础的数据结构在平时写程序的时候用的并不多但在操作系统里面使用的非常多。FreeRTOS 中存在着大量的基础数据结构链表和链表项的操作list 和 list item。FreeRTOS 中与链表相关的操作均在 list.h 和 list.c 这两个文件中实现。链表比数组最大优势是占用的内存空间可以随着需求扩大或缩小动态调整。实际FreeRTOS中各种任务的记录都是依靠链表动态管理具体的可以参考源码的任务控制块tskTCB。任务切换状态就是将对应的链表进行操作链表操作涉及创建和插入、删除和查找。2.2 队列队列是一种只允许在表的前端front进行删除操作而在表的后端rear进行插入操作。队尾放入数据对头挤出。先进先出称为FIFO2.3 任务在裸机系统中系统的主体就是 main 函数里面顺序执行的无限循环这个无限循环里面 CPU 按照顺序完成各种事情。在多任务系统中根据功能的不同把整个系统分割成一个个独立的且无法返回的函数这个函数我们称为任务。系统中的每一任务都有多种运行状态。系统初始化完成后创建的任务就可以在系统中竞争一定的资源由内核进行调度。 就绪Ready该任务在就绪列表中就绪的任务已经具备执行的能力只等待调度器进行调度新创建的任务会初始化为就绪态。 运行Running该状态表明任务正在执行此时它占用处理器调度器选择运行的永远是处于最高优先级的就绪态任务。 阻塞Blocked任务当前正在等待某个事件比如信号量或外部中断。 挂起态(Suspended)处于挂起态的任务对调度器而言是不可见的。挂起态与阻塞态的区别当任务有较长的时间不允许运行的时候我们可以挂起任务这样子调度器就不会管这个任务的任何信息直到调用恢复任务的 接口而任务处于阻塞态的时候系统还需要判断阻塞态的任务是否超时是否可以解除阻塞。各任务运行时使用消息、信号量等方式进行通信不能是全局变量。任务通常会运行在一个死循环中不会退出如果不再需要可以调用删除任务。2.4 临界区临界区就是一段在执行的时候不能被中断的代码段。在多任务操作系统里面对全局变量的操作不能被打断不能执行到一半就被其他任务再次操作。一般被打断原因就是系统调度或外部中断。对临界区的保护控制归根到底就是对系统中断的使能控制。在使用临界区时关闭中断响应对部分优先级的中断进行屏蔽因此临界区不允许运行时间过长。为了对临界区进行控制就需要使用信号量通信实现同步或互斥操作。三、 初识 FreeRTOS3.1 FreeRTOS源码FreeRTOS 由美国的 Richard Barry 于 2003 年发布 2018 年被亚马逊收购改名为 AWS FreeRTOS版本号升级为 V10支持MIT开源协议亚马逊收购 FreeRTOS 也是为了进入物联网和人工智能新版本增加了物联网行业的网络协议等功能。FreeRTOS 是开源免费的可从官网 www.freertos.org 下载源码和说明手册。例如展锐的UIS8910使用的是V10。以FreeRTOSv10.4.1为例包含 Demo 例程Source内核的源码License许可文件。3.1.1 Source 文件夹FreeRTOS/ Source 文件夹下的文件包括FreeRTOS 的通用的头文件include和 C 文件包括任务、队列、定时器等适用于各种编译器和处理器是通用的。需要特殊处理适配的在portblle文件夹其下内容与编译器和处理器相关 FreeRTOS 要想运行在一个单片机上面它们就必须关联在一起通常由汇编和 C 联合编写。通常难度比较高不过一般芯片原厂提供移植好的接口文件。这里不介绍移植的方法因为自己也不明白。Portblle/MemMang 文件夹下存放的是跟内存管理相关的总共有五个 heap 文件有5种内存动态分配方式一般物联网产品选用 heap4.c 。3.1.2 Demo 文件夹里面包含了 FreeRTOS 官方为各个单片机移植好的工程代码FreeRTOS 为了推广自己会给针对不同半导体厂商的评估板实现基础功能范例 Demo下就是参考范例。3.1.3 FreeRTOSConfig.h配置FreeRTOSConfig.h头文件对FreeRTOS 所需的功能的宏均做了定义需要根据应用情况配置合适的参数其作用类似MTK功能机平台的主mak文件部分定义如下1. #define configUSE_PREEMPTION 1
2. #define configUSE_IDLE_HOOK 0
3. #define configUSE_TICK_HOOK 0
4. #define configCPU_CLOCK_HZ ( SystemCoreClock )
5. #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
例如系统时钟tick等参数在就这个文件配置具体作用可以看注释。一般情况下使用SDK不需要改动特殊情况下咨询原厂再调整。3.2 FreeRTOS 编码规范接触一个新平台或者SDK明白它的编码规范文件作用可以提高源码阅读效率快速熟悉其内部实现。3.2.1 数据类型FreeRTOS针对不同的处理器对标准C的数据类型进行了重定义。1. #define portCHAR char
2. #define portFLOAT float
3. #define portDOUBLE double
4. #define portLONG long
5. #define portSHORT short
6. #define portSTACK_TYPE uint32_t
7. #define portBASE_TYPE long
应用编码中推荐使用的是下面这种风格。1. typedef int int32_t;
2. typedef short int16_t;
3. typedef char int8_t;
4. typedef unsigned int uint32_t;
5. typedef unsigned short uint16_t;
6. typedef unsigned char uint8_t;
3.2.2 变量名FreeRTOS 中定义变量的时候往往会把变量的类型当作前缀好处看到就知道其类型。 char 型变量的前缀是 c short 型变量的前缀是 s long 型变量的前缀是 l 复杂的结构体句柄等定义的变量名的前缀是 x 变量是无符号型的再加前缀 u是指针变量则加前缀 p3.2.3 函数名函数名包含了函数返回值的类型、函数所在的文件名和函数的功能如果是私有的函数则会加一个 prvprivate的前缀。 例如vTaskPrioritySet()函数的返回值为 void 型在 task.c 这个文件中定义。3.2.4 宏宏内容是由大写字母表示前缀是小写字母表示该宏在哪个头文件定义如1. #define taskYIELD() portYIELD()
表示该宏是在task.h。3.2.5 个人解读1、编码不缺编码规范但是实际使用中很难完全依照标准执行即使freeRTOS源码也是如此。 2、关于函数或者宏定义中带文件名的作用使用Source Insight 编辑代码该前缀的意义不大。 3、规则是活的只要所有人都按一个规则执行它就是标准。3.3 FreeRTOS应用开发关于freeRTOS的应用开发主要是任务的创建和调度任务间的通信与同步涉及队列、信号量等操作系统通用接口。结合应用需求涉及定时器、延时、中断控制等接口。特别说明有些功能的实现方式有多种形式只针对常用方式进行说明例如task的创建只说明动态创建方式因为很少使用静态方式。四、 任务4.1 创建任务xTaskCreate()使用动态内存的方式创建一个任务。1. ret xTaskCreate((TaskFunction_t) master_task_main, /* 任务入口函数 */(1)
2. “MASTER”, /* 任务名字 */(2)
3. 64*1024, /* 任务栈大小 */(3)
4. NULL, ,/* 任务入口函数参数 */(4)
5. TASK_PRIORITY_NORMAL, /* 任务的优先级 */(5)
6. task_master_handler); /* 任务控制块指针 */(6)
创建任务就是软件运行时的一个while1的入口一般阅读其他代码找到这个函数再跟踪到任务入口函数学习基于freeRTOS系统的代码首先就是找到main和这个接口。(1)任务入口函数即任务函数的名称需要我们自己定义并且实现。 (2)任务名字字符串形式最大长度由 FreeRTOSConfig.h 中定义的 configMAX_TASK_NAME_LEN 宏指定多余部分会被自动截掉只是方便调试。(3)任务堆栈大小单位为字 4 个字节这个要注意否则系统内存紧缺。(4)任务入口函数形参不用的时候配置为 0 或者NULL 即可。(5) 任务的优先级在 FreeRTOS 中数值越大优先级越高0 代表最低优先级。基于其SDK开发可将自定义的所有业务功能task设为同一个优先级按时间片轮询调度。(6)任务控制块指针使用动态内存的时候任务创建函数 xTaskCreate()会返回一个指针指向任务控制块也可以设为NULL因为任务句柄后期可以不使用。4.2 开启调度当任务创建成功后处于就绪状态Ready在就绪态的任务可以参与操作系统的调度。操作系统任务调度器只启动一次之后就不会再次执行了FreeRTOS 中启动任务调度器的函数是 vTaskStartScheduler()并且启动任务调度器的时候就不会返回从此任务管理都由FreeRTOS 管理此时才是真正进入实时操作系统中的第一步。vTaskStartScheduler开启调度时顺便会创建空闲任务和定时器任务。FreeRTOS 为了任务启动和任务切换使用了三个异常SVC、PendSV 和SysTick。SVC系统服务调用亦简称系统调用用于任务启动。PendSV可挂起系统调用用于完成任务切换它是可以像普通的中断一样被挂起的它的最大特性是如果当前有优先级比它高的中断在运行PendSV会延迟执行直到高优先级中断执行完毕这样产生的PendSV 中断就不会打断其他中断的运行。SysTick 用于产生系统节拍时钟提供一个时间片如果多个任务共享同一个优先级则每次 SysTick 中断下一个任务将获得一个时间片。FreeRTOS 中的任务是抢占式调度机制高优先级的任务可打断低优先级任务低优先级任务必须在高优先级任务阻塞或结束后才能得到调度。相同优先级的任务采用时间片轮转方式进行调度也就是分时调度时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效。4.3 启动方式FreeRTOS有两种启动方式效果一样看个人喜好。第一种main 函数中将硬件初始化 RTOS 系统初始化所有任务的创建完成最后一步开启调度。目前看到的几个芯片SDK都是这种方式。第二种main 函数中将硬件和 RTOS 系统先初始化好只创建一个任务后就启动调度器然后在这个任务里面创建其它应用任务当所有任务都创建成功后启动任务再把自己删除。4.4 任务创建源码分析xTaskCreate()创建任务。1. BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
2. const char * const pcName, /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
3. const configSTACK_DEPTH_TYPE usStackDepth,
4. void * const pvParameters,
5. UBaseType_t uxPriority,
6. TaskHandle_t * const pxCreatedTask )
7. {
8. TCB_t * pxNewTCB;
9. BaseType_t xReturn;
10.
11. /* If the stack grows down then allocate the stack then the TCB so the stack
12. * does not grow into the TCB. Likewise if the stack grows up then allocate
13. * the TCB then the stack. */
14. #if ( portSTACK_GROWTH 0 )
15. {
16. /**/
17. }
18. #else /* portSTACK_GROWTH */
19. {
20. StackType_t * pxStack;
21.
22. /* Allocate space for the stack used by the task being created. */
23. pxStack pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCUs stack and this allocation is the stack. */
24.
25. if( pxStack ! NULL )
26. {
27. /* Allocate space for the TCB. */
28. pxNewTCB ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e9087 !e9079 All values returned by pvPortMalloc() have at least the alignment required by the MCUs stack, and the first member of TCB_t is always a pointer to the tasks stack. */
29.
30. if( pxNewTCB ! NULL )
31. {
32. /* Store the stack location in the TCB. */
33. pxNewTCB-pxStack pxStack;
34. }
35. else
36. {
37. /* The stack cannot be used as the TCB was not created. Free
38. * it again. */
39. vPortFree( pxStack );
40. }
41. }
42. else
43. {
44. pxNewTCB NULL;
45. }
46. }
47. #endif /* portSTACK_GROWTH */
48.
49. if( pxNewTCB ! NULL )
50. {
51. #if ( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE ! 0 ) /*lint !e9029 !e731 Macro has been consolidated for readability reasons. */
52. {
53. /* Tasks can be created statically or dynamically, so note this
54. * task was created dynamically in case it is later deleted. */
55. pxNewTCB-ucStaticallyAllocated tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
56. }
57. #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */
58.
59. prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
60. prvAddNewTaskToReadyList( pxNewTCB ); //将新任务加入到就绪链表候着
61. xReturn pdPASS;
62. }
63. else
64. {
65. xReturn errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
66. }
67.
68. return xReturn;
69. }
申请任务控制块内存检查配置参数初始化将任务信息加入到就绪链表等待调度。前面链表部分提到freeRTOS的任务信息都是使用链表记录在task.c有1. PRIVILEGED_DATA static List_t pxReadyTasksLists[configMAX_PRIORITIES];//就绪
2. PRIVILEGED_DATA static List_t xDelayedTaskList1; //延时
3. PRIVILEGED_DATA static List_t xDelayedTaskList2;
4. PRIVILEGED_DATA static List_t xPendingReadyList; //挂起
5. PRIVILEGED_DATA static List_t xSuspendedTaskList; //阻塞
分别记录就绪态、阻塞态和挂起的任务其中阻塞态有2个是因为特殊考虑时间溢出 的问题实际开发单片机项目计时超过24h的可以借鉴。其中pxReadyTasksLists链表数组其下标就是任务的优先级。4.5 任务调度源码分析创建完任务的时候vTaskStartScheduler开启调度器空闲任务、定时器任务也是在开启调度函数中实现的。为什么要空闲任务因为 FreeRTOS一旦启动就必须要保证系统中每时每刻都有一个任务处于运行态Runing并且空闲任务不可以被挂起与删除空闲任务的优先级是最低的以便系统中其他任务能随时抢占空闲任务的 CPU 使用权。这些都是系统必要的东西也无需自己实现。1. void vTaskStartScheduler( void )
2. {
3. BaseType_t xReturn;
4.
5. /* Add the idle task at the lowest priority. */
6. #if ( configSUPPORT_STATIC_ALLOCATION 1 )
7. {
8. /***/
9. }
10. #else /* if ( configSUPPORT_STATIC_ALLOCATION 1 ) */
11. {
12. /*创建空闲任务*/
13. xReturn xTaskCreate( prvIdleTask,
14. configIDLE_TASK_NAME,
15. configMINIMAL_STACK_SIZE,
16. ( void * ) NULL,
17. portPRIVILEGE_BIT, //优先级为0
18. xIdleTaskHandle );
19. }
20. #endif /* configSUPPORT_STATIC_ALLOCATION */
21.
22. #if ( configUSE_TIMERS 1 )
23. {
24. if( xReturn pdPASS )
25. {
26. //创建定时器task接收开始、结束定时器等命令
27. xReturn xTimerCreateTimerTask();
28. }
29. else
30. {
31. mtCOVERAGE_TEST_MARKER();
32. }
33. }
34. #endif /* configUSE_TIMERS */
35.
36. if( xReturn pdPASS )
37. {
38. /* freertos_tasks_c_additions_init() should only be called if the user
39. * definable macro FREERTOS_TASKS_C_ADDITIONS_INIT() is defined, as that is
40. * the only macro called by the function. */
41. #ifdef FREERTOS_TASKS_C_ADDITIONS_INIT
42. {
43. freertos_tasks_c_additions_init();
44. }
45. #endif
46.
47. portDISABLE_INTERRUPTS();
48.
49. #if ( configUSE_NEWLIB_REENTRANT 1 )
50. {
51. _impure_ptr ( pxCurrentTCB-xNewLib_reent );
52. }
53. #endif /* configUSE_NEWLIB_REENTRANT */
54.
55. xNextTaskUnblockTime portMAX_DELAY;
56. xSchedulerRunning pdTRUE;
57. xTickCount ( TickType_t ) configINITIAL_TICK_COUNT;
58.
59. portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
60.
61. traceTASK_SWITCHED_IN();
62.
63. /* Setting up the timer tick is hardware specific and thus in the
64. * portable interface. */
65. if( xPortStartScheduler() ! pdFALSE )
66. {
67. /* 系统开始运行 */
68. }
69. else
70. {
71. /* Should only reach here if a task calls xTaskEndScheduler(). */
72. }
73. }
74. else
75. {
76. /*****/
77. }
4.6 任务状态切换FreeRTOS 系统中的每一个任务都有多种运行状态具体如下 任务挂起函数vTaskSuspend()
挂起指定任务被挂起的任务绝不会得到 CPU 的使用权vTaskSuspendAll()
将所有的任务都挂起 任务恢复函数vTaskResume()
vTaskResume()
xTaskResumeFromISR()
任务恢复就是让挂起的任务重新进入就绪状态恢复的任务会保留挂起前的状态信息在恢复的时候根据挂起时的状态继续运行。xTaskResumeFromISR() 专门用在中断服务程序中。无论通过调用一次或多次vTaskSuspend()函数而被挂起的任务也只需调用一次恢复即可解挂 。 任务删除函数 vTaskDelete()用于删除任务。当一个任务可以删除另外一个任务形参为要删除任 务创建时返回的任务句柄如果是删除自身 则形参为 NULL。4.7 任务使用注意点1、中断服务函数是不允许调用任何会阻塞运行的接口。一般在中断服务函数中只做标记事件的发生然后通知任务让对应任务去执行相关处理 。2、将紧急的处理事件的任务优先级设置偏高一些。 3、空闲任务idle 任务是 FreeRTOS 系统中没有其他工作进行时自动进入的系统任务永远不会挂起空闲任务不应该陷入死循环。4、创建任务使用的内存不要过多按需申请。如果浪费太多后续应用申请大空间可能提示内存不足。五、 队列5.1 队列的概念队列用于任务间通信的数据结构通过消息队列服务任务或中断服务将消息放入消息队列中。其他任务或者自身从消息队列中获得消息。实现队列可以在任务与任务间、中断和任务间传递信息。队列操作支持阻塞等待向已经填满的队列发送数据或者从空队列读出数据都会导致阻塞时间自定义。消息队列的运作过程具如下5.2 队列创建xQueueCreate()用于创建一个新的队列并返回可用于访问这个队列的句柄。队列句柄其实就是一个指向队列数据结构类型的指针。1. master_queue xQueueCreate(50, sizeof(task_message_struct_t));
创建队列占用50个单元每个单元为sizeof(task_message_struct_t)字节和 malloc比较类似。其最终使用的函数是 xQueueGenericCreate()后续信号量等也是使用它创建只是最后的队列类型不同。申请内存后xQueueGenericReset再对其进行初始化队列的结构体xQUEUE成员1. typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
2. {
3. int8_t * pcHead; /* Points to the beginning of the queue storage area. */
4. int8_t * pcWriteTo; /* Points to the free next place in the storage area. */
5. //类型
6. union
7. {
8. QueuePointers_t xQueue; /* Data required exclusively when this structure is used as a queue. */
9. SemaphoreData_t xSemaphore; /* Data required exclusively when this structure is used as a semaphore. */
10. } u;
11.
12. //当前向队列写数据阻塞的任务列表或者从队列取数阻塞的链表
13. List_t xTasksWaitingToSend;
14. List_t xTasksWaitingToReceive;
15.
16. //队列里有多少个单元被占用应用中需要
17. volatile UBaseType_t uxMessagesWaiting;
18.
19. UBaseType_t uxLength; /* The length of the queue defined as the number of items it will hold, not the number of bytes. */
20. UBaseType_t uxItemSize; /* The size of each items that the queue will hold. */
21.
22. /******/
23. } xQUEUE;
5.3 队列删除队列删除函数 vQueueDelete()需传入要删除的消息队列的句柄即可删除之后这个消息队列的所有信息都会被系统回收清空而且不能再次使用这个消息队列了。实际应用中很少使用。5.4 向队列发送消息任务或者中断服务程序都可以给消息队列发送消息当发送消息时如果队列未满或者允许覆盖入队FreeRTOS 会将消息拷贝到消息队列队尾否则会根据用户指定的超时时间进行阻塞消息发送接口很多最简单的是 xQueueSend()用于向队列尾部发送一个队列消息。消息以拷贝的形式入队该函数绝对不能在中断服务程序里面被调用中断中必须使用带有中断保护功能的 xQueueSendFromISR()来代替。BaseType_t xQueueSend(QueueHandle_t xQueue,const void* pvItemToQueue, TickType_t xTicksToWait);
用于向队列尾部发送一个队列消息。参数 xQueue 队列句柄pvItemToQueue 指针指向要发送到队列尾部的队列消息。 xTicksToWait 队列满时等待队列空闲的最大超时时间。如果队列满并且xTicksToWait 被设置成 0函数立刻返回。超时时间的单位为系统节拍周期 tick延时为 portMAX_DELAY 将导致任务挂起没有超时。 返回值 消息发送成功成功返回 pdTRUE否则返回 errQUEUE_FULL。xQueueSendToBack与xQueueSend完全相同 xQueueSendFromISR()与 xQueueSendToBackFromISR()带FromISR表示只能在中断中使用freeRTOS所以带这个后缀的都是这个含义。xQueueSendToFront()和QueueSendToFrontFromISR()用于向队列队首发送一个消息。这些在任务中发送消息的函数都是 xQueueGenericSend()展开的宏定义。1. BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
2. const void * const pvItemToQueue,
3. TickType_t xTicksToWait,
4. const BaseType_t xCopyPosition ) //发送数据到消息队列的位置
一般使用xQueueSend和xQueueSendFromISR如不确定当前运行的是系统服务还是中断服务一般ARM都支持查询中断状态寄存器判断可以封装一层接口只管发消息内部判断是否使用支持中断嵌套的版本UIS8910就是如此。特殊情况下如发送网络数据包未收到服务器响应期望立刻入队再次发送它可以xQueueSendToFront向队头发消息。5.5 从队列读取消息当任务试图读队列中的消息时可以指定一个阻塞超时时间当且仅当消息队列中有消息的时候任务才能读取到消息。如果队列为空该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务程序往其等待的队列中写入了数据该任务将自动由阻塞态转为就绪态。当任务等待的时间超过了指定的阻塞时间即使队列中尚无有效数据任务也会自动从阻塞态转移为就绪态。所有的task主入口while循环体都是按这个执行。例如1. static void track_master_task_main()
2. {
3. track_task_message_struct_t queue_item {0};
4. /****/
5.
6. while(1)
7. {
8. if(xQueueReceive(master_queue, queue_item, portMAX_DELAY))//阻塞等待
9. {
10. track_master_task_msg_handler(queue_item);
11. }
12. }
13. }
xQueueReceive()用于从一个队列中接收消息并把消息从队列中删除。如果不想删除消息的话就调用 xQueuePeek()函数。xQueueReceiveFromISR()与xQueuePeekFromISR()是中断版本用于在中断服务程序中接收一个队列消息并把消息。这两个函数只能用于中断是不带有阻塞机制的实际项目没有使用。5.6 查询队列使用情况uxQueueMessagesWaiting()查询队列中存储的信息数目具有中断保护的版本为uxQueueMessagesWaitingFromISR()。查询队列的空闲数目uxQueueSpacesAvailable()。5.7 队列使用注意点使用队列函数需要注意以下几点1、中断中必须使用带FromISR后缀的接口 2、发送或者是接收消息都是以拷贝的方式进行如果消息内容过于庞大可以将消息的地址作为消息进行发送、接收。1. typedef struct
2. {
3. TaskHandle_t src_mod_id;
4. int message_id;
5. int32_t param;
6. union
7. {
8. int32_t result;
9. int32_t socket_id;
10. };
11. void* pvdata; //大数据使用动态申请内存保存队列只传递指针
12. } track_task_message_struct_t;
3、队列并不属于任何任务所有任务都可以向同一队列写入和读出一个队列可以由多任务或中断读写。 4、队列的深度要结合实际可以多申请点前提是每个队列单元尽可能小。 5、队列存在一定限制在队头没有取出来之前是无法取出第二个和STL链表存在差异。六、 软件定时器6.1 软件定时器的概念定时器有硬件定时器和软件定时器之分硬件定时器是芯片本身提供的定时功能精度高并且是中断触发方式。软件定时器是由操作系统封装的接口它构建在硬件定时器基础之上使系统能够提供不受硬件定时器资源限制其实现的功能与硬件定时器也是类似的。在操作系统中通常软件定时器以系统节拍周期为计时单位。系统节拍配置为configTICK_RATE_HZ该宏在 FreeRTOSConfig.h 中一般是100或者1000。根据实际系统 CPU 的处理能力和实时性需求设置合适的数值系统节拍周期的值越小精度越高但是系统开销也将越大因为这代表在 1 秒中系统进入时钟中断的次数也就越多。6.2 软件定时器创建软件定时器需先创建才允许使用动态创建方式是xTimerCreate()返回一个句柄。软件定时器在创建成功后是处于休眠状态的没有开始计时运行。FreeRTOS的软件定时器支持单次模式和周期模式。单次模式当用户创建了定时器并启动了定时器后定时时间到了只执行一次回调函数之后不再执行。周期模式定时器会按照设置的定时时间循环执行回调函数直到用户将定时器停止或删除。实际项目中使用这种模式对单片机喂狗就比较省事。1. TimerHandle_t xTimerCreate( const char * const pcTimerName, //定时器名称
2. const TickType_t xTimerPeriodInTicks, //定时时间
3. const UBaseType_t uxAutoReload, //是否自动重载
4. void * const pvTimerID, //回调函数的参数
5. TimerCallbackFunction_t pxCallbackFunction ) //回调函数
6.3 软件定时器开启新创建的定时器没有开始计时启动可以使用xTimerStart()、
xTimerReset()、
xTimerStartFromISR() 、xTimerResetFromISR()
xTimerChangePeriod()、xTimerChangePeriodFromISR()
这些函数将其状态转换为活跃态开始运行。区别如果定时器设定60秒间隔已经运行了30秒reset是将定时器重置为原来设定的时间间隔也就是重新开始延时60秒。ChangePeriod重新设置计时周期。6.4 软件定时器停止xTimerStop() 用于停止一个已经启动的软件定时器xTimerStopFromISR()是中断版本。6.5 软件定时器删除xTimerDelete用于删除一个已经被创建成功的软件定时器释放资源删除之后不能再使用。实际项目中任务和队列都是按需创建一直使用但是定时器不使用的就应该删除并且删除后一定要将句柄置为NULL。6.6 软件定时器源码分析软件定时器任务是在系统开始调度的时候就被创建vTaskStartScheduler()—xTimerCreateTimerTask。1. BaseType_t xTimerCreateTimerTask( void )
2. {
3. BaseType_t xReturn pdFAIL;
4.
5. prvCheckForValidListAndQueue(); //创建定时器任务的队列
6.
7. if( xTimerQueue ! NULL )
8. {
9. #if ( configSUPPORT_STATIC_ALLOCATION 1 )
10. {
11. /**/
12. }
13. #else /* if ( configSUPPORT_STATIC_ALLOCATION 1 ) */
14. {
15. //创建定时器任务
16. xReturn xTaskCreate( prvTimerTask,
17. configTIMER_SERVICE_TASK_NAME,
18. configTIMER_TASK_STACK_DEPTH,
19. NULL,
20. ( ( UBaseType_t ) configTIMER_TASK_PRIORITY ) | portPRIVILEGE_BIT,
21. xTimerTaskHandle );
22. }
23. #endif /* configSUPPORT_STATIC_ALLOCATION */
24. }
25. /**/
26. return xReturn;
27. }
任务创建后等候命令执行1.static portTASK_FUNCTION( prvTimerTask, pvParameters )
2. {
3. /**/
4.
5. for( ; ; )
6. {
7. //最近即将超时的定时器还有多长时间溢出
8. xNextExpireTime prvGetNextExpireTime( xListWasEmpty );
9.
10. //阻塞等待定时器溢出或受到命令进入下一步原因不明
11. prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
12.
13. //接收命令并处理见下面
14. prvProcessReceivedCommands();
15. }
16. }
所有定时器接口都是使用xTimerGenericCommand向队列发送控制命令命令如下1. #define tmrCOMMAND_START_DONT_TRACE ( ( BaseType_t ) 0 )
2. #define tmrCOMMAND_START ( ( BaseType_t ) 1 )
3. #define tmrCOMMAND_RESET ( ( BaseType_t ) 2 )
4. #define tmrCOMMAND_STOP ( ( BaseType_t ) 3 )
5. #define tmrCOMMAND_CHANGE_PERIOD ( ( BaseType_t ) 4 )
6. #define tmrCOMMAND_DELETE ( ( BaseType_t ) 5 )
6.7 软件定时器使用注意点1、查看其他开源代码对定时器的使用并不多但实际项目中过多依赖定时器导致应用逻辑混乱。 2、freeRTOS 的定时器不是无限制的其根源是接收定时器控制命令消息的队列默认只有10个单元。1. xTimerQueue xQueueCreate( ( UBaseType_t ) configTIMER_QUEUE_LENGTH, sizeof( DaemonTaskMessage_t ) );
定时器过多可能出现发起定时器命令失败原因是队列已满。可以将默认的10扩大为15后续尽量使用信号量来优化代码。 4、软件定时器的回调函数要快进快出而且不能有任何阻塞任务运行的情况不能有vTaskDelay() 以及其它能阻塞任务运行的函数。特别说明其回调函数是在定时器任务执行的并不是开启定时器的任务。七、 信号量7.1 信号量的概念信号量Semaphore是一种实现任务间通信的机制可以实现任务之间同步或临界资源的互斥访问常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中各任务之间需要同步或互斥实现临界资源的保护信号量功能可以为用户提供这方面的支持。可以简单认为是为支持多任务同时操作的全局变量个人理解。7.1.1 二值信号量比如有一个停车位多个人都想占用停车这种情况就可以使用一个变量标记车位状态它只有两种情况被占用或者没被占用。在多任务中使用二值信号量表示用于任务与任务、任务与中断的同步。在freeRTOS中二值信号量看作只有一个消息的队列因此这个队列只能为空或满。7.1.2 计数信号量如果有100个停车位可以停100辆车每进去一辆车车位的数量就要减一当停车场停满了 100 辆车的时候再来的车就不能停进去了。这种场景就需要计数信号量来表示多个状态。二进制信号量可以被认为是长度为 1 的队列而计数信号量则可以被认为长度大于 1 的队列信号量使用者依然不必关心存储在队列中的消息只需关心队列是否有消息即可。7.1.3 互斥信号量还是前面车位问题只剩一个空车位虽然员工车离得近但是领导车来了要优先安排给领导使用这就是由地位决定。互斥信号量其实是特殊的二值信号量由于其特有的优先级继承机制从而使它更适用于简单互锁也就是保护临界资源。优先级翻转问题假设有任务H任务M和任务L三个任务优先级逐次降低。低优先级的任务L抢先占有资源导致高优先级的任务H阻塞等待此时再有中等优先级的任务M它不需要该资源且优先级高于任务L它优先执行之后再执行任务L最后才执行任务H。看起来就是高优先级的任务反而不如低优先级的任务即优先级翻转。改进型的互斥信号量具有优先级继承机制操作系统对获取到临界资源的任务提高其优先级为所有等待该资源的任务中的最高优先级。一旦任务释放了该资源就恢复到原来的优先级。任务L先占用资源任务H申请不到资源会进入阻塞态同时系统就会把当前正在使用资源的任务L的优先级临时提高到与任务H优先级相同即使任务M被唤醒了因为它的优先级比任务H低所以无法打断任务L因为任务L的优先级被临时提升到 H任务L使用完该资源任务H优先级最高将接着抢占 CPU 的使用权这样保证任务H在任务M前优先执行。上面的这些就是为了说明二值信号量因为优先级翻转不能用于对临界区的访问。7.1.4 递归互斥信号量信号量是每获取一次可用信号量个数就会减少一个释放一次就增加一个。但是递归信号量则不同。对于已经获取递归互斥量的任务可以重复获取该递归互斥量该任务拥有递归信号量的所有权。任务成功获取几次递归互斥量就要返还几次在此之前递归互斥量都处于无效状态其他任务无法获取只有持有递归信号量的任务才能获取与释放。类似栈的效果。7.2 二值信号量的应用二值信号量是任务与任务间、任务与中断间同步的重要手段。例如任务A使用串口发出AT数据后获取二值信号量无效进入阻塞某个时间后任务B中串口收到正确的回复释放二值信号量。任务A就立即从阻塞态中解除进入就绪态等待运行。这种机制用在模块AT交互很合适。7.3 计数信号量的应用计数信号量可以用于资源管理允许多个任务获取信号量访问共享资源。例如有公共资源车位3个但是有多个任务要使用这种场景就必须使用计数信号量。三个资源最多支持 3 个任务访问那么第 4 个任务访问的时候会因为获取不到信号量而进入阻塞。也就是第4个人无法占用车位必须前面有车离开。等到其中一个有任务比如任务 1 释放掉该资源的时候第 4 个任务才能获取到信号量从而进行资源的访问。其运作的机制类似下图。在这里插入图片描述7.4 互斥信号量的应用多任务环境下往往存在多个任务竞争同一临界资源的应用场景互斥量可被用于对临界资源的保护从而实现独占式访问。互斥量可以降低信号量存在的优先级翻转问题带来的影响。比如有两个任务需要对串口进行发送数据其硬件资源只有一个那么两个任务肯定不能同时发送不然导致数据错误那么就可以用互斥量对串口资源进行保护当一个任务正在使用串口的时候另一个任务则无法使用串口等到前一个任务使用串口完成后 另外一个任务才能获得串口的使用权。另外需要注意的是互斥量不能在中断服务函数中使用因为其特有的优先级继承机制只在任务起作用在中断的上下文环境毫无意义。互斥信号量可以在多个任务之间进行资源保护而临界区只能是在同一个任务进行但是其速度快。个人理解7.5 信号量接口所有信号量semaphore使用套路相近都是创建creat、删除delete、释放give和获取take四种释放和获取支持任务级和中断级FromISR其中互斥量和递归互斥量不支持中断。使用对应的信号量需要在FreeRTOSConfig.h开启对应的功能。7.5.1 信号量创建xSemaphoreCreateBinary()用于创建一个二值信号量并返回一个句柄默认二值信号量为空在使用函数 xSemaphoreTake()获取之前必须 先 调 用 函 数 xSemaphoreGive() 释放后才可以获取。xSemaphoreCreateCounting()创建计数信号量。1. #define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount )
uxMaxCount 计数信号量的最大值当达到这个值的时候信号量不能再被释放。uxInitialCount 创建计数信号量的初始值。xSemaphoreCreateMutex()用于创建一个互斥量并返回一个互斥量句柄只能被同一个任务获取一次如果同一个任务想再次获取则会失败。xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量递归信号量可以被同一个任务获取很多次获取多少次就需要释放多少次。递归信号量与互斥量一样都实现了优先级继承机制可以减少优先级反转的反生。7.5.2 信号量删除vSemaphoreDelete()用于删除一个信号量包括二值信号量计数信号量互斥量和递 归互斥量。如果有任务阻塞在该信号量上暂时不要删除该信号量。传入的参数为创建时返回的句柄。7.5.3 信号量释放当信号量有效的时候任务才能获取信号量信号量变得有效就是释放信号量。每调用一次该函数就释放一个信号量注意释放的次数尤其是计数信号量。xSemaphoreGive()是任务中释放信号量的宏可以用于二值信号量、计数信号量、互斥量的释放但不能释放由函数xSemaphoreCreateRecursiveMutex()创建的递归互斥量递归互斥信号量用xSemaphoreGiveRecursive()释放。xSemaphoreGiveFromISR()带中断保护释放一个信号量被释放的信号量可以是二值信号量和计数信号量不能释放互斥量和递归互斥量因为互斥量和递归互斥量不可在中断中使用互斥量的优先级继承机制只能在任务中起作用。7.5.4 信号量获取与释放信号量对应的是获取信号量当信号量有效的时候任务才能获取信号量当任务获取了某个信号量的时候该信号量的可用个数就减一当它减到0 的时候任务就无法再获取了并且获取的任务会进入阻塞态如果设定了阻塞超时时间。xSemaphoreTake()函数用于获取信号量不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量但是递归互斥量并不能使用它。1. #define xSemaphoreTake( xSemaphore, xBlockTime )
xSemaphore 信号量句柄 xBlockTime 等待信号量可用的最大超时时间单位为 tick 获取 成 功 则 返 回 pdTRUE 在 指定的 超时 时间 中 没 有 获 取 成 功 则 返 回errQUEUE_EMPTY。使用xSemaphoreTakeRecursive()获取递归互斥量。xSemaphoreTakeFromISR()是获取信号量的中断版本是一个不带阻塞机制获取信号量的函数获取对象必须由是已经创建的信号量信号量类型可以是二值信号量和计数信号量它与 xSemaphoreTake()函数不同它不能用于获取互斥量因为互斥量不可以在中断中使用并且互斥量特有的优先级继承机制只能在任务中起作用而在中断中毫无意义。7.6 信号量使用注意点1、建议合理使用信号量进行事件同步处理减少对定时器的依赖。2、使用前合理设定超时时间和依赖关系避免多个任务互相等待对方释放的信号量而死锁。八、 事件8.1 事件的概念信号量用于单个任务与任务或任务与中断之间的同步但有些任务可能与多个任务由关联此时信号量实现就比较麻烦可以使用事件机制。事件是一种实现任务间通信的机制多任务环境下任务、中断之间往往需要同步操作一个事件发生会告知等待中的任务即形成一个任务与任务、中断与任务间的同步。事件可以提供一对多、多对多的同步操作。一对多同步模型一个任务等待多个事件的触发这种情况是比较常见的。任务可以通过设置事件位来实现事件的触发和等待操作。FreeRTOS 的事件仅用于同步不提供数据传输功能。8.2 事件的应用在某些场合可能需要多个事件发生了才能进行下一步操作。各个事件可分别发送或一起操作事件标志组而任务可以等待多个事件任务仅对感兴趣的事件进行关注。当有感兴趣的事件发生时并且符合感兴趣的条件任务将被唤醒并进行后续的处理动作。其机制类似一个全局变量子任务使用特殊的接口函数对指定的位进行写1或者清零主任务阻塞等待该变量满足设定的规则则返回运行。例如项目中的喂狗机制多个任务只要有一个任务发生异常则主任务停止喂狗等待被重启。不使用事件机制则3个任务定时向主master task发送消息表明自身任务运行正常同时master task定时查询是否收到3个任务的消息如果全都收到表示正常清除进入下一个定时检查周期如果其中一个未收到则表示对应任务异常故意停止喂狗等待被重启。使用事件机制则相对容易3个任务定时设置对应的标志位master task只需要等待指定的事件位超时就表示异常不需要自身定时查询也省去了定时发消息。当然缺点是master task只能阻塞等待事件不能执行其他业务逻辑。8.3 事件接口xEventGroupCreate()用于创建一个事件组vEventGroupDelete()删除事件对象控制块来释放系统资源。事件组置位任务中使用 xEventGroupSetBits()中断中使用xEventGroupSetBitsFromISR()xEventGroup 事件句柄。uxBitsToSet 指定事件中的事件标志位。如设置 uxBitsToSet 为 0x09 则位 3和位 0 都需要被置位。返回调用 xEventGroupSetBits() 时事件组中的值。事件组清除位任务中使用xEventGroupClearBits()中断中使用 xEventGroupClearBitsFromISR()都是用于清除事件组指定的位如果在获取事件的时候没有将对应的标志位清除那么就需要用这个函数来进行显式清除。xEventGroup 事件句柄。uxBitsToClear 指定事件组中的哪个位需要清除。如设置 uxBitsToSet 为 0x09则位 3和位 0 都需要被清除。读取事件标志任务中使用 xEventGroupGetBits()中断中使用xEventGroupGetBitsFromISR()。重点是等待事件函数 xEventGroupWaitBits()获取任务感兴趣的事件且支持等待超时机制当且仅当任务等待的事件发生时任务才能获取到事件信息。否则任务将保持阻塞状态以等待事件发生。当其它任务或中断服务程序往其等待的事件设置对应的标志位该任务将自动由阻塞态转为就绪态。EventGroupWaitBits()用于获取事件组中的一个或多个事件发生标志当要读取的事件标志位没有被置位时任务将进入阻塞等待状态。要想使用该函数必 须 把FreeRTOS/source/event_groups.c 这个 C 文件添加到工程中。1. EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
2. const EventBits_t uxBitsToWaitFor,
3. const BaseType_t xClearOnExit,
4. const BaseType_t xWaitForAllBits,
5. TickType_t xTicksToWait )
参数 xEventGroup 事件句柄。 uxBitsToWaitFor 一个按位或的值指定需要等待事件组中的哪些位置1。如需要等待 bits 0 and/or bit 1 and/or bit 2则 uxBitsToWaitFor 配置为 0x07(0111b)。xClearOnExit pdTRUExEventGroupWaitBits() 等待到满足任务唤醒的事件时系统将清除由形参 uxBitsToWaitFor 指定的事件标志位。pdFALSE不会清除由形参 uxBitsToWaitFor 指定的事件标志位。xWaitForAllBits pdTRUE 当形参 uxBitsToWaitFor 指定的位都置位的时候xEventGroupWaitBits()才满足任务唤醒的条件这也是“逻辑与”等待事件并且在没有超时的情况下返回对应的事件标志位的值。pdFALSE当形参 uxBitsToWaitFor 指定的位有其中任意一个置位的时候这也是常说的“逻辑或”等待事件在没有超时的情况下 函数返回对应的事件标志位的值。xTicksToWait 最大超时时间单位为系统节拍周期返回值返回事件中的哪些事件标志位被置位返回值很可能并不是用户指定的事件位需要对返回值进行 判断再处理 。其应用类似某个全局变量等待事件的任务在设定的时间内监控该变量某些位的值该值由其他任务或中断修改。九、 任务通知FreeRTOS 从 V8.2.0 版本开始提供任务通知这个功能可以在一定场合下替代 FreeRTOS 的信号量队列、事件组等但是使用也有局限性。将宏定义 configUSE_TASK_NOTIFICATIONS 设置为 1才能开启开功能。但该功能并不常用。十、 内存管理10.1 内存管理的概念FreeRTOS 内存管理模块管理用于系统中内存资源它是操作系统的核心模块之一。主要包括内存的初始化、分配以及释放。一般不同的平台移植代码内存的动态申请和释放接口需要替换。嵌入式实时操作系统中一般不支持标准C库中的 malloc()和 free()其内存有限随着内存不断被分配和释放整个系统内存区域会产生越来越多的碎片。FreeRTOS提供了 5 种内存管理算法源文件在Source\portable\MemMang 路径下使用的时候选择其中一个。heap_1.c、heap_2.c 和 heap_4.c 这三种内存管理方案内存堆实际上是一个很大的 数 组ucHeap。heap_1.c内存管理方案简单,它只能申请内存而不能进行内存释放。有些嵌入式系统并不会经常动态申请与释放内存一般都是在系统启动后就一直使用下去永不删除适合这种方式。heap_2.c 方案支持释放申请的内存但是它不能把相邻的两个小的内存块合成一个大的内存块对于每次申请内存大小都比较固定的但每次申请并不是固定内存大小的则会造成内存碎片。如下图随着不断的申请释放空闲空间会变成很多小片段。heap_3.c 方案只是封装了标准 C 库中的 malloc()和 free()函数由编译器提供需要通过编译器或者启动文件设置堆空间。heap_4.c 方案是在heap_2.c 基础上对内存碎片进行了改进能把相邻的空闲的内存块合并成一个更大的块这样可以减少内存碎片。heap_5.c 方案在实现动态内存分配时与 heap4.c 方案一样采用最佳匹配算法和合并算法并且允许内存堆跨越多个非连续的内存区也就是允许在不连续的内存堆中实现内存分配比如做图形显示可能芯片内部的 RAM 不足额外扩展SDRAM那这种内存管理方案则比较合适。一般物联网平台使用的是heap_4.c。10.2 内存管理接口不管其内部的管理如何实现的对上层应用层的接口都是一样的。1. void *pvPortMalloc( size_t xSize ); //内存申请函数
2. void vPortFree( void *pv ); //内存释放函数
3. void vPortInitialiseBlocks( void ); //初始化内存堆函数
4. size_t xPortGetFreeHeapSize( void ); //获取当前未分配的内存堆大小
5. size_t xPortGetMinimumEverFreeHeapSize( void ); //获取未分配的内存堆历史最小值
一般主要是使用内存申请和释放两个接口用法和注意事项同malloc/free一样成对使用。内存释放后尽量将指针设为NULL。十一、 通用接口一些常用接口进行说明。11.1 临界段进入和退出临界段的宏在 task.h 中定义进入和退出临界段的宏分中断保护版本和非中断版本但最终都是通过开/关中断来实现。主要用于对全局变量的控制系统使用非常多但实际项目中没使用因为全局变量的异常访问时小概率问题只是测试没发现理论上是存在问题的。1. /* 在中断场合*/ {
2. uint32_t ulReturn;
3.
4. ulReturn taskENTER_CRITICAL_FROM_ISR(); /* 进入临界段临界段可以嵌套 */
5.
6. /* 临界段代码 */
7.
8. taskEXIT_CRITICAL_FROM_ISR( ulReturn ); } /* 退出临界段 */
1. /* 在非中断场合 */ {
2.
3. taskENTER_CRITICAL(); /* 进入临界段 */
4.
5. /* 临界段代码 */
6.
7. taskEXIT_CRITICAL(); } /* 退出临界段*/
11.2 任务阻塞延时vTaskDelay ()阻塞延时任务调用该延时函数后会被剥离 CPU 使用权进入阻塞状态直到延时结束。但是该函数不能用在中断服务和定时回调函数。延时单位是tick。11.3 获取系统时钟计数值1. TickType_t xTaskGetTickCount( void )
2. TickType_t xTaskGetTickCountFromISR( void )
注意该接口分任务版和中断版该接口获取的是tick计数值需要结合系统时钟频率转换成时间。11.4 中断回调函数和其它平台不同中断回调中释放中断标记即可freeRTOS中中断触发后可能某些阻塞的任务获取了相关信号需要立刻执行因此中断服务发送消息后需要主动查询阻塞任务的情况执行任务切换动作。1. static uint32_t ulExampleInterruptHandler( void )
2. {
3. BaseType_t xHigherPriorityTaskWoken;
4.
5. xQueueSendToBackFromISR (xQueueRx,cChar,xHigherPriorityTaskWoken);
6. portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
7. }
推荐阅读 专辑|Linux文章汇总 专辑|程序人生 专辑|C语言嵌入式Linux微信扫描二维码关注我的公众号