0基础入门嵌入式记录:STM32按键中断控制LED项目总结
本篇为个人零基础学习 STM32 点灯项目的复盘与记录旨在梳理自己的理解与踩坑过程并非专业的系统教学教程。本人也是初学阶段文中若有表述不严谨或理解偏差的地方欢迎各位前辈和读者批评指正共同学习进步。对芯片引脚和运作方式的简单理解在第一次搭建硬件前一定要看清它的引脚编号不要接错了。在运作中实际连线与代码是一一对应的。在运作时注意记得将GND引脚接地3.3V引脚接在面包板正极。在芯片上电时引脚默认处于“高阻悬空”的状态可以粗略视为“未通电”状态所以每次在新建工程时代码中一定要编写GPIO配置函数为芯片“通电”。代码运行时一般由芯片CPU处理。在使用stm 32前注意观察引脚丝印新建工程前要记得将gnd引脚接地3.3伏引脚接正 芯片才能正常运行硬件搭建硬件电路图如图。我选择将按键连接在PB1和PB11另一端接地LED接在PA1和PA2另一端接面包板正极。硬件连接图在连接芯片和STLINK时要注意接口是否一一对应如果连接有错误的话keil5是无法新建工程的。STM32的接口是SWCLK、SWDIO、GND、3.3V、5.5V。注意观察STLINK和STM32上的丝印准确连接LED的两种接法在这里LED可以有两种接入方法。1.低电平点亮灌电流这种方法是将LED连接在面包板3.3V电源和芯片引脚。当初始设置为低电平函数ResetBits时引脚初始输出低电平此时LED正极连接在面包板正极存在压差所以当运行代码时LED初始是点亮的状态。当初始设置为高电平函数SetBits时引脚初始输出高电平此时LED正极连接在面包板正极LED两边电压都是3.3V没有压差所以当运行代码时LED初始是熄灭的状态。低电平连接2.高电平点亮拉电流这种方法是将LED连接在面包板负极和芯片引脚。当初始设置为低电平函数ResetBits时引脚初始输出低电平此时LED正极连接在面包板负极LED两极都为0V没有压差所以当运行代码时LED初始是熄灭的状态。当初始设置为高电平函数SetBits时引脚初始输出高电平此时LED正极连接在面包板负极存在压差所以当运行代码时LED初始是点亮的状态。在搭建硬件电路时如果需要确定LED的初始状态一定要注意元件的连接方式和高低电平函数的选定。高电平连接代码解析完整代码简单注释#include stm32f10x.h//表示不在电脑自带库 表示在电脑自带库 // 宏定义 将LED_BLUE_PIN替换成GPIO_Pin_1 并不是真的定义了函数 #define LED_BLUE_PIN GPIO_Pin_1 #define LED_GREEN_PIN GPIO_Pin_2 #define KEY1_PIN GPIO_Pin_1 #define KEY2_PIN GPIO_Pin_11 // 全局标志位 volatile防止出错 使用场景中断服务函数 硬件外设寄存器 多任务系统 volatile uint8_t g_key1_pressed 0; //uint8_t嵌入式里专门使用的类型 代表无符号8位整数 g_前缀 代表全局变量global) volatile uint8_t g_key2_pressed 0; // 函数声明 int---有返回值的函数 void无返回值的函数 void GPIO_Configuration(void);//void代表无需参数 无需返回值 在嵌入式中只使用void void EXTI_Configuration(void); void NVIC_Configuration(void); void Delay_ms(uint32_t); int main(void) { //函数调用 GPIO_Configuration(); EXTI_Configuration(); NVIC_Configuration(); Delay_ms(20); // 初始灭灯 ResetBits 低电平函数输出低电平0v高电平函数SetBits //调用低电平函数 传入参数 控制特定硬件 GPIO_ResetBits(GPIOA, LED_BLUE_PIN); GPIO_ResetBits(GPIOA, LED_GREEN_PIN); while (1) { // --- 处理按键1中断标志 --- if (g_key1_pressed) { Delay_ms(20); // 消抖 if (GPIO_ReadInputDataBit(GPIOB, KEY1_PIN) Bit_RESET) // 确认按下 Bit_RESET在标准库里为低电平 { // 翻转蓝色LED if (GPIO_ReadOutputDataBit(GPIOA, LED_BLUE_PIN)) GPIO_ResetBits(GPIOA, LED_BLUE_PIN); else GPIO_SetBits(GPIOA, LED_BLUE_PIN); // 确认按键松开防止连触发 while (GPIO_ReadInputDataBit(GPIOB, KEY1_PIN) Bit_RESET); } g_key1_pressed 0; // 处理完清除标志 } // --- 处理按键2中断标志 --- if (g_key2_pressed) { Delay_ms(20); // 消抖 if (GPIO_ReadInputDataBit(GPIOB, KEY2_PIN) Bit_RESET) // 确认按下 { // 翻转绿色LED if (GPIO_ReadOutputDataBit(GPIOA, LED_GREEN_PIN)) GPIO_ResetBits(GPIOA, LED_GREEN_PIN); else GPIO_SetBits(GPIOA, LED_GREEN_PIN); while (GPIO_ReadInputDataBit(GPIOB, KEY2_PIN) Bit_RESET); } g_key2_pressed 0; // 清除标志 } } } // ---- GPIO配置 ---- void GPIO_Configuration(void) { //配置函数必写抬头73-75 GPIO_InitTypeDef GPIO_InitStructure;//空白任务单 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);//打开电闸 // LED推挽输出 GPIO_InitStructure.GPIO_Pin LED_BLUE_PIN | LED_GREEN_PIN;//定义“任务对象” GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP;//定义方法推挽输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;//定义响应速度 GPIO_Init(GPIOA, GPIO_InitStructure);//定义对应的GPIO口 // 按键上拉输入 GPIO_InitStructure.GPIO_Pin KEY1_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU;//定义方法上拉输入 GPIO_Init(GPIOB, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin KEY2_PIN; GPIO_Init(GPIOB, GPIO_InitStructure); } // ---- EXTI 配置 ---- void EXTI_Configuration(void) { EXTI_InitTypeDef EXTI_InitStructure; // 将PB1映射到EXTI1PB11映射到EXTI11 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource11); // 配置EXTI Line 1 (按键1) EXTI_InitStructure.EXTI_Line EXTI_Line1; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; // 下降沿触发按下 EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); // 配置EXTI Line 11 (按键2) EXTI_InitStructure.EXTI_Line EXTI_Line11; EXTI_Init(EXTI_InitStructure); } // ---- NVIC 配置 ---- void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; // 优先级分组抢占2位响应2位 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 按键1中断EXTI1 NVIC_InitStructure.NVIC_IRQChannel EXTI1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); // 按键2中断EXTI15_10因为EXTI11属于这个组 NVIC_InitStructure.NVIC_IRQChannel EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 2; NVIC_Init(NVIC_InitStructure); } // ---- 中断服务函数 ---- void EXTI1_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line1) ! RESET) { g_key1_pressed 1; // 仅置标志不做延时 EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志 } } void EXTI15_10_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line11) ! RESET) { g_key2_pressed 1; // 仅置标志 EXTI_ClearITPendingBit(EXTI_Line11); } } void Delay_ms(uint32_t ms) { uint32_t i,j; for(i 0; i ms; i) for(j 0; j 8000; j); }中断函数注释EXTI一般而言代码由芯片的CPU去处理。而中断函数的作用是让其他元件代替CPU进行监测从而节约CPU的算力。所以显然中断函数的使用场景是当CPU的算力需要被放在其他更优先级的任务时。中断函数的运作流程是对信号进行监测一旦接收到有效信号就向CPU发送一个中断请求让CPU响应信号。因此中断函数需要设定对象和发出中断请求的具体信号。在本例中中断为中断配置函数、中断服务函数、中断信号优先级配置函数NVIC。中断配置中断配置函数是作用于硬件部分的函数。将所需硬件设定为中断对象设定中断请求信号。// ---- EXTI 配置 ---- void EXTI_Configuration(void) { //空白任务单 EXTI_InitTypeDef EXTI_InitStructure; // 将GPIOB_PB1设定为EXTI1的对象GPIOB_PB11设定为EXTI11的对象 GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource11); //将按键1、2下降设定为具体信号 // 配置EXTI Line 1 (按键1) EXTI_InitStructure.EXTI_Line EXTI_Line1; EXTI_InitStructure.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger EXTI_Trigger_Falling; // 下降沿触发按下 EXTI_InitStructure.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStructure); // 配置EXTI Line 11 (按键2) EXTI_InitStructure.EXTI_Line EXTI_Line11; EXTI_Init(EXTI_InitStructure); }中断服务中断服务是作用于CPU处理逻辑的函数。// ---- 中断服务函数 ---- void EXTI1_IRQHandler(void) { //当CPU接收到中断请求时标志并到main运行g_key1_pressed 1 if (EXTI_GetITStatus(EXTI_Line1) ! RESET) { g_key1_pressed 1; // 仅置标志不做延时 EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志 } } void EXTI15_10_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line11) ! RESET) { g_key2_pressed 1; // 仅置标志 EXTI_ClearITPendingBit(EXTI_Line11); } }请注意在EXTI11的中断服务函数时函数名为EXTI15_10_IRQHandler而不是EXTI11_IRQHandler。这是由于ST的硬件设计将中断线10到15打包成一个共同的中断入口。所以处理按键2Line11时必须在进入 EXTI15_10_IRQHandler 后先用 if (EXTI_GetITStatus(EXTI_Line11) ! RESET) 确认到底是 Line11 触发的信号再执行置位动作。中断优先级配置中断优先级配置函数是用于设定CPU处理多个中断信号时的先后次序。// ---- NVIC 配置设定CPU响应中断的优先级次序 ---- void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; // 1. 设置分组规则。每组中断信号有4位寄存器这里配置为“抢占优先级”占2位“响应优先级”占2位。 // 这意味着我们填写的优先级数字只能在 0~3 之间。数字越小级别越高。 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // ---------- 配置按键1 中断优先级 ---------- // 选择中断通道EXTI1 NVIC_InitStructure.NVIC_IRQChannel EXTI1_IRQn; // 抢占优先级设为1。如果和其他高优先级的任务冲突数字越小越优先处理。 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; // 响应优先级设为1。当多个同等级中断同时发生时响应数字越小排队排得越靠前。 NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; // 使能中断 NVIC_Init(NVIC_InitStructure); // 提交配置 // ---------- 配置按键2 中断优先级 ---------- // 注意因为EXTI11硬件上属于EXTI15_10这个共享中断组所以通道必须选 EXTI15_10_IRQn NVIC_InitStructure.NVIC_IRQChannel EXTI15_10_IRQn; // 抢占优先级同为1。由于和按键1同级意味着它们不能互相打断对方。 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; // 响应优先级设为2。如果两个按键同时被按按键1响应优先级1会比按键2响应优先级2被CPU先处理。 NVIC_InitStructure.NVIC_IRQChannelSubPriority 2; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); }

相关新闻

Windows免安装版MySQL8详细安装配置教程(ZIP 压缩包版)

Windows免安装版MySQL8详细安装配置教程(ZIP 压缩包版)

1 前言很多小伙伴在安装MySQL时,习惯去找MSI安装包一路“下一步”,但这种方式会在系统里写入大量注册表信息,卸载不干净时还容易留下各种残留,导致重装失败。其实MySQL官方一直提供ZIP压缩包版(也叫noinstall ZIP Arch…

2026/10/15 15:22:07 阅读更多 →
atom-in-orbit调试技巧:在浏览器中调试Atom的完整方法

atom-in-orbit调试技巧:在浏览器中调试Atom的完整方法

atom-in-orbit调试技巧:在浏览器中调试Atom的完整方法 【免费下载链接】atom-in-orbit Putting Atom in the browser 项目地址: https://gitcode.com/gh_mirrors/at/atom-in-orbit 想要在浏览器中运行和调试Atom编辑器吗?atom-in-orbit项目为您提…

2026/10/15 2:33:51 阅读更多 →
官宣|AT Work 正式开源,中小团队免费部署的一站式云研发工作台

官宣|AT Work 正式开源,中小团队免费部署的一站式云研发工作台

中小研发团队的日常,大概率是这样的: 需求写在 Jira 或飞书多维表里,代码在 GitLab,文档散落在语雀和腾讯文档,文件靠微信传来传去,沟通靠企业微信或钉钉——每天在 5 款以上工具之间来回切换,…

2026/9/29 17:20:08 阅读更多 →

最新新闻

Grad-CAM原理解析与工业级实现指南

Grad-CAM原理解析与工业级实现指南

1. 这不是“画个热力图”那么简单:Grad-CAM到底在解决什么问题?Grad-CAM可视化,这五个字最近在模型可解释性讨论里出现频率极高,但很多人一上手就卡在“为什么我的热力图全是噪点”“为什么关键区域没亮起来”“为什么不同层的图完…

2026/10/15 15:22:08 阅读更多 →
Vue3源码解析:从effect看watch与computed的设计与调度

Vue3源码解析:从effect看watch与computed的设计与调度

Vue 3 的watch和computed这两条 API,可能是绝大多数项目里使用率最高的响应式能力,但也是误解最多的一对。平时查资料,大家见到的多半是用法层面的总结:computed 用于同步派生状态、watch 用于执行副作用、能用 computed 就别用 w…

2026/10/15 15:22:08 阅读更多 →
碰碰好评卡功能模块开发实战指南 附全流程实现思路与踩坑汇总

碰碰好评卡功能模块开发实战指南 附全流程实现思路与踩坑汇总

碰碰好评卡功能模块开发实战指南 附全流程实现思路与踩坑汇总 在电商、本地生活、上门服务等各类线上业务场景中,用户评价是影响商品/服务转化的核心指标,碰碰好评卡作为引导用户主动完成评价的核心功能模块,需要兼顾触发灵活性、多端兼容性、…

2026/10/15 15:22:08 阅读更多 →
ZooKeeper核心机制精讲:数据模型、Watch与Leader选举

ZooKeeper核心机制精讲:数据模型、Watch与Leader选举

1. 为什么分布式系统里必须有ZooKeeper这样的角色早些年我做分布式系统的时候,最头疼的事情不是接口怎么写,而是几台机器之间怎么“商量事儿”。比如一个服务一共开了三个实例,数据库连接池一共 60 个连接,分配 30 个给 A、20 个给…

2026/10/15 15:22:08 阅读更多 →
okhttputils 使用指南:基于 OkHttp 3.3.1 的 Android 网络请求封装库完整实战

okhttputils 使用指南:基于 OkHttp 3.3.1 的 Android 网络请求封装库完整实战

网络移动开发 【免费下载链接】okhttputils [停止维护]okhttp的辅助类 项目地址: https://gitcode.com/gh_mirrors/ok/okhttputils 点击查看 免费下载 本文是一份围绕开源仓库 okhttputils 的完整使用指南。okhttputils 是一套对 Square OkHttp 的辅助封装类&#…

2026/10/15 15:22:08 阅读更多 →
基于单片机的电阻炉温控系统:DS18B20与PID调参实战

基于单片机的电阻炉温控系统:DS18B20与PID调参实战

简介:基于单片机的电阻炉温控制系统设计,是一套面向电子、自动化专业学生及嵌入式初学者的完整课程设计/毕设方案,涵盖DS18B20数字测温、单片机温度曲线动态跟踪和上位机通信三大环节。方案从硬件原理图、下位机控制程序到VB上位机界面均有实…

2026/10/15 15:21:06 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →