ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32 实验任务3:HAL 库 + 按键中断控制流水灯暂停/恢复

STM32 实验任务3:HAL 库 + 按键中断控制流水灯暂停/恢复 STM32 实验任务3HAL 库 按键中断控制流水灯暂停/恢复任务要求在前面实验寄存器、标准外设库流水灯基础上改用HAL 库 按键中断方式控制流水灯运行的暂停、恢复继续。问 1没有 CubeMXHAL 库工程怎么手动搭建通常用 HAL 库开发会用 STM32CubeMX 自动生成工程但我电脑上没有 CubeMX也没有官方 CubeF1 固件包所以选择手动下载 HAL 库文件 手写工程顺便把 HAL 库工程结构彻底搞明白。1.1 下载 HAL 库文件HAL 库STM32CubeF1在 GitHub 上其实拆成了两个仓库都是 git submodulecmsis_device_f1设备头文件、启动文件、系统时钟源文件stm32f1xx_hal_driverHAL 外设驱动.c/.h把需要的 16 个文件下载后整理成工程目录stm32-led-hal/ ├── main.c ← 用户主程序 ├── stm32f1xx_hal_conf.h ← HAL 配置头文件外设模块总开关 ├── Device/ │ ├── Include/ ← 芯片头文件stm32f1xx.h 等 │ ├── Source/ ← system_stm32f1xx.c系统时钟初始化 │ └── Startup/ ← startup_stm32f103xb.s启动文件 └── HAL/ ├── Inc/ ← HAL 驱动头文件hal.h、hal_gpio.h 等 └── Src/ ← HAL 驱动源文件hal.c、hal_gpio.c 等1.2 Keil 工程配置点魔术棒Options for Target→C/C页配置项值说明DefineSTM32F103xB,USE_HAL_DRIVER芯片型号宏 使用 HAL 库宏Include Paths.\;.\HAL\Inc;.\Device\Include头文件搜索路径Language Cc99HAL 库/CMSIS 6.x 要求Run-Time Environment 里只勾选CMSIS → CORE不勾 Device → Startup启动文件用 HAL 仓库下载的 startup_stm32f103xb.s。1.3 踩坑记录编译/链接报错排查坑 1Legacy/stm32_hal_legacy.h file not found编译 HAL 驱动时报找不到Legacy/stm32_hal_legacy.h。这个头文件在 HAL 仓库的Inc/Legacy/子目录里补下载到HAL/Inc/Legacy/下解决。坑 2stm32f1xx_hal_rcc_ex.h file not foundRCC 驱动里还引用了_ex扩展头文件。预防性把stm32f1xx_hal_flash_ex.h、stm32f1xx_hal_gpio_ex.h、stm32f1xx_hal_rcc_ex.h全部补下载。坑 3链接报错Undefined symbol HAL_GPIO_Init等关键坑编译通过但链接报一串 undefined symbolFLASH_PageErase、HAL_GPIO_Init、HAL_GPIO_WritePin……排查发现根因是stm32f1xx_hal_conf.h里漏写了HAL_GPIO_MODULE_ENABLED。新版 HAL 库每个驱动文件整体被这种宏包住#ifdefHAL_GPIO_MODULE_ENABLED...gpio 驱动全部内容...#endif宏没定义 → gpio 驱动文件编译出来是空文件链接时自然找不到函数。把用到的模块宏全补上#defineHAL_FLASH_MODULE_ENABLED#defineHAL_GPIO_MODULE_ENABLED#defineHAL_RCC_MODULE_ENABLED#defineHAL_CORTEX_MODULE_ENABLED并且还需要在工程里添加stm32f1xx_hal_flash_ex.cFLASH_PageErase在这个文件里。补完后0 Error 0 Warning。问 2按键中断怎么实现2.1 硬件接线器件引脚说明红灯PA0高电平点亮绿灯PB0高电平点亮蓝灯PC14高电平点亮PC13/14/15 低速引脚板载灯PC13低电平点亮极性相反轻触按键PA1 → GND按下时 PA1 被拉低按键一端接 PA1另一端接 GND按下按键 PA1 产生下降沿正好用 EXTI 外部中断的下降沿触发来检测。2.2 按键初始化外部中断 上拉voidKEY_Init(void){GPIO_InitTypeDef GPIO_InitStruct{0};/* PA1外部中断模式下降沿触发按下→低电平内部上拉平时为高 */GPIO_InitStruct.PinGPIO_PIN_1;GPIO_InitStruct.ModeGPIO_MODE_IT_FALLING;GPIO_InitStruct.PullGPIO_PULLUP;HAL_GPIO_Init(GPIOA,GPIO_InitStruct);/* 使能 EXTI1 中断优先级 0最高 */HAL_NVIC_SetPriority(EXTI1_IRQn,0,0);HAL_NVIC_EnableIRQ(EXTI1_IRQn);}2.3 中断服务函数与回调HAL 库的中断处理分三层中断服务函数 → HAL 通用处理函数 → 用户回调/* ① 中断服务函数框架自动调用 */voidEXTI1_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);}/* ② 用户回调按键按下 → 翻转暂停标志 */voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){if(GPIO_PinGPIO_PIN_1){g_pause!g_pause;/* 暂停 ↔ 恢复切换 */}}按键按下 → 硬件进入 EXTI1 中断 →HAL_GPIO_EXTI_IRQHandler清中断标志并调用HAL_GPIO_EXTI_Callback→ 我们只在回调里翻转一个标志变量g_pause不做复杂操作中断里代码越短越好。2.4 踩坑 4烧录后只亮红灯不流水、按键无反应烧录后红灯常亮、完全不流水按键也没反应。排查很久发现根因是main.c 里漏写了SysTick_HandlervoidSysTick_Handler(void){HAL_IncTick();/* 每 1ms 中断一次tick 计数 1 */}HAL_Delay 依赖 SysTick 每 1ms 触发一次中断累计 tick这个服务函数没实现HAL_Delay 就永远卡死——红灯点亮后延时无限长看起来就是只亮红灯不流水。补上后流水立刻正常。问 3怎么做到按下立即暂停、再按恢复3.1 最初的设计有缺陷一开始只在主循环开头检查暂停标志while(1){while(g_pause)HAL_Delay(10);/* 只有每轮开头检查 */红灯亮1s;绿灯亮1s;蓝灯亮1s;板载亮1s;}实测发现问题流水过程中按按键不会立即停而是要等一整轮4 秒走完才停。原因g_pause 只在每轮开头被检查按键按下时主循环正卡在某一盏灯的 HAL_Delay(1000) 里要等当前灯亮完 后面所有灯亮完才回到检查点。3.2 改进把暂停检查做进延时函数把每一盏灯的延时改成delay_pause()延时期间每 10ms 检查一次按键标志按下立即原地等待当前灯保持点亮voiddelay_pause(uint32_tms){uint32_ti;for(i0;ims;i10){if(g_pause){/* 暂停保持当前灯亮着等按键恢复 */while(g_pause){HAL_Delay(10);}return;/* 恢复后跳过本灯剩余时间进入下一盏 */}HAL_Delay(10);}}主循环里把所有HAL_Delay(1000)换成delay_pause(1000)。这样按下→ 最多 10ms 内响应当前灯停在点亮状态再按→ 恢复跳过当前灯剩余时间进入下一盏灯3.3 完整代码main.c#includestm32f1xx_hal.hvolatileuint8_tg_pause0;/* 暂停标志0运行 1暂停中断中修改 *//* 系统时钟HSE 8MHz × PLL9 72MHz */voidSystemClock_Config(void){RCC_OscInitTypeDef RCC_OscInitStruct{0};RCC_ClkInitTypeDef RCC_ClkInitStruct{0};RCC_OscInitStruct.OscillatorTypeRCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEStateRCC_HSE_ON;RCC_OscInitStruct.HSEPredivValueRCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.PLL.PLLStateRCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSourceRCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMULRCC_PLL_MUL9;HAL_RCC_OscConfig(RCC_OscInitStruct);RCC_ClkInitStruct.ClockTypeRCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSourceRCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDividerRCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDividerRCC_HCLK_DIV2;RCC_ClkInitStruct.APB2CLKDividerRCC_HCLK_DIV1;HAL_RCC_ClockConfig(RCC_ClkInitStruct,FLASH_LATENCY_2);}/* LED 初始化PA0红 PB0绿 PC14蓝 PC13板载 */voidLED_Init(void){GPIO_InitTypeDef GPIO_InitStruct{0};__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_GPIOC_CLK_ENABLE();GPIO_InitStruct.PinGPIO_PIN_0;GPIO_InitStruct.ModeGPIO_MODE_OUTPUT_PP;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA,GPIO_InitStruct);/* PA0 红灯 */GPIO_InitStruct.PinGPIO_PIN_0;HAL_GPIO_Init(GPIOB,GPIO_InitStruct);/* PB0 绿灯 */GPIO_InitStruct.PinGPIO_PIN_14;GPIO_InitStruct.SpeedGPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOC,GPIO_InitStruct);/* PC14 蓝灯低速引脚 */GPIO_InitStruct.PinGPIO_PIN_13;HAL_GPIO_Init(GPIOC,GPIO_InitStruct);/* PC13 板载灯 */HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);}/* 按键初始化PA1 下降沿中断 */voidKEY_Init(void){GPIO_InitTypeDef GPIO_InitStruct{0};GPIO_InitStruct.PinGPIO_PIN_1;GPIO_InitStruct.ModeGPIO_MODE_IT_FALLING;GPIO_InitStruct.PullGPIO_PULLUP;HAL_GPIO_Init(GPIOA,GPIO_InitStruct);HAL_NVIC_SetPriority(EXTI1_IRQn,0,0);HAL_NVIC_EnableIRQ(EXTI1_IRQn);}/* SysTick 中断服务函数HAL_Delay 的时基必须实现 */voidSysTick_Handler(void){HAL_IncTick();}voidEXTI1_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);}/* 中断回调按键按下 → 翻转暂停标志 */voidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){if(GPIO_PinGPIO_PIN_1){g_pause!g_pause;}}/* 带暂停检测的延时每 10ms 检查按键按下立即暂停 */voiddelay_pause(uint32_tms){uint32_ti;for(i0;ims;i10){if(g_pause){while(g_pause)HAL_Delay(10);/* 灯保持亮等恢复 */return;}HAL_Delay(10);}}intmain(void){HAL_Init();/* SysTick 1ms 时基、NVIC 分组 */SystemClock_Config();/* 72MHz */LED_Init();KEY_Init();while(1){HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_SET);/* 红灯 */delay_pause(1000);HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);/* 绿灯 */delay_pause(1000);HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_SET);/* 蓝灯 */delay_pause(1000);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_14,GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);/* 板载灯 */delay_pause(1000);HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);}}运行效果流水灯正常轮流点亮按下按键立即暂停当前灯保持点亮再按一次恢复继续流水总结这个实验让我把 STM32 的开发方式从寄存器、标准外设库推进到了 HAL 库而且是在没有 CubeMX 的情况下纯手动搭建工程把 HAL 库设备层 驱动层 配置文件的结构彻底看了一遍中间解决了三个编译链接坑legacy 头文件、ex 扩展头文件、HAL_xxx_MODULE_ENABLED 模块开关和两个运行逻辑坑SysTick_Handler 漏写导致 HAL_Delay 卡死、暂停检查位置不对导致不能立即暂停。按键中断的思路也很有价值中断服务函数里只翻转一个标志主循环轮询标志两者通过共享变量协作避免在中断里做耗时操作。同时 HAL_Delay 基于 SysTick 定时器与实验 2 的软件循环延时实测 0.88s 对比体现了硬件定时器比软件延时精确得多这一嵌入式开发的重要原则。
返回列表