STM32基本GPIO操作:按键输入(扫描+外部中断)
(涉及专有名词较多,难免解释不到位,若有错误还请指出,谢谢!)
硬件连接图如下:
一、扫描
思路是在main函数中通过死循环来扫描端口电平状态检测,以此判断按键是否按下。实现较为简单。
1.初始化(注意C语言中变量声明需放在函数开头)
以下是初始化PB5端口(LED灯)的代码,每一条语句的含义在我另一篇博客里
GPIO_InitTypeDef GPIO_Init1;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_Init1.GPIO_Pin = GPIO_Pin_5;
GPIO_Init1.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_SetBits(GPIOB, GPIO_Pin_5); //先熄灯
GPIO_Init(GPIOB, &GPIO_Init1);
以下是初始化PE3端口(按键)的代码
GPIO_InitTypeDef GPIO_Init2;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
GPIO_Init2.GPIO_Pin = GPIO_Pin_3; // 设置GPIO端口号为5
GPIO_Init2.GPIO_Mode = GPIO_Mode_IPU; // 设置端口模式为输入上拉
// 设置为输入端口时不需要指定GPIO_Speed参数
GPIO_Init(GPIOE, &GPIO_Init2);
输入上拉与输入下拉的区别:
输入上拉(GPIO_Mode_IPU):端口与VCC通过一个电阻串连,因此没有输入或输入高电平时端口为高电平,输入低电平时端口为低电平
输入下拉(GPIO_Mode_IPD):端口与GND通过一个电阻串连,因此没有输入或输入低电平时端口为低电平,输入高电平时端口为高电平
从硬件图上得知按键与GND相连,如果端口设置为输入上拉,那么松开按键时端口为高电平,按下按键时端口为低电平,可以区分两种状态
如果端口设置为输入下拉,那么无论是按下还是松开按键时端口总为低电平,无法区分两种状态
类似地,如果按键与VCC相连,则端口需要设置为输入下拉才能区分按下/松开两种状态
2.扫描
读取PE3端口的状态:
GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)
返回值为SET则端口为高电位,返回值为RESET则端口为低电位
在main函数中放入以下死循环代码以实现扫描PE3端口并点灯的功能
while (1)
{
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==SET) // 如果按键对应端口为高电平
{
GPIO_SetBits(GPIOB, GPIO_Pin_5); // 熄灯(LED负极连接PB5,LED正极连接VCC,PB5高电平熄灯)
}
else // 否则
{
GPIO_ResetBits(GPIOB, GPIO_Pin_5); // 亮灯
}
delay_ms(10); // 一些开发板例程当中提供了delay函数,需要通过delay_init()初始化后才可使用
// 若无现成delay函数,可通过一定次数的for循环来代替
}
3.例程
代码如下:
int main(void)
{
GPIO_InitTypeDef GPIO_Init1, GPIO_Init2;
delay_init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_Init1.GPIO_Pin = GPIO_Pin_5;
GPIO_Init1.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_Init1);
GPIO_SetBits(GPIOB, GPIO_Pin_5); //先熄灯
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
GPIO_Init2.GPIO_Pin = GPIO_Pin_3;
GPIO_Init2.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOE, &GPIO_Init2);
delay_ms(200);
while (1)
{
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==SET)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5);
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
}
delay_ms(10);
}
}
二、中断
0.相关概念
中断:程序运行过程中,系统外部、系统内部或者现行程序本身若出现紧急事件,处理机立即中止现行程序的运行,自动转入相应的处理程序(中断服务程序),待处理完后,再返回原来的程序运行
简而言之就是触发某一事件可以使得MCU跳转执行该事件的处理程序,而按键按下或放开(GPIO口电平改变)则可作为一个外部中断,通过编写这一事件的处理程序从而达到改变灯亮灭状态的目的
(这里提到的“事件”并不是STM32当中的专有名词“事件”,而是泛指发生了某一件事)
使用扫描方式获得按键输入的思路如下:
主函数()
{
初始化()
死循环
{
如果(按键按下)
{……}
否则
{……}
}
}
而使用中断获得按键输入的思路如下:
主函数()
{
初始化()
其它操作()
}
中断处理函数()
{
如果(按键按下)
{……}
否则
{……}
}
对比可知使用扫描方式将使得芯片无法(难以)处理其它事务
NVIC:全名为“内嵌向量中断控制器”,主要用来控制芯片中各个中断的优先级,在很多地方都会使用(串口通信、SPI通信、定时器、I?C通信等涉及到实时处理的功能都会与中断有关)
EXTI(不是EXIT):全名为“外部中断/事件控制器”,可以实现输入信号的上升沿检测和下降沿的检测
1.初始化(注意C语言中变量声明需放在函数开头)
1.1 NVIC
需要用到的初始化语句如下:
NVIC_InitTypeDef NVIC_I; //定义初始化结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置整个系统的中断优先级分组
NVIC_I.NVIC_IRQChannel=EXTI3_IRQn; //设置初始化哪个中断
NVIC_I.NVIC_IRQChannelPreemptionPriority=0x02; //设置中断抢占优先级
NVIC_I.NVIC_IRQChannelSubPriority=0x02; //设置中断响应优先级(子优先级)
NVIC_I.NVIC_IRQChannelCmd=ENABLE; //中断使能(启动)
NVIC_Init(&NVIC_I); //初始化
中断优先级分组、抢占优先级和子优先级的关系:
STM32系列的芯片当中一般会有很多的中断,而当多个中断同时发生时就需要一个调度机制来控制它们的执行顺序,因此有了中断的优先级的概念。优先级遵循以下几点:
1.优先级的数字越小优先级越高
2.抢占优先级高的中断会先执行,它也可以打断抢占优先级低的中断
3.当抢占优先级相同时,响应优先级高的中断会先执行,但它不可以打断响应优先级低的中断
4.当两个中断的抢占优先级和响应优先级都相同时,先产生的中断先执行(按照时间顺序)
举个例子,现在有三个中断:
中断1:抢占优先级为2,响应优先级为1
中断2:抢占优先级为3,响应优先级为0
中断3:抢占优先级为2,响应优先级为0
则3个中断的优先级顺序是中断3>中断1>中断2,同时中断1、3可以打断中断2,中断3不能打断中断1
而两类优先级可以设置成哪些值呢?这取决于整个系统的中断优先级分组。通过
NVIC_PriorityGroupConfig();
可以设置整个系统的中断优先级分组,其参数可以是NVIC_PriorityGroup_0、NVIC_PriorityGroup_1、NVIC_PriorityGroup_2、NVIC_PriorityGroup_3、NVIC_PriorityGroup_4之一。具体关系如下:
NVIC_PriorityGroup_0:0位抢占优先级(无效)+4位响应优先级(0~15)
NVIC_PriorityGroup_1:1位抢占优先级(01)+3位响应优先级(07)
NVIC_PriorityGroup_2:2位抢占优先级(03)+2位响应优先级(03)
NVIC_PriorityGroup_3:3位抢占优先级(07)+1位响应优先级(01)
NVIC_PriorityGroup_4:4位抢占优先级(0~15)+0位响应优先级(无效)
例如中断分组设置为3,则所有中断的抢占优先级可以被设置为0~7,响应优先级可以被设置为0、1
需要注意的是如果程序中用到了二次封装的一些库(比如开发板例程中厂商为初学者写的串口库等),则NVIC_PriorityGroupConfig()可能已经被调用过了,此时再次修改可能会带来其它问题
1.2 EXTI
需要用到的初始化语句如下:
EXTI_InitTypeDef EXTI_I; //定义初始化结构体
EXTI_I.EXTI_Line=EXTI_Line3; //设置初始化哪条中断/事件线
EXTI_I.EXTI_Mode = EXTI_Mode_Interrupt; //设置为产生中断(EXTI_Mode_Event为产生事件)
EXTI_I.EXTI_Trigger = EXTI_Trigger_Falling; //设置为下降沿触发
EXTI_I.EXTI_LineCmd = ENABLE; //使能
EXTI_Init(&EXTI_I); //初始化
中断/事件的区别:
中断产生后经由NVIC交给MCU进行处理(软件层面)
事件最终作为一个脉冲信号直接触发其它硬件(硬件层面)
附一张EXTI的框图便于理解,蓝色是中断,红色是事件
EXTI、NVIC与GPIO的对应关系:
如图所示
上升/下降沿:
低电平跳到高电平为上升沿,高电平跳到低电平为下降沿
1.3 GPIO
与扫描方式的初始化代码相同
GPIO_InitTypeDef GPIO_Init1, GPIO_Init2;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_Init1.GPIO_Pin = GPIO_Pin_5;
GPIO_Init1.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_Init1);
GPIO_SetBits(GPIOB, GPIO_Pin_5); //先熄灯
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
GPIO_Init2.GPIO_Pin = GPIO_Pin_3;
GPIO_Init2.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOE, &GPIO_Init2);
1.4 其它
目前不明确这两条语句的作用
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //开启端口复用,涉及到GPIO口做外部中断时都需要这一条语句
2.中断处理函数
函数名与中断/事件线有着对应关系,可参照上一张表
void EXTI3_IRQHandler(void)
{
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==SET)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5);
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
最后的EXTI_ClearITPendingBit()用于清除中断标志位,避免对之后的中断造成影响
3.例程
代码如下:
int main(void)
{
GPIO_InitTypeDef GPIO_Init1, GPIO_Init2;
NVIC_InitTypeDef NVIC_I;
EXTI_InitTypeDef EXTI_I;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource3);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
NVIC_I.NVIC_IRQChannel=EXTI3_IRQn;
NVIC_I.NVIC_IRQChannelPreemptionPriority=0x02;
NVIC_I.NVIC_IRQChannelSubPriority=0x02;
NVIC_I.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_I);
EXTI_I.EXTI_Line=EXTI_Line3;
EXTI_I.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_I.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_I.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_I);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_Init1.GPIO_Pin = GPIO_Pin_5;
GPIO_Init1.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init1.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_Init1);
GPIO_SetBits(GPIOB, GPIO_Pin_5); //先熄灯
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
GPIO_Init2.GPIO_Pin = GPIO_Pin_3;
GPIO_Init2.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOE, &GPIO_Init2);
}
void EXTI3_IRQHandler(void)
{
if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==SET)
{
GPIO_SetBits(GPIOB, GPIO_Pin_5);
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_5);
}
EXTI_ClearITPendingBit(EXTI_Line3);
}
2019.12.22
STM32基本GPIO操作:按键输入(扫描+外部中断)的更多相关文章
- STM32之GPIO操作
啊哈.没办法.外国人的芯片就喜欢用英文来命名,所以中文的:通用输入/输出 就用GPIO来代替..谁叫哥们都不是外国人呢.好啦.胡扯了一下,借用唐伯虎点秋香的话:小小书童,可笑可笑... 知道了GPI ...
- STM32基本GPIO操作:点灯(库函数+寄存器)
社团作业=_= 开发版上的LED灯负极连接在PB5口,正极串联一510Ω电阻后与3.3V相连 若开发板不带LED灯则需要自行连接,务必串联一个合适的电阻防止LED灯烧坏 零.一个有趣的延时函数 来自于 ...
- STM32 常用GPIO操作函数记录
STM32读具体GPIOx的某一位是1还是0 /** * @brief Reads the specified input port pin. * @param GPIOx: where x can ...
- STM32外部中断具体解释
一.基本概念 ARM Coetex-M3内核共支持256个中断,当中16个内部中断,240个外部中断和可编程的256级中断优先级的设置.STM32眼下支持的中断共84个(16个内部+68个外部), ...
- STM32(3)——外部中断的使用
1 .简介 ARM Coetex-M3内核共支持256个中断,其中16个内部中断,240个外部中断和可编程的256级中断优先级的设置.STM32目前支持的中断共84个(16个内部+68个外部),还有1 ...
- stm32 外部中断学习
今天我们看看STM32的外部中断实验. STM32 供 IO 口使用的中断线只有 16 个,但是 STM32 的 IO 口却远远不止 16 个,那么 STM32 是怎么把 16 个中断线和 IO 口一 ...
- STM32外部中断初理解
PA0,PB0...PG0--->EXTI0 PA1,PB1...PG1--->EXTI1 ....... PA15,PB15...PG15--->EXTI15 以上为GPIO和中断 ...
- STM32学习笔记(九) 外部中断,待机模式和事件唤醒
学会知识只需要不段的积累和提高,但是如何将知识系统的讲解出来就需要深入的认知和系统的了解.外部中断和事件学习难度并不高,不过涉及到STM32的电源控制部分,还是值得认真了解的,在本文中我将以实际代码为 ...
- stm32学习笔记——外部中断的使用
stm32学习笔记——外部中断的使用 基本概念 stm32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个.比如说,PA0,PB0 ...
随机推荐
- typedef & #defiine & struct
#define(宏定义)只是简单的字符串代换(原地扩展),它本身并不在编译过程中进行,而是在这之前(预处理过程)就已经完成了. typedef是为了增加可读性而为标识符另起的新名称(仅仅只是个别名), ...
- 负载均衡集群介绍、LVS介绍、LVS调度算法、LVS NAT模式搭建
7月4日任务 18.6 负载均衡集群介绍18.7 LVS介绍18.8 LVS调度算法18.9/18.10 LVS NAT模式搭建 扩展lvs 三种模式详解 http://www.it165.net/a ...
- 42步进阶学习—让你成为优秀的Java大数据科学家!
作者 灯塔大数据 本文转自公众号灯塔大数据(DTbigdata),转载需授权 如果你对各种数据类的科学课题感兴趣,你就来对地方了.本文将给大家介绍让你成为优秀数据科学家的42个步骤.深入掌握数据准备, ...
- Android 寻找Drawable资源的流程
寻找设备对应Drawable资源时,会先在设备对象dpi的drawable文件夹下寻找,如果没找到,会上溯到更高一级dpi文件夹下寻找,上溯最高两级.如果还是没有找到,会寻找noDensity文件夹下 ...
- ThreadLocal快速了解一下
欢迎点赞阅读,一同学习交流,有疑问请留言 . GitHub上也有开源 JavaHouse 欢迎star 1 引入 在Java8里面,ThreadLocal 是一个泛型类.这个类可以提供线程变量.每个线 ...
- VLAN应用实例
VLAN 此次内容主要介绍VLAN的Access接口.Trunk接口.Hybird接口的配置实例,以及实际应用. 一.介绍三种接口 1.Access接口 (1)Access接口是交换机上用来连接用户主 ...
- Node.js 中 __dirname 和 ./ 的区别
概要 __dirname 总是指向被执行 js 文件的绝对路径 在 /d1/d2/myscript.js 文件中写了 __dirname, 它的值就是 /d1/d2 . ./ 会返回你执行 node ...
- 声明式服务调用Feign
什么是 Feign Feign 是种声明式.模板化的 HTTP 客户端(仅在 consumer 中使用). 什么是声明式,有什么作用,解决什么问题? 声明式调用就像调用本地方法一样调用远程方法;无 ...
- 数据库Oracle的含义
数据库的含义: 所谓的数据库其实就是数据的集合.用户可以对集合中的数据进行新增.查询.更新. 删除等操作.数据库是以一定方式储存在一起.能与多个用户共享.具有尽可能小的冗余度. 与应用程序彼此独立的数 ...
- openlayers4 入门开发系列结合 echarts4 实现交通线流动图
前言 openlayers4 官网的 api 文档介绍地址 openlayers4 api,里面详细的介绍 openlayers4 各个类的介绍,还有就是在线例子:openlayers4 官网在线例子 ...