FreeRTOS应用基础(一)

发布时间 2023-05-22 22:29:54作者: cloudraysun

  本系列主要作为自己第一次系统学习RTOS的记录,以正点原子的STM32F103战舰,keil环境编程为例。想要达到以下目标:
  1:初步熟悉FreeRTOS的移植和使用,并迁移完成一个小型项目;
  2:以FreeRTOS为入门,了解RTOS的本质,并提升阅读源码的能力;
  本系列文章主要参考以下资料,本文仅作为汇总,并添加了一些个人理解。
  韦东山freeRTOS快速入门
  【安富莱】FreeRTOS操作系统教程-硬汉嵌入式论坛-Powered by Discuz!

RTOS

  按对外部事件的响应能力来分类,嵌入式操作系统有分时操作系统和实时操作系统。如果操作系统能使计算机系统及时的响应外部事件请求,并能控制所有实时设备和实时任务协调运行,且能在一个规定的时间内完成对事件的处理,那么这种系统就称为实时操作系统(RTOS)。
  按时间的正确程度来分,实时操作系统又分为硬件的实时操作系统和软件的实时操作系统。系统必须在极其严格的时间内完成的任务叫做硬件的实时操作系统,如果不是很严格的话就是软件的实时操作系统。
  FreeRTOS为一个完全免费、可商业化的实时操作系统,为不适合或不可在 eCOS、嵌入式 Linux(或实时 Linux)甚至 uCLinux 上开发的应用程序提供更小、更简单的实时处理系统。

官方中文网站:
RTOS - Free professionally developed and robust real time operating system for small embedded systems development
https://www.freertos.org/zh-cn-cmn-s/RTOS.html

文件结构

FreeRTO部分文件结构如下

|--FreeRTOS
|  |--Demo              //预先制作的示例工程
|  |--Common            //独立于demo的通用代码,大部分已废弃
|  |--source            //核心文件
|  |  |--task.c         //必需,任务操作
|  |  |--list.c         //必需,列表
|  |  |--queue.c        //基本必需,提供队列操作、信号量(semaphore)操作
|  |  |--timer.c        //可选,software timer
|  |  |--crountine.c    //可选,提供event group功能
|  |  |--stream_buffer.c 
|  |  |--event_groups.c
|  |  |--icnclude       //FreeRTOS本身的头文件
|  |  |--portable       //移植时涉及的文件
|  |  |  |--RVDS        //IDE环境,一般为keil或RVD
|  |  |  |  |--ARM_CM3  //cortexM3架构的移植文件
|  |  |  |  |  |--port.c
|  |  |  |  |  |--portmacro.h
|  |  |  |......
|  |  |  |  |......
|  |  |  |  |--MemMang  //内存管理文件,包含五种内存分配方式
|  |  |  |  |  |--heap_1.c
|  |  |  |  |  |--......

工程移植

  此处应用的工程模板为自制,以stm32f1的工程为例子,参考为安富莱开发板的FreeRTOS教程;
  首先在工程目录中建立FreeRTOS的文件目录,将FreeRTOSV8.2.3\FreeRTOS\Source里面如下所有文件移植到刚刚创建的目录中

  然后在Keil工程中建立Source和Portable目录,将以下文件包含在里面,完成后如图所示:

  其中heap4.c为系统内存分配方式,port.c和portmacro.h为RVDS中Cortex-M3对应的移植文件,
heap_4.c文件路径: FreeRTOS\Source\portable\MemMang
port.c和portmacro.h文件的路径:FreeRTOS\Source\portable\RVDS\ARM_CM3
  接着从FreeRTOSV8.2.3\FreeRTOS\Demo\CORTEX_STM32F103_Keil目录下将FreeRTOSConfig.h复制到工程中,此文件为官方整理好的配置文件,用户可通过此文件配置系统,可自定义位置。
  (此步可选)接着建立includes.h,可以用来集中管理头文件,完成后的目录如下:

includes.h可按照以下内容模板:

#ifndef __INCLUDES_
#define __INCLUDES_
/*
****************************************************************
*							标准库
****************************************************************
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
/*
****************************************************************
*							OS
****************************************************************
*/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "croutine.h"

/*
****************************************************************
*							其他
****************************************************************
*/

#endif

  添加文件路径

  最后修改FreeRTOSConfig.h文件,在其中添加以下几行

#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

  目的是更改底层映射函数(此步也可以通过其他方式完成,但此方法较为简单)
  其中vPortSVCHandler,xPortPendSVHandler和xPortSysTickHandler是在port.c文件里面定义的。
  SVC_Handler,PendSV_Handler和SysTick_Handler在startup_stm32f10x_hd.s文件里面进行了定义。
  并在stm32f10x_it.c中将对应的函数注销:

  接着编译时会出现以下错误

..\Object\Objects\project.axf: Error: L6218E: Undefined symbol xTaskGetCurrentTaskHandle (referred from stream_buffer.o).

  查阅网上资料,修改FreeRTOS.h中206行定义即可

  至此,编译0错误0警告,基本移植完成,注意在编译时会报很多FreeRTOS文件的警告,无视重新编译即可,其他问题则需逐个解决

数据类型、命名规则和注释风格

  FreeRTOS核心源码文件的编写遵循MISRA代码规则,同时支持各种编译器。
FreeRTOS没有引入C99和C11的一些新特性和语法,除stdint.h文件(C99标准库)。

基础数据类型

  FFreeRTOS面对不同架构的设备,主要包含四种数据类型,每个移植的版本都含有自己的portmacro.h 头文件,包含了TickType_t和BaseType_t:

  1. TickType_t:
  • FreeRTOS配置了一个周期性的时钟中断:Tick Interrupt,每发生一次中断,中断次数累加,这被称为tick count,tick count这个变量的类型就是TickType_t;
  • 如果用户使能了宏定义 configUSE_16_BIT_TICKS,那么TickType_t定义的就是16位无符号数,否则就是32位无符号数;
  • 对于32位架构,一定要禁止configUSE_16_BIT_TICKS定义,将此宏设置为0,即配置成32位无符号数;
  1. BaseType_t
  • 使用设备架构最基础和最高效的数据类型
  • 由架构决定,32位架构就是32位有符号数、16位架构就是16位有效数据、8位架构同理;
  • 如果BaseType_t被定义成了char型,要特别注意将其设置为有符号数,因为部分函数的返回值是用负数来表示错误类型。
  • BaseType_t通常用作简单的返回值的类型,还有逻辑值,比如pdTRUE/pdFALSE

变量名

  FreeRTOS主要以前缀区分变量

基础前缀 含义
c char,根据MISRA代码规则,char定义的变量只能用于ASCII字符,前缀使用c。
s short,int16_t
i long, int32_t
u unsigned
p 指针
e 枚举
x 其他非标准的类型:结构体、task handle、queue handle等
复合前缀 含义
pc char指针,根据MISRA代码规则,char *定义的指针变量只能用于ASCII字符串,前缀使用pc。
uc unsigned char
pus uint16_t定义的指针变量

函数名

  函数名的前缀有2部分:返回值类型、在哪个文件定义

函数名前缀 含义
prv 加上了static声明的函数, private的缩写。
v 无返回值类型的函数
c 返回值类型为char的函数
Task 根据文件名,文件中相应的函数定义时也将文件名加到函数命名中,如tasks.c中的函数,前缀都带有Task

例子

函数名 含义
vTaskPrioritySet 返回值类型:void,在task.c中定义
xQueueReceive 返回值类型:BaseType_t, 在queue.c中定义
pvTimerGetTimerID 返回值类型:pointer to void,在tmer.c中定义

宏名

  宏名主体部分以大写为主,可添加小写前缀,用来表示这个宏在哪个文件中定义,例如

函数名 含义:在哪个文件中定义
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

点灯

  为了验证工程移植正确,通过实际点亮LED灯验证,main.c如下:

#include "main.h"
#include "includes.h"

static void vTaskLED1(void *pvParameters);
static void vTaskLED2(void *pvParameters);
	
int main()
{
	//SystemInit();       //设置系统时钟为72M;
	//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//禁止JTAG和SWJ功能,关闭调试,释放引脚
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	LED_Init();
	/* 创建任务*/
	xTaskCreate( vTaskLED1, /* 任务函数*/
				"vTaskLED", /* 任务名*/
				512, /*任务栈 大小,单位 word ,也就是 4 字节*/
				NULL, /* 任务参数*/
				1, /* 任务优先级*/
				NULL /* 任务句柄*/
	);
	xTaskCreate( vTaskLED2, /* 任务函数*/
				"vTaskLED", /* 任务名*/
				512, /*任务栈 大小,单位 word ,也就是 4 字节*/
				NULL, /* 任务参数*/
				2, /* 任务优先级*/
				NULL /* 任务句柄*/
	);
	/* 启动调度,开始执行任务*/
	vTaskStartScheduler();	
	
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	/*
	如果系统正常启动是不会运行到这里的,运行到这里极有可能是用于定时器任务或者空闲任务的
	heap 空间不足造成创建失败,此时要加大 FreeRTOSConfig.h 文件中定义的 heap 大小:
	#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 )*/
	while(1);
	
}
/*
*********************************************************************************************************
*	函 数 名: vTaskLED1
*	功能说明: LED闪烁任务
*	形    参: pvParameters 是在创建该任务时传递的形参
*	返 回 值: 无
*	优 先 级:  1	(: 1 数值越小优先级越低,这个跟 uCOS 相反)
*	说    明:
*********************************************************************************************************
*/
static void vTaskLED1(void *pvParameters)
{
	while(1)
	{
		LED_Toggle(1);
		vTaskDelay(1000);
	}
}
/*
*********************************************************************************************************
*	函 数 名: vTaskLED1
*	功能说明: LED闪烁任务
*	形    参: pvParameters 是在创建该任务时传递的形参
*	返 回 值: 无
*	优 先 级:  2
*	说    明:
*********************************************************************************************************
*/
static void vTaskLED2(void *pvParameters)
{
	while(1)
	{
		LED_Toggle(2);
		vTaskDelay(2000);
	}
}

  成功点亮LED!