使用 Arduino 中断 – 硬件、引脚变化和定时器

这篇具有很好参考价值的文章主要介绍了使用 Arduino 中断 – 硬件、引脚变化和定时器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

使用 Arduino 中断 – 硬件、引脚变化和定时器

查看原文
今天我们将学习中断,这是Arduino和其他微控制器的一个非常重要的基本功能。虽然我们将专注于Arduino Uno,但这里介绍的概念与其他板同样有效。

介绍

当我们设计一个项目时,我们通常基于微控制器。这样做有很多很好的理由,其中包括:

  • 微控制器可以处理多个输入和输出。
  • 微控制器可以提供精密定时脉冲。
  • 微控制器速度很快。

因为它们可以处理多个输入,并且因为它们可以做很多事情,所以微控制器可能会变得非常繁忙。繁忙的微控制器需要一种方法来管理外部事件,例如按下按钮,同时兼顾其他输入和输出时序过程。

控制外部输入或内部定时事件的一种方法是使用中断。

使用 Arduino 中断 – 硬件、引脚变化和定时器

中断的工作原理

中断顾名思义,是一种中断程序执行以处理其他事情的方法。

中断绝不是微控制器所独有的,它们已经在计算机和控制器中使用了几十年。当您在键盘上键入、移动鼠标或在触摸屏上滑动时,您正在创建中断,这些中断使服务正常工作,从而为您的操作创建适当的响应。

在其基本形式中,中断的工作方式如下:

  • 程序正在运行。
  • 发生中断。
  • 程序将暂停,其数据被放在一边,以便以后可以恢复。
  • 运行与中断相关的代码。
  • 中断代码完成后,程序将从中断的位置继续。

中断非常适合监发生的事件,例如按下开关或警报触发。当您需要精确测量输入脉冲时,它们也是正确的选择。

微控制器和微处理器使用的中断类型有很多种,中断功能因型号而异。它们都可以大致分为两类:

  • 硬件中断 这些通常来自外部信号。
  • 软件中断 – 这些是内部信号,通常由计时器或软件相关事件控制。

使用中断会让你成为一个更好的编码员,一旦你熟悉了它们,它们就不难使用。今天,我们将看到如何使用Arduino Uno的中断。

Arduino Uno 中断

Arduino Uno支持三种类型的中断:

  • 硬件中断 – 特定引脚上的外部中断信号。
  • 引脚更改引发中断 – 任何引脚上的外部中断,分组到端口中。
  • 定时器中断 – 内部定时器生成的中断,在软件中操作。

我们将在稍后详细讨论这些内容,但现在,我们只是说它们都以基本相同的方式工作。发生中断事件时,微控制器会运行您放置在“中断服务例程”或 ISR 函数中的一些代码。

中断适合的位置

让我们看看所有这些如何融入您的Arduino程序。

我们可以使用一个简单的流程图来可视化Arduino程序,如下所示:

使用 Arduino 中断 – 硬件、引脚变化和定时器

程序首先包括库(如果需要)并定义全局变量和对象。

在设置功能中,我们设置 PinModes,启动对象和设备,并运行要在微控制器启动时执行的任何一次性代码。

然后我们继续循环。在循环中,我们按顺序运行代码正文。一旦我们到达循环的底部,我们就会从顶部重新开始。我们一直处于循环中,直到微控制器重置,此时我们又回到了起点。

现在查看相同的流程图,只是这次使用中断:

使用 Arduino 中断 – 硬件、引脚变化和定时器

程序的执行与原始示例相同,程序在运行“启动”和“设置”过程后仍保留在循环中。但除此之外,我们还有另一个标有“ISR”的框。这是中断服务例程,它将在发生中断事件时运行。

因此,在我们的流程图中,我们可以看到代码执行分支出循环并分支到 ISR。ISR 代码完成后,代码执行将返回到循环中,位于它分支的同一位置。

ISR 仅在发生中断时运行。

也可以有一个只有ISR的程序,并且不使用循环。在这种情况下,在发生中断之前,不会运行任何内容。稍后您将看到几个示例。

中断服务例程

中断服务例程 (ISR) 本质上是一个函数。但是,与常规的Arduino函数不同,您不能将参数传递给它,也不能从中返回任何值。

实际上,ISR 函数有许多限制,其中大多数是由于同一件事——它们需要快速。非常非常快。

想想看,你实际上是在中断一个正在工作的微控制器,而很多工作都涉及计时。您无法长时间中断它,因此在 ISR 中无法执行许多操作:

  • 你不能使用delay( ) 函数。
  • 不仅如此,您也不能使用millis( ) 函数。
  • 没有串行库,因此无法打印到串行监视器。
  • 仅使用全局变量,这些变量应声明为volatile。

尽管有这些限制,ISR 可以执行非常有用的工作,例如更改一个或多个全局变量的值,然后可以在循环中读取这些变量。

只要记住尽快完成有用的工作!

为什么要使用中断?

为了说明使用中断的价值,我们将运行一个非常简单的实验,除了Arduino之外,实际上只需要一个按钮开关。我也在使用 LED,但由于它连接到引脚 13,您可以放弃它,而只依靠内置 LED。

在我们的实验中,我们将让按钮充当切换开关,在每次按下按钮时交替打开和关闭 LED。这是一个非常常见的应用程序,我相信你已经看过它,并且可能以前已经为它编码过。

以下是我们将如何连接这一切:

使用 Arduino 中断 – 硬件、引脚变化和定时器

我为我的红色 LED 使用了 220 欧姆的压降电阻,但您可以使用 150 到 470 欧姆之间的任何值,您可以使用您喜欢的任何颜色的 LED。或者只需取消 LED 并使用板载 LED,因为它也连接到引脚 13。

现在这是我们同样简单的程序:

/*
  LED Toggle
  led-toggle.ino
  Use pushbutton switch to toggle LED
  
  DroneBot Workshop 2022
  https://dronebotworkshop.com
*/

// Define LED and switch connections
const byte ledPin = 13;
const byte buttonPin = 2;

// Boolean to represent toggle state

volatile bool toggleState = false;

void checkSwitch() {
  // Check status of switch
  // Toggle LED if button pressed
  
  if (digitalRead(buttonPin) == LOW) {
    // Switch was pressed
    // Slight delay to debounce
    delay(200);
    // Change state of toggle
    toggleState = !toggleState;
    // Indicate state on LED
    digitalWrite(ledPin,toggleState);
  }
}

void setup() {
  // Set LED pin as output
  pinMode(ledPin, OUTPUT);
  // Set switch pin as INPUT with pullup
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  
  // Check switch
  checkSwitch();
  
}

我们首先为开关和LED引脚声明几个常量字节。

我们还定义了一个名为togglestate 的布尔值,我们将用它来表示切换开关的当前状态。这被初始化为假。

接下来,我们定义一个名为checkSwitch 的函数。这是一个非常简单的功能,可以检查按钮的状态,如果按下按钮,则会反转当前切换值,然后使用它来更改 LED 的状态。

setup()中,我们只需将LED引脚设置为输出,将开关引脚设置为输入即可。我们使用内部上拉电阻作为输入。

我们在循环中所做的只是调用checkSwitch函数,因此我们始终查询开关状态。

加载程序并进行测试。每次按下开关时,LED 都应打开或关闭。

修改(即破坏)我们的程序

我们的开关似乎工作得很好,如果您只想拥有一个拨动开关,那么您没有理由需要更进一步。

但是,如果开关是更大设计的一个组件呢?假设一个温度计使用它从摄氏度切换到华氏度。建造起来有多容易?

从表面上看,这似乎很简单,只需将DHT22或类似传感器的代码添加到切换代码中,然后使用切换值来确定温度单位。

但是,在实践中,这可能是一个不同的事!为了说明这一点,让我们对代码进行一个小的修改:

/*
  LED Toggle with Delay
  led-toggle-delay.ino
  Use pushbutton switch to toggle LED

  DroneBot Workshop 2022
  https://dronebotworkshop.com
*/

// Define LED and switch connections
const byte ledPin = 13;
const byte buttonPin = 2;

// Boolean to represent toggle state

volatile bool toggleState = false;

void checkSwitch() {
  // Check status of switch
  // Toggle LED if button pressed

  if (digitalRead(buttonPin) == LOW) {
    // Switch was pressed
    // Slight delay to debounce
    delay(200);
    // Change state of toggle
    toggleState = !toggleState;
    // Indicate state on LED
    digitalWrite(ledPin, toggleState);
  }
}

void setup() {
  // Set LED pin as output
  pinMode(ledPin, OUTPUT);
  // Set switch pin as INPUT with pullup
  pinMode(buttonPin, INPUT_PULLUP);
  // Setup Serial Monitor
  Serial.begin(9600);
}

void loop() {
  // Check switch
  checkSwitch();

  // Add a 5-second time delay
  Serial.println("Delay Started");
  delay(5000);
  Serial.println("Delay Finished");
  Serial.println("..............");
}

您会注意到修改后的程序中有两个差异:

  • 我们设置了串行监视器,并在循环中打印到它。
  • 我们在循环底部添加了 5 秒的延迟。

现在,5秒的延迟诚然是一件愚蠢的事情,添加到程序中,但我把它放在那里是为了说明一个观点。它可以很容易地读取DHT22,然后在传感器稳定时延迟2秒。你明白了,我正在添加一个需要一些时间才能完成的过程到我们的循环中。

将程序加载到 Arduino,然后重试切换。我想你会注意到一个不同。

你可能认为你破坏了它,但实际上它只是退化了。查看串行监视器并观察,尝试在延迟事件之间的短暂时间内按下按钮。您可能会很幸运并实际触发切换。

当然,我想说明的一点是,如果你在循环中还有其他任何可能占用超过几毫秒时间的东西,那么轮询循环内的开关并不是从中获取读数的最佳方式。

更好的解决方案是使用硬件中断。

硬件中断

硬件中断是外部中断,在大多数Arduino型号上仅限于特定的引脚。这些引脚配置为输入,可以通过操纵其逻辑状态来触发硬件中断。

这些可能是微控制器实验者最常用的中断类型,我们在DroneBot研讨会上也多次使用它们。

Arduino 硬件中断引脚

在Arduino Uno上,只有两个引脚支持硬件中断:

使用 Arduino 中断 – 硬件、引脚变化和定时器

  • 引脚 2 – 中断向量 0
  • 引脚 3 – 中断向量 1。

并非所有 Arduino 板都限制为 2 个硬件中断引脚,一些 Arduino 板具有更多。下图显示了几个常见 Arduino 板上的硬件中断数:

使用硬件中断

使用硬件中断实际上非常简单,因为您实际上只需要做两件事:

  • 编写一个函数以用作中断服务例程。
  • 将函数附加到要使用的特定中断,并指定如何触发它。

ISR 函数应遵守有关速度和使用全局变量的规则,但除此之外,它只是一个函数。它可以具有所需的任何有效名称,但不能具有输入参数。

附加中断功能

使用 Arduino 中断 – 硬件、引脚变化和定时器

要将函数“粘合”到特定中断,您将使用attachInterrupt函数。此函数具有以下语法和参数:

  • 中断向量 – 您希望使用的中断。请注意,这是内部中断向量编号,而不是引脚编号。
  • ISR – 要粘附到中断的中断服务例程函数的名称。
  • 模式 – 您希望如何触发中断。

对于模式,有四个选项:

  • RISING(上升) – 当输入从低到高时触发。
  • FALLING(下降)– 当输入从高到低时触发。
  • LOW(低) – 当输入保持低电平时触发。
  • CHANGE– 每当输入状态从高到低或从低到高时触发。

您通常会在设置函数中使用 attachInterrupt

数字引脚到中断功能

attachInterrupt 函数中的中断矢量参数与引脚编号不同,并且在 Arduino 板之间可能会有所不同。获取此数字的(更好)方法是使用digitalPinToInterrupt函数。

该函数的名称也是其描述,它接受引脚编号并返回中断向量编号。

您可以直接在attachInterrupt 函数中使用digitalPinToInterrupt

attachInterrupt(digitalPinToInterrupt(pin),ISR,Mode)

这是硬件中断编码的首选方式,因为它使代码在电路板之间可移植。

重写硬件中断程序

现在我们进一步了解了如何使用硬件中断,让我们修改切换和延迟程序以使用它们。

以下是使用硬件中断的程序的更新版本:

/*
  LED Toggle with Delay & Interrupt
  led-toggle-interrupt.ino
  Use pushbutton switch to toggle LED with interrupt

  DroneBot Workshop 2022
  https://dronebotworkshop.com
*/

// Define LED and switch connections
const byte ledPin = 13;
const byte buttonPin = 2;

// Boolean to represent toggle state

volatile bool toggleState = false;

void checkSwitch() {
  // Check status of switch
  // Toggle LED if button pressed

  if (digitalRead(buttonPin) == LOW) {
    // Switch was pressed
    // Change state of toggle
    toggleState = !toggleState;
    // Indicate state on LED
    digitalWrite(ledPin, toggleState);
  }
}

void setup() {
  // Set LED pin as output
  pinMode(ledPin, OUTPUT);
  // Set switch pin as INPUT with pullup
  pinMode(buttonPin, INPUT_PULLUP);

  // Attach Interrupt to Interrupt Service Routine
  attachInterrupt(digitalPinToInterrupt(buttonPin),checkSwitch, FALLING); 
}

void loop() {
  
  // 5-second time delay
  Serial.println("Delay Started");
  delay(5000);
  Serial.println("Delay Finished");
  Serial.println("..............");
}

我们同样地从声明 LED 和开关的引脚号开始。我们还使用相同的布尔值进行切换。

请注意,布尔值是可变的。这很重要,因为它的值是在中断服务例程中操作的。如果没有 volatile 语句,Arduino IDE 编译器可能会尝试过度优化代码并删除变量。

我们的checkSwitch 功能几乎与以前相同,唯一的区别是我们删除了延迟功能。这是因为我们将使用checkSwitch作为中断服务例程,并且我们不能在 ISR 中使用延迟

setup()中,我们执行通常的*pinMode命令,初始化串行监视器,然后运行attachInterrupt以将checkSwitch*功能附加到引脚2上的硬件中断。我们使用下降模式,因为我们希望在按下(并接地)开关时捕获。

我们在循环中所拥有的只是延迟,现在它将连续运行。现在,任何开关活动都将由中断处理。

加载程序并使用按钮播放,同时观察 LED 和串行监视器。您应该看到,尽管循环始终处于延迟状态,但切换开关仍有效。

如您所见,硬件中断是捕获开关输入的一种更有效的方法。

引脚变化引发中断

引脚更改引发中断是硬件中断的另一种形式。与我们刚刚使用的中断不同,它们不限于特定的引脚,所有引脚都可用于引脚更改引发中断。

问题是引脚更改引发中断被分组到端口中,并且同一端口上的所有引脚创建相同的引脚更改引发中断。如果您只使用一个引脚,这很好,否则,您需要找出导致中断的引脚。

引脚更改引发中断被限制为仅监视逻辑状态的更改。因此,按下开关将产生两个中断,一个在按下开关时,另一个在松开开关时。您必须自己弄清楚中断是由高电平还是低电平输入引起的。

引脚更改引发端口

Arduino Uno(和其他板卡)所基于的ATmega328芯片上的24个引脚几乎每个引脚都支持引脚切换中断。这包括用于16MHz晶体振荡器的两个引脚。

使用 Arduino 中断 – 硬件、引脚变化和定时器

在Arduino Uno上,有20个引脚可用于引脚更改引发中断,它们分为三个端口。

使用 Arduino 中断 – 硬件、引脚变化和定时器

引脚 8 到 13 位于端口 B 上。

使用 Arduino 中断 – 硬件、引脚变化和定时器

针脚 A0 至 A5 位于端口 C 上。

使用 Arduino 中断 – 硬件、引脚变化和定时器

引脚 0 到 7 位于端口 D 上。

使用引脚更改引发中断

要使用引脚更改引发中断,您需要执行以下操作:

  • 确定要使用的引脚。还需要告诉您使用哪些端口。
  • 启用所需的端口。
  • 启用该端口内必须为中断启用的引脚。
  • 编辑相应的中断服务例程。如果在同一端口上使用多个引脚,则 ISR 需要能够确定哪个引脚导致了中断。

让我们更详细地看一下这些步骤。对于我们的示例,我们将只使用单个引脚,稍后我们将运行一个在同一端口上使用两个引脚的程序。

选择端口

第一步是启用相应的端口,您将根据引脚编号确定该端口。要启用该端口,您将使用*引脚更改中断控制寄存器(Pin Change Interrupt Control Register)*或 PCICR。

使用 Arduino 中断 – 硬件、引脚变化和定时器

PCICR 有三个感兴趣的位:位 0、位 1 和位 2。每个位都与其中一个端口相关联,将其设置为 1 将启用该端口。

setup()程序中,您将向 PCICR 写入二进制数,以将相应的位设置为 1。当然,您可以启用多个端口。

启用/禁用端口上的引脚

启用端口后,您需要启用要用于引脚更改引发中断的引脚。您可以通过修改所选端口的引脚更改引发掩码来执行此操作。
使用 Arduino 中断 – 硬件、引脚变化和定时器

有三个引脚更改引发掩码,每个模板可以启用或禁用 8 个引脚。要启用引脚,请为其写入“1”。您可以根据需要启用任意数量的引脚,请记住,您需要找到一种方法来区分中断服务例程中的引脚。

使用 Arduino 中断 – 硬件、引脚变化和定时器

中断服务例程

与硬件中断不同,您不只是创建一个中断服务例程并为其指定任何随机名称。使用引脚更改引发中断时,已经为您定义了 ISR,因此您需要为您的端口使用正确的 ISR。

有三个端口,因此有三个 ISR,其名称如下所示:

使用 Arduino 中断 – 硬件、引脚变化和定时器

有关中断服务例程的所有相同规则适用于与引脚更改引发中断一起使用的 ISR。保持简短并使用易失性的全局变量。

试验引脚更改引发中断

为了运行接下来的几个示例,我们需要在Arduino中添加另一个按钮和LED。最终的连接如下所示:
使用 Arduino 中断 – 硬件、引脚变化和定时器

所以我们现在有:

  • 引脚 D2 上的按钮。
  • 引脚 D7 上的按钮。
  • 引脚 D11 上的 LED。
  • 引脚 D13 上的 LED。

请注意,D2 和 D7 上的两个按钮开关位于同一端口 D 上。

引脚更改引发中断示例 1 – 简单中断

我们将运行的第一个示例将说明如何为引脚更改引发中断编写代码。在这个实验中,我们将只使用一个开关和一个LED。请注意,我们的开关位于引脚 D7 上,它不是硬件中断引脚。这并不重要,因为我们将通过换针中断来感应它!

每次在引脚 D7 上遇到中断时,我们都会切换 LED 的状态。要记住的一件重要事情是,我们将感知输入状态的变化,因此按钮将产生两个中断 - 一个在按下它时,另一个在释放它时。

这将使我们的 LED 运行得很像刚刚与开关串联的!

这是我们的程序:

/*
  Pin Change Interrupt Test
  pin-change-test.ino
  Demonstrates use of Pin Change Interrupt
  Input on D7, LED on D13

  DroneBot Workshop 2022
  https://dronebotworkshop.com
*/

// LED and switch
const byte ledPin = 12;
const byte buttonPin = 7;

// Boolean to represent toggle state
volatile bool togglestate = false;

void setup() {

  // Set LED as output
  pinMode(ledPin, OUTPUT);

  // Set switch as input with pullup
  pinMode(buttonPin, INPUT_PULLUP);


  // Enable PCIE2 Bit3 = 1 (Port D)
  PCICR |= B00000100;
  // Select PCINT23 Bit7 = 1 (Pin D7)
  PCMSK2 |= B10000000;
}

void loop() {
  // No code in Loop
}

ISR(PCINT2_vect) {
  // Interrupt for Port D
  // Invert toggle state
  togglestate = !togglestate;
  // Indicate state on LED
  digitalWrite(ledPin, togglestate);
}

我们首先定义开关和LED的引脚。

接下来,我们创建一个volatile布尔值来表示切换状态。

在设置中,我们使用pinMode函数来定义我们的输入和输出。

然后我们设置了引脚更改引发中断。我们修改PCICR寄存器,让它知道我们要使用端口D,并修改端口D的掩码PCMSK2,将引脚D7设置为中断输入。

我们在 Loop 中没有任何代码,因为我们使用中断服务例程执行所有操作。

接下来是中断服务例程,当我们使用端口 D 时,我们使用*ISR(PCINT2_vect)。在 ISR 中,我们翻转toggleState*变量的状态并使用它来驱动 LED。

加载代码并尝试一下。按住开关时 LED 应亮起,松开开关时 LED 应熄灭。当然,使用微控制器确实是矫枉过正,但它确实演示了引脚更改引发中断!

引脚更改引发中断示例 2 – 同一端口上的多个中断

在前面的示例中,我们只能对引脚更改引发中断进行操作,因为端口上只有一个引脚启用了中断。毫无疑问,是什么导致了中断。

但是,当您在同一端口上为引脚更改引发中断启用两个或多个引脚时,如何处理它?您需要确定谁导致了中断,以便您可以采取相应的措施。

在此示例中,我们将有两个按钮和两个 LED。每个按钮将充当其各自 LED 的切换开关。这意味着当我们得到中断时,我们需要知道两件事:

  • 什么引脚导致中断?
  • 是低还是高?

我们将在程序中回答这两个问题:

/*
  Multiple Pin Change Interrupt Demo
  pin-change-multiple-test.ino
  Demonstrates Pin Change Interrupts with two on same port
  Inputs on D2 & D7, LEDs on D11 and D13

  DroneBot Workshop 2022
  https://dronebotworkshop.com
*/

// LEDs and switchs
const byte ledPin1 = 11;
const byte ledPin2 = 13;
const byte buttonPin1 = 2;
const byte buttonPin2 = 7;

// Booleans for input states
volatile bool D2_state = LOW;
volatile bool D7_state = LOW;

void setup() {

  // Set LEDs as outputs
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);

  // Set switches as inputs with pullups
  pinMode(buttonPin1, INPUT_PULLUP);
  pinMode(buttonPin2, INPUT_PULLUP);

  // Enable PCIE2 Bit3 = 1 (Port D)
  PCICR |= B00000100;
  // Enable PCINT18  & PCINT23 (Pins D2 & D7)
  PCMSK2 |= B10000100;
}

void loop() {

  // Loop code
}

ISR(PCINT2_vect) {
  // Port D Interrupt occured

  // Check if this was D2
  if (digitalRead(buttonPin1) == LOW) {
    //Pin D2 triggered the ISR on a Falling pulse
    D2_state = !D2_state;
    //Set LED 1 to state of D2_state boolean
    digitalWrite(ledPin1, D2_state);
  }

  // Check if this was D7
  if (digitalRead(buttonPin2) == LOW) {
    //Pin D7 triggered the ISR on a Falling pulse
    D7_state = !D7_state;
    //Set LED 2 to state of D7_state boolean
    digitalWrite(ledPin2, D7_state);
  }
}

程序如您所料开始,我们为 LED 和开关定义了一堆变量以及两个布尔值,每个切换状态一个。

在设置中,我们设置开关和LED的引脚模式,再次使用内部上拉电阻作为输入。

然后,我们启用端口D,就像我们在前面的程序中所做的那样,通过写入PCICR寄存器的“1”到位2。

接下来,我们为 PCMSK2 掩码的第 7 位和第 2 位写入“1”,让它知道引脚 D7 和 D2 将被视为引脚更改引发中断。

同样,循环中没有代码,一切都在中断服务例程中完成。

该例程是 ISR (PCINT2_vect),即端口 D 的 ISR,这与我们在上一个程序中使用的 ISR 相同。只有这一次,我们需要弄清楚是引脚D2还是D7导致了中断。

我们在此程序中寻找低条件,因为我们想在按下开关时切换按钮,而不是在松开开关时切换按钮。此外,我们使用几个if语句和一个数字读取来检查每个输入并确定它当前是否为LOW。如果是,那么我们切换相应的布尔值并使用它来控制 LED。

加载程序并对其进行测试。连接到引脚 D2 的按钮应控制引脚 D11 上的 LED,D7 上的按钮应与引脚 D13 上的 LED 配合使用。您应该能够独立切换它们中的每一个。

当扫描一组或一组开关时,或者当您需要外部中断但没有备用硬件中断引脚时,引脚更改引发中断非常有用。

定时器中断

定时器中断不使用外部信号。相反,这些中断是在软件中生成的,它们的时序基于Arduino Uno的16 MHz时钟振荡器。

您可能一直在使用定时器中断而没有意识到这一点,因为一些流行的库(如伺服和音调库)在内部使用定时器中断。请记住,如果您使用的是使用计时器的库,则需要知道这一点,以便不会编写冲突的代码。

Arduino Uno 计时器

Arduino Uno有三个内部定时器,Timer0,Timer1和Timer2。

使用 Arduino 中断 – 硬件、引脚变化和定时器
这些计时器并不相同,因为 Timer1 是 16 位计时器,而其他两个计时器只是 8 位计时器。位数决定了计时器可以计数的最大数字,8 位计时器为 256,16 位计时器为 65,536。

这些计时器中的值按时钟频率或时钟频率的一小部分递增。您可以使用软件来确定要设置的中断触发的计数,也可以在计时器溢出时触发中断。

对时钟频率进行分频

定时器由 ATmega328 内部的 16 MHz 振荡器计时。

使用 Arduino 中断 – 硬件、引脚变化和定时器

时钟的每个周期都是一个计时器“滴答”,因此在 16 MHz 时,“滴答”的周期为 62.5 纳秒。这是一个相当短的周期,对于许多定时应用来说,它太短而没有太多的实际用途。

为了减慢时钟信号,ATmega328 有一个“预分频器”,本质上是时钟频率的分频器。预分频器可以将时钟划分为更易于管理的较低频率,您可以从许多常见分频中进行选择,以创建长达64us的脉冲。

使用 Arduino 中断 – 硬件、引脚变化和定时器

每个定时器有三个时钟选择位,这些位的值可以确定预分频器设置以及定时源。您也可以通过将所有时钟选择位设置为零来完全停止时钟。

使用 Arduino 中断 – 硬件、引脚变化和定时器
Timer0 是一个 8 位定时器,使用位 CS01、CS02 和 CS03。
使用 Arduino 中断 – 硬件、引脚变化和定时器
Timer1 是 16 位定时器,使用位 CS10、CS11 和 CS12。

使用 Arduino 中断 – 硬件、引脚变化和定时器
Timer2 是另一个 8 位定时器,使用 CS20CS21CS22 位。

定时器的重要寄存器:

在配置计时器之前,让我们看一下计时器的一些重要寄存器-

TCCRnA/B– 代表定时器/计数器控制寄存器。

  • 保存计时器的主控制位。
  • 使用 WGM 位控制定时器模式。
  • 控制计时器的预分频器。

预分频器允许我们设置我们正在处理的计时器的时钟速度。Uno 支持预分频器为 1, 8,64,256,1024。将预分频器视为一个常数,如果我们除以主时钟频率(即 16MHz),我们将得到定时器的工作频率。例如,我们希望时钟的速度变慢,我们将预分频器的值设置为8,那么定时器的频率将减慢到16MHz / 8 = 2MHz,即,现在我们将在500ns时将其设置为62ns。

TCNTn– 代表定时器/计数器寄存器。

  • 控制计数器值(计数数)。

OCRnA/B– 代表 输出比较寄存器。

  • 顾名思义,这些寄存器中的值用于比较某些操作的计数器值。

TIMSKn– 代表寄存器中的计时器/计数器掩码。

  • 它们有助于启动某些计时器功能。

设置这些寄存器可以允许我们执行多个操作,其中之一就是“中断”或“计时器中断”。中断是一组中断源代码指令的命令。我们都知道 Uno 逐步执行指令,但是如果我们希望定期执行某些命令,并且在这样做时,原始代码应该停止在其当前位置,即中断发挥作用时。中断允许:-

  • 执行命令,即以固定的间隔接收输入或传递输出。
  • 修改 PWM 引脚上的 PWM 输出。
  • 生成特定频率的波形。

要设置中断,我们必须配置计时器。存在各种模式来配置Uno-

  • 正常模式
  • CTC 模式
  • 脉宽调制模式

我们将使用最常用的模式来配置计时器和设置中断,即 CTC 模式。

CTC 代表 比较匹配时的清除计时器。CTC的工作很简单 - 在寄存器(OCRnA)中定义一个计数器值,设置定时器时钟的速度,现在当定时器计数到比较寄存器值时,会发生中断,定时器再次重新启动。

定时器1的寄存器

正如我们已经知道的,Timer1能够生成输出比较匹配,溢出和输入捕获中断。对于输出比较匹配启用,使用 OCIE1BOCIE1A 位。同样,对于溢出中断启用,使用 TOIE1 位。同样,我们使用ICIE1位作为输入捕获中断。

定时器1由两个主要寄存器TCCR1ATCCR1B组成,它们控制TCCR1A负责PWM的定时器,TCCR1B用于设置前标量值。我们将TCCR1A寄存器中的所有位设置为0,因为我们不会使用它。

7 6 5 4 3 2 1 0
(0x80) COM1A1 COM1A0 COM1B1 COM1B0 WGM11 WGM10
读/写 R/W R/W R/W R/W R R R/W R/W
初始值 0 0 0 0 0 0 0 0

TCCR1A 定时器1 控制寄存器 A

但是,对于TCCR1B,前三位用于设置前标量值。这些被称为CS10,CS11和CS12位。

7 6 5 4 3 2 1 0
(0x81) ICNC1 ICES1 WGM13 WGM12 CS12 CS11 CS10
读/写 W W R R/W R/W R/W R/W R/W
初始值 0 0 0 0 0 0 0 0

TCCR1B 定时器1 控制寄存器 B

下表分别显示了 CS12、CS11 和 CS10 用于设置预分频器值的位。

CS12 CS11 CS10 描述
0 0 0 定时器停止
0 0 1 预分频器=1
0 1 0 预分频器=8
0 1 1 预分频器=64
1 0 0 预分频器=256
1 0 1 预分频器=1024
1 1 0 下降沿
1 1 1 上升沿

时钟选择位说明

要设置操作模式,请参阅下表:

WGM13 WGM12 WGM11 WGM10 模式 返回页首
0 0 0 0 正常 0xFFFF
0 1 0 0 CTC OCR1A
定时器2的寄存器

现在对于 Timer2,我们已经知道 Timer2 能够生成输出比较匹配和溢出中断。对于输出比较匹配启用,使用 OCIE2BOCIE2A 位。同样,对于溢出中断启用,使用 TOIE2 位。

Timer2由两个主要寄存器TCCR2ATCCR2B组成,它们控制TCCR2A负责PWM的定时器,TCCR2B用于设置前标量值。我们将TCCR2A寄存器中的所有位设置为0,因为我们不会使用它。

7 6 5 4 3 2 1 0
(0xB0) COM2A1 COM2A0 COM2B1 COM2B0 WGM21 WGM20
读/写 R/W R/W R/W R/W R R R/W R/W
初始值 0 0 0 0 0 0 0 0

TCCR2A 定时器2 控制寄存器 A

但是,对于TCCR2B,前三位用于设置前标量值。这些被称为 CS20、CS21 和 CS22 位。

7 6 5 4 3 2 1 0
(0xB1) FOC2A FOC2B WGM22 CS22 CS21 CS20
读/写 W W R R R/W R/W R/W R/W
初始值 0 0 0 0 0 0 0 0

TCCR2B 定时器2 控制寄存器 B

下表分别显示了 CS22、CS21 和 CS20 用于设置预分频器值的位。

CS22 CS21 CS20 描述
0 0 0 定时器停止
0 0 1 预分频器=1
0 1 0 预分频器=8
0 1 1 预分频器=32
1 0 0 预分频器=64
1 0 1 预分频器=128
1 1 0 预分频器=256
1 1 1 预分频器=1024

时钟选择位说明

要设置操作模式,请参阅下表:

WGM22 WGM21 WGM20 模式 返回页首
0 0 0 正常 0xFF
0 1 0 CTC OCR2A

使用计时器中断

定时器中断可以在几种不同的模式下操作,包括比较匹配模式和溢出模式

在比较匹配模式下,将计数器值放入比较匹配寄存器中。当计时器计数器与该寄存器中的值匹配时,将生成计时器中断。

在溢出模式下,当计时器达到其计数结束时,将生成计时器中断,将生成中断,计时器重置为零并再次开始计数。

通过将比较匹配寄存器与预分频器结合使用,您可以很好地获得您想要的任何定时周期,假设它在您的计时器范围内(8 位定时器最多只能除以 255)。

确定定时器输出频率和周期的公式如下:

使用 Arduino 中断 – 硬件、引脚变化和定时器

时 钟 预 分 频 器 × ( 比 较 匹 配 寄 存 器 + 1 ) = 频 率 \frac{时钟}{预分频器 \times(比较匹配寄存器 + 1)} = 频率 ×(+1)=

如果您知道所需的频率并想要确定“比较匹配寄存器”值,则可以按如下方式重写公式:
时 钟 预 分 频 器 × 频 率 − 1 = 比 较 匹 配 寄 存 器 \frac{时钟}{预分频器 \times 频率} -1 = 比较匹配寄存器 ×1=

使用上述公式,我们可以计算出要获得 1 Hz 输出,我们可以使用 1024 的预分频器和 15,624 的比较匹配寄存器。当此值超过 255 时,我们将只能使用 Timer1,即 16 位计时器。

计时器中断服务例程

与引脚更改引发中断一样,已经为您确定了定时器中断的中断服务例程的名称。
使用 Arduino 中断 – 硬件、引脚变化和定时器
每个计时器都有两个与之关联的 ISR,一个用于比较匹配模式,另一个用于溢出模式。

在上图中,单词“TIMER”后面的“x”应替换为计时器编号,例如 0、1 或 2。

简单计时器示例

这个非常简单的定时器示例为2 Hz输出或每秒两次设置定时器。然后我们使用计时器来控制LED,所以本质上我们正在构建闪烁程序!

我们将在 Arduino Uno引脚13上使用LED(带下降电阻)。如果您愿意,您可以修改以前的实验之一,或者只是使用 Arduino 板上的内置 LED。

以下是我们将使用的程序:

/*
  Arduino Timer Interrupt Flash Demo
  timer-int-flash.ino
  Flash LED using Timer1

  DroneBot Workshop 2022
  https://dronebotworkshop.com
*/

// Define the LED pin
#define ledPin 13

// Define timer compare match register value
int timer1_compare_match;

ISR(TIMER1_COMPA_vect)
// Interrupt Service Routine for compare mode
{
  // Preload timer with compare match value
  TCNT1 = timer1_compare_match;

  // Write opposite value to LED
  digitalWrite(ledPin, digitalRead(ledPin) ^ 1);
}

void setup() {
  // Set LED as output
  pinMode(ledPin, OUTPUT);

  // Disable all interrupts
  noInterrupts();

  // Initialize Timer1
  TCCR1A = 0;
  TCCR1B = 0;

  // Set timer1_compare_match to the correct compare match register value
  // 256 prescaler & 31246 compare match = 2Hz
  timer1_compare_match = 31249;

  // Preload timer with compare match value
  TCNT1 = timer1_compare_match;

  // Set prescaler to 256
  TCCR1B |= (1 << CS12);

  // Enable timer interrupt for compare mode
  TIMSK1 |= (1 << OCIE1A);

  // Enable all interrupts
  interrupts();
}

void loop() {
}

在此程序中,我们使用比较匹配模式,因此在定义LED引脚后,我们还创建一个整数来保存比较匹配寄存器值。

接下来是我们的中断服务例程。由于我们在 Timer1 上使用比较匹配模式,我们将使用ISR(TIMER1_COMPA_vect)。

在 ISR 中,我们做两件事:

  • 使用比较匹配值预加载计时器,以再次启动循环
  • 翻转 LED 的值。

在设置中,我们将 LED 引脚设置为输出。然后,我们暂时禁用所有中断,以防止在我们仍在设置时出现一个中断。

我们使用两个命令初始化 Timer1,然后设置我们的比较匹配值。由于我们希望实现 2 Hz,因此如果我们使用 31246 的预分频器,我们计算出 256 是一个很好的值。

然后,我们使用比较匹配值预加载计时器,将预分频器设置为 256,并在比较匹配模式下启用中断。

最后,我们重新启用所有中断。

加载程序并观察 LED 闪烁。在这种快感消失后,尝试不同的预分频器和比较匹配值。

结论

中断是构建需要精确计时或响应式用户界面的项目的好方法。虽然我们大多数人都涉足硬件中断,但许多人回避引脚更改引发或定时器中断,这是一种耻辱,因为它们非常有用,而且一旦您了解寄存器的使用,二进制编码就不是那么困难了。

我鼓励您将中断纳入您的下一个设计中。请记住,在某些情况下,被打断并不粗鲁!

资源

代码示例–本文中使用的所有代码都很好地打包在一个ZIP文件中。

附加中断 – 与硬件中断一起使用的附加中断函数的参考。文章来源地址https://www.toymoban.com/news/detail-426564.html

到了这里,关于使用 Arduino 中断 – 硬件、引脚变化和定时器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【雕爷学编程】Arduino智能家居之使用定时器获取未来三天的天气预报

    Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用Arduino IDE(集成开发环境)来

    2024年02月04日
    浏览(44)
  • STM-32:TIM定时中断—定时器定时中断/定时器外部时钟

    定时器可以对输入的时钟进行计数,并在达到设定值时触发中断 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等

    2024年02月09日
    浏览(58)
  • ESP32 Arduino框架入门(二)外部中断和TouchPad(电容触摸引脚)

    接ESP32 Arduino框架入门(一)介绍和工程创建(8条消息) ESP32 Arduino框架入门(一)介绍和工程创建_长谷深风灯盏的博客-CSDN博客_arduino的esp32软件系统架构        接下来继续介绍ESP32-Wroom-32E的外部引脚、中断、定时器等部分内容         ESP32的外部引脚功能有ADC,GPIO,

    2024年02月10日
    浏览(43)
  • 定时器定时中断&定时器外部时钟

    1、RCC开启时钟,此时定时器的基准时钟和整个外设的工作时钟都打开 2、选择时基单元的时钟源,对于定时中断选择内部时钟源 3、配置时基单元,包括预分频器,自动重装器,计数模式等 4、配置输出中断控制允许更新中断输出到NVIC 5、配置NVIC,在NVIC中打开定时器中断的通

    2024年02月15日
    浏览(51)
  • STM32单片机(六)TIM定时器 -> 第二节:TIM定时中断练习(定时器定时中断和定时器外部时钟)

    ❤️ 专栏简介:本专栏记录了从零学习单片机的过程,其中包括51单片机和STM32单片机两部分;建议先学习51单片机,其是STM32等高级单片机的基础;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 :适用于想要从零基础开始学习入门单片机,且有一定C语言基础的的童鞋

    2024年02月09日
    浏览(41)
  • STM32F103定时器引脚重定义功能问题处理

    1、概要 在一个项目中,使用STM32F103RCT6单片机,定时器引脚使用的PC6,PC6默认复用定时器为TIM8_CH1,但是由于其他原因,不能使用TIM8_CH1,因此需要使用其重定义功能TIM3_CH1,本文分享了本人在该项目中定时器TIM3引脚复用功能重定义的一些问题及解决过程,希望对大家能有帮助

    2024年02月10日
    浏览(46)
  • STM32——定时器——定时中断

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 本节先只讲解定时器的定时中断,内外中断源选择。 TIM(Timer)定时器 定时器可以 对输入的时钟进行计数 ,并在计数值达到设定值时触发中断。 16位计数器、预分频器、自动重装寄存器的时基单元 ,

    2024年04月10日
    浏览(50)
  • STM32 hal库编程定时器清除中断标志位,开启定时器中断会立即进入中断的问题

    1、如果不清除中断标志位,开启定时器中断的话,不能完成相应的计时,会立即进入中断。 2、如果在开启定时器中断前清除了中断标志位,就会等待计时完成再出发中断。 3、主要是针对单次计时使用,尤其是对第一次计时有要求的程序。

    2024年02月01日
    浏览(58)
  • stm32 定时器部分(定时中断)

    一.定时中断(概念部分) 定时中断主要包含两种中断一种是更新中断还有一种是输入捕获中断 更新中断 :更新中断通常用于定时器的基本定时功能。当定时器计数器溢出并重新从零开始计数时,会触发更新中断。你可以配置定时器的计数周期和预分频器来控制定时器的计时

    2024年03月15日
    浏览(60)
  • STM32定时器-定时器中断功能详解

    STM32的众多定时器中我们使用最多的是高级定时器和通用定时器,而高级定时器一般也是用作通用定时器的功能,下面我们就以通用定时器为例进行讲解,其功能和特点包括: 通用与基本定时器(2~7)位于低速的APB1总线上 高级定时器(1、8)位于高速的APB2总线上 自动装载计

    2024年02月08日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包