Skip to content

Latest commit

 

History

History
535 lines (365 loc) · 22.4 KB

File metadata and controls

535 lines (365 loc) · 22.4 KB

四、处理中断

嵌入式应用的主要任务之一是与外部硬件外设进行通信。 使用输出端口向外设发送数据很容易理解。 然而,当涉及到阅读时,事情就变得更加复杂了。

嵌入式开发人员必须知道何时可以读取数据。 由于外部设备位于处理器外部,因此随时可能发生这种情况。

在本章中,我们将学习什么是中断以及如何处理它们。 在使用 8 位微控制器 8051 作为目标平台时,我们将了解以下主题:

  • 如何实现基本的中断处理
  • 如何使用定时器的中断在M****微控制器单元(MCU)的输出引脚上产生信号
  • 如何使用中断对 MCU 外部引脚上的事件进行计数
  • 如何使用中断在串行通道上通信

我们将通过完成以下食谱来了解这些主题:

  • 实现中断服务例程
  • 使用 8 位自动重载模式生成 5 kHz 方形信号
  • 使用定时器 1 作为事件计数器对 1 Hz 脉冲进行计数
  • 串行收发数据

理解如何处理中断的核心概念将帮助您实现响应迅速且高能效的嵌入式应用。

不过,在此之前,我们将了解一些概念的背景知识。

数据轮询数据轮询

等待来自外部源的数据的第一种方法称为轮询。 应用定期查询外部设备的输入端口,以检查其是否有新数据。 它很容易实现,但也有很大的缺点。

首先,它浪费处理器资源。 大多数民意调查电话报告说数据还不可用,我们需要继续等待。 由于这些调用不会导致某些数据处理,因此是对计算资源的浪费。 此外,轮询间隔应该足够短,以便快速响应外部事件。 开发人员应该在有效利用处理器能力和反应时间之间寻找折衷方案。

其次,它使程序的逻辑错综复杂。 如果程序应该轮询事件,例如,每隔 5 毫秒轮询一次,那么它的任何子例程都不应该花费超过 5 毫秒的时间。 因此,开发人员人为地将代码拆分成更小的块,并组织它们之间的复杂切换,以允许轮询。

中断服务例程

中断是轮询的替代方案。 一旦外部设备有新数据,它就会在处理器中触发一个称为中断的事件。 顾名思义,它会中断执行指令的正常工作流。 处理器保存其当前状态并开始执行来自不同地址的指令,直到它遇到来自中断指令的返回。 然后,它读取保存的状态,以便从指令流被中断的那一刻开始继续执行该指令流。 此替代指令序列称为中断服务例程(ISR)。

每个处理器都定义了自己的一组指令和约定来处理中断;但是,它们在处理中断时都使用相同的通用方法:

  • 中断由数字标识,从 0 开始。 这些数字映射到物理上对应于特定处理器引脚的硬件中断请求线(IRQ)。

  • 当 IRQ 线被激活时,处理器使用其编号作为中断向量阵列中的偏移量,以定位中断服务例程的地址。 中断向量阵列存储在存储器中的固定地址上。

  • 开发人员可以通过更新中断向量阵列中的条目来定义或重新定义 ISR。

  • 可以对处理器进行编程,以使能或禁用中断,无论是针对特定 IRQ 线路还是一次所有中断。 当中断被禁用时,处理器不会调用相应的 ISR,尽管 IRQ 线路的状态可以读取。

  • 根据物理引脚上的信号,可以对 IRQ 线进行编程以触发中断。 这可以处于信号的低电平、信号的高电平或边沿(这是从低到高或从高到低的转变)。

ISR 的一般注意事项

由于中断处理是在硬件级别执行的,因此这种方法不会将处理器资源浪费在轮询上,并提供非常短的响应时间。 但是,开发人员应该了解它的细节,以避免将来出现严重或难以检测的问题。

首先,同时处理多个中断,或者在仍然处理前一个中断的同时响应相同的中断,是很难实现的。 这就是执行 ISR 时禁用中断的原因。 这可以防止 ISR 被另一个中断中断,但这也意味着挂起中断的反应时间可能更长。 更糟糕的是,如果不快速重新启用中断,这可能会导致数据或事件丢失。

为了避免这种情况,所有 ISR 都写得简短。 它们只做极少量的工作来读取或确认来自设备的数据。 复杂的数据分析和处理在 ISR 之外执行。

8051 单片机中断

8051 微控制器支持六个中断源-复位、两个硬件中断、两个定时器中断和一个串行通信中断:

| 中断号 | 说明 | 以字节为单位的偏移量 | | | 重新设置 / 清零 / 复位 | 0 | | 0 | 外部中断 int0 | 3. | | 1. | 定时器 0(TF0) | 11. | | 2 个 | 外部中断 INT1 | 19 个 | | 3. | 计时器 1(TF1) | 27 | | 4. | 连续的 / 连续作案的 / 连载的 / 分期偿还的 | 36 |

中断向量阵列位于地址 0;除 RESET 外的每个条目的大小为 8 字节。 虽然最小的 ISR 可以容纳 8 个字节,但通常情况下,条目包含将执行重定向到位于其他位置的实际 ISR 的代码。

重置条目是特殊的。 它由复位信号激活,并立即跳转到主程序所在的地址。

8051 定义了一个称为中断启用(EA)的特殊寄存器,用于启用和禁用中断。 其 8 位按以下方式分配:

| | 名称 | 含义 | | 0 | EX0 | 外部中断%0 | | 1. | ET0 | 定时器 0 中断 | | 2 个 | EX1 | 外部中断 1 | | 3. | ET1 | 定时器 1 中断 | | 4. | 萨尔瓦多 / 回声测深 | 串口中断 | | 5. | -你知道吗? | 未使用 | | 6. | -你知道吗? | 未使用 | | 7. | Electronics Arts 电子艺界游戏公司;Electronics Arts,电子艺界游戏公司 | 全局中断控制 |

将这些位设置为 1 可使能相应的中断,设置为 0 则禁用这些中断。 EA 位使能或禁用所有中断。

实现中断服务例程

在本菜谱中,我们将学习如何为 8051 单片机定义中断服务例程。

怎么做……

请按照以下步骤完成本食谱:

  1. 切换到我们在第 2 章设置环境中设置的构建系统。
  2. 确保安装了 8051 仿真器:
# apt install -y mcu8051ide
  1. 启动mcu8051ide并创建一个名为Test的新项目。

  2. 创建一个名为test.c的新文件,并将以下代码片段放入其中。 这会为每个定时器中断增加内部counter

#include<mcs51reg.h> 

volatile int Counter = 0;
void timer0_ISR (void) __interrupt(1) /*interrupt no. 1 for Timer0 */
{ 

  Counter++ ;
} 

void main(void) 
{ 
  TMOD = 0x03; 
  TH0 = 0x0; 
  TL0 = 0x0; 
  ET0 = 1; 
  TR0 = 1;
  EA = 1;
  while (1); /* do nothing */ 
} 
  1. 选择工具|编译以构建代码。 消息窗口将显示以下输出:
Starting compiler ...

cd "/home/dev"
sdcc -mmcs51 --iram-size 128 --xram-size 0 --code-size 4096 --nooverlay --noinduction --verbose --debug -V --std-sdcc89 --model-small "test.c"
sdcc: Calling preprocessor...
+ /usr/bin/sdcpp -nostdinc -Wall -obj-ext=.rel -D__SDCC_NOOVERLAY -DSDCC_NOOVERLAY -D__SDCC_MODEL_SMALL -DSDCC_MODEL_SMALL -D__SDCC_FLOAT_REENT -DSDCC_FLOAT_REENT -D__SDCC=3_4_0 -DSDCC=340 -D__SDCC_REVISION=8981 -DSDCC_REVISION=8981 -D__SDCC_mcs51 -DSDCC_mcs51 -D__mcs51 -D__STDC_NO_COMPLEX__ -D__STDC_NO_THREADS__ -D__STDC_NO_ATOMICS__ -D__STDC_NO_VLA__ -isystem /usr/bin/../share/sdcc/include/mcs51 -isystem /usr/share/sdcc/include/mcs51 -isystem /usr/bin/../share/sdcc/include -isystem /usr/share/sdcc/include test.c
sdcc: Generating code...
sdcc: Calling assembler...
+ /usr/bin/sdas8051 -plosgffwy test.rel test.asm
sdcc: Calling linker...
sdcc: Calling linker...
+ /usr/bin/sdld -nf test.lk

Compilation successful
  1. 选择模拟器|启动/关闭菜单项以激活模拟器。
  2. 选择模拟器|动画以在慢速模式下运行程序。
  3. 切换到 C Variables 面板并向下滚动,直到显示 Counter Variable。
  4. 观察它是如何随着时间的推移而增加的:

如您所见,Counter变量的值字段现在是 74。

它是如何运作的..。

对于我们的示例应用,我们将使用 8051 微控制器的仿真器。 其中有几个是可用的;但是,我们将使用 MCU8051IDE,因为它可以在 Ubuntu 存储库中随时获得。

我们将其作为常规的 Ubuntu 包安装,如下所示:

# apt install -y mcu8051ide

这是一个 GUI IDE,需要 X Window 系统才能运行。 如果您使用 linux 或 windows 作为您的工作环境,请考虑直接从https://sourceforge.net/projects/mcu8051ide/files/安装和运行它。

我们创建的简单程序定义了一个名为Counter的全局变量,如下所示*:*

volatile int Counter = 0;

这被定义为volatile,表示它可以在外部更改,编译器不应该试图优化代码来消除它。

接下来,我们定义一个名为timer0_ISR*:*的简单函数

void timer0_ISR (void) __interrupt(1)

它不接受任何参数,也不返回任何值。 它唯一做的事情就是递增Counter变量。 它是用一个称为__interrupt(1)的重要属性声明的,以让编译器知道它是一个中断处理程序,并且它服务于中断编号 1。编译器生成自动更新中断向量数组的相应条目的代码。

定义 ISR 本身后,我们配置计时器的参数:

TMOD = 0x03; 
TH0 = 0x0; 
TL0 = 0x0;

然后,我们打开计时器 0,如下所示:

TR0 = 1;

以下命令启用定时器 0 的中断:

ET0 = 1; 

以下代码启用所有中断:

EA = 1;

此时,我们的 ISR 被定时器的中断周期性地激活。 由于所有工作都是在 ISR 内完成的,因此我们会运行一个无休止的循环,什么也不做:

while (1); // do nothing 

当我们在模拟器中运行前面的代码时,我们将看到counter变量的实际值随着时间的推移而变化,这表明我们的 ISR 正在被计时器激活。

使用 8 位自动重载模式生成 5 kHz 方形信号

在前面的配方中,我们了解了如何创建仅执行计数器递增的简单 ISR。 让我们让中断例程做一些更有用的事情。 在本食谱中,我们将学习如何对 8051 微控制器进行编程,使其产生具有给定频率的信号。

8051 微控制器有两个定时器-定时器 0 和定时器 1-均使用两个特殊功能寄存器进行配置:定时器模式(TMOD)和定时器控制(TCON)。 定时器的值存储在定时器 0 的 TH0 和 TL0 定时器寄存器以及定时器 1 的 TH1 和 TL1 定时器寄存器中。

TMOD 和 TCON 位具有特殊含义。 TMOD 寄存器的位定义如下:

| | 计时器 | 名称 | 目的 | | 0 | 0 | M0 | 定时器模式选择器-低位。 | | 1. | 0 | M1 型 | 定时器模式选择器-高位。 | | 2 个 | 0 | 计算机化 X 线体层照相术 | 计数器(1)或定时器(0)模式。 | | 3. | 0 | 门 / 大门 / 出入口 | 启用定时器 1,但仅当 INT0 的外部中断为高电平时。 | | 4. | 1. | M0 | 定时器模式选择器-低位。 | | 5. | 1. | M1 型 | 定时器模式选择器-高位。 | | 6. | 1. | 计算机化 X 线体层照相术 | 计数器(1)或定时器(0)模式。 | | 7. | 1. | 门 / 大门 / 出入口 | 启用定时器 1,但仅当 INT1 的外部中断为高电平时。 |

低 4 位分配给定时器 0,高 4 位分配给定时器 1。

M0 和 M1 位允许我们以四种模式之一配置定时器:

| 模式 | M0 | M1 | 说明 | | 0 | 0 | 0 | 13 位模式。 TL0 或 TL1 寄存器包含低 5 位,TH0 或 TH1 寄存器包含相应计时器值的高 8 位。 | | 1. | 0 | 1. | 16 位模式。 TL0 或 TL1 寄存器包含低 8 位,TH0 或 TH1 寄存器包含相应计时器值的高 8 位。 | | 2 个 | 1. | 0 | 具有自动重新加载功能的 8 位模式。 TL0 或 TL1 包含相应的计时器值,而 TH0 或 TL1 包含重载值。 | | 3. | 1. | 1. | 定时器 0 的特殊 8 位模式 |

定时器****控件(TCON)注册控件的定时器中断。 其位定义如下:

| | 名称 | 目的 | | 0 | IT0 | 外部中断 0 控制位。 | | 1. | IE0 | 外部中断 0 边缘标志。 当在 INT0 处接收到高至低沿信号时,设置为 1。 | | 2 个 | IT1 | 外部中断 1 控制位。 | | 3. | IE1 | 外部中断 1 边缘标志。 当 INT1 接收到高至低沿信号时,置 1。 | | 4. | TR0 | 运行计时器 0 的控制。 设置为 1 可启动,设置为 0 可停止计时器。 | | 5. | TF0 | 计时器 0 溢出。 当计时器达到其最大值时设置为 1。 | | 6. | TR1 | 运行计时器 1 的控制。设置为 1 可启动计时器,设置为 0 可停止计时器。 | | 7. | TF1 | 计时器 1 溢出。 当计时器达到其最大值时设置为 1。 |

我们将使用 8051 定时器的特定模式,称为自动重新加载。 在此模式下,TL0(定时器 1 的 TL1)寄存器包含定时器值,而 TH0(定时器 1 的 TH1)包含重载值。 一旦 TL0 达到最大值 255,它就会产生溢出中断,并自动复位到重载值。

怎么做……

请按照以下步骤完成本食谱:

  1. 启动mce8051ide并创建一个名为Test的新项目。
  2. 创建一个名为generator.c的新文件,并将以下代码片段放入其中。 这将在 MCU 的P0_0引脚上产生 5 kHz 信号:
#include<8051.h> 

void timer0_ISR (void) __interrupt(1) 
{ 
  P0_0 = !P0_0;
} 

void main(void) 
{ 
  TMOD = 0x02;
  TH0 = 0xa3; 
  TL0 = 0x0; 
  TR0 = 1;
  EA = 1; 
  while (1); // do nothing 
}
  1. 选择工具|编译以构建代码。
  2. 选择模拟器|启动/关闭菜单项以激活模拟器。
  3. 选择模拟器|动画以在慢速模式下运行程序。

它是如何运作的..。

以下代码定义计时器 0 的 ISR:

void timer0_ISR (void) __interrupt(1) 

在每次定时器中断时,我们翻转 P0 的输入输出寄存器的 0 位。 这将在 P0 输出引脚上有效地产生方波信号。

现在,我们需要弄清楚如何对定时器进行编程,以生成具有给定频率的中断。 要生成 5 kHz 信号,我们需要用 10 kHz 频率翻转比特,因为每个波都由一个高相位和一个低相位组成。

8051 单片机使用外部振荡器作为时钟源。 定时器单元将外部频率除以 12。对于通常用作 8051 的时间源的 11.0592 兆赫振荡器,定时器每隔 1/11059200*12=1.085 毫秒激活一次。

我们的定时器 ISR 应该以 10 千赫的频率激活,或者每 100 毫秒激活一次,或者在每 100/1.085=92 个定时器滴答声之后激活。

我们将定时器 0 编程为在模式 2 下运行,如下所示:

TMOD = 0x02;

在此模式下,我们将定时器的复位值存储在 TH0 寄存器中。 ISR 由定时器溢出激活,定时器溢出在定时器计数器达到最大值后发生。 模式 2 是 8 位模式,这意味着最大值为 255。 要每 92 个刻度激活 ISR,自动重新加载值应为 255-92=163,或十六进制表示的0xa3

我们将自动重载值与初始定时器值一起存储在定时器寄存器中:

TH0 = 0xa3; 
TL0 = 0x0;

定时器 0 被激活,如下所示:

TR0 = 1;

然后,我们启用计时器中断:

TR0 = 1;

最后,所有中断都被激活:

EA = 1; 

从现在开始,我们的 ISR 每 100 微秒调用一次,如以下代码所示:

P0_0 = !P0_0;

这会翻转P0寄存器的0位,从而在相应的输出引脚上产生 5 kHz 方形信号。

使用定时器 1 作为事件计数器对 1 Hz 脉冲进行计数

8051 定时器具有双重功能。 当它们被时钟振荡器激活时,它们充当计时器。 但是,它们也可以由外部引脚上的信号脉冲激活,即充当计数器的 P3.4(定时器 0)和 P3.5(定时器 1)。

在本食谱中,我们将学习如何对定时器 1 进行编程,使其对 8051 处理器的 P3.5 引脚的激活进行计数。

怎么做……

请按照以下步骤完成本食谱:

  1. 打开 mcu8051ide。
  2. 创建一个名为Counters的新项目。
  3. 创建一个名为generator.c的新文件,并将以下代码片段放入其中。 每次触发定时器中断时,这会递增计数器变量:
#include<8051.h> 

volatile int counter = 0;
void timer1_ISR (void) __interrupt(3) 
{ 
  counter++ ;
} 

void main(void) 
{ 
  TMOD = 0x60;
  TH1 = 254; 
  TL1 = 254; 
  TR1 = 1;
  ET1 = 1;
  EA = 1; 
  while (1); // do nothing 
}
  1. 选择工具|编译以构建代码。
  2. 打开 Virtual HW(虚拟硬件)菜单,然后选择 Simple(简单)键...。 进入。 将打开一个新窗口。
  3. 在 Simple Keypad 窗口中,将端口 3 和位 5 分配给第一个密钥。 然后,单击打开或关闭按钮将其激活:

  1. 选择模拟器|Start/Shutdown(模拟器|启动/关闭)菜单项以激活模拟器。
  2. 选择模拟器|动画以动画模式运行程序,该模式在调试器窗口中显示对特殊寄存器的所有更改。
  3. 切换到 Simple Keypad 窗口并单击第一个键。

它是如何运作的..。

在这个配方中,我们利用 8051 定时器的功能,使它们充当计数器。 我们定义中断服务例程的方式与定义普通定时器的方式完全相同。 由于我们使用定时器 1 作为计数器,因此使用中断行号3,如下所示:

void timer1_ISR (void) __interrupt(3) 

中断例程的主体很简单。 我们只递增counter变量。

现在,让我们确保 ISR 是由外部源激活的,而不是由时钟振荡器激活的。 为此,我们通过将TMOD特殊功能寄存器的 C/T 位设置为 1 来配置定时器 1:

TMOD = 0x60;

同一行将定时器 1 配置为在自动重新加载的模式 2-8 位模式下运行。 由于我们的目标是在每次外部引脚激活时调用中断例程,因此我们将自动重新加载和初始值设置为最大值254

TH1 = 254; 
TL1 = 254; 

接下来,我们启用计时器 1:

 TR1 = 1;

然后,定时器 1 的所有中断都被激活,如下所示:

 ET1 = 1;
 EA = 1;

之后,我们可以进入不做任何事情的无限循环,因为所有工作都是在中断服务例程中完成的:

 while (1); // do nothing 

此时,我们可以在仿真器中运行代码。 但是,我们需要配置外部事件源。 为此,我们利用了 MCU8051IDE 支持的虚拟外部硬件组件之一-虚拟小键盘。

我们配置它的一个按键来激活 8051 的 P3.5 引脚。 当计时器 1 在计数模式下使用时,该引脚用作定时器 1 的信号源。

现在,我们运行代码。 按下虚拟键可激活计数器。 一旦计时器值溢出,我们的 ISR 就会被触发,使counter变量递增。

还有更多的..。

在本食谱中,我们使用定时器 1 作为计数器。 同样的情况也可以应用于计数器 0。 在这种情况下,引脚 P3.4 应用作外部源。

串行收发数据

8051 微控制器带有内置的通用异步接收器发射器(UART)端口,用于串行数据交换。

串行端口由称为串行控制(SCON)的特殊功能寄存器(SFR)控制。 其位定义如下:

| | 名称 | 目的 | | 0 | RI(接收****中断的缩写) | 完全接收到一个字节时由 UART 置位 | | 1. | TI(发送****中断的缩写) | 当一个字节传输完成时由 UART 置位 | | 2 个 | RB8(接收位8的缩写) | 以 9 位模式存储接收数据的第九位。 | | 3. | TB8(发送位 8的缩写) | 存储要以 9 位模式传输的第九位数据(见下文) | | 4. | REN(接收器启用的缩写) | 启用(1)或禁用(0)接收操作 | | 5. | SM2(启用多处理器) | 启用(1)或禁用(0)9 位模式的多处理器通信 | | 6. | SM1(串行模式,高位) | 定义串行通信模式 | | 7. | SM0(串行模式,低位) | 定义串行通信模式 |

8051 UART 支持四种 m 种串行通信模式,均由 SM1 和 SM0 位定义:

| 模式 | [[T0]SM0[] | [#T0#SM1#T1] | 说明 | | 0 | 0 | 0 | 移位寄存器,固定波特率 | | 1. | 0 | 1. | 8 位 UART,使用定时器 1 设置波特率 | | 2 个 | 1. | 0 | 9 位 UART,固定波特率 | | 3. | 1. | 1. | 9 位 UART,使用定时器 1 设置波特率 |

在本菜谱中,我们将学习如何使用中断在使用 8 位 UART 模式和可编程波特率(模式 1)的串行端口上实现简单的数据交换。

怎么做……

请按照以下步骤完成本食谱:

  1. 打开 mcu8051ide 并创建一个新项目。
  2. 创建一个名为serial.c的新文件,并将以下代码片段复制到其中。 此代码将通过串行链路接收的字节复制到P0输出寄存器。 这与 MCU 上的通用输入/输出引脚相关联:
#include<8051.h>

void serial_isr() __interrupt(4) { 
    if(RI == 1) {
        P0 = SBUF;
        RI = 0;
    }
 }

void main() {
    SCON = 0x50;
    TMOD = 0x20;
    TH1 = 0xFD;
    TR1 = 1; 
    ES = 1;
    EA = 1;

    while(1);
 }
  1. 选择工具|编译以构建代码。
  2. 选择模拟器|Start/Shutdown(模拟器|启动/关闭)菜单项以激活模拟器。

它是如何运作的..。

我们为中断线路4定义 ISR,串行端口事件触发中断线路4

void serial_isr() __interrupt(4)

一旦接收到完整字节并将其存储在串行缓冲寄存器(SBUF)中,立即调用中断例程。 我们的 ISR 实现只将接收到的字节复制到输入/输出端口,即P0

P0 = SBUF;

然后,它重置 RI 标志以使能即将到来的字节的中断。

为了使中断按预期工作,我们配置了串行端口和定时器。 首先,配置串口,如下所示:

SCON = 0x50;

根据上表,这意味着只有串行控制寄存器(SCON)的 SM1 和 REN 位被设置为 1,从而选择通信模式 1。这是一个 8 位 UARS,具有通过定时器 1 定义的波特率。然后,它启用接收器。

由于波特率由定时器 1 定义,因此下一步是配置定时器,如下所示:

TMOD = 0x20;

上述代码将计时器 1 配置为使用模式 2,即 8 位自动重新加载模式。

将 0xFD 写入 TH1 寄存器会将波特率设置为 9600 bps。 然后,我们启用定时器 1、串行中断和所有中断。

还有更多的..。

数据传输可以以类似的方式实现。 如果将数据写入 SBUF 特殊寄存器,8051 UART 将开始传输。 一旦完成,将调用串行中断,并且 TI 标志将被设置为 1。