MAXQ微控制器中断编程

描述

阅读本笔记没有暗示或要求的特殊知识,但对MAXQ架构和寄存器映射有基本的熟悉是一个加分项。这些信息可以在MAXQ系列用户指南、数据资料(如MAXQ2000)和其他应用笔记(如MAXQ架构简介;使用MAXQ2000评估板的示例应用)。本应用笔记中使用的示例的源代码和项目文件可以下载。

MAXQ10和MAXQ20微控制器具有简单、廉价的单向量中断机制,而中断源和控制则在逻辑上组织成三级分层结构。硬件不会优先考虑中断。应用程序代码负责通过单个向量调度各种中断,因此,对中断进行编程和调试是应用程序开发周期的重要组成部分。本说明提供:

为需要编写简单中断功能编程的用户提供的入门指南

有关实现更详细的中断优先级方案、嵌套中断和可用硬件资源的最佳利用的提示。

MAXQ中断机制概述

MAXQ系列微控制器具有一个寄存器IV(中断向量),用于保存中断例程的地址,以及一个位INS(中断iN服务),用于指示中断活动。当中断被触发时,处理器内核的行为就像在代码中插入了“呼叫IV”和“移动INS,#1”指令一样。MAXQ内核执行子程序调用,即指令指针IP被推入堆栈并加载IV寄存器的内容,然后设置INS位表示存在活动的“服务中断”,以防止进一步的中断调用。中断服务以“RETI”(或“POPI”)指令结束,该指令将返回地址从堆栈弹出到 IP 中并清除 INS 位,用户代码从中断的位置恢复。

中断触发逻辑如图1所示,以MAXQ2000微控制器为例。其它MAXQ微控制器可能具有不同的中断源集,但逻辑结构在整个MAXQ系列中都是通用的。

逻辑结构从图 1 左侧列出的各个中断源开始。每个源都有一个关联的中断标志,即在检测到来自该源的中断事件时由硬件设置的特殊位。每个源还具有单独的使能位,允许应用程序启用或禁用中断源。仅当标志位和使能位都设置为 1 时,来自该源的中断信号才有效。这些位在MAXQ架构中提供单独的中断信号和控制。

处理器

图1.MAXQ2000微控制器中的中断触发逻辑

由于MAXQ架构是模块化的,中断也根据其在模块中的位置进行分组。与单个中断源一样,每个模块都有自己的中断标志和使能位,如图 1 中间所示。该标志是来自该模块的所有底层中断信号的逻辑“OR”,而使能位允许应用程序启用或禁用整个模块的中断。这些位分配在两个8位寄存器中(IIR中的标志和IMR中的使能),并在模块级别提供中断信号和控制。

最后,来自所有模块的中断信号被“OR化”以形成全局中断触发信号,如图1右侧所示。“中断全局使能”位IGE允许应用禁用或启用此信号,从而提供全局级别的中断控制。

设置后,单个中断标志将保持设置状态,直到被软件(即中断服务代码)清除,即使导致设置该标志的条件消失或删除。如果软件清除标志失败,退出服务代码后将反复触发中断。模块化中断标志(IIR 寄存器)是只读的;一旦应用程序代码清除了模块中的所有基础单个标志,它就会自动清除。

在图 1 所示的配置中,右侧的全局中断触发信号处于活动状态,由左侧的三个独立源引起:UART1 传输、定时器1 溢出和看门狗定时器。这三者都设置了各自的标志和启用位。另一个源(模块 8 中的 Ext1)也是信令,但该信号在模块级别被屏蔽(禁用),因为中断掩码位 IMR.1 为 0。中断服务例程 (ISR) 中的软件通过分析标志和使能位(图 1 中以相反的顺序显示,即从右到左)来识别信令源,然后为每个活动源提供服务并清除各个中断标志。

对简单的中断服务例程进行编程

许多应用程序不需要复杂的中断功能,只需要一两个中断,而不考虑优先级。让我们看看如何为MAXQ编程这样的服务例程。我们使用IAR嵌入式工作台作为示例编程工具,但是我们的代码主要是可移植的(除了一些特定于工具的功能,如下所示)。®

假设现有应用程序需要每毫秒在端口引脚上触发短脉冲,同时执行许多其他重要任务。这可以使用配置为每 1ms 生成一次中断请求的板载定时器来实现。

为了完成中断编程,我们需要向现有的无中断应用程序添加一些与中断相关的代码。与中断相关的代码由两部分组成:初始化和 ISR。初始化代码放置在应用程序开头的某个位置,以设置正确的中断配置:使能位(单个、模块和全局)和 IV 寄存器。ISR 可以放置在任何位置,并且在退出之前应执行三个基本任务:

确定中断源(如果仅使用一个源,则不需要)

清除中断标志

执行所需的操作

首先,让我们看看这一切在汇编语言中是如何工作的。C程序员可以跳过这一部分,尽管它确实提供了对幕后发生的事情的有用见解。图 2 显示了我们的无中断应用程序的源代码。除了许多其他非常重要的任务外,它还以0的波特率连续输出字节35x5(ASCII符号“115200”)。在硬件上运行时,可以通过COM端口轻松捕获输出,如图3所示。

处理器

图2.没有中断的示例应用程序代码。

处理器

图3.图 2 中示例无中断应用程序的输出。

现在我们按照上述四个步骤添加中断代码,从 ISR 开始。

由于只有一个中断处于活动状态,因此不需要源标识;

如果我们使用 Timer1 溢出中断,则标志是模块 3 的寄存器 8 的第 4 位。清除它

move  M4[8].3,#0  ; clear interrupt flag

如果我们使用端口引脚 P0.0 触发脉冲,则操作代码可能如下所示:

move  M0[0].0,#1  ; set pin high
move  M0[0].0,#0  ; set pin low

  这解决了任务,但是如果没有示波器,我们就无法看到结果。为了可视化它,让我们通过串行端口输出“5”以外的内容,例如“$”符号(0x24):

move  M2[7],#0x24 ; start transmission

  通过此添加,我们希望在输出上的 5 中偶尔出现$s(如果执行 ISR)。请注意,当串行端口忙于传输时,可以调用 ISR,从而导致“$”和“5”字符之间的冲突。当然,正确编写的 ISR 应该避免此类冲突,但为了简单起见,我们有意忽略了这个问题。

退出

reti  ; exit ISR

中断初始化代码应:

将定时器1配置为每1ms溢出一次。

move  M4[10],#(65536-16000)  ; ovfl every 1ms @ 16MHz
move  M4[9],M4[10]           ; init counter
move  M4[16],#0x0            ; 16-bit timer mode

使用ISR的地址加载IV寄存器。

move  IV,#   ;  load interrupt vector

在所有三个级别启用 Timer1 溢出中断:单个、模块和全局。

move  M4[0],#0x88      ; int enabled, start the timer
move  IMR.4,#1         ; enable ints from module 4
move  IC.0,#1          ; enable global interrupts (IGE=1)

我们还必须初始化端口引脚,即设置方向和电压电平:

 

move  M0[16].0,#1 ; Configure Port Pin P0.0 direction (output)
move  M0[0].0,#0  ; Set Port Pin P0.0 Low

将所有部分组装在一起,我们最终得到图 4 中所示的代码及其输出(如图 5 所示)。$s如预期的那样,端口引脚P0.0每1ms发射一次短脉冲。由于上述传输冲突,$s和 5 的模式并不完全规则——有些$s无法通过。

现在让我们在 C 中重做相同的应用程序。这甚至更容易,因为C编译器将为我们做一些工作。也就是说,IAR C 编译器将中断向量 IV 设置为通用 ISR,用于标识信令模块并调用与此模块对应的 C 函数。它还负责保存/恢复 ISR 内部使用的寄存器,因此应用程序流不会因中断而中断。我们需要做的就是为每个模块编写 ISR 函数,该函数将生成中断并进行配置,即正确设置启用位。后者很重要 — 如果应用程序错误地启用了没有相应 ISR 功能的中断,则结果将难以预测。

处理器

图4.带中断的示例应用程序代码。

处理器

图5.图 4 和图 6 中示例应用程序的典型输出。

我们现在遵循相同的逻辑步骤,但这次对 ISR 函数使用 C 语法。

来源识别。我们必须告诉编译器哪个模块是中断的来源:

#pragma vector=4
__interrupt void  isr_module4() {}

  #pragma 指令和关键字__interrupt通知编译器,当模块 4 有活动中断信号时,应调用以下示例中的 isr_module4())。由于模块 4 中只有一个源处于活动状态,因此不需要更多源标识。

清除标志。如果我们使用定时器1溢出中断,则标志在寄存器T2CNB2中为位TF1。清除它

T2CNB1_bit.TF2=0; // clear interrupt flag

  请注意,名称 TF2 和 T2CNB1_bit 分别只是位 3 和寄存器 M4[8] 的替代品。它们在特定于工具的包含文件“iomaxq200x.h”中定义。

中断操作。在端口引脚 P0.0 上发出脉冲:

PO0=1;            // set pin high
PO0=0;            // set pin low

退出。无事可做;编译器会处理这个问题。

SBUF0='$';        // start transmission

同样,中断初始化代码是直接的ASM到C转换,除了设置由编译器自动完成的IV寄存器:

将定时器1配置为每1ms溢出一次。

T2R1=65536-16000; // ovfl every 1ms @ 16MHz
T2V1=T2R1;        // init counter
T2CFG1=0;         // 16-bit timer mode

使用 ISR 地址加载 IV 寄存器 — 由编译器完成。

在所有三个级别启用 Timer1 溢出中断:单个、模块和全局。

T2CNA1=0x88;      // int enabled, start the timer
IMR_bit.IM4=1;    // enable ints from module 4
IC_bit.IGE=1;     // enable global interrupts (IGE=1)
 

处理器

图6.在 C 语言中使用中断的示例应用程序。

我们还必须初始化端口引脚,即设置方向和电压电平:

 

PD0=1;            // Configure P0.0 direction (output)
PO0=0;            // Set P0.0 Low

将所有部分组装在一起,我们最终得到图 6 中所示的代码。在硬件上运行时,其输出看起来与图 5 所示相同。

对嵌套中断进行编程

通常,当中断被处理时,触发信号在全局级别被称为INS的特殊位阻止(图1中未显示)。该位在进入 ISR 时由硬件自动设置,并在执行 RETI 或 POPI 指令时清除(通常在退出 ISR 时)。当 INS = 1 时,无法触发中断。但是,某些应用程序可能希望允许嵌套或递归中断。这可以通过清除 ISR 内的 INS 位来完成,从而允许中断中断服务例程流。请注意,第二个中断调用将向量到与第一个中断调用相同的 IV 寄存器指向的 ISR,因此应用程序在允许递归中断调用时应针对无限循环进行配置。

图7显示了一个在端口引脚(连接到按钮)的下降沿激活中断的应用。本应用使用模块0中的MAXQ2000上的Ext0中断。按钮连接到相应的端口引脚P0.4。中断服务例程旨在通过清除 INS 位(图 14 中的第 7 行)来中断其自身的代码。为此目的提供了内在函数 __reenable_interrupt(),尽管可以通过直接写入位来完成相同的工作:IC_bit。INS=0;。为了防止无限循环,ISR 通过在进入时递增全局变量nest_level和在退出时递减来计算嵌套级别。仅当嵌套级别不超过特定限制时,才会清除 INS 位(此示例中最多允许 7 个级别)。

当没有发生中断时,应用程序通过串行端口连续输出字符“0”,指示正在执行 main() 函数。按下按钮时,ISR 会打印当前嵌套级别并执行空闲循环几秒钟,以便在 ISR 仍在运行时再次按下按钮。当嵌套达到级别 7 时,不再清除 INS 位。如果在级别 7 执行 ISR 时按下该按钮,则新的中断请求将保持挂起状态(设置了标志,但触发信号被 INS = 1 阻止),直到 ISR 完成并退出,清除 INS 位并在级别 6 恢复 ISR。然后,挂起的中断变为活动状态,并再次导致级别 7 的 ISR 呼叫。图 8 说明了这种描述的行为 — 每个非零数字表示一个 ISR 条目,包括递归中断调用。

编程中断优先级方案

到目前为止,我们只考虑了一个中断源的简单应用程序。更高级的应用可以同时使用多个中断源,例如RTC报警,定时器/计数器,按钮或其他外部I / O信号,看门狗,发送/接收与UART的通信,SPI™、1-Wire等各种中断可能会来来去去,多个中断源可能会同时变为活动状态,但应用程序一次只能为它们提供服务。因此,应用程序必须应用一些规则来决定应首先、第二、依此类推为哪些同时处于活动状态的源提供服务。这些规则通常以中断优先级表示 — 为每个中断源分配一个称为优先级的整数。如果发生冲突,优先级较高的源会先于优先级较低的源进行服务。®

在MAXQ架构中,这种中断服务排序必须在软件中实现,因为不存在硬件优先级。在本应用笔记中,我们仅考虑确定中断服务优先级的众多可能方法中的两种:

源标识排序(无递归中断)

动态中断重新配置(带递归中断)

事实上,这两种方法都可以在一个应用程序中混合在一起,正如我们将在下面的示例中看到的那样,创建灵活有效的中断服务结构。

处理器

图7.在 C 语言中具有嵌套中断的示例应用程序。

前一种方法意味着安排ISR的来源识别部分,以便首先识别和维修优先级较高的源,然后再识别和维修优先级较低的源。例如,假设图 1 中的三个源同时发出信号,但应用程序希望首先处理看门狗中断,然后处理 Timer1 溢出,然后处理 UART1 传输。该任务可以通过以下伪代码解决:

 

if (  )
        {  }
if (  )
        {  }
if (  )
        {  }

这种简单的技术几乎没有开销;它不需要额外的内存或递归中断调用。每个中断都按照接收顺序从头到尾执行,除非有两个或多个中断挂起。在这种情况下,服务顺序由应用程序代码中的源标识排列定义。但这种方法有一个缺点:如果在为另一个源提供服务时发生紧急、优先级最高的中断,则必须等到 ISR 完成,到那时可能为时已晚。为了克服这个问题,我们必须允许中断ISR流,这就引出了第二种方法——动态中断重新配置。

后一种方法更通用,意味着每个单独的 ISR 需要执行以下步骤:

保存当前中断配置并重新配置整个使能位集,以禁用所有较低(低于当前)或同等优先级的源,但启用更高优先级的源

允许递归中断,即清除 INS 位

执行当前中断的操作

禁止递归中断,即设置 INS 位

恢复保存在步骤i)中断配置并退出。

此实现允许几乎立即为较高优先级的中断提供服务,即使它在为较低优先级的源提供服务时发生。但是,此方法会消耗更多的数据和程序空间,并且随着方案中添加的每个额外中断源而增加。

处理器

图8.图 7 中示例应用程序的输出。

为了演示这两种方法,我们创建一个MAXQ2000应用示例,具有以下中断优先级方案:

 

中断源 源模块 优先权 中断操作
看门狗定时器 7 99 (最高) 清除看门狗定时器;将符号打印到 UART0
UART0 传输 2 70 从缓冲区传输下一个字节(如果有)
定时器1 溢出 4 50 在端口引脚 P0.0 上触发脉冲(每 128 毫秒)
外部国际 0 0 30 非常重要的按钮操作(红色按钮)
外部国际 10,11,12 1 10, 11, 12 按钮操作(绿色按钮)

 

看门狗定时器具有最高优先级,因为如果未及时清除,它将重置设备。然后我们利用UART的传输中断,它表示一个字节已成功发送,下一个字节传输可以开始。此中断也是确保快速通信和防止缓冲区溢出的高优先级。接下来是Timer1,它以相对较慢但有规律的速度发射脉冲。按钮中断的优先级较低,因为它们是不规则的。

模块化架构使在模块级别分配优先级变得简单方便。在这种情况下,唯一要保存/重新配置/恢复的配置数据是 IMR 寄存器,即 8 位模块中断掩码。否则,必须在每个单独的 ISR 中存储、重新配置和恢复整个分散的单个使能位集。因此,我们为模块实现了通用优先级方法(动态重新配置),但对模块内的中断源实现了简单的排序方法。

我们示例中的模块 1 有多个中断源,图 9 显示了如何在模块 1 中断例程 isr_module1() 中实现优先级。首先,它保存当前中断配置(图 39 中的第 9 行),然后重新配置中断以禁用模块 1 中的源,但启用更高优先级的模块 0、4、2、7(第 40 行),并通过清除 INS 位(第 41 行)重新启用中断。然后,它按照优先级顺序识别模块 1 中的中断源并为其提供服务。首先检查优先级较高的 int12(第 45-50 行),其次检查优先级较低的 int11(第 52-57 行),最后检查优先级最低的 int10(第 59-64 行)。这些中断的服务例程具有较长的延迟循环(在 button() 函数内部,图 48 中的第 55、62 和 9 行),因此在中断例程 isr_module1() 仍在运行时可能会发生其他中断。

其他中断也以类似的方式设计(请参阅附录A中的完整源代码,可供下载),但优先级最高的看门狗中断除外,其中不需要重新配置。应用程序通过 UART0 写入各种 ASCII 标记,以便可以在 PC 上捕获它们以可视化执行流程。为了演示,硬件组装有两个按钮:一个连接到引脚P0.4,按下时激活外部中断0;另一个连接到引脚 P5.2、P5.3、P6.0,分别对应于外部中断 10、11、12,因此当按下该按钮时,所有三个中断都会同时激活。由于前一个中断 Ext0 具有更高的优先级,因此我们将其称为“红色按钮”,而另一个按钮将称为“绿色按钮”,激活三个优先级较低的中断。

当未按下任何按钮时,应用程序打印“=Reset=”,然后连续写入单词“Main”,点“...”和单词“”,表示重置后它执行main()函数,并且经常被看门狗(点)中断,偶尔被Timer1溢出打断(参见图10)。当检测到任何按钮中断时,应用程序在 ISR 条目上打印“”,在 ISR 出口时打印“ExtN>”(N 是外部中断的编号)。对于图 10 中的示例,按下“绿色”按钮一次,然后按几次“红色”按钮。“绿色”按钮同时激活了三个外部中断 — 10、11 和 12 — 但如图 10 所示,int12 首先根据其优先级提供服务,而 int11 和 int10 处于挂起状态。ISR 显然被看门狗和计时器打断。紧接着是 int11(int10 仍处于挂起状态),它被使用“红色”按钮激活的更高优先级的 int0 打断。最后,紧跟在 int11 之后的是 int10,而 int0 又被按下 (int10) 的“红色”按钮打断了几次。在这个过程中的某个时刻,所有优先中断都被嵌套了:main功能被int0按钮中断,被int1按钮中断,被Timer<>溢出中断,被UART传输中断,被看门狗中断!

 

处理器

 

图9.具有多个优先级中断的示例应用程序(片段)。

 

处理器

 

图 10.图 9(附录 A)中示例应用程序的输出。

结论

MAXQ微控制器的中断机制非常简单,可配置性强,使得MAXQ的中断编程变得容易。尽管硬件资源有限,但MAXQ独特的模块化架构允许开发人员以极低的开销实现复杂的中断优先方案。

审核编辑:郭婷

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分