
简介RH850/F1K是瑞萨面向车载控制领域推出的32位MCU其内部集成多种定时器资源这套例程围绕最基本的定时器中断展开完整呈现中断架构、中断向量表以及TAU定时器中断服务函数的处理流程适合正在基于RH850/F1K开发底层驱动或刚接触该芯片的单片机工程师、相关专业学习者使用。压缩包内共41个文件既有h头文件与c源文件作为程序主体也包含gpj工程文件、ld链接脚本、hex、map、lst等编译产物以及ReadMe说明文档和调试连接配置文件类型覆盖从源码、编译到烧录验证的完整链路。整个压缩包仅358KB体积小巧便于直接解压导入开发环境。目前该例程已有363人学习尤其适合初学者快速建立对RH850/F1K中断机制的整体认知。通过阅读代码与工程配置读者可以掌握定时器中断初始化、中断源使能、优先级设置和中断服务程序挂接方法并以此为模板继续扩展PWM、输入捕获或计数器应用减少项目前期的环境搭建与底层摸索成本。1. F1K_GHS_2_R7F701581_TimerInterrupt 这个压缩包,把哪些事绑在了一起看到 F1K_GHS_2_R7F701581_TimerInterrupt.7z 这个压缩包名,基本不用猜它是什么这是瑞萨 RH850/F1K 系列里 R7F701581 这颗芯片的 Green Hills(GHS)工程,任务落点是跑通一个定时器中断。F1K 是 RH850 面向车身控制的 MCU 系列,R7F701581 是具体型号,GHS 代表 Green Hills 的 MULTI 工具链,TimerInterrupt 则是工程的核心。常见做法是,这个包解压后是.gpj工程,里面给的是最小外设初始化和中断处理,而不是完整应用逻辑。它适合两类人一类是刚拿到 R7F701581 评估板、想确认定时器和中断链路是否正常的应用工程师;另一类是从 STM32 等平台迁到 RH850、想搞清 GHS 里中断函数写法和向量注册惯例的人。下面的内容不假设包内原稿,只讲同类工程在 GHS 下从打开到验证的完整路径。2. 在 GHS 里先把 R7F701581 的时钟和定时器中断源对齐我拿到这类包后不会急着点 build,而是先确认三张表外设时钟树、定时器通道、中断请求号。定时器中断跑不起来,十有八九不是 timer init 写错,而是时钟没开、向量没配或者中断优先级被屏蔽。GHS 工程里这些配置分散在头文件、启动汇编和.gpj的链接设置里,先对齐再动手,后面能少踩一大半坑。2.1 R7F701581 的定时器命名和中断请求名别按 ST 习惯找RH850 的外设寄存器不是库函数里的外设句柄,而是地址映射;GHS 的编译器和调试器直接吃这些地址映射,不靠魔改函数库。芯片资料里定时器按单元分,常见缩写是 TAU、TAUB 这类名字,通道从 0 开始。中断请求名以 INT 开头,比如定时器通道比较匹配中断可能写作INTTAUB0。你要做的第一件事,是在工程的 include 目录里翻r7f701581.h,找到定时器结构体指针和中断号定义,而不是先找 main 函数。7z x F1K_GHS_2_R7F701581_TimerInterrupt.7z -oF1K_GHS_2 cd F1K_GHS_2 find . -maxdepth 2 \( -name *.gpj -o -name *.h \) -print | sort grep -R -E INTTAU|INTTAUB|TAU --include*.h . | head -207z先把工程解到固定目录,避免路径带中文导致 GHS 的工程引用失效。find看工程文件和设备头文件是否齐备,很多 R7F701581 工程会直接引用瑞萨发布的r7f701581.h,这个头文件就是寄存器与中断宏的最终依据。grep则用来定位定时器模块宏,如果搜出来是空的,说明工程里可能用了 BSP 命名前缀,比如GK_TAU0之类,这时要去查 GHS 的-define选项。2.2 时钟树上的三个环节决定 TimerInterrupt 能否触发定时器中断链路从外设时钟开始,经过定时器计数时钟、比较匹配事件,再到中断控制器(ICU),最后才到 CPU。只要中间一个环节没有使能,你在 ISR 里打的断点就永远不会被撞到。下面这张表是排查时固定要看的四段环节检查点常见失误外设时钟门控定时器模块所在电源域有没有开只开了 CPU 时钟,忘了外设时钟定时器分频计数时钟是否符合预期主频 80MHz 却按 40MHz 算周期比较匹配配置周期寄存器与中断使能位写了周期,关了 TMIEICU/中断控制器向量号、优先级、全局屏蔽优先级写错或全局中断未解封我一般会在 MULTI 的寄存器窗口里把外设时钟寄存器读一遍,再对照手册 Power Management 章节。寄存器窗口直接显示原始位值,比打印日志快得多;读的时候注意看模块使能位是否写后即锁,很多 RH850 外设使能位需要在PRCR解锁后写入,写完再回读,防止被写保护拦住。2.3 GHS 的外设窗口帮你验证中断源GHS MULTI 提供外设寄存器窗口,和调试器连上后可以直接看到 TAU/TAUB 的TSR、TMCR、TMDR等状态。验证方法很简单把工程编译完下载到板子,先不运行,在寄存器窗口把定时器模块地址展开,手动把计数寄存器设为非零值,单步启动计数,看是否有中断请求锁存。如果请求位置 1 而 ISR 没进去,问题基本就是向量没挂上,下一步直接去工程启动文件里找向量表。3. 用寄存器初始化定时器并注册 GHS 中断函数时钟和中断源都对齐后,才开始写初始化代码。GHS 编译器对中断函数的处理有自己习惯,不是 GCC 的__attribute__((interrupt)),也不是 IAR 的__interrupt在函数名前后的位置差异。它以__interrupt关键字修饰函数,并由工程链接配置决定这个函数放在哪个向量入口。整体步骤并不复杂,但时序和清标志顺序一定要按手册来。3.1 定时器通道初始化的最小 C 代码下面这段我按 1ms 周期的目标写,寄存器名和位域以工程内的r7f701581.h为准,不同子型号可能在分频位、中断使能位上差几位。#include r7f701581.h static volatile unsigned long sys_tick; /* GHS 编译器的中断修饰关键字 */ __interrupt void Timer_ISR(void) { uint16_t stat TAU.TSR0.WORD; /* 读一次当前状态 */ TAU.TSR0.BIT.TCF 0U; /* 清比较匹配标志 */ /* 中断里只做轻量动作 */ sys_tick; } void timer0_start(void) { /* 假设外设时钟 80MHz,CK3 表示 64 分频 */ TAU.TMCR0.BIT.CK 3U; /* 计数时钟 1.25MHz */ TAU.TMDR0.WORD 1250U; /* 1.25MHz / 1250 1kHz 1ms */ TAU.TSR0.BIT.TCF 0U; /* 先清标志,防误触发 */ TAU.TMCS0.BIT.TMIE 1U; /* 开比较匹配中断 */ TAU.TMCR0.BIT.START 1U; /* 启动计数 */ }CK3是分频选择位,具体 bit 宽度每个定时器单元不一定一样,有的型号里CK是 3 位,有的只有 2 位,写入前要看头文件里TMCR0的定义。TMDR0是周期计数寄存器,写入 1250 之后计数器从 0 增到 1250 才触发比较匹配,所以中断周期就是计数时钟除这个值。TCF清零必须放在TMIE和START之前,否则可能上电后残留标志导致第一次中断立即触发,却拿着空数据进入 ISR。这段写完后,如果中断函数编译报变量未使用,说明stat被优化掉,清标志时确保访问的是 volatile 外设位域。3.2 GHS 的__interrupt与向量表注册__interrupt关键字让编译器生成rte返回指令并保存必要寄存器,但不会自动把函数放进向量表。向量表来自工程的启动文件和链接脚本。GHS 常见的做法是在启动汇编里给每个中断源留一个跳转占位,或者在链接脚本里把某个段放到指定地址。启动汇编里的示意写法如下; 向量表示意,实际地址以启动文件为准 .section .intvec .global _intv_TAU0 _intv_TAU0: jr _Timer_ISRjr是短跳转指令,如果代码区超过跳转范围,需要改成jmp或者借助寄存器间接跳转。实际工程里一般不用手改这段,收尾工程时常见做法是:先从 GHS 的启动文件里搜定时器中断对应占位符,把_Timer_ISR填进去;如果 BSP 里有中断分发函数,也可以向它注册函数指针。判断是否注册成功,看编译出的.map文件,确认向量表地址上已经出现_Timer_ISR符号。3.3 中断优先级和嵌套的配置RH850 的中断优先级一般在 ICU 寄存器里设置,不是每个外设一个优先级寄存器。设置时要先关全局中断,写完再开,防止中间插进其他中断。void set_irq_priority(unsigned int ch, unsigned int prio) { __disable_interrupt(); /* 设备头文件里的 ICU 结构体,字段名以实际头文件为准 */ ICU.ICUC[ch].BIT.PRI prio; __enable_interrupt(); }PRI位宽的取值在不同型号上不一致,有些用 0 表示最高优先级,有些是反转规则。拿到新板子先看 User Manual 里 Interrupt Priority 那一节,不要凭经验猜。优先级建议按下表分配:优先级段使用场景建议0 到 1系统异常、紧急电源监控定时器中断不建议占用2 到 7周期控制、电机控制1ms 定时器中断放这里8 到 15通讯、慢速外设避免与 TimerInterrupt 抢优先级如果同一个优先级里放了多个中断,中断请求会按 CPU 内部的仲裁号排队,导致实际抖动变大。所以定时器中断最好独占一个较低数值的优先级,保证每次触发到执行这段时间稳定。4. 用 GHS MULTI 验证 TimerInterrupt 真的按周期触发写完了不代表运行正常,尤其是定时器中断,很多错误是周期性发生的,不是一开机就崩。我在 GHS 里的验证顺序是: build、下载、设断点、量周期、用示波器复核。四步走完才算这个定时器中断可用。4.1 用 gbuild 编译并下载到目标板GHS 的命令行构建工具是gbuild,它和 MULTI 共用同一个.gpj工程文件,适合从 CI 层面验证工程是否可重复构建。gbuild -clean -cfg Debug F1K_GHS_2_R7F701581_TimerInterrupt.gpj-clean强制清掉上一次的中间文件,避免增量编译把旧的中断向量表留下。-cfg Debug选择 Debug 配置,和 MULTI 里的当前配置保持一致。如果工程里有多个 linked project,gbuild后面还可以加-top指定顶层工程。编译成功后在 MULTI 里选 Debug - Load,加载生成的.elf或.out文件;R7F701581 这类汽车级芯片还可能要选好硬件调试接口,比如 Lauterbach 或瑞萨 E2 仿真器,GHS 里就是 Debug Device 下拉框的事。4.2 在 ISR 设断点并统计命中次数第一步先不设条件,直接在Timer_ISR第一行加断点,单次运行看看会不会进来。命中后查看调用栈和sys_tick值,确认来源是定时器而不是别的错误异常。确认能进去后,把断点属性改成 Count 事件,比如让它每 1000 次才停一次,这样能快速判断中断是不是在持续触发。验证方法操作能回答的问题断点命中断点窗口设置 Count 1000ISR 是否持续执行周期时间测量MULTI 的 Time 工具条两次中断间隔是否稳定寄存器窗口展开 TAU/TAUB 模块计数器和状态位是否按预期翻转Time 工具条测的是 CPU 周期数,不能直接推出硬件引脚上的真实延迟;真实延迟要用 GPIO 翻转加示波器读。断点命中时,切忌直接改寄存器值,尤其是TCF标志和周期寄存器一改,后面测到的就不是真实运行状态。4.3 用 GPIO 翻转把中断周期暴露到示波器最硬核的验证是在 ISR 里翻转一个测试引脚,示波器看方波周期。代码只加两行:__interrupt void Timer_ISR(void) { TAU.TSR0.BIT.TCF 0U; LED_PORT ^ (1u 4); /* 测试引脚翻转,周期 2ms 正负各 1ms */ sys_tick; }^方式翻转不用保存旧状态,1ms 中断一次方波周期是 2ms;看到示波器上的周期差,基本就能定位是不是分频算错。这里要注意:测试代码会引入 1 到 2 条指令的额外时间,不影响 1ms 级别;如果验证的是亚微秒级 timer,就不要用 GPIO 翻转,直接看 MULTI 的周期计数窗口。如果断点一直不命中,就按第 2 章的表格倒查:先看外设时钟门控有没有使能,再看TMIE有没有写进去,最后查向量表是否真的填了_Timer_ISR。三处都没问题,再怀疑目标板晶振和复位电路,千万不要一开始就怀疑 GHS 编译器优化没生效。5. 高优先级 TimerInterrupt 会咬到你的主循环三个收尾动作定时器中断在单板调试时很容易“能跑就行”,一旦进系统,优先级和耗时就会反噬主循环。这里有三个收尾动作建议直接做进去。5.1 中断里只改状态,不调耗时函数很多悲剧都源于在 1ms 的定时器中断里做了串口发送或者printf。一个 115200bps 串口发 20 字节需要接近 2ms,直接拖垮整个中断节奏,后面的中断被锁住,堆栈也会慢慢顶穿。常见做法是把中断里的任务改成设置一个 flag,主循环轮询这个 flag 后再处理。比如sys_tick和g_irq_flag 1;就这么简单,其他全部挪出去。5.2 周期读数要锁存,防止 tick 撕裂如果中断里的sys_tick是volatile unsigned long,主循环读它时可能读出撕裂值32 位 MCU 上虽然单次读是原子的,但如果代码把sys_tick当成多字节去处理,或者将来改成 64 位,就一定会踩坑。安全写法用 do-while 连续读两次:unsigned long get_tick(void) { unsigned long a, b; do { a sys_tick; b sys_tick; } while (a ! b); return b; }这段代码在两次读之间若被定时器中断打断,至少保证读到的两次值一致,不会拼出一个不存在的时间点。sys_tick本身必须是 volatile,否则编译器可能把第二次读优化成本地变量,循环就失去意义。5.3 把 TimerInterrupt 和 RTOS tick 错开如果这个 R7F701581 工程后面要跑 AUTOSAR OS 或者 FreeRTOS,不要把裸机的sys_tick和 RTOS 的 tick 完全叠在一个优先级。常见的标准做法是让 RTOS 直接复用这个 1ms 中断源,否则两个定时器各自争 CPU 和 ICU 通道,调试时连问题归属都分不清。移植时先关掉裸机里的sys_tick,保留中断清标志,把 RTOS 的 tick hook 放进去,再按第 4 节的 GPIO 方法量一次 OS 启动前后的中断周期抖动。抖动幅度一旦超过 100us,优先检查所有同优先级中断和临界区关中断时间,而不是去调定时器分频。本文还有配套的精品资源点击获取