【STM32】Gpio通用输入输出学习笔记(跑马灯、蜂鸣器和按键输入实验)


一、前言

1.1 开发环境

①Keil MDK V5.38a
②STM32CubemxV6.10.0
③正点原子战舰板(STM32F103ZET6)
④高速DAP仿真器
⑤MCU Package V1.8.5

1.2 GPIO电路原理

GPIO(General-purpose input/output,通用输入输出),通过与外围电路的直接连接,达到获取外围电路电平状态(输入),或者控制外围电路电平状态(输出)。GPIO的电路原理框图如下所示:
在这里插入图片描述
由上图可知,GPIO存在3种输入状态:模拟/复用/普通 + 上/下/浮空输入,将输入上/下拉的目的,在于指定未连接外围电路时的输入电平状态。上拉表示在未连接外围电路时读取到IO输入电平应为高;下拉表示在未连接外围电路时读取到IO输入电平应为低,浮空则表示不关心此电平状态。
GPIO存在2种输出状态:普通/复用 + 推挽/开漏输出,将输出推挽/开漏的目的,在于指定输出高电平的强度。推挽输出时的高电平强度大,相当于把I/O直接连到了电源,此时若将I/O连接到GND将会发生短路,大概率会烧毁I/O,通常在控制外围电路时使用此功能(例:点亮/熄灭LED);开漏输出时需要通过外围电路的上拉才能输出高电平,此时若将I/O连接到GND不会发生短路现象,通常在仅需接收外围电路传输信息时使用此功能。

1.3 板卡电路原理

1.3.1 按键电路原理

由下图可知,Key0、Key1和Key2会在按键按下时,将对应的Gpio管脚连接到Gnd,即将Gpio管脚拉低,当按键没有按下时Gpio管脚应该默认为高电平,用来区分按键按下和未按下两种状态;所以需要使能Gpio管脚的输入模式,并配置Gpio管脚上拉。同理Wkup连接的Gpio管脚应该配置为输入模式,并配置Gpio管脚下拉。
在这里插入图片描述

1.3.2 Led电路原理

由下图可知,当Gpio管脚输出低电平时,会在Led的两端产生电压差,从而驱动Led点亮,当Gpio管脚输出高电平时,会在Led的两端没有电压差,Led熄灭;所以需要使能Gpio管脚为输出模式,并配置Gpio管脚上拉。
在这里插入图片描述

1.3.3 Beep电路原理

由下图可知,当Gpio管脚输出高电平时,会在导通三极管,同时Beep的两端会产生电压差,从而驱动Beep鸣叫,当Gpio管脚输出低电平时,会在关断三极管,同时Beep的两端没有电压差,Beep停止鸣叫;所以需要使能Gpio管脚为输出模式,并配置Gpio管脚下拉。
在这里插入图片描述


二、功能实现

2.1 配置STM32Cubemx工程

1、配置MCU调试功能(PS:若不配置该参数可能出现程序下载一次后,再次下载失败的情况)
在这里插入图片描述
根据实际调试器与MCU引脚的连接数量选择调试参数,通常调试器除了VCC和GND外,只有2个管脚,需要选择Serial Wire。

2、配置连接LED1的GPIO管脚(鼠标左键选择GPIO_Output即可,鼠标右键选择Enter User Label即可设置GPIO管脚名称)
在这里插入图片描述

3、配置连接BEEP的GPIO管脚
在这里插入图片描述

4、配置连接KEY2的GPIO管脚
在这里插入图片描述

5、配置连接KEY1的GPIO管脚

在这里插入图片描述

6、配置连接KEY0的GPIO管脚
在这里插入图片描述

7、配置MCU时钟树,默认使用外部时钟源,并配置系统时钟为72MHz
在这里插入图片描述
在这里插入图片描述

8、生成Keil MDK工程
在这里插入图片描述
若不勾选"Generate ......",则生成的所有的驱动代码都会在main.c中;勾选后生成的驱动代码将存在于各自的驱动文件。
在这里插入图片描述
在这里插入图片描述

2.2 Keil MDK工程编码

2.2.1 按键驱动代码

Key.c:

#include "Key.h"

static GPIO_TypeDef *Key_Gpio_Port[4] = { GPIOE, GPIOE, GPIOE, GPIOA };
static uint32_t Key_Gpio_Pinx[4] = { KEY0_Pin, KEY1_Pin, KEY2_Pin, WKUP_Pin };

/*
 * @brief 初始化独立按键
 * @param Key_Index:独立按键索引号
*/
void Key_Init(KEY_INDEX_E Key_Index)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* 使能Key管脚端口时钟 */
    if((KEY_INDEX_0 == Key_Index) || \
       (KEY_INDEX_1 == Key_Index) || \
       (KEY_INDEX_2 == Key_Index))
    {
        __HAL_RCC_GPIOE_CLK_ENABLE();
        GPIO_InitStruct.Pull = GPIO_PULLUP;
    }
    else
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();
        GPIO_InitStruct.Pull = GPIO_PULLDOWN;
    }

    /* 设置Key管脚初始化参数并完成初始化 */
    GPIO_InitStruct.Pin = Key_Gpio_Pinx[Key_Index];
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(Key_Gpio_Port[Key_Index], &GPIO_InitStruct);
}

/*
 * @brief 初始化独立按键
 * @param Key_Index:独立按键索引号
 * @retval 独立按键当前状态
*/
KEY_STATUS_E Key_Get_Status(KEY_INDEX_E Key_Index)
{
    GPIO_PinState pin_status = GPIO_PIN_RESET;
    KEY_STATUS_E retval = KEY_STATUS_NO_PRESS;

    pin_status = HAL_GPIO_ReadPin(Key_Gpio_Port[Key_Index], \
                                  Key_Gpio_Pinx[Key_Index]);
    if(KEY_INDEX_WKUP == Key_Index)
    {
        if(GPIO_PIN_SET == pin_status)
        {
            retval = KEY_STATUS_PRESS;
        }
    }
    else
    {
        if(GPIO_PIN_RESET == pin_status)
        {
            retval = KEY_STATUS_PRESS;
        }
    }

    return retval;
}

/*
 * @brief 反初始化独立按键
 * @param Key_Index:独立按键索引号
*/
void Key_DeInit(KEY_INDEX_E Key_Index)
{
    HAL_GPIO_DeInit(Key_Gpio_Port[Key_Index], Key_Gpio_Pinx[Key_Index]);
}

Key.h

#ifndef __KEY_H
#define __KEY_H

#include "main.h"

/* 独立按键索引号枚举 */
typedef enum 
{
    KEY_INDEX_0 = 0,
    KEY_INDEX_1,
    KEY_INDEX_2,
    KEY_INDEX_WKUP,
} KEY_INDEX_E;

/* 独立按键状态枚举 */
typedef enum 
{
    KEY_STATUS_NO_PRESS = 0,
    KEY_STATUS_PRESS,
} KEY_STATUS_E;

/*
 * @brief 初始化独立按键
 * @param Key_Index:独立按键索引号
*/
extern void Key_Init(KEY_INDEX_E Key_Index);

/*
 * @brief 初始化独立按键
 * @param Key_Index:独立按键索引号
 * @retval 独立按键当前状态
*/
extern KEY_STATUS_E Key_Get_Status(KEY_INDEX_E Key_Index);

/*
 * @brief 反初始化独立按键
 * @param Key_Index:独立按键索引号
*/
extern void Key_DeInit(KEY_INDEX_E Key_Index);

#endif /* __KEY_H */


2.2.2 LED灯驱动代码

#include "Led.h"

static GPIO_TypeDef *Led_Gpio_Port[2] = { GPIOB, GPIOE };
static uint32_t Led_Gpio_Pinx[2] = { LED0_Pin, LED1_Pin };

/* 
 * @brief 初始化Led
 * @param Led_Index:Led索引号
 */
void Led_Init(LED_INDEX_E Led_Index)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* 使能Led管脚端口时钟 */
    if(LED_INDEX_0 == Led_Index)
    {
        __HAL_RCC_GPIOB_CLK_ENABLE();
    }
    else
    {
        __HAL_RCC_GPIOE_CLK_ENABLE();
    }

    /* 设置Led管脚初始化电平,确保初始化后Led处于关闭状态 */
    HAL_GPIO_WritePin(Led_Gpio_Port[Led_Index], \
                      Led_Gpio_Pinx[Led_Index], GPIO_PIN_SET);

    /* 设置Led管脚初始化参数并完成初始化 */
    GPIO_InitStruct.Pin = Led_Gpio_Pinx[Led_Index];
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(Led_Gpio_Port[Led_Index], &GPIO_InitStruct);
}

/*
 * @brief 开启Led
 * @param Led_Index:Led索引号
 */
void Led_Open(LED_INDEX_E Led_Index)
{
    HAL_GPIO_WritePin(Led_Gpio_Port[Led_Index], \
                      Led_Gpio_Pinx[Led_Index], GPIO_PIN_RESET);
}

/*
 * @brief 关闭Led
 * @param Led_Index:Led索引号
 */
void Led_Close(LED_INDEX_E Led_Index)
{
    HAL_GPIO_WritePin(Led_Gpio_Port[Led_Index], \
                      Led_Gpio_Pinx[Led_Index], GPIO_PIN_SET);
}

/*
 * @brief 反初始化Led
 * @param Led_Index:Led索引号
 */
void Led_Deinit(LED_INDEX_E Led_Index)
{
    HAL_GPIO_DeInit(Led_Gpio_Port[Led_Index], Led_Gpio_Pinx[Led_Index]);
}

Led.h

#ifndef __LED_H
#define __LED_H

#include "main.h"

/* Led索引号枚举类型 */
typedef enum 
{
    LED_INDEX_0 = 0,
    LED_INDEX_1,
} LED_INDEX_E;

/* 
 * @brief 初始化Led
 * @param Led_Index:Led索引号
 */
extern void Led_Init(LED_INDEX_E Led_Index);

/*
 * @brief 开启Led
 * @param Led_Index:Led索引号
 */
extern void Led_Open(LED_INDEX_E Led_Index);

/*
 * @brief 关闭Led
 * @param Led_Index:Led索引号
 */
extern void Led_Open(LED_INDEX_E Led_Index);

/*
 * @brief 反初始化Led
 * @param Led_Index:Led索引号
 */
extern void Led_Deinit(LED_INDEX_E Led_Index);

#endif /* __LED_H */

2.2.3 Beep驱动代码

Beep.c

#include "Beep.h"

/* 
 * @brief 初始化蜂鸣器
 */
void Beep_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* 使能Beep管脚端口时钟 */
    __HAL_RCC_GPIOB_CLK_ENABLE();

    /* 设置Beep管脚初始化电平,确保初始化后蜂鸣器处于关闭状态 */
    HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);

    /* 设置Beep管脚初始化参数并完成初始化 */
    GPIO_InitStruct.Pin = BEEP_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(BEEP_GPIO_Port, &GPIO_InitStruct);
}

/*
 * @brief 开启蜂鸣器
 */
void Beep_Open(void)
{
    HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_SET);
}

/*
 * @brief 关闭蜂鸣器
 */
void Beep_Close(void)
{
    HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
}

/*
 * @brief 反初始化蜂鸣器
 */
void Beep_Deinit(void)
{
    HAL_GPIO_DeInit(BEEP_GPIO_Port, BEEP_Pin);
}

Beep.h

#ifndef __BEEP_H
#define __BEEP_H

#include "main.h"

/* 
 * @brief 初始化蜂鸣器
 */
extern void Beep_Init(void);

/*
 * @brief 开启蜂鸣器
 */
extern void Beep_Open(void);

/*
 * @brief 关闭蜂鸣器
 */
extern void Beep_Close(void);

/*
 * @brief 反初始化蜂鸣器
 */
extern void Beep_Deinit(void);

#endif /* __BEEP_H */

2.2.4 功能测试代码

void Drive_Func_Test(void)
{
    Led_Key_Beep_Test();
}

static void Led_Key_Beep_Test(void)
{
    static uint8_t Led0_Status = 0;
    static uint8_t Led1_Status = 0;
    static uint8_t Beep_Status = 0;
    static uint32_t Key_Shake_Off_Cnt = 0;

    /* 初始化Led */
    Led_Init(LED_INDEX_0);
    Led_Init(LED_INDEX_1);

    /* 初始化Key */
    Key_Init(KEY_INDEX_0);
    Key_Init(KEY_INDEX_1);
    Key_Init(KEY_INDEX_2);
    Key_Init(KEY_INDEX_WKUP);

    /* 初始化Beep */
    Beep_Init();

    while(1)
    {
        if(Key_Shake_Off_Cnt++ >= 5)
        {
            Key_Shake_Off_Cnt = 0;

            /* Key0按下后Led0状态翻转 */
            if(KEY_STATUS_PRESS == Key_Get_Status(KEY_INDEX_0))
            {
                if(0 == Led0_Status)
                {
                    Led_Open(LED_INDEX_0);
                }
                else
                {
                    Led_Close(LED_INDEX_0);
                }
                Led0_Status = !Led0_Status;
            }

            /* Key1按下后Led1状态翻转 */
            if(KEY_STATUS_PRESS == Key_Get_Status(KEY_INDEX_1))
            {
                if(0 == Led1_Status)
                {
                    Led_Open(LED_INDEX_1);
                }
                else
                {
                    Led_Close(LED_INDEX_1);
                }
                Led1_Status = !Led1_Status;
            }

            /* Key2按下后Beep状态翻转 */
            if(KEY_STATUS_PRESS == Key_Get_Status(KEY_INDEX_2))
            {
                if(0 == Beep_Status)
                {
                    Beep_Open();
                }
                else
                {
                    Beep_Close();
                }
                Beep_Status = !Beep_Status;
            }

            /* Wkup按下后Beep鸣叫100ms */
            if(KEY_STATUS_PRESS == Key_Get_Status(KEY_INDEX_WKUP))
            {
                Beep_Open();
                HAL_Delay(100);
                Beep_Close();
            }
        }

        HAL_Delay(10);
    }
}

2.2.5 Main函数代码

extern void Drive_Func_Test(void);

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 */
  Drive_Func_Test();
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

2.2.5 Keil MDK调试参数配置

在这里插入图片描述
若不勾选"Reset and Run",则通过调试器下载代码后需要在手动复位,程序才能运行起来;勾选后可不用再手动复位,程序就可以运行起来。
在这里插入图片描述

2.2.6 下载验证

①按下Key0后,Led0状态翻转
②按下Key1后,Led1状态翻转
③按下Key2后,Beep状态翻转
④按下Wkup后,Beep鸣叫100ms后关闭


三、参考资料

[1]正点原子战舰板原理图-WarShip STM32F1_V3.4_SCH
[2]STM32F1开发指南-HAL库版本_V1.1
[3]STM32中文参考手册_V10
[4]Cortex-M3权威指南(中文)

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值