相信有不少小伙伴手上只有M4的开发板,想要移植FreeRTOS;但是,网上大部分都是M3移植教程。因此,陷入深深的迷茫中,难不成只能使用仿真了???🤔因此,小编特意写了一篇关于M4的移植教程。
本文移植基于以下软硬件平台:
- 硬件平台为:主板为Cortex-M4芯片的开发板,此处,小编将以蓝桥杯嵌入式开发板——STM32G431RBT6开发板作为示例;
- 软件系统为:FreeRTOSv202212.00;
FreeRTOS简介
在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。
FreeRTOS就是一款嵌入式实时操作系统,并且还是一款“开源免费”的实时操作系统,遵循GPLv2+的许可协议。好好使用该系统,大家将告别裸机开发,迈向基于操作系统的程序开发。
其功能与特点有:
- 用户可配置内核功能;
- 多平台的支持;
- 提供一个高层次的信任代码的完整性;
- 目标代码小,简单易用;
- 遵循MISRA-C标准的编程规范;
- 强大的执行跟踪功能;
- 堆栈溢出检测;
- 没有限制的任务数量;
- 没有限制的任务优先级;
- 多个任务可以分配相同的优先权;
- 队列,二进制信号量,计数信号灯和递归通信和同步的任务;
- 优先级继承;
- 免费开源的源代码;
项目工程建立
由于STM32G431RBT6无法直接使用Keil建立工程,需要借助CubeMx才能完成此工作,因此,项目工程建立都是基于CubeMx的,当然,如果大家会使用CubeMx建立工程或者开发板可以不使用CubeMx建立工程就可跳过此部分。
模板工程配置
-
步骤一:打开CubeMX后,初始化时钟RCC,使用无源外部晶振;
-
步骤二:调整系统主频至80MHz;
-
步骤三:配置工程信息;
LEDCubeMX配置
-
PC8-PC15 配置成 GPIO_OutPut,将默认电平电平设置成高电平,不加上拉下拉;
-
PD2配置成GPIO_OutPut,将默认电平设置成低电平,不加上拉下拉;
每次使用LED时一定要记得将PD2拉高拉低,也就是打开关闭锁存器;否则,本次操作就会变成无效操作。
FreeRTOS下载以及移植步骤
下载?🤔当然还是进入官网下载啦!🤣🤣🤣FreeRTOS官网
解压后可以得到以下文件,而目标文件则是第一个FreeRTOS。
移植时需要将source里的大部分文件全部移植,因此可以将整个文件夹都搬过去,而demo中的文件是使用示例,在测试时可以康康里面的示例。
在Source/portable文件夹中,我们需要的就是内存管理文件与处理器文件,其余的文件暂时用不到,可以将除RVDS与MemMang两个文件夹外其余所有的文件夹都删掉。
Source/portable/RVDS文件夹中,其提供了里面包含了各种处理器相关的文件,如图所示,因为本次所使用开发板的处理器是M4,因此要保留ARM_CM4F文件夹的内容,可以将其他内容都删除。ARM_CM4F中,ARM_CM4表示其处理器为M4,F表示支持该芯片加强了浮点运算。
Source/portable/MemMang文件夹中,其提供的是一些内存管理文件,共有5个,各个文件解释如下:
- heap1:最简单的内存管理,管理的其实是一个静态全局变量,只允许分配,不允许释放,设计之初就是用于创建信号量、任务、队列一般不需要释放的数据,不过在FreeRTOS V9.0.0及其以后版本添加 support for static allocation基本就被替代了。
- heap2.:比1多了内存释放,分配也因为有内存释放原因多了一个最佳适配算法,不过在内存释放后没有空闲块合并的功能,只适合信号量队列任务等大小固定的内存分配,随机大小的内存分配释放会因为内存碎片问题而无法进行。后面heap4是heap2的优化版。
- heap3:简单封装标准C库mallco和free,提供线程安全。
- heap4:一般来说FREERTOS五种堆管理里这个是最常见使用的了,首次适配算法,heap2的优化版,多了相邻内存碎片合并功能,内存碎片的风险少很多,不过没有MMU的芯片没有逻辑地址和物理地址的映射概念,内存分配就是一个萝卜一个坑,想做内存紧凑比较费劲。
- heap5:比heap4多了不连续地址多段内存管理,分配释放代码完全一样。
删除部分文件后完整的移植所需的内核文件如下:
移植后验证
目标:
首次实验的目标是,使用FreeRTOS创建两个任务,即:
任务一:LED1以合适的时间间隔闪烁;
任务二:LED2以合适的时间间隔闪烁;
由于FreeRTOS是一个可配置的实时操作系统,因此在使用前还需要配置其功能,也就是设置宏定义的值,否则编译时会报错。
程序
首先,给大家展示小编理想代码
其思路是:
- 1.创建两个任务,并且保存创建时的返回值;
- 2.判断两个任务是否都创建成功,如果失败,就点亮LED6,;否则就进入裸机的
while(1)
中,执行裸机程序,将不再执行下面步骤;- 3.开启FreeRTOS的任务调度;
可以看出来实现步骤非常简单,而且下面的任务函数也非常简单;但是在实际使用时,一旦所有任务经vTaskDelay
函数进入阻塞状态,这些任务将无法恢复,整个系统就会处于空闲状态(也就是没有任何正在执行的任务)。🤔🤔🤔 这个问题小编暂时还未解决方法,如有必要,后续会出一篇文件讲解这个问题。
//任务控制权柄
TaskHandle_t xHandleTsak[2];
/*****************************************
* 函数功能:freertos工作函数
* 函数参数:无
* 函数返回值:无
*****************************************/
void freertosWork(void)
{
//存储创建任务的返回值
BaseType_t xReturn[2] ;
//动态创建任务1
xReturn[0] = xTaskCreate(
(TaskFunction_t )task1,//任务入口函数
(const char *)"task1",//任务名字
(uint16_t)100,//任务栈大小
(void*)NULL,//任务入口参数
1,//任务优先级 优先级越高,任务优先选越高
&xHandleTsak[0]//任务控制块
);
//动态创建任务2
xReturn[1] = xTaskCreate(
(TaskFunction_t )task2,//任务入口函数
(const char *)"task2",//任务名字
(uint16_t)100,//任务栈大小
(void*)NULL,//任务入口参数
1,//任务优先级 优先级越高,任务优先选越高
&xHandleTsak[1]//任务控制块
);
//创建成功
if (pdPASS == xReturn[0] == xReturn[1])
//启动任务,开启调度
vTaskStartScheduler();
//创建失败
else
//点亮LED6
changeLedStateByLocation(LED6,ON);
}
//任务1
void task1(void)
{
const volatile TickType_t xDelay1000ms = pdMS_TO_TICKS( 1000UL );
while(1)
{
//LED1反转状态
rollbackLedByLocation(LED1);
vTaskDelay( xDelay1000ms );
}
}
//任务2
void task2(void)
{
const volatile TickType_t xDelay1000ms = pdMS_TO_TICKS( 900UL );
while(1)
{
//LED2状态反转
rollbackLedByLocation(LED2);
vTaskDelay( xDelay1000ms );
}
}
这是小编实际使用的代码
其实,实际使用的代码跟上述代码大部分一样,并且逻辑也大致相似。
不同的是,实现LED灯闪烁的方式,也就是任务的阻塞态与运行态转换的方式不同。本来是打算借助于vTaskDelay
函数实现的,但是一旦出现该函数,程序就会死机;因此,小编打算使用空闲任务来定时激活两个任务,这里的前提条件是两个任务一旦执行完一次就必须进入挂起状态(阻塞状态),因为只有这样,该空闲任务才能重新激活已经挂起的任务。
//任务控制权柄
TaskHandle_t xHandleTsak[2];
//存储计数值
volatile uint32_t count[3] = {0,0,0};
/*****************************************
* 函数功能:freertos工作函数
* 函数参数:无
* 函数返回值:无
*****************************************/
void freertosWork(void)
{
//存储创建任务的返回值
BaseType_t xReturn[2] ;
//动态创建任务1
xReturn[0] = xTaskCreate(
(TaskFunction_t )task1,//任务入口函数
(const char *)"task1",//任务名字
(uint16_t)100,//任务栈大小
(void*)NULL,//任务入口参数
1,//任务优先级 优先级越高,任务优先选越高
&xHandleTsak[0]//任务控制块
);
//动态创建任务2
xReturn[1] = xTaskCreate(
(TaskFunction_t )task2,//任务入口函数
(const char *)"task2",//任务名字
(uint16_t)100,//任务栈大小
(void*)NULL,//任务入口参数
1,//任务优先级 优先级越高,任务优先选越高
&xHandleTsak[1]//任务控制块
);
//创建成功
if (pdPASS == xReturn[0] == xReturn[1])
//启动任务,开启调度
vTaskStartScheduler();
//创建失败
else
//点亮LED6
changeLedStateByLocation(LED6,ON);
}
//任务1
void task1(void)
{
const volatile TickType_t xDelay1000ms = pdMS_TO_TICKS( 1000UL );
while(1)
{
//LED1反转状态
rollbackLedByLocation(LED1);
//挂起任务
vTaskSuspend(xHandleTsak[0]);
}
}
//任务2
void task2(void)
{
const volatile TickType_t xDelay1000ms = pdMS_TO_TICKS( 900UL );
while(1)
{
//LED2状态反转
rollbackLedByLocation(LED2);
//挂起任务
vTaskSuspend(xHandleTsak[1]);
}
}
//空闲任务
void vApplicationIdleHook (void)
{
if(count[0]++ % 900000 == 0)
//恢复任务1
vTaskResume(xHandleTsak[1]);
else if(count[0] % 1000000 == 0)
//恢复任务0
vTaskResume(xHandleTsak[0]);
}
结果
本来在此处放置一个视频的,但是放置后发现好丑啊🤣🤣🤣。因此,在此处就放置三张图片,注意,这三张图片是连续的哈🤔🤔🤔。
![](https://i-blog.csdnimg.cn/blog_migrate/a22ee7d88b8dfa3c72807f86f9bde02c.png)
结果1-点亮LED1
![](https://i-blog.csdnimg.cn/blog_migrate/c2edcd4f46b62077d1b0e850b7b2cdf5.png)
结果2-点亮LED2
![](https://i-blog.csdnimg.cn/blog_migrate/162e0fcdfdfc054f9750e6544e3dce42.png)
结果3-点亮LED1与LED2
移植时遇到的典型问题
问题一:中断服务函数重复
问题描述:该报错是因为FreeRTOS中断服务函数与Cortex-M4中断服务函数重复;
问题解决:
- 1.使用弱定义__weak修饰其中一个函数;
- 2.删除其中一个函数;
问题二:汇编代码报错
问题描述:该报错是因为汇编代码操作数据时数据类型有误;
问题解决:将stm32g431xx.h文件(指的是芯片头文件)中__NVIC_PRIO_BITS 声明的值由4U改成4即可,如下图;
问题三:芯片空间不足
问题描述:由于芯片空间不足就会出现类似 freertosTest\freertosTest.axf: Error: L6406E: No space in execution regions with .ANY selector matching heap_4.o(.bss).的报错
解决方法:该问题小编暂时还未解决,不过网上有很多博客都在解释这个报错,有兴趣的朋友可以去看看。
看到这,相信大部分朋友可能会问:小编都没有解决这个问题,那这个工程时咋跑起来的呢?这不框我嘛😢😢😢。别急,小编这就告诉你原因,正如小编前面所说,小编一开始是移植一位大佬线程的FreeRTOS操作系统的,但是呢,在多方面尝试后,发现咋都不行;因此,小编就去官网下载了官方的FreeRTOS,再参考大佬的移植步骤,最后移植出一个可使用的带FreeRTOS操作系统的G431开发板😁😁😁。
小结
虽然移植过程非常磨人,也花了很多时间去写bug与修bug,但是好在最终还是弄出来了。总的来说移植并不难,只要大家找对芯片信号,再仔细对照教程移植,相信大家总会拥有一个带操作系统的开发板🎉🎉🎉。
福利
下边是小编个人整理出来免费的、保姆级的蓝桥杯嵌入式福利,有需要的童鞋可以自取哟!🤤🤤🤤
【蓝桥杯嵌入式】第十一届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解
【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式省赛程序设计试题以及详细题解
【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛程序设计试题及其详细题解
【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解
最后,欢迎大家留言或私信交流,共同进步哟!😉😉😉
追更1:解决理想代码无法使用
经过小编不懈努力,终于确定使用上述理想代码报错的原因啦!!!
问题在现及说明
对,没错,理想代码没有实现就是因为主板捕捉到了硬件错误(HardFault_Handler) ,网络上也有很多关于该报错的解决方案,常见的报错分类有:
- 内存溢出,访问越界;
- 堆栈溢出 ,程序跑飞;
- 数组越界;
- 中断处理错误;
首先说明,小编在初始化硬件资源时,有用到LED与LCD,本来是打算利用FreeRTOS创建两个点灯任务,并且显示在LCD屏上当前任务名称。
这就是导致出现的HardFault_Handler的原因,也就是说LCD初始化时出现了问题,就这个无意间的任务让小编找了两三天错误🤣🤣🤣。
这里LCD初始化、显示使用硬件IIC实现,也有可能是因为LCD是由硬件实现的所以导致小编整个FreeRTOS任务执行失败😢😢😢。
至于,上述代码是怎么实现LCD与LED同时工作的嘛,这里小编可以讲解一下:
- 首先,不给FreeRTOS设置基础时钟,也就是注释
#define xPortSysTickHandler SysTick_Handler
; - 其次,使用任务挂起与二次唤醒机制来实现任务切换。当然了,这里需要借助空闲任务来实现唤醒;
解决方法
因为小编刚开始学FreeRTOS,因此这里的解决方法比较简单粗暴——将关于LCD部分的代码完全注释。后续小编还会尝试使用软件IIC,尝试解决该错误🤣。
结果展示
![](https://i-blog.csdnimg.cn/blog_migrate/79dc3da5218b95f75da65f203d9109c6.png)
结果1-当前处于空闲任务点亮LED3
![](https://i-blog.csdnimg.cn/blog_migrate/b9137e394119091241228f72953ae27e.png)
结果2-当前处于空闲任务点亮LED3,上一次任务为任务1点亮LED1
![](https://i-blog.csdnimg.cn/blog_migrate/056701c550196522172caf95328dd21a.png)
结果3-当前处于空闲任务点亮LED3,上一次任务为任务2点亮LED2
第一次在蓝桥杯嵌入式STM32G431RBT6开发板上使用FreeRTOS操作系统点亮LED灯 完整视屏
追更2:解决FreeRTOS无法与LCD一起使用的问题以及使用CubeMX建立FreeRTOS项目
针对上述代码LCD无法与FreeRTOS同时使用的问题,其主要原因是FreeRTOS的时钟配置问题。
解决方法1:
小编提出的第一个解决方法,是配置FreeRTOS时钟与STM32时钟,即解决FreeRTOS时钟配置问题。
- 步骤1:在freeRTOSConfig.h文件中添加以下FreeRTOS与STM32时钟相关的配置;
/* freertos与中断服务函数相关的配置 */
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
#define INCLUDE_xTaskGetSchedulerState 1
- 步骤2:在stm32g4xx_it.c文件中将下FreeRTOS的滴答时钟与STM32的滴答时钟想关联,依次保证STM32滴答时钟与FreeRTOS滴答时钟正常工作;这里注意,下图中蓝色框中的内容一定要添加,否则FreeRTOS的时钟还是无法使用。🙂
解决方法2:
于是乎小编提出一个第二个解决方法,即使用CubeMX来建立一个FreeRTOS项目。
项目建立时,硬件初始化步骤跟 第二部分-项目工程建立 是一样的,可以参考上文即可。
下面就是freeRTOS项目建立的关键步骤,即使能FreeRTOS与创建新的任务,这里需要注意,第一个任务defaultTask是一个默认任务。
工程创建成功后,大家可以直接在默认任务或者新建任务中写下自己想要实现的功能。
如,小编想要实现 移植后验证的目标功能 ,那么freeRTOS的三个任务分别为
/* USER CODE BEGIN Header_StartDefaultTask */
/**
* @brief Function implementing the defaultTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;)
{
//LED2状态反转
rollbackLedByLocation(LED1);
osDelay(500);
}
/* USER CODE END StartDefaultTask */
}
/* USER CODE BEGIN Header_StartTask02 */
/**
* @brief Function implementing the myTask02 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask02 */
void StartTask02(void *argument)
{
/* USER CODE BEGIN StartTask02 */
/* Infinite loop */
for(;;)
{
//LED2状态反转
rollbackLedByLocation(LED2);
osDelay(1000);
}
/* USER CODE END StartTask02 */
}
/* USER CODE BEGIN Header_StartTask03 */
/**
* @brief Function implementing the myTask03 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTask03 */
void StartTask03(void *argument)
{
/* USER CODE BEGIN StartTask03 */
/* Infinite loop */
for(;;)
{
//LED2状态反转
rollbackLedByLocation(LED3);
osDelay(1000);
}
/* USER CODE END StartTask03 */
}