04V 移植 RT-Thread
主要描述过程中遇到的相关问题和相关记录
程序运行到浮点数时系统崩溃
总结
浮点数操作会导致系统崩溃由 MMU 初始化失败引发
代码
|
|
参数解析
platform_mem_desc 数组定义了一系列内存区域的映射规则,供 MMU(内存管理单元)在系统启动时配置页表。数组中的每个结构体(struct mem_desc)通常包含四个关键参数:
| 序号 | 参数名(通常约定) | 示例值 | 含义 |
|---|---|---|---|
| 1 | 虚拟地址起始 (vaddr_start) |
0x80000000 |
CPU 访问该内存区域时使用的虚拟地址的起始点。 |
| 2 | 虚拟地址结束 (vaddr_end) |
0x80000000 + 0x7E000000 |
CPU 访问该内存区域时使用的虚拟地址的结束点(或紧随其后的地址)。 |
| 3 | 物理地址起始 (paddr_start) |
0x80000000 |
该内存区域在实际硬件上的物理地址的起始点。 |
| 4 | 内存属性 (attr) |
NORMAL_MEM |
定义该内存区域的访问特性和缓存策略。 |
内存属性总结
| 属性宏 | 描述 | 主要用途 | 缓存特性 |
|---|---|---|---|
DEVICE_MEM |
设备内存 | 映射外设寄存器、不可缓存的片上 RAM 等。 | 不可缓存,每次读写直接操作硬件。 |
NORMAL_MEM |
普通内存 | 映射主存储器(DRAM/SDRAM),存放代码、堆栈、数据等。 | 可缓存,由缓存机制加速访问。 |
参数地址说明
根据 04V 空间地址划分, ‘0x00000000 - 0x00030000’ 为 arm 的私有空间 ‘0x30000000 - 0x30B3FFFF’ 系统共享片上外设配置空间 ‘0x80000000 - 0xFFFFFFFF’ 为 DDR 空间
不同的板卡需要根据寄存器地址空间说明来配置
定时器校准
计时不准确的问题
问题描述:
系统中通过 rt_tick_get() 获取的时间与实际时间存在偏差,定时不准确。
分析发现,RT-Thread 内核时钟(tick)由 Generic Timer 提供中断驱动,而定时器频率配置错误导致 tick 周期不正确。
问题原因:
gtimer_set_counter_frequency()中手动设置的CNT_FREQUENCY(如 10MHz)与实际硬件计数器频率不一致。gtimer_set_load_value()参数使用了错误的步进值,导致 tick 中断周期计算错误。
确认硬件计数器频率
操作与分析:
使用 gtimer_get_counter_frequency() 读取系统实际的定时器频率:
|
|
输出结果为:
|
|
说明:
- 该值来自 ARM 架构的 CNTFRQ 寄存器,表示 PL1 Physical Timer(Generic Timer) 的输入时钟频率。
- 该定时器为 ARM Cortex-A 系列(如 Cortex-A15)内置硬件定时器,与 CPU 主频相关但 通常经过分频,即定时器频率 ≤ CPU 主频。
修复方法
修复思路:
使 RT-Thread 的 tick 中断周期与硬件计数频率严格匹配。
具体修改:
-
不再手动设置 CNTFRQ,保持硬件默认值:
1// gtimer_set_counter_frequency(CNT_FREQUENCY); // 删除此行 -
基于实际频率计算 tick 周期:
1 2timer_step = gtimer_get_counter_frequency() / RT_TICK_PER_SECOND; gtimer_set_load_value(timer_step); -
中断服务函数保持一次重装载:
1 2 3 4 5 6 7 8 9 10 11void rt_hw_timer_isr(int vector, void *parameter) { rt_hw_interrupt_mask(GENERIC_TIMER_IRQ_NUM); arm_gic_clear_pending_irq(GENERIC_TIMER_ID0, GENERIC_TIMER_IRQ_NUM); arm_gic_clear_active(GENERIC_TIMER_ID0, GENERIC_TIMER_IRQ_NUM); rt_tick_increase(); // 通知RTT系统时间+1tick gtimer_set_load_value(timer_step); // 仅在ISR中重装载计数值 rt_hw_interrupt_umask(GENERIC_TIMER_IRQ_NUM); }
校准验证结果
验证方法:
同时通过:
- 硬件计数值:
gtimer_get_counter() - 内核tick:
rt_tick_get()
分别计算时间间隔并对比。
验证结果:
两者计时结果完全一致,表明系统 tick 与硬件定时器周期完全同步,计时准确。
补充说明
| 项目 | 说明 |
|---|---|
| PL1 Physical Timer | ARM Cortex-A 架构(如 A15)内置的 Generic Timer,寄存器包括 CNTFRQ、CNTP_TVAL、CNTP_CTL 等。 |
| CPU 主频与定时器频率关系 | 定时器通常由主时钟分频得到,频率可能为主频的 1/2、1/4 等;由 SoC 硬件设计决定。 |
| 准确计时的关键 | 使用 __get_cntfrq() 获取真实频率,并以此计算每 tick 的装载值。 |
|
|
✅ 最终结论::
通过读取硬件实际计数频率(CNTFRQ)并以此计算每 tick 周期,RT-Thread 系统时钟与 ARM Generic Timer 完全同步,实现了高精度计时。
-O2 优化下系统崩溃
💬 问题描述
- 04V 适配 RTT 后,在 debug 模式下运行正常,但切换到 release 模式后,一旦运行浮点数操作,系统崩溃。
- debug 和 release 模式就只有 O0 和 O2 的区别,当优化等级为 O1 时,系统也正常。
✅ 分析过程
对比了特定 GCC 版本和目标架构下,优化级别 -O1 和 -O2 分别启用了哪些具体的优化选项
- 通过分别运行
arm-none-eabi-gcc -O1 -Q --help=optimizers和arm-none-eabi-gcc -O2 -Q --help=optimizers并提供输出文件(O1_opts.txt和O2_opts.txt)实现了这一目标。 - 根据对比结果,将 仅属于
-O2的大量优化选项 手动添加到其rtconfig.py配置文件的-O1编译标志中。 - 报告了一个特定的运行时错误:程序在
-O0和-O1下运行正常,但在-O2下系统崩溃。 - 发现通过禁用
-O2选项列表中的-fschedule-insns2优化后,系统能够正确运行。
✅ 临时解决方法
在 release 模式下,编译标志中添加 -fno-schedule-insns2,以临时禁用导致崩溃的指令调度优化。
开启 neno-vfpv4 会导致系统崩溃
问题描述与初步诊断
初始现象:
系统启动时,使用 04v 等指令初始化 VFP/NEON 单元的寄存器(CPACR、NSACR、FPEXC)后,打印 VFP/NEON 状态为“✅启用”。但在 RT-Thread 开始 do components initialization 阶段或首次线程切换时,系统进入无限的 backtrace: 打印循环(硬错误/异常)。
关键发现:
将编译器参数从 -mfpu=neon-vfpv4 改为 -mfpu=vfpv4 后,系统可正常运行。
诊断结论: 问题不在于 VFP/NEON 单元的**启用(Enable)**本身,而是出在 NEON 相关的上下文保存与恢复逻辑上。VFPv4 仅使用 D0-D15 寄存器,而 NEON/VFPv4 使用 D0-D31 全套 32 个 D 寄存器。因此,问题集中在 D16-D31 的处理上。
根源分析(基于 RT-Thread 源码)
通过分析 RT-Thread 的汇编上下文切换文件 (context_gcc.S) 和栈初始化文件 (stack.c),定位到两个核心错误:
错误原因一:FPU 栈帧空间预留不足 (堆栈溢出/错位):
- 汇编要求 (
context_gcc.S): 在 FPU 启用时,上下文切换代码 (rt_hw_context_switch_exit的恢复逻辑) 会尝试从栈上恢复 66 个 32 位字 (264 字节) 的 FPU 上下文:- D0-D31 寄存器:32 个 64 位寄存器 = 64 个 32 位字
- FPSCR 寄存器:1 个 32 位字
- FPEXC 寄存器:1 个 32 位字
- 原始
stack.c缺陷:rt_hw_stack_init函数中#ifdef RT_USING_FPU部分,只预留了一个 4 字节的字 (*(--stk) = 0;),远小于实际所需的 264 字节。 - 后果: 第一次线程切换时,栈指针被严重错位,导致 CPU 加载错误的 PC/LR 寄存器值,从而跑飞或进入 Hard Fault。
错误原因二:FPU 栈帧压栈顺序与对齐错误:
即使修正了空间不足的问题,如果不按照汇编的恢复顺序来初始化栈帧,或者未满足 ARM EABI 的 8 字节对齐要求,系统仍会崩溃。
- 压栈顺序错误:
context_gcc.S中的 FPU 寄存器恢复顺序是:FPEXC→FPSCR→D16-D31→D0-D15。- 因此,
rt_hw_stack_init的 压栈顺序(从高地址到低地址) 必须是反序的:D0-D15→D16-D31→FPSCR→FPEXC。
- 8 字节对齐错误:
- 通用寄存器栈帧(17 个字) + FPU 栈帧(66 个字) = 83 个字 (332 字节)。
- $332$ 字节不是 $8$ 的倍数,导致栈指针 4 字节错位,违反了 NEON/64 位数据操作的对齐要求,最终导致 Data Abort 或跑飞。
最终解决办法
修正 rt_hw_stack_init:
解决问题的核心在于修正 stack.c 中的 rt_hw_stack_init 函数,确保正确的空间、正确的顺序和正确的对齐。
C 代码修改 (在 stack.c 中):
将 rt_hw_stack_init 函数中的 #ifdef RT_USING_FPU 块替换为以下代码:
|
|
配置调整
- 编译器参数: 确保使用正确的 NEON 选项:
' -mfloat-abi=hard'' -mfpu=neon-vfpv4'
- 栈大小: 由于 FPU 栈帧增加了 268 字节,强烈建议将所有线程的栈大小(如
RT_MAIN_THREAD_STACK_SIZE)适当增加,例如从 2048 调整到 3072 或 4096,以避免线程启动失败(堆分配不足)。
问题
为什么通过寄存器填:0xdeadbeef,而 D0-D15 填0x00000000?
- 通用寄存器 (R4-R12, LR, PC 等): 我们通常用 0xdeadbeef 或其他模式值初始化。
- 0xdeadbeef 是一个调试辅助标记,它的值本身对程序的正确运行没有影响。
- 浮点寄存器(D0-D31)和通用寄存器有着本质的区别:它们保存的是数据,而不是控制流或调试标记。
- 浮点寄存器: 用 0x00000000 来保证数据符合 IEEE 754 规范,并避免在新线程启动时立即触发浮点异常。
开启硬件浮点
|
|
查看标量/向量浮点指令
|
|
串口有输出无输入
在 FTDOC4614B_FT-M7004V 片上集成外设使用手册.pdf 中
2.2.1.7 IRQSn 寄存器 章节介绍:
|
|
2.2.2 寄存器列表 章节介绍:
|
|
因此,需要写 1 软件清除对应的中断事件标志,具体实现如下所示:
在 serial/fpl011 的 FPl011InterruptHandler 中断处理函数中
|
|
清除中断事件标志。
优化后:
|
|
rt-thread-v5.1.0 修改内容
消除警告
-
components/net/lwip/port/ethernetif.c : 584
1 2 3 4/* set name */ // rt_strncpy(netif->name, name, NETIF_NAMESIZE); rt_strncpy(netif->name, name, NETIF_NAMESIZE - 1); netif->name[NETIF_NAMESIZE - 1] = '\0'; -
components/net/netdev/src/netdev.c : 100
1 2 3 4 5/* fill network interface device */ // rt_strncpy(netdev->name, name, RT_NAME_MAX); rt_strncpy(netdev->name, name, RT_NAME_MAX - 1); netdev->name[RT_NAME_MAX - 1] = '\0'; netdev->user_data = user_data; -
src/object6.c : 384
1 2 3 4 5 6 7#if RT_NAME_MAX > 0 // rt_strncpy(object->name, name, RT_NAME_MAX); /* copy name */ rt_strncpy(object->name, name, RT_NAME_MAX - 1); object->name[RT_NAME_MAX - 1] = '\0'; #else object->name = name; #endif /* RT_NAME_MAX > 0 */
bug 修复
libcpu/arm/cortex-a/cpuport.h
|
|
libcpu/arm/cortex-a/stack.c
|
|