ARTICLE DETAIL

资讯详情

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

STM32标准外设库、HAL库实现流水灯

STM32标准外设库、HAL库实现流水灯 本次实验继续使用 STM32F103C8T6 最小系统板和三个外接 LED。在任务二中我使用 STM32 标准外设库实现流水灯在任务三中我改用 HAL 库并加入按键中断控制流水灯暂停和恢复。实验目的熟悉 STM32 标准外设库和 HAL 库的基本工程结构。学会使用库函数配置 GPIO 输出并控制多个 LED 依次点亮。理解软件延时的误差能够估算流水灯的完整循环周期。学会配置 GPIO 外部中断并利用按键控制程序运行状态。理解阻塞延时和非阻塞计时的区别进一步熟悉HAL_GetTick()的使用。一、实验器材与环境STM32F103C8T6 最小系统板红、绿、蓝 LED 各一个限流电阻、按键、面包板和杜邦线ST-Link 下载器Keil µVision 5STM32F10x 标准外设库和 STM32F1 HAL 库。三个 LED 的接线相同GPIO 引脚经过限流电阻连接 LED 正极LED 负极接 GND因此输出高电平时点亮。LED引脚点亮顺序蓝灯PA2第一个红灯PA0第二个绿灯PA1第三个二、实验任务二标准外设库流水灯2.1 实验要求使用 STM32F10x 标准外设库控制 PA0、PA1、PA2 三个引脚使蓝、红、绿三个 LED 每隔约 1 秒依次点亮。与上一次直接操作寄存器的方式相比本任务不再手动设置 RCC 和 GPIO 寄存器的具体位而是通过 ST 提供的标准外设库函数完成时钟使能、GPIO 初始化和输出控制。库函数的底层仍然会访问寄存器但代码更容易阅读和维护。2.2 建立工程并添加标准库我先准备 STM32F10x 标准外设库工程使用的固件库版本为STM32F10x_StdPeriph_Lib_V3.5.0。标准外设库的src文件夹中保存.c源文件将这些文件复制到工程的Library文件夹。标准外设库的inc文件夹中保存.h头文件也将这些文件复制到工程的Library文件夹。工程主要建立以下四个文件夹文件夹主要内容Start启动文件、CMSIS 内核和芯片文件LibrarySTM32F10x 标准外设库文件System延时模块Delay.c/.hUsermain.c、中断和配置文件在 Keil 中选择STM32F103C8外部晶振设置为 8 MHz在 C/C 设置中加入宏USE_STDPERIPH_DRIVER并添加Start、Library、System和User的头文件搜索路径。工程配置的主要步骤如下新建 Keil 工程并选择STM32F103C8芯片。添加中容量启动文件startup_stm32f10x_md.s。建立Start、Library、System、User四个文件组。将标准外设库源文件、头文件以及用户代码加入对应文件组。在预处理宏中加入USE_STDPERIPH_DRIVER。将./Start、./Library、./System、./User加入 Include Paths。在 Output 选项中勾选 Create HEX File最后重新编译工程。2.3 程序设计程序先打开 GPIOA 时钟再将 PA0、PA1、PA2 配置为 50 MHz 通用推挽输出。每次先关闭三个 LED再打开当前 LED保证同一时间只亮一盏灯。主要初始化和显示程序如下#includestm32f10x.h#includeDelay.h#defineLED_RED_PINGPIO_Pin_0/* PA0 */#defineLED_GREEN_PINGPIO_Pin_1/* PA1 */#defineLED_BLUE_PINGPIO_Pin_2/* PA2 */#defineLED_ALL_PINS(LED_RED_PIN|LED_GREEN_PIN|LED_BLUE_PIN)staticvoidLED_Init(void){GPIO_InitTypeDef GPIO_InitStructure;/* 打开 GPIOA 的外设时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);/* 初始状态下三个 LED 全部熄灭 */GPIO_ResetBits(GPIOA,LED_ALL_PINS);/* PA0、PA1、PA2 配置为通用推挽输出 */GPIO_InitStructure.GPIO_PinLED_ALL_PINS;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_Init(GPIOA,GPIO_InitStructure);}staticvoidLED_Show(uint16_tpin){GPIO_ResetBits(GPIOA,LED_ALL_PINS);/* 先熄灭全部灯 */GPIO_SetBits(GPIOA,pin);/* 点亮指定灯 */}主循环如下intmain(void){LED_Init();while(1){LED_Show(LED_BLUE_PIN);Delay_ms(1000);LED_Show(LED_RED_PIN);Delay_ms(1000);LED_Show(LED_GREEN_PIN);Delay_ms(1000);}}程序中主要使用了以下标准库函数RCC_APB2PeriphClockCmd()打开 GPIOA 时钟GPIO_Init()配置 GPIO 工作方式GPIO_ResetBits()输出低电平熄灭 LEDGPIO_SetBits()输出高电平点亮 LED。任务二的执行流程如下程序开始使能 GPIOA 时钟配置 PA0、PA1、PA2 为推挽输出蓝灯 PA2 亮约 1 秒红灯 PA0 亮约 1 秒绿灯 PA1 亮约 1 秒GPIO_Speed_50MHz表示 GPIO 输出电路允许的速度等级并不表示 LED 以 50 MHz 的速度闪烁。LED 的切换速度由程序中的Delay_ms(1000)决定。2.4 流水灯周期分析三个状态各延时约 1 秒因此完整流水周期为T ≈ 1 s × 3 3 s所以每只 LED 点亮约 1 秒、熄灭约 2 秒同一只 LED 从本次点亮到下次点亮的重复周期约为 3 秒占空比约为 33.3%。程序运行和晶振会产生少量误差因此实际周期可能略大于 3 秒。在没有示波器的情况下可以进入 Keil 调试模式使用 Logic Analyzer 添加 PA0、PA1、PA2 对应信号观察三个 GPIO 的高低电平变化。正常情况下每个信号的高电平约持续 1 秒相邻信号依次出现高电平同一个引脚相邻两次高电平的间隔约为 3 秒。若使用软件仿真需要确认仿真时钟设置与工程实际时钟相符否则观察到的时间可能与真实开发板存在差异。结果如图2.5 实验结果任务二工程已经通过 Keil 编译结果为0 Error(s), 0 Warning(s)程序运行后LED 按照“蓝灯 PA2→红灯 PA0→绿灯 PA1”的顺序循环点亮。实验二视频三、实验任务三HAL 库与按键中断3.1 实验要求任务三改用 HAL 库控制流水灯并增加一个按键第一次按下按键时暂停流水灯当前 LED 保持点亮再次按下按键时恢复运行并接着暂停前剩余的时间继续计时。本任务没有在中断函数中直接完成全部控制而是采用“中断提交请求、主循环处理请求”的方法。这样可以缩短中断执行时间使程序结构更清晰也能避免在中断函数中使用阻塞延时。3.2 HAL 工程结构HAL 工程包含HAL、Start、System和User等文件夹。HAL文件夹中又分为Inc和Src分别保存 HAL 头文件和源文件。Keil 中建立Start、System、HAL和User文件组将相应文件加入工程。调试时可以使用软件仿真也可以选择 ST-Link 下载到开发板。3.3 硬件连接与中断设置LED 接线与任务二相同。按键一端连接PB11另一端连接 GND。PB11 配置为内部上拉输入平时为高电平按下按键后变为低电平产生下降沿中断。PB11 对应 EXTI 线 11使用EXTI15_10_IRQn中断入口。#defineKEY_PINGPIO_PIN_11#defineKEY_PORTGPIOBstaticvoidKEY_EXTI_Init(void){GPIO_InitTypeDef gpio{0};__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_AFIO_CLK_ENABLE();gpio.PinKEY_PIN;gpio.ModeGPIO_MODE_IT_FALLING;/* 下降沿触发 */gpio.PullGPIO_PULLUP;/* 内部上拉 */HAL_GPIO_Init(KEY_PORT,gpio);HAL_NVIC_SetPriority(EXTI15_10_IRQn,1U,0U);HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);}按键平时由内部上拉保持高电平按下后 PB11 被接到 GND电平由高变低。这个下降沿会触发 EXTI11中断控制器再进入EXTI15_10_IRQHandler()。3.4 HAL 流水灯程序LED 使用HAL_GPIO_WritePin()控制。程序利用HAL_GetTick()判断是否经过 1000 ms不使用阻塞式延时因此主循环可以及时处理按键请求。#defineLED_ON_TIME_MS1000Ustaticconstuint16_ts_led_pins[3]{GPIO_PIN_2,/* 蓝灯 PA2 */GPIO_PIN_0,/* 红灯 PA0 */GPIO_PIN_1/* 绿灯 PA1 */};staticvoidLED_Show(uint8_tindex){uint8_ti;for(i0U;i3U;i){HAL_GPIO_WritePin(GPIOA,s_led_pins[i],(iindex)?GPIO_PIN_SET:GPIO_PIN_RESET);}}任务三的整体流程如下否是否是是否HAL 初始化和系统时钟配置初始化 LED 与 PB11 外部中断蓝灯开始点亮收到按键请求?当前灯已亮满 1 秒?切换到下一盏灯当前是否运行?保存剩余时间并暂停恢复运行并继续剩余时间程序使用以下几个状态变量变量作用s_running表示流水灯处于运行还是暂停状态s_led_index记录当前点亮的是蓝灯、红灯还是绿灯s_led_start_tick保存当前 LED 开始点亮的时间s_led_remaining暂停时保存当前 LED 剩余的点亮时间s_key_request中断回调向主循环提交的按键请求主循环在运行状态下每隔 1 秒切换 LED收到按键请求时在运行和暂停两种状态之间切换while(1){if(s_key_request!0U){s_key_request0U;if(s_running!0U){uint32_tnowHAL_GetTick();uint32_telapsednow-s_led_start_tick;/* 暂停并保存当前灯剩余的点亮时间 */s_led_remaining(elapsedLED_ON_TIME_MS)?LED_ON_TIME_MS:(LED_ON_TIME_MS-elapsed);s_running0U;}else{uint32_tnowHAL_GetTick();/* 恢复后继续暂停前剩余的时间 */s_led_start_ticknow-(LED_ON_TIME_MS-s_led_remaining);s_running1U;LED_Show(s_led_index);}}if(s_running!0U){uint32_tnowHAL_GetTick();if((now-s_led_start_tick)LED_ON_TIME_MS){s_led_index(uint8_t)((s_led_index1U)%3U);s_led_start_ticknow;LED_Show(s_led_index);}}}3.5 按键消抖和中断回调机械按键按下时可能连续产生多个跳变因此程序使用HAL_GetTick()实现约 50 ms 的非阻塞消抖。中断回调中只设置请求标志暂停和恢复的具体处理放在主循环中完成。#defineKEY_DEBOUNCE_MS50UvoidHAL_GPIO_EXTI_Callback(uint16_tGPIO_Pin){uint32_tnow;if(GPIO_Pin!KEY_PIN){return;}nowHAL_GetTick();if((now-s_last_key_tick)KEY_DEBOUNCE_MS){return;}s_last_key_ticknow;s_key_request1U;}中断服务函数调用 HAL 的 EXTI 处理函数再进入上面的回调voidEXTI15_10_IRQHandler(void){HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);}一次完整的按键控制过程可以表示为操作流水灯状态LED 状态上电运行正常切换蓝→红→绿循环第一次按键暂停当前 LED 保持点亮暂停期间不推进序号画面保持不变第二次按键恢复当前 LED 继续剩余时间剩余时间结束正常运行切换到下一盏 LED例如某盏 LED 已点亮约 400 ms 时按下按键程序会保存约 600 ms 的剩余时间。再次按键恢复后这盏灯继续保持约 600 ms然后才切换到下一盏灯而不是重新点亮完整的 1 秒。3.6 实验结果任务三工程已经通过 Keil 编译结果为Program Size: Code4108 RO-data316 RW-data36 ZI-data1636 0 Error(s), 0 Warning(s)正常运行时三盏灯按照蓝、红、绿顺序循环。第一次按下按键后流水灯暂停并保持当前 LED再次按下后继续运行。实验三视频四、实验中遇到的问题与处理方法4.1 标准库文件较多STM32F10x 标准外设库包含许多外设的.c和.h文件。本实验实际主要使用 GPIO 和 RCC但为了保持完整工程模板仍然保留了其他标准库文件。编译时必须正确设置头文件搜索路径和USE_STDPERIPH_DRIVER宏否则容易出现找不到头文件或函数声明的问题。4.2 LED 不亮或顺序不正确出现这种情况时应检查 LED 正负极、限流电阻、公共 GND 和 PA0PA2 接线。由于本实验采用高电平点亮程序需要先复位全部输出再置位当前 LED 引脚。4.3 按一次却切换多次机械按键存在抖动一次按下可能产生多个下降沿。任务三使用 50 ms 消抖时间短时间内重复产生的中断会被忽略从而使一次按键只完成一次暂停或恢复。4.4 中断中不使用长延时如果在中断函数中执行长时间延时主程序和其他中断会被阻塞。因此本实验的中断回调只设置s_key_request实际状态切换放到主循环中完成。五、三种实现方式比较开发方式主要写法优点特点寄存器方式直接设置寄存器地址和位能清楚理解硬件底层原理代码较繁琐需要仔细查手册标准外设库调用GPIO_Init()等函数可读性较好旧版 STM32F1 示例较多需要添加标准库文件和宏定义HAL 库调用HAL_GPIO_Init()等函数接口统一便于配置中断和复杂外设工程文件和代码量相对较大任务二让我熟悉了 STM32 标准外设库的文件结构和 GPIO 控制函数任务三进一步学习了 HAL 库、外部中断、按键消抖和非阻塞计时。通过这两个任务我实现了流水灯的基本运行以及使用按键控制暂停和恢复。通过对比可以看出不同开发方式最终都会对 GPIO 和中断相关寄存器产生操作只是代码封装层次不同。寄存器方式适合理解底层标准外设库适合熟悉 STM32F1 的传统项目HAL 库则更方便组织较完整的应用程序。
返回列表