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

IoT/HCIP实验-4/单片机基础实验(LCD/LED/按键操作/GPIO/EXTI中断服务)

文章目录

  • 概述
  • LCD使用
    • LCD 驱动程序
    • LCD驱动典型接口
    • 板载LCD屏幕显示
  • GPIO 概要
  • LED使用
    • LED 电路
    • LED闪烁
  • GPIO 轮询/扫描板载按键
    • 实验编码
    • 按键GPIO工作模式
    • 实验效果说明
    • GPIO 扫描的弊端
  • GPIO中断服务/EXTI检测板载按键
    • STM32中断服务函数
    • 启动文件
    • LiteOS中断注册机制
    • 中断号定义
    • 定义按键中断函数并注册
    • 清除 EXTI 挂起标志位
  • 总结

概述

本实验控制LCD屏幕和LED灯闪烁,了解开发板工作原理。包含以下实验目的:
1、实现板载LCD屏幕显示、学会板载LED灯闪烁
2、GPIO扫描检查板载按键控制LED(GPIO基础知识)
3、EXTI检测板载按键控制LED(LiteOS中断注册机制等)
4、学会看简单的目标板子原理图

@HISTORY
请先阅读 #<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 LiteOS Studio + GCC + JLink>#、#<IoT/HCIP实验-3/LiteOS操作系统内核实验Part1>#,了解IDE和LiteOS的基础使用。

LCD使用

LCD(Liquid Crystal Display,液晶显示器)基于液晶材料的光学特性实现图像显示。液晶介于固态与液态之间,其分子排列可通过电场控制。当施加电压时,液晶分子有序排列,允许光线通过;未通电时分子无序排列,阻挡光线。通过调节电压大小,可精确控制每个像素的透光量,从而形成图像。通过彩色滤光片(RGB三原色)和背光源(如LED)的组合实现全彩显示。每个像素由红、绿、蓝子像素构成,通过混合比例呈现不同颜色。

LCD 驱动程序

小熊派开发板板载1.3寸LCD屏幕,分辨率240*240,色彩深度16bit,使用ST7789V2液晶控制器。
在这里插入图片描述
TFT-LCD(Thin-Film Transistor Liquid Crystal Display) 是薄膜晶体管液晶显示屏 的缩写,属于有源矩阵液晶显示器(AM-LCD) 的一种。其核心特点是通过在玻璃基板上集成薄膜晶体管(TFT)阵列,每个像素对应一个TFT,作为开关控制液晶分子的电压,实现对每个像素的独立控制,从而显著提升显示性能。

这里使用的LCD驱动芯片ST7789V2,是STMicroelectronics推出的 TFT-LCD控制器,专为中小尺寸LCD屏优化。支持 SPI接口通信,具备低功耗、高刷新率特性,适合嵌入式设备使用。开发板通过 SPI2接口与ST7789V2通信,主要使用 SCLK(PB13)和 MOSI(PC3)引脚。控制引脚包括 LCD_RESET(PC7)、LCD_POWER(PB15)和 LCD_WR_RS(PC6),分别用于复位、电源管理和命令/数据切换。

开发板配套的驱动库封装了底层函数(如LCD_ShowCharStr显示字符、LCD_Show_Image显示图片),开发者无需直接操作寄存器。示例代码中通过SPI发送像素数据,支持绘制图形(如直线、圆形)和动态刷新等。
在这里插入图片描述

LCD驱动典型接口

/*** @brief	显示字符串* @param   x,y		起点坐标* @param   width	字符显示区域宽度* @param   height	字符显示区域高度* @param   size	字体大小* @param   p		字符串起始地址* @return  void*/
void LCD_ShowString(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t size, char *p)/*** @brief	画一个圆* @param   x0,y0	圆心坐标* @param   r       圆半径* @return  void*/
void LCD_Draw_Circle(uint16_t x0, uint16_t y0, uint8_t r)/*** @brief	显示图片* @remark	Image2Lcd取模方式:	C语言数据/水平扫描/16位真彩色(RGB565)/高位在前		其他的不要选* @param   x,y		起点坐标* @param   width	图片宽度* @param   height	图片高度* @param   p		图片缓存数据起始地址* @return  void*/
void LCD_Show_Image(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t *p)

ST7789V2的默认坐标系原点为左上角(多数LCD驱动芯片采用此标准),x轴向右延伸,y轴向下延伸。参数x和y的单位是像素坐标,每个整数值对应一个物理像素点。对于分辨率为 240×240 的屏幕,x和y的有效范围应为 0到239。若传入x=240或y=240,驱动芯片可能忽略超限部分,或触发未定义行为。我们仅重点谈谈LCD_ShowString函数,
函数参数 @字体尺寸(size参数),
不同字号对应不同的像素尺寸(如12px字体高度为12像素),需确保height参数大于等于字体实际高度。还要注意的是,本驱动仅支持16/24/32号字体,参见LCD_ShowChar定义。width和height定义了每个字符的显示区域,若文本超出区域宽度,需手动处理换行或省略。

板载LCD屏幕显示

int standard_app_demo_main() {
#if TRY_CODE_LCD//清除LCD显示LCD_Clear(BLACK);//设置字体颜色/画笔颜色POINT_COLOR = RED;//显示字符串 /字号24LCD_ShowString(10, 10, 200, 16, 24, "Welcome to LiteOS");//尝试显示中文LCD_ShowString(10, 50, 200, 16, 24, "端午节快乐");//尝试显示中文LCD_ShowString(10, 90, 200, 16, 24, "CSDN 大河qu");//LCD_ShowString(10, 130, 200, 16, 24, "ABCDEF");//故意制造重叠位置LCD_ShowString(10, 145, 200, 16, 24, "HIJKLMNOPQ");
#endif...
}

上述代码运行结果,
在这里插入图片描述
如上,可以观察到,
1、如果显示起始坐标或字号或区域设定不合理,可能导致显示上的重叠。
2、小熊派板载的ST7789V2液晶控制器本身不直接支持中文显示,但这并非驱动芯片的硬件缺陷,而是软件层的中文字库和渲染逻辑缺失所致。理论上可以获取开源字库,扩展驱动函数,以支持中文。

GPIO 概要

GPIO mode 引脚工作模式,分为四类八种,每个GPIO管脚都具有部分模式功能,
在这里插入图片描述
8种具体工作模式如下,大体了解吧,我还看不太懂它们各种的电路图☺,
在这里插入图片描述
一些常见场景下的,GPIO模式选择推荐,
在这里插入图片描述

HAL 层GPIO结构定义,stm32f1xx_hal_gpio.h #

/** @brief GPIO Init structure definition   */ 
typedef struct {uint32_t Pin;       /*!< Specifies the GPIO pins to be configured.This parameter can be any value of @ref GPIO_pins_define */uint32_t Mode;      /*!< Specifies the operating mode for the selected pins.This parameter can be a value of @ref GPIO_mode_define */uint32_t Pull;      /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.This parameter can be a value of @ref GPIO_pull_define */uint32_t Speed;     /*!< Specifies the speed for the selected pins.This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;

LED使用

LED(Light Emitting Diode,发光二极管)是一种通过半导体PN结电致发光将电能直接转化为光能的电子元件。当正向电压施加于PN结时,电子(N区)与空穴(P区)在结区复合,释放能量,能量以光子形式释放(波长由半导体材料能隙决定)。

LED 电路

在这里插入图片描述

LED闪烁

//LED闪烁任务
static int led_task() {GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.Pin = LED_Pin;GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStructure.Pull = GPIO_NOPULL;GPIO_InitStructure.Speed = GPIO_SPEED_LOW;HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);#if TRY_CODE_LED_TWNKwhile (1) {HAL_GPIO_WritePin(LED_GPIO_Port/*GPIOC*/, LED_Pin, GPIO_PIN_SET);osal_task_sleep(1*1000);HAL_GPIO_WritePin(LED_GPIO_Port/*GPIOC*/, LED_Pin, GPIO_PIN_RESET);osal_task_sleep(1*1000);//GPIO的操作函数并不是只有write和read,HAL_GPIO_TogglePin等也是可以的,其用于翻转 GPIO 引脚电平状态。}
#endif
}

LED等闪烁效果如下,
在这里插入图片描述

GPIO 轮询/扫描板载按键

如下是两个按键的电路原理图,它们并不与LED直接相连,而是作为 “外部中断信号” 使用。
在这里插入图片描述

实验编码

//LED工作任务
static int led_task() {GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.Pin = LED_Pin;GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStructure.Pull = GPIO_NOPULL;GPIO_InitStructure.Speed = GPIO_SPEED_LOW;HAL_GPIO_Init(GPIOC, &GPIO_InitStructure);#if TRY_CODE_GPIO_SCANwhile (1) {//查询按键1低电平 /即按下if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET) {printf("check key1 low level\r\n");HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);}//查询按键1低电平 /即按下else if (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET) {printf("check key2 low level\r\n");HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);}else {//do nothingprintf("dead loop..\r\n");//示例程序中无此代码 //sleep会导致识别按键状态的过程变慢/甚至错过osal_task_sleep(100);}}
#endifreturn 0;
}

按键GPIO工作模式

在 LiteOS_Lab_HCIP\targets\STM32L431_BearPi\Src 小熊派提供的源码 gpio.c 文件中(CubeMX可生成此同名文件),可以看到按键对应的GPIO的初始化过程如下,

//从文件名/函数名均可以判断处此函数是MX生成的
void MX_GPIO_Init(void) {...GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin;GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;//工作在下降沿触发中断模式GPIO_InitStruct.Pull = GPIO_PULLUP;         //引脚内部配置上拉电阻HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}

按键对应的GPIO其工作模式为 GPIO_MODE_IT_FALLING(下降沿触发外部中断),这是一种扩展工作模式,是一种输入模式,专用于外部中断检测。当引脚检测到电平从高到低的变化(下降沿) 时,触发中断请求。典型应用包含,按键检测(按键按下时接地)、传感器信号跳变、低电平有效的硬件事件响应等。该模式下GPIO仍作为输入引脚,其电平状态可通过寄存器直接读取,如通过HAL_GPIO_ReadPin() 函数读取。中断触发仅自动置位标志位,不影响GPIO输入状态的实时性,即,本小节GPIO扫描功能实现的理论基础。

实验效果说明

这里就不贴图了,太费劲。我们只透过实验现象大体解释下源码。
1、在任何按键按下前,else分组被执行,dead loop…被持续打印。
2、当按下按键KEY1后,KEY1_Pin读取到低电平,check key1 low level 被打印一次,LED亮起来。
3、松开KEY1后,LED继续保持点亮,分支1也不会再进入(即check key1 low level不会打印)。此时dead loop…重新被持续打印,即KEY1和KEY2的GPIO,都不能检测到低电平。在KEY2按下前,LED将始终保持点亮状态不变。
4、当按下KEY2后,check key2 low level 打印一次,LED被熄灭。松开后,保持熄灭,并重新进入dead loop 分支。

GPIO 扫描的弊端

在示例代码中,分支3并没有sleep过程,必须要意识到,此时while死循环占用CPU,如果该任务优先级较高,则比她优先级低的任务将无法执行。如果向我那样加入sleep语句,虽解决上述问题,但会降低了Key按下或松开操作被识别到的灵敏度。如果睡100ms,那么最不幸运的情况下,就是100ms后,操作才能被识别到。当然对于普通人和普通按键,似乎是足够啦,一般不会出现扫描漏检。

GPIO中断服务/EXTI检测板载按键

在嵌入式开发中,EXTI 是 External Interrupt/Event Controller(外部中断/事件控制器) 的缩写,注意不是EXIT哈,是EXT-Interrupt的意思,HCIP实验手册中就大概率是写错了。它是微控制器中用于管理外部中断信号的硬件模块,负责检测GPIO引脚的电平变化(如按键按下/释放),并触发相应的中断服务程序。

STM32中断服务函数

嵌入式开发中,中断服务函数的名称并不是随意定的,ARM架构和芯片厂商都对此有规范。ARM架构层面主要规范核心中断的名称和优先级,芯片厂商主要规范外设中断的名称和优先级。开发者需以具体芯片的官方文档和启动代码为唯一依据,不可自行编造名称。
以ARM Cortex-M 系列为例,
ARM 内核定义了统一的核心中断名称,这些名称在 CMSIS-Core 标准中固化,适用于所有基于该内核的芯片。例如,系统异常,NMI_Handler、HardFault_Handler、SysTick_Handler 等,这些中断服务名称固定且必须与启动文件中的向量表一致。ARM 还规定了核心中断的编号(如 SysTick_IRQn = -1)和优先级寄存器结构,芯片厂商需在此框架下扩展。
外设中断名称由芯片厂商在 ARM 标准基础上定义,但遵循固定格式,需与启动文件中的中断向量表完全匹配。命名规则通常为 外设名 + 中断类型后缀,如 STM32 的 USART1_IRQHandler、TIM2_IRQHandler,NXP 的 LPUART0_IRQHandler 等 。

其实在HAL层也有一层约定,如,
HAL_CAN_RxFifo0MsgPendingCallback 是 STM32 HAL 库中预定义的固定命名回调函数,其该回调函数在 CAN 外设接收到新消息 且消息被存入 FIFO0 时触发,用于通知应用程序处理接收数据。该函数声明在…Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_can.h文件中,其函数实现要在用户代码中编写。
在这里插入图片描述
上述HAL接口的上层函数,

/*** @brief  Handles CAN interrupt request* @param  hcan pointer to a CAN_HandleTypeDef structure that contains*         the configuration information for the specified CAN.* @retval None*/
void HAL_CAN_IRQHandler(CAN_HandleTypeDef *hcan) {.../* Receive FIFO 1 message pending Callback */#if USE_HAL_CAN_REGISTER_CALLBACKS == 1/* Call registered callback*/hcan->RxFifo1MsgPendingCallback(hcan);#else/* Call weak (surcharged) callback */HAL_CAN_RxFifo1MsgPendingCallback(hcan);#endif /* USE_HAL_CAN_REGISTER_CALLBACKS */}...
}

呢?山猫好像又发现新大陆了!

在 STM32 HAL 库的 CAN 中断处理代码中,Call weak (surcharged) callback 和 Call registered callback 是两种不同的回调函数调用机制,其核心区别在于 回调函数的绑定方式和灵活性。
Call registered callback(调用注册的回调函数)
过 HAL_CAN_RegisterCallback() 函数,将用户自定义的回调函数指针动态绑定到 hcan->RxFifo1MsgPendingCallback 成员变量。当 USE_HAL_CAN_REGISTER_CALLBACKS == 1 时启用此模式。支持动态绑定、多CAN实例支持、具有更高的灵活性。
Call weak (overloaded) callback(调用弱定义的回调函数)
使用弱符号(weak) 定义的回调函数 HAL_CAN_RxFifo1MsgPendingCallback(),用户可在代码中重新实现该函数以覆盖默认行为。
在这里插入图片描述

启动文件

上述HAL_CAN_IRQHandler还不是芯片级别上的回调函数,她在CAN1_TX_IRQHandler、CAN1_RX0_IRQHandler、CAN1_RX1_IRQHandler、CAN1_SCE_IRQHandler等根源中断服务函数中调用。
在这里插入图片描述
这些函数直接对应STM32芯片的硬件中断向量表,在启动文件(如startup_stm32f40xx.s)中以弱符号([WEAK])形式声明。用户必须在项目中显式实现这些函数(通常位于stm32f4xx_it.c),否则会使用默认的弱实现(可能为空或触发断言错误具体中断函数的名称需参考芯片的启动文件(如 startup_stm32f103x.s),其中定义了完整的中断向量表。

在Keil工程中、在bearpi-iot_std-master小熊派IDE工程下,都是存在启动文件的。在LITEOS_LAB_HCIP源码中,并没有看到startup_stm32l431xx.s这样的启动文件,可能LiteOS Studio并不直接使用此文件。
我以目前手头项目下的启动文件(STM32F427,使用MX构建初始工程)为例,…MDK-ARM\startup_stm32f427xx.s,
在这里插入图片描述
startup_stm32f427xx.s 是 直接参与编译 的汇编文件,无需转换为 .h 或 .c 文件。它是 STM32 系列芯片启动时执行的第一段代码,负责初始化硬件环境并跳转到 C 语言的 main() 函数。而 los_hwi.lst 会被解析并生成对应的 C 头文件(如 los_hwi.h),LiteOS 将传统的启动代码(如堆栈初始化、中断向量表)整合到内核的 C 语言代码中,而非依赖汇编文件。

LiteOS中断注册机制

LiteOS是提供了中断处理函数注册接口接口的。其实前文我们已经提到过,HAL也是具有中断处理函数注册接口的,这极大提升了中断编程的灵活性。那么,LiteOS中是怎么实现的呢?它没有基于Hal的注册接口。

//导出接口
int osal_int_connect(int intnum, int prio, int mode, fn_interrupt_handle callback, void *arg) {int ret = -1;if((NULL != s_os_cb) &&(NULL != s_os_cb->ops) &&(NULL != s_os_cb->ops->int_connect)) {ret = s_os_cb->ops->int_connect(intnum, prio, mode, callback, arg);}return ret;
}//int_connect是如下函数的函数指针
static int __int_connect(int intnum, int prio, int mode, fn_interrupt_handle callback, void* arg) {...return LOS_HwiCreate((HWI_HANDLE_T)intnum, (HWI_PRIOR_T)prio,(HWI_MODE_T) mode, (HWI_PROC_FUNC)callback, (HWI_ARG_T)(uintptr_t)arg);
}

上述过程的核心函数是LOS_HwiCreate,函数名中Hwi 是 Hardware Interrupt(硬件中断) 的含义。接下来首先要注意的是 LOS_HwiCreate 在不同的架构(如msp430、arm)下有不同的实现,我们这里重点关注arm架构下的实现方式。
ARM架构下LOS_HwiCreate函数参数说明,
在这里插入图片描述
以定时器为例的一个简略地接口使用过程如下,

void Timer_Handler(uintptr_t arg) {// 执行定时任务(如更新计数器)*(volatile uint32_t*)arg += 1;  // 通过 arg 修改共享变量
}
// 注册定时器中断
LOS_HwiCreate(TIM2_IRQ, 0, OS_IRQ_MODE_LEVEL, Timer_Handler, (uintptr_t)&counter);

以ARM为例,函数实现在 LiteOS_Lab_HCIP\iot_link\os\liteos\arch\arm\arm-m\src\los_hwi.c文件中,

LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiCreate(HWI_HANDLE_T uwHwiNum, HWI_PRIOR_T usHwiPrio, HWI_MODE_T usMode, HWI_PROC_FUNC pfnHandler, HWI_ARG_T uwArg) {...m_pstHwiSlaveForm[uwHwiNum + VECTOR_IDX_OFFSET].pfnHandler = pfnHandler;m_pstHwiSlaveForm[uwHwiNum + VECTOR_IDX_OFFSET].pParm = (VOID*)uwArg;...
}

接下来我就开始找m_pstHwiSlaveForm全局变量是在哪里使用的!有点蒙,我表示没有完全理解。
在传统开发中,中断向量表(如 startup_stm32f427xx.s)直接定义在汇编文件中。而 LiteOS 将中断配置抽象为 los_hwi.lst,通过宏定义和代码生成机制动态构建中断处理逻辑,简化了底层硬件适配。编译时,los_hwi.lst 会被解析并生成对应的 C 头文件(如 los_hwi.h),最终链接到内核镜像中,增量编译的时候不会更新到 los_hwi.lst 文件。
在这里插入图片描述
就这样的,搞不下去啦实在,等到整理 #<IDE/IoT/搭建物联网(LiteOS)集成开发环境,基于 VSCode最新版本的开发环境># 这篇文章的时候再去探究吧。小熊派社区给的示例程序,似乎是使用了 startup_stm32f427xx.s 文件的,那个时候就要懂得,如何在gcc下使用启动文件了,也不用理解 LiteOS Studio 这套复杂(自己的菜导致)的启动过程了。

中断号定义

如下定义在 stm32l431xx.h 文件中,

typedef enum
{
/******  Cortex-M4 Processor Exceptions Numbers ****************************************************************/...SysTick_IRQn                = -1,     /*!< 15 Cortex-M4 System Tick Interrupt                                */
/******  STM32 specific Interrupt Numbers **********************************************************************/WWDG_IRQn                   = 0,      /*!< Window WatchDog Interrupt                                         */...                                       */EXTI2_IRQn                  = 8,      /*!< EXTI Line2 Interrupt                                              */EXTI3_IRQn                  = 9,      /*!< EXTI Line3 Interrupt                                              */...CAN1_RX1_IRQn               = 21,     /*!< CAN1 RX1 Interrupt     ...   

本实验中会用到 EXTI2_IRQn 和 EXTI3_IRQn 两个中断号。在STM32微控制器中,EXTI0_IRQn到EXTI4_IRQn 是外部中断/事件 控制器(EXTI)的中断号定义,这些中断号对应着芯片的外部中断线。EXTI0-4 每个引脚有独立中断号(不共享)、EXTI5-9 共享EXTI9_5_IRQn(中断号23)、EXTI10-15 共享EXTI15_10_IRQn(中断号40),关于共享中断线,本次暂不讨论。在本实验中,两个按键分别使用了 EXTI2 和 EXTI3 两个外部事件控制器。

定义按键中断函数并注册


#if TRY_CODE_GPIO_EXTI
//osal中断服务函数原型定义
//typedef void (*fn_interrupt_handle)(void* arg);//gpio/按键1中断服务函数
static void Key1_interrupt_entry(void* arg) {//Key1下降沿中断printf("check key1 falling..\r\n");//点亮LEDHAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);//手动清空Key1-GPIO中断标识? /对应2号外部中断线__HAL_GPIO_EXTI_CLEAR_FLAG(KEY1_Pin/*GPIO_PIN_2*/);//在实际测试中也验证了,若不清除确实会一直存在中断函数的调用/打印过程
}//gpio/按键2中断服务函数
static void Key2_interrupt_entry(void* arg) {//Key1下降沿中断printf("check key2 falling..\r\n");//点亮LEDHAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);//手动清空Key2-GPIO中断标识? /对应3号外部中断线__HAL_GPIO_EXTI_CLEAR_FLAG(KEY2_Pin/*GPIO_PIN_3*/);
}
#endif
int standard_app_demo_main() {
#if TRY_CODE_GPIO_EXTI//初始化LEDGPIO /原先是在任务入口函数中进行的GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.Pin = LED_Pin;GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStructure.Pull = GPIO_NOPULL;GPIO_InitStructure.Speed = GPIO_SPEED_LOW;HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStructure);//int osal_int_connect(int intnum, int prio, int mode, fn_interrupt_handle callback, void *arg);//注册中断回调函数osal_int_connect(EXTI2_IRQn, 3, 0, Key1_interrupt_entry, NULL);//注册中断回调函数osal_int_connect(EXTI3_IRQn, 4, 0, Key2_interrupt_entry, NULL);//设置中断优先级并使能中断? /不需要! //HAL_NVIC_EnableIRQ(EXTI2_IRQn); HAL_NVIC_EnableIRQ(EXTI3_IRQn);
#endifreturn 0;
}

清除 EXTI 挂起标志位

__HAL_GPIO_EXTI_CLEAR_FLAG 函数是STM32 HAL库中用于清除外部中断(EXTI)挂起标志位的关键宏。其设计理念主要在于防止中断重复触发:若不清除标志位,CPU会认为中断持续存在,导致中断服务程序(ISR)被反复调用。一个典型应用如下,

//在EXTI中断服务函数(如 EXTI0_IRQHandler)中调用,确保每次中断仅处理一次事件
void EXTI0_IRQHandler(void) {__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);  // 实际内部调用CLEAR_FLAGHAL_GPIO_EXTI_Callback(GPIO_PIN_0);     // 执行用户回调
}

当外部中断事件(如上升沿/下降沿)触发时,EXTI控制器的"挂起寄存器"(Pending Register, PR)中对应引脚的中断标志位会被硬件置1。该函数通过向PR寄存器写入对应引脚掩码,将标志位清零。看起来理念并不复杂,但我有个问题,该函数的参数中只有引脚号(Pin),并没有端口(Port)参数,这是怎么回事呢?
这得从EXTI控制器的设计特性开始谈起。STM32的EXTI控制器将所有GPIO端口的相同引脚编号映射到同一条EXTI线上(例如:PA0、PB0、PC0共享EXTI0线)。中断标志按线管理,EXTI的PR寄存器按线(Line)记录中断标志,而非按端口。

实测验证,若不清除外部中断挂起标志,确实会一直存在中断函数的调用,表现为打印过程持续输出。
在这里插入图片描述
另外在LiteOS Studio在调试前,似乎要先烧录程序,否则调试感觉总对不上号,与Keil不太一样。

总结

在这里插入图片描述边学IoT,边学嵌入式开发啦。接下来先不搞实验5,转战对华为云IoT集成开发环境的继续探究。

LiteOS Studio 我已经用够了,主要有两个原因,
1、安装不了一些新的VSCode插件,如Bookmarks、Codeium、豆包Trae AI…
2、LiteOS Studio 编辑器也不好用。
3、LiteOS Studio 推荐使用 JLink,OpenOCD+STLink只是理论可用,实际上巨难用。

相关文章:

  • untiy 模拟人物在街道走路和跑步
  • Java中==和equals的区别
  • 理解JavaScript中map和parseInt的陷阱:一个常见的面试题解析
  • sklearn 和 pytorch tensorflow什么关系
  • 12.vite,webpack构建工具
  • 【Linux 学习计划】-- 简易版shell编写
  • 刷题记录(7)二叉树
  • 六、【ESP32开发全栈指南:深入解析ESP32 IDF中的WiFi AP模式开发】
  • 欧拉定理和费马定理
  • Gerrit+repo管理git仓库,如果本地有新分支不能执行repo sync来同步远程所有修改,会报错
  • 飞牛使用Docker部署Tailscale 内网穿透教程
  • 【Linux基础知识系列】第十四篇-系统监控与性能优化
  • 校招 Java 面试基础题目解析学习指南含新技术实操要点
  • [特殊字符]解决 “IDEA 登录失败。不支持早于 14.0 的 GitLab 版本” 问题的几种方法
  • 51单片机——计分器
  • 【51单片机】0. 基础软件安装
  • 构建 MCP 服务器:第 2 部分 — 使用资源模板扩展资源
  • [蓝桥杯]采油
  • 影楼精修-AI衣服祛褶皱算法解析
  • Pytorch安装后 如何快速查看经典的网络模型.py文件(例如Alexnet,VGG)(已解决)
  • 做网站的为什么一直拖/seo有哪些经典的案例
  • wordpress 谷歌/seo权重是什么意思
  • 3g网站建设/生意参谋指数在线转换
  • 胶州经济技术开发区 建设局 网站/百度网盘怎么找片
  • 欧美设计网站推荐/百度推广手机版
  • php网站开发员工资/重庆seo整站优化报价