HardFault 问题解决方案记录
版本速递 5945 2026-08-31 21:15:12

HardFault 问题解决方案记录

问题概述

现象: 系统运行过程中随机触发 HardFault,导致单片机重启

错误信息:

HardFault_Handler

mcause: 0x00000004 (RISC-V Load Address Misaligned)

mtval: 0x2000000f (出错的内存地址 - 奇数地址,未对齐)

mepc: 0x000028c2 (出错指令 PC,位于 swap_endian 函数内)

问题分析

1. 异常类型分析

mcause = 0x04: RISC-V 异常码 #4,表示 "Load Address Misaligned"(加载地址未对齐)

mtval = 0x2000000f: 触发异常的地址,末位为 0xf(奇数),不是 2 字节或 4 字节对齐地址

mepc = 0x28c2: 异常指令位于 swap_endian 函数内,反汇编显示指令为 lhu a5,0(a0)(加载半字)

2. 代码追踪

通过分析汇编代码和 Map 文件,定位问题调用链:

user_mb_app_m.c:76

slavePollingCallbacksRouter(pucRegBuffer)

→ parse_polling_request_solve()

→ parse_polling_request_solve_bit16()

→ swap_endian(data, info->quantity) ← 崩溃点

3. 根本原因

类型不匹配导致未对齐访问:

user_mb_app_m.c 中调用 slavePollingCallbacksRouter(pucRegBuffer)

参数 pucRegBuffer 类型为 uint8_t*(单字节指针),地址可能是奇数(如 0x2000000f)

函数内部将 uint8_t* 强制当作 uint16_t* 处理

swap_endian 函数直接解引用 uint16_t*:data[i] = ...

RISC-V 架构要求 16 位访问必须 2 字节对齐,奇数地址触发 HardFault

4. 为什么之前没发现问题

Modbus 数据缓冲区通常从偶数地址开始分配,问题被掩盖

流程引擎加入后,内存使用模式改变,缓冲区可能落在奇数地址

RISC-V 架构对内存对齐要求严格(ARM Cortex-M 可以配置允许非对齐访问)

解决方案

修改方案

文件: ModbusTCPtoRTUGateway/User/gateway.c

原代码:

static void swap_endian(uint16_t *data, size_t length)

{

for (size_t i = 0; i < length; ++i)

{

// 直接解引用 uint16_t*,要求地址 2 字节对齐

data[i] = ((data[i] >> 8) & 0xFF) | ((data[i] << 8) & 0xFF00);

}

}

修复后代码:

// 【修复】使用逐字节处理,避免未对齐访问

static void swap_endian(uint16_t *data, size_t length)

{

uint8_t *p = (uint8_t *)data;

for (size_t i = 0; i < length; ++i)

{

// 逐字节读取(安全,不依赖对齐)

uint8_t b0 = p[i * 2];

uint8_t b1 = p[i * 2 + 1];

// 交换后写回

p[i * 2] = b1;

p[i * 2 + 1] = b0;

}

}

修复要点

逐字节访问: 将 uint16_t* 强制转为 uint8_t*,按字节访问

无对齐要求: 字节访问可以在任意地址进行,不受对齐限制

功能等价: 仍然完成大小端交换的功能,只是实现方式更安全

验证测试

测试步骤

重新编译固件

烧录到设备

运行可视化流程,触发 Modbus 读写操作

观察是否还有 HardFault

预期结果

不再出现 mcause: 0x00000004 错误

流程引擎可以正常执行多个周期

Modbus 读写操作正常工作

相关修改记录

本次修改文件

文件

修改内容

原因

gateway.c

修改 swap_endian 函数实现

避免未对齐内存访问

历史相关修改(未完全解决问题)

文件

修改内容

原因

flow_engine.c

使用 memcpy 复制节点到 RAM

解决 Flash XIP packed 结构体未对齐问题

flow_storage.h

添加安全访问辅助函数

提供安全的节点字段访问接口

flow_engine.h

增加任务栈大小到 640

防止栈溢出(后发现不是根本原因)

经验总结

1. RISC-V 架构注意事项

严格对齐要求: 16 位访问必须 2 字节对齐,32 位访问必须 4 字节对齐

与 ARM 差异: ARM Cortex-M 可以配置允许非对齐访问,RISC-V 通常不行

编译器行为: __attribute__((packed)) 结构体中的多字节字段可能未对齐,访问时需要特殊处理

2. 调试技巧

mcause/mepc/mtval: 这三个寄存器是定位 HardFault 的关键

Map 文件: 通过 mepc 地址查找对应函数,缩小问题范围

反汇编: 查看具体指令(如 lhu)确认访问类型和寄存器

3. 防御性编程建议

类型安全: 避免 uint8_t* 和 uint16_t* 混用,必要时使用逐字节访问

结构体定义: packed 结构体中的多字节字段,访问时使用 memcpy 或逐字节读取

代码审查: 检查所有类型强制转换,特别是涉及硬件寄存器或通信缓冲区的地方

附录:相关代码片段

调用链详情

// FreeModbus/port/user_mb_app_m.c:76

eMBErrorCode eMBMasterRegInputCB(..., UCHAR *pucRegBuffer, ...)

{

// pucRegBuffer 是 uint8_t*,可能为奇数地址

slavePollingCallbacksRouter(pucRegBuffer); // ← 传入未对齐指针

}

// gateway.c:2278

void slavePollingCallbacksRouter(uint16_t *data) // ← 被当作 uint16_t*

{

parse_polling_request_solve(receive_info, data);

}

// gateway.c:761

void parse_polling_request_solve_bit16(..., uint16_t *data)

{

swap_endian(data, info->quantity); // ← 解引用触发 HardFault

}

汇编指令分析

28c2: lhu a5,0(a0) # 加载半字(16位),要求 a0 寄存器值为偶数

当 a0 = 0x2000000f(奇数)时,触发 Load Address Misaligned 异常。

记录日期: 2026-04-04

记录人: SHEN.ZT

状态: 已解决,已验证

Copyright © 2022 GXLC网游资讯网-新版本速递_限时活动_礼包兑换 All Rights Reserved.