1、freeRTOS
1.1 什么是FreeRTOS
Free就是免费的,RTOS全称是real time operating system,即实时操作系统。FreeRTOS是一个迷你的实时操作系统内核,作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可满足较小系统的需要。
严格上来说,FreeRTOS并不是一个实时操作系统,因为它是分时复用的,系统将时间分割成很多时间片,然后轮流执行各个任务。每个任务都是独立运行的,互不影响,由于切块的频率很快,感觉就像是同时运行的一样。
1.2 为什么选择FreeRTOS
第一是其是免费的,源码公开,具有可移植。可裁剪。调度灵活等特点,可以方便地移植到各种单片机上运行;其二是很多半导体厂商产品的SDk(Software Development Kit)软件开发工具包,就是使用FreeRTOS作为其操作系统,尤其是WIFI、蓝牙这些带有协议栈的芯片或模块;其三是因为FreeRTOS的文件数量很少,移植简单。
1.3 FreeRTOS资料与源码下载
最好的地方就是官网:FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions
2、堆/栈
2.1 堆
堆就是一块空闲的内存,我们可以管理这块内存,从这块内存中取出一部分使用,使用完再释放回去。
//heap_buf就是我们开辟的堆
char heap_buf[1024];
int pos = 0;
void *my_malloc(int size)
{
int old_pos = pos;
pos += size;
return &heap_buf[old_pos];
}
void my_free(void *buf)
{
/* err */
}
int main(void)
{
char ch = 65; // char ch = 'A';
int i;
char *buf = my_malloc(100);
unsigned char uch = 200;
for (i = 0; i < 26; i++)
buf[i] = 'A' + i;
return 0;
}
2.2 栈
在编写代码中,我们可能感觉不到栈的存在,但是它确实一直在默默存在。
void c_fun(void)
{
}
void b_fun(void)
{
}
int a_fun(int val)
{
int a = 8;
a += val;
b_fun();
c_fun();
return a;
}
int main(void)
{
a_fun(46);
return 0;
}
在上面的程序中,main函数调用了a_fun,在a_fun中调用了b_fun和c_fun。main函数在调用a_fun之前,会将返回的地址保存在LR(Link Register)中,然后调用a_fun,在main中返回的地址就是return 0语句所在的位置。
a_fun函数在调用b_fun和c_fun时也是类似的,在调用之前要先将返回的地址保存在LR(Link Register)中,然后去调用,而在a_fun中调用b_fun的返回地址是c_fun所在的语句。那么此时main和a_fun函数的返回值的地址都保存在LR中,LR会不会被覆盖了?
在a_fun函数内部,它会将LR的值存入栈中,然后再来保存本身的返回值地址,这样就不怕LR会被覆盖了,因此函数返回值的地址是被存入栈中的。栈也是一块空闲的内存。在main函数执行时,它会给main函数开辟一部分的栈空间,里面存放LR等寄存器和局部变量的值。
2.3 堆和栈的区别
管理方式不同:栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏。
分配方式不同:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。
空间大小不同:栈的大小要远远小于堆的大小。
生长方向不同:堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
数据结构不同:堆可以看成一棵树,如堆排序;栈是一种先进后出的结构。
作用域的不同:堆的作用域是在全局,栈的作用域是在函数局部。
3、从官方源码精简出第一个freeRTOS
3.1 从官网下载
FreeRTOS - Free RTOS Source Code Downloads, the official FreeRTOS zip file release download
解压之后打开的目录
FreeRTOS-Plus里面是FreeRTOS生态的文件,非必需,tools里面是亚马逊相关的文件,也不需要。文件夹只需要保留FreeRTOS。
进入FreeRTOS-Demo文件夹,选择我们的目标文件夹,例如CORTEX_STM32F103_Keil,其余的就可以删掉了,在Demo中的文件夹的命名规则是指令集+芯片+编译器或者芯片+编译器。
在FreeRTOS-Source文件夹中,里面的目录结构如下所示,在portable文件夹中,我们保留RVDS和MemMang,这里的命名规则是编译器/架构,在RVDS文件夹中,我们可以只保存ARM_CM3。
至此,从官网下载的文件中,我们暂时不需要的文件都已经清除,文件大小从455MB降到了8.28MB。
3.2 打开工程
这个工程是用Keil4来编写的,我们要将其更新成Keil5
点击 Migrate to Device Pack 即可,更新完之后关掉再重新打开。
打开之后会出现一些错误,然后根据提示更改。嫌麻烦可以将FreeRTOS-Demo中的Common文件夹保留,就不会报错了。
3.3 添加串口打印功能
3.3.1 去掉无关的代码
System中的lcd.c,Demo files中的ParTest.c timertest.c spi_test.c semtest.c BlockQ.c blocktim.c comtest.c death.c flash.c integer.c PollQ.c
然后编译,根据错误提示,来进行修改。
3.3.2 添加串口打印功能
第一步要初始化串口
第二步要实现fputc功能,因为printf函数是标准库写好的,里面用到了fputc函数,我们要用串口将其打印出来就要对其进行改写。
3.3.3 使用Keil自带模拟器运行
点击魔法棒-Debug,勾选Use Simulator
然后点击debug,运行查看结果。
4、自己的第一个freeRTOS程序
4.1 创建两个打印任务
//参数:
//函数指针,任务函数
//任务的名字
//栈大小,单位为word,10代表40字节
//调用任务函数时传入的参数
//优先级
//任务句柄,以后使用它来操作这个函数
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const configSTACK_DEPTH_TYPE usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
//关键代码
void Task1Function( void * param)
{
while(1){
printf("1");
}
}
void Task2Function( void * param)
{
while(1){
printf("2");
}
}
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL); //这样写也可以
该程序运行结果会发现1和2交替打印。
既可以仿真查看结果,也可以上传到开发板查看结果。
在开发板上直接调试,要保证开发板上电,开发板要连接调试器,并且在Keil中要选择真实的调试器。
如果在keil中无法在源文件中设置断点,要确保调试器接好,并且将(魔术棒-Debug-调试器Settings-Debug)Debug中的“Verify Code Download”和“Download to flash”去掉勾选。
如果要在keil中重新使用模拟器运行代码,要在Device中选择模拟的设备,Debug中选择“Use Simulator”,指定DLL:DARMSTM.DLL,指定参数:-STM32Fxxxx
4.2 freeRTOS源码结构
以Keil工具下STM32F103芯片为例,它的FreeRTOS的目录如下:
主要涉及2个目录:
Demo:
Demo目录下是工程文件,以"芯片和编译器"组合成一个名字,比如:CORTEX_STM32F103_Keil
Source:
根目录下是核心文件,这些文件是通用的
portable目录下是移植时需要实现的文件
目录名为:[compiler]/[architecture]
比如:RVDS/ARM_CM3,这表示cortexM3架构在RVDS工具上的移植文件
4.2.1 核心文件
freeRTOS的最核心文件只有两个:
FreeRTOS/Source/task.c
FreeRTOS/Source/list.c
其它文件的作用:
FreeRTOS/Source/下的文件 | 作用 |
task.c | 必需,任务操作 |
list.c | 必需,列表操作 |
queue.c | 基本必需,提供队列操作、信号量(semaphore)操作 |
timer.c | 可选,software timer |
event_groups.c | 可选,提供event group功能 |
croutine.c | 可选,过时了 |
4.2.2 移植时涉及的文件
移植FreeRTOS时涉及的文件放在FreeRToS/Source/portable/[compiler]/[architecture]目录下,比如:RVDS/ARM_CM3,这表示cortexM3架构在RVDS或Keil工具上的移植文件。
里面有两个文件:port.c和portmacro.h
4.2.3 头文件相关
FreeRTOS需要3个头文件目录:
FreeRTOS本身的头文件:FreeRTOS/Source/linclude
移植时用到的头文件:FreeRTOS/Source/portable/[compiler]/[architecture]
含有配置文件FreeRTOSConfig.h的目录(Demo/目标文件夹)
头文件列表:
头文件 | 作用 |
FreeRTOSConfig.h |
FreeRTOS
的配置文件,比如选择调度算法:
configUSE_PREEMPTION
每个
demo
都必定含有
FreeRTOSConfig.h
建议去修改
demo
中的
FreeRTOSConfig.h
,而不是从头写一个
|
FreeRTOS.h |
使用
FreeRTOS API
函数时,必须包含此文件。
在
FreeRTOS.h
之后,再去包含其他头文件,比如:
task.h
、
queue.h
、
semphr.h
、
event_group.h
|
4.2.4 内存管理
文件在FreeRTOS/Source/portable/MemMang下,它也是放在portable目录下,表示你可以提供自己的函数。
源码中默认提供了5个文件,对应内存管理的5种方法:
文件 | 优点 | 缺点 |
heap_1.c
|
分配简单,时间确定
|
只分配、不回收
|
heap_2.c
|
动态分配、最佳匹配
|
碎片、时间不定
|
heap_3.c
|
调用标准库函数
|
速度慢、时间不定
|
heap_4.c
|
相邻空闲内存可合并
|
可解决碎片问题、时间不定
|
heap_5.c
|
在
heap_4
基础上支持分隔的内存块
|
可解决碎片问题、时间不定
|
4.2.5 Demo
Demo目录下是预先配置好的、没有编译错误的工程。目的是让你可以基于它进行修改,以适配你的单板。
这些Demo还可以继续精简:
Demo/ common中的文件可以完全删除
main函数中只需要保留2个函数:
prvSetupHardware()
vTaskStartScheduler()
如下图所示:
4.3 freeRTOS数据类型和编程规范
4.3.1 数据类型
每个移植的版本都含有自己的portmacro.h头文件,里面定义了2个数据类型:
TickType_t:
FreeRTOS配置了一个周期性的时钟中断:Tick Interrupt
每发生一次中断,中断次数累加,这被称为tick counto
tick count这个变量的类型就是TickType_t
TickType_t可以是16位的,也可以是32位的
FreeRTOSConfig.h中定义configUSE_16_BIT_TICKS时,TickType_t就是uint16_t
否则TickType_t就是uint32_t
对于32位架构,建议把TickType_t配置为uint32_t
BaseType_t:
这是该架构最高效的数据类型
32位架构中,它就是uint32_t
16位架构中,它就是uint16_t
8位架构中,它就是uint8_t
BaseType_t通常用作简单的返回值的类型,还有逻辑值,比如pdTRUE/pdFALSE
4.3.2 变量名
在FreeRTOS的源码中,其变量名通常带有小写字母的前缀:
变量名前缀 |
含义
|
c |
char
|
s |
int16_t
,
short
|
l |
int32_t
,
long
|
x |
BaseType_t
,
其他非标准的类型:结构体、
task handle
、
queue handle
等
|
u |
unsigned
|
p |
指针
|
uc |
uint8_t
,
unsigned char
|
pc |
char
指针
|
4.3.3 函数名
在FreeRTOS的源码中,函数名的前缀有2部分:返回值类型、在哪个文件定义。
函数名前缀
|
含义
|
vTaskPrioritySet
|
返回值类型:
void
在
task.c
中定义
|
xQueueReceive
|
返回值类型:
BaseType_t
在
queue.c
中定义
|
pvTimerGetTimerID
|
返回值类型:
pointer to void
在
tmer.c
中定义
|
4.3.2 宏的名
在FreeRTOS的源码中,宏的名字是大小,可以添加小写的前缀。前缀是用来表示:宏在哪个文件中定义。
宏的前缀
|
含义:在哪个文件里定义
|
port (
比如
portMAX_DELAY)
|
portable.h
或
portmacro.h
|
task (
比如
taskENTER_CRITICAL())
|
task.h
|
pd (
比如
pdTRUE)
|
projdefs.h
|
config (
比如
configUSE_PREEMPTION)
|
FreeRTOSConfig.h
|
err (
比如
errQUEUE_FULL)
|
projdefs.h
|
通用的宏定义:
宏 | 值 |
pdTRUE
| 1 |
pdFALSE
| 0 |
pdPASS
| 1 |
pdFAIL
| 0 |