FreeRTOS学习day1:Keil 工程配置与 FreeRTOS 链表机制理解

今天主要复盘了两个部分:一是 Keil 工程配置中的 Linker、Scatter File、时钟配置问题 ;二是 FreeRTOS 内核中链表结构、类型转换和常用宏定义的理解


一、Keil 中 Linker 和 Scatter File 的理解

一开始遇到的报错是:

复制代码
Error: L6218E: Undefined symbol Image$$ARM_LIB_STACK$$ZI$$Limit

这个错误不是 main.c 的语法问题,而是链接阶段的问题

Keil 构建工程大致分为几个阶段:

复制代码
.c 文件
  ↓ 编译
.o 目标文件
  ↓ 链接
.axf 可执行文件
  ↓ 下载/调试

其中 Compiler 编译器 负责把每个 .c 文件单独编译成 .o 文件,而 Linker 链接器 负责把这些 .o 文件组合起来,生成最终的 .axf 文件。

Linker 主要做三件事:

  1. 把多个 .o 文件合并成完整程序;
  2. 解决函数、变量之间的引用关系;
  3. 根据内存布局文件,把代码和数据放到正确的 Flash/RAM 地址中。

Keil 里面的 .sct 文件就是 Scatter File,也就是内存布局说明文件。它告诉链接器:

复制代码
复制代码
代码放在哪里
只读数据放在哪里
全局变量放在哪里
堆和栈放在哪里

比如 Cortex-M3 常见地址是:

复制代码
复制代码
Flash 起始地址:0x00000000
RAM   起始地址:0x20000000

之前报错的原因是启动文件需要一个栈顶地址符号:

复制代码
复制代码
Image$$ARM_LIB_STACK$$ZI$$Limit

但是 Linker 没有从当前的 Scatter File 中找到对应的栈区定义,所以无法生成这个符号。后来把 Scatter File 修改成:

复制代码
复制代码
.\RTE\Device\ARMCM3\ARMCM3_ac6.sct

参考博客:[Keil5报错:Error: L6218E: Undefined symbol ImageARM_LIB_STACKZILimit_keil5 l6218e-CSDN博客](https://blog.csdn.net/m0_63116406/article/details/135585040?spm=1001.2014.3001.5506 "Keil5报错:Error: L6218E: Undefined symbol ImageARM_LIB_STACKZILimit_keil5 l6218e-CSDN博客")


二、ULU 这类整数后缀的含义

system_ARMCM3.c 里面看到:

复制代码
#define  XTAL            (50000000UL)
#define  SYSTEM_CLOCK    (XTAL / 2U)

这里的 UL 是 C 语言整数常量后缀。

复制代码
U  = unsigned,无符号
L  = long,长整型
UL = unsigned long,无符号长整型

所以:

复制代码
50000000UL

表示这个数字是一个 unsigned long 类型的常量。

而:

复制代码
2U

表示数字 2 是一个 unsigned int 类型。

这样写的好处是类型更明确,尤其在嵌入式里,时钟频率、寄存器值、地址值通常都希望使用无符号类型,避免符号扩展或者编译警告。

这段代码的含义是:

复制代码
XTAL = 50 MHz
SYSTEM_CLOCK = XTAL / 2 = 25 MHz

也就是系统时钟为 25 MHz。


三、Keil 中 Xtal 灰色无法修改的原因

在 Keil 的 Target 页面里看到:

复制代码
Xtal (MHz): <undefined>

而且是灰色的,无法修改。

原因是当前工程选择的是:

复制代码
ARM ARMCM3

这是一个通用 Cortex-M3 内核模板 ,不是某一颗真实芯片。Keil 不知道这颗"芯片"的外部晶振是多少 MHz,所以这个地方显示 <undefined>,并且不能修改。

如果选择真实芯片,例如 STM32F103、LPC1768 等,Keil 可能会根据芯片包提供更多可配置项。


四、FreeRTOS 链表的基本结构

今天重点学习了 FreeRTOS 的链表机制。

FreeRTOS 链表不是普通的 NULL 结尾链表,而是一个带哨兵节点的双向循环链表

核心结构有三个:

复制代码
typedef struct xLIST_ITEM
{
    TickType_t xItemValue;
    struct xLIST_ITEM * pxNext;
    struct xLIST_ITEM * pxPrevious;
    void * pvOwner;
    struct xLIST * pxContainer;
} ListItem_t;

普通链表节点 ListItem_t 中:

复制代码
xItemValue   :排序辅助值
pxNext       :下一个节点
pxPrevious   :上一个节点
pvOwner      :节点拥有者,通常是 TCB
pxContainer  :该节点当前属于哪个链表

链表本体大致是:

复制代码
typedef struct xLIST
{
    UBaseType_t uxNumberOfItems;
    ListItem_t * pxIndex;
    MiniListItem_t xListEnd;
} List_t;

其中:

复制代码
uxNumberOfItems :链表中真实节点数量
pxIndex         :链表遍历游标
xListEnd        :链表结束标记节点,也叫哨兵节点

空链表不是 NULL,而是:

复制代码
xListEnd.pxNext     指向 xListEnd 自己
xListEnd.pxPrevious 指向 xListEnd 自己
uxNumberOfItems     等于 0

有节点时结构类似:

复制代码
xListEnd <-> A <-> B <-> C <-> xListEnd

这种设计的好处是:插入、删除、遍历时不需要频繁判断头节点、尾节点或者空指针,代码更统一。


五、FreeRTOS 中的类型转换

之前看到这句代码:

复制代码
pxList->xListEnd.pxNext = ( ListItem_t * ) &pxList->xListEnd;

这句代码的意思是:

复制代码
让 xListEnd 的 next 指针指向它自己

也就是初始化空链表。

其中:

复制代码
&pxList->xListEnd

表示取 xListEnd 的地址。

而:

复制代码
( ListItem_t * )

是强制类型转换,把 xListEnd 的地址转换成 ListItem_t * 类型。

这里之所以需要强制转换,是因为 xListEnd 的真实类型是 MiniListItem_t,而 pxNext 的类型是 ListItem_t *

MiniListItem_tListItem_t 少了后两个成员:

复制代码
pvOwner
pxContainer

但它们前三个成员布局一致:

复制代码
xItemValue
pxNext
pxPrevious

所以 FreeRTOS 可以把 MiniListItem_t 当成一个简化版的 ListItem_t 使用。这样既能作为链表结束节点,又能节省内存。


六、删除链表节点时为什么要处理 pxIndex

uxListRemove() 中看到这段代码:

复制代码
if( pxList->pxIndex == pxItemToRemove )
{
    pxList->pxIndex = pxItemToRemove->pxPrevious;
}

这段代码的作用是:如果链表的遍历游标正好指向要删除的节点,就必须把游标移到一个仍然有效的节点上。

比如链表是:

复制代码
xListEnd <-> A <-> B <-> C <-> xListEnd

假设:

复制代码
pxIndex = B
pxItemToRemove = B

如果删除 B 之后不处理 pxIndex,那么 pxIndex 仍然指向 B,但 B 已经不在链表中,这样后续遍历可能出错。

所以删除前要把它改成:

复制代码
pxList->pxIndex = pxItemToRemove->pxPrevious;

也就是让 pxIndex 退回到 B 的前一个节点 A。

为什么不是指向 B 的后一个节点 C?

因为 FreeRTOS 在获取下一个节点时,通常会先执行:

复制代码
pxIndex = pxIndex->pxNext;

如果删除 B 后让 pxIndex = A,下一次遍历时就会走到 C,刚好不会跳过节点。

如果直接让 pxIndex = C,下一次再执行 pxIndex = pxIndex->pxNext,就会跳过 C。

所以这里设置为 pxPrevious 是为了配合 FreeRTOS 的遍历逻辑。

相关推荐
心中有国也有家1 小时前
使用 DevEco Studio 配置 Flutter 鸿蒙签名
学习·flutter·华为·harmonyos
灯澜忆梦1 小时前
MySQL 完整学习路线
学习·mysql
森林古猿12 小时前
再论斜率优化
c++·学习·算法
心中有国也有家3 小时前
鸿蒙Flutter开发环境从零搭建教程(Windows/macOS双平台·避坑版)
学习·flutter·华为·harmonyos
十月的皮皮4 小时前
C语言学习笔记20260706-栈的压入、弹出序列验证
c语言·笔记·学习
心中有国也有家5 小时前
Flutter 鸿蒙适配第一步:从 hive 迁移到 hive\_ce
hive·学习·flutter·华为·harmonyos
utf8mb4安全女神5 小时前
【Redis数据库】哨兵集群/redis集群/安装配置/主从复制/数据持久化操作/数据结构/安全限制/PHP redis/
linux·服务器·数据结构·数据库·redis·缓存
渣渣灰飞6 小时前
MySQL 系统学习 第二阶段 第三章 DQL(Data Query Language)第二节:WHERE 条件查询
sql·学习·mysql
心中有国也有家6 小时前
AtomGit Flutter 鸿蒙客户端:E-Brufen 架构设计
学习·flutter·华为·harmonyos