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

      • 步骤二:调整系统主频至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函数进入阻塞状态,这些任务将无法恢复,整个系统就会处于空闲状态(也就是没有任何正在执行的任务)。🤔🤔🤔 这个问题小编暂时还未解决方法,如有必要,后续会出一篇文件讲解这个问题。

      c 复制代码
      //任务控制权柄
      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函数实现的,但是一旦出现该函数,程序就会死机;因此,小编打算使用空闲任务来定时激活两个任务,这里的前提条件是两个任务一旦执行完一次就必须进入挂起状态(阻塞状态),因为只有这样,该空闲任务才能重新激活已经挂起的任务。

      c 复制代码
      //任务控制权柄
      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]);										
      }

      移植时遇到的典型问题

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

      问题描述:该报错是因为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,尝试解决该错误🤣。

      第一次在蓝桥杯嵌入式STM32G431RBT6开发板上使用FreeRTOS操作系统点亮LED灯 完整视屏



      追更2:解决FreeRTOS无法与LCD一起使用的问题以及使用CubeMX建立FreeRTOS项目

      针对上述代码LCD无法与FreeRTOS同时使用的问题,其主要原因是FreeRTOS的时钟配置问题

      解决方法1: 小编提出的第一个解决方法,是配置FreeRTOS时钟与STM32时钟,即解决FreeRTOS时钟配置问题。

      • 步骤1:在freeRTOSConfig.h文件中添加以下FreeRTOS与STM32时钟相关的配置;
      c 复制代码
      /* 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的三个任务分别为

      c 复制代码
      /* 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 */
      }
相关推荐
_小猪沉塘2 小时前
L11&12&13 【哈工大_操作系统】内核级线程&内核级线程实现&操作系统之“树”
操作系统
醉颜凉11 小时前
银河麒麟桌面操作系统V10 SP1:取消安装应用的安全授权认证
运维·安全·操作系统·国产化·麒麟·kylin os·安全授权认证
碳苯18 小时前
【rCore OS 开源操作系统】Rust 枚举与模式匹配
开发语言·人工智能·后端·rust·操作系统·os
安红豆.1 天前
Linux基础入门 --13 DAY(SHELL脚本编程基础)
linux·运维·操作系统
shimly1234561 天前
(undone) 阅读 MapReduce 论文笔记
操作系统
碳苯2 天前
【rCore OS 开源操作系统】Rust HashMap应用 知识点及练习题
开发语言·rust·操作系统
碳苯2 天前
【rCore OS 开源操作系统】Rust mod模块和static生命周期 知识点及练习题
rust·操作系统·生命周期·模块·os
碳苯2 天前
【rCore OS 开源操作系统】Rust 练习题题解: Structs
rust·开源·操作系统·os·内存安全
结衣结衣.3 天前
Linux——环境变量
linux·运维·服务器·c语言·笔记·学习·操作系统
结衣结衣.3 天前
程序地址空间
linux·c语言·笔记·学习·操作系统