赞
踩
往期内容:
【stm32】00亲爱的勇者,在踏上这个波澜壮阔的大陆之前,厉兵秣马,我们即刻出发
【stm32】01,今晚,整个32将为你闪烁(GPIO输出,点亮led灯)
亲爱的勇者们,许久不见甚是想念!我是weib,在我不在的日子里也祝愿你们早安、午安和晚安!欢迎各位勇者来到了新的关卡,在上一关里我们主要学习了GPIO输出模式的控制,各位勇者可还记得在那个不太聪明的王国里那扇“偏执”的门?那对纯洁的0和1所代表的状态可还有印象?那三条神奇的秘籍能熟练使用了吗?各位勇者可以一遍又一遍的回顾过往的关卡,weib会一直陪着你。
今天我们将学习对GPIO输入模式的控制,同时通过按键的方式控制led的闪烁——曾经的boss如今被我们玩弄于股掌之间,这难道不是一件很酷的事情吗?咳咳~
请各位勇者再次确认自己的装备是否齐全——keil5-mdk及其芯片包、cubemx及其jdk环境以及一颗强大的心脏。因为今天的boss是按键,所以各位勇者要确保自己的单片机有外接的按键、或者用面包板搭一个按键电路,以避免历经千辛万苦到达目的地却找不不到博士生的情况发生。
最后请闭上眼睛,轻轻的抚摸你的键盘,她是你最忠实的伙伴,她将陪伴着你攻克道道难题。
这是一个按键和led灯电路的原理图,在上一个关卡中我们我们已经熟悉了led的电路。,所以现在我们将把目光着眼于按键电路之上。
现在,请各位小聪明思考一个问题——着眼于当前按键电路,在按键断开(开路)的情况下,PA0端的电平是高还是低?
我相信已经有小聪明看出不对应来——外部电路开路,PA0处于浮空状态,我哪知道它电平的高低啊!
说时迟,那时快,只见你手中的单片机跳出来说:嗨嗨嗨~大哥说得没错,我也不造(知道)啊!
接下来,我们再来康康这幅图
现在weib想再问问各位勇者——现在在按键没按下的情况下,PA0的电平为什么呢?
相信有小聪明立马就能回答——为高!
莫错~我们通过加了一个电阻就把PA0端口在按键开路时的电平确定了,这是就有善于观察的小聪明会问:weibweib,你在电阻的上端加上电源就可以把PA0的状态保持为高电平,那为什么不让电阻的上端接地、让它保持低电平呢?
能问出这种问题的小聪明,请让weib给你一朵大大的小红花。莫错,电阻的上端接地、让它保持低电平在我们的日常工程应用中是可行的、经常被用到的。同时这两种用法分别被成为上拉电阻模式(电阻一端接电源)和下拉电阻模式(电阻一端接地),这两种对GPIO的配置方法也是我们在以后的工程中会经常用到的。
回到刚才的问题,我们为什么要在这使用上拉电阻,而不是下拉电阻呢?现在请各位小聪明思考另一个问题——当按键按下时,PA0的电平为?莫错~相信有小聪明已经看出,当按键按下时,PA0直接与GND相连,它的电平肯定为低。莫错~我们再来思考如果我们采用下拉电阻的方式的话,在按键按下之前PA0为低电平,按下按键之后PA0还是为低电平——我们该如何判断按键时候按下。
我们把目光看下手中的单片机,单片机瓮声瓮气的说:大哥,我也不造啊,要不你加个摄像头写个图像识别?
嘿嘿~言归正传,所以在按键检测中我们要配置GPIO为上拉电阻模式。
经过刚才的讨论我们得出以下结论:
当按键没被按下时:所对应引脚为高电平(1)
当按键被按下时 :所对应引脚为低电平(0)
好啦,战术分析到此结束了,接下来我们要正式踏上旅途了,各位勇者准备好了吗?
在上一关中,我们利用GPIO的输出模式去控制对应的GPIO输出纯洁的0和1,以达到控制led等点亮和熄灭的目的。
在今天,weib将带领各位勇者通过判断GPIO端口的电平去判断按键是否按下,而判断GPIO的电平的高低就是我们今天的重点——对GPIO输入模式的应用。
那么就有小机灵鬼会问:GPIO的输入模式是什么呢?
我们先来康康chatAI的解释:
GPIO输入模式是指将某个引脚配置为输入状态,用于接收外部信号或传感器的输入。
在GPIO输入模式下,引脚可以接收来自外部电路的电平信号,并将其传递给单片机或其他设备进行处理。引脚可以处于高电平(通常表示逻辑1)或低电平(通常表示逻辑0)状态,具体取决于外部信号或传感器的电平。
GPIO输入模式通常需要设置引脚的输入电阻,以确定电平的稳定性和防止电流过大。此外,还可以配置引脚的中断功能,使系统在引脚状态变化时能够及时作出响应。
总而言之,GPIO输入模式允许单片机或其他设备以一种灵活的方式接收外部信号或传感器的输入,并进行相应的处理。
不好意思,weib的知识薄弱,上面的解释我看得云里雾里。嘿嘿~我们现在只需知道,为了得到传输给单片机的高低信号,我们需要通过配置GPIO的输入模式来实现,而根据情况设置上下拉电阻可以达到稳定输入电平的目的。
接下来,请打开我们心爱的cubemx,我们继续前进!
还记得之前weib说这是几乎每个工程都要做的事情吗,这不就遇到了吗,嘿嘿~
具体操作weib就不累述了,如果有小傻瓜忘记了可以回到上一个关卡重温对应章节~
【stm32】01,今晚,整个32将为你闪烁(GPIO输出,点亮led灯)
首先,和点亮led一样,我们先要找到我们自己板子上按键连接的引脚,如我这是PA0,有些小倒霉蛋的单片机核心板上可能没有额外的按键,也可以自己用面包板搭一个按键电路,只要思想不滑坡 办法总比困难多嘛。在接下来的使用中,我将用PA0引脚示例。
如图所示,首先我们选择要配置的引脚,然后用鼠标左键点击它选择GPIO_Input把该引脚设置为输入模式。
如图所示,我们进入GPIO配置菜单、选择对应的GPIO进行配置:
1、GPIO模式,因为刚才我们在芯片图上配置过,所以默认的就是输出模式;
2、GPIO上拉下拉设置,一共有三个选项:No pull-up no pu-down(无上拉下拉,浮空)、pull-up(上拉)、pu-down(下拉),在这里我们一定要选择pull-up上拉模式!!!
3、引脚名称的自定义,在上一关的“多学一招”里面weib详细讲了的,感兴趣的小伙伴可以回去再康康,weib是非常推荐这样使用的!
配置好了按键,有急性子的小可爱就在问weib:weibweib现在是不是应该生成keil工程了,我已经迫不及待地想去大展拳脚了。
noooo~,小伙伴们思考一个问题,在上一个关卡中我们学习GPIO的输出使用方法,是如何验证使用成功的呢?是不是就是通过led的状态直观的告诉我们单片机是否按照我们的想法工作的,那么按键的话我们该怎么去判断单片机正确检测到了按键的按下与释放呢?
莫错~在这次的旅行中我们要借助led的力量来展现按键的状态,在未来的开发中我们也将会通过串口、屏幕、输出波形等来监测单片机的工作状态。
所以我们仍要对连接led的引脚进行配置,如上个关卡一样。
具体操作如上,接下来我们就开始编写程序了,各位小伙伴们,你们准备好了吗?
打开keil我们先进行常规操作:编译、打开main.c、找到main函数,没配置好下载选项的小伙伴需要先去配置下载选项,具体操作可以参考上一个关卡的内容。
今天我们只需要学一个秘籍即可,虽然量少,但是各位勇者们一定要把它学透哦。咳咳~集中注意力,要来了哦!
- /**
- * @brief Reads the specified input port pin.
- * @param GPIOx: where x can be (A..G depending on device used) to select the GPIO peripheral
- * @param GPIO_Pin: specifies the port bit to read.
- * This parameter can be GPIO_PIN_x where x can be (0..15).
- * @retval The input port pin value.
- */
- GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
- {
- GPIO_PinState bitstatus;
-
- /* Check the parameters */
- assert_param(IS_GPIO_PIN(GPIO_Pin));
-
- if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)GPIO_PIN_RESET)
- {
- bitstatus = GPIO_PIN_SET;
- }
- else
- {
- bitstatus = GPIO_PIN_RESET;
- }
- return bitstatus;
- }
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
这是hal库为我们提供的,用来检测GPIO输入电平的函数
它的第一个参数是GPIO_TypeDef *GPIOx,它表示对应端口编号,如GPIOA、GPIOB等,而我要设置的PA0脚,所以我要写入应该是GPIOA
它的第二个参数uint16_t GPIO_Pin代表引脚编号,所以PC13对应的引脚编号是GPIO_PIN_0
而它的返回值就是,该GPIO当前的状态0或1
所以我们想要读取按键的状态我们该这样写:
HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
但是如图所示,值得我们关注的一点是,如果我们在引脚配置阶段的配置了自定义引脚名的话,我们可以用cubemx帮我们宏定义的端口代替我们本来要写入的端口,宏定义的端口编号代替本来的端口编号。从编译的角度来看其意义是一样的,但是利用宏定义的方式去进行程序编写的话,可以增加程序可读性,而且…而且,你都让cubemx帮你配置了,你不用她会伤心的~
所以我们也可以这样用这条函数:
HAL_GPIO_ReadPin(Key_GPIO_Port,Key_Pin);
现在,我们就要结合之前我们所学的几条秘籍进行编程了,各位小伙伴准备好了吗?
在以一个程序中,我们去实现一个很简单的操作:当检测到按键按下之时led灯被点亮,当按键被释放之时led熄灭
首先我们要学会抓住其重点:检测按下时刻、检测松开时刻
那么这个程序该如何编写呢?各位小伙伴们可以先去试试,weib相信你们一定可以的。完成功能后再继续往下看,康康weib写得有多菜~
这是weib的作品,各位小伙伴觉得怎么样呢?肯定比各位小聪明low多了吧。
在刚才的程序中weib虽然实现了其功能,但是小伙伴们思考一个问题,如果我们还想要在程序中进行其他操作,比如让另一盏led闪烁,而我们的程序被按键等待释放时的while循环所占用了,那盏led还能正常闪烁的。就如一个安排一个领导,本意是想让控制各项工作的,但是不能干扰其他工作的正常进行啊!
而且在我们日常的应用中,我们更习惯的是按键松开生效,而不是按下立即生效,特别是带有长按短按功能的按键。
所以,现在我们现在来优化一下程序,想要达到的效果是在不影响的其他功能的情况下实现用按键控制led的亮灭。
- int main(void)
- {
- /* USER CODE BEGIN 1 */
-
- /* USER CODE END 1 */
-
- /* MCU Configuration--------------------------------------------------------*/
-
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
-
- /* USER CODE BEGIN Init */
-
- /* USER CODE END Init */
-
- /* Configure the system clock */
- SystemClock_Config();
-
- /* USER CODE BEGIN SysInit */
-
- /* USER CODE END SysInit */
-
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- /* USER CODE BEGIN 2 */
- uint8_t key_state = 0;//定义一个变量来表示按键的状态0为没被按下,1为被按下
- /* USER CODE END 2 */
-
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- if(HAL_GPIO_ReadPin(Key_GPIO_Port,Key_Pin) == 0 && key_state == 0)//判断按键是否被按下,如若被按下判断状态是否被标记为按下
- {
- HAL_Delay(5);//通过延时进行软件消抖
- if(HAL_GPIO_ReadPin(Key_GPIO_Port,Key_Pin) == 0)//再次判断按键状态——消抖处理
- {
- key_state = 1;//把按下状态标记为按下
- }
- }
- if(HAL_GPIO_ReadPin(Key_GPIO_Port,Key_Pin) == 1 && key_state == 1)//判断按键松开状态下,是否有按下标记,如若有表示前一刻被释放,需做出处理
- {
- HAL_Delay(5);//通过延时进行软件消抖
- if(HAL_GPIO_ReadPin(Key_GPIO_Port,Key_Pin) == 1)//再次判断按键状态——消抖处理
- {
- HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);//翻转led灯(按键处理操作)
- key_state = 0;//清楚标记
- }
- }
- /* USER CODE END WHILE */
-
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }
通过这个程序我们就可以实现按键的基本功能啦,对于刚接触编程的小伙伴来说可能会有一些困难,逻辑性的东西的确是需要自己去理解的,但是没关系每个人都是一步一步的脚印走过来的,天才只是少数,weib相信你们可以的!
看到上面的代码,有小聪明会问:weib你这还不是用了延时,阻塞了程序的工作。
是的,这段程序并不是完美的,它通过延时来消除按键的抖动,而其缺点就是在一定程度上对其他程序的顺序执行造成了影响。那么有没有还可以改进的地方呢?请看本关卡的"多学一招"。
刚才提到了我们通过利用延时的方式来实现按键的消抖,这种方式叫做软件消抖。
这时肯定会有好奇宝宝提问:weibweib什么是按键的消抖呢?那么有了软件消抖是不是还有硬件消抖呀?
别急,我们一个一个来讨论;
我们来康康chatAI是怎么说的吧:
按键消抖是指在单片机开发中,由于按键机械性能等因素导致按键信号产生抖动现象,需要通过技术手段进行处理的过程。按键抖动是指在按下或释放按键时,由于按键接点的物理特性,会导致按键信号在短时间内不稳定地跳变多次。
按键消抖的目的是确保按键信号的稳定性,避免抖动信号干扰系统正常的按键处理逻辑。抖动信号可能会导致误触发、多次触发或无法触发按键事件等问题。
想必它已经解释得很清楚,不需要weib多说什么了吧。各位小伙伴只需要知道按键的消抖是必要的!
我们再来康康chatAI的回答吧:
我们在刚才的使用中,利用延时的方式进行消抖就是一种常见的软件消抖,而硬件消抖常用方式是在按键两端并联一个电容,利用电容两端电压无法发生突变的特性进行消抖,如下图所示。
而在我们的实际的开发中,硬件消抖和软件消抖是并行的。
好啦~今天的内容到这也就差不不多啦,今天我们用按键控制了led的闪烁,体验了一把“宇宙”为我控制的“主宰”的快感,后面的内容会更加精彩哦。
如果,到这已经筋疲力尽的小伙伴可以结束这段旅程,你们已经很棒了,已经完成了这次旅程的目标,再好好吸收一下本次旅途中的内容就行啦。
而还有余力的小伙伴可以选择跟随weib再去习得一个秘籍从而掌握一套功法,我们继续向前吧!
在这个章节中,weib将为大家介绍一个weib在日常开发中实际用到的方法,这套方法是weib在刚入门的时候一位在嵌入式行业深耕多年的大师传授给我的,现在weib将把它传授给你们。
- /**
- * @brief Provides a tick value in millisecond.
- * @note This function is declared as __weak to be overwritten in case of other
- * implementations in user file.
- * @retval tick value
- */
- __weak uint32_t HAL_GetTick(void)
- {
- return uwTick;
- }
这也是hal库提供给我们的一个函数,其功能是获取当前系统运行时间,单位为us(微秒)
现在我们来思考一个问题,我们如何获取得到一段时间的间隔,以作为软件消抖时间判定的条件啊?
想必已经有小聪明已经得到了答案,两个时间之差!
好啦,让我们带着以上的讨论进入实际操作中。
如果我们要编写大量的代码,放在main.c里面会使其变得冗杂、可读性低,所以在一般的开发中,我们常常把函数、结构体的声明和定义放入对应的板级支持包中,以增强工程结构的稳定性、代码的可读性。
根据上述步骤,我们的bsp的框架就搭建好了,接下来就开始编程啦
main.c
- /* USER CODE BEGIN Header */
- /**
- ******************************************************************************
- * @file : main.c
- * @brief : Main program body
- ******************************************************************************
- * @attention
- *
- * Copyright (c) 2023 STMicroelectronics.
- * All rights reserved.
- *
- * This software is licensed under terms that can be found in the LICENSE file
- * in the root directory of this software component.
- * If no LICENSE file comes with this software, it is provided AS-IS.
- *
- ******************************************************************************
- */
- /* USER CODE END Header */
- /* Includes ------------------------------------------------------------------*/
- #include "main.h"
- #include "gpio.h"
-
- /* Private includes ----------------------------------------------------------*/
- /* USER CODE BEGIN Includes */
- #include "key.h"//添加bsp头文件
- /* USER CODE END Includes */
-
- /* Private typedef -----------------------------------------------------------*/
- /* USER CODE BEGIN PTD */
-
- /* USER CODE END PTD */
-
- /* Private define ------------------------------------------------------------*/
- /* USER CODE BEGIN PD */
-
- /* USER CODE END PD */
-
- /* Private macro -------------------------------------------------------------*/
- /* USER CODE BEGIN PM */
-
- /* USER CODE END PM */
-
- /* Private variables ---------------------------------------------------------*/
-
- /* USER CODE BEGIN PV */
-
- /* USER CODE END PV */
-
- /* Private function prototypes -----------------------------------------------*/
- void SystemClock_Config(void);
- /* USER CODE BEGIN PFP */
-
- /* USER CODE END PFP */
-
- /* Private user code ---------------------------------------------------------*/
- /* USER CODE BEGIN 0 */
-
- /* USER CODE END 0 */
-
- /**
- * @brief The application entry point.
- * @retval int
- */
- int main(void)
- {
- /* USER CODE BEGIN 1 */
-
- /* USER CODE END 1 */
-
- /* MCU Configuration--------------------------------------------------------*/
-
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
-
- /* USER CODE BEGIN Init */
-
- /* USER CODE END Init */
-
- /* Configure the system clock */
- SystemClock_Config();
-
- /* USER CODE BEGIN SysInit */
-
- /* USER CODE END SysInit */
-
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- /* USER CODE BEGIN 2 */
- KEY key_led = {0,0};//初始化按键
- /* USER CODE END 2 */
-
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- key_scan(&key_led,HAL_GPIO_ReadPin(Key_GPIO_Port,Key_Pin));//调用按键状态处理函数
- if(key_led.key_state == key_ok)
- {
- HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
- }
- /* USER CODE END WHILE */
-
- /* USER CODE BEGIN 3 */
- }
- /* USER CODE END 3 */
- }
-
- /**
- * @brief System Clock Configuration
- * @retval None
- */
- void SystemClock_Config(void)
- {
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};
- RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
-
- /** Initializes the RCC Oscillators according to the specified parameters
- * in the RCC_OscInitTypeDef structure.
- */
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
- RCC_OscInitStruct.HSEState = RCC_HSE_ON;
- RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
- RCC_OscInitStruct.HSIState = RCC_HSI_ON;
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
- RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
- if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
- {
- Error_Handler();
- }
-
- /** Initializes the CPU, AHB and APB buses clocks
- */
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
- |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
-
- if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
- {
- Error_Handler();
- }
- }
-
- /* USER CODE BEGIN 4 */
-
- /* USER CODE END 4 */
-
- /**
- * @brief This function is executed in case of error occurrence.
- * @retval None
- */
- void Error_Handler(void)
- {
- /* USER CODE BEGIN Error_Handler_Debug */
- /* User can add his own implementation to report the HAL error return state */
- __disable_irq();
- while (1)
- {
- }
- /* USER CODE END Error_Handler_Debug */
- }
-
- #ifdef USE_FULL_ASSERT
- /**
- * @brief Reports the name of the source file and the source line number
- * where the assert_param error has occurred.
- * @param file: pointer to the source file name
- * @param line: assert_param error line source number
- * @retval None
- */
- void assert_failed(uint8_t *file, uint32_t line)
- {
- /* USER CODE BEGIN 6 */
- /* User can add his own implementation to report the file name and line number,
- ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
- /* USER CODE END 6 */
- }
- #endif /* USE_FULL_ASSERT */
key.h
- #ifndef KEY_H
- #define KEY_H
-
-
- #include "main.h"
-
- typedef struct
- {
- uint32_t key_wait;//用于计算消抖时间
- uint8_t key_state;//用于记录按键的状态
- }KEY;//存储单个按键
-
- typedef enum
- {
- key_no = 0,
- key_yes,
- key_ok,
- }state;//按键的状态
-
- void key_scan(KEY *key,uint8_t level);//按键状态处理函数
-
- #endif /*KEY_H*/
key.c
- #include "key.h"
-
-
- void key_scan(KEY *key,uint8_t level)//传入需要检测的按键的结构体和按键当前电平
- {
- uint32_t time = HAL_GetTick();//获取当前时间
- if(!level && key->key_state == key_no)//如果按键被按下,并且未被标记
- {
- key->key_state = key_yes;//按键状态改为按下
- key->key_wait = time;//记录当前时间
- }
- if(level && key->key_state == key_yes && time - key->key_wait >= 20) //释放按键后,进行消抖
- {
- key->key_state = key_ok;// 消抖确认无误后,返回可处理状态
- }
- else if(level && (key->key_state == key_ok || (key->key_state == key_yes && time - key->key_wait < 20)))//按键在未按下状态下,消抖返回为抖动或处理完成后,消除标记
- {
- key->key_state = key_no;
- }
- }
-
-
这个功法weib是传授给给位勇者了,修炼就全靠个人咯,当然有任何问题可以通过不同的渠道来问weib。
终于在这周结束之前把这篇赶了出来,着实是不容易啊。最近事情确实是挺多的,尽量保证一周能有一篇吧。
各位可能在本篇文章中有小聪明发现了一个小彩蛋——在有些截图中会出现一个小新,嘿嘿~这是weib做的桌宠,在一年前weib用Java做了一个桌宠,但是效果是差强人意的,最近在研究qt,就用qt把桌宠给重构了,这次做出了感觉还行,想要的功能也基本实现了。如果有机会的话可以出一个关于桌宠的支线~
我们下篇主线不出意外的话是去探索中断的奥秘,争取在下周与大家见面吧~
好啦,本次的旅程到这就结束了,我是weib我们下次旅途再见!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。