【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)

相信有不少小伙伴手上只有M4的开发板,想要移植FreeRTOS;但是,网上大部分都是M3移植教程。因此,陷入深深的迷茫中,难不成只能使用仿真了???因此,小编特意写了一篇关于M4的移植教程。
本文移植基于以下软硬件平台:

  • 硬件平台为:主板为Cortex-M4芯片的开发板,此处,小编将以蓝桥杯嵌入式开发板——STM32G431RBT6开发板作为示例
  • 软件系统为:FreeRTOSv202212.00

FreeRTOS简介

在嵌入式领域中,嵌入式实时操作系统正得到越来越广泛的应用。采用嵌入式实时操作系统(RTOS)可以更合理、更有效地利用CPU的资源,简化应用软件的设计,缩短系统开发时间,更好地保证系统的实时性和可靠性。
FreeRTOS就是一款嵌入式实时操作系统,并且还是一款“开源免费”的实时操作系统,遵循GPLv2+的许可协议。好好使用该系统,大家将告别裸机开发,迈向基于操作系统的程序开发。
其功能与特点有:

  • 用户可配置内核功能;
  • 多平台的支持;
  • 提供一个高层次的信任代码的完整性;
  • 目标代码小,简单易用;
  • 遵循MISRA-C标准的编程规范;
  • 强大的执行跟踪功能;
  • 堆栈溢出检测;
  • 没有限制的任务数量;
  • 没有限制的任务优先级;
  • 多个任务可以分配相同的优先权;
  • 队列,二进制信号量,计数信号灯和递归通信和同步的任务;
  • 优先级继承;
  • 免费开源的源代码;

项目工程建立

由于STM32G431RBT6无法直接使用Keil建立工程,需要借助CubeMx才能完成此工作,因此,项目工程建立都是基于CubeMx的,当然,如果大家会使用CubeMx建立工程或者开发板可以不使用CubeMx建立工程就可跳过此部分。

模板工程配置

  • 步骤一:打开CubeMX后,初始化时钟RCC,使用无源外部晶振;
    【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第1张图片

  • 步骤二:调整系统主频至80MHz
    【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第2张图片

  • 步骤三:配置工程信息;
    【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第3张图片
    【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第4张图片

LEDCubeMX配置

  • PC8-PC15 配置成 GPIO_OutPut,将默认电平电平设置成高电平,不加上拉下拉;

  • PD2配置成GPIO_OutPut,将默认电平设置成低电平,不加上拉下拉;
    【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第5张图片
    每次使用LED时一定要记得将PD2拉高拉低,也就是打开关闭锁存器;否则,本次操作就会变成无效操作。

FreeRTOS下载以及移植步骤

下载?当然还是进入官网下载啦!FreeRTOS官网
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第6张图片
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第7张图片
解压后可以得到以下文件,而目标文件则是第一个FreeRTOS。
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第8张图片
移植时需要将source里的大部分文件全部移植,因此可以将整个文件夹都搬过去,而demo中的文件是使用示例,在测试时可以康康里面的示例。【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第9张图片
在Source/portable文件夹中,我们需要的就是内存管理文件与处理器文件,其余的文件暂时用不到,可以将除RVDS与MemMang两个文件夹外其余所有的文件夹都删掉。
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第10张图片
Source/portable/RVDS文件夹中,其提供了里面包含了各种处理器相关的文件,如图所示,因为本次所使用开发板的处理器是M4,因此要保留ARM_CM4F文件夹的内容,可以将其他内容都删除。ARM_CM4F中,ARM_CM4表示其处理器为M4,F表示支持该芯片加强了浮点运算。
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第11张图片
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】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第12张图片

移植后验证

目标:
首次实验的目标是,使用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]);										
}

结果

本来在此处放置一个视频的,但是放置后发现好丑啊。因此,在此处就放置三张图片,注意,这三张图片是连续的哈。


结果1-点亮LED1

结果2-点亮LED2

结果3-点亮LED1与LED2

移植时遇到的典型问题

问题一:中断服务函数重复

问题描述:该报错是因为FreeRTOS中断服务函数与Cortex-M4中断服务函数重复;
在这里插入图片描述
问题解决:

  • 1.使用弱定义__weak修饰其中一个函数;
  • 2.删除其中一个函数;

问题二:汇编代码报错

问题描述:该报错是因为汇编代码操作数据时数据类型有误;
在这里插入图片描述
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第13张图片

问题解决:将stm32g431xx.h文件(指的是芯片头文件)中__NVIC_PRIO_BITS 声明的值由4U改成4即可,如下图;
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第14张图片

问题三:芯片空间不足

问题描述:由于芯片空间不足就会出现类似 freertosTest\freertosTest.axf: Error: L6406E: No space in execution regions with .ANY selector matching heap_4.o(.bss).的报错
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第15张图片
解决方法:该问题小编暂时还未解决,不过网上有很多博客都在解释这个报错,有兴趣的朋友可以去看看。
看到这,相信大部分朋友可能会问:小编都没有解决这个问题,那这个工程时咋跑起来的呢?这不框我嘛。别急,小编这就告诉你原因,正如小编前面所说,小编一开始是移植一位大佬线程的FreeRTOS操作系统的,但是呢,在多方面尝试后,发现咋都不行;因此,小编就去官网下载了官方的FreeRTOS,再参考大佬的移植步骤,最后移植出一个可使用的带FreeRTOS操作系统的G431开发板。

小结

虽然移植过程非常磨人,也花了很多时间去写bug与修bug,但是好在最终还是弄出来了。总的来说移植并不难,只要大家找对芯片信号,再仔细对照教程移植,相信大家总会拥有一个带操作系统的开发板。

福利

下边是小编个人整理出来免费的、保姆级的蓝桥杯嵌入式福利,有需要的童鞋可以自取哟!
【蓝桥杯嵌入式】第十一届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解
【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式省赛程序设计试题以及详细题解
【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛程序设计试题及其详细题解
【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解

最后,欢迎大家留言或私信交流,共同进步哟



追更1:解决理想代码无法使用

经过小编不懈努力,终于确定使用上述理想代码报错的原因啦!!!

问题在现及说明

对,没错,理想代码没有实现就是因为主板捕捉到了硬件错误(HardFault_Handler) ,网络上也有很多关于该报错的解决方案,常见的报错分类有:

  • 内存溢出,访问越界;
  • 堆栈溢出 ,程序跑飞;
  • 数组越界;
  • 中断处理错误;

【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第16张图片

首先说明,小编在初始化硬件资源时,有用到LED与LCD,本来是打算利用FreeRTOS创建两个点灯任务,并且显示在LCD屏上当前任务名称。
这就是导致出现的HardFault_Handler的原因,也就是说LCD初始化时出现了问题,就这个无意间的任务让小编找了两三天错误。
这里LCD初始化、显示使用硬件IIC实现,也有可能是因为LCD是由硬件实现的所以导致小编整个FreeRTOS任务执行失败。
至于,上述代码是怎么实现LCD与LED同时工作的嘛,这里小编可以讲解一下:

  • 首先,不给FreeRTOS设置基础时钟,也就是注释#define xPortSysTickHandler SysTick_Handler ;
  • 其次,使用任务挂起与二次唤醒机制来实现任务切换。当然了,这里需要借助空闲任务来实现唤醒;

解决方法

因为小编刚开始学FreeRTOS,因此这里的解决方法比较简单粗暴——将关于LCD部分的代码完全注释。后续小编还会尝试使用软件IIC,尝试解决该错误。

结果展示


结果1-当前处于空闲任务点亮LED3

结果2-当前处于空闲任务点亮LED3,上一次任务为任务1点亮LED1

结果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

【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第17张图片

  • 步骤2:在stm32g4xx_it.c文件中将下FreeRTOS的滴答时钟与STM32的滴答时钟想关联,依次保证STM32滴答时钟与FreeRTOS滴答时钟正常工作;这里注意,下图中蓝色框中的内容一定要添加,否则FreeRTOS的时钟还是无法使用。
    【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第18张图片

解决方法2:
于是乎小编提出一个第二个解决方法,即使用CubeMX来建立一个FreeRTOS项目
项目建立时,硬件初始化步骤跟 第二部分-项目工程建立 是一样的,可以参考上文即可。
下面就是freeRTOS项目建立的关键步骤,即使能FreeRTOS与创建新的任务,这里需要注意,第一个任务defaultTask是一个默认任务。
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第19张图片

工程创建成功后,大家可以直接在默认任务或者新建任务中写下自己想要实现的功能。
【FreeRTOS】在Cortex-M4开发板上移植FreeRTOS并且实现LED灯闪烁(保姆级教程)_第20张图片
如,小编想要实现 移植后验证的目标功能 ,那么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 */
}

你可能感兴趣的:(#,FreeRTOS,操作系统,单片机,stm32,嵌入式硬件)