一 RTOS操作系统概述
RTOS: Real Time OS, 就是实时操作系统
强调实时性
实时操作系统分为软实时和硬实时
实时操作系统最大的特色就是其“实时性”。也就是说,如果有任务需要执行,实时操作系统会立即
(在较短时间内)执行该任务,保证了任务在指定时间内完成。
实时操作系统根据任务执行的实时性,分为“硬实时”操作系统和“软实时”操作系统,
“硬实时”操作系统比“软实时”操作系统响应更快、实时性更高,“硬实时”操作系统大多应用于工业领域。
“硬实时”操作系统必须使任务在确定的时间内完成。
“软实时”操作系统能让绝大多数任务在确定时间内完成。
常见的RTOS系统有: FreeRTOS, UCOS, RTX, RT-Tread 等
FreeRTOS 是可剥夺型内核,剥夺其他进程对CPU的使用权,它总是运行在
就绪任务重的优先级最高的任务。
也就是说,高优先级的任务不执行完,低优先级的任务是没有机会执行的
任务调度原则:
高优先级的进程可以打断低优先级进程的执行
中断可以打断任务的执行,
但是中断处理程序并没有返回到被打断的点,而是返回到了更高优先级任务的位置
记住:Free-RTOS执行的永远是就绪态的任务队列中,优先级最高的那个任务!
FreeRTOS 是一个可剪裁的,可剥夺型的多任务内核,而且没有任务数的限制。
FreeRTOS提供了实时操作系统所需的所有的功能,包括资源管理,同步,任务通信等。
为什么企业中使用FreeRTOS?
免费、开源、支持多种第三方组件!
FreeRTOS 是一个免费的实时嵌入式操作系统
免费开源: 商业产品中使用,无潜在的风险,无需担心
可裁剪: FreeRTOS的核心代码9000+行,包含在3个.c文件中
简单:简单易用,可移植性非常好
优先级不限:任务优先级分配没有限制,多任务可以同一优先级
任务不限:可以创建的实时任务数量没有软件限制
支持抢占/协程/任务调度
重点:
FreeRTOS的官方: http://www.freertos.org
开源电子网:www.openedv.com
FreeRTOS的基础知识 了解任务调度器,以及RTOS的特性, 打好基础
FreeRTOS的内核 任务启动流程,中断管理,任务切换
FreeRTOS各项功能 列表、队列、信号量、内存管理
二 命名惯例
RTOS 内核和演示应用程序源代码使用以下惯例:
变量
变量名称使用驼峰式大小写,具有明确的描述性,并使用完整的单词(没有缩写,但普遍接受的缩写除外)。
uint32_t 类型变量以 ul 为前缀,其中“u”表示“unsigned” ,“l”表示“long”。
uint16_t 类型变量以 us 为前缀,其中“u”表示“unsigned” , “s”表示“short”。
uint8_t 类型变量以 uc 为前缀,其中“u”表示“unsigned” , “c”表示“char ”。
非 stdint 类型的变量以 x 为前缀。
例如,BaseType_t 和 TickType_t,二者分别是可移植层定义的定义类型,主要架构的自然类型或最有效类型,以及用于保存 RTOS ticks 计数的类型。
非 stdint 类型的未签名变量存在附加前缀 u。
例如,UBaseType_t(未签名 BaseType_t)类型变量以 ux 为前缀。
size_t 类型变量也带有 x 前缀。
枚举变量以 e 为前缀
指针以附加 p 为前缀,例如,指向 uint16_t 的指针将以 pus 为前缀。
根据 MISRA 指南,未限定标准 char 类型仅可包含 ASCII 字符,并以 c 为前缀。
根据 MISRA 指南,char * 类型变量仅可包含指向 ASCII 字符串的指针,并以 pc 为前缀。
函数
函数名称使用驼峰式大小写,具有明确的描述性,并使用完整的单词(无缩写,但普遍接受的缩写除外)。
文件作用域静态(私有)函数以 prv 为前缀。
根据变量定义的相关规定,API 函数以其返回类型为前缀,并为 void 添加前缀 v。
API 函数名称以定义 API 函数文件的名称开头。例如,在 tasks.c 中定义 vTaskDelete,并且具有 void 返回类型。
宏
宏具有明确的描述性,并使用完整的单词(无缩写,但普遍接受的缩写除外)。
宏以定义宏的文件为前缀。前缀为小写。例如,在 FreeRTOSConfig.h 中定义 configUSE_PREEMPTION。
除前缀外,所有宏均使用大写字母书写,并使用下划线来分隔单词。
三 FreeRTOS源码初探
从官网上下载FreeRTOS的源码
FreeRTOS下面有4个目录:
1 Demo文件夹
FreeRTOS的相关例程 移植的时候可以进行参考
2 License文件夹
相关许可
3 Source文件夹
FreeRTOS的相关源码
FreeRTOS:
Source --> include 包含了头文件,移植的时候需要
--> 源文件 移植的时候需要
--> protable FreeRTOS与不同硬件直接连接的桥梁
--> Keil 转向RVDS 使用MDK集成开发环境所需要的文件
--> ARM_CM3
--> port.c portmacro.h
--> MemMang 内存管理相关文件,移植需要
FreeRTOS-Plus:
源码其实并不是 FreeRTOS 系统的源码,而是在 FreeRTOS
系统上另外增加的一些功能代码,比如 CLI、FAT、Trace 等等
四 FreeRTOS基础知识
1 任务调度器
调度器就是使用相关的调度算法,来决定当前需要执行哪个任务
FreeRTOS 一共支持三种任务调度方式
抢占式调度
主要是针对优先级不同的任务,每个任务都有一个优先级,优先级高的任务可以抢占
优先级低的任务
抢占式总结:
1 高优先级任务,优先执行
2 高优先级任务不停止,低优先级任务无法执行
时间片调度
主要是针对优先级相同的任务,当多个任务的优先级相同时,任务调度器会在每一次系统时钟节拍
到的时候切换任务
同等优先级任务轮流的享有相同的CPU,叫时间片,在FreeRTOS中,
一个时间片就等于SysTick中断周期(一般就是1ms)
eg:
运行条件:
1 创建三个任务 task1 task2 task3
2 task1、task2、task3的优先级均为1;即3个任务的优先级一致
运行过程如下
1 首先task1运行完一个时钟周期,切换至task2运行
2 task2运行完一个时间片之后,切换至task3运行
3 task3运行过程中(没有运行完一个时间片), task3阻塞了(系统延时或者等待信号量)此时直接切换到task1运行
4 task1运行完一个时间片后,切换至task2运行
注意:
1 同等优先级任务,轮流执行,时间片轮转
2 一个时间片大小,取决为滴答定时器中断周期
3 注意没有完全用完的时间片不会再使用,下次任务task3得到执行还是按照一个时间片
的时钟节拍运行
协程式调度(了解)
当前执行任务将会一直运行,同时高优先级的任务不会抢占低优先级任务
FreeRTOS现在虽然还支持,但是官方已经表示不会再更新协程式调度
2 任务状态
a 运行态 正在运行的任务,该任务就是运行态,注意,在STM32中,同一时间仅一个任务处于运行态
b 就绪态 如果该任务已经能够被执行,但是尚未被执行,那么该任务处于就绪态
c 阻塞态 如果一个任务因为延时或者等待外部事件发生,那么这个任务就处于阻塞态
d 挂起态 类似暂停,调用vTaskSuspend()进入挂起状态,需要调用vTaskResume()才可以进入就绪态
总结:
1 仅就绪态可以进入运行态
2 其他状态的任务想要运行,必须先进入就绪态
这四种状态中,除了运行态,其他三种任务状态的任务都有其对应的任务状态列表
就绪列表 pxReadyTasksLists[x], 其中x代表任务优先级数值 //用硬件来表示优先级的话,x的取值为0~31
阻塞列表 pxDelayedTaskList
挂起列表 xSuspendedTasklist
将来从就绪列表中挑选一个优先级最高的任务进行执行!
通过一个32b的变量,当某一位置1时,代表所对应的优先级列表中就有任务。
加入创建了三个任务:Task1 Task2 Task3, 他们分别对应的优先级是 31,30,29
他们三个任务会被分别放到 pxReadyTaskLists[31], pxReadyTaskLists[30],pxReadyTaskLists[29]
结合变量中位置为1的位置,cpu就知道先去执行哪个列表中的任务了
eg:
调度器总是在所有就绪列表的任务中,选择具有最高优先级的任务来执行
五 FreeRTOS在STM32F103上的移植
5.0 先将01test拷贝一份, 放到porting目录下面,
添加led.c led.h 编译运行,保证led0, led1, led2 是正常可以点亮的
为了确保在移植的过程中某一步出错,导致移植失败
5.1 向工程中添加相应的文件
a) 添加FreeRTOS源码
在01test中新建一个FreeRTOS的目录,将来FreeRTOS的相关的文件放到这里面来
将从官网下的FreeRTOS的源码中的include目录拷贝到FreeRTOS目录下面来
将从官网下的FreeRTOS的源码中的源文件拷贝到FreeRTOS目录下面来
将从官网下的FreeRTOS的源码中的protable目录拷贝到FreeRTOS目录下面来
将拷贝过来的protable 目录中的 Keil、MemMang、RVDS三个目录保留下来,其余的删除就可以了
打开项目工程,右键Target,点击 add Group ... --> 右键 New Group -->
点击"Manage project items" ---> 将"New Group"的名字改为"FreeRTOS_protable"
双击 FreeRTOS_protable 将 官方源码中的port.c 和 heap_4.c 添加到项目工程中来
打开项目工程,右键Target,点击 add Group ... --> 右键 New Group -->
点击"Manage project items" ---> 将"New Group"的名字改为"FreeRTOS_core"
双击 FreeRTOS_core 将 官方源码中的源文件添加到项目工程中来
5.2 添加相应的头文件路径
分别右键 FreeRTOS_protable 和 FreeRTOS_core
点击"Options for group..." --> 点击Include Paths --> 新增两条路径
--》 ..\FreeRTOS\include;
..\FreeRTOS\portable\RVDS\ARM_CM3
包含完头文件之后,编译会发现报 --》 找不到FreeRTOSConfig.h 头文件
去FreeRTOS官方移植的Demo中找到这个头文件:
FreeRTOSv202212.01\FreeRTOS\Demo\CORTEX_STM32F103_Keil 有该文件
将其放到FreeRTOS源码的include目录下, 但是建议使用模板工程中的FreeRTOSConfig.h
到这里我们再编译一次,没有错误!
5.3 添加相关的文件
a) 添加sys.h (模板工程中的sys.h) 文件拷贝到mylib中
在sys.h中添加:
//定义该宏表示支持操作系统
#define SYSTEM_SUPPORT_OS 1
其他位置不需要修改
b) 添加delay.c 和delay.h 到mylib中,并将delay.c 添加到项目工程中
vi delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "sys.h"
void delay_init(void);
void delay_ms(u32 nms);
void delay_us(u32 nus);
void delay_xms(u32 nms);
#endif
vi delay.c
#include "delay.h"
#include "FreeRTOS.h"
#include "task.h"
static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数
extern void xPortSysTickHandler(void);
//systick中断服务函数
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
xPortSysTickHandler();
}
}
//初始化延迟函数
//SYSTICK的时钟固定为AHB时钟,基础例程里面SYSTICK时钟频率为AHB/8
//这里为了兼容FreeRTOS,所以将SYSTICK的时钟频率改为AHB的频率 及系统的时钟频率为72MHz
//SYSCLK:系统时钟频率
void delay_init()
{
u32 reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);//选择外部时钟 HCLK
fac_us = SystemCoreClock / 1000000; //不论是否使用OS,fac_us都需要使用
reload = SystemCoreClock / 1000000; //每秒钟的计数次数 单位为M
reload *= 1000000 / configTICK_RATE_HZ; //根据configTICK_RATE_HZ设定溢出时间
//reload为24位寄存器,最大值:16777216,在72M下,约合0.233s左右
fac_ms = 1000 / configTICK_RATE_HZ; //代表OS可以延时的最少单位
SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD=reload; //每1/configTICK_RATE_HZ秒中断一次
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
}
//延时nus
//nus:要延时的us数.
//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)
void delay_us(u32 nus)
{
u32 ticks;
u32 told,tnow,tcnt = 0;
u32 reload = SysTick->LOAD; //LOAD的值
ticks = nus*fac_us; //需要的节拍数
told = SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow = SysTick->VAL;
if(tnow != told)
{
if(tnow < told) {
tcnt += told - tnow;
} //这里注意一下SYSTICK是一个递减的计数器就可以了.
else {
tcnt += reload - tnow + told;
}
told = tnow;
//时间超过/等于要延迟的时间,则退出.
if(tcnt >= ticks) {
break;
}
}
};
}
//延时nms
//nms:要延时的ms数
//nms:0~65535
void delay_ms(u32 nms)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)//系统已经运行
{
if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
{
vTaskDelay(nms / fac_ms); //FreeRTOS延时, FreeRTOS的最小的延时是1ms
}
nms % =fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
}
delay_us((u32)(nms*1000)); //普通方式延时
}
//延时nms,不会引起任务调度
//nms:要延时的ms数
void delay_xms(u32 nms)
{
u32 i;
for(i=0;i<nms;i++) delay_us(1000);
}
c) 增加头文件的索引路径:
此时再重新编译, 会报如下的错误:
在stm32f10x_it.c 中, 将上面的三个函数屏蔽掉即可
d) 添加misc.c
双击fwliib将misc.c 添加到项目工程中来即可
编译通过! FreeRTOS的基本移植就完成了
六 移植验证实验
vi main.c
#include "led.h"
#include "delay.h"
#include "FreeRTOS.h"
#include "task.h"
//任务优先级
#define START_TASK_PRIO 1
//任务使用的栈的大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED0_TASK_PRIO 2
//任务使用的栈的大小
#define LED0_STK_SIZE 256
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 3
//任务使用的栈的大小
#define LED1_STK_SIZE 256
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define LED2_TASK_PRIO 4
//任务使用的栈的大小
#define LED2_STK_SIZE 256
//任务句柄
TaskHandle_t LED2Task_Handler;
//任务函数
void led2_task(void *pvParameters);
int main(void)
{
led_init ();
delay_init ();
//创建开始任务
xTaskCreate(start_task, "start_task", START_STK_SIZE, NULL,
START_TASK_PRIO, &StartTask_Handler);
//开启任务调度
vTaskStartScheduler();
}
//开始任务任务函数
void start_task(void *pvParameters)
{
//进入临界区
taskENTER_CRITICAL();
//创建led0任务
xTaskCreate(led0_task, "led0_task", LED0_STK_SIZE, NULL, LED0_TASK_PRIO, &LED0Task_Handler);
//创建led1任务
xTaskCreate(led1_task, "led1_task", LED1_STK_SIZE, NULL, LED1_TASK_PRIO, &LED1Task_Handler);
//创建led2任务
xTaskCreate(led2_task, "led2_task", LED2_STK_SIZE, NULL, LED2_TASK_PRIO, &LED2Task_Handler);
//删除开始任务
vTaskDelete(StartTask_Handler);
//退出临界区
taskEXIT_CRITICAL();
}
//LED0任务函数
void led0_task(void *pvParameters)
{
while(1)
{
led_on (0);
vTaskDelay(300);
led_off (0);
vTaskDelay (300);
}
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
led_on (1);
vTaskDelay(300);
led_off (1);
vTaskDelay (600);
}
}
//led2任务函数
void led2_task(void *pvParameters)
{
while(1)
{
led_on (2);
vTaskDelay(300);
led_off (2);
vTaskDelay(900);
}
}
七 移植总结:
FreeRTOSConfig.h 文件
“INCLUDE_”开头的宏用来表示使能或禁止 FreeRTOS 中相应的 API 函数,作用就是用来配置 FreeRTOS 中的可选 API 函数的。
比如当宏 INCLUDE_vTaskPrioritySet 设置为 0 的时候 表示不能 使用函数 vTaskPrioritySet() ,
当设置 为 1 的时 候就表示可 以使用函 数vTaskPrioritySet()。这个功能其实就是条件编译
“config”开始的宏, “config”开始的宏和“INCLUDE_”开始的宏一样,
都是用来完成 FreeRTOS 的配置和裁剪的
八 中断管理
Cortex-M 处理器有多个用于管理中断和异常的可编程寄存器,这些寄存器大多数都在
NVIC 和系统控制块(SCB)中,CMSIS 将这些寄存器定义为结构体
core_cm3.h 中:
NVIC的内存映射结构:
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register */
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register */
} NVIC_Type;
系统控制块:
typedef struct
{
__I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
__IO uint32_t ICSR; /*!< Offset: 0x04 Interrupt Control State Register */
__IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
__IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
__IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
__IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
__IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15) */
__IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
__IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
__IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
__IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
__IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
__IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
__IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
__I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */
__I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
__I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
__I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
__I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
} SCB_Type;
NVIC 和 SCB 都位于系统控制空间(SCS)内,SCS 的地址从 0XE000E000 开始
#define SCS_BASE (0xE000E000)
#define NVIC_BASE (SCS_BASE + 0x0100)
#define SCB_BASE (SCS_BASE + 0x0D00)
将来需要重点关心primask, basepri, Faultmask 这三个寄存器即可
primask 寄存器是中断使能寄存器
priimask 用于禁止除 NMI 和 HardFalut 外的所有异常和中断
可以使用:
CPSIE I; //清除 PRIMASK(使能中断)
CPSID I; //设置 PRIMASK(禁止中断)
还可以通过 MRS 和 MSR 指令访问:
MOVS R0, #1
MSR PRIMASK, R0 ;//将 1 写入 PRIMASK 禁止所有中断
MOVS R0, #0
MSR PRIMASK, R0 ;//将 0 写入 PRIMASK 以使能中断
faultmask 可以连 HardFault 都屏蔽掉, 使用方法与primask类似
CPSIE F ;清除 FAULTMASK
CPSID F ;设置 FAULTMASK
还可以利用 MRS 和 MSR 指令访问 FAULTMASK 寄存器
MOVS R0, #1
MSR FAULTMASK, R0 ;将 1 写入 FAULTMASK 禁止所有中断
MOVS R0, #0
MSR FAULTMASK, R0 ;将 0 写入 FAULTMASK 使能中断
在 BASEPRI 寄存器中,不过如果向 BASEPRI 写 0 的话就会停止屏蔽中断。
比如,我们要屏蔽优先级不高于 0X60 的中断,则可以使用如下汇编编程:
MOV R0, #0X60
MSR basepri, R0
如果需要取消 BASEPRI 对中断的屏蔽, 可以使用如下代码:
MOV R0, #0
MSR BASEPRI, R0
STM32 中使用了寄存器中的4位表示优先级, 最多能表示16个优先级
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
FreeRTOS 中断配置宏:
configPRIO_BITS
此宏用来设置 MCU 使用几位优先级,STM32 使用的是 4 位,因此此宏为 4
configLIBRARY_LOWEST_INTERRUPT_PRIORITY
优先级数就是 16 个,最低优先级那就是 15
configKERNEL_INTERRUPT_PRIORIT
内核中断优先级
#define configKERNEL_INTERRUPT_PRIORITY
( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
宏 configKERNEL_INTERRUPT_PRIORITY 为,
宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY 左移 8-configPRIO_BITS 位,也就是左移 4位。
为什么要左移 4 位呢?前面我们说了,STM32 使用了 4 位作为优先级,而这 4 位是高 4 位,
因此要左移4位才是真正的优先级 。