Arduino基础篇(三)-- 带你了解Arduino中断的秘密

Arduino基础篇(三)-- 带你了解Arduino中断的秘密

文章目录

1 中断的基本概念2 外部中断2.1 中断引脚与中断编号2.2 中断模式2.3 中断函数

3 定时器中断3.1 定时器的作用3.2 定时器的基本概念3.3 定时器的配置3.4 定时器中断的使用

参考

1 中断的基本概念

中断装置和中断处理中断处理程序统称为中断系统。中断(Interrupt)是计算机的一个重要概念,现代计算机普遍采用中断技术。

当计算机执行正常程序时,系统中会出现某些急需处理的异常情况和特殊请求,此时 CPU 会暂时中止现行程序,转去对随机发生的更为紧迫的事件进行处理,处理完毕后,CPU 自动返回原来的程序继续执行,此过程就叫做中断。实现中断功能的硬件和软件统称中断系统。一个完整的中断处理过程包括中断请求、中断响应、中断处理和中断返回。

中断请求 中断过程是从中断源向 CPU 发出中断请求而开始的,其中断请求信号应该至少保持到 CPU 做出响应为止。中断响应 CPU检测到中断请求后,在一定的条件和情况下进行响应。中断处理 CPU响应中断结束后,返回原先被中断的程序并继续执行。中断返回 中断返回是指把运行程序从中断服务程序转回到被中断的主程序中。

中断结构如下图所示:

我们从一个生活中的例子引入。你正在家中看书,突然电话铃响了,你放下书本,去接电话,和来电话的人交谈,然后放下电话,回来继续看你的书。这就是生活中的“中断”的现象,就是正常的工作过程被外部的事件打断了。

常见的中断有外部中断和时钟中断:

时钟中断,是指设定CPU内部定时器后,当到达指定时间,将产生中断请求。常用于定时。

外部中断,就是当CPU的外部中断管脚电平变动时,将产生中断请求。 常用于键盘输入、串口通信等。

如果没有中断的话,arduino 是一直运行 loop 内的代码,一遍一遍重复运行。当有中断产生时候,单片机会停止 loop 的代码,开始运行中断服务函数的代码,运行一遍中断服务函数后,继续回到 loop 内接着刚才运行的代码运行。

2 外部中断

外部中断是由外部设备发起请求的中断。要想使用外部中断,就需了解中断引脚的位置,根据外部设备选择中断模式,以及编写一个中断被触发后需执行的中断函数。

2.1 中断引脚与中断编号

在不同型号的 Arduino 控制器上,中断引脚的位置也不相同,只有中断信号发生在带有外部中断功能的引脚上,Arduino才能捕获到该中断信号并做出响应,下表列举了 Arduino 常见型号控制器的中断引脚所对应的外部中断编号。

Arduino 型号int0 int1 int2 int3 int4 int5

UNO

2

3

—

—

—

—

MEGA

2

3

21

20

19

18

Lernardo

3

2

0

1

—

—

DUE

所有引脚均可使用外部中断

注:表格中int0、int1等都为外部中断编号。

2.2 中断模式

为了设置中断模式,还需要了解设备触发外部中断的输入信号类型。中断模式也就是中断触发的方式,在大多数 Arduino 上支持下表中的四种中断触发方式。

模式名称说明LOW低电平出发CHANGE电平变化出发,即由高变低、由低变高RISING上升沿触发,即低电平变高电平FALLING下降沿触发,即高电平变低电平

在 Arduino Due 中,还可以使用高电平(HIGH)来触发中断,另外 Arduino Due 上的每一个 I/O 口都可以触发中断,其中断编号便是引脚编号。

2.3 中断函数

除了设置中断模式外,还需要编写一个响应中断的处理程序——中断函数,当中断被触发后,便可以让Arduino运行该中断函数。中断函数就是当中断被触发后要去执行的函数,该函数不能带有任何参数,且返回类型为空,如:

void counter()

{

count++;

}

当中断被触发后,Arduino 便会执行该函数中的语句。 这些准备工作完成后,还需要在 setup() 中使用 attachInterrupt() 函数对中断引脚进行初始化配置,以开启 Arduino 的外部中断功能,其用法如下:

(1)attachInterrupt( interrupt,function,mode) 功能:对中断引脚进行初始化配置。 参数: interrupt,中断编号,注意,这里的中断编号并不是引脚编号。 function,中断函数名,当中断被触发后即会运行此函数名称所代表的中断函数。 mode,中断模式。

attachInterrupt(0,counter,RISING);

如果使用的是 Arduino UNO 或者 MEGA 控制器,则该语句即会开启2号引脚(中断编号0)上的外部中断功能,并指定上升沿时触发该中断。当2号引脚上的电平由低变高后,该中断会被触发,而 Arduino 即会运行 counter()函数中的语句。 如果不需要使用外部中断了,则可以使用中断分离函数 detachInterrupt() 来关闭中断功能。

在使用 attachInterrupt 函数时要注意以下几点:

在中断函数中 delay 函数不能使用。使用 millis 函数始终返回进入中断前的值。读取串口数据的话,可能会丢失。中断函数中使用的变量需要定义为 volatile 型。 attachInterrupt 函数的函数原型可在文件 WInterrupts.c 中找到,如下所示:

(2)detachInterrupt( interrupt) 功能:禁用外部中断。 参数: interrupt,需要禁用的中断编号。

(3)interrupts()和nolnterrupts() interrupts 和 noInterrupts 函数在 Arduino 中负责打开和关闭总中断,函数无返回值,无参数,可以在文件 wiring.h 中查看函数原型,如下:

#define interrupts() sei()

#define noInterrupts() cli()

3 定时器中断

3.1 定时器的作用

定时器对于单片机来说就类似我们现实生活中的时钟,记录很多和时间相关的事件。在我们平时经常使用的 delay() millis() ,micros() ,delayMicroseconds() ,PWM 波生成的 analogWrite() 和 tone() 函数都是通过定时器实现的,不过这些都被 Arduino 的封装库隐藏起来了,为了让使用者更快更便捷地开发项目。 我们平常使用的 Arduino 单片机为 UNO,NANO和MEGA 2560。UNO 和 NANO 都使用的是 ATmega328 芯片,这款芯片有3个定时器,Timer0,Timer1,Timer2,其中Timer0和Timer2都是8位寄存器(256),Timer1是16位寄存器(65536),意味着更高的分辨率。mege2560 使用的是 ATmege2560 芯片,这款芯片有 6 个定时器,在328 的基础上,增加了 Timer3,Timer4,Timer5。这三个定时器都是16位的寄存器。

Arduino 型号 参数Timer0 Timer1 Timer2 Timer3 Timer4 Timer5

UNO

位数

8bit

16bit

8bit

—

—

—

对应引脚

5,6

9,10

3,11

—

—

—

封装函数

delay(),millis()和micors()等

servo库

tone()等

—

—

—

MEGA

位数

8bit

16bit

8bit

16bit

16bit

16bit

对应引脚

4,13

11,12

9,10

2,3,5

6,7,8

46,45,44

封装函数

delay(),millis()和micors()等

—

tone()等

—

—

servo库

注意:对于UNO开发板,引脚5和6的 PWM 功能输出时将产生高于预期的占空比,这是因为 millis() 和 delay() 函数共用一个内部定时器,使得内部计时器在处理PWM 时候分心,这种情况一般出现在低占空比时。MEGA 2560以此类比。

3.2 定时器的基本概念

1、寄存器 寄存器列表如下,x代表0,1,2,3,4,5这6种定时器。

寄存器作用TCCRx定时器/计数器控制寄存器;预分频器可以在这里配置TCNTx定时器/计数器寄存器OCRx输出比较寄存器ICRx输入捕捉寄存器(仅适用于16位定时器)TIMSKx定时器/计数器中断屏蔽寄存器;启用/禁用定时器中断TIFRx定时器/计数器中断标志寄存器;表示挂起的定时器中断

这里给个 Timer0 的寄存器,更多请参考技术文档。

2、预分频系数与比较匹配器

Arduino UNO 时钟以16MHz运行。计数器的一个刻度值表示 1 / 16,000,000秒(~63ns),跑完1s需要计数值16,000,000。

(1)Timer0 和 Timer2 是8位定时器,可以存储最大计数器值255。

(2)Timer1 是一个16位定时器,可以存储最大计数器值65535。

一旦计数器达到其最大值,它将回到零(这称为溢出)。因此,需要对时钟频率进行分频处理,即预分频器。通过预分频器控制定时计数器的增量速度。预分频器与定时器的计数速度如下:

定时器速度(HZ) = Arduino UNO时钟速度(16MHz) / 预分频器系数

因此,1预分频器将以16MHz递增计数器,8预分频器将在2MHz递增,64预分频器= 250kHz,依此类推。 定时器 Timer0 的预分频系数配置如表:

现在您可以用以下公式计算中断频率:

中断频率(Hz)=(Arduino时钟速度16MHz)/(预分频器*(比较匹配寄存器+ 1))

重新排列上面的等式,给出你想要的中断频率,你可以求解比较匹配寄存器值:

比较匹配寄存器= [16,000,000Hz /(预分频器*所需的中断频率)] - 1

当你使用定时器0和2时,这个数字必须小于256,对于timer1小于65536。

所以如果你想每秒一次中断(频率为1Hz):

比较匹配寄存器= [16,000,000 /(预分频器 * 1)] -1

预分频器为1024,你得到:

比较匹配寄存器= [16,000,000 /(1024 * 1)] -1 = 15,624

因为256 <15,624 <65,536,你必须使用timer1来实现这个中断。

3、定时器模式

定时器可以配置为不同的模式。

(1)PWM模式。 纸浆宽度调制模式。 OCxy输出用于生成PWM信号

(2)CTC模式。 比较匹配时清除计时器。 当定时器计数器到达比较匹配寄存器时,定时器将被清除。

定时器 Timer0 的模式选择配置如表:

3.3 定时器的配置

请注意不同型号板子对应配置不同,详细配置请看技术文档。

int toggle0,toggle1,toggle2;

void setup(){

cli();关闭全局中断

//设置定时器0为10kHz(100us)

TCCR0A = 0;//将整个TCCR0A寄存器设置为0

TCCR0B = 0;//将整个TCCR0B寄存器设置为0

TCNT0 = 0;//将计数器值初始化为0

//设置计数器为10kHZ,即100us

OCR0A = 24;//比较匹配寄存器= [16,000,000Hz /(预分频器*所需中断频率)] - 1

//比较匹配寄存器=24,中断间隔=100us即中断频率10khz

TCCR0A |= (1 << WGM01);//打开CTC模式

TCCR0B |= (1 << CS01) | (1 << CS00); //设置CS01位为1,CS00位为1(64倍预分频)

TIMSK0 |= (1 << OCIE0A);//启用定时器比较中断

//设置定时器1为1kHz

TCCR1A = 0;//将整个TCCR1A寄存器设置为0

TCCR1B = 0;//将整个TCCR1B寄存器设置为0

TCNT1 = 0;//将计数器值初始化为0

//设置计数器为1kHZ,即1ms

OCR1A = 1999;// = (16*10^6)/(1000*8) - 1 (must be <65536)

TCCR1B |= (1 << WGM12);//打开CTC模式

TCCR1B |= (1 << CS11);//设置CS11位为1(8倍预分频)

TIMSK1 |= (1 << OCIE1A);

//设置定时器2为8kHz

TCCR2A = 0;// set entire TCCR2A register to 0

TCCR2B = 0;// same for TCCR2B

TCNT2 = 0;//initialize counter value to 0

// set compare match register for 8khz increments

OCR2A = 249;// = (16*10^6) / (8000*8) - 1 (must be <256)

// turn on CTC mode

TCCR2A |= (1 << WGM21);//打开CTC模式

// Set CS21 bit for 8 prescaler

TCCR2B |= (1 << CS21);

// enable timer compare interrupt

TIMSK2 |= (1 << OCIE2A);

sei();//打开全局中断

}

//中断0服务函数

ISR(TIMER0_COMPA_vect){// timer0中断2Hz切换引脚13(LED)

//产生频率为10kHz / 2 = 5kHz的脉冲波

if(toggle0){

digitalWrite(8,HIGH);

toggle0 = 0;

}

else{

digitalWrite(8,LOW);

toggle0 = 1;

}

}

ISR(TIMER1_COMPA_vect){// timer1中断2Hz切换引脚13(LED)

//产生频率为2Hz / 2 = 1Hz的脉冲波

if(toggle1>=500)

digitalWrite(13,HIGH);

if(toggle1<=500)

digitalWrite(13,LOW);

toggle1 += 1;

if(toggle1 >= 1000)

toggle1 = 0;

}

ISR(TIMER2_COMPA_vect){// timer2中断8kHz切换引脚9

//产生频率为8kHz / 2 = 4kHz的脉冲波

if(toggle2){

digitalWrite(9,HIGH);

toggle2 = 0;

}

else{

digitalWrite(9,LOW);

toggle2 = 1;

}

}

//loop function

void loop(){

}

3.4 定时器中断的使用

使用定时器中断前,必须先安装对应的 Timer 库,然后导入到 Arduino 的库文件里,并在程序中引用头文件 Timer.h 。 实例:

// 秒切换一次引脚13的电平

// 包含定时器库的头文件

#include

// 中断服务程序

void flash() {

static boolean output = HIGH;

digitalWrite(13, output);

output = !output;

}

void setup() {

pinMode(13, OUTPUT);

FlexiTimer2::set(500, flash); // 中断设置函数,每 500ms 进入一次中断

FlexiTimer2::start(); // 开始计时

}

void loop() {

}

其他 Timer 的使用基本一样,只要找到合适的库函数就能够使用,注意使用的定时器不要和你已使用的封装函数冲突,比如对于 UNO 来说,你在使用 Servo.h 的时候,就不能再使用 timer1 了,此时IDE会给你编译报错。

参考

官方教程:https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/定时器和中断:https://www.robotshop.com/community/forum/t/arduino-101-timers-and-interrupts/13072Arduino定时器和中断的使用:https://www.pipipi.net/1030.htmlTimer1&Timer3库文件:https://playground.arduino.cc/Code/Timer1/Timer2库文件:https://playground.arduino.cc/Main/FlexiTimer2/

相关文章

365结束投注 十大好玩的手机游戏(2018年)

十大好玩的手机游戏(2018年)

🗓️ 06-27 👁️ 3278
365体育投注网址亚洲下载 女性为什么对服装特别痴迷

女性为什么对服装特别痴迷

🗓️ 07-05 👁️ 9137
365结束投注 2025年桂林市流量卡挑选避坑指南

2025年桂林市流量卡挑选避坑指南

🗓️ 07-08 👁️ 9148
彩票365苹果版怎么下载不了 2018世界杯 1/8决赛 法国(4

2018世界杯 1/8决赛 法国(4

🗓️ 07-01 👁️ 1212
365结束投注 现在买ps4pro会不会淘汰了

现在买ps4pro会不会淘汰了

🗓️ 07-01 👁️ 4551
365结束投注 滤镜总排行榜

滤镜总排行榜

🗓️ 06-29 👁️ 2123