(涉及专有名词较多,难免解释不到位,若有错误还请指出,谢谢!)

硬件连接图如下:

一、扫描

思路是在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操作:按键输入(扫描+外部中断)的更多相关文章

  1. STM32之GPIO操作

    啊哈.没办法.外国人的芯片就喜欢用英文来命名,所以中文的:通用输入/输出  就用GPIO来代替..谁叫哥们都不是外国人呢.好啦.胡扯了一下,借用唐伯虎点秋香的话:小小书童,可笑可笑... 知道了GPI ...

  2. STM32基本GPIO操作:点灯(库函数+寄存器)

    社团作业=_= 开发版上的LED灯负极连接在PB5口,正极串联一510Ω电阻后与3.3V相连 若开发板不带LED灯则需要自行连接,务必串联一个合适的电阻防止LED灯烧坏 零.一个有趣的延时函数 来自于 ...

  3. STM32 常用GPIO操作函数记录

    STM32读具体GPIOx的某一位是1还是0 /** * @brief Reads the specified input port pin. * @param GPIOx: where x can ...

  4. STM32外部中断具体解释

      一.基本概念 ARM Coetex-M3内核共支持256个中断,当中16个内部中断,240个外部中断和可编程的256级中断优先级的设置.STM32眼下支持的中断共84个(16个内部+68个外部), ...

  5. STM32(3)——外部中断的使用

    1 .简介 ARM Coetex-M3内核共支持256个中断,其中16个内部中断,240个外部中断和可编程的256级中断优先级的设置.STM32目前支持的中断共84个(16个内部+68个外部),还有1 ...

  6. stm32 外部中断学习

    今天我们看看STM32的外部中断实验. STM32 供 IO 口使用的中断线只有 16 个,但是 STM32 的 IO 口却远远不止 16 个,那么 STM32 是怎么把 16 个中断线和 IO 口一 ...

  7. STM32外部中断初理解

    PA0,PB0...PG0--->EXTI0 PA1,PB1...PG1--->EXTI1 ....... PA15,PB15...PG15--->EXTI15 以上为GPIO和中断 ...

  8. STM32学习笔记(九) 外部中断,待机模式和事件唤醒

    学会知识只需要不段的积累和提高,但是如何将知识系统的讲解出来就需要深入的认知和系统的了解.外部中断和事件学习难度并不高,不过涉及到STM32的电源控制部分,还是值得认真了解的,在本文中我将以实际代码为 ...

  9. stm32学习笔记——外部中断的使用

    stm32学习笔记——外部中断的使用 基本概念 stm32中,每一个GPIO都可以触发一个外部中断,但是,GPIO的中断是以组为一个单位的,同组间的外部中断同一时间只能使用一个.比如说,PA0,PB0 ...

随机推荐

  1. 两个对象值相同(x.equals(y)==true),但却可有不同的hashcode这句话对吗?

    1.这句话当然不对啦,请参看官方文档给出的解释! hashCode public int hashCode()返回该对象的哈希码值.支持此方法是为了提高哈希表(例如 java.util.Hashtab ...

  2. 进入编辑模式、vim命令模式、vim实践

    第4周第5次课(4月13日) 课程内容:5.5 进入编辑模式5.6 vim命令模式5.7 vim实践 5.5 进入编辑模式 所谓编辑模式就是进入到一个可以编辑文本文档的模式,常规的方式就是按小i进入编 ...

  3. 升级Xcode7&iOS9后,出现NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -980X)

    在info.plist里面添加如下内容即可: <key>NSAppTransportSecurity</key> <dict> <key>NSAllow ...

  4. Vue 一个注册页面有省市联动

    var vm = new Vue({ el: '#complete-info', data: { provinceList: [], selectedProvince: "", c ...

  5. 华为“方舟编译器”到底是啥?一文看懂TA如何让手机性能再突破【华为云技术分享】

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...

  6. 数据库Oracle 数字,字符,日期之间的相互转换

    数据类型转换分为俩种 . 隐式数据类型转换:当源数据的类型和目标数据的类型不同的时候,如果没有转换函数,就会发生隐式转换,也称自动转换. 对于直接赋值转换:  对于表达式赋值: 隐式转换的问题: 性能 ...

  7. [TimLinux] JavaScript 如何在AJAX中替换元素的图片

    1. 示例代码 /* * <img id="idTestImg" src="/static/test.png" /> */ var idTestIm ...

  8. HihoCoder1449 后缀自动机三·重复旋律6

    描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数.但是K不是固定的,小Hi想知道对 ...

  9. ZOJ 3195 Design the city (LCA 模板题)

    Cerror is the mayor of city HangZhou. As you may know, the traffic system of this city is so terribl ...

  10. WOE(证据权重)为何这样计算?

    更多大数据分析.建模等内容请关注公众号<bigdatamodeling> 先简单回顾一下WOE的含义.假设x是类别变量或分箱处理过的连续变量,含R个类别或分段,取值为{C1, ..., C ...