文章目录
本文章采用的开发板是野火stm32mini版,前面四个部分只是一些介绍内容,重点移植内容在后面。
一、了解FreeRTOS
- RTOS
Real Time Operating System 实时操作系统。 - FreeRTOS
FreeRTOS 是一款 “开源免费”的实时操作系统,遵循的是 GPLv2+的许可协议。 - FreeRTOS的编程风格
①FreeRTOS 的数据类型
对标准 C 的数据类型进行了重定义。
详细内容如下:新定义的数据类型 实际的数据类型 说明 portCHAR char 字符型 ortSHORT short 短整型 ortLONG long 长整型 ortTickType unsigned short int或者unsigned int 均用于定义系统时基计数器的值和阻塞时间的值。当 FreeRTOSConfig.h 头文件中的宏configUSE_16_BIT_TICKS 为 1 时,unsigned short int则为 16位,unsigned int则为 32位。 ortBASE_TYPE long 根据处理器的架构来决定是多少位的,如果是 32/16/8bit 的处理器则是 32/16/8bit 的数据类型。一般用于定义函数的返回值或者布尔类型。
定义变量的时候往往会把变量的类型当作前缀加在变量上。
通常规则是char 型变量的前缀是 c,short 型变量的前缀是 s,long 型变量的缀是 l, portBASE_TYPE 类型,数据结构,任务句柄,队列句柄变量的前缀是 x。如果一个变量是无符号型的那么会有一个前缀 u,如果是一个指针变量则会有一个前缀 p。因此,当我们定义一个无符号的 char 型变量的时候会加一个 uc 前缀,当定义一个char 型的指针变量的时候会有一个 pc 前缀。
③FreeRTOS的函数名
函数名包含了函数返回值的类型、函数所在的文件名和函数的功能,如果是私有的函数则会加一个 prv(private)的前缀。
④FreeRTOS的宏
宏均是由大写字母表示,并配有小写字母的前缀,前缀用于表示该宏在个头文件定义。比如 configUSE_PREEMPTION(config就表示宏定义在FreeRTOSConfig.h中)小技巧
:编写FreeRTOS代码的时候缩进最好不要采用tab键,使用空格,移植代码不容易出现出现格式问题。
二、使用Keil创建FreeRTOS 工程(不使用Free RTOS源码)
-
准备相关文件夹
电脑上创建一个FreeRTOS(名称可以自己取)文件夹,然后再该文件夹下创建下面文件夹或文件件夹名称 文件夹用途 Doc 存放工程的说明文件 Project 存放新建的工程文件 User 存放main.c和其他用户编写的程序 freeRTOS/Demo 存放板级支持包 freeRTOS/License 存放FreeRTOS组件 freeRTOS/Source/include 存放头文件 freeRTOS/Source 存放FreeRTOS内核源码 freeRTOS/Source/protable/RVDS/ARM_CM3 存放与处理器相关的接口文件(移植文件) freeRTOS/Source/protable/RVDS/ARM_CM4 存放与处理器相关的接口文件(移植文件) freeRTOS/Source/protable/RVDS/ARM_CM7 存放与处理器相关的接口文件(移植文件) -
Keil创建工程
①点击Project—>New uVision Project,输入工程名称(可以随便取名)
②选择处理器,我们选择 ARMCM3(ARMCM4 或 ARMCM7,根据自己的开发板进行选择)
③Manage Run-Time Environment 选项框中选择CMSIS 栏中 CORE 和 Device 栏中 Startup
④keil工程里面新建文件组和添加本地文件
工程里面添加 User、freeRTOS/ports、freeRTOS/source 和 Doc 这几个文件组。
文件组中添加本地文件,User中添加main.c,Doc中添加readme.txt。
选择工程,右键选择Manage Project Items
三、了解裸机系统与多任务系统
-
裸机系统
裸机系统通常分成轮询系统和前后台系统。
①轮询系统
在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情。只需要顺序执行代码且不需要外部事件来驱动的就能完成的事情。
②前后台系统
在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断在这里我们称为前台,main 函数里面的无限循环我们称为后台。 -
多任务系统
多任务系统的事件响应也是在中断中完成的,但是事件的处理是在任务中完成的。在多任务系统中,任务跟中断一样,也具有优先级,优先级高的任务会被优先执行。当一个紧急的事件在中断被标记之后,如果事件对应的任务的优先级足够高,就会立马得到响应。多任务系统与前后台系统的区别在于对于事件的处理位置不同。 -
三种系统的对比
模型 事件响应 事件处理 特点 轮询系统 主程序 主程序 轮询响应事件,轮询处理事件 前后台系统 中断 主程序 实时响应事件,轮询处理事件 多任务系统 中断 任务 实时响应事件,实时处理事件
四、FreeRTOS的任务
-
任务的定义
把整个系统分割成一个个独立的且无法返回的函数,这些函数我们称为任务。 -
创建任务
①定义任务栈
每个任务都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于 RAM 中。#define TASK1_STACK_SIZE 128 StackType_t Task1Stack[TASK1_STACK_SIZE]; #define TASK2_STACK_SIZE 128 StackType_t Task2Stack[TASK2_STACK_SIZE];
②定义任务函数
void delay (uint32_t count) { for (; count!=0; count--); } /* 任务 1 */ void Task1_Entry( void *p_arg ) { for ( ;; ) { flag1 = 1; delay( 100 ); flag1 = 0; delay( 100 ); } } /* 任务 2 */ void Task2_Entry( void *p_arg ) { for ( ;; ) { flag2 = 1; delay( 100 ); flag2 = 0; delay( 100 ); } }
③定义任务控制块
任务控制块就相当于任务的身份证,里面存有任务的所有信息,比如任务的栈指针,任务名称,任务的形参等内容。typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /* 栈顶 */ ListItem_t xStateListItem; /* 任务节点 */ StackType_t *pxStack; /* 任务栈起始地址 */ /* 任务名称,字符串形式 */ char pcTaskName[ configMAX_TASK_NAME_LEN ]; } tskTCB; typedef tskTCB TCB_t;//数据类型重定义
④实现任务创建函数
/* 任务的创建方法:动态创建,静态创建。 动态创建时,任务控制块和栈的内存是创建任务时动态分配的,任务删除时,内存可以释放。 静态创建时,任务控制块和栈的内存需要事先定义好,是静态的内存任务删除时,内存不能释放。 此处是静态创建 */ #if( configSUPPORT_STATIC_ALLOCATION == 1 ) TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, //任务入口,即任务的函数名称。 const char * const pcName, //任务名称,字符串形式 const uint32_t ulStackDepth,//任务栈大小,单位为字 void * const pvParameters,//任务形参 StackType_t * const puxStackBuffer,//任务栈起始地址 TCB_t * const pxTaskBuffer ) //任务控制块指针 { TCB_t *pxNewTCB; TaskHandle_t xReturn; //定义一个任务句柄 xReturn,任务句柄用于指向任务的 TCB if ( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) ) { pxNewTCB = ( TCB_t * ) pxTaskBuffer; pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer; /* 创建新的任务 */ /*调用 prvInitialiseNewTask()函数,创建新任务 pxTaskCode:任务入口 pcName:任务名称,字符串形式 ulStackDepth:任务栈大小,单位为字 pvParameters:任务形参 &xReturn:任务句柄 pxNewTCB):任务栈起始地址 */ prvInitialiseNewTask( pxTaskCode, pcName, ulStackDepth, pvParameters, &xReturn,pxNewTCB); } else { xReturn = NULL; } /* 返回任务句柄,如果任务创建成功,此时 xReturn 应该指向任务控制块 */ return xReturn; //返回任务句柄,如果任务创建成功,此时 xReturn 应该指向任务控制块,xReturn 作为形参传入到 prvInitialiseNewTask 函数 } #endif /* configSUPPORT_STATIC_ALLOCATION */
-
实现就绪列表
①定义就绪列表List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
②就绪列表初始化
void prvInitialiseTaskLists( void ) { UBaseType_t uxPriority; for ( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES;uxPriority++ ) { vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) ); } }
③将任务插入到就绪列表
1 /* 初始化与任务相关的列表,如就绪列表 */ prvInitialiseTaskLists(); Task1_Handle = /* 任务句柄 */ xTaskCreateStatic( (TaskFunction_t)Task1_Entry, /* 任务入口 */ (char *)"Task1", /* 任务名称,字符串形式 */ (uint32_t)TASK1_STACK_SIZE , /* 任务栈大小,单位为字 */ (void *) NULL, /* 任务形参 */ (StackType_t *)Task1Stack, /* 任务栈起始地址 */ (TCB_t *)&Task1TCB ); /* 任务控制块 */ /* 将任务添加到就绪列表 */ vListInsertEnd( &( pxReadyTasksLists[1] ), &( ((TCB_t *)(&Task1TCB))->xStateListItem ) );
-
实现调度器
调度器是操作系统的核心,其主要功能是用于实现任务的切换,即从就绪列表里面找到优先级最高的任务,然后去执行该任务。
①启动调度器void vTaskStartScheduler( void ) { /* 手动指定第一个运行的任务 */ pxCurrentTCB = &Task1TCB; //的全局指针,用于指向当前正在运行或者即将要运行的任务的任务控制块 /* 启动调度器 */ if ( xPortStartScheduler() != pdFALSE ) { /* 调度器启动成功,则不会返回,即不会来到这里 */ } }
②任务切换
void vTaskSwitchContext( void ) { /* 两个任务轮流切换 */ if ( pxCurrentTCB == &Task1TCB ) //如果当前任务为任务 1,则把下一个要运行的任务改为任务 2 { pxCurrentTCB = &Task2TCB; } else 如果当前任务为任务 2,则把下一个要运行的任务改为任务 1 { pxCurrentTCB = &Task1TCB; } }
当前任务1和任务2之间不存在优先级的,所以此处任务切换是采用的轮流切换的方式。
五、移植FreeRTOS到STM32
- 获取 STM32 的裸机工程模板
已建好的一个基于固件库的STM32工程。 - 下载 FreeRTOS V9.0.0 源码
FreeRTOS 的源码获取地址:
https://sourceforge.net/projects/freertos/files/FreeRTOS/ - FreeRTOS源码文件的介绍
- 往裸机工程添加 FreeRTOS 源码
添加最简的FreeRTOS源码方法
①在STM32裸机工程模板根目录下新建一个文件夹 ,命名为“FreeRTOS”
②在FreeRTOS文件夹下新建两个空文件夹,分别命名为“src”与“port”,src 文件夹用于保存 FreeRTOS 中的核心源文件(‘.c 文件’),port 文件夹用于保存内存管理以及处理器架构相关代码
③FreeRTOS V9.0.0 源码的部分文件复制到STM32裸机工程下的FreeRTOS文件
“FreeRTOSv9.0.0\ FreeRTOS\Source”目录下找到“include”文件夹,直接复制到FreeRTOS文件夹
直接拷贝整个FreeRTOS源码到STM32裸机工程 - 添加 FreeRTOS 源码到工程组文件夹
选中工程,右键选择Manage Project Items
添加头文件路径
编译出现错误
解决方法
在D:\FreeRTOSv9.0.0\FreeRTOS\Demo\CORTEX_STM32F103_Keil(该路径是我电脑路径,你需要找到你下载源码所放置的位置)中找到FreeRTOSConfig.h复制到STM32工程中的FreeRTOS文件夹。
六、实现多任务程序
-
创建任务句柄
static TaskHandle_t AppTaskCreate_Handle = NULL; /* LED任务句柄 */ static TaskHandle_t LED_Task_Handle = NULL; /* 串口任务句柄 */ static TaskHandle_t USART_Task_Handle = NULL; /* 温度任务句柄 */ static TaskHandle_t Temperature_Task_Handle = NULL;
-
创建任务
/* 创建LED_Task任务 */ xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */ (const char* )"LED_Task",/* 任务名字 */ (uint16_t )512, /* 任务栈大小 */ (void* )NULL, /* 任务入口函数参数 */ (UBaseType_t )2, /* 任务的优先级 */ (TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */ /* 创建USART_Task任务 */ xTaskCreate((TaskFunction_t )USART_Task, /* 任务入口函数 */ (const char* )"USART_Task",/* 任务名字 */ (uint16_t )512, /* 任务栈大小 */ (void* )NULL, /* 任务入口函数参数 */ (UBaseType_t )2, /* 任务的优先级 */ (TaskHandle_t* )&USART_Task_Handle);/* 任务控制块指针 */ /* 创建Temperature_Task任务 */ xTaskCreate((TaskFunction_t )Temperature_Task, /* 任务入口函数 */ (const char* )"Temperature_Task",/* 任务名字 */ (uint16_t )512, /* 任务栈大小 */ (void* )NULL, /* 任务入口函数参数 */ (UBaseType_t )2, /* 任务的优先级 */ (TaskHandle_t* )&Temperature_Task_Handle);/* 任务控制块指针 */
-
任务功能函数
static void LED_Task(void* parameter) { while (1) { printf("LED is ON!\n"); LED1_ON; vTaskDelay(500); /* 延时500个tick */ printf("LED is OFF!\n"); LED1_OFF; vTaskDelay(500); /* 延时500个tick */ } } static void USART_Task(void* pvParameters) { while(1) { printf("Hello Windows!\n"); vTaskDelay(1000); } } static void Temperature_Task(void* pvParameters) { //具体功能还没有实现,此处采用一个输出语句来表明进行该项任务 while(1) { printf("检测温度!\n"); vTaskDelay(1000); } }
-
main函数
/***************************************************************** * @brief 主函数 * @param 无 * @retval 无 * @note 第一步:开发板硬件初始化 第二步:创建APP应用任务 第三步:启动FreeRTOS,开始多任务调度 ****************************************************************/ int main(void) { BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */ /* 开发板硬件初始化 */ BSP_Init(); /* 创建AppTaskCreate任务 */ xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */ (const char* )"AppTaskCreate",/* 任务名字 */ (uint16_t )512, /* 任务栈大小 */ (void* )NULL,/* 任务入口函数参数 */ (UBaseType_t )1, /* 任务的优先级 */ (TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */ /* 启动任务调度 */ if(pdPASS == xReturn) vTaskStartScheduler(); /* 启动任务,开启调度 */ else return -1; while(1); /* 正常不会执行到这里 */ }
-
编译烧录
-
使用串口来验证结果
整个工程代码百度网盘链接:
https://pan.baidu.com/s/1_Vs0Yl2HkydNiAfphZDAwg
提取码:m63f
七、总结
本文章的重点内容是FreeRTOS的移植和多任务的实现。前面内容只是对整个内容的一些介绍,不想了解也没什么关系。只是可能在后面代码部分,看的不是很懂。不清楚每个部分具体是完成一些什么操作。本过程初始化函数是必要的,一定不要忘记添加。特别是对串口的初始化,如果没有初始化,程序可能不会报错,但是,使用串口调试助手进行数据接收就什么也不会显示。程序中的printf函数并不是指的C程序中的printf函数,而是串口中重新定义的函数,不要混淆了。
八、参考资料
野火FreeRTOS内核实现与应用开发实战.pdf
下载地址:野火官方产品资料下载