【蓝桥杯嵌入式】【模块】二、LED相关配置及代码模板
1. 前言
最近在准备16届的蓝桥杯嵌入式赛道的国赛,打算出一个系列的博客,记录STM32G431RBT6这块比赛用板上所有模块可能涉及到的所有考点,如果有错误或者遗漏欢迎各位大佬斧正。
本系列博客会分为以下两大类:
1.1. 单独模块的讲解
在这部分,我会分享自己总结的各个模块的相关配置、代码书写模板,涉及到的大致框架如下:
这个框架后续可能会不断更新,欢迎各位给出建议。
这一大类相关的文章链接如下(持续补充中):
【蓝桥杯嵌入式】【模块】一、系统初始化-CSDN博客
【蓝桥杯嵌入式】【模块】二、LED相关配置及代码模板-CSDN博客
1.2. 蓝桥杯各届的真题、模拟题复盘及个人答案
在这一部分,我会分享个人练过的所有题的复盘思路及代码。
这一大类相关的文章链接如下(持续补充中):
以下是本篇博客正文内容:
2. 在cubemx中配置led
根据开发板手册,与led相关的gpio口如图红框圈出的所示。
其中,PC8-PC15是LED的输出引脚,分别对应LD1到LD8,低电平有效;PD2则是锁存器,电平拉高时进行数据传输。
为什么会有一个锁存器呢?翻看手册可以发现,LCD与LED的PC8-PC15引脚是公用的:
所以,当对PC8-PC15进行电平赋值时,可能会同时对led和lcd造成影响,为了避免这种情况,需要用一个锁存器PD2,当对led的电平做赋值后,需要先拉高,让数据通过,赋值成功,再拉低,将数据锁住,即便 PC8~PC15 后续因lcd变化,led仍显示原数据。
在左侧gpio栏做一些基本的gpio配置,可以将PC8-PC15先初始化成高电平,这样最开始的灯就是灭掉的。
3. led的初始化、点亮和熄灭
3.1. 代码放置位置
由于led相关的操作是基于gpio的,所以我习惯直接将相关代码放在gpio.c文件下,省去了新建文件的麻烦。
3.2. 初始化代码
初始化代码如下:
void led_init(void)
{HAL_GPIO_WritePin(GPIOC, GPIO_PIN_All, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
1. 首先,将PC的所有引脚置高电平,这一步会让led相关的PC8-PC15引脚置高电平,造成led熄灭,实际这一步可有可不有,因为在之前配置cubemx时就已经拉高过了。
2. 后面两步,先让PD2拉高再拉低,这样led引脚的赋值才会有效,后续对led的引脚电平改变都需要加这两句。
3.3. led的亮灭
相关代码如下:
uint8_t led_8bits = 0;
void led_on_by_8bits(void)
{for (int i = 0; i < 8; i++) {uint16_t pin = GPIO_PIN_8 << i; // PC8~PC15GPIO_PinState state = (led_8bits >> i) & 1 ? GPIO_PIN_RESET : GPIO_PIN_SET;HAL_GPIO_WritePin(GPIOC, pin, state);}// 锁存刷新HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
我喜欢采用上述方法,用一个8位数据的0到7位来记录LD1到LD8的亮灭情况,想让哪个灯亮,对这个8位数据的对应位置1即可,反之清0可让对应灯熄灭。
这样处理的好处时,采用了异步的思想,外部只处理led_8bits 这个标志位中的0-7位,寄存器的赋值放到统一的函数中进行,避免了外部函数在需要对led做操作时直接操作寄存器造成混乱(我有时这样操作会发现其会与lcd相互影响)。
举个例子:
// 示例
void led_task(void)
{led_8bits = ((MODE == 0) ? led_8bits (1 << 7) : led_8bits & ~(1 << 7));led_8bits = ((GEAR == 1) ? led_8bits | (1 << 0) : led_8bits & ~(1 << 0));led_8bits = ((GEAR == 2) ? led_8bits | (1 << 1) : led_8bits & ~(1 << 1));led_8bits = ((GEAR == 3) ? led_8bits | (1 << 2) : led_8bits & ~(1 << 2));led_8bits = ((usart_flag == 1) ? led_8bits | (1 << 3) : led_8bits & ~(1 << 3));led_on_by_8bits();
}
1. 当外部的函数需要点亮LD8时,使用下面的语句即可:
led_8bits |= (1 << 7);
这一步会让该标志位的最后一位置1,在后面的led_on_by_8bits函数中会让对应的LD8点亮。
2. 如果要让LD8熄灭的话,用下面的语句:
led_8bits &= ~(1 << 7);
这一步会让该标志位的最后一位清0,在后面的led_on_by_8bits函数中会让对应的LD8熄灭。
4. led的闪烁
闪烁的本质实际就是led的亮、灭定时切换,基于HAL_Delay延时函数即可做到想要的效果,但这样在蓝桥杯比赛中是万万不行的,因为HAL_Delay会阻塞系统的运行,影响其他功能的实现。所以,正确的办法是使用定时器中断。
4.1. 在cubemx中配置一个定时器中断
步骤如下:
在timer中选择一个TIM来用作中断源,我比较喜欢用TIM4,因为根据经验来看,许多届的考题都没有涉及到过TIM4的使用,其他的TIM2、TIM3等常常会被用来做PWM相关的东西,所以我觉得选用TIM4来单独的作为定时器中断用来实现led闪烁、长亮、按键检测是一个不错的选择。
如图上步骤所示,我使用TIM4定时器,预分频系数为10,重装载值为10000,由此生成了一个频率为(80000000 / 00 / 10000 = 100)HZ的定时器,也就是每隔1 / 100 = 0.01s = 10ms会进一次中断回调函数。
记得要使能中断,之后点击生成代码即可。
4.2. 在定时器中断中书写led闪烁逻辑
首先,在初始化中,需要加入定时器中断的开启逻辑:
HAL_TIM_Base_Start_IT(&htim4);
(同理,定时器相关的函数逻辑我建议放在tim.c文件里)。
之后,重写中断回调函数,在内部书写led闪烁的逻辑:
这里以让LD2以500ms为周期闪烁为例:
extern uint8_t led_8bits;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM4){static uint32_t ld1_cnt = 0;if(ld1_cnt == 50){ld1_cnt = 0;led_8bits ^= (1 << 1);}else{ld1_cnt++;}}
}
每10ms,都会进该中断回调函数内执行逻辑,使用了一个计数器,当进该中断50次,即花费50 * 10 = 500ms = 0.5s后,会触发一次led_8bits 第1位的电平反转,也就是LD2的亮灭切换。
之后,在led_task中加入对应的处理逻辑
void led_task(void)
{led_on_by_8bits();
}
后续将led_task放入主循环,便可实现LD2每隔0.5s闪烁一次。
5. led亮一段时间后熄灭
这个实际与led闪烁很像,依旧也是基于定时器,代码如下:
5.1. 在定时器中断中书写逻辑
以让LD2亮5s后熄灭为例。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM4){static uint32_t ld1_cnt = 0;if(ld1_cnt < 500){ld1_cnt++;led_8bits |= (1 << 1);}else{led_8bits &= ~(1 << 1);}}
}
如上所示,在进定时器中断的前500次内都让LD2亮,即亮500 * 0.01 = 5s,之后熄灭,便达到了需求。
总结
本文介绍了led相关操作的模板代码,主要是基于异步思想,实现led亮灭、闪烁、长亮后熄灭。