freeRTOS
1》什么是RTOS
RTOS全称 Real Time Operating System,中文名就是实时操作系统。
1、RTOS全称 Real Time Operating System,中文名就是实时操作系统
2、RTOS是指一类操作系统。而不是单指某一个操作系统,比如UCOS,FreeRTOS,RT-Thread等这些都是RTOS操作系统
3、在单片机开发过程中有两种开发方式
1、裸机开发
2、RTOS开发
两者的区别
1 | 裸机开发 |
2》什么是freeRTOS
1、根据名字,我们可以分成两部分:free和rtos,free就是免费、自由,不受约束的意思。
2、是RTOS中的一种,freeRTOS十分小巧,可以在很多有限的微控制器上进行运行,单从文件上来说就要比UCOS小得多
3》为什么要用freeRTOS
1、freertos是免费的这点是选择它的最重要的一点,企业做产品肯定是要控制成本的,所以就这一点我们的freertos就是最好选择
2、使用者很多,资料多,解决问题时方面查找,市场占有率很高,很多厂商提供的SDK包都支持freertos,尤其是一些带蓝牙、WIFI协议栈的芯片或者模块
3、简单,文件数量少,方便移植和上手
4、可以移植到很多不同的微控制器上,比如STM32 F1\F3\F4\F7上都可以移植
4》什么是任务
任务是一个运行的函数(包括函数和栈),是一段保存在FLASH上的代码,在CPU上运行
ARM架构
F407中有:
CPU:中央处理器(计算单元进行计算)
FLASH:存放代码 通过JLINK等专业的工具烧入到FLASH(保证代码不会被轻易破坏)
RAM:存放数据 可读可写
串口等等模块
CPU和内存的关系
CPU RAM:存放数据
//对于内存来说只有两个功能,将数据读出来和写进去
//要实现a=a+b这条语句CPU需要怎么操作
——————————————
1.读出a变量的值(对应的汇编指令LDR load register)
2.读出b变量的值
3.进行计算a、b的和(对应的汇编指令add)
4.将结果放到a的地址(对应汇编指令STR store)
CUP内部的寄存器 R0、R1……R15;R0-R12可以任意用来计算 R13-R15具有特殊功能:R13-SP(栈),R14-LR(返回地址),R15-PC(当前指令的地址)
ARM7架构处理器采用三级流水线的结构。包括取指(fetch)->译码(decode)->执行(execute)三级。
当第指令执行时,第二指令正在译码,第三条指令正在取指阶段。也就是说当第一条指令在执行时,PC寄存器应当指向第三条指令
也就是说只要处理器是三级流水结构时,PC寄存器总是指向第三条指令
5》任务的状态
1、运行态
2、就绪态
3、阻塞态
4、挂起态(暂停态)
5、停止态
6》任务之间的转换(参考freertos任务转换图)
FreeRTOS(一个用于嵌入式系统的实时操作系统)中任务(Task)的几种状态以及它们之间的转换关系
运行态(运行状态):
这是任务正在 CPU 上执行的状态。在单核处理器上,同一时间只能有一个任务处于运行态。
就绪态(就绪状态):
任务已经准备好运行,但正在等待 CPU 的时间片(时间分配)。一旦 CPU 可用,就绪态的任务就可能变为运行态。
阻塞态(阻塞状态):
- 任务因为等待某个事件(比如等待数据或硬件操作完成)而不能继续执行。当它等待的事件发生时,任务会从阻塞态转换为就绪态,等待 CPU 再次分配时间给它。
挂起态(挂起状态):
- 任务被暂时停止执行。它不会消耗 CPU 时间,也不会被调度器调度。任务可以被“唤醒”(即从挂起态转换为就绪态),以便它能够再次运行。
图中还显示了状态之间的转换是如何通过 API 函数调用实现的:
vTaskSuspend()
和vTaskResume()
函数用于将任务从就绪态转换为挂起态,或从挂起态转换为就绪态。- 调用阻塞 API 函数会将任务从就绪态转换为阻塞态。
- 当任务等待的事件发生时,它会自动从阻塞态转换为就绪态。
7》任务优先级(数字越大。优先级越高)
源码获取
1》free官网 https://www.freertos.org/
2》源码文件夹介绍
1、FreeRTOS和FreeRTOS-Plus是freertos的源码
FreeRTOS文件中
Demo 文件夹中是放的针对不同MCU提供的相关例程
License 文件夹中是放相关的许可信息
Source 文件夹中是放FREERTOS的源码,也就是我们后面打交道的重要资料
include 放头文件
portable 放freertos系统和具体硬件之间的桥梁,只需要留下keil、memmang、RVDS三个文件夹,其余的文件夹都可以删除
keil 使用MDK编译环境所需要文件信息
Menmang 内存管理文件
RVDS针对不同架构的MCU做了详细分类,stm32f407可以参考ARM_CM4F中的内容
移植步骤
1、找一个demo工程,在文件夹下新建一个freertos的文件夹
2、将FreeRTOSv9.0.0\FreeRTOS\Source下的所有文件拷贝到新建freertos文件夹下
3、打开KEIL工程,在工程目录下新建一个freertos文件夹并添加如下文件
demo\freertos下的所有.c文件
demo\freertos\portable\MemMang下的heap_4.c文件
demo\freertos\portable\RVDS\ARM_CM4F下的port.c文件
4、添加freertos需要的头文件路径
5、编译报如下错
..\freertos\include\FreeRTOS.h(98): error: #5: cannot open source input file “FreeRTOSConfig.h”: No such file or directory
到源码\FreeRTOSv9.0.0\FreeRTOS\Demo\CORTEX_M4F_STM32F407ZG-SK下将FreeRTOSConfig.h拷贝到\demo\freertos\include下
6、编译报如下错
..\freertos\portable\RVDS\ARM_CM4F\port.c(713): error: #20: identifier “SystemCoreClock” is undefined
对FreeRTOSConfig.h文件的第87-90行代码进行如下修改
//#ifdef ICCARM
#include <stdint.h>
extern uint32_t SystemCoreClock;
//#endif
7、编译报如下错
.\Objects\demo.axf: Error: L6200E: Symbol PendSV_Handler multiply defined (by port.o and stm32f4xx_it.o).
.\Objects\demo.axf: Error: L6200E: Symbol SVC_Handler multiply defined (by port.o and stm32f4xx_it.o).
.\Objects\demo.axf: Error: L6200E: Symbol SysTick_Handler multiply defined (by port.o and stm32f4xx_it.o).
将stm32f4xx_it.c文件下的PendSV_Handler、SVC_Handler、SysTick_Handler屏蔽
8、编译报如下错
.\Objects\demo.axf: Error: L6218E: Undefined symbol vApplicationIdleHook (referred from tasks.o).
.\Objects\demo.axf: Error: L6218E: Undefined symbol vApplicationStackOverflowHook (referred from tasks.o).
.\Objects\demo.axf: Error: L6218E: Undefined symbol vApplicationTickHook (referred from tasks.o).
.\Objects\demo.axf: Error: L6218E: Undefined symbol vApplicationMallocFailedHook (referred from heap_4.o).
在FreeRTOSConfig.h文件中将vApplicationIdleHook、vApplicationStackOverflowHook,vApplicationTickHook,vApplicationMallocFailedHook这四个的钩子函数功能关闭
9、编译0警告、0错误代表移植成功
“.\Objects\demo.axf” - 0 Error(s), 0 Warning(s).
FreeRTOSConfig.h文件用于对freertos系统的配置文件,可以通过对里面的开关进行修改实现对freertos功能的裁剪
1、动态创建任务
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
参数1: 指向任务函数的入口,任务的函数名
参数2: 字符串,任务的函数名
参数3: 任务堆栈大小,实际分配的大小是需要乘上4
参数4: 需要传递给任务的参数
参数5: 任务优先级,取值范围0~configMAX_PRIORITIES-1
参数6: 任务句柄
返回值:pdPASS:任务创建成功,会返回任务的句柄
错误码参考projdefs.h中的定义
2、启动动任务调度
vTaskStartScheduler();
-————————————————————————————————————
任务的几个关键函数
xTaskCreate()//使用动态的方法创建一个任务(默认)
xTaskCreateStatic()//使用静态的方法创建一个任务
xTaskCreateRestricted();//创建一个使用MPU(内存管理单元)进行限制的任务,相关内存采用动态内存分配
vTaskDelete();//删除一个任务
vTaskStartScheduler();//启动任务调度
vTaskSuspend();//任务挂起
vTaskResume();//恢复一个挂起任务
xTaskResumeFromISR()//在中断中恢复挂起任务
1、动态创建任务
aseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
参数1: 指向任务函数的入口,任务的函数名
参数2: 字符串,任务的函数名
参数3: 任务堆栈大小,实际分配的大小是需要乘上4
参数4: 需要传递给任务的参数
参数5: 任务优先级,取值范围0~configMAX_PRIORITIES-1
参数6: 任务句柄
返回值:pdPASS:任务创建成功,会返回任务的句柄
错误码参考projdefs.h中的定义
2、静态创建任务接口函数
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
StackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
参数1: 指向任务函数的入口,任务的函数名
参数2: 字符串,任务的函数名
参数3: 任务堆栈大小,实际分配的大小是需要乘上4
参数4: 需要传递给任务的参数
参数5: 任务优先级,取值范围0~configMAX_PRIORITIES-1
参数6: 任务堆栈,一般是数组,成员类型需要是StackType_t类型
参数7: 任务控制块,必须要指向类型为StaticTask_t的变量,这个变量用于保存创建任务的数据结构(TCB),因此它必须是持久
返回值:NULL,代表创建失败
其它值,代表成功,任务句柄
注意:使用静态创建任务时需要用户自己实现两个函数vApplicationGetIdleTaskMemory()和vApplicationGetTimerTaskMemory();
通过这个两个函数来给空闲任务和定时器服务任务的任务堆栈和任务控制块分配内存
我们可以在main.c文件的main涵数之前进行定义
static StaticTask_t IdleTaskTCB;
static StackType_t dleTaskStack[configMINIMAL_STACK_SIZE];
static StaticTask_t TimerTaskTCB;
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
void vApplicationGetIdleTaskMemory(StaticTask_t * * ppxIdleTaskTCBBuffer, StackType_t * * ppxIdleTaskStackBuffer, uint32_t * pulIdleTaskStackSize)
{
* ppxIdleTaskTCBBuffer = &IdleTaskTCB;//空闲任务控制块
* ppxIdleTaskStackBuffer = dleTaskStack;//空闲任务的堆栈
* pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;//堆栈大小
}
void vApplicationGetTimerTaskMemory(StaticTask_t * * ppxTimerTaskTCBBuffer, StackType_t * * ppxTimerTaskStackBuffer, uint32_t * pulTimerTaskStackSize)
{
* ppxTimerTaskTCBBuffer = &TimerTaskTCB;//任务控制块
* ppxTimerTaskStackBuffer = TimerTaskStack;//任务堆栈
* pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;//堆栈大小
}
任务怎么暂停和恢复
假如创建3三个任务
任务1
任务2
任务3
执行过程
任务1-》任务2-》任务3
void 任务1()
{
while(1)
{
A();
———>运行到这个地方时,系统进行了一次调度(切换任务)。
B();
}
}
//前面我们有说任务是一段保存在FLASH上的代码(函数),如仅仅只是一段代码,更不不需要报存,但是这段代码一旦运行了,就会产生数据比如局部变量,PC寄存器的值,代码运行的位置。
切任务时需要做的事情
1、保存程序执行的位置
2、保存任务中产生的变量的值
等等
当程序运行时被打断,需要现场保护,也就是前面所说的16个寄存器的值,将他们保存起来,保存在哪里?-》保存在栈里面
堆栈的作用
1》栈(stack)
1、函数的形参,以及函数里面定义的局部变量就是存储在栈里,(我们的局部变量,数组这些不能超过1K),否则程序会进入hardfault
2、实时操作系统的现场保护,返回地址也是保存在栈里
3、栈的增长方向是从高地址到低地址
2》堆(heap)
1、malloc()函数动态分配的内存就是从堆空间分配
3》静态空间区域
全局变量,静态变量是不存在堆里的,堆以外的静态空间区域
4》栈的大小怎么分
—取决于局部变量的大小和调用的深度,只能估算,没有刚好的确定值(由大到小去调节)
5》栈从哪里分配
—-heap4.c中有一个很大的数组,从这个数组中划分出去各个任务的栈
任务调度的机制
1》在创建任务时做了以下事情
1、分配栈空间
2、将函数地址给PC,也就是R15
3、将参数给到R0
4、分配优先级
5、分配了TCB结构体
任务切换时需要将R0-R15寄存器的值保存到栈里,再次运行时,需要从栈里面恢复R0-R15寄存器的值
2》优先级
1、高优先级的任务可以抢占低优先级任务
2、高优先级的任务不主动放弃CPU资源,低优先级的任务永远无法运行
3、同等优先级的任务按时间片轮询依次执行
4、空闲任务礼让其他同级别的任务—空闲任务主动放弃一次运行机会
3》怎么去管理任务
1、找到最高优先级的任务运行
2、优先级相同轮流执行,排队,排在就绪列表前的先执行,运行一个tick后,让出CPU的使用权,去链表尾部排队
4》高优先级任务怎么主动释放CPU使用权
用vTaskDelay()函数可以释放CPU使用权
5》空闲任务
1、主要起清理作用,比如一个任务自杀了,由空闲任务来释放任务的栈空间
2、当创建的任务优先级都为0时,最先运行的是空闲任务,因为空闲任务是在启动任务调度器时才创建
启动文件
Stack_Size EQU 0x00000400//分配了一个栈空间
AREA STACK, NOINIT, READWRITE, ALIGN=3//定义一个段,代码节或数据节,说明定义段的相关属性
Stack_Mem SPACE Stack_Size
//SPACE(申请一段空间)用于分配大小等于Stack_Size连续内存空间,单位为字节//类似MALLOC
__initial_sp //表示这块区域的高地址指向栈顶 “先进后出”
STACK 表示这块区域的名称没有限制写啥都行
NOINIT 表示这块区域不需要初始化
READWRITE 表示这块区域可读可写,可读写(内存),ROM是指读区域
ALIGN=3 表示按照2^3(8)字节对齐
启动文件中的堆栈空间是用来管理裸机开发时有用,freertos中的堆栈是由heap4.c管理
6》在freertos中,最低优先级的中断也比最好优先级的任务先运行,中断永远都是先执行
任务之间的通信
1、消息队列
2、共享内存
3、信号量
4、二值信号量