在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,使用无源外部晶振;

  • 步骤二:调整系统主频至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]);										
    }
    

    结果

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




    结果1-点亮LED1




    结果2-点亮LED2




    结果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,尝试解决该错误🤣。

    结果展示




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

  • 步骤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 */
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » 在Cortex-M4开发板上移植FreeRTOS:保姆级教程,实现LED灯的闪烁

    发表评论