ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RH850/F1K MPU自检工程实现:R7F701587权限配置与异常捕获

RH850/F1K MPU自检工程实现:R7F701587权限配置与异常捕获 简介面向瑞萨汽车级三十二位微控制器开发者的内存保护单元自检例程以七z压缩包形式提供专门解决MPU读写及执行权限的验证与调试问题。该芯片支持功能安全ASIL B等级适合汽车电子工程师和单片机学习者使用压缩包共包含七十九个文件大小约七百九十五千字节涵盖C源文件、头文件、Green Hills工程文件、链接脚本、可执行映像及内存映射文件并附有详细说明文档。资源已有四百六十七人学习下载对于正在基于RH850F1K平台开展项目或准备功能安全认证的团队具有直接参考价值。内容除主程序外还提供时钟控制器、复位控制器等底层驱动结合编译输出文件可对照验证MPU访问权限文档解释了特权模式地址窗口配置目录清晰便于导入Green Hills工具进行编译烧录和自检调试。1. 为什么 R7F701587 的 MPU 自检值得单独做成一个工程很多RH850/F1K工程师用GHS时只把MPU当成一个“打开就完事”的寄存器但事实上权限配置写错后第一个异常往往不是来自CPU而是来自调试器突然访问不了内存。R7F701587作为RH850/F1K系列里的汽车级32位MCU带ASIL B功能安全要求MPU就是其中一层安全防线用来限制CPU对指定memory空间的读、写、执行权限。这个例程把MPU自检独立出来通过3_MPU.c主动配置区域、故意触发违规访问验证AccessPrivilege是否真正生效。适合做底层驱动、启动代码或功能安全自检库的工程师也适合刚接触RH850的MCU学习者完整复现一遍寄存器操作流程。工程里附带的example.map、example.list、example.graph等输出文件正好用来反查权限配置和CPU实际访问路径比单纯看手册更直观。2. RH850/F1K 的 MPU 区域寄存器与 AccessPrivilege 权限位模型2.1 从 dr7f701587.dvf.h 找到 MPU 寄存器定义这个工程里带了一套完整的设备头文件包括device.h、dr7f701587.dvf.h、io_macros_v2.h。device.h是总入口芯片相关的寄存器最终落到dr7f701587.dvf.h中cpu.h负责内核相关定义r_typedefs.h提供基础类型。拿到代码后不要先去查手册背地址应该直接在这些头文件里确认MPU寄存器地址是否与当前芯片子型号一致。R7F701587和R7F7015874在保留位、区域数量上可能不同使用头文件里的宏可以避开这些差异。寄存器定义通常长这样/* dr7f701587.dvf.h 内 MPU 相关寄存器地址映射示意 */ #define MPUCTL (*(volatile uint32_t *)0xFFF28000U) /* MPU 控制寄存器 */ #define MPUSTS (*(volatile uint32_t *)0xFFF28004U) /* MPU 状态/违规地址寄存器 */ #define MPUBG0 (*(volatile uint32_t *)0xFFF28200U) /* Region 0 起始地址 */ #define MPUEG0 (*(volatile uint32_t *)0xFFF28208U) /* Region 0 结束地址 */ #define MPUAT0 (*(volatile uint32_t *)0xFFF28210U) /* Region 0 访问属性 */MPUCTL控制MPU模块总开关MPUSTS记录最后一次违规访问的相关信息MPUBGn/MPUEGn决定一块连续地址区间的首尾MPUATn保存该区间的访问权限和总线主控信息。地址注释里的数值是示意真正使用时必须以dr7f701587.dvf.h为准。io_macros_v2.h提供的位操作宏用来做读-改-写避免把保留位冲掉这一点在修改MPUATn时尤其重要。2.2 AccessPrivilege 位字段与 0x91 模板工程名里的AccessPrivilege91H第一次看会有点奇怪我刚开始以为“0x91”就是给用户区域开放读写执行权限。翻完ReadMe.docx和3_MPU.c后更合理的理解是0x91是一个初始模板用来让调试器在连接阶段能进入受保护区域而真正的自检用例会在运行时把区域属性改写成练习册里定义的一组受限权限。MPUATn的位定义在不同子系列上不完全一样需要逐个对照dr7f701587.dvf.h确认bit0、bit1、bit2是否就是R/W/X。下面是在自检中常见的权限组合权限组合自检目的典型触发方式只读R验证写访问被拦截向该区域执行store只写W验证读访问被拦截从该区域执行load可读可执行但禁写验证写访问会被记录正常读后尝试写全禁止0x00验证任何访问都报警load、store、跳转均可自检时最好从单一权限开始比如先配置一块只读区域故意写一次确认MPUSTS里的违规标志能读到再继续扩展其他组合。MPUATx高位可能还带区域使能、总线主控ID、缓存策略等位段不要只改低三位就把整个寄存器写坏。2.3 权限自检的本质是让 CPU 主动踩一次违规自检不能只检查寄存器写完CPU有没有死掉因为部分权限错误会被总线访问掩盖。比如代码跳到保留地址后产生的是另一种异常或者访问落在未映射区域触发的根本就不是MPU异常。因此例程把每个用例都设计成一对操作先执行一次合法访问确认路径通畅再执行一次非法访问确认MPUSTS标志或异常入口被触发。只有两次结果都符合预期该区域的AccessPrivilege才算验证通过。这个“故意出错”的动作不能影响主流程必须放在一个独立的异常回调中否则非法写会把当前栈击穿。接下来的实现部分会给出可直接参考的代码写法。3. 在 GHS 工程里配置 MPU 区域并完成一次非法访问捕获3.1 device.gpj、source.gpj 与 debug.gpj 的构建分工这个例程包含三个.gpj文件对应GHS工程的不同阶段。device.gpj描述R7F701587目标芯片的配置包括内核型号、编译选项和内存映射是其他工程的基础source.gpj是真正的应用工程里面包含main.c、1_ResetController.c、2_ClockController.c、3_MPU.c以及链接脚本dr7f701587.lddebug.gpj供MULTI调试器使用负责下载算法和调试会话参数。命令行构建时可以按顺序执行gbuild -f device.gpj -top gbuild -f source.gpj -top-top表示从最顶层目标重新构建适合第一次解压工程后生成完整输出。如果只是改了一个.c文件不需要加-topGHS会做增量编译。debug.gpj一般不需要在命令行执行MULTI启动调试时会自动读取它并下载example.hex。工程里生成的example.dep是依赖关系文件example.rec是调试记录修改MPU区域后可以用来回放异常触发过程。3.2 复位、时钟与 MPU 的启动顺序启动代码被拆成1_ResetController.c、2_ClockController.c、3_MPU.c是有原因的。MPU一旦使能CPU对内部总线上所有地址空间的访问都会被过滤如果时钟还没稳定代码访问外设寄存器时产生的等待状态可能被总线监测逻辑误判甚至被包装成MPU违规。通常的顺序是先由1_ResetController.c确认芯片退出复位状态并关掉看门狗再由2_ClockController.c把系统时钟与总线时钟切到目标频率最后才执行3_MPU.c配置区域并做自检。main.c里只有在三步完成之后才进入应用逻辑这样一旦MPU自检失败能直接定位到启动阶段。/* main.c 中启动代码调用顺序示意 */ int main(void) { reset_controller_init(); /* 第1步处理复位状态与看门狗 */ clock_controller_init(); /* 第2步配置PLL与总线分频 */ mpu_self_test(); /* 第3步MPU区域配置加自检 */ app_main(); /* 第4步业务逻辑 */ }这里的mpu_self_test()不是只配置一次寄存器它内部会执行多轮权限检查失败时进入error_handler()。只有从mpu_self_test()正常返回后app_main()才能放心依赖MPU提供的保护边界。3.3 mpu_region_set 与访问违规回调的实现在3_MPU.c里区域设置函数是一个典型封装禁能MPU、写区域寄存器、重新使能。参考写法如下static void mpu_region_set(uint32_t idx, uint32_t begin, uint32_t end, uint32_t priv) { MPUCTL ~0x1U; /* MPUCTL bit0 0关闭MPU */ MPUBGx(idx) begin; /* 区域起始地址 */ MPUEGx(idx) end; /* 区域结束地址 */ MPUATx(idx) priv; /* 权限值和主控信息 */ __DSB(); /* 数据同步屏障确保寄存器写入完成 */ MPUCTL | 0x1U; /* 重新使能MPU */ }先关闭MPUCTL再改区域属性是为了避免正在修改起始地址时CPU恰好执行到该区域内部而触发一次不是自检本意的违规。MPUBGx(idx)这里表示设备头文件里按区域编号展开的寄存器宏不同子系列区域间地址间隔不一定相同优先使用宏而不是对地址做偏移。__DSB()在GHS的RH850工具链里映射到对应的同步指令确保后续使能操作不会提前执行。自检的核心用例可以用一块只读RAM区域演示。先在链接脚本里保留下面的测试段再在3_MPU.c中声明数组MPU区域由链接器分配地址就不需要手动指定绝对地址static volatile uint32_t mpu_test_area[64] __attribute__((section(.mpu_test_ram))); void mpu_self_test(void) { volatile uint32_t rd; mpu_region_set(1, (uint32_t)mpu_test_area, (uint32_t)mpu_test_area[63], 0x01U); /* 只读 */ rd mpu_test_area[0]; /* 合法读 */ (void)rd; mpu_test_area[0] 0xDEADBEEFU; /* 非法写预期触发异常 */ /* 如果执行到这里说明MPU没有拦下这次写操作 */ error_handler(MPU_FAULT_NOT_DETECTED); }mpu_region_set的最后一个参数0x01U假设最低位表示读权限实际工程里建议使用dr7f701587.dvf.h提供的宏比如MPU_AT_R。非法写触发异常后CPU会跳转到MPU异常入口异常处理里需要先保存现场再清除MPUSTS标志否则后续用例会把上一次违规状态当成新事件。mpu_test_area定义成volatile uint32_t数组是为了防止编译器把读操作优化掉如果把非法写优化成无效store自检就失效了。4. 用 map、mem、list、graph 对 MPU 自检结果做回归核对4.1 从 example.map 提取区域边界符号MPU自检能通过只能说明寄存器配置和代码逻辑自洽还需要确认链接后的实际地址与预期一致。如果头文件里写的区域地址没问题但链接脚本把变量分配到了另一块内存MPU保护的对象可能就不是目标数据。example.map可以用文本搜索直接定位。在工程根目录执行grep -n 3_MPU.o example.map | head -30 grep -n mpu_test_area example.map | head -20第一条命令用来找3_MPU.c中函数和变量链接后的地址第二条命令确认mpu_test_area被放到哪个段。看到mpu_test_area的地址落在dr7f701587.ld里定义的内部RAM区再回头核对mpu_region_set的begin和end参数就能保证MPU保护范围和链接器分配范围没有错位。如果地址出现在一个被MPU配置为禁读的区域程序会在访问这个数组时先触发自己的自检异常这类问题靠看代码很难发现。4.2 输出文件各自的检查重点工程构建后会生成一组以example开头的输出文件每个文件对应不同的调试信息侧重点不要只盯着example.hex看烧录结果。输出文件检查重点example.map符号地址、段分配、是否有区域重叠example.list反汇编代码核对非法写指令确实是storeexample.graph调用关系确认自检函数在main入口前被调用example.mem内存占用确认MPU区域没有扩展到未初始化RAMexample.siz函数体积观察MPU自检代码是否膨胀到不可接受example.rec调试录制回放异常触发那一刻的PC和总线操作用example.graph看调用关系时重点确认main到mpu_self_test再到mpu_region_set这条链路上没有插入其他会修改MPU区域的函数。.dnm和.dla是符号与下载相关的中间文件通常只在调试器加载异常时才去分析。比如example.dla内容里如果加载地址和链接地址不一致说明调试器可能绕过了物理地址映射导致MPU看到的地址和CPU实际访问地址不同自检结果会失真。4.3 MPU 使能后常见的三个坑单步、下载、中断向量MPU打开后最常见的现象是板子突然连不上调试器或者复位后程序停在奇怪位置。第一个坑是单步执行时断点命中在可执行权限关闭的区域CPU无法取指但仿真器仍尝试插入断点结果是反复复位。第二个坑是调试器通过JTAG写Flash时如果MPU区域覆盖了Flash且没有开放写权限下载算法会失败。第三个坑是中断向量表所在区域的执行权限被错误关闭程序一旦开中断就直接进入异常。遇到这三种情况优先在调试连接阶段关闭MPU完成下载后再执行mpu_self_test()。自检代码里可以提供一个调试旁路宏#ifdef DEBUG_MPU_BYPASS #define MPU_ENABLE() do { /* 调试阶段不使能 */ } while(0) #else #define MPU_ENABLE() do { MPUCTL | 0x1U; } while(0) #endifDEBUG_MPU_BYPASS只应在调试器连接阶段定义产品固件里必须去掉否则MPU一直处于关闭状态自检通过没有任何意义。修改后重新生成example.hex再用example.rec回放一次完整上电过程确认第一次违规的PC指向的是自检代码中的非法store指令而不是随机跳变。5. 把 MPU 自检结果回写到备份 RAM 并让异常地址可追溯启动阶段的MPU自检通过后业务代码运行时仍然需要知道“这一轮上电自检到底过没过”。比较稳妥的做法是把自检结果固化到一个链接脚本保留的RAM地址中这样即使业务代码把整个栈写坏调试器还是能从固定符号读出结论。实现时先在外围声明一个变量再在示例中使用extern volatile uint32_t mpu_result_area; #define MPU_RESULT_OK 0xA5A5A5A5U #define MPU_RESULT_FAIL 0x5A5A5A5AU void mpu_self_test(void) { /* ... 前面的区域配置和用例 ... */ if (fault_detected) { mpu_result_area MPU_RESULT_FAIL; error_handler(MPU_FAULT_DETECTED); } else { mpu_result_area MPU_RESULT_OK; } }mpu_result_area的地址要在dr7f701587.ld中保留不能让它出现在堆栈或常规.bss段里。可以单独分出一个.mpu_dbg段并固定在备份RAM区域内。这样即使后续软件复位只要备份域没有掉电调试器依然能读取上一次的MPU自检结果。异常入口里除了保存结果标志还要把发生违规的PC和地址一起保存下来。MPU违规时MPUSTS会包含触发违规的地址信息第一时间读取并保存之后再清标志避免中断现场被后续操作覆盖volatile uint32_t mpu_last_fault_pc; volatile uint32_t mpu_last_fault_addr; void mpu_fault_handler(void) { mpu_last_fault_pc /* 异常入口保存的返回地址 */; mpu_last_fault_addr MPUSTS 0xFFFFFFFCU; MPUSTS MPUSTS; /* 写1清除错误标志 */ /* 后续根据 mpu_last_fault_pc 反查指令 */ }MPUSTS清标志时是写入值清零而不是读-改-写回0。如果自检用例里连续执行多次非法访问而异常处理函数没有在每条用例之间清标志下一次用例开始时看到的状态还是上次残留值就会把“本次没有触发异常”误判成“异常已触发”。每个用例结束时加两条NOP等MPU状态稳定后读一次MPUSTS并清空再进入下一个用例最后备份RAM里的MPU_RESULT_OK/FAIL才是真正对应最后一轮自检结果。本文还有配套的精品资源点击获取
返回列表