【2】STM32F103嵌入式编程之路:按键控制LED灯

您所在的位置:网站首页 STM32F103R6仿真跑马灯 【2】STM32F103嵌入式编程之路:按键控制LED灯

【2】STM32F103嵌入式编程之路:按键控制LED灯

#【2】STM32F103嵌入式编程之路:按键控制LED灯| 来源: 网络整理| 查看: 265

按键控制LED灯

我所使用的神舟3号开发板除了RESET按钮外,还有4个实体按钮。按照通用教程的习惯顺序,在学习LED跑马灯后一般是选择学习蜂鸣器的使用。但是蜂鸣器实在是太吵了(笑),且和LED相比难度甚至更低,这里就先做一个跳过。

现在让我们来通过开发板上的实体按钮来控制4个LED灯,任务是做到按住按钮可以使某个灯常亮。

硬件部分

首先我们看电路图 电路图 得到的对应关系如下: 对应关系 而上一节里也提到了LED灯的电路关系,这里再重复一次: 对应关系

代码部分 头文件及定义

定义其实与上一篇中的LED等地方基本相同,此处就不再赘述:

#include "stm32f10x.h" #include "stm32_eval.h" #include #define RCC_GPIO_LED RCC_APB2Periph_GPIOF #define LEDn 4 #define GPIO_LED GPIOF #define DS1_PIN GPIO_Pin_6 #define DS2_PIN GPIO_Pin_7 #define DS3_PIN GPIO_Pin_8 #define DS4_PIN GPIO_Pin_9 #define GPIO_LED_ALL DS1_PIN |DS2_PIN |DS3_PIN |DS4_PIN #define RCC_KEY1 RCC_APB2Periph_GPIOD #define GPIO_KEY1_PORT GPIOD #define GPIO_KEY1 GPIO_Pin_3 #define RCC_KEY2 RCC_APB2Periph_GPIOA #define GPIO_KEY2_PORT GPIOA #define GPIO_KEY2 GPIO_Pin_8 #define RCC_KEY3 RCC_APB2Periph_GPIOC #define GPIO_KEY3_PORT GPIOC #define GPIO_KEY3 GPIO_Pin_13 #define RCC_KEY4 RCC_APB2Periph_GPIOA #define GPIO_KEY4_PORT GPIOA #define GPIO_KEY4 GPIO_Pin_0 #define GPIO_KEY_ANTI_TAMP GPIO_KEY3 #define GPIO_KEY_WEAK_UP GPIO_KEY4 /* Values magic to the Board keys */ #define NOKEY 0 #define KEY1 1 #define KEY2 2 #define KEY3 3 #define KEY4 4 功能函数模块

首先我们要写一个延时函数,这里的延时函数是不精准的,如果要做到精准模拟时间会涉及到CPU频率等问题,后面遇到了会再解释。

static void Delay(__IO uint32_t nCount) { for (; nCount != 0; nCount--); }

这里的__IO在嵌入式编程中非常常见,可以追一下代码:

#define __IO volatile

可以看到,这里__IO的定义是C语言中的volatile,volatile的作用简而言之就是精确编译,禁止使用优化,因为编辑器往往会因为追求效率等原因,从缓存栈中直接读取参数,但是设置了volatile后每一次遇到都会直接从内存中读取,保证了编译中的精确度。

接着我们来对按键进行初始化操作,由于按键按下后是输出低电平,所以我们需要给IO口默认设置为高电平,这下按下按键后才能有电平的反馈。这里我们设置为上拉输出GPIO_Mode_IPU,就可以有这种效果:

void GPIO_KEY_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; /* Configure KEY1 Button */ RCC_APB2PeriphClockCmd(RCC_KEY1, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_KEY1; GPIO_Init(GPIO_KEY1_PORT, &GPIO_InitStructure); /* Configure KEY2 Button */ RCC_APB2PeriphClockCmd(RCC_KEY2, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_KEY2; GPIO_Init(GPIO_KEY2_PORT, &GPIO_InitStructure); /* Configure KEY3 Button */ RCC_APB2PeriphClockCmd(RCC_KEY3, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_KEY3; GPIO_Init(GPIO_KEY3_PORT, &GPIO_InitStructure); /* Configure KEY4 Button */ RCC_APB2PeriphClockCmd(RCC_KEY4, ENABLE); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Pin = GPIO_KEY4; GPIO_Init(GPIO_KEY4_PORT, &GPIO_InitStructure); }

这里我们可以观察到,GPIO_InitStructure实际上被使用了4次。而我们后续的代码中实际使用的都是初始化完毕的GPIO_KEYx_PORT,对于GPIO_InitStructure只是为了端口的初始化起到一个传参的作用。

接下来我们看按键的监听与读取:

u8 ReadKeyDown(void) { /* 1 key is pressed */ if(!GPIO_ReadInputDataBit(GPIO_KEY1_PORT, GPIO_KEY1)) { return KEY1; } /* 2 key is pressed */ if(!GPIO_ReadInputDataBit(GPIO_KEY2_PORT, GPIO_KEY2)) { return KEY2; } /* 3 key is pressed */ if(!GPIO_ReadInputDataBit(GPIO_KEY3_PORT, GPIO_KEY3)) { return KEY3; } /* 4 key is pressed */ if(!GPIO_ReadInputDataBit(GPIO_KEY4_PORT, GPIO_KEY4)) { return KEY4; } /* No key is pressed */ else { return NOKEY; } }

可以注意到这个函数的返回值是一个u8类型的,如果读取到某个按键按下,将会返回对应的KEY值。当中值得注意的是GPIO_ReadInputDataBit( )这个函数,这里这个函数的用法是读取是否有电平输入,当你设置KEY为上拉输入,去读IO口状态的时候,若你没有按下按键,你读出来的IO值是1,(因为上拉输入把IO口拉高),当你按下按键的时候,你读出来的IO值是0,(因为按下按键把IO拉低) 。设置为下拉输入时结果相反。如果对于此处有不理解的话,我们可以跟进一下这个函数:

typedef enum { Bit_RESET = 0, Bit_SET //枚举类型中,如果本行没设置参数,那么默认为上一行的值+1 }BitAction; uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { uint8_t bitstatus = 0x00; /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GET_GPIO_PIN(GPIO_Pin)); if ((GPIOx->IDR & GPIO_Pin) != (uint32_t)Bit_RESET) { bitstatus = (uint8_t)Bit_SET; } else { bitstatus = (uint8_t)Bit_RESET; } return bitstatus; }

assert_param()是用于检查数据类型的函数。结合上面的代码,如果采用上拉输出,那么在不按按键的情况下,输入状态寄存器IDR应该有一个输入状态,同时GPIO_Pin也保持高电平,所以根据逻辑运算,bitstatus将被置1。而在按下按键以后,不满足上述逻辑表达式,bitstatus将被置0。

可以总结出GPIO_ReadInputDataBit( )这个函数实际上逻辑可以简化为读取这个IO口的电位,如果是高电平则返回1,低电平则返回0。非常好记。

而在按键读取状态的代码中,由于按键是输出低电平的,所以需要加!进行取反,这样读取到按键按下以后,逻辑表达式就会取true而读取到相应按键。

LED灯的相关设置如下:

void LED_config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_GPIO_LED | RCC_APB2Periph_AFIO , ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_LED_ALL; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIO_LED, &GPIO_InitStructure); } void Led_Turn_on_all(void) { GPIO_ResetBits(GPIO_LED, GPIO_LED_ALL); } void Led_Turn_on_1(void) { GPIO_ResetBits(GPIO_LED, DS1_PIN); } void Led_Turn_on_2(void) { GPIO_ResetBits(GPIO_LED, DS2_PIN ); } void Led_Turn_on_3(void) { GPIO_ResetBits(GPIO_LED, DS3_PIN); } void Led_Turn_on_4(void) { GPIO_ResetBits(GPIO_LED, DS4_PIN); } void Led_Turn_off_all(void) { GPIO_SetBits(GPIO_LED, GPIO_LED_ALL); } void Led_Turn_on(u8 led) { Led_Turn_off_all(); switch(led) { case 0: Led_Turn_on_1(); break; case 1: Led_Turn_on_2(); break; case 2: Led_Turn_on_3(); break; case 3: Led_Turn_on_4(); break; default: Led_Turn_off_all(); break; } } 主函数

搞定上面几个功能函数后,主函数的逻辑就非常简单了:

int main(void) { u8 KeyNum = 0; LED_config(); //启动LED灯初始化 Led_Turn_on_all(); //全部开启 Delay(6000000); //先让灯亮一会 Led_Turn_off_all(); //全部关闭 GPIO_KEY_Config(); //按键初始化 while (1) { KeyNum = ReadKeyDown(); //读取按键 Led_Turn_on(KeyNum-1); //点亮相应的灯 } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3