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

news2024/11/16 2:21:10

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

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

介绍

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

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

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

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

在这里插入图片描述

中断的工作原理

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

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

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

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

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

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

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

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

Arduino Uno 中断

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

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

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

中断适合的位置

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

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

在这里插入图片描述

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

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

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

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

在这里插入图片描述

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

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

ISR 仅在发生中断时运行。

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

中断服务例程

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

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

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

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

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

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

为什么要使用中断?

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

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

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

在这里插入图片描述

我为我的红色 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上,只有两个引脚支持硬件中断:

在这里插入图片描述

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

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

使用硬件中断

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

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

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

附加中断功能

在这里插入图片描述

要将函数“粘合”到特定中断,您将使用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 Uno上,有20个引脚可用于引脚更改引发中断,它们分为三个端口。

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

使用引脚更改引发中断

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

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

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

选择端口

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

在这里插入图片描述

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

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

启用/禁用端口上的引脚

启用端口后,您需要启用要用于引脚更改引发中断的引脚。您可以通过修改所选端口的引脚更改引发掩码来执行此操作。
在这里插入图片描述

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

在这里插入图片描述

中断服务例程

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

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

img

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

试验引脚更改引发中断

为了运行接下来的几个示例,我们需要在Arduino中添加另一个按钮和LED。最终的连接如下所示:
在这里插入图片描述

所以我们现在有:

  • 引脚 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 = 13;
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的引脚。

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

在设置中,我们使用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。

在这里插入图片描述

这些计时器并不相同,因为 Timer1 是 16 位计时器,而其他两个计时器只是 8 位计时器。位数决定了计时器可以计数的最大数字,8 位计时器为 256,16 位计时器为 65,536。

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

对时钟频率进行分频

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

在这里插入图片描述

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

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

在这里插入图片描述

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

在这里插入图片描述

Timer0 是一个 8 位定时器,使用位 CS01、CS02 和 CS03。
在这里插入图片描述

Timer1 是 16 位定时器,使用位 CS10、CS11 和 CS12。

在这里插入图片描述

Timer2 是另一个 8 位定时器,使用 CS20、CS21 和 CS22 位。

使用计时器中断

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

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

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

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

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

在这里插入图片描述

*时钟/(预分频器 x (比较匹配寄存器 + 1)) = 频率*

如果您知道所需的频率并想要确定“比较匹配寄存器”值,则可以按如下方式重写公式:

*[时钟/(预分频器 x 频率)] -1 = 比较匹配寄存器*

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

计时器中断服务例程

与引脚更改引发中断一样,已经为您确定了定时器中断的中断服务例程的名称。
在这里插入图片描述

每个计时器都有两个与之关联的 ISR,一个用于比较匹配模式,另一个用于溢出模式。

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

简单计时器示例

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

我们将在引脚 13 上使用 Arduino Uno 和 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文件中。

附加中断 – 与硬件中断一起使用的附加中断函数的参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/71108.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Release notes for VPP 22.10

本次发布新增了212个提交&#xff0c;包括118个修复。关于本次发布的更多信息&#xff0c;请访问&#xff1a;https://gerrit.fd.io/r/gitweb?pvpp.git;ablob;fdocs/aboutvpp/releasenotes/v22.10.rst;h5dfbff5d48e957e83d7e3c2f978820c95c41a2e4;hb07e0c05e698cf5ffd1e2d2de0…

「图文教程」iOS 16测试版如何升级iOS 16正式版?

苹果iOS 16正式版已经更新到iOS 16.1.2了&#xff0c;如果你的iPhone之前为了尝鲜已经下载安装iOS 16测试版&#xff0c;该如何升级iOS 16正式版呢&#xff1f;一起来了解下吧&#xff01; 方法一、移除iOS 16 Beta描述文件 1、进入【设置】-【通用】-【VPN与设备管理】&…

python+django汽车站售票票务管理系统

1.用户需要进行注册才可以登录本系统。 2.用户登录系统后可以在通知公告中获取最新的通知或者搜索需要的通知&#xff1b;可以在车票信息中查询到所需的车票信息并且可以通过站点、票价等选项进行筛选&#xff0c;选定车票后可以直接购票。用户还可以在订单管理中进行退票操作&…

引爆全球的ChatGPT,Java、面试、刷题、双色球它都会?

大家好&#xff0c;我是二哥呀。 这两天&#xff0c;ChatGPT 引爆全球&#xff0c;不管是搞技术的&#xff0c;还是没搞技术的&#xff0c;都在玩&#xff0c;玩的不亦乐乎&#xff0c;仿佛找到了内心真正的伴侣&#xff08;&#x1f602;&#xff09;。 一开始我以为这玩意不…

图像数据的特征工程

一提到特征工程&#xff0c;我们立即想到是表格数据。但是我们也可以得到图像数据的特征&#xff0c;提取图像中最重要的方面。这样做可以更容易地找到数据和目标变量之间的映射。 这样可以使用更少的数据和训练更小的模型。更小的模型可以减少预测所需的时间。这在部署到边缘设…

【 第八章 SQL执行效率,慢日志查询,profile,explain,最左前缀法则,范围查询】

第八章 SQL执行效率&#xff0c;慢日志查询&#xff0c;profile&#xff0c;explain&#xff0c;最左前缀法则&#xff0c;范围查询 1.SQL执行效率&#xff1a; MySQL 客户端连接成功后&#xff0c;通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指…

微服务框架 SpringCloud微服务架构 26 数据聚合 26.1 聚合的分类

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构26 数据聚合26.1 聚合的分类26.1.1 聚合的分类26.1.2 总结26 数据聚合 26…

基础入门 - SpringBoot 自动配置

目录 3、自动配置原理入门 3.1、引导加载自动配置类 1、SpringBootConfiguration 2、ComponentScan 3、EnableAutoConfiguration 1、AutoConfigurationPackage 2、Import(AutoConfigurationImportSelector.class) 3.2、按需开启自动配置项 3.3、修改默认配置 1、自动配…

写作人的福音——obsidian非官方插件之senGener

背景 之前的是转发作者的项目文档&#xff0c;这次应作者要求&#xff0c;写了个试用报告。 刚用这个插件的时候&#xff0c;还磕磕碰碰&#xff0c;总有点小问题&#xff0c;按快捷键不反应&#xff0c;服务器崩溃什么的&#xff0c;并不指望真的用起来&#xff0c;权当个玩具…

【Mysql】当Naviact创建数据库时失败出现1044 -Access denied for user ‘root‘@‘%‘ to database

业务背景&#xff1a;当我用Navicat连接我服务器上的MySQL8&#xff0c;然后点击创建数据库时&#xff0c;创建失败&#xff0c;并报异常 1044 -Access denied for user root% to database解决方案&#xff1a;本问题是因为&#xff0c;账号无权限导致的&#xff0c;因此只需要…

C语言实现一个闪烁的圣诞树(控制台)

下下下周就是圣诞节啦&#xff0c;C语言的圣诞树必须安排起&#xff01;&#xff01;&#xff01; 效果展示&#xff1a; 原理说明&#xff1a; 函数 layer 画出树的层次&#xff0c;根据坐标来输出位置&#xff1b; void layer(int x, int y, int num, int col) 函数 tri…

毕业设计-基于大数据的电影推荐系统-python

目录 前言 课题背景和意义 实现技术思路 实现效果图样例 前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着备考或实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越难,有不少课题是研究生级别难度的,对本科…

(七) Docker安装常规软件

Docker安装常规软件一、安装tomcat1、docker hub上面查找tomcat镜像2、从docker hub上拉取tomcat镜像到本地3、docker images查看是否有拉取到的tomcat4、使用tomcat镜像创建容器实例5、访问Tomcat首页6、拉取这个版本不需要去修改Webapps目录&#xff08;免修改版&#xff09;…

在Python中自然语言处理生成词云WordCloud

了解如何在Python中使用WordCloud对自然语言处理执行探索性数据分析。 最近我们被客户要求撰写关于自然语言处理的研究报告&#xff0c;包括一些图形和统计输出。 什么是WordCloud&#xff1f; 很多时候&#xff0c;您可能会看到一片云&#xff0c;上面堆满了许多大小不同的单…

花了1块钱体验一把最近很火的ChatGPT

前言 最近 OpenAI 发布了 ChatGPT&#xff0c;一经发布就在科技圈火得不行。 ChatGPT是什么呢&#xff1f; 简单得说&#xff0c;ChatGPT&#xff0c;是一种基于对话的 AI 聊天工具。我们来看看ChatGPT自己得回答。 下面是在ChatGPT注册成功后&#xff0c;正常使用后的截图 …

代码随想录训练营day57, 回文子串, 回文子序列

回文子串 计算这个字符串中有多少个回文子串 动态规划 数组定义: 表示区间[i,j]的资产是否为回文子串, 如果是dp[i][j]则为true, 否为false递推: 整理来说就是两种, s[i]和s[j]相等或者不相等 相等有三种情况 下标i与j相同, 同一个字符例如a, b下标差一位, 那就是aa, bb下标…

Unity3D2020+导出Android工程中使用并交互

, 目录 1&#xff0c;版本信息 2&#xff0c;前期准备 Unity方面&#xff1a; Android方面&#xff1a; 3&#xff0c;Android与Unity3D交互 1&#xff0c;版本信息 unity2020 android studio 2021 *不要用android studio 2020系列&#xff0c;存在不能导入Library的b…

从Eureka到Polaris,好未来AI中台注册中心是如何实现0代码迁移的

导语&#xff1a;2021年&#xff0c;好未来的AI 中台业务规模激增&#xff0c;日调用量超6亿&#xff0c;总调用量上千亿。业务的快速发展给中台的现有技术架构带来一定的冲击。好未来AI中台是微服务架构&#xff0c;完成一个业务请求涉及多个服务之间相互调用&#xff0c;可以…

DVWA靶场下的xss漏洞练习及分析

目录 JavaScript操作cookie 反射型xss漏洞 存储型xss漏洞 JavaScript操作cookie 1.alert(document.cookie)&#xff1b; 2.document.cookie"coleak"; 3.document.cookie"modify"; 反射型xss漏洞 low级别 <script>alert(11)</script> med级别…

知识点10--Docker的DockerFile

Docker有两种镜像生成的方式&#xff0c;供我们在打包自己的环境时选择&#xff0c;前面的知识点都提到过&#xff0c;分别是commit和DockerFile build&#xff0c;两者的区别就在于commit使用自己已有的容器生成&#xff0c;而DockerFile是直接通过操作基础镜像来生成&#xf…