1、freertos简介
1、freertos顾名思义最大的特点就是免费,已经有越来越多的厂商的示例代码都是用freertos,比如st。 2、其次,freertos的文件数量很少,较为精简。 3、freertos经过多年的发展,其市场占有率移植位居前列,稳定性已经得到了市场的认可
1、1 freertos特点
freertos是一个可以裁剪的小型实时操作系统。其重要的特点简述如下:
- 内核支持抢占式,合作式和时间片调度,一般选择为抢占式
- 任务数量不限
- 任务优先级不限
- 高效的软件定时器,这个用起来很爽
- 堆栈溢出检测功能
- 任务间通信方式为:信号量,互斥量,队列,数据流,消息等几种方式
2、freertos官网介绍
官网地址:https://freertos.org/ 这个新页面比之前老的现代化不少了。 下载上面的最新版就行了,下面的长期支持版,里面加了很多aiot的组件,学习的话,先用上面的版本,较为简单纯粹。 下载完成后解压,目录如上图,其中需要用的是FreeRTOS这个文件夹,-plus的这里面实际上也是freertos增加了一些第三方组件。FreeRTOS这个文件夹下就是淡出的内核,使用这个文件夹就行了。
打开文件夹 可以看到source文件下放的就是内核源码。include这里面明显是头文件,接下来看下portable文件夹下的内容。 这里面存放的是与编译器以及硬件相关的一些内容。由于我用的是keil编译器,所以需要用到上述三个文件夹
Keil :与keil相关的 MemMang:存放的是内存管理相关的代码 RVDS:存放的是与mcu内核相关的内容
具体可以看到keil文件夹下就只有一句话,叫我们看RVDS文件,RVDS文件夹下面存放mcu内核相关,看的cm3 cm4是不是很熟悉,stm32f1,stm32f4。
大致的freertos相关介绍就简单介绍到这里,下面开始移植操作。
2、移植freertos
我目前用到的是gd32f107(stm32太贵了,都切国产芯片了,用起来都差不多)。下面就以gd32f107为例 前期准备:
1.keil 工程文件 2.freertos源码,就是上文介绍的
2.1 新建freertos目录
打开功能目录,新建一个freertos文件夹
2.2拷贝源码
间源码FreeRTOS文件夹下面的Source文件夹下的文件全部拷贝到2.1工程目录下面新建的freertos文件夹
2.3删除多余文件
打开工程目录下的freertos文件下的portable,只保留Keil ,MenmMang,RVDS文件夹,其他文件夹全部删除。由于keil文件下就一句话,我就没有保留keil文件夹。删除后的文件夹目录如下图; 下面就要打开工程文件进行操作。
2.4配置工程
打开工程文件。 在工程下面新建两个组,Freertos,Freertos_PORTABLE。 双击Freertos组,添加文件,如下图操作。 双击Freertos_PORTABLE添加文件 添加内存管理: 定位到MenMang文件夹,添加heap_4。基本能满足部分情况下的使用需求,具体却别见https://www.freertos.org/a00111.html介绍 heap4支持动态和静态内存分配堆内存。 注意:要在FreeRTOSConfig.h,打开configAPPLICATION_ALLOCATED_HEAP宏定义,才能使能动态内存分配
添加port文件: 由于我用的gd32f107所以添加的CM3中的port,其他内核要添加对应文件夹下面的port.c 定位到RVDS->ARM_CM3文件夹,添加port.c 添加索引,按下图操作,添加项目目录下的Freertos\include,Freertos\portable\RVDS\ARM_CM3 到这里源码移植就完成了,现在不要想着编译,编译出来会报错错误的,还需要对源码进行一些调整。
2.5源码调整
2.4中提到了FreeRTOSConfig.h文件,这个文件在源码中是没有的,需要使用者自己创建,这个头文件的作用是对freertos进行剪裁,确定需要使用哪些功能。 哪里能找到现成的呢,在源码目录FreeRTOS\Demo下面放着的都是各个芯片的示例工程 打开其中的stm32f103_keil目录,这里面就有FreeRTOSConfig.h文件,接下来我们将这个文件拷贝到工程目录的include文件夹,(这个头文件可以放到其他地方,看个人喜好,为了便于管理,将其放到了Freertos文件下的include文件夹)
3、编译
上述步骤完成后,编译发现存在错误,显示INCLUDE_xTaskGetCurrentTaskHandle未定义 这就需要在FreeRTOSConfig.h中使能该功能
#define configUSE_MUTEXES 1
添加上面的宏定义即可编译成功 虽然这个时候是编译成功了,但是freertos并没有运行。Freertos这些操作系统都系统时钟提供节拍,所以还需要对项目进行调整。
4、运行freertos
freertos运行要依靠mcu的系统时钟提供节拍,一般系统时钟周期设置为1ms。
4.1添加时钟节拍
在系统时钟中断函数中添加如下内容,对于gd32f107,其系统时钟定时器的中断函数位于gd32f107_it.c中 按照如下方式修改:
extern void xPortSysTickHandler( void );
void SysTick_Handler(void)
{
if(xTaskGetSchedulerState()!=taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
}
delay_decrement();
}
在FreeRTOSConfig.h添加
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1)
#define configTIMER_QUEUE_LENGTH 5
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2)
4.2设置异常处理
将gd32f107_it.c中的这两个异常处理函数屏蔽掉 //void PendSV_Handler(void) //void SVC_Handler(void) 因为这两个异常处理函数在port.c中被重新定义了。
最后编译没有错误。
4.3测试
接下来添加一段测试代码,看下freertos的正常运行。 新建一个u_task.c文件,后续所有的测试问题都放到这里。 新建三个任务
- 启动任务:负责启动其他所有任务后,删除掉自身
- 任务1:控制led灯闪烁
- 任务2:控制串口打印输出
代码如下
#include "FreeRTOS.h"
#include "task.h"
#include "usart.h"
#include "gpio.h"
#include "gd32f10x.h"
#include "stdio.h"
TaskHandle_t start_task_Handler;
void start_task(void *pvParameters);
TaskHandle_t task1_Handler;
void task1(void *pvParameters);
TaskHandle_t task2_Handler;
void task2(void *pvParameters);
TaskHandle_t task3_Handler;
void task3(void *pvParameters);
void start_os(void)
{
xTaskCreate(start_task,"start_task",128,NULL,1,&start_task_Handler);
vTaskStartScheduler();
}
void start_task(void *pvParameters)
{
taskENTER_CRITICAL();
xTaskCreate(task1,"task1",128,NULL,2,&task1_Handler);
xTaskCreate(task2,"task2",128,NULL,3,&task2_Handler);
vTaskDelete(start_task_Handler);
taskEXIT_CRITICAL();
}
void task1(void *pvParameters)
{
init_gpio();
while(1)
{
gpio_bit_write(GPIOC,GPIO_PIN_5,gpio_output_bit_get(GPIOC,GPIO_PIN_5)?0:1);
vTaskDelay(500);
}
}
void task2(void *pvParameters)
{
init_usart3();
while(1)
{
printf("hello \r\n");
vTaskDelay(500);
}
}
在main.c中调用start_os()函数。
extern void start_os(void);
int main(void)
{
systick_config();
start_os();
}
编译下载运行,可以看到串口已经在输出了 运行正常。
4.4新建任务解析
新建步骤 1、先定义任务句柄
TaskHandle_t start_task_Handler
2、定义任务
void start_task(void *pvParameters)
{
while(1)
{
vTaskDelay(500);
}
}
每个任务都有一个while主循环,每个循环中要用vTaskDelay来确定任务的执行周期。vTaskDelay是相对定时,后面还有绝对定时函数,后面用到再进行说明。
3、创建任务
xTaskCreate(start_task,"start_task",128,NULL,2,&task1_Handler);
函数的原型如下:
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 )
其中:
- pxTaskCode:任务函数的名称
- pcName:定义一个能看到的任务名称,为字符串,可以与任务函数不同。后续可以通过xTaskGetHandle()函数来获取到这个函数的句柄。长度定义在configMAX_TASK_NAME_LEN,默认是16字节
- usStackDepth:定义栈空间大小,configSTACK_DEPTH_TYPE默认定义的是uint16_t,也就是说,当usStackDepth=100实际的堆栈空间大小为200字节。usStackDepth最小值为configMINIMAL_STACK_SIZE,默认定义是128。所以每个任务堆栈最小为256字节
- pvParameters:传递给任务的参数
- uxPriority:任务优先级,范围 0~ configMAX_PRIORITIES-1。
- pxCreatedTask :任务句柄
返回值为
0:pdPASS创建成功 -1:errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY堆内存不足
任务创建相关函数主要有4个
xTaskCreate() 动态创建一个任务 xTaskCreateStatic() 静态创建一个任务 xTaskCreateRestricted() 创建一个动态MPU(Memory Protection Unit),一般不使用 vTaskDelete() 删除任务
任务创建相关函数,一般使用xTaskCreate 和vTaskDelete函数,其他两个一般较少使用。后面示例主要以这两个函数为主。
|