Blogs

微控制器简介 - 中断

Mike Silva.2013年9月18日6评论

快速链接

谈论中断太快了!

至少,至少,可能是对本章的一个反应。  但多年来,我变得相信,新的微控制器程序员应该了解中断,然后在将任何复杂的外围设备引入定时器,UART,ADC以及在现代微控制器上找到的所有其他强大功能块之前。 由于这些外围设备通常与中断使用,因此对其不包括中断的任何介绍是一个不完整的引入,并且在最终引入中断概念后必须重新审视每个外围设备。 这只是让我浪费。

所以,让我们谈谈中断。 当我开始编程时,我曾经被他们吓倒了μCS,也许你也是。 这是非常错误的态度 - 中断不是某种魔法,而只是一种非常强大,非常可管理的技术,可以在“意外”时代需要注意的各种东西。

本文以PDF格式提供,便于打印

考虑这种情况。 我们需要监控GPIO输入并响应在几微秒内活动的输入。由于我们的人类按钮推动,但是,没有数十毫秒,但是几微秒。 The μC程序可以在执行此输入活动时执行任何点。 我们需要相当于每秒监控此信号而不是百次,但是 数十万 每秒时代,虽然仍在做我们的计划需要做的所有其他事情。 我们怎样才能如此迅速地回复,并且仍有任何时间在我们的计划中做任何其他工作? 中断是答案。

现在你看到了,现在你没有

中断是一个信号(通常被称为“中断请求”)到CPU,以立即开始执行不同的代码,编写的代码响应中断的原因。 在最佳情况下,“立即”可以尽快结束。  中断请求的生成与ISR中的进入之间的时间称为“中断延迟”,更快(较低延迟) is always better. CPU将记住通过将该指令地址存储在寄存器或内存位置中的指令地址,然后将其直接跳转到由程序员指定的代码来执行该指令的位置。 除了保存下一个指令的地址外,它还通常还会保存CPU状态寄存器并禁用进一步的中断。 此外,在大多数情况下,在大多数情况下,会自动清除触发ISR条目的中断请求,以便单个中断请求不会导致多次输入的ISR。 偶尔,根据中断类型和微控制器设计,它将需要ISR中的用户代码明确清除中断请求。 检查一个给定的中断源的数据表是非常重要的,以查看ISR代码是否必须清除中断请求,或者UC只会继续在一个中断请求活动中重新中断。

微控制器CPU将设计用于响应多个不同的中断源(可能是10到100个源,通常),并且每个源都可以具有特定的用户写入代码,该代码在该中断触发时执行。 执行中断的代码称为“中断服务例程”或ISR。  “现在你看到了它,现在你不会”标题是指你在观看慢动作中的代码是否会看到的内容。  The program counter 将从一个指令移动到下一个指令,然后当中断触发PC突然 end up 在某些完全不同的程序区域(ISR的入口点)。 然后,当ISR完成时,PC会突然指向下一个指令,就像没有发生任何事情一样。

超出重置时,中断始终关闭或禁用。 事实上,它们是双倍禁用的。 每个单独的中断源都被禁用,并且还禁用了全局CPU中断标志。 对于发生的中断(要具有其ISR运行),必须启用各个中断,并且必须启用CPU全局中断。 当然,当然,必须发生中断状态本身。销钉被驱动为低电平,或者将定时器滚动,或者无论是触发特定中断的定时器。

它由用户程序选择要启用的中断,并且在程序执行中的哪个点以启用它们(并且一旦启用,可以再次禁用中断,然后根据需要经常启用中断)。 用户程序也将有 每个中断将被启用的ISR(非常类似于函数),并且每个ISR都将映射到其相应的中断源。 必须有史以来启用的任何中断,这是一个至关重要的是,有一个有效的ISR映射到它。 否则,当中断触发器时,CPU将开始尝试在杂草中的一些垃圾位置中执行代码,并且这就是该结尾。 或者,如果您幸运的话,编译器将提供哎呀!所有未使用中断的ISR,以及您的调试器,您会发现μC循环在那个ISR中并弄清楚你出错的地方。

此图尝试展示中断,如果启用,暂停后台代码并运行关联的ISR:

中断图

这里是一个图表的图表 启用中断之前发生的中断事件,以及如何在启用后响应中断:

中断图2B.

在哪里 是否保存了返回地址和注册数据?

作为对中断请求的自动响应的一部分,UC硬件将保存要执行的下一个指令的地址。 该地址将保存在堆栈上(如CPU堆栈指针指向),或者可以保存到特殊的CPU寄存器中,通常称为“链接寄存器”。 AVR使用硬件堆栈指针,而STM32使用链接寄存器。 保存到链接寄存器的速度更快,因为寄存器到寄存器移动始终比注册到内存移动更快,但是当另一个级别的中断(或子例程) 是必需的,然后必须通过用户代码(如果您使用的HLL,编译器生成的代码,必须将链接寄存器保存到内存中。

至于任何 注册保存和恢复的注册数据,数据也将保存到堆栈 - 即将到内存。 该堆栈可以是由硬件堆栈指针维护的堆栈,或者它可以是由软件维护的堆栈,就像用于在必要时保存链接寄存器一样。

是一个函数吗?

如上所述,ISR类似于功能,但对于大多数微控制器设计存在一些关键差异。 这些差异中的大部分围绕着ISR必须“不可见”到执行的后台代码 ISR is run. 这意味着可以改变CPU寄存器或标志,即背景代码 may be using. 与背景代码所知,CPU的总状态必须是相同的,当ISR返回时,就像中断触发时一样。 因此,ISR必须在条目上保存CPU的状态,并在退出时恢复相同的状态。

如果使用高级语言,编译器将负责大多数或所有这些状态保存和恢复。 程序员只需告诉编译器,ISR函数是ISR,以及它映射到的中断,编译器将填写代码详细信息。 编译器将为ISR添加以下代码:

  • 在用户ISR代码之前,为维护主代码状态所需的所有标志和寄存器的代码
  • 在用户ISR代码之后,以正确的顺序恢复所有已保存的标志和寄存器的代码
  • 最后,返回从中断指令执行任何最终清理,例如恢复已保存的状态寄存器,并在中断指令后返回指令的程序执行

这compiler will also handle mapping the actual ISR code to the associated interrupt.  如果在ASM中写入程序员必须明确地编写所有状态保存和恢复代码,并明确地将ISR的地址放入相应的中断向量或插槽。 

每个可能的中断将在通常称为中断表或中断向量表的内存区域中具有条目。 根据CPU设计,这些条目可能包含实际代码,或者它们可能是存储相关ISRS的起始地址的数据位置。 在发生时,AVR使用第一形式的中断表(每个中断向量位置包含跳转到关联的ISR),而STM32使用第二种表格(每个中断向量位置包含相关的ISR的地址)。

中断向量表条目对于永远不会启用的中断,可以留空,或者都可以设置为点立即返回的虚拟ISR,或者全部可以设置为某种方式的ISR,以某种方式日志和报告意外的ISR中断(是硬件故障吗?  程序员是否忘记为已启用的中断写入ISR?)。

ISR应该做些什么?

在大多数情况下,答案尽可能少。 ISR应该做必须立即完成的事情,但不应该做任何更好的东西,以便在后面的背景代码中完成。  Here's an analogy: 当门铃圈(中断!)时,您正在为您的汉语俱乐部雕刻雕塑冰雕塑。 你回答了门(ISR的开始)并接收你订购的2000件拼图。 现在,你在这一刻坐下来完成拼图吗? 或者你把拼图放在一个安全的地方,然后在融化之前回到雕塑? 谨慎说你把拼图放在一边,然后回到另一个工作,然后当你没有什么比迫切需要做的时候回到谜题。 这同样适用于ISR。 执行ISR中必须的内容,并保存任何数据或状态信息,以便您的背景代码可以在更合适的时间内完成其余工作。

当启用多个中断时,这变得更加关键,通常在嵌入式系统中通常是这种情况。 拥有需要很长时间才能运行的ISR(因为它们在ISR内部做太多)可能会导致对需要快速处理的其他中断的延迟响应。

谁响了?

在某些情况下,只有一个事件可以触发给定的中断,因此该中断的ISR确切地知道已触发它的事件。 但是,在许多情况下,多个事件将所有向量传染料到单个ISR。 例如,使用STM32多达4个定时器事件可以向量到单个计时器ISR(我们将在Timers的章节中看到它)。 在这些情况下,ISR的第一个任务是确定哪个事件导致中断。 它将通过检查所有可能的中断事件的适当标志来执行此操作,直到它找到导致中断的情况。 然后它将以正常方式处理中断事件。

中断驱动的LED程序

所有中断的形式,所有的微控制器都可以响应指定的引脚上的简单数字变化(通常称为“外部中断”引脚)。 例如,如果引脚通过上拉电阻保持高电平,则由某些信号或动作提起低,引脚上的高到低过渡可以触发中断。 我们可以修改按钮控制的LED程序,以通过中断检测按钮转换并相应地更改LED。 这将为我们提供简单的中断介绍,并显示为什么读取与外部的按钮 中断并不是一个好主意。

无论您是使用AVR还是STM32,第一步就是要查看芯片数据表,以查看哪些引脚或引脚支持外部中断。 对于具有ATMEGA8515的STK-500,有三个这样的引脚,PD2,PD3和PE0,因此我们将选择将STK-500的8个按钮连接到Portd,并使用按钮2或按钮3 - 让我们选择按钮2(pd2),它是外部中断INT0。 对于STM32VLDISCOVERY板,我们只有一个按钮,连接到PA0。 事实证明,STM32设备非常灵活,几乎任何GPIO都可以用作外部中断,因此PA0上的按钮将很好。

外部中断类型和极性

可以根据特定的微控制器允许的方式配置外部中断的几种不同的方法。 首先,外部中断可以是边缘触发的或级别触发的。 边缘触发的中断生成中断请求 只有在边缘 - 即,当中断线从一个状态到相反状态时(1->0 or 0->1).  触发级别的中断生成中断请求 只要中断行处于活动状态,例如低级触发中断就会生成请求 每当线路低时,(这很重要),将继续生成请求,直到线路带高。 如果软件或硬件设计错误,则单个级别触发的中断可能会导致中断的激发循环。中断..输入中断,每秒千分之一或数百万次。

外部中断的第二方面是中断极性。 对于边缘触发中断,极性可能是触发的上升沿(0->1)触发或下降边缘(1->0).  对于级触发中断,极性可以是低触发的(每当输入为0时)或触发高电平(只要输入为1)。 同样,这些是可能的配置选项,但不是每个微控制器上的每个外部中断输入都将支持所有这些选项。 甚至可以具有外部中断输入,触发任何逻辑变化 - 即,它将在高低转换时生成中断请求(1->0),或在低高的过渡(0->1).  这只是一个边缘触发的中断,它会在两种可能的边缘上自动触发,而不是在一个指定的边缘上触发。

还要注意 边缘触发和级别触发中断之间的区别是所有类型中断的基本区别,而不仅仅是外部中断。

配置中断

在一般情况下,配置特定中断需要良好的外观芯片数据表。 对于固定引脚上的外部中断,事物的内容比STM32的零件上的可配置中断更容易。 AVR Int0中断只需要我们选择中断极性并启用中断。 AVR INT0可以配置用于上升沿,下降沿,侧边缘或低级触发。 记住STK-500按钮处于活动状态,我们将选择下降沿以便能够捕获初始按钮推(否则我们必须等到按钮在生成中断之前释放按钮)。 查看我们的AVR数据表我们看到我们需要将寄存器配置MCUCR中的BITS ISC01 / ISC00设置为1/0,以供负边触发。 现在我们需要启用INT0中断,因此我们需要在中断使能寄存器GICR中设置位INT0。 此时,如果启用了全局中断,则CPU将响应INT0上的任何负边缘输入。 更重要的是,如果有一个以前的边缘设置内部INT0标志,则现在设置的标志现在将生成中断。 这几乎总是一件坏事,从先前设置的标志中产生中断,因此我们需要在启用中断之前做另一件事。 我们需要在中断标志寄存器GIFR中写入“1”到位INTF0。 写一个“1”清除任何挂起的INT0中断,这是我们在启用中断之前要做的。 所以我们的完整AVR配置如下:

MCUCR = 0b10 << ISC00;  // negative edge trigger
GIFR = 1 << INTF0;	// clear any pending interrupt
GICR = 1 << INT0;	// enable INT0

这ISR

现在是时候编写中断服务例程了。 对于AVR,负边缘意味着按钮刚刚被推动,因此我们希望转动LED。 同样,正边缘意味着按钮已被释放,我们希望关闭LED。 但是等待,我们只配置了负边缘,我们将如何检测到正边缘。这实际上非常简单。 在ISR内部,如果中断配置为负边缘,请重新配置正边沿。 如果配置为正边沿,则重新配置为负边。 在重新配置中断的相同代码分支中,根据这次将我们带入ISR的边缘或关闭LED打开或关闭。

将ISR标记为ISR,并将其映射到INT0向量,如提到的那样依赖于非常编译器。 对于Atmel Studio的GCC编译器版本,它将如下所示:

ISR(INT0_vect)
{
   // ISR code here
}

这Final Program - AVR

这是完整的程序,具有上面讨论的配置和ISR,并且在进入Forever循环之前,全局中断使能。 ISR备用下一个中断边缘,并根据刚刚导致中断的边路打开或关闭LED。 如上所述,当输入ISR时,硬件会自动清除中断请求,因此ISR中没有代码为此任务。 

为了模拟自己做自己的东西的背景代码,程序有我们的旧LED闪烁的循环运行。 因此,在“操作”中,您将看到一个LED通过背景码闪烁,并通过按钮中断另一个打开和关闭。 

// AVR_INT1
// Blink LED on PB0
// Control LED on PB1 via
// button interrupt on PD3

#include <avr/io.h>
#include <avr/interrupt.h> // bring in interrupt stuff

ISR(INT0_vect)             // tell compiler this ISR is for INT0 (PD2)
{
  if (MCUCR & (1<<ISC00))  // true if pos. edge trigger
  {
    PORTB |= (1<<PB1);     // LED 1 OFF
    MCUCR &= ~(1<<ISC00);  // set to neg. edge trigger
  }
  else                     // neg. edge trigger
  {
    PORTB &= ~(1<<PB1);    // LED 1 ON
    MCUCR |= (1<<ISC00);   // set to pos. edge trigger
  }
}

void delay(volatile uint32_t d)
{
  while (d-- != 0)
    ;
}

int main(void)
{
  DDRB = (3<<PB0);         // LED output on PB0, PB1
  PORTB = (3<<PB0);        // start with LEDs OFF
  
  MCUCR = 0b10<<ISC00;     // negative edge trigger
  GIFR = 1<<INTF0;         // clear any pending interrupt
  GICR = 1<<INT0;          // enable INT0
  
  sei();                   // enable all interrupts
  
  while(1)
  {
    PORTB ^= (1<<PB0);	 // toggle LED 0 in the background
    delay(80000);
  }
}

此范围图像显示中断触发按钮推动(绿色)之间的延迟 和ISR内部的LED输出与上述程序运行。 时间尺度是2US /划分,因此从外部输入到ISR输出的响应时间仅为3微秒(带有8 MHz的AVR):

中断响应

这Final Program - STM32

再次,此程序的STM32版本与AVR版本基本相同。 什么是大多数是所需的中断配置和ISR声明。 由于STM32外部中断容量如此自定义,因此它需要比AVR更多的初始化。 关于ISR声明,ARM Cortex M系列在设计中是唯一的唯一功能,以便常规功能可以是ISR。 这是可能的,因为Cortex M设计自动保存在输入ISR之前所需的所有状态和寄存器数据,并在ISR的末尾恢复全部。 也就是说,Cortex M会自动完成大多数其他设计中需要额外的ISR代码的内务监听。 因此,我们的ISR只是一个正常的函数,没有任何额外的ISR代码,它给出了exti0中断的正确名称,以便它覆盖了启动代码中的弱声明。 这可能有点高级,所以只知道它足以命名为正确的名字 - 我确定它在某处的文档中,但我只是看着启动文件以查找正确的名称。  能够查看你的编译器启动文件是一个很大的优势,而且你不应该害怕在他们身上捅。

另请注意,与AVR不同,STM32要求在ISR内明确清除中断请求。

// STM32_INT1
// Blink LED on PC9
// Control LED on PC8 via
// button interrupt on PA0

#include <stm32f10x.h>

void EXTI0_IRQHandler(void)     // ISR is just a regular function with correct name
{
  if (EXTI->RTSR & 1)           // was set for positive edge
  {
    GPIOC->ODR |= (1<<8);       // set blue LED
    EXTI->RTSR = 0;
    EXTI->FTSR = 1;             // set for negative edge
  }
  else  // was set for negative edge
  {
    GPIOC->ODR &= ~(1<<8);      // clr blue LED
    EXTI->FTSR = 0;
    EXTI->RTSR = 1;             // set for positive edge
  }
  EXTI->PR = 1;                 // clear this interrupt flag
}

void delay(volatile uint32_t d)
{
  while (d-- != 0)
    ;
}

int main(void)
{
  RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // enable PORTA for button input
  GPIOA->CRL = (0b0100);              // CNF=1, MODE=0 (floating input)

  RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // enable PORTC for LED output
  GPIOC->CRH = 0b0010 | (0b0010<<4);  // CNF=0, MODE=2 (2MHz output) (PC8,PC9)

  AFIO->EXTICR[0] = 0;                // EXTI0 is PA0
  EXTI->RTSR = 1;                     // rising edge, EXTI0
  EXTI->IMR = 1;                      // enable EXTI0
  NVIC->ISER[0] = (1 << EXTI0_IRQn);  // enable EXTI0 in NVIC

  while (1)
  {
    GPIOC->ODR ^= (1<<9);             // toggle green LED
    delay(80000);
  }
}

中断不能那么简单,对吗?

正确的。 关于中断,有很多细节和Gotchas讨论。 例如,我们上面的中断程序实际上是垃圾,即,他们可以偶尔给出错误的结果。 您甚至可能会注意到LED偶尔似乎错过了按钮推动。 但它们足以向您展示如何中断工作,并为与他们进一步合作提供基础。 下一个教程章节将进入一些最重要的细节和Gotchas,包括我们的中断程序以及如何修复它们的错误。 之后我们将开始查看定时器,其中最有用的是所有微控制器外围设备,而是一个几乎需要有效地使用中断的一个。


[]
评论 Lovakiranvarma.2013年9月27日
谢谢代码!它真的很有帮助。非常干净和整洁的解释
[]
评论 Lovakiranvarma.2013年9月27日
谢谢代码!它真的很有帮助。非常干净,整洁的解释frm



http://www.npeducations.com
[]
评论 匿名19.2013年10月3日
Minor error on "这Final Program - AVR":
中断INT0在PD2上(不是PD3)
[]
评论 mjsilva.2013年10月14日
是的,很好,我已经修复了。经典的脱机错误 - 我一直思考“第3键!”谢谢你抓住它。
[]
评论 西德博梁2015年8月22日
嗨mjsilva,
代码结构是STM32和AVR的代码结构吗?
http://www.circuitstudy.com/

谢谢你。
[]
评论 emin562018年11月23日

谢谢你的教程

要发布回复评论,请单击连接到每个注释的“回复”按钮。发布新的评论(不是回复评论),请在评论的顶部查看“写评论”选项卡。

注册将允许您参加所有相关网站的论坛,并为您提供所有PDF下载。

注册

我同意 使用条款隐私政策.

尝试我们偶尔但流行的时事通讯。非常容易取消订阅。
或登录