0基础入门嵌入式记录:STM32按键中断控制LED项目总结

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); }