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

按键控制LED灯

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

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

硬件部分

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

代码部分

头文件及定义

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

#include "stm32f10x.h"
#include "stm32_eval.h"
#include <stdio.h>


#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);    //点亮相应的灯
  }  
}

猜你喜欢

转载自blog.csdn.net/weixin_44234948/article/details/85237067