目录
0x03 使用CubeMX将RTOS部署到STM32L151
0x05 STM32L151移植FreeRTOS时需要port.c文件依据
0x01 FreeRTOS编程风格
一、数据类型
在 FreeRTOS 中,使用的数据类型虽然都是标准 C 里面的数据类型,但是针对不同的 处理器,对标准 C 的数据类型又进行了重定义,给它们取了一个新的名字,比如 char 重新定义了一个名字 portCHAR,这里面的 port 表示接口的意思,就是 FreeRTOS 要移植到这些处理器上需要这些接口文件来把它们连接在一起。
其主要的规定位于文件portmacro.h:
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE long
typedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
#if( configUSE_16_BIT_TICKS == 1 ) (1)
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif
在 FreeRTOS 中,int 型从 不使用,只使用 short 和 long 型。在 Cortex-M 内核的 MCU 中,short 为 16 位,long 为 32 位。
在 FreeRTOS 中,我们都需要明确的指定变量 char 是有符号的还是无符号的。设定时可以进入keil中进行配置:

二、变量名、函数名、宏的规定
在 FreeRTOS 中,定义变量的时候往往会把变量的类型当作前缀加在变量上,这样的好处是让用户一看到这个变量就知道该变量的类型。
变量规定:
-
char 型变量的前缀是 c,short 型变量的前缀是 s,long 型变量的前缀是 l, portBASE_TYPE 类型变量的前缀是 x。
-
其他 的数据类型,比如数据结构,任务句柄,队列句柄等定义的变量名的前缀也是 x。
-
无符号型的会有一个前缀 u。
-
指针变量会有一个前缀 p。
函数名规定:
-
函数名包含了函数返回值类型、函数所在的文件名和函数的功能,如果是私有的函数则会加一个 prv(private)的前缀。
vTaskPrioritySet()函数的返回值为 void 型,在task.c 这个文件中定义。
xQueueReceive()函数的返回值为 portBASE_TYPE 型,在 queue.c 这个文件中定义。
vSemaphoreCreateBinary()函数的返回值为 void 型,在 semphr.h 这个文件中定义。
宏的规定:
-
宏均是由大写字母表示,并配有小写字母的前缀,前缀用于表示该宏在哪个头文件定义。
-
信号量的函数都是一个宏定义,但是它的函数的命名方法是遵循函数的命名方法而不是宏定义的方法。
-
通用宏,表示0和1的宏:

0x02 FreeRTOS内核原理(链表)
一、裸机与操作系统的区别
裸机系统通常分成轮询系统和前后台系统:
-
轮询系统即是在裸机编程的时候,先初始化好相关的硬件,然后让主程序在一个死循环里面不断循环,顺序地做各种事情。轮询系统只适合顺序执行的功能代码,当有外部事件驱动时,实时性会降低。
-
相比轮询系统,前后台系统是在轮询系统的基础上加入了中断。外部事件的响应在中断里面完成,事件的处理还是回到轮询系统中完成,中断在这里我们称为前台,main 函数里面的无限循环我们称为后台。
在顺序执行后台程序的时候,如果有中断来临,那么中断会打断后台程序的正常执行流,转而去执行中断服务程序,在中断服务程序里面标记事件,如果事件要处理的事情很简短,则可在中断服务程序里面处理,如果事件要处理的事情比较多,则返回到后台程序里面处理。虽然事件的响应和处理是分开了,但是事件的处理还是在后台里面顺序执行的,但相比轮询系统,前后台系统确保了事件不会丢失,再加上中断具有可嵌套的功能,这可以大大的提高程序的实时响应能力。在大多数的中小型项目中,前后台系统运用的好,堪称有操作系统的效果。
二、多任务系统
相比前后台系统中后台顺序执行的程序主体,在多任务系统中,根据程序的功能,我 们把这个程序主体分割成一个个独立的,无限循环且不能返回的小程序,这个小程序我们称之为任务。每个任务都是独立的,互不干扰的,且具备自身的优先级,它由操作系统调度管理。加入操作系统后,我们在编程的时候不需要精心地去设计程序的执行流,不用担 心每个功能模块之间是否存在干扰。整个系统随之带来的额外开销就是操作系统占据的那一丁点的 FLASH 和 RAM。
三、数据结构
单链表
单链表:该链表中共有 n个节点,前一个节点都有一个箭头指向后一个节点,首尾相连,组成一个圈。

节点都是一个自定义类型的数据结构,在这个数据结构里面可以有单个的数据、数组、指针数据和自定义的结构体数据类型等等信息:
struct node
{
struct node *next; /* 指向链表的下一个节点 */
char data1; /* 单个的数据 */
unsigned char array[]; /* 数组 */
unsigned long *prt /* 指针数据 */
struct userstruct data2; /* 自定义结构体类型数据 */
/* ...... */
}
通常的做法是节点里面只包含一个用于指向下一个节点的指针。要通过链表存储的数据内嵌一个节点即可,这些要存储的数据通过这个内嵌的节点即可挂接到链表中:
/* 节点定义 */
struct node
{
struct node *next; /* 指向链表的下一个节点 */
}
struct userstruct
{
/* 在结构体中,内嵌一个节点指针,通过这个节点将数据挂接到链表 */
struct node *next;
/* 各种各样......,要存储的数据 */
}
链表的操作
链表常规的操作就是节点的插入和删除,为了顺利的插入,通常一条链表我们会人为地规定一个根节点,这个根节点称为生产者。通常根节点还会有一个节点计数器,用于统计整条链表的节点个数。

双向链表
双向链表与单向链表的区别就是节点中有两个节点指针,分别指向前后两个节点,其它完全一样。
四、FreeRTOS中链表的表现
FreeRTOS 中与链表相关的操作均在 list.h和list.c 这两个文件中实现。
定义链表节点的数据结构
struct xLIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
void * pvOwner; /* 指向拥有该节点的内核对象,通常是 TCB */
void * pvContainer; /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */
链表节点初始化
初始化函数主要位于list.c中:
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
pxItem->pvContainer = NULL;
}
链表节点 ListItem_t 总共有 5 个成员,但是初始化的时候只需将pvContainer 初始化为空即可,表示该节点还没有插入到任何链表。
实现链表根节点
根节点数据结构位于list.h:
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 链表节点计数器 */
ListItem_t * pxIndex; /* 链表节点索引指针 */
MiniListItem_t xListEnd; /* 链表最后一个节点 */
} List_t;

该根节点为生产者,链表为首尾相连的结构,该生产者的数据类型是一个精简节点,于list.h:
struct xMINI_LIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做升序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 精简节点数据类型重定义 */
在FreeRTOS中链表的操作
-
根节点初始化函数
void vListInitialise( List_t * const pxList ) -
将节点插入到链表尾部
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem ) -
将节点按照升序排列插入到链表
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem ) -
将节点从链表中删除
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
0x03 使用CubeMX将RTOS部署到STM32L151
首先是新建工程,之后选择MCU以及对应的封装,接下来就是配置时钟了:
配置调试模式:

需要注意的是对sys的配置中,需要选择除了SysTick之外的时钟作为HAL库的时基:

-
在裸机运行时,可以通过systick或TIMx定时器来维护SYS Timebase Source,也就是HAL库中的uwTick,这是HAL库中维护的一个全局变量,在裸机的情况下,我们可以选择SysTick方式即可,直接放在SysTick_Handler()中断服务函数中来维护。
-
带OS的运行时,SYS Timebase Source是STM32的HAL库中新增的部分,主要是实现于
HAL_Delay()以及各种作为timeout的时钟基准。在使用了OS(操作系统)之后,OS的运行也需要一个时钟基准(简称“时基”),来对任务和时间等进行管理。而OS的这个 时基 一般也都是通过SysTick(滴答定时器) 来维护的,这时就需要考虑 “HAL的时基” 和 “OS的时基” 是否要共用SysTick(滴答定时器) 了。
使用RTOS时,最好也不要共用SysTick,据哟潜在的风险,并且最后生成代码的时候会有这样的提示:

在最后程序生成后,可以看到我们设置的SYS使用的时基是什么:
在main()->HAL_Init()->HAL_InitTick(TICK_INT_PRIORITY);其中TICK_INT_PRIORITY的值为0,此时tim2的中断优先级是最高的。在tim2的中断函数中,我们可以看到,HAL库使用tim2的更新中断作为了时钟源:

文章详细介绍了FreeRTOS操作系统在STM32L151上的部署过程,包括任务创建、链表原理、中断处理、任务调度和任务切换的实现。重点阐述了任务创建的函数接口、任务控制块、栈初始化和任务调度器的工作机制,强调了FreeRTOS如何利用链表管理任务状态,并通过中断服务函数实现任务切换。
最低0.47元/天 解锁文章
637

被折叠的 条评论
为什么被折叠?



