蓝桥杯单片机备赛之入门篇 -- 基础外设的应用2
在上一节提到了,本节的内容会先介绍中断的知识,再讲一下这些基本外设配合中断进行使用的写法,以及独立键盘与矩阵键盘的函数编写。
一、中断
中断的定义
中断是指计算机在执行程序的过程中,当出现某些紧急事件时,暂时停止正在执行的程序,转去处理紧急事件,处理完后再返回原程序继续执行的过程。在 STC15F2K60S2 单片机中,中断可以由外部事件(如外部引脚电平变化)、内部事件(如定时器溢出、串口数据接收等)触发。
为了更好理解中断,这里举一个例子,比如说有一天,你正躺在沙发上打游戏,这时候门铃响了,你不得不停下手中的游戏去开门,开完门后,你才能够回到沙发上继续玩游戏。而“门铃响了”就是一个中断事件,类比到程序运行过程中,程序在主函数的while大循环中反复执行,当一个中断事件触发之后,程序需要“响应”这个中断,马上跳转到中断服务函数,执行完中断服务函数的内容,才能够回到主函数继续执行。

中断的作用
中断能够提高单片机的实时响应能力,使单片机可以及时处理各种突发事件,如外部设备的请求、定时任务等,有效提高系统的运行效率和可靠性。
中断源
STC15F2K60S2 单片机有多个中断源,包括外部中断 0、外部中断 1、定时器 0 中断、定时器 1 中断、串口中断等。不同的中断源对应着不同的事件,例如外部中断可用于检测外部引脚的电平变化,定时器中断可用于定时控制等。本节将会用到定时器2的中断。
中断优先级
每个中断源都有相应的优先级设置,通过设置优先级可以确定当多个中断同时发生时,单片机优先响应哪个中断。STC15F2K60S2 单片机支持中断优先级分组,可以根据实际需求灵活配置各中断源的优先级。但是比赛一般不会要求配置各中断源的优先级,这里就不赘述。
中断相关的寄存器
在STC15F2K60S2 单片机当中,与中断相关的寄存器主要有中断允许寄存器IE、中断优先级寄存器IP以及一些与特定中断源相关的寄存器,例如定时器 / 计数器控制寄存器TCON、定时器 / 计数器工作方式寄存器TMOD、串行控制寄存器SCON。具体每个寄存器的作用和使用方法,可以查阅数据手册或者问AI就可以啦。
中断处理流程
中断请求:当某个中断源发生特定事件时,会向单片机发出中断请求信号。例如,定时器溢出时,定时器中断源会产生中断请求。
中断响应:单片机在每个机器周期都会查询中断标志位,当检测到有中断请求且满足中断响应条件(如中断允许位已置 1、当前指令执行完毕等)时,会暂停当前正在执行的程序,保存当前程序的断点(即程序计数器 PC 的值),然后根据中断向量表跳转到相应的中断服务程序入口地址。
中断服务程序:在中断服务程序中,编写处理中断事件的具体代码。例如,对于定时器中断,可以在中断服务程序中进行定时任务的处理,如每隔一定时间对某个变量进行加 1 操作;对于外部中断,可以在中断服务程序中读取外部引脚的状态,并根据状态执行相应的操作。
中断返回:当中断服务程序执行完毕后,通过执行中断返回指令 RETI,单片机将恢复之前保存的断点地址,继续执行原来被中断的程序。
二、应用中断实现对基础外设的控制
以上就是对中断相关知识的介绍,那么本节是利用定时器2产生中断,来实现对基础外设的控制。至于为什么选择定时器2,是因为在蓝桥杯单片机比赛中,定时器0常用于测频率,定时器1常用来测距离。接下来直接切入正题。
直接在STC-ISP生成定时器2初始化函数:按照图片方式配置,点击复制代码。

粘贴到main.c当中,并且记得在后面加上对IE2和EA的操作,目的是允许定时器2产生中断和打开总中断的开关EA。



至此,我们就将定时器2的初始化函数写好了,每经过1ms,定时器2溢出,就会进入定时器2的中断服务函数。我们将其在while大循环之前进行调用:

在编写定时器2的中断服务函数之前,我们先定义几个变量:具体的作用,后面会说明。

接下来,我们需要编写定时器2的中断服务函数:对于LED,我们定义了一个大小为9的数组,Led[1] ~ Led[8]分别对应八个LED灯,如果想要实现控制LED灯的亮灭,只要在主函数当中对Led[1] ~ Led[8]进行赋值即可,并且基于下图代码的写法,此时为高电平点亮,低电平熄灭,更符合用户的操作习惯。
对于数码管,我们在定时器2的中断服务函数之前定义了一个静态局部变量,它只初始化一次,这个变量用于实现数码管的动态扫描;接着到if(++count_dsp),每进入一次中断服务函数,count_dsp的值都会自加1,直到其值大于8,才将其置为1;Control(0xc0, 0x00)是为了实现数码管动态显示前的消影,接着Control(0xe0, Code[Bit[count_dsp]]),为了理解这一语句,我们代入特殊值,例如当count_dsp = 1,即传递给Code数组的变量是Bit[1],而我们要控制第一个数码管显示某个数字或者字符,只需要在主函数中对Bit[1]进行赋值即可,例如,Bit[1] = 3;那么就把3作为参数传递给Code数组,而Code数组正是共阳数码管的段码数组,传递的参数为3,Code[Bit[1]]的值就为“3”这个数字的段码,这样一来,我们就实现了数码管的段选;再下一句Control(0xc0, 1 << (count_dsp - 1))目的是实现数码管的位选,count_dsp的值为多少,点亮的就是那一位数码管。这样一来我们也可以知道,8ms就能扫描一遍所有数码管,而且使用定时器产生中断实现的,效果稳定,无明显闪烁感,绝对是可以满足所有题目的要求的。
对于继电器和蜂鸣器,相信也很容易看懂了,要实现对继电器和蜂鸣器的操作,只需要在主函数当中对Relay和Buzzer进行赋值即可。By the way,这里定义Relay和Buzzer完全可以不用unsigned char,用bit数据类型就够了。

这样,我们就通过定时器中断实现了对基础外设的控制。我们在主函数中添加一些代码,实现一些简单的功能。


三、独立键盘与矩阵键盘的函数编写
接下来就到了最后一个基础外设,键盘。常规写法当中用到了大量的软件延时,我们知道,软件延时会占用CPU,大大影响了程序运行的实时性,因此,键盘的扫描我们仍然采用结合中断来实现的方法。
这里我们直接附上程序源码,并在注释当中进行讲解:
#include "stc15.h"
#define Control(x, y) P0 = y; P2 = x; P2 = 0
unsigned char Led[9]; // 定义LED数组并在中断服务函数中实现对每一个LED灯的独立控制
// 计数变量声明
unsigned char Count_Key;
void Timer2_Init(void) //1毫秒@12.000MHz
{
AUXR |= 0x04; //定时器时钟1T模式
T2L = 0x20; //设置定时初始值
T2H = 0xD1; //设置定时初始值
AUXR |= 0x10; //定时器2开始计时
IE2 |= 0x04; // 允许定时器2产生中断
EA = 1; // 开总中断
}
void Timer2_Service() interrupt 12
{
// LED
Control(0x80, ~(Led[8] << 7 | Led[7] << 6 | Led[6] << 5 | Led[5] << 4 |
Led[4] << 3 | Led[3] << 2 | Led[2] << 1 | Led[1] << 0));
// 计数变量自加
++Count_Key;
}
// 独立按键键值读取函数
unsigned char Read_Key()
{
static bit flag_press = 0; // 按键按下标志位
static unsigned char count_press = 0; // 计数标志位,用于按键消抖
unsigned char value = 1; // 按键没有按下时,默认键值为1
unsigned char original = 0;
original = P3 ^ 0xff; // P3 ^ 0xff即将P3按位取反后赋值给original
if(original > 0) // 若此时origianl > 0说明可能有按键按下
{
if((flag_press == 0) && (++count_press >= 20))
{ // 按键消抖,判断按键按下的第二重保障
flag_press = 1; // 按键真正按下,而不是抖动
count_press = 0;
if(original & 0x08) value = 4; // 查看原理图,P33对应键值为4,依此类推
else if(original & 0x04) value = 5;
else if(original & 0x02) value = 6;
else if(original & 0x01) value = 7;
}
}
else
{
flag_press = 0;
count_press = 0;
}
return value;
}
int main()
{
Timer2_Init();
while(1)
{
unsigned char key_value = 0;
if(Count_Key >= 1) // 相当于1ms扫描一次
{
Count_Key = 0;
key_value = Read_Key();
}
if(key_value == 4)
{
Led[1] ^= 1; // 若按下按键S4,则将LED1的亮灭状态取反
}
}
}

再附上矩阵按键键值读取函数的写法:
unsigned char Read_Keyboard()
{
static unsigned char count_press = 0;
static bit flag_press = 0;
unsigned char value = 1;
unsigned char x = 0;
unsigned char y = 0;
P3 = 0x0f;
P4 = 0x00;
if(P30 == 0) x = 3;
else if(P31 == 0) x = 2;
else if(P32 == 0) x = 1;
else if(P33 == 0) x = 0;
P3 = 0xf0;
P4 = 0xff;
if(P34 == 0) y = 4;
else if(P35 == 0) y = 3;
else if(P42 == 0) y = 2;
else if(P44 == 0) y = 1;
if(y > 0)
{
//20ms内连续20次检测到按键按下
if((flag_press == 0) && (++count_press >= 20))
{
flag_press = 1;
count_press = 0;
//根据在x轴和y轴上的值,计算按键值
value = x + y * 4;
}
}
else
{
flag_press = 0;
count_press = 0;
}
return value;
}
四、总结
自此,本节内容就结束了,本节主要介绍了中断的相关知识,然后介绍了使用定时器2产生中断对基础外设的简单控制,最后介绍了用中断实现键盘扫描的方法。下期见。
更多推荐
所有评论(0)