引言: 本文专门为参加了蓝桥杯嵌入式赛道的同学准备, 大家可能会有这样一个问题, 比完赛之后, 对于像继续使用STM32G431RBT6学习FreeRTOS的, 发现网上的教程使用的板子基本上都是F1和F4的, 其实呢, 随便移植一下就能在我们自己的板子上面运行FreeTROS了。如果大家有ARM Linux的学习经历, 比如系统移植的基础, 那再来学这个FreeRTOS就比较容易了。
目录
一、FreeRTOS简介
二、什么是移植
三、移植实验开始
1.源码下载
2.源码目录结构简单分析
3.创建工程
4.开始移植
5.各种报错开始
四、创建几个任务看看移植效果
五、FreeRTOS为什么能进行裁剪
六、总结
七、FreeRTOS学习资源
一、FreeRTOS简介
首先看一下FreeRTOS 的名字,可以分为两部分:“Free”和“RTOS”,“F ree”就是免费的、自由的、不受约束的意思,“RTOSRTOS”全称是R eal Time Operating System System,中文名就是实时操作系统,要注意的是,RTOS 并不是值某一特定的操作系统,而是指一类操作系统,例如,µ C/OS OS,FreeRTOSFreeRTOS,RTXRTX,RT T hread 等这些都是RTOS 类的操作系统。因此,从FreeRTOS 的名字中就能看出,F reeROTS 是一款免费的实时操作系统。
操作系统是允许多个任务“同时运行”的,操作系统的这个特性被称为多任务。然而实际上,一个CPU 核心在某一时刻只能运行一个任务,而操作系统中任务调度器的责任就是决定在某一时刻CPU 究竟要运行哪一个任务,任务调度器使得CPU 在各个任务之间来回切换并处理任务,由于切换处理任务的速度非常快,因此就给人造成了一种同一时刻有多个任务同时运行的错觉。
操作系统的分类方式可以由任务调度器的工作方式决定,比如有的操作系统给每个任务分配同样的运行时间,时间到了就切换到下一个任务,Unix 操作系统就是这样的。RTOS 的任务调度器被设计为可预测的,而这正是嵌入式实时操作系统所需要的。在实时环境中,要求操作系统必须实时地对某一个事件做出响应,因此任务调度器的行为必须是可预测的。像F reeRTOS这种传统的RTOS 类操作系统是由用户给每个任务分配一个任务优先级,任务调度器就可以根据此优先级来决定下一刻应该运行哪个任务。
FreeRTOS 是众多RTOS 类操作系统中的一种,F reeRTOS 十分的小巧,可以在资源有限的微控制器中运行,当然了,F reeRTOS 也不仅仅局限于在微控制器中使用。就单从文件数量上来看F reeRTOS 要比µ C/OS 少得多。
国内外常见的几种物联网操作系统:
题外话: Linux和物联网操作系统的区别
Linux是一个通用的操作系统内核,最初由Linus Torvalds创建,用于各种计算设备,从个人电脑到服务器再到嵌入式系统。Linux内核是一个开放源代码项目,因此可以根据需要进行定制和修改。物联网操作系统则是专门为连接到互联网的物联网设备设计的操作系统,通常具有以下特点:
1. 资源受限性:物联网设备通常具有有限的处理能力、内存和存储空间,因此物联网操作系统需要具备轻量级和高效能的特性。
2. 实时性:物联网应用中的某些任务可能对时间敏感,需要在特定时间内完成。因此,一些物联网操作系统提供实时性能,以确保任务的及时执行。
3. 低功耗:许多物联网设备需要长时间运行,因此物联网操作系统需要优化能源消耗,以延长设备的电池寿命或降低能源成本。
4. 通信支持:物联网操作系统通常需要支持各种通信协议,如Wi-Fi、蓝牙、LoRa等,以便设备能够与其他设备或云平台进行通信。
5. 安全性: 物联网设备可能面临各种安全威胁,因此物联网操作系统需要提供安全功能,如数据加密。
上面这张图是基于LInux内核开发的几款操作系统。
二、什么是移植
当在嵌入式系统中从一个硬件平台或操作系统移植软件时,我们称之为移植(Porting)。移植意味着将软件(通常是操作系统、驱动程序或应用程序)从一个平台或环境移植到另一个平台或环境,使其能够在目标平台上运行。 移植通常涉及以下几个方面:
硬件适配:目标平台通常具有不同的处理器架构、外设、存储器和输入/输出接口等硬件特性。因此,移植过程需要对软件进行适配,以确保其能够与目标硬件兼容并正确运行。这可能涉及修改驱动程序或底层硬件抽象层(HAL)。
操作系统适配: 如果软件依赖于特定的操作系统,那么在将其移植到新平台时,需要确保目标平台上有相应的操作系统支持。如果目标平台上没有现成的操作系统支持,可能需要移植一个新的操作系统或调整现有操作系统以适应目标平台。
编译和链接: 移植软件可能需要调整编译器、链接器和构建工具链以适应目标平台的体系结构和工具集。
性能优化:在移植软件时,通常需要对性能进行优化,以确保在目标平台上能够达到合理的性能水平。这可能涉及调整算法、数据结构或代码结构,以充分利用目标平台的硬件资源。
功能兼容性:移植软件时需要确保其功能在目标平台上能够正常运行,并且与原始平台上的行为保持一致。
总的来说,移植是将软件从一个环境移植到另一个环境的过程,涉及到对硬件、操作系统、编译工具链和性能进行适配和优化,以确保软件能够在目标平台上正确运行。想象成人类的器官移植, 在移植之前是不是得适配才能正常的工作。
三、移植实验开始
移植实验开始之前相信大家肯定都是有裸机开发的基础的, 提前说一下我对操作系统的理解, 没有装操作系统之前, 我们下载到单片机中运行的程序都叫做裸机程序, 那么, 操作系统又是什么呢, 其实操作系统就是第一个裸机程序, 它的作用是为应用程序的执行提供运行环境。如果在有人问题操作系统是什么, 你就可以说操作系统其实就是一个裸机程序, 为应用程序的运行提供运行的环境。对于Linux这样的通用型操作系统内核来说, 它的作用和上面一样, 但是还需要加上这几句话, 向上提供接口, 向下控制硬件, 它的几大功能模块分别是内存管理, 进程管理、文件系统、设备驱动、网络协议栈。
上面这张图就展示了Linux整体框架构图。
1.源码下载
官网: www.freertos.org
下载可能有些慢, 嫌慢的兄弟可以去码云下载。
2.源码目录结构简单分析
3.创建工程
这里使用cubeMx创建工程我就大致掠过了, 相信大家都是会的。
打开Keil工程
编译一下
接着去到源码目录下, 准备一个存放BSP相关代码的地方
头文件的地方我们也来一个
4.开始移植
去到FreeRTOS源码位置, 将内存管理, 和M4相关的代码拿到我们创建号的工程目录下
继续, 把下面的代码移植过去先
接着把FreeRTOS的头文件拿过来
其实这些步骤大家可以自己按照自己的习惯来,这些都不是关键的地方。
修改一下调试器相关的内容
到此我们先编译一下, 说找不到这个配置文件, 这个文件很重要, 稍后做出解释。
这个文件有几种办法获取, 第一自己编写, 需要对整个FreeRTOS的源码非常的熟悉, 不推荐, 第二去源码中找示例, 推荐。
将其拷贝到我们的工程目录的头文件路径下面去
又报错了, 头文件找不到
下问题, 找一下
这个位置, 存放cpu代码的目录下面有一个头文件, 加一下
5.各种报错开始
这个错误出现的原因是因为FreeRTOS的源码中因为需要匹配各种架构的cpu和板卡, 使用了大量的条件编译, 以达到根据条件编译的定义内容编译不同的架构的代码。
方便大家复制
#if defined (__ICCARM__) || defined (__CC_ARM__) || defined (__GNUC__)
#include <stdint.h>
extern uint32_t SystemCoreClock;
#endif
继续报错
把FreeRTOSConfig.h文件包含在port.c文件中, 这样就能找到了
再次编译
又又报错了, 这次的原因是因为这几个异常的ISR定义重复了, 为什么会重复呢, 是因为在FreeRTOS的任务调度代码中就是利用了这几个异常的机制, 实现的任务每隔一个时间片调度依次任务。就是FreeRTOS定义了。
请看上图, 在FreeRTOS的配置文件中定义了, 所谓的配置文件, 没办法因为咱们嵌入式开发是没有啥子精美的界面的, 又不可能搞一个很漂亮的界面给我们自己根据cpu的架构, 板卡的资源信息自行选配, 所以就是用了大量的宏开关, 简单来说就是编译器在编译整个项目源码的时候, 会根据这些宏开关,或者说是符号的定义轻快选择不同的代码编译。
回到这几个ISR符号重复定义的问题, 解决办法是去到我们的xxx_it.c中注释掉这几个符号
再次编译
又又又报错了, 说这几个函数没有定义,是因为我们的配置文件选上了这些配置, 解决办法是去到配置文件中将相应的宏开关关闭, 这样在编译的使用就不编译这几个函数了。
修改前
修改后
其实还可以我们自己定义这些钩子函数, 其实就是回调函数, 是官方留给我们的接口, 可以自己定义在出现这些错误的时候, 做一些出错处理
再次编译
0error 0warning, nice, 恭喜, 大家马上接近成功了。为什么这样说呢, 是因为咱们的这个栈空间的设置是有问题的, 一会再最后给大家演示。
这个是因为我们再创建工程的时候没有用到adc uart timer等, 所以Mx没有拷贝对应的定以到工程中。我们自己添加一下
打开咱们使用到的
如果大家有移植的是由栈空间不够了,教大家怎么修改
但还是要基于我们的硬件来改。
内存布局:
四、创建几个任务看看移植效果
http://www.freertos.org
可以去FreeRTOS的官网查看API示例, 还是非常简单的这些函数
#include "main.h"
#include "bsp_led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "bsp_uart.h"
#include <stdio.h>
#include "FreeRTOSConfig.h"
#include "lcd.h"
TaskHandle_t start_handle;
void start_task(void *arg);
void FreeRTOS_Entry(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
Led_Init();
Uart_Init();
FreeRTOS_Entry();
}
void FreeRTOS_Entry(void)
{
BaseType_t retval;
printf("1\r\n");
retval = xTaskCreate( start_task,
(const char *) " start_task",
128,
NULL,
1,
(TaskHandle_t *)&start_handle
);
if(retval != pdPASS ) {
Error_Handler();
}
printf("2\r\n");
vTaskStartScheduler();
printf("3\r\n");
}
void start_task(void *arg)
{
while(1) {
printf("task running\r\n");
Led_Control(LED_ON, 1);
Led_Control(LED_TOGGLE, 1);
Led_Control(LED_TOGGLE, 3);
Led_Control(LED_TOGGLE, 5);
Led_Control(LED_TOGGLE, 7);
vTaskDelay(1000);
}
}
五、FreeRTOS为什么能进行裁剪
就是因为freeRtos根据不同的不同的硬件情况、包括不同的PCU架构, 不同的Flash SRAM大小,
FreeRTOS还提供不同的内存分配算法。
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* 头文件 */
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include <stdint.h>
extern uint32_t SystemCoreClock;
/* 基础配置项 */
#define configUSE_PREEMPTION 1 /* 1: 抢占式调度器, 0: 协程式调度器, 无默认需定义 */
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 /* 1: 使用硬件计算下一个要运行的任务, 0: 使用软件算法计算下一个要运行的任务, 默认: 0 */
#define configUSE_TICKLESS_IDLE 0 /* 1: 使能tickless低功耗模式, 默认: 0 */
#define configCPU_CLOCK_HZ SystemCoreClock /* 定义CPU主频, 单位: Hz, 无默认需定义 */
//#define configSYSTICK_CLOCK_HZ (configCPU_CLOCK_HZ / 8)/* 定义SysTick时钟频率,当SysTick时钟频率与内核时钟频率不同时才可以定义, 单位: Hz, 默认: 不定义 */
#define configTICK_RATE_HZ 1000 /* 定义系统时钟节拍频率, 单位: Hz, 无默认需定义 */
#define configMAX_PRIORITIES 32 /* 定义最大优先级数, 最大优先级=configMAX_PRIORITIES-1, 无默认需定义 */
#define configMINIMAL_STACK_SIZE 128 /* 定义空闲任务的栈空间大小, 单位: Word, 无默认需定义 */
#define configMAX_TASK_NAME_LEN 16 /* 定义任务名最大字符数, 默认: 16 */
#define configUSE_16_BIT_TICKS 0 /* 1: 定义系统时钟节拍计数器的数据类型为16位无符号数, 无默认需定义 */
#define configIDLE_SHOULD_YIELD 1 /* 1: 使能在抢占式调度下,同优先级的任务能抢占空闲任务, 默认: 1 */
#define configUSE_TASK_NOTIFICATIONS 1 /* 1: 使能任务间直接的消息传递,包括信号量、事件标志组和消息邮箱, 默认: 1 */
#define configTASK_NOTIFICATION_ARRAY_ENTRIES 1 /* 定义任务通知数组的大小, 默认: 1 */
#define configUSE_MUTEXES 1 /* 1: 使能互斥信号量, 默认: 0 */
#define configUSE_RECURSIVE_MUTEXES 1 /* 1: 使能递归互斥信号量, 默认: 0 */
#define configUSE_COUNTING_SEMAPHORES 1 /* 1: 使能计数信号量, 默认: 0 */
#define configUSE_ALTERNATIVE_API 0 /* 已弃用!!! */
#define configQUEUE_REGISTRY_SIZE 8 /* 定义可以注册的信号量和消息队列的个数, 默认: 0 */
#define configUSE_QUEUE_SETS 1 /* 1: 使能队列集, 默认: 0 */
#define configUSE_TIME_SLICING 1 /* 1: 使能时间片调度, 默认: 1 */
#define configUSE_NEWLIB_REENTRANT 0 /* 1: 任务创建时分配Newlib的重入结构体, 默认: 0 */
#define configENABLE_BACKWARD_COMPATIBILITY 0 /* 1: 使能兼容老版本, 默认: 1 */
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 0 /* 定义线程本地存储指针的个数, 默认: 0 */
#define configSTACK_DEPTH_TYPE uint16_t /* 定义任务堆栈深度的数据类型, 默认: uint16_t */
#define configMESSAGE_BUFFER_LENGTH_TYPE size_t /* 定义消息缓冲区中消息长度的数据类型, 默认: size_t */
/* 内存分配相关定义 */
#define configSUPPORT_STATIC_ALLOCATION 0 /* 1: 支持静态申请内存, 默认: 0 */
#define configSUPPORT_DYNAMIC_ALLOCATION 1 /* 1: 支持动态申请内存, 默认: 1 */
#define configTOTAL_HEAP_SIZE ((size_t)(10 * 1024)) /* FreeRTOS堆中可用的RAM总量, 单位: Byte, 无默认需定义 */
#define configAPPLICATION_ALLOCATED_HEAP 0 /* 1: 用户手动分配FreeRTOS内存堆(ucHeap), 默认: 0 */
#define configSTACK_ALLOCATION_FROM_SEPARATE_HEAP 0 /* 1: 用户自行实现任务创建时使用的内存申请与释放函数, 默认: 0 */
/* 钩子函数相关定义 */
#define configUSE_IDLE_HOOK 0 /* 1: 使能空闲任务钩子函数, 无默认需定义 */
#define configUSE_TICK_HOOK 0 /* 1: 使能系统时钟节拍中断钩子函数, 无默认需定义 */
#define configCHECK_FOR_STACK_OVERFLOW 0 /* 1: 使能栈溢出检测方法1, 2: 使能栈溢出检测方法2, 默认: 0 */
#define configUSE_MALLOC_FAILED_HOOK 0 /* 1: 使能动态内存申请失败钩子函数, 默认: 0 */
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0 /* 1: 使能定时器服务任务首次执行前的钩子函数, 默认: 0 */
/* 运行时间和任务状态统计相关定义 */
#define configGENERATE_RUN_TIME_STATS 0 /* 1: 使能任务运行时间统计功能, 默认: 0 */
#if configGENERATE_RUN_TIME_STATS
#include "./BSP/TIMER/btim.h"
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ConfigureTimeForRunTimeStats()
extern uint32_t FreeRTOSRunTimeTicks;
#define portGET_RUN_TIME_COUNTER_VALUE() FreeRTOSRunTimeTicks
#endif
#define configUSE_TRACE_FACILITY 1 /* 1: 使能可视化跟踪调试, 默认: 0 */
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 /* 1: configUSE_TRACE_FACILITY为1时,会编译vTaskList()和vTaskGetRunTimeStats()函数, 默认: 0 */
/* 协程相关定义 */
#define configUSE_CO_ROUTINES 0 /* 1: 启用协程, 默认: 0 */
#define configMAX_CO_ROUTINE_PRIORITIES 2 /* 定义协程的最大优先级, 最大优先级=configMAX_CO_ROUTINE_PRIORITIES-1, 无默认configUSE_CO_ROUTINES为1时需定义 */
/* 软件定时器相关定义 */
#define configUSE_TIMERS 1 /* 1: 使能软件定时器, 默认: 0 */
#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) /* 定义软件定时器任务的优先级, 无默认configUSE_TIMERS为1时需定义 */
#define configTIMER_QUEUE_LENGTH 5 /* 定义软件定时器命令队列的长度, 无默认configUSE_TIMERS为1时需定义 */
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2) /* 定义软件定时器任务的栈空间大小, 无默认configUSE_TIMERS为1时需定义 */
/* 可选函数, 1: 使能 */
#define INCLUDE_vTaskPrioritySet 1 /* 设置任务优先级 */
#define INCLUDE_uxTaskPriorityGet 1 /* 获取任务优先级 */
#define INCLUDE_vTaskDelete 1 /* 删除任务 */
#define INCLUDE_vTaskSuspend 1 /* 挂起任务 */
#define INCLUDE_xResumeFromISR 1 /* 恢复在中断中挂起的任务 */
#define INCLUDE_vTaskDelayUntil 1 /* 任务绝对延时 */
#define INCLUDE_vTaskDelay 1 /* 任务延时 */
#define INCLUDE_xTaskGetSchedulerState 1 /* 获取任务调度器状态 */
#define INCLUDE_xTaskGetCurrentTaskHandle 1 /* 获取当前任务的任务句柄 */
#define INCLUDE_uxTaskGetStackHighWaterMark 1 /* 获取任务堆栈历史剩余最小值 */
#define INCLUDE_xTaskGetIdleTaskHandle 1 /* 获取空闲任务的任务句柄 */
#define INCLUDE_eTaskGetState 1 /* 获取任务状态 */
#define INCLUDE_xEventGroupSetBitFromISR 1 /* 在中断中设置事件标志位 */
#define INCLUDE_xTimerPendFunctionCall 1 /* 将函数的执行挂到定时器服务任务 */
#define INCLUDE_xTaskAbortDelay 1 /* 中断任务延时 */
#define INCLUDE_xTaskGetHandle 1 /* 通过任务名获取任务句柄 */
#define INCLUDE_xTaskResumeFromISR 1 /* 恢复在中断中挂起的任务 */
/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 /* 中断最低优先级 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 /* FreeRTOS可管理的最高中断优先级 */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY
/* FreeRTOS中断服务函数相关定义 */
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler
/* 断言 */
#define vAssertCalled(char, int) printf("Error: %s, %d\r\n", char, int)
#define configASSERT( x ) if( ( x ) == 0 ) vAssertCalled( __FILE__, __LINE__ )
/* FreeRTOS MPU 特殊定义 */
//#define configINCLUDE_APPLICATION_DEFINED_PRIVILEGED_FUNCTIONS 0
//#define configTOTAL_MPU_REGIONS 8
//#define configTEX_S_C_B_FLASH 0x07UL
//#define configTEX_S_C_B_SRAM 0x07UL
//#define configENFORCE_SYSTEM_CALLS_FROM_KERNEL_ONLY 1
//#define configALLOW_UNPRIVILEGED_CRITICAL_SECTIONS 1
/* ARMv8-M 安全侧端口相关定义。 */
//#define secureconfigMAX_SECURE_CONTEXTS 5
#endif /* FREERTOS_CONFIG_H */
六、总结
上述实验中虽然实现了将FreeRTOS移植到了比赛指定的板子上面, 但是由于SRAM的大小有限, 当代码量很大的时候就会出现硬件异常。所以咱们比赛使用的板子还是不适合移植FreeRTOS。
七、FreeRTOS学习资源
正点原子资料下载: 正点原子资料下载中心 — 正点原子资料下载中心 1.0.0 文档
B站教程
在这里对正点原子背后的工作人员、授课老师致以崇高的敬意。