完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
今天我要通过库函数操作stm32f407上的按键实现控制LED小灯以及蜂鸣器,实现的功能如下:
LED的初始化配置 想要点亮LED,首先我们需要确定LED在stm32f407开发板上的硬件威廉希尔官方网站 连接,如下图所示: 从图上可以看出,两个LED属于共阳极连接,也就是说,当GPIO口输出高电平时,LED熄灭,当GPIO口输出低电平时,LED点亮,那么只需要配置好GPIO的输出,即可实现LED的亮灭,所以,我们还得知道控制LED的GPIO口是哪一个,毕竟stm32f4有70个用于控制LED的GPIO口; 我们可以从从上图中看到,控制LED0的是GPIOF_9,控制LED1的是GPIOF_10,同时我们还可以看到GPIOF_8是用来控制蜂鸣器(BEEP)的。知道了LED的硬件连接后,接下来就可以开始通过库函数配置我们的GPIO了。 通过库函数来配置GPIO,我们需要用到的库函数是GPIO_Init,对于这个函数,有如下的说明: void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); 从GPIO_Init的函数原型可以看出,需要用到两个参数,这两个参数分别是:
该结构体的成员呢有以下几个: uint32_t GPIO_Pin; //该参数对应的是某组GPIO中的某一个,毕竟一组GPIO有16个GPIO引脚 //就如前面说到控制LED0的GPIO口是GPIOF_9;控制LED1的GPIO口是GPIOF_10; /*下面这几个参数我就放到正文里细说吧*/ GPIOMode_TypeDef GPIO_Mode; GPIOSpeed_TypeDef GPIO_Speed; GPIOOType_TypeDef GPIO_OType; GPIOPuPd_TypeDef GPIO_PuPd; GPIO_InitStruct的后面4个参数就是用于配置GPIO模式寄存器的,我在第一篇文章里说到,配置GPIO的模式需要用到4个寄存器,如果你没有看过我的第一篇文章的话,没有关系,我再来详细的说明一下:这里说到的寄存器分别是:
下面我就来具体说说要控制LED,这4个GPIOF分别需要怎么配置: >GPIOF_MODER GPIOF_MODER寄存器可选的配置分别有:输入模式(复位状态),通用输出模式,复用功能模式,模拟模式,这里很容易想到,我们要向控制LED0,需要通过GPIOF_9输出高电平或低电平,所以毫无疑问我们需要将GPIOF_MODER寄存器配置成通用输出模式,对于该寄存器的四种模式在库函数中有如下的说明: GPIO_Mode_IN = 0x00, /*输入模式*/ GPIO_Mode_OUT = 0x01, /*通用输出模式*/ GPIO_Mode_AF = 0x02, /*复用功能模式*/ GPIO_Mode_AN = 0x03 /*模拟模式*/ 在此我们只需选择GPIO_Mode_OUT即可,一句话代码如下: led_gpio.GPIO_Mode = GPIO_Mode_OUT; >GPIOF_OSPEEDR GPIOF_OSPEEDR寄存器可选的配置分别有:2MHz,25MHz,50MHz,100MHz,在此我们选择100MHz的端口输出速度(对于速度的选择我也不太明白会有啥影响,或者说选择的标准是啥,希望明白的大佬可以在评论区里解答下,非常感谢),对于这4中选择在库函数中有如下定义: typedef enum { GPIO_OType_PP = 0x00, //推挽输出 GPIO_OType_OD = 0x01 //开漏输出 }GPIOOType_TypeDef; 所以一句话代码如下: led_gpio.GPIO_Speed = GPIO_Speed_100MHz; >GPIOx_OTYPER GPIOF_OSPEEDR寄存器可选的配置分别有:推挽输出,开漏输出,在此我们选择推挽输出模式(同样不明白这两种模式的区别以及选择标准,希望有大佬可以在评论区解答下,感谢),对于这两种输出模式在库函数中有如下的定义: GPIO_PuPd_NOPULL = 0x00, GPIO_PuPd_UP = 0x01, GPIO_PuPd_DOWN = 0x02 所以一句话代码如下: led_gpio.GPIO_OType = GPIO_OType_PP; >GPIOx_PUPDR GPIOF_OSPEEDR寄存器可选的配置分别有:浮空,上拉,下拉,GPIOF_OSPEEDR的配置就比较自由,在此我选择上拉,以确保开发板上电时是熄灭的,对于这3种模式在库函数中有如下定义: GPIO_PuPd_NOPULL = 0x00, GPIO_PuPd_UP = 0x01, GPIO_PuPd_DOWN = 0x02 所以一句话代码如下 led_gpio.GPIO_PuPd = GPIO_PuPd_UP; 到此,LED的GPIO配置就完成了,注意LED0和LED1配置的GPIO_pin是有区别的,同时不要忘了在调用GPIO_Init之前,一定要先使能GPIOF的外设时钟,这里需要用到的函数为: void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState) 对于该函数的第1个参数用来指明初始化的是哪一个外设时钟,有如下定义(只截取了一段): #define RCC_AHB1Periph_GPIOA ((uint32_t)0x00000001) #define RCC_AHB1Periph_GPIOB ((uint32_t)0x00000002) #define RCC_AHB1Periph_GPIOC ((uint32_t)0x00000004) #define RCC_AHB1Periph_GPIOD ((uint32_t)0x00000008) #define RCC_AHB1Periph_GPIOE ((uint32_t)0x00000010) #define RCC_AHB1Periph_GPIOF ((uint32_t)0x00000020) #define RCC_AHB1Periph_GPIOG ((uint32_t)0x00000040) 在此我们选择RCC_AHB1Periph_GPIOF即可,对于第2个参数是用于说明是否要初始化的,具体的定义如下: #define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE)) 很明显我们要选择ENABLE 那么到这里第一个阶段的工作就完成了,我们可以将以上的代码封装成一个LED初始化的.c文件,只需要在main函数中调用即可,led_init.c代码如下: void led_init(void){ GPIO_InitTypeDef led_gpio; //定义的GPIO模式初始化结构体 /*使能GPIOF外设时钟*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); /*配置控制LED的GPIOF_9和GPIOF_10*/ led_gpio.GPIO_Pin = GPIO_Pin_9||GPIO_Pin_10;//采用或运算可同时对LED1和LED0进行初始化 led_gpio.GPIO_Mode = GPIO_Mode_OUT; led_gpio.GPIO_Speed = GPIO_Speed_100MHz; led_gpio.GPIO_OType = GPIO_OType_PP; led_gpio.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOF,&led_gpio); } BEEP(蜂鸣器)的初始化配置 在了解了GPIO的配置过程后,后续的共组就容易的多了。同样的,想要配置好BEEP,首先我们需要先知道BEEP的硬件是怎样连接的,从而知道应该对哪一个GPIO口进行配置,从前面的LED的硬件连接图上可以找的BEEP连接的是GPIOF_8,那么下面我直接上代码,详细的内容见代码的注释,毕竟今天的重点是按键: void beep_init(){ GPIO_InitTypeDef beep_gpio; //定义的GPIO模式初始化结构体 /*使能GPIOF外设时钟*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE); /*配置控制BEEP的GPIOF_8*/ beep_gpio.GPIO_Mode = GPIO_Mode_OUT; //配置GPIO模式为通用输出模式 beep_gpio.GPIO_OType = GPIO_OType_PP; //配置GPIO输出模式为推挽输出 beep_gpio.GPIO_Pin = GPIO_Pin_8; //选择配置8号引脚 beep_gpio.GPIO_PuPd = GPIO_PuPd_UP; //配置GPIO端口为上拉模式 beep_gpio.GPIO_Speed = GPIO_Speed_100MHz; //配置GPIO输出速度为100MHz GPIO_Init(GPIOF,&beep_gpio); //调用GPIO初始化函数 } KEY的初始化配置与扫描 KEY的初始化配置 首先逃不掉的还是硬件连接图: 从图中可以看出,对于按键KEY0,KEY1,KEY2,当它们连接到的GPIO端口为上拉模式时,如按键按下,此时GPIO口读到低电平,若按键没有按下,则GPIO口读到高电平,二对于KEY_UP来说就正好相反,当它对应的GPIO口为下拉模式时,没有按键按下,GPIO口读到第电平,按键按下则读到高点配,也就意味着我们对KEY_num和KEY_UP的配置是不一样的,同时,我们现在需要的不再是GPIO的通用输出模式,而是输入模式了,而且此处我们不需要再配置GPIO的输出模式(即推挽输出或者开漏输出),有了这样的认识后,我么们再来确定需要配置哪写GPIO口吧: 由上图可以知道,对于KEY0,KEY1,KEY2需要配置的GPIO分别是GPIOE_4,GPIOE_3,GPIOE_2,而KEY_UP配置的GPIO是GPIOA_0。在此,模仿LED与BEEP的配置过程可以有一下代码: void key_init(void){ GPIO_InitTypeDef key_gpio; //定义GPIO初始化的结构体 /*使能GPIOE和GPIOA的外设时钟*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOA,ENABLE); /*配置GPIOE_4 & GPIOE_3 & GPIOE_2*/ key_gpio.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_3|GPIO_Pin_2;//采用或运算可同时对KEY0,KEY1和KEY2进行初始化 key_gpio.GPIO_Mode = GPIO_Mode_IN; //配置GPIO模式为输入模式 key_gpio.GPIO_Speed = GPIO_Speed_100MHz;//配置GPIO输出速度为100MHz key_gpio.GPIO_PuPd = GPIO_PuPd_UP; //配置GPIO端口为上拉模式 GPIO_Init(GPIOE,&key_gpio); //调用GPIO初始化函数 /*ÅäÖÃGPIOA_0*/ key_gpio.GPIO_Pin = GPIO_Pin_0; //对KEY_UP进行初始化 key_gpio.GPIO_PuPd = GPIO_PuPd_DOWN; //配置GPIO端口为下拉模式 GPIO_Init(GPIOA,&key_gpio); //调用GPIO初始化函数 } KEY的扫描 想要按下不同的按键实现不同的功能,就需要知道按下的键是哪一个,然后通过按下的按键控制相应的设备,就需要循环扫描按键以确定键值,下面使用伪代码说明一下按键扫描的过程: //key_scan.c void key_scan{ if(有按键按下){ 延时10ms消抖; if(确实有按键按下) return 键值 else return 无效值; } } //main.c int main(){ while(1){ int key = key_scan(); switch(key){ case 1 : 点亮LED;break; case 2 : 熄灭LED;break; ... } 延时10ms } } 这样便实现了通过按键控制不同设备了,但是存在一个问题,当我按下某一个按键不放时,key_scan函数永远检测到有按键按下并且返回该键键值,再main函数中就会一直点亮或熄灭LED,如何避免呢,在此可以很巧妙的运用static定义一个静态的变量作为按键按下与否的标志位(简单来说就是保存一下上一次按键的结果),直接上伪代码解释(只需再原来的按键扫描的基础上修改即可) //key_scan.c void key_scan{ static KEY_STATE = 1; if(KEY_STATE = 1 && 有按键按下){ 延时10ms消抖; KEY_STATE = 0 //按键有效从而更新状态 if(确实有按键按下) return 键值 else if(没有按键按下) KEY_STATE= 1;//重新更新状态 } return 0; } 可能不好理解,可以试着多循环几次应该就会明朗许多,那么具体的按键扫描函数如下所示: u8 key_scan(u8 mode){ static u8 key_state = 1; if(mode) key_state = 1; if(key_state && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WKUP == 1)){ delay_ms(10); key_state = 0; if(KEY0 == 0) return 1; else if(KEY1 == 0) return 2; else if(KEY2 == 0) return 3; else if(WKUP == 1) return 4; }else if(KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WKUP == 0) key_state = 1; return 0; } 在此需要说明的是:判断是否有按键按下,我们需要调用函数实现 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) 在以上的按键扫描函数中KEY0 == 0这样的表示即为GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4),是在头文件中进行了宏定义而已,具体如下: //key.h #define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) #define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) #define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) #define WKUP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) 分别对应判读KEY0,KEY1,KEY2以及KEY_UP是否按下。 至此所有的流程都已搞定,接下啦就是main函数的类容了: int main(void){ u8 key; delay_init(168); led_init(); beep_init(); key_init(); LED0 = 0; LED1 = 0; while(1){ key = key_scan(0); if(key){ switch(key){ case LED0_PRES: LED0 = !LED0; break; case LED1_PRES: LED1 = !LED1; break; case BEEP_PRES: BEEP = !BEEP; break; case CHAG_PRES: { LED0 = !LED0; LED1 = !LED1; } break; } }else delay_ms(10); } } 同样这里需要注意的是对于LED和蜂鸣器的操作我采用了位操作的方式,位操作的相关内容可以参考我上一篇的文章,这里也是采用的宏定义方式,具体如下: //led.h #define LED0 PFout(9) #define LED1 PFout(10) //beep.h #define BEEP PFout(8) |
|
|
|
只有小组成员才能发言,加入小组>>
3322 浏览 9 评论
3000 浏览 16 评论
3497 浏览 1 评论
9070 浏览 16 评论
4090 浏览 18 评论
1191浏览 3评论
613浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
603浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2341浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1899浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-28 23:01 , Processed in 1.271138 second(s), Total 49, Slave 40 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (威廉希尔官方网站 图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号