AM275x RTI模块深度解析:从定时器到看门狗的全功能实战指南
1. RTI模块核心功能与设计思路拆解
在AM275x这类高性能信号处理器上做嵌入式开发,RTI(Real-Time Interrupt)模块是构建系统“心跳”和“安全网”的基石。它远不止是一个简单的定时器,而是一个集成了自由运行计数器、比较匹配、事件捕获和数字看门狗等功能的复杂定时系统。理解它的设计思路,是进行高效、可靠配置的前提。
AM275x的RTI模块设计体现了几个核心思想:灵活性、精确性和可靠性。灵活性体现在它提供了多个独立的比较通道(COMP0-COMP3),每个通道都可以独立配置中断或DMA触发,并且可以通过更新比较寄存器(UDCPx)实现自动重载,生成连续的周期性事件,而无需CPU频繁干预。精确性则通过捕获功能实现,当外部引脚(NTUx)发生指定事件(如上升沿)时,能瞬间“冻结”自由运行计数器(RTICAFRCx)和向上计数器(RTICAUCx)的当前值,为测量脉冲宽度、信号周期或事件时间戳提供了硬件级的精度。可靠性则由其数字看门狗(DWD)和窗口看门狗(WWD)机制保障,这是防止软件跑飞、死锁的最后一道硬件防线。
从程序员视角看,RTI模块的寄存器大致分为四类:计数器类(如自由运行计数器,只读,持续累加)、比较类(COMPx,可读写,设定匹配点)、更新类(UDCPx,设定重载值)、控制与状态类(如中断使能SETINT/CLEARINT、标志位INTFLAG、看门狗控制DWDCTRL等)。操作它的典型流程是:先通过全局配置寄存器设定时钟源和分频,初始化计数器;然后配置比较通道,设定首次匹配值和自动更新值;接着使能所需的中断或DMA;最后,在中断服务程序或DMA完成回调中处理业务,并清除中断标志。看门狗则需要独立、谨慎地配置和伺候,一旦开启,就必须在超时前按正确序列“喂狗”。
这里最容易混淆的是自由运行计数器(Free Running Counter)和向上计数器(Up Counter)的关系。你可以把自由运行计数器想象成一个永不停止的秒表,上电后从0开始一直累加,溢出后从0重新开始。而向上计数器更像一个可编程的定时器,它从0开始计数,达到你设定的周期值(RTICPUC0)后自动清零,然后重新开始。捕获事件发生时,两个计数器的值会被同时锁存到RTICAFRCx和RTICAUCx寄存器中。读取时必须遵循固定顺序:先读RTICAFRC1,再读RTICAUC1。这个顺序保证了即使两次读取之间发生了新的捕获事件,你读到的两个值仍然是属于同一个捕获事件的“快照”,避免了数据错位。这是硬件设计上的一个精妙之处,也是很多新手容易忽略而导致时间计算错误的地方。
2. 关键寄存器深度解析与配置要点
面对手册里几十个寄存器,我们不需要全部死记硬背,但必须深刻理解几个核心寄存器的“脾气秉性”。下面我结合自己的调试经验,把几个最容易出问题的寄存器掰开揉碎了讲。
2.1 捕获寄存器:RTICAFRC1 与 RTICAUC1
这两个是只读寄存器,地址偏移分别为0x40和0x44。它们的核心作用就是“拍照”。当外部捕获事件(由NTUx引脚信号边沿触发)发生时,硬件会自动将此刻自由运行计数器1和向上计数器1的值“冻住”,存入这两个寄存器。
关键提示:捕获功能的使能、触发边沿选择等,并不在这两个寄存器里配置,而是在RTI捕获控制寄存器(RTICAPCTRL)里。RTICAFRC1和RTICAUC1只是结果的“存储柜”。如果你发现读出来的捕获值一直是0或者不变化,第一反应不应该是怀疑这两个寄存器,而应该去检查RTICAPCTRL的配置是否正确,以及NTUx引脚上是否有预期的信号变化。
读取顺序的强制性前面已经强调过,这里再解释一下原因。假设自由运行计数器(FRC)和向上计数器(UC)都在运行。在t1时刻发生捕获,FRC值A和UC值a被锁存。如果你先读UC(得到a),此时硬件可能已经发生了第二次捕获(t2时刻),更新了FRC的锁存值为B。然后你再读FRC,得到的就是B。这样你拿到的(B, a)就不是同一时刻的数据对,计算出的时间完全错误。硬件通过强制先读FRC(RTICAFRC1)来避免这个问题:读FRC的操作会暂时“锁定”当前捕获值对,直到对应的UC(RTICAUC1)被读取后,才允许更新为新的捕获值。所以,务必在代码中严格遵循frc_val = RTICAFRC1; uc_val = RTICAUC1;的顺序。
2.2 比较与更新寄存器:RTICOMPx 与 RTIUDCPx
这是RTI模块的“调度中心”。以COMP0(偏移0x50)和UDCP0(偏移0x54)为例。COMP0存放着第一次比较匹配的阈值。当被选中的计数器(通常是自由运行计数器)的值等于COMP0时,就会触发比较匹配事件。
UDCP0的存在让这个模块变得非常强大。它实现了自动重载功能。当第一次比较匹配发生后,硬件会自动执行一个操作:COMP0 = COMP0 + UDCP0。这意味着,你只需要设置好初始值(COMP0)和步进值(UDCP0),就能产生周期固定的连续中断或DMA请求,CPU完全不用管。这对于生成精确的PWM波形、定时采样ADC数据等任务来说是绝佳的特性。
配置时的一个经典坑是:在计数器运行期间直接修改COMPx的值。手册里提到,向COMPx写入新值会立即更新比较寄存器。但如果你的目的是改变下一次中断的周期,而计数器已经接近或超过了旧值,可能会立即触发一次意外的比较匹配。安全的做法是:先停止计数器,修改COMPx和UDCPx,再重新使能计数器。或者,利用一些RTI模块提供的“影子寄存器”或“缓冲写入”机制(如果支持),在新的周期开始时自动生效新值。
另一个细节是复位行为。手册明确写道:“A reset does not generate a compare match, since the compare logic will only be active, when the associated counter block is enabled.” 意思是,复位不会产生比较匹配,因为比较逻辑只在对应的计数器块使能后才生效。这很合理,防止了系统一上电就莫名其妙进中断。但这也提醒我们,配置流程应该是:配置比较值 -> 使能中断 -> 最后才使能计数器。
2.3 中断控制三剑客:RTISETINT, RTICLEARINT, RTIINTFLAG
这三个寄存器(偏移0x80,0x84,0x88)共同管理着中断的使能、禁能和状态查询。它们的位定义是对齐的,例如位0都对应INT0(比较通道0中断)。
- RTISETINT (Set Interrupt):写1到某一位,使能对应的中断。读操作返回当前中断使能状态。
- RTICLEARINT (Clear Interrupt):写1到某一位,禁用对应的中断。读操作同样返回中断使能状态。
- RTIINTFLAG (Interrupt Flag):这是状态寄存器。当比较匹配事件发生时,对应的INTx位会被硬件自动置1,表示有中断 pending。CPU在进入中断服务程序后,必须通过向该位写1来清除这个标志位,否则退出中断后会立即再次进入。
这里有一个非常重要的操作模式:R/W1TS(Read/Write 1 to Set) 和R/W1TC(Read/Write 1 to Clear)。对于SETINT和CLEARINT,写0是无操作的,只有写1才有效。对于INTFLAG,写1是为了清除标志。这种设计避免了误操作,但要求程序员必须用正确的值去写。
标准的中断处理流程如下:
- 配置好比较值和更新值。
- 向
RTISETINT寄存器写,使能目标中断(例如,使能INT0)。 - 使能计数器,开始计时。
- 当比较匹配发生时,硬件将
RTIINTFLAG.INT0置1,CPU跳转至中断服务程序。 - 在中断服务程序中: a. 进行必要的业务处理(如翻转一个GPIO,发送一个数据包)。 b.关键步骤:向
RTIINTFLAG.INT0位写1,清除中断标志。 c. (可选)如果需要动态改变周期,可以在这里更新RTICOMP0或RTIUDCP0。 - 退出中断服务程序。
忘记第5步的清除标志操作,是导致“中断只进一次”或“中断风暴”的常见原因。另外,RTIINTFLAG的TBINT位(位16)比较特殊,它是在检测到外部时钟丢失(TBEXT位被硬件清零)时置位的,而不是由软件清TBEXT触发的。
2.4 看门狗核心:RTIDWDCTRL 与 RTIDWDPRLD
数字看门狗是系统的“救命稻草”。AM275x的RTI看门狗设计得比较严谨,也有点“小脾气”。
RTIDWDCTRL (Digital Watchdog Control Register, 偏移 0x90)这个寄存器控制看门狗的使能,但它有一个一次性写入(One-Write)的保护机制。它的复位值是0x5312ACED,这个值代表看门狗禁用。如果你想使能看门狗,必须向该寄存器写入特定的使能值0xA98559DA。写入任何其他值(包括复位值0x5312ACED),看门狗状态都不会改变。
致命陷阱:这个寄存器在系统复位后,只能成功写入一次。一旦你写入了
0xA98559DA使能了看门狗,在下次系统复位之前,任何对该寄存器的写操作都将被忽略。这意味着你不能通过软件随意关闭已经开启的看门狗。设计意图是防止跑飞的程序意外禁用看门狗。所以,开启看门狗是一个需要慎重考虑的决定。
RTIDWDPRLD (Digital Watchdog Preload Register, 偏移 0x94)这个12位的寄存器(位[11:0])设置看门狗的超时预加载值。看门狗计数器是一个递减计数器,超时时间计算公式手册已经给出:t_exp = (RTIDWDPRLD + 1) * 2^13 / RTICLK1
这里RTICLK1是驱动看门狗计数器的时钟频率。举个例子,如果RTICLK1 = 100MHz,RTIDWDPRLD = 4095(最大值),那么最大超时时间t_exp = (4095+1)*8192 / 100e6 ≈ 0.3355秒。你需要根据系统所需的安全响应时间来设置这个值,太短会导致频繁误复位,太长则失去保护意义。
喂狗是通过向RTIWDKEY寄存器(手册未在此片段给出,但必然存在)写入正确的密钥序列来完成。一旦看门狗使能,就必须在t_exp时间到期前完成喂狗,否则系统会被复位。
配置看门狗的黄金法则:
- 在系统初始化早期,根据需求计算并设置好
RTIDWDPRLD。 - 在系统关键任务初始化完成、进入主循环或任务调度之前,最后一步才写入
RTIDWDCTRL=0xA98559DA使能看门狗。 - 在系统的主循环或一个高优先级的定期任务中,确保以正确的序列喂狗。
- 永远假设看门狗已开启,并以此为前提设计你的喂狗逻辑。
3. 从零构建一个RTI应用:配置与编程实战
理论讲得再多,不如动手调一遍。假设我们要用RTI0模块实现两个功能:1)通过COMP0产生一个1ms周期的定时中断;2)利用捕获功能测量一个外部脉冲的宽度。以下是基于AM275x和典型驱动库的实战代码思路。
3.1 硬件与时钟初始化
首先,需要启用RTI模块的时钟。这通常通过处理器系统配置模块(如PRCM)完成。然后,配置RTI的输入时钟源和分频。假设我们使用RTICLK1,并希望计数器时钟为100MHz。
// 伪代码,基于TI HAL库风格 #include “ti/drv/rti/rti.h” #include “ti/drv/rti/soc/rti_soc.h” RTI_Handle rtiHandle; RTI_Params rtiParams; RTI_Object rtiObject; // 1. 初始化RTI驱动 RTI_init(); // 2. 设置参数,打开RTI0实例 RTI_Params_init(&rtiParams); rtiParams.instanceId = RTI_INSTANCE_0; // 使用RTI0 // 3. 打开RTI实例,获取操作句柄 rtiHandle = RTI_open(RTI_INSTANCE_0, &rtiParams); if (rtiHandle == NULL) { // 错误处理 } // 4. 配置RTI全局时钟。假设函数为RTI_setClockSource // 选择内部时钟源,设置分频使RTICLK1=100MHz RTI_setClockSource(rtiHandle, RTI_CLK_SRC_INTERNAL, 1); // 1分频3.2 配置1ms周期性中断(使用COMP0和UDCP0)
我们的目标是1ms中断。计数器时钟是100MHz,周期是10ns。1ms需要计数1ms / 10ns = 100,000个周期。
自由运行计数器是32位的,最大值约42.9秒(2^32 * 10ns)。我们使用比较匹配自动重载模式。
- 计算比较值:我们希望第一次比较匹配发生在1ms后。假设计数器从0开始,那么
COMP0应设为100,000。 - 设置更新值:为了后续每个1ms都产生中断,
UDCP0也应设为100,000。 - 配置步骤: a. 在计数器使能前,先配置比较和更新寄存器。 b. 配置比较控制逻辑,将COMP0与自由运行计数器1关联。 c. 使能COMP0的中断。 d. 最后使能自由运行计数器1。
// 定义比较值 (1ms @ 100MHz) #define RTI_COMPARE_VALUE_1MS 100000U // 停止计数器(如果正在运行) RTI_disableCounter(rtiHandle, RTI_COUNTER_BLOCK_FRC1); // 配置比较通道0 RTI_compareConfig compareConfig; compareConfig.compareReg = RTI_COMPARE_REG_0; compareConfig.counterSelect = RTI_COUNTER_FRC1; // 与自由运行计数器1比较 compareConfig.compareValue = RTI_COMPARE_VALUE_1MS; // 初始比较值 compareConfig.updateValue = RTI_COMPARE_VALUE_1MS; // 自动更新增量值 compareConfig.enableDMA = false; // 本例只用中断 compareConfig.enableInterrupt = true; // 使能中断 RTI_configCompare(rtiHandle, &compareConfig); // 使能COMP0的中断(通过SETINT寄存器) uint32_t intEnableMask = RTI_INT_NUMBER_0; // INT0对应COMP0 RTI_enableInterrupt(rtiHandle, intEnableMask); // 注册中断服务函数(需与具体中断向量表绑定,此处为示意) RTI_registerInterrupt(rtiHandle, RTI_INT_NUMBER_0, &myRti0Isr, NULL); // 最后,使能自由运行计数器1 RTI_enableCounter(rtiHandle, RTI_COUNTER_BLOCK_FRC1);中断服务程序示例:
void myRti0Isr(void *args) { // 1. 清除中断标志位(向INTFLAG.INT0写1) // 通常HAL库会提供函数封装 RTI_clearInterruptFlag(rtiHandle, RTI_INT_NUMBER_0); // 2. 处理你的1ms定时任务 // 例如,翻转一个LED,更新系统心跳等 my_1ms_task(); // 注意:由于使用了UDCP0自动重载,我们不需要在这里手动更新COMP0 }3.3 配置脉冲宽度捕获(使用捕获功能)
假设我们要测量NTU0引脚上正脉冲的宽度。需要用到捕获功能。
- 配置捕获控制:设置捕获事件为NTU0的上升沿和下降沿触发(用于测量脉宽),并关联到捕获寄存器1。
- 使能捕获。
- 在中断中读取捕获值:通常,捕获事件也会产生中断。
// 配置捕获控制 RTI_captureConfig capConfig; capConfig.captureReg = RTI_CAPTURE_REG_1; // 使用捕获寄存器1 capConfig.inputSignal = RTI_CAPTURE_SRC_NTU0; // NTU0引脚 capConfig.edgeDetect = RTI_CAPTURE_EDGE_BOTH; // 上升沿和下降沿都捕获 capConfig.enableInterrupt = true; // 使能捕获中断 RTI_configCapture(rtiHandle, &capConfig); // 使能捕获中断 uint32_t capIntEnableMask = RTI_INT_NUMBER_CAPTURE1; // 假设库定义了捕获中断号 RTI_enableInterrupt(rtiHandle, capIntEnableMask); RTI_registerInterrupt(rtiHandle, RTI_INT_NUMBER_CAPTURE1, &myCapIsr, NULL); // 使能捕获功能 RTI_enableCapture(rtiHandle, RTI_CAPTURE_REG_1);捕获中断服务程序:
volatile uint32_t riseTimeFRC = 0, riseTimeUC = 0; volatile uint32_t fallTimeFRC = 0, fallTimeUC = 0; volatile uint8_t captureState = 0; // 0:等待上升沿, 1:已捕获上升沿,等待下降沿 void myCapIsr(void *args) { uint32_t frc_val, uc_val; // 1. 严格按照顺序读取捕获值 frc_val = RTI_getCaptureFreeRunningValue(rtiHandle, RTI_CAPTURE_REG_1); uc_val = RTI_getCaptureUpCounterValue(rtiHandle, RTI_CAPTURE_REG_1); // 2. 根据状态判断是上升沿还是下降沿 if (captureState == 0) { // 假设第一个边沿是上升沿 riseTimeFRC = frc_val; riseTimeUC = uc_val; captureState = 1; // 可以重新配置为仅下降沿捕获以提高精度,但需考虑信号抖动 } else if (captureState == 1) { // 下降沿捕获 fallTimeFRC = frc_val; fallTimeUC = uc_val; captureState = 0; // 3. 计算脉冲宽度(注意计数器溢出!) uint32_t delta_frc, delta_uc; // 计算自由运行计数器的差值,处理溢出 if (fallTimeFRC >= riseTimeFRC) { delta_frc = fallTimeFRC - riseTimeFRC; } else { // 发生了溢出,需要加上计数器的模(2^32) delta_frc = (0xFFFFFFFF - riseTimeFRC) + fallTimeFRC + 1; } // 向上计数器在达到RTICPUC0后会复位,计算更复杂,需要结合周期值 // 此处简化,假设在同一个周期内 if (fallTimeUC >= riseTimeUC) { delta_uc = fallTimeUC - riseTimeUC; } else { // 向上计数器发生了回绕 // 需要知道RTICPUC0的值,假设为period_uc0 uint32_t period_uc0 = getRTICPUC0Value(); // 获取向上计数器0的周期值 delta_uc = (period_uc0 - riseTimeUC) + fallTimeUC; } // 将计数差值转换为时间(单位:秒) // 假设时钟频率为CLK_FREQ (Hz) float pulse_width_frc = (float)delta_frc / CLK_FREQ; float pulse_width_uc = (float)delta_uc / CLK_FREQ; // 理论上两者应一致,可用于互相校验 } // 4. 清除捕获中断标志 RTI_clearInterruptFlag(rtiHandle, RTI_INT_NUMBER_CAPTURE1); }计算注意事项:上述脉冲宽度计算是简化版。在实际高精度测量中,必须结合自由运行计数器和向上计数器的特性,并考虑两者可能存在的不同溢出周期。最稳健的方法是记录两次捕获时的完整“时间戳”(frc_val + uc_val组合),并设计一个处理溢出和周期回绕的差量计算函数。
3.4 看门狗配置与喂狗例程
看门狗的配置必须极其小心。
// 1. 配置看门狗预加载值(超时时间) // 假设RTICLK1 = 100MHz,我们设置超时时间为100ms // t_exp = (DWDPRLD + 1) * 8192 / RTICLK1 // 100e-3 = (DWDPRLD + 1) * 8192 / 100e6 // DWDPRLD + 1 = (100e-3 * 100e6) / 8192 ≈ 1220.7 // DWDPRLD = 1220 (取整) #define DWD_PRELOAD_VALUE 1220U // 在系统初始化早期,看门狗使能前设置预加载值 RTI_setWatchdogPreload(rtiHandle, DWD_PRELOAD_VALUE); // 2. (可选)配置窗口看门狗窗口(如果需要) // RTI_setWindowedWatchdogWindow(startTime, endTime); // 3. 在系统所有关键初始化完成后,最后使能看门狗 // 此操作不可逆,直到下次复位 RTI_enableWatchdog(rtiHandle); // 内部会写入 0xA98559DA 到 RTIDWDCTRL // 4. 在主循环或定时任务中喂狗 void main_loop(void) { while(1) { // ... 执行应用任务 ... // 定期喂狗,必须在超时前完成 // 需要写入正确的密钥序列到RTIWDKEY寄存器 // 例如:先写0xE51A,再写0xA35C RTI_serviceWatchdog(rtiHandle); // 库函数封装了正确的写入序列 // 注意喂狗间隔必须小于超时时间,且如果使能了窗口看门狗, // 还必须在指定的时间窗口内喂狗。 } }4. 调试陷阱与常见问题排查实录
调RTI模块,特别是涉及中断和精确计时时,踩坑是常态。下面是我总结的几个典型问题场景和排查思路。
4.1 问题一:中断无法进入或只进入一次
现象:配置了比较值和中断,但程序似乎从未进入中断服务程序,或者只进入了一次就再也不进了。
排查清单:
- 中断使能了吗?这是最常被忘记的一步。检查
RTISETINT寄存器对应的位(如INT0)是否被置1。不仅要看代码里是否调用了使能函数,最好在调试器里直接读取该寄存器确认。 - 中断标志清除了吗?如果中断只进一次,大概率是中断服务程序里忘记清除
RTIINTFLAG标志位。硬件在产生中断时会置位标志,CPU响应中断后必须手动写1清除它。否则,中断状态会一直保持,导致无法触发新的中断(对于某些控制器,可能会反复触发)。 - 全局中断使能了吗?AM275x是Cortex-A系列,需要确保CPU的CPSR中的I位(或GIC中断控制器中的相应使能位)是打开的。RTI模块的中断只是外设级使能,全局中断开关是另一回事。
- 中断向量表配置正确吗?确认RTI中断号对应的中断服务函数地址是否正确注册到了系统的中断向量表或中断控制器(GIC)中。
- 计数器真的在跑吗?通过调试器读取自由运行计数器(如
RTIFRC1)的值,看看它是否在随时间递增。如果没有,检查计数器块是否被使能(RTIGCTRL等控制寄存器)。 - 比较值设置对了吗?确认你写入
RTICOMPx的值是有效的,并且小于计数器的溢出值(如果是自由运行计数器)。一个常见的错误是计算周期时单位弄错,导致比较值巨大或为0。
4.2 问题二:捕获值读取错误或不变
现象:使能了捕获功能,外部信号也有变化,但读取RTICAFRC1和RTICAUC1的值总是0或者固定不变。
排查清单:
- 捕获源和边沿配置正确吗?检查
RTICAPCTRL寄存器,确认CAPSEL位选择了正确的输入源(如NTU0),并且EDGE位配置成了你期望的边沿(上升、下降或双边沿)。 - 捕获功能使能了吗?
RTICAPCTRL中通常有一个CAPEN位,需要置1才能使能捕获逻辑。 - 读取顺序对吗?这是硬性规定。必须先读RTICAFRC1,再读RTICAUC1。反序读取会得到无意义的数据,并且可能锁住旧的捕获值,导致新的捕获事件无法更新寄存器。
- 信号物理连接和电气特性对吗?用示波器检查NTUx引脚上的信号,确认其电压幅值、边沿速度是否符合要求,是否有毛刺。软件无法捕获一个硬件上不存在的信号。
- 有噪声或抖动吗?如果信号边沿有严重抖动,可能导致捕获不稳定。可以考虑在
RTICAPCTRL中配置数字滤波器(如果支持)来稳定信号。
4.3 问题三:看门狗意外复位系统
现象:系统在运行一段时间后莫名其妙复位,调试发现是看门狗触发了。
排查清单:
- 喂狗间隔是否超时?计算一下你的喂狗函数执行周期,是否大于看门狗的超时时间
t_exp。确保在最坏的任务执行时间下,喂狗间隔仍然安全。 - 喂狗序列正确吗?查阅手册,确认喂狗(服务看门狗)需要写入
RTIWDKEY寄存器的正确序列。通常是两个特定的16位值(如0xE51A和0xA35C),必须按顺序写入,不能错也不能少。很多驱动库提供了RTI_serviceWatchdog()函数,确保你调用的是正确的。 - 是否使能了窗口看门狗模式?如果配置了窗口看门狗,喂狗必须在特定的时间窗口内(
[RTITBLCOMP, RTITBHCOMP])进行。过早(在窗口开始前)或过晚(在窗口结��后)喂狗都会触发复位。检查窗口时间设置和你的喂狗时间点。 - 看门狗时钟源对吗?确认驱动看门狗计数器的时钟
RTICLK1是否如你预期的那样运行。如果时钟被意外关闭或分频比设得太大,实际超时时间会变短,导致提前复位。 - 有多个任务喂狗吗?避免在多个不同周期的任务中喂狗,这可能导致逻辑混乱。通常建议在一个确定性的、最高优先级的定时任务或主循环中集中喂狗。
4.4 问题四:使用DMA时数据搬运不成功
现象:配置了比较匹配触发DMA,但DMA传输没有启动或数据没有正确搬运。
排查清单:
- DMA请求使能了吗?除了在RTI的比较控制中使能DMA请求(
RTICOMPCTRL中对应位),还需要在RTISETINT寄存器中使能对应的SETDMAx位。 - DMA控制器配置正确吗?RTI只负责产生DMA请求脉冲。你需要正确配置DMA控制器的源地址、目标地址、传输量等参数,并将DMA通道的触发源设置为对应的RTI事件。
- 中断和DMA冲突吗?如果同时使能了中断和DMA,要清楚它们的行为。比较匹配事件会同时置位中断标志和产生DMA请求。通常,如果使用DMA进行周期性数据搬运,可以禁用中断以减少CPU开销。
- DMA请求优先级?如果系统中有多个DMA请求源,需要确认RTI产生的DMA请求优先级是否足够高,以免被持续阻塞。
把这些排查思路做成一个检查表,在调试时逐一核对,能节省大量漫无目的的抓瞎时间。嵌入式开发就是这样,寄存器每一位都有它的脾气,硬件逻辑不会妥协,只有我们完全理解了它的规则,才能让它稳定可靠地工作。
