提示:文章
文章目录
- 前言
- 一、背景
- 二、
- 2.1
- **第6章**
- **第7章**
- **第8章**
- **第9章 空闲任务和阻塞延时的实现**
- **第13章**
- **第14章 创建任务**
- **第15章**
- **第16章 任务管理**
- **16.2 任务调度器**
- **18章 信号量**
- **事件**
- **21、定时器**
- **任务通知**
- **24 中断**
- 2.2
- 三、
- 3.1
- 总结
前言
前期疑问:
本文目标:
一、背景
最近
二、
2.1
freeRTOS学习
看野火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.h
portable.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的时间,而计数一次的时间就是T=1/72M,则Tsys=T*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的教程,提到报警的优先级是搞得,至于具体那一章节我不清楚了。但是总体意思就是当发生故障时应该立即停止的,所以报警优先级应该是高的。这个好验证,看下中位机代码就可以知道了。另外检测任务线程应该是一个线程,然后根据状态变量来判断执行哪一块程序。比如使用一个变量num,num执行++操作,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 配置为1
| 1、 | vTaskSuspend() | 任务挂起函数 | |
|---|---|---|---|
| 2、 | vTaskSuspendAll() | 所有任务挂起 | |
| 3、 | vTaskResume() | 任务恢复函数 | |
| 4、 | xTaskResumeFromISR() | 专门用在中断服务程序 | |
| 5、 | vTaskSuspendAll() | ||
| 6、 | vTaskDelete() | 必须在FreeRTOSConfig.h 中将宏定义INCLUDE_vTaskDelete 配置为1 | |
| 7、 | vTaskDelay() | 延时函数 | 必须在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定义为 1 来使能每个任务必须要有阻塞延时,否则低优先级任务无法运行 |
| 8、 | vTaskDelayUntil() | 绝对延时函数 | 必须在FreeRTOSConfig.h 中把INCLUDE_vTaskDelayUntil 定义为1 |
第16章节设计的实验为创建两个任务,一个是LED灯任务,一个是KEY任务。LED灯任务中对LED灯执行亮灭操作,亮灭之前的延时时间为500个时间中断。KEY任务中当按键按下时挂起LED灯任务,松开按键恢复LED灯任务。
18章 信号量
信号量这一章上来介绍了
| 序号 | 信号量类型 | 说明 | |
|---|---|---|---|
| 1、 | 二值 | 一个任务平时都是阻塞的,直到某些事件发生时才会执行,可以使用二进制信号量来实现任务间的同步。之前还在疑惑什么是任务同步,二值信号量实现的效果就是任务同步 | 详细在page253 |
| 2、 | 计数 | 计数信号量一开始没有看懂,后面看了实例,用的是停车位的例子,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设置为1 |
|---|---|
| 2、 | 相关代码编译进来 |
创建定时器
| xTimerCreate()/xTimerCreateStatic()函数 | FreeRTOSConfig.h 中把宏configUSE_TIMERS 和configSUPPORT_DYNAMIC_ALLOCATION 均定义为1(configSUPPORT_DYNAMIC_ALLOCATION 在FreeRTOS.h 中默认定义为1),并且需要把FreeRTOS/source/times.c 这个C 文件添加到工程中。 | |
|---|---|---|
| xTimerStart() | 启动定时器 | |
| xTimerStartFromISR() | 在中断中启动定时器 | 什么情境下需要从中断中启动定时器 |
| xTimerStop() |
在这个章节的实验中,创建了两个定时器任务,一个是周期性任务,在回调函数中对变量加1并打印。另一个是单词任务,在回调函数中加1并打印。我能想到的就是灯的闪烁可以用一个周期定时器任务来操作,灯的开关在回调函数中操作。
任务通知
| FreeRTOSConfig.h 中的宏定义configUSE_TASK_NOTIFICATIONS 设置为1,默认是打开的 |
|---|
实验内容是一个发送通知任务,任务内检测两个按键是否被按下,两个按键被按下后分别向两个接受任务发送通知。
内存管理
| 3、 | heap3_.c | heap_3.c 方案只是简单的封装了标准C 库中的malloc()和free()函数,重新封装后的malloc()和free()函数具有保护功能,采用的封装方式是操作内存前挂起调度器、完成后再恢复调度器。 | 需要链接器设置一个堆 |
| heap4_.c | heap_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
总结
未完待续