FreeRTOS快速入门课程学习笔记
文章目录
前言
本文是基于韦东山老师的FreeRTOS的课程笔记,持续更新中。。。。
提示:以下是本篇文章正文内容,下面案例可供参考
一、_单片机_RTOS_架构的概念
1.RTOS有什么用
妈妈在喂饭的时候,同事感觉没人理我,当妈妈在回复同事的时候,小孩感觉没人理我
以上就是裸机开发的流程,执行完一个任务才能去执行另一个任务
接下来是基于RTOS的开发
任务之间穿插执行,只要速度够快剧看不出来有短暂延迟,近似实现了同时执行两个任务
优先级高的事件先处理,优先级相同的事件交叉处理
2.深入理解CPU架构
二、嵌入式基本数据结构
1.堆的概念
所谓堆就是一块空闲的内存,你也可以来管理这块内存,从这块内存中来取出一部分,用完之后再把它释放回去。
就比如说我们举一个例子
char heap_buf[1024];
int pos=0;
void *my_malloc(int size)
{
int old_pos=pos;
pos+=size;
return &heap_buf[old_pos];
}
我们说堆就是一块空闲的内存,那么我们可以定义一个数组heap_buf[1024],它就是一块空闲的内存,我只要在他上面来实现内存的分配和释放,那么它就是一个堆。
我们来实现一个比较简单的函数*my_malloc(int size),分配了size大小的空间,返回这个空间的首地址
当我们在这块空闲的内存是实现malloc函数的时候,这块空闲的内存就被称为堆,我们使用一个整数来表示空闲内存的位置,使用这种最简单的方法的话,我们没用办法实现free函数,在这里我们先写出一个free函数
void my_free(void *buf)
{
/*err*/
}
假装我们实现的free函数可以使用了
int main (void)
{
char ch=65; //char ch ='A';
int i;
char *buf=*my_malloc(100);
unsigned char uch=200;
for(i=o;i<26;i++)
buf[i]= 'A'+i;
}
我们可以先调试一下,先编译,我们需要设置一下,点击debug这里使用模拟器,然后点击调试
先打断点,可以看到,在执行my_malloc(int size)之前,buf是空的。
进入my_malloc(int size)一路执行
它会返回这个buf中的某一个地址
这个数组的地址在这里
我们可以看到这个heap_buf的地址是0x20000004,当我们第一次分配的时候肯定是返回他的首地址,我们现在可以继续执行,多执行几次,我们来看看这个buf的内容
看到没有,这里面已经添加了好几个数据
2.栈的概念
对于栈它是一个幕后英雄,在我们编写程序的时候,你感受不到栈的存在,我们写一个简单的程序
void c_fun(void)
{
}
void b_fun(void)
{
}
void a_fun(int val)
{
int a=8;
a+=val;
b_fun();
c_fun();
return a;
}
int main (void)
{
char ch=65; //char ch ='A';
int i;
char *buf=*my_malloc(100);
unsigned char uch=200;
for(i=o;i<26;i++)
buf[i]= 'A'+i;
a_fun(46);
可以看到main函数调用a函数,a函数调用b函数和c函数,在整个过程中我们都没有感觉到栈的存在,但是我来问几个问题:
1.返回地址保存在哪里?
main函数怎么调用到函数a,我得记录一下函数a的返回地址。所以说第一步它吧返回地址保存在某个寄存器里面LR(LinkRegister),后面我们讲到ARM架构时,你会知道这些寄存器的,可以用来保存一些词,你看,main函数调用函数a之前,它会先把这条语句的地址保存起来,LR等于(return 0)这句话的地址,然后调用函数a;同理,函数a要调用函数b,它也要先把c_fun()这个返回地址保存下来,LR等于这句话的地址,然后调用函数b_fun().
那么问题来了,LR会被覆盖吗?
在这个场景里面,如果你不继续做处理的话,LR寄存器里面值肯定会被覆盖,那我们怎么处理呢,简单!!
在函数a内部它要把LR的值保存起来,存入栈中,我把LR的值也就是语句1的地址保存在栈中,后面我再来调用函数b的时候,我就不怕这个寄存器被覆盖了;同样道理函数b也会在函数b的内部把返回地址保存在栈中,到这里,第一个问题就解决了,返回地址保存在哪里?保存在栈里
什么是栈,栈就是一块空闲的内存,就比如说打开我们的代码
在调用main函数之前,它要使用汇编代码设置这个栈,SP就是栈寄存器,让他指向某一块空闲的内存,这样就可以了,以后你就可以调用c函数了,整个使用栈的过程是怎样的呢,我们来演示一下。
我们刚才已经总结了一个c函数要做什么事情,在c函数的入口,也就是开始前面那一部分代码
,它会做什么事情呢,我们先把要做的事情给完善一下。c函数开头,你看它要把LR等等寄存器保存在栈中,还要把局部变量等保存在栈中,那么它肯定得划分出栈呀。第一,划分栈,有LR寄存器和局部变量,划分出来的栈用来保存这些LR寄存器还有局部变量。第二,那显然是LR等寄存器存入栈。第三,执行代码。
就比如说代码里面假设它有a=8的话,我不是为这个a在栈里面划分了空间吗,我就把8这个值写到那个栈里面去。
让SP指向起始位置,设置好栈之后就可以去调用main函数了,它怎么调用main函数呢,它使用BL指令来调用main函数。我们知道它调用main函数,main函数中会调用a函数,a函数中又会调用到b函数。在这个过程中,它在汇编文件里面使用BL指令来调用main函数,BL会把下一条指令也就是main函数的返回地址保存在LR寄存器里面。
所以,第一步BL main会做两件事情,让LR等于返回地址,还有执行main函数,main函数它就会划分出自己的栈,假设它划分出一块空间,SP=SP-N,这N字节的内存就是main函数的栈,在里面它会保存LR等寄存器和局部变量。
第二步,调用函数a,让LR等于函数a的返回地址,执行函数a。在函数a的开头它做的事也是类似的,一开始的时候它也会划分出M字节的空间,让SP=SP-M,这M字节的空间就是函数a的栈,在里面它会保存LR等寄存器和局部变量。
第三步,函数b做的事同理。
假设函数b执行完了,它会返回到哪里。他就会函数b的栈中把LR的值给取出来,跳过去执行
二、FreeRTOS源码怎么用?
1.从官方源码精简出第一个FreeRTOS程序
1.1 FreeRTOS目录结构
1.2 核心文件
FreeRTOS/Source/tasks.c
FreeRTOS/Source/list.c
其他文件的作用也一起列表如下:
1.3 移植时涉及的文件
移植FreeRTOS时涉及的文件放在FreeRTOS/Source/portable/[compiler]/[architecture]目录下,
比如:RVDS/ARM_CM3,这表示cortexM3架构在RVDS或Keil工具上的移植文件。
里面有2个文件:
port.c
portmacro.h
1.4 内存管理
文件在FreeRTOS/Source/portable/MemMang下,它也是放在portable目录下,表示你可以提供自己的函数。
源码中默认提供了5个文件,对应内存管理的5种方法。
1.5 USER目录下是预先配置好的、没有编译错误的工程。目的是让你可以基于它进行修改,以适配你的单板。
这些Demo还可以继续精简:
main函数中只需要保留2个函数:
prvSetupHardware()
vTaskStartScheduler()
如下图所示
1.6 数据类型和编程规范
1.6.1 数据类型
每个移植的版本都含有自己的portmacro.h头文件,里面定义了2个数据类型:
TickType_t:
FreeRTOS配置了一个周期性的时钟中断:Tick Interrupt;
每发生一次中断,中断次数累加,这被称为tick count;
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
1.6.2 变量名
变量名有前缀:
1.6.3 函数名
函数名的前缀有2部分:返回值类型、在哪个文件定义。
1.6.4 宏的名
宏的名字是大小,可以添加小写的前缀。前缀是用来表示:宏在哪个文件中定义。
通用的宏定义如下: