当前位置: 首页 > news >正文

普中STM32F103ZET6开发攻略(三)

接续上文:普中STM32F103ZET6开发攻略(二)-CSDN博客

点关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!!

目录

3.GPIO端口实验_中断控制:

3.1 实验目的:

3.2 实验环境:

3.3 实验原理

3.4 实验思路

3.5 实验代码

3.5.1 LED头、源

3.5.2 key头、源

3.5.3 beep头、源

3.5.4 exit头、源

3.5.5 实验现象

3.6 实验思考和拓展

3.6.1 如何增强中断服务函数中的按键消抖处理,使其更加可靠?

3.6.2 如何通过修改中断优先级,实现不同按键间的优先级控制?

3.6.3 如何在中断服务函数中实现按键长按和短按的区分?

3.6.4 如何实现组合按键功能(同时按下多个按键)?在中断方式下有何困难?

3.6.5 中断方式与轮询方式检测按键各有什么优缺点?在什么场景下选择中断方式更合适?

3.7 注意事项


3.GPIO端口实验_中断控制

3.1 实验目的:

  1. 熟悉STM32F10x微控制器的外部中断(EXTI)系统结构和基本操作

  1. 掌握STM32标准库函数对外部中断的配置方法

  1. 学会使用中断方式处理按键输入,控制LED和蜂鸣器的工作状态

  1. 理解中断优先级设置和中断服务函数编写原则

3.2 实验环境:

  • 开发板:STM32F103ZET6

  • IDE:Keil MDK 5 /Visual Studio

  • 调试工具:CMSIS-DAP

3.3 实验原理

1. *外部中断基本原理*

STM32的外部中断系统可以监测IO口电平变化,并在符合预设触发条件时执行中断服务函数。触发条件包括:

上升沿触发:电平从低变高时触发中断

下降沿触发:电平从高变低时触发中断

双边沿触发:电平变化时均触发中断 本实验中KEY_UP按键使用上升沿触发,KEY0、KEY1、KEY2按键使用下降沿触发。

2. *中断优先级控制*

STM32微控制器通过嵌套向量中断控制器(NVIC)管理各种中断请求。中断优先级分为抢占优先级子优先级

抢占优先级:决定是否可以打断当前正在执行的中断服务函数

子优先级:决定同时发生的同级抢占优先级中断的执行顺序

当抢占优先级相同时比较子优先级谁的数字更小谁先执行

3. *中断消抖技术*

机械按键在中断模式下仍需要消抖处理,通常的做法是在中断服务函数中加入短时延时,再次检测按键状态以确认。

4. *LED和蜂鸣器控制原理*

本实验板上的LED采用共阳极连接方式,GPIO输出低电平时LED点亮;蜂鸣器则是GPIO输出高电平时发声。

3.4 实验思路

3.5 实验代码

3.5.1 LED头、源

led.h

#ifndef __LED_H
#define __LED_H
​
#include "stm32f10x.h"
​
#define LED0_GPIO_PORT GPIOB
#define LED0_GPIO_PIN  GPIO_Pin_5 //LED0
​
#define LED1_GPIO_PORT GPIOE
#define LED1_GPIO_PIN  GPIO_Pin_5 //LED1
​
void LED_Init(void);
void LED0_TOGGLE(void);
void LED1_ON(void);
void LED1_OFF(void);
​
#endif
​

led.c

#include "led.h"
​
void LED_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;// 开启 GPIOB 和 GPIOE 的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE);
​//配置PB5(LED0)GPIO_InitStructure.GPIO_Pin = LED0_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(LED0_GPIO_PORT, &GPIO_InitStructure);
​//配置PE5(LED1)GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);
​//低电平输出GPIO_ResetBits(LED0_GPIO_PORT, LED0_GPIO_PIN);GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
}
​
void LED0_TOGGLE(void)
{LED0_GPIO_PORT->ODR ^= LED0_GPIO_PIN;
}
​
void LED1_ON(void)
{GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
}
​
void LED1_OFF(void)
{GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
}
​

3.5.2 key头、源

key.h

#ifndef __KEY_H
#define __KEY_H
​
#include "stm32f10x.h"
​
// GPIO 输入宏
#define PAin(n)  (GPIO_ReadInputDataBit(GPIOA, (1 << (n))))
#define PEin(n)  (GPIO_ReadInputDataBit(GPIOE, (1 << (n))))
​
// 按键 GPIO 宏定义
#define KEY0_PIN      GPIO_Pin_4
#define KEY1_PIN      GPIO_Pin_3
#define KEY2_PIN      GPIO_Pin_2
#define KEY_UP_PIN    GPIO_Pin_0
​
#define KEY_PORT      GPIOE
#define KEY_UP_PORT   GPIOA
​
#define KEY_UP        PAin(0)
#define KEY0          PEin(4)
#define KEY1          PEin(3)
#define KEY2          PEin(2)
​
// 返回值宏定义
#define KEY_UP_PRESS  1
#define KEY0_PRESS    2
#define KEY1_PRESS    3
#define KEY2_PRESS    4
​
void KEY_Init(void);
u8 KEY_Scan(u8 mode);
​
#endif
​

key.c

#include "key.h"
#include "delay.h"
​
void KEY_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE);
​// KEY_UP: 下拉输入GPIO_InitStructure.GPIO_Pin = KEY_UP_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;GPIO_Init(KEY_UP_PORT, &GPIO_InitStructure);
​// KEY0, KEY1, KEY2: 上拉输入GPIO_InitStructure.GPIO_Pin = KEY0_PIN | KEY1_PIN | KEY2_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(KEY_PORT, &GPIO_InitStructure);
}
​
u8 KEY_Scan(u8 mode)
{static u8 key_up = 1;
​if (mode) key_up = 1;
​if (key_up && (KEY_UP || !KEY0 || !KEY1 || !KEY2)){Delay_ms(10);  // 消抖key_up = 0;if (KEY_UP)   return KEY_UP_PRESS;if (!KEY0)    return KEY0_PRESS;if (!KEY1)    return KEY1_PRESS;if (!KEY2)    return KEY2_PRESS;}else if (!KEY_UP && KEY0 && KEY1 && KEY2){key_up = 1;}
​return 0;
}
​

3.5.3 beep头、源

bepp.h

#ifndef __BEEP_H
#define __BEEP_H
​
#include "stm32f10x.h"
​
#define BEEP_GPIO_PORT GPIOB
#define BEEP_GPIO_PIN  GPIO_Pin_8
​
void BEEP_Init(void);
void BEEP_ON(void);
void BEEP_OFF(void);
​
#endif
​

beep.c

#include "beep.h"
​
void BEEP_Init(void)
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//初始化各项参数:GPIO_InitStructure.GPIO_Pin = BEEP_GPIO_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(BEEP_GPIO_PORT, &GPIO_InitStructure);
​GPIO_ResetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN);  // 默认关闭蜂鸣器
}
​
void BEEP_OFF(void)
{GPIO_ResetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN);
}
​
void BEEP_ON(void)
{GPIO_SetBits(BEEP_GPIO_PORT, BEEP_GPIO_PIN);
}
​

3.5.4 exit头、源

exit.h

#ifndef __EXTI_H
#define __EXTI_H
​
#include "stm32f10x.h"  // 基础库头文件
​
void my_exti_int(void);  // 外部中断初始化函数声明
​
#endif
​

exit.c

#include "exti.h"
#include "key.h"
​
void my_exti_int(void)
{EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;// 开启 GPIO 时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // AFIO 复用功能RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 开启 GPIO 时钟:灯光RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
​// 配置 KEY_UP 为下拉输入模式(PA0)//GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = KEY_UP_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  // 下拉输入GPIO_Init(GPIOA, &GPIO_InitStructure);
​// 配置 KEY0~2 为上拉输入模式(PE2、PE3、PE4)GPIO_InitStructure.GPIO_Pin = KEY0_PIN | KEY1_PIN | KEY2_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(GPIOE, &GPIO_InitStructure);
​// GPIO 映射到 EXTI 通道GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // KEY_UP -> EXTI0GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4); // KEY0 -> EXTI4GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3); // KEY1 -> EXTI3GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2); // KEY2 -> EXTI2
​// 配置 EXTI0(KEY_UP)EXTI_InitStructure.EXTI_Line = EXTI_Line0;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  // 上升沿触发EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能特定的 EXTI 中断线EXTI_Init(&EXTI_InitStructure);
​// 配置 EXTI2(KEY2)EXTI_InitStructure.EXTI_Line = EXTI_Line2;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  // 下降沿触发EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);
​// 配置 EXTI3(KEY1)EXTI_InitStructure.EXTI_Line = EXTI_Line3;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  // 下降沿触发EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);
​// 配置 EXTI4(KEY0)EXTI_InitStructure.EXTI_Line = EXTI_Line4;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  // 下降沿触发EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure);
​// 配置 NVIC 中断优先级// EXTI0_IRQnNVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
​// EXTI2_IRQnNVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
​// EXTI3_IRQnNVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
​// EXTI4_IRQnNVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);
​
}
​

分析:

在 STM32 系列微控制器中,GPIO 引脚映射到 EXTI(外部中断 / 事件控制器)通道时,不同的 EXTI 通道具有以下区别:

通道编号与引脚限制

STM32 的 EXTI 通道分为两种类型:

  • 0-15 号通道:每个通道对应一组 GPIO 引脚中的

    同一位号。

    • 例如:EXTI0 只能连接到所有 GPIO 端口的Pin0(如 PA0、PB0、PC0 等)。

    • 同一时刻,每个通道只能映射一个 GPIO 引脚

  • 16-22 号通道:用于特殊功能(如 RTC 闹钟、USB 唤醒等),与 GPIO 无关。

总结

  • 通道编号决定中断函数:不同通道对应不同的中断服务函数。

  • 同一通道只能映射一个引脚:例如EXTI0不能同时连接 PA0 和 PB0。

  • 触发方式和优先级独立配置:每个通道可单独设置触发条件和中断优先级。

通过合理分配 EXTI 通道,你可以高效处理多个按键的中断事件。

 EXTI_InitStructure.EXTI_LineCmd = ENABLE;`EXTI_LineCmd = ENABLE`是启用外部中断功能必不可少的一步。只有完成了这一步,当 GPIO 引脚出现电平变化时,才会触发相应的中断服务函数。

3.5.5 实验现象
  1. DS0指示灯会不断闪烁(200ms翻转一次)

  1. 按下KEY_UP键,DS1指示灯点亮

  1. 按下KEY2键,DS1指示灯熄灭

  1. 按下KEY1键,蜂鸣器发声

  1. 按下KEY0键,蜂鸣器停止发声

3.6 实验思考和拓展

3.6.1 如何增强中断服务函数中的按键消抖处理,使其更加可靠?

改进方案

  1. 硬件消抖 + 软件消抖结合

    • 硬件:添加 0.1μF 电容到按键两端,滤除高频抖动

    • 软件:采用定时器消抖,避免阻塞中断服务函数

  2. 状态机消抖

    • 将按键状态分为:按下抖动→稳定按下→释放抖动→稳定释放

    • 使用定时器确认状态变化的稳定性

    • 基本原理:

      状态机消抖将按键的整个生命周期划分为多个稳定状态过渡状态,利用定时器对每个状态的持续时间进行验证,只有当信号在足够长的时间内保持稳定时,才认为状态发生了有效变化。

    • 典型状态划分:

      1. IDLE(空闲状态):按键未被按下,引脚保持高电平。

      2. PRESS_DEBOUNCE(按下抖动):检测到引脚电平下降,进入消抖验证。

      3. PRESSED(稳定按下):确认按键已稳定按下。

      4. RELEASE_DEBOUNCE(释放抖动):检测到引脚电平上升,进入释放消抖。

3.6.2 如何通过修改中断优先级,实现不同按键间的优先级控制?

实现步骤

  1. 配置 NVIC 优先级分组

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 2位抢占+2位子优先级
  2. 设置不同的抢占优先级

    // 紧急按键(如复位)
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 最高抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    ​
    // 普通按键
    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 次高抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  3. 优先级规则

    • 抢占优先级高的中断可打断正在执行的低优先级中断

    • 抢占优先级相同的中断按子优先级排序,但不能互相打断。子优先级数字越小,优先级就越高~~~

3.6.3 如何在中断服务函数中实现按键长按和短按的区分?

方案设计

  1. 时间阈值法

    • 按下时记录时间戳,释放时计算持续时间

    • 超过 2 秒为长按,否则为短按

  2. 示例代码:

    static uint32_t pressTime = 0;
    static bool longPressDetected = false;
    ​
    void EXTI0_IRQHandler(void) {if(EXTI_GetITStatus(EXTI_Line0) != RESET) {if(KEY_PIN == 0) { // 按下pressTime = HAL_GetTick();longPressDetected = false;HAL_TIM_Base_Start_IT(&htim2); // 启动长按检测定时器} else { // 释放HAL_TIM_Base_Stop_IT(&htim2);if(!longPressDetected) {ShortPressCallback(); // 短按处理}}EXTI_ClearITPendingBit(EXTI_Line0);}
    }
    ​
    // 长按检测定时器回调
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {if(htim == &htim2 && KEY_PIN == 0) {if(HAL_GetTick() - pressTime > 2000) { // 2秒长按longPressDetected = true;LongPressCallback();HAL_TIM_Base_Stop_IT(&htim2);}}
    }

3.6.4 如何实现组合按键功能(同时按下多个按键)?在中断方式下有何困难?

组合按键实现

  1. 轮询方式

    void ScanKeyCombination(void) {static uint8_t keyState = 0;keyState = (KEY1 << 0) | (KEY2 << 1);switch(keyState) {case 0b00: Key1AndKey2Pressed(); break;case 0b01: Key1Pressed(); break;case 0b10: Key2Pressed(); break;}
    }
  2. 中断方式的困难

    • 时序同步问题:多个按键的中断可能不同时触发

    • 抖动叠加:多个按键同时按下时抖动可能互相影响

    • 解决方案:

      • 检测到第一个按键中断时启动定时器,在定时窗口内检测其他按键

      • 使用状态机记录各按键状态变化

3.6.5 中断方式与轮询方式检测按键各有什么优缺点?在什么场景下选择中断方式更合适?
特性中断方式轮询方式
响应速度即时响应,不受主程序影响依赖扫描周期,可能有延迟
CPU 占用空闲时不占用 CPU持续占用 CPU(即使无按键操作)
资源消耗需要配置 NVIC、EXTI 等资源仅需 GPIO 读取
实现复杂度较高(需处理中断优先级、抖动)较低(简单循环检测)
多按键处理适合少量紧急按键适合大量按键同时检测
适用场景实时性要求高(如安全关键系统)按键数量多、响应时间要求宽松

推荐使用中断的场景

  • 低功耗设计(仅在按键操作时唤醒 CPU)

  • 紧急停机、复位等关键功能

  • 要求立即响应的人机交互(如游戏控制器)

3.7 注意事项

(1) 外部中断的触发方式需要与按键接线方式一致(KEY_UP用上升沿触发,其他按键用下降沿触发)

(2) 中断服务函数中必须清除中断标志位,否则会造成中断反复触发

(3) 中断服务函数应尽量简短,避免长时间占用CPU

(4) 使用标准库函数时,需要注意头文件的包含和依赖关系

(5) GPIO和外部中断初始化前必须先使能相应的外设时钟

(6) 避免在中断服务函数中使用过长的延时函数

文章有写的不当的地方,欢迎在评论区中指正修改。如果感觉文章实用对你有帮助,欢迎点赞收藏和关注,你的点赞关注就是我动力,大家一起学习进步。

有不懂的可以在评论区里提出来哟,博主看见后会及时回答的。

相关文章:

  • 什么是 /proc/buddyinfo
  • redis缓存常见问题
  • 12.7 LangChain实战:1.2秒响应!用LCEL构建高效RAG系统,准确率提升41%
  • 力扣 88.合并两个有序数组
  • vscode配置lua
  • PowerShell脚本编程基础指南
  • 《认知觉醒》第二章——驯服你的“脑内大象”:理智、本能与情绪的共生之道
  • 【Harmony OS】数据存储
  • Modbus转Ethernet IP网关助力罗克韦尔PLC数据交互
  • 项目目标和期望未被清晰传达,如何改进?
  • 【计算机网络】第七章 运输层
  • 动态规划-数位DP
  • 【学习笔记】深度学习-过拟合解决方案
  • 基于Halcon深度学习之分类
  • 【bpmn.js 使用总结】最简单实现Palette
  • 在Mathematica中实现Newton-Raphson迭代
  • 从零打造AI面试系统全栈开发
  • 生成JavaDoc文档
  • [Java 基础]运算符,将盒子套起来
  • Qiskit:量子计算模拟器
  • 太原网站上排名/百度在线问答
  • 北京中心网站建设/刷移动关键词优化
  • 北京网站开发公司有哪些/线上如何推广自己的产品
  • 风铃网站具体是做那方面的/优化关键词排名哪家好
  • 科泉网站/品牌全网推广
  • 做网站要源代码/seo软件代理