传统上,程序员和组织对使用中断有一种不合理的恐惧。如果没有遵循正确的中断实现,系统肯定会发生奇怪的事情。那么,嵌入式开发人员怎样才能确保中断被正确地实现,并按照预期的那样被利用呢?
技巧1——创建ISR表
中断服务例程表是一个数组,包含微控制器上可能发生的所有中断的列表。每个中断都由中断发生时执行的函数(指向函数的指针)填充,这样做有很多好处。首先,将一个函数调用分配给一个中断变得非常容易。只需更改表中的函数名,重新编译,现在中断发生时该函数将被调用。
接下来,程序员为每个中断包含一个函数,这是一个很好的实践,因为每个中断都是用代码初始化的!在调试期间,如果发生了错误的中断,而不是跳开并执行未知代码,则可以改为执行“DummyISR”,这让错误的中断被捕获和调试。最后,以这种方式使用中断表迫使中断代码采取易于理解和配置的有组织的结构。
有许多不同的方法可以实现该表。最常见的是通过使用#pragma。这允许将表放在闪存中的特定存储位置。最常见的两种实现是允许指定闪存位置的起始地址或定义链接器内存标签。嵌入式开发人员应该尽量避免使用#pragma,但是如果这是实现中断表的唯一方法,那么这将是一个很好的例外。
技巧2——保持简短快速
根据定义,中断是对正在执行的应用程序的正常流程的中断。为了处理中断,程序实际上停止了正在做的任何事情。在这种情况下,中断服务程序显然应该简短扼要,以便主应用程序可以继续执行。
中断的真正目的是处理需要系统注意的紧急事件。为了保持日常工作的简短,只做当时真正需要做的最少的事情。例如,如果通信数据触发中断,将数据放入缓冲区,设置一个标志,让主程序处理数据,不要试图在中断中处理它!
保持代码简短而快速有时会有欺骗性。例如,做一个简单的浮点计算(只有一行代码)可能看起来很短,但是一个没有硬件浮点单元的微控制器可能会花费嵌入式开发人员很长的时间来处理数学运算(也可能是毫秒)!有几个简单的规则可以确保中断服务程序快速运行:
l 不要从你的中断中调用函数(除非它们是内联函数),函数调用开销会浪费你的时间。
l 任何处理器密集型活动,如处理数据缓冲区、执行计算等,都应该设置一个标志,并让主应用程序进行处理。
l 应该避免Wait语句。
l 也应该避免循环或任何时间密集型逻辑,如循环、除法或模数运算。
技巧3——仔细检查你的初始化
出于这样或那样的原因,中断似乎总是让正常工作变得很痛苦,它们在概念上是直截了当的,但有些实现需要大量的前瞻性思考才能正确。在设置和调试中断时,嵌入式开发人员应该问一些问题。其中一些看起来很简单,但是仍然应该检查和询问。
中断是否启用?(当然,我以为我启用了它,但是MCU寄存器显示什么?)
是否设置了中断的优先级?
ISR在中断表中的位置是否正确?
中断是否映射到正确的硬件引脚?到正确的外围设备?
中断是否尽快得到确认?
ISR中的中断标志是否在正确的时间被清除?
技巧4——尽可能少地禁用中断
禁用中断可能是一项极其危险的工作。禁用中断会导致错过应该处理的中断!为什么会有人想要禁用中断呢?有时候,开发人员希望在不中断的情况下自动运行某些代码部分。允许发生可能会改变计算中使用的变量的中断可能会导致灾难!因此,嵌入式开发人员可能会在“临界区”之前禁用中断,在启用中断之前执行计算或运行代码。
现代32位处理器有助于最小化禁用中断的需求,如果可能的话,当然应该不惜一切代价避免这样做。但是,如果发现需要,安全禁用中断是一个很好的起点,可以安全地做到这一点。禁用中断前,应读取并保存中断的当前状态。关键代码段在将中断状态恢复到之前的状态运行。
技巧5——对共享变量使用volatile
在ISR中遇到的最大错误之一是共享变量处理不当。一个中断可能被用来填充一个缓冲区或者修改一个被应用程序其余部分使用的变量。如果应用程序将要使用这个变量,并且中断触发并更新这个变量,应用程序可能仍然使用旧的值!这当然会导致系统潜在的一些不可预测的行为。
C语言有一个名为volatile的关键字,其目的是在每次使用时强制重新读取变量。每当变量可能被程序正常运行时之外的进程修改时,嵌入式开发人员就可以使用它。它通常在访问硬件寄存器时使用。硬件可以在程序不知道的情况下改变存储的值,所以内存映射变量被声明为volatile。由于中断可以在程序不知道的情况下随时改变值,所以与ISR共享的变量也应该声明为volatile。
技巧6——了解ISR的时间安排
微控制器最常用于具有硬实时或至少软实时要求的实时系统。以简单的上下文切换到中断为例。这可能需要四到二十个时钟周期。这似乎不值得一提,但事实上情况可能更糟!对于以8 MHz运行的微控制器,这可能是500到2500纳秒的延迟。这只是一个上下文切换到中断!离开中断的时候还有一个!
上下文切换计时值得理解,但更有趣的是中断运行时的剩余部分。开发人员的代码运行需要多长时间?毕竟,这才是真正可以改变和控制的部分。就像应用程序的其余部分一样,理解这种时序的最简单方法是在ISR的开始和结束时切换anI/O位,这可以让开发人员了解中断时序。
嵌入式开发人员确实需要了解周期性和异步中断发生的频率,这样他们才能确保程序的其余部分有机会运行。了解中断是否有可能同时发生,以及它们是否了解一次完成和下一次运行之间的延迟,也很重要。嵌套中断会极大地影响系统的运行,为了实现实时性能,工程师需要了解与时序相关的电位问题。
技巧7——利用中断驱动架构
越来越多的应用程序正在走向移动化,并依赖电池为它们提供运行时电源。为了最大化电池寿命和运行时间,微控制器需要尽可能多的时间处于睡眠状态。最好的方法是利用中断驱动架构。在这种架构中,系统处于非常低的功耗状态,微控制器内核基本上处于关闭状态,这在轮询系统中是无法实现的,然后系统被一个中断唤醒,这可能是一个外部事件或内部计时器。系统醒来,做它需要做的事情,然后回到睡眠状态。
目前可用的微控制器架构有一些很好的技巧,可以用来帮助最大限度地延长低功耗模式下的时间。例如,通过使用中断来唤醒系统,可以确保只有一个事件可以唤醒系统,但更重要的是,系统可以设置为不会发生到中断的上下文切换!系统在中断中唤醒,然后使用一种特殊的模式,称为“休眠退出”,一旦中断完成,系统立即返回休眠状态!
这些只是一些技巧,可以帮助任何开发人员在调用中断时做出正确的选择。当然,有些时候需要使用轮询技术,但是当中断是合理的并且具有设计意义时,嵌入式开发人员使用这些技巧来确保正确设置中断,这将节省大量的时间和调试基于中断的系统的麻烦。