目录
无中断按键
第一步 分析原理图
电路工作原理
第二步 配置寄存器
第一步 配置为通用IO口
第二步 配置为输入
第三步 输入模式选择
按键抖动
中断按键
第一步中断寄存器的配置
上升沿
下降沿
第一步 开启总中断使能
第二步 开启端口中断使能
第三步 开启端口引脚中断使能
第四步 中断触发方式设置为下降沿
第二步 中断服务函数
对应的XXX填入内容及原因
中断服务函数内部代码讲解
为什么要清除P0IFG
代码的流程图:
下面是我的CC2530的学习笔记之按键中断部分。
首先按键其实和点灯逻辑上都是一样的,先简单来过一下没有中断的按键,光是依靠if判断来进行改变LED灯亮灭的代码逻辑。
无中断按键
第一步 分析原理图
通过此图可以得出的信息:
电源连接:
- 电路中有两个3.3V电源连接点,分别为上方和下方的3.3V标记。
按键连接:
- 按键S1和S2分别连接到P0_0和P0_1引脚。
- 按键S1和S2的另一端连接到GND(地),这意味着按键按下时会将相应引脚拉低到地电平(0V)。
上拉电阻:
- 每个按键都有一个上拉电阻(R10和R13),阻值为10K欧姆。这些电阻连接在3.3V电源和按键之间。
- 上拉电阻的作用是当按键未按下时,将P0_0和P0_1引脚拉高到3.3V(高电平)。
电路工作原理
- 未按下按键时:由于上拉电阻的作用,P0_0和P0_1引脚的电平为高电平(3.3V)。
- 按下按键时:按键将P0_0和P0_1引脚直接连接到地(GND),使得引脚电平变为低电平(0V)。
有关上下拉电阻:
一、上拉电阻如图所示:
1、概念:将一个不确定的信号,通过一个电阻与电源VCC相连,固定在高电平;
2、上拉是对器件注入电流;灌电流;
3、当一个接有上拉电阻的IO端口设置为输入状态时,它的常态为高电平;
二、下拉电阻如图所示:
1、 概念:将一个不确定的信号,通过一个电阻与地GND相连,固定在低电平;
2、下拉是从器件输出电流;拉电流;
3、当一个接有下拉电阻的IO端口设置为输入状态时,它的常态为低电平;
此处摘自 “上拉电阻与下拉电阻”通俗解读_上拉电阻和下拉电阻-CSDN博客
第二步 配置寄存器
第一步 配置为通用IO口
对于CC2530单片机的P0_0和P0_1引脚,配置为通用I/O口而不是外设I/O口是因为我们需要直接监测这些引脚的电平状态来判断按键的电频状态。外设I/O口通常用于特定的硬件外设(如UART、SPI、I2C等),而在这种情况下,选择通用I/O口更适合用于简单的引脚电平控制
我们根据上图以及上文可以看到我们需要配置端口功能为通用IO口,就选择PxSEL寄存器因为我们是P0_0和P0_1引脚。 所以我们就选择P0SEL寄存器。
代码
P0SEL &= ~0X03; //第0引脚和第1引脚置为0
第二步 配置为输入
在将方向设置为输入的时候,我们要先知道什么是输入输出,不需要多懂,简单了解一下就好了,懂这个为啥设为输入即可。
输入:输入是指检测各个引脚上的电平状态。
电平状态:电平状态分为高电平和低电平。高电平表示有电压,通常用数字符号1表示;低电平表示接地,通常用数字符号0表示。
应用示例:一个常见的输入例子是按钮。当按钮按下时,引脚检测到高电平(1);当按钮松开时,引脚检测到低电平(0)。
输出:输出是指控制引脚电平的高低状态。
电平状态:通过设置引脚的电平状态,可以控制外部设备。例如,高电平(1)可以点亮LED,低电平(0)可以熄灭LED。
应用示例:你提供的图片就是一个输出的例子。P1_0和P1_1引脚通过电阻连接到LED,当引脚输出高电平时,LED亮起;当引脚输出低电平时,LED熄灭。
我们根据图可以看到如果我们需要配置改变方向为输入,就选择PxDIR寄存器因为我们是P0_0和P0_1引脚。 所以我们就选择P0DIR寄存器。
PxDIR寄存器的作用
PxDIR寄存器就像一个控制面板,用来决定每个引脚是作为输入还是输出。每个引脚都有一个对应的开关(位),你可以通过这个开关来设置引脚的方向是输入还是输出。
代码如下:
P0SEL &= ~0x03; /将P1_0和P1_1配置为通用I/O
P0DIR &= 0x03; // 将P1_0和P1_1设置为输入
第三步 输入模式选择
还是 很简单,为什么我们要进行输入模式的选择上下拉呢,说白点就是保险点 稳定点,当需要确保输入引脚的电平稳定时,如按键输入或传感器输入,就需要去选择。
因为我们是要选择上拉所以代码这么写:
P0SEL &= ~0X03; //通用IO
P0DIR &= ~0X03; // 方向选择 --- 输入模式
P0INP &= ~0x03; // 输入模式设置 设置上/下拉
第四步 上拉模式的选择
首先我们要知道 CC2530 有3组端口 P0 P1 P2 ---> P0 P1 有8个引脚 P2 有5个引脚。合起来有21个数字输入/输出引脚。P2 少了三个引脚而P2的5 6 7 引脚就可以用来选择具体的端口上拉还是下拉,需要注意的就是这个上拉下拉只能精确到端口!!!不能有P1_1是上拉 P1_2是下拉的这个情况,你要问为啥就是没有为啥 别人就是这么规定的,要么就换芯片 哈哈哈哈。
代码如下
P0SEL &= ~0X03;//通用功能
P0DIR &= ~0X03;// 方向选择 --- 输入模式
P0INP &= ~0x03; // 输入模式设置 设置上/下拉
P2INP &= ~0X20; //设置上拉 P2INP 第5 6 7 位设置 P0端口 第5位 xx0x xxxx
到这里 按键的寄存器配置就搞定了。
需要注意的就是按键会有一个抖动的过程。简单了解一下
按键抖动
按键抖动是一个常见的现象,发生在机械按键或开关的按下和松开过程中。以下是按键抖动的原因及其影响:
按键抖动的原因
机械结构:
- 按键内部的机械结构在按下或松开的瞬间会发生弹跳。这是因为按键的金属触点在接触或分离时不会立即稳定,而是会在短时间内多次接触和分离。
弹性材料:
- 按键内部的弹性材料(如弹簧)在按下或松开时会产生振动,导致触点在短时间内多次接触和分离。
电气噪声:
- 按键的机械抖动会引起电气噪声,使得引脚电平在短时间内快速变化,产生多个高低电平的切换。
按键抖动的影响
- 误触发:由于抖动,单次按键操作可能会被误认为多次按键,导致系统误触发多次。
- 不稳定信号:抖动会导致输入信号不稳定,影响系统的可靠性和准确性。
按键抖动的解决方法
硬件消抖:
- 电容消抖:在按键两端并联一个小电容(如0.1µF),可以滤除高频抖动信号。
- RC滤波器:使用电阻和电容组成的RC滤波器,可以平滑按键的抖动信号。
软件消抖:
- 延时法:在检测到按键按下后,延时一段时间(如10ms),再重新检测按键状态。如果按键状态稳定,则认为按键有效。
- 计数法:连续多次读取按键状态,如果在一定时间内状态一致,则认为按键有效。
比较简单的就是加一个延时 把这个一定程度上给规避掉。
下面我们就可以来编写简单的LED按键开关的代码了,下面是代码的流程图:
代码如下:
#include "ioCC2530.h"
#define LED1 P1_0
#define LED2 P1_1
#define S1 P0_0
#define S2 P0_1
void delay(int num)
{
int i = 0;
for(i = 0; i < num; i++)
{
int j = 0;
for(j = 0; j <400; j++)
;
}
}
void LED_Init()
{
// 通用功能
P1SEL &= ~0X03;
// 方向选择
P1DIR |= 0X03;
// 初始化LED灯
LED1 = 0;
LED2 = 0;
}
//按键初始化 按键 输入 模式 上拉
void Key_Init()
{
//通用功能
P0SEL &= ~0X03;
// 方向选择 --- 输入模式
P0DIR &= ~0X03;
// 输入模式设置 设置上/下拉
P0INP &= ~0x03;
//设置上拉 P2INP 第5 6 7 位设置 P0端口 第5位 xx0x xxxx
P2INP &= ~0X20;
}
void main()
{
LED_Init();
Key_Init();
while(1)
{
// 输入模式可以直接判断引脚状态
if(S1 == 0) // 按下按键
{
//消抖
delay(10);
if(S1 == 0) // 确认按键是否真的按下
{
LED1 = ~LED1;
LED2 = ~LED2;
// 等待按键松开
while(S1 == 0);
}
}
}
}
当当当 就搞定了 你就可以通过按键去控制LED灯的亮灭了。
当然 一般都不这么写,一般都会写一个中断程序,按键来触发,所以接下来我们就进入中断按键喽
中断按键
什么是中断?
中断是一种机制,当某个事件发生时,单片机可以暂停当前正在执行的任务,转而去处理这个事件。处理完事件后,单片机可以继续执行之前的任务。
中断的类型
- 外部中断:由外部硬件事件触发,例如按键按下、传感器信号等。
- 内部中断:由单片机内部事件触发,例如定时器溢出、ADC转换完成等。
中断的优先级
不同的中断源可以有不同的优先级。当多个中断同时发生时,优先级高的中断会优先得到处理。
中断向量表
中断向量表是一个存储中断服务程序(ISR)地址的表。当中断发生时,单片机会根据中断向量表跳转到相应的ISR。
中断服务程序(ISR)
中断服务程序是处理中断事件的函数。当中断发生时,单片机会自动跳转到相应的ISR执行。ISR通常需要尽快执行完毕,以便让单片机返回主程序。
中断的使能和屏蔽
单片机可以通过设置寄存器来使能或屏蔽某个中断。使能中断后,当相应事件发生时,中断会被触发;屏蔽中断后,即使事件发生,中断也不会被触发。
中断的处理流程
- 中断事件发生:例如按键按下。
- 中断请求:中断控制器检测到中断事件,向CPU发出中断请求。
- 中断响应:CPU完成当前指令后,保存当前程序计数器和状态寄存器,跳转到ISR。
- 执行ISR:处理中断事件。
- 恢复现场:ISR执行完毕后,恢复之前保存的程序计数器和状态寄存器,返回主程序。
第一步中断寄存器的配置
首先我们要知道,只有通用I/O口设置成输入后,才可以用于产生中断。中断可以设置在外部信号的上升或下降沿触发。这个上下沿先看一下 下文有介绍。
这里简单讲解一下什么是上升沿和下降沿。
上升沿
上升沿是指信号从低电平(通常是0V)跃升到高电平(通常是3.3V或5V)的瞬间。这个瞬间的变化称为上升沿。
下降沿
下降沿是指信号从高电平(通常是3.3V或5V)下降到低电平(通常是0V)的瞬间。这个瞬间的变化称为下降沿。
在已经设置为通用I/O并且已经设置成输入后。 我们就开始配置中断寄存器了。
第一步 开启总中断使能
这里可以看到EA是总中断使能位。当EA被设置为1时,单片机允许所有中断请求被响应。
这是为了确保单片机能够响应任何中断请求。如果EA不被使能,即使有中断请求发生,单片机也不会响应。
这个是在ioCC250的头文件里面找到的,对其进行了位定义
代码如下:
//P0端口的中断使能
//P0端口中断源 开关在 IEN1 寄存器中定义 第5位
IEN1 |= 0X20; // 0010 0000
P0IE = 1; // iocc2530 头文件 给IEN1 寄存器做了位定义!!
第二步 开启端口中断使能
配置端口中断使能的原因是:为了使能P0_0和P0_1引脚的中断功能,使得这两个引脚可以独立触发中断。
代码如下:
//P0端口的中断使能
//P0端口中断源 开关在 IEN1 寄存器中定义 第5位
IEN1 |= 0X20; // 0010 0000
P0IE = 1; // iocc2530 头文件 给IEN1 寄存器做了位定义!!
第三步 开启端口引脚中断使能
配置端口引脚中断使能的原因:为了使能P0_0和P0_1引脚的中断功能,使得这两个引脚可以独立触发中断。
代码如下:
//P0的引脚的 中断使能
//开启P0端口 P0_0 P0_1
P0IEN |= 0X03; // 0000 0011
第四步 中断触发方式设置为下降沿
这里可以看到 PICTL寄存器可以设置P0、P1、P2的触发沿设置。
为什么设置下降沿而不是上升沿呢:这是为了在按键按下(通常会导致引脚电平从高变低)时触发中断。下降沿触发可以确保在按键按下时立即响应中断。
第二步 中断服务函数
红框标注的是中断向量 我个人认为需要结合代码来理解这个中断服务函数。示例代码如下:
#pragma vector = XXX_VECTOR
__interrupt void XXX_ISR()
{
// 处理中断函数的步骤
}
对应的XXX填入内容及原因
-
XXX_VECTOR
- 解释:
XXX_VECTOR
是中断向量的名称,用于指定哪个中断源触发这个中断服务函数。 - 填入内容:根据具体的中断源,填入相应的中断向量名称。例如:
P0INT_VECTOR
:用于P0端口的中断。T1_VECTOR
:用于定时器1的中断。ADC_VECTOR
:用于ADC转换完成的中断。
- 原因:填入具体的中断向量名称是为了告诉编译器,当对应的中断源触发中断时,应该跳转到这个中断服务函数执行。
- 解释:
-
XXX_ISR
- 解释:
XXX_ISR
是中断服务函数的名称(可以随意填 最后最好跟一个_ISR),用于处理特定中断源的中断事件。 - 填入内容:根据具体的中断源,填入相应的中断服务函数名称。例如:
P0_ISR
:用于处理P0端口的中断。T1_ISR
:用于处理定时器1的中断。ADC_ISR
:用于处理ADC转换完成的中断。
- 原因:填入具体的中断服务函数名称是为了定义一个函数,用于处理特定中断源的中断事件。在中断发生时,单片机会跳转到这个函数执行相应的中断处理步骤。
- 解释:
通过填入具体的中断向量名称和中断服务函数名称,我们可以定义和配置中断服务函数,使得单片机在中断发生时能够正确跳转到相应的函数执行中断处理步骤。
中断服务函数内部代码讲解
接下来我们结合案例代码来讲解一下中断服务函数中处理中断函数的步骤在写的时候应该注意什么
#pragma vector = P0INT_VECTOR
__interrupt void Key_ISR()
{
if(P0IFG & 0X01) //S1
{
LED2 = !LED2; // 中断操作
P0IFG &= ~0X01;
}
if(P0IFG & 0X02) //S2
{
LED2 = !LED2; // 中断操作
P0IFG &= ~0X02;
}
//清除端口中断标志 IRCON 寄存器第5位 但是头文件做了位定义
P0IF = 0;
}
进入中断服务函数我们可以看到:有个P0IFG,那么这个寄存器的含义是什么呢?
儿豁 看到这个麻了,有点没懂。
第一个图意思就是
P0IFG
是P0端口的中断标志寄存器。当P0端口的某个引脚发生中断条件(例如电平变化)时,相应的中断标志位会被置为1。即使该引脚的中断使能位没有被设置,中断标志位也会被置位。然后置为一了。第二个图的未决信号:未决信号意味着中断事件已经发生,但尚未被处理。当中断请求发生时,相应的中断标志位会被设置为1。这个标志位的状态(通常是位于特定的寄存器中)告诉CPU有一个中断请求需要处理。CPU或中断控制器会检查这些标志位,确定哪个中断请求是活跃的,并执行相应的中断服务程序(ISR)来处理该事件。
清除中断标志位的步骤如下:
- 检测中断标志:首先检测哪个引脚触发了中断。
- 执行中断操作:根据触发中断的引脚执行相应的操作。
- 清除中断标志:清除相应的中断标志位,防止重复触发中断。
- 清除端口中断标志:最后清除整个端口的中断标志。
为什么要清除P0IFG
当中断已经执行完毕后,需要清除相应的中断标志位,以防止重复触发中断。
这个P0IF是经过了位定义的。
可以看到IRCON的第五位是控制着端口0 的中断标志的,把它置零的原因就是确保所有P0端口的中断标志都被清除,防止重复触发中断。
代码的流程图:
下面我们就可以来编写简单的中断按键控制LED的代码了,下面是代码的流程图:
咋样 是不是很简单。
完整代码附上:
#include "ioCC2530.h"
#define LED1 P1_0
#define LED2 P1_1
#define S1 P0_0
#define S2 P0_1
void delay(int num)
{
int i = 0;
for(i = 0; i < num; i++)
{
int j = 0;
for(j = 0; j <400; j++)
;
}
}
void LED_Init()
{
// 通用功能
P1SEL &= ~0X03;
// 方向选择
P1DIR |= 0X03;
// 初始化LED灯
LED1 = 0;
LED2 = 0;
}
//按键初始化 按键 输入 模式 上拉
void Key_Init()
{
//通用功能
P0SEL &= ~0X03;
// 方向选择 --- 输入模式
P0DIR &= ~0X03;
// 输入模式设置 设置上/下拉
P0INP &= ~0x03;
//设置上拉 P2INP 第5 6 7 位设置 P0端口 第5位 xx0x xxxx
P2INP &= ~0X20;
}
// 按键中断 初始化
void Key_ISR_Init()
{
// 总中断使能
EA = 1;
//P0端口的中断
P0IE = 1;
// P0具体引脚中断
P0IEN |= 0X03;
//中断触发方式 下降沿
PICTL |= 0X01;
}
void main()
{
LED_Init();
Key_Init();
Key_ISR_Init();
while(1)
{
LED1 = !LED1;
delay(1000);
}
}
// 当按键中断来临需要处理的特殊操作
#pragma vector = P0INT_VECTOR
__interrupt void Key_ISR()
{
if(P0IFG & 0X01) //S1
{
LED2 = !LED2; // 中断操作
P0IFG &= ~0X01;
}
if(P0IFG & 0X02) //S2
{
LED2 = !LED2; // 中断操作
P0IFG &= ~0X02;
}
//清除端口中断标志 IRCON 寄存器第5位 但是头文件做了位定义
P0IF = 0;
}
芜湖 完结 撒花
附上原理图