提示文章文章目录前言一、背景二、2.1**第6章****第7章****第8章****第9章 空闲任务和阻塞延时的实现****第13章****第14章 创建任务****第15章****第16章 任务管理****16.2 任务调度器****18章 信号量****事件****21、定时器****任务通知****24 中断**2.2三、3.1总结前言前期疑问本文目标一、背景最近二、2.1freeRTOS学习看野火freeRTOS的资料看到第7章 任务的定义与任务切换的实现——7.6 main 函数章节教程写了初版最简单的多线程demo然后想自己也照着实现工程然后回到第3章 初识FreeRTOS——3.3 FreeRTOS 资料获取准备通过链接https://sourceforge.net/projects/freertos/files/FreeRTOS/下载freeRTOS源码但是不确定下载哪个版本本来想下载最新版本想了下下载BDM同版本的吧。看BDM100的代码源码查看下述文件确定是V10.2.1所以下载对应版本。第6章这个章节在前面新建的本地工程基础上在freertos文件夹下新建一个include文件夹里面放list.h文件同时freertos文件夹新建一个list.c文件。将两个文件添加到freeRTOS/Source组中其中list.h文件还需要在头文件中设置。我直接从野火现有工程中拷贝文件。进入freertos文件夹下看到除了list文件还有其他很多文件主要是include文件夹中有不少其他文件。处理方法是只拷贝list文件下面设置头文件路径。添加后编译正常。下面将野火工程中main文件代码拷贝到keil中文会乱码设置keil以下两个项目设置好后重新拷贝中文不乱码。重新编译后报错拷贝FreeRTOS.h文件到include文件夹下编译报错如下按照错误再继续添加文件FreeRTOSConfig.hportable.h添加文件后报错继续添加这个文件portmacro.h编译通过keil警告查了下是因为版本授权过期。报这个警告遇到“no previous extern declaration for non-static variable”错误通常意味着在代码中使用了某个变量但是在使用它之前没有正确地声明或者定义该变量。尝试将头文件全部添加到freertos组中该告警继续存在。忽略仿真调试在for语句处打断点将List_Item、List_Item1、List_Item2、List_Item3添加到watch窗口全速运行查看实验结果可以看到四个变量中存储的前后节点情况。所以这个章节应该就是尝试写了个关于freertos常用的链表的demo程序为什么freertos常用链表呢。据我了解任务有四个状态就绪态运行态阻塞态挂起态。其中就绪态任务处于就绪态的时候就是将任务放在就绪列表中。就绪列表就是一个链表里面存了很多任务。继续往下看。第7章按照第7章教程将第7章工程main文件代码拷贝到自己的工程main文件中编译报错拷贝task.h和task.c文件。第7章用到了程序块按照教程在FreeRTOS.h文件中增加skTCB结构体定义。FreeRTOS.h文件差异如下拷贝后configMAX_TASK_NAME_LEN宏定义报错查看FreeRTOSConfig.h文件差异如下编译报错缺少文件拷贝文件该文件中内容如下拷贝文件后编译。编译报错根据错误搜索发现portable.h文件也有差异如下最终指向涉及到这个文件这个文件是第7章新增的文件路径如下图所示添加文件后编译port.c文件报错很多错误。如下图所示这个问题比较奇怪之前没有遇到过最后查了资料看到下面这篇文章得到答案Keil5.37版本下使用ARMcomplier6编译__asm 函数报错的问题,解决办法如下图切换成下面的版本。编译之前的报错消失了仅剩下面的一个错误。根据这个错误定位到portmacro.h文件有差异。修改后编译通过。尝试虚拟调试将flag1和flag2加入analysis运行出现预期效果第8章这个章节表述的是临界段的板胡看的云里雾里主要就记住了临界段的数据保护分为两种情况一种是在终端场合另一种是在非中段场合。第9章 空闲任务和阻塞延时的实现这个章节的开头表述了前面第6章的代码中使用的是delay延时delay延时是让cpu一直执行for循环指令等于是在浪费cpu资源。然后rtos最擅长的就是榨干cpu资源。表述到rtos的延时是阻塞延时【当任务需要延时的时候任务进入阻塞状态此时cpu干什么去了如果没有其他任务cpu会执行空闲任务】继续往下面可能看提到任务进入延时时进入阻塞状态这时候会剥离cpu使用权执行其他任务。这里我有疑问就是剥离cpu后怎么知道多久后重新获取cpu呢我个人猜测文中提到的调用延时函数调用的这个延时函数应该承担延时结束启动任务的工作。文中提到的延时函数为vTaskDelay()。看教程到9.3章节systick延时服务函数段落验证了我之前的猜想终端服务函数PendSV会调用上下文切换函数vTaskSwitchContext()这个上下文切换函数会遍历判断每个函数是否延时时间到0了如果到0的话就将任务置成就绪态。和我猜测的方式差不多。第9章节还实现了一个实验下面在本地实现教程的代码。将野火第9章代码main.c文件代码拷贝到自己工程main.c文件中编译报错根据错误找到FreeRTOSConfig.h文件差别修改后再次编译报错【.\Objects\Fire_FreeRTOS.axf: Error: L6218E: Undefined symbol vTaskDelay (referred from main.o).】即vTaskDelay 函数找不到搜索后发现在task.c文件中新增了vTaskDelay函数增加该函数声明后报错如下TCB_t结构体中缺少xTicksToDelay成员对比后确实如此修改后编译无异常debug虚拟仿真运行结果如下和教程所述现象有区别上述实验现象存在两个问题1、电平变化一次的时间不是20ms大概是10ms。2、上下两个变量变化的时间节点不像教程中一样是同一时刻的看起来cpu好像在同时做两件事情。然后我开始思考vTaskDelay( 2 );函数中的2是什么看了教程这个2不是时间而是代表(2个systick中断周期)也就是两个时钟周期吧 。这边表述好像有点问题回忆一下之前看的野火教程systick成为计时器可以实现延时计时器如何实现延时就是systick每次进中断时对全局变量num做–操作外面用while循环对全局变量num做判断num减为0则结束延时整个判断时间就是延时时间。而TIM称为定时器有基本定时器和高级定时器基本定时器是向上计数达到计数值后会进入中断可以实现定时效果。这边所述的systick中断周期就是systick计数寄存器计数为0的时间而计数一次的时间就是T1/72M则TsysT*count。所以Tsys的时间也可以称为systick中断周期时间是可以更改的更改的时间变化来源于count值变化。按照上述的理解在野火教程代码中搜索教程中提到的systic初始化函数vPortSetupTimerInterrupt()可以发现野火教程中多了个vPortSetupTimerInterrupt()函数下面在自己的工程中也增加这个配置函数增加这个函数后应该就能实现预期效果。增加了vPortSetupTimerInterrupt函数还是没有实现预期的效果可能还有其他的问题。下面记录想实现预期效果的结果过程:1、然后看了port.c文件差异发现野火教程还有个函数xPortSysTickHandler()自己的代码没有加上加上这个函数以及加上这个函数的调用函数还是没有实现预期效果。2、发现vTaskStartScheduler()函数的实现有变化按照野火教程修改vTaskStartScheduler函数并且根绝报错情况增加其他函数编译通过后仿真调试后没有出现预期效果。第13章这个章节提到的一个点就是一直freeRTOS代码需要先修改代码其中一个点就是屏蔽stm32f10x_it.h文件中的两个中断PendSV_Handler()与SVC_Handler()。今天还突然想到之前面试别人提出的一个问题就是问我中位机有哪些线程我说一个检测任务线程一个报警线程然后又问我哪个线程优先级高我说当然是检测线程优先级高。现在想来因为看freertos的教程提到报警的优先级是搞得至于具体那一章节我不清楚了。但是总体意思就是当发生故障时应该立即停止的所以报警优先级应该是高的。这个好验证看下中位机代码就可以知道了。另外检测任务线程应该是一个线程然后根据状态变量来判断执行哪一块程序。比如使用一个变量numnum执行操作num为1时执行加样臂的动作num为2时执行样本臂的动作。刚才看教程是第13章提到的提及的是无人机障碍物检测这个检测线程应该是优先级最高的。这边补充关于临界区的理解临界区指的是任务共享的资源对这种共享的资源访问时需要开启关中断因为访问临界区资源是不能被打断的。这也就是两个函数的用法taskENTER_CRITICAL(); //进入临界区和taskEXIT_CRITICAL(); //退出临界区第14章 创建任务这个章节讲的是创建任务学习到的新知识点就是main.c文件中创建任务时先要创建一个任务创建任务在这个任务创建任务中再创建LED任务到这里还是创建了任务需要vTaskStartScheduler();函数启动任务开启调度。另外还有一个需要注意的点是当在任务创建任务中创建LED任务后判断LED任务是否创建成功。判断创建成功后需要删除任务创建任务。创建任务还分为静态创建任务和动态创建任务主要由两个函数实现静态创建任务函数xTaskCreateStatic()动态创建任务函数xTaskCreate()这两个函数的区别主要影响申请内存情况。第15章这个章节介绍了两种RTOS启动流程第一种是我之前一直疑惑或者正常能想到的就是比如有两个任务我需要创建两个线程方法就是创建第一个任务然后创建第二个任务然后开启RTOS任务调度器。另外一种是先创建一个任务创建任务在任务创建任务中创建两个我需要的任务完成两个任务创建后产出任务创建任务。然后开启任务调度。开启任务运行。教程介绍到liteOS和uc-OS第一种和第二种都能用没有孰优孰劣。freeRTOS默认是使用第二种方式。开启任务调度器就是调用vTaskStartScheduler()函数该函数主要做两件事情一个是创建空闲任务一个是创建定时器任务。这其中好像要是能两个标志位1、configSUPPORT_STATIC_ALLOCATION 1添加空闲任务标志位2、configUSE_TIMERS 1创建定时器标志位之前疑惑什么是时间片然后看到freeRTOS的一段话systick产生系统时钟节拍提供一个时间片。如果多个任务共享同一个优先级则每次systick终端下一个任务将获取一个时间片。根据上述的表述可以理解为一个时间片其实就是一段时间如果遇到相同优先级的几个任务中断产生后会让其他同优先级的任务执行每个任务平等占用的这段时间就是时间片。时间片的时间就是中断的时间。第16章 任务管理16.2 任务调度器文章中提到任务调度器如何找到优先级最高的任务当然是从遍历任务列表然后找到优先级最高的任务但是这里存在一个问题就是这个查找优先级最高的任务根任务总数n有关如果有很多的任务那么找优先级最高的任务将是一个耗时的事情会导致RTOS不能成为实时操作系统。如何解决这个问题教程中表述了两种方式一种是在创建任务的时候就将任务按照优先级排序再顺序查找时间复杂度就降低很多。另外一种方式是使用计算前导零指令CLZ。受限于平台STM平台是提供这种资源的。这个章节还着重介绍了任务挂起即将任务置为挂起态。挂起态的任务不在获取CPU使用权。任务挂起函数是vTaskSuspend()想使用任务挂起函数需要开启宏定义INCLUDE_vTaskSuspend 配置为11、vTaskSuspend()任务挂起函数2、vTaskSuspendAll()所有任务挂起3、vTaskResume()任务恢复函数4、xTaskResumeFromISR()专门用在中断服务程序5、vTaskSuspendAll()6、vTaskDelete()必须在FreeRTOSConfig.h 中将宏定义INCLUDE_vTaskDelete 配置为17、vTaskDelay()延时函数必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 来使能每个任务必须要有阻塞延时否则低优先级任务无法运行8、vTaskDelayUntil()绝对延时函数必须在FreeRTOSConfig.h 中把INCLUDE_vTaskDelayUntil 定义为1第16章节设计的实验为创建两个任务一个是LED灯任务一个是KEY任务。LED灯任务中对LED灯执行亮灭操作亮灭之前的延时时间为500个时间中断。KEY任务中当按键按下时挂起LED灯任务松开按键恢复LED灯任务。18章 信号量信号量这一章上来介绍了序号信号量类型说明1、二值一个任务平时都是阻塞的直到某些事件发生时才会执行可以使用二进制信号量来实现任务间的同步。之前还在疑惑什么是任务同步二值信号量实现的效果就是任务同步详细在page2532、计数计数信号量一开始没有看懂后面看了实例用的是停车位的例子key1按下用于申请计数信号量申请5次信号量被申请完初始设置的计数信号量数值是5。按下key2释放信号量这时候key1又能申请计数信号量。对应停车场5个车位停满车就进不了车出来车才能再进车3、互斥信号量比如两个任务都需要向串口发送数据但是串口只有一个就需要使用互斥信号量概念1优先级继承当某个任务占用互斥信号量如果一个更高优先级的任务来访问临界资源会因为获取不到信号量进入阻塞此时会将占用信号量的任务优先级提升到和阻塞任务的优先级一样的优先级。这个提升优先级过程就叫优先级继承。这样处理的好处是保证高优先级任务进入阻塞状态的时间尽可能短。概念2-优先级翻转低优先级的任务先获取了互斥信号量高优先级访问临界资源时因为获取不到信号量也无法访问临界资源高优先级却不能比低优先级先访问到资源就叫优先级翻转。优先级反转的危害是什么教程中举的例子是任务L占有互斥信号量H任务拿不到互斥信号量进入阻塞态此时如果M任务被唤醒会抢占L任务开始执行M任务M任务执行完L任务继续执行。L任务执行完H任务最后执行。如图这边有个问题就是为什么M能抢占L任务我一开始不懂还问了copilot也没理解但是写到这里好像又理解一些。M任务能抢占L任务可能是L任务不是全程抢占临界资源的。比如首尾访问临界资源中间没有占用临界资源。而M任务此时刚才进入就绪态就能立即抢占到资源。那么为什么这时候H任务没有抢占到呢可能是在阻塞态吧。那么阻塞态怎么变成就绪态呢copilot说法是等待的信号量释放。对于M可以抢占L可能是M不需要申请临界资源就可以打断L任务了不知道是不是这个原因。如果后面有实例代码可以验证下。修改源码地方把宏configSUPPORT_DYNAMIC_ALLOCATION 和configUSE_RECURSIVE_MUTEXES 均定义为1 啊啊看了优先级翻转的实例代码原来真的是我想的第二种情况4、递归事件21、定时器修改项1、FreeRTOSConfig.h设置为12、相关代码编译进来创建定时器xTimerCreate()/xTimerCreateStatic()函数FreeRTOSConfig.h 中把宏configUSE_TIMERS 和configSUPPORT_DYNAMIC_ALLOCATION 均定义为1configSUPPORT_DYNAMIC_ALLOCATION 在FreeRTOS.h 中默认定义为1并且需要把FreeRTOS/source/times.c 这个C 文件添加到工程中。xTimerStart()启动定时器xTimerStartFromISR()在中断中启动定时器什么情境下需要从中断中启动定时器xTimerStop()在这个章节的实验中创建了两个定时器任务一个是周期性任务在回调函数中对变量加1并打印。另一个是单词任务在回调函数中加1并打印。我能想到的就是灯的闪烁可以用一个周期定时器任务来操作灯的开关在回调函数中操作。任务通知FreeRTOSConfig.h 中的宏定义configUSE_TASK_NOTIFICATIONS 设置为1默认是打开的实验内容是一个发送通知任务任务内检测两个按键是否被按下两个按键被按下后分别向两个接受任务发送通知。内存管理3、heap3_.cheap_3.c 方案只是简单的封装了标准C 库中的malloc()和free()函数重新封装后的malloc()和free()函数具有保护功能采用的封装方式是操作内存前挂起调度器、完成后再恢复调度器。需要链接器设置一个堆heap4_.cheap_4.c 方案与heap_2.c 方案一样都采用最佳匹配算法来实现动态的内存分配但是不一样的是heap_4.c 方案还包含了一种合并算法能把相邻的空闲的内存块合并成一个更大的块这样可以减少内存碎片。heap_4.c 方案特别适用于移植层中可以直接使用pvPortMalloc()和 vPortFree()函数来分配和释放内存的代码。24 中断异常与中断的基本概念异常同步异常由内部事件处理器指令运行产生的事件引起的异常成为同步异常例如除0引发的异常异步异常来源于外部硬件装置例如按下设备某个按钮产生的时间。同步异常与异步异常的区别还在于同步异常触发后系统必须立刻进行处理而不能够依然执行原有的程序指令步骤而异步异常则可以延缓处理甚至是忽略例如按键中断异常系统可以忽略它继续运行。中端中断属于异步异常中断能打断任务的运行无论该任务具有什么样的优先级因此中断一般用于处理比较紧急的事件通过中断机制可以使CPU 避免把大量时间耗费在等待、查询外设状态的操作上因此将大大提高系统实时性以及执行效率。这个就是之前在考虑的一个点就是比如串口接收数据没那么cpu就需要一直轮询串口是否有数据收到。但是实际上cpu并不会这样处理当接收中断产生时产生一个中断这时候cpu才来处理接收的数据避免浪费cpu资源。cpu怎么知道产生了中断呢这个是个问题。由操作系统的中断响应比裸机慢。因为操作系统访问临界资源的资源会将中断屏蔽。这时候来了一个中断这个中断会被挂起不能得到及时响应。2.2三、3.1总结未完待续