STM32自学笔记(寄存器)——GPIO input(2)

STM32自学笔记(寄存器)——GPIO output(1)

VSCode代码格式化

前排贴前置文章

目录

一、硬件

二、基础知识

当GPIO被配置为输入模式时:

三、代码实现

1.RCC宏定义

2.GPIO宏定义

3.GPIO配置

4.LED代码

5.KEY代码

6.主函数

7.调试和下载验证


一、硬件

开发板为正点原子stm32f103rct6mini板

LED:LED0->PA8, LED1->PD2, 阳极均连接VCC3.3v, 低电平导通

KEY: KEY0->PC5, KEY1->PA15, 按下接通GND

二、基础知识

当GPIO被配置为输入模式时:

1.输出缓存器被禁用

即图中所框部分被禁用, 可以看到FIgure15的Output驱动电路部分用了开路表示

禁用输出缓存器是为了确保引脚能够准确读取外部信号,如果在输入模式下不禁用输出缓存器,内部的输出信号可能会与外部输入信号发生冲突,导致读取的信号不准确。同时确保引脚处于高阻抗状态,引脚不会输出任何信号, 只会被动读取外部的信号电平。

2.施密特触发器输入被激活

施密特触发器的作用是去噪声和抖动,通过两个阈值输出干净的波形

上阈值电压(Upper Threshold):输入信号必须超过这个电压,输出才会从低电平切换为高电平。

下阈值电压(Lower Threshold​):输入信号必须低于这个电压,输出才会从高电平切换为低电平。

尽管施密特触发器能够过滤掉小的电平变化,但如果按键在这个时间内反复抖动,仍有可能会在代码中造成多次误判。例如,在一次按下动作中,可能会误判按键被按下了多次,所以软件消抖还是需要的。

3.弱上拉电阻和弱下拉电阻可以启动和关闭(浮空)

当一个引脚配置为输入模式时,如果没有外部连接或驱动信号,这个引脚为浮空状态,电平可能不稳定。在这种情况下,输入引脚可能会捕获到噪声或随机信号,导致不确定的高/低状态。为了解决这个问题,可以启用弱上拉弱下拉电阻

4.每个APB2 时钟周期都会对 I/O 引脚的状态进行采样并存储在输入数据寄存器中。

在配置GPIO输出模式时有个输出速度的配置项,在输入模式中与之对应的就是采样率了,引脚的电平状态在APB2时钟周期之间不会丢失,这使得我们可以灵活地以任意时间读取引脚的状态。

stm32f103的APB2总线最大时钟为72Mhz,对于绝大多数场景都是够用的。

注意事项:引脚是有容忍电压的,超过容忍值会损坏芯片,具体要查询datasheet

图中FT表示能容忍5V电压,在stm32中大部分引脚都能容忍5V输入

三、代码实现

有了基础知识就能靠代码实现了

1.RCC宏定义

#ifndef __STM32F103_RCC_H
#define __STM32F103_RCC_H

#ifdef __cplusplus
extern "C"
{
#endif

#define RCC_BASE           (0x40021000)
#define RCC_APB2ENR_OFFSET 0x18
#define RCC_APB2ENR        (*(volatile unsigned int *)(RCC_BASE + RCC_APB2ENR_OFFSET))

#define RCC_APB2ENR_IOPAEN (1 << 2)
#define RCC_APB2ENR_IOPCEN (1 << 4)
#define RCC_APB2ENR_IOPDEN (1 << 5)

#ifdef __cplusplus
}
#endif

#endif /* __STM32F103_RCC_H */

2.GPIO宏定义

#ifndef __STM32F103_GPIO_H
#define __STM32F103_GPIO_H

#ifdef __cplusplus
extern "C"
{
#endif

#define GPIOA_BASE               (0x40010800)
#define GPIOC_BASE               (0x40011000)
#define GPIOD_BASE               (0x40011400)

#define GPIO_CRL_OFFSET          0x00
#define GPIO_CRH_OFFSET          0x04
#define GPIO_IDR_OFFSET          0x08
#define GPIO_BSRR_OFFSET         0x10

#define GPIOA_CRH                (*(volatile unsigned int *)(GPIOA_BASE + GPIO_CRH_OFFSET))
#define GPIOA_BSRR               (*(volatile unsigned int *)(GPIOA_BASE + GPIO_BSRR_OFFSET))
#define GPIOA_IDR                (*(volatile unsigned int *)(GPIOA_BASE + GPIO_IDR_OFFSET))
#define GPIOC_CRL                (*(volatile unsigned int *)(GPIOC_BASE + GPIO_CRL_OFFSET))
#define GPIOC_BSRR               (*(volatile unsigned int *)(GPIOC_BASE + GPIO_BSRR_OFFSET))
#define GPIOC_IDR                (*(volatile unsigned int *)(GPIOC_BASE + GPIO_IDR_OFFSET))
#define GPIOD_BSRR               (*(volatile unsigned int *)(GPIOD_BASE + GPIO_BSRR_OFFSET))
#define GPIOD_CRL                (*(volatile unsigned int *)(GPIOD_BASE + GPIO_CRL_OFFSET))

#define GPIO_MODE_OUTPUT_2MHZ_PP 0x02
#define GPIO_MODE_OUTOUT_2MHZ_OD 0x06
#define GPIO_MODE_INPUT          0x08

#define GPIO_PIN_SET(pin)        (1 << pin)
#define GPIO_PIN_RESET(pin)      (1 << pin + 16)
#define GPIOA_PIN_Read(pin)      (GPIOA_IDR & (1 << pin))
#define GPIOC_PIN_Read(pin)      (GPIOC_IDR & (1 << pin))

#define PA8_PIN                  8  // LED0
#define PA15_PIN                 15 // KEY1
#define PC5_PIN                  5  // KEY0
#define PD2_PIN                  2  // LED1

#ifdef __cplusplus
}
#endif

#endif /* __STM32F103_GPIO_H */

3.GPIO配置

void GPIO_Init(void)
{
    // Enable the clock for GPIOA, GPIOD, and GPIOC
    RCC_APB2ENR |= RCC_APB2ENR_IOPAEN; // Enable GPIOA clock
    RCC_APB2ENR |= RCC_APB2ENR_IOPDEN; // Enable GPIOD clock
    RCC_APB2ENR |= RCC_APB2ENR_IOPCEN; // Enable GPIOC clock

    // Configure PA8(LED0) as Push-Pull Output with 2 MHz speed
    GPIOA_CRH &= ~(0xF << (PA8_PIN - 8) * 4);                   // Clear the 4 bits for PA8 configuration
    GPIOA_CRH |= GPIO_MODE_OUTPUT_2MHZ_PP << (PA8_PIN - 8) * 4; // Set PA8 as 2 MHz Push-Pull output

    // Configure PD2(LED1) as Push-Pull Output with 2 MHz speed
    GPIOD_CRL &= ~(0xF << PD2_PIN * 4);                   // Clear the 4 bits for PD2 configuration
    GPIOD_CRL |= GPIO_MODE_OUTPUT_2MHZ_PP << PD2_PIN * 4; // Set PD2 as 2 MHz Push-Pull output

    // Configure PA15(KEY1) as Input mode with pull-up
    GPIOA_CRH &= ~(0xF << (PA15_PIN - 8) * 4);            // Clear the 4 bits for PA15 configuration
    GPIOA_CRH |= (GPIO_MODE_INPUT << (PA15_PIN - 8) * 4); // Set PA15 as input mode
    GPIOA_BSRR |= GPIO_PIN_SET(PA15_PIN);                 // Enable pull-up on PA15

    // Configure PC5(KEY0) as Input mode with pull-up
    GPIOC_CRL &= ~(0xF << (PC5_PIN * 4));            // Clear the 4 bits for PC5 configuration
    GPIOC_CRL |= (GPIO_MODE_INPUT << (PC5_PIN * 4)); // Set PC5 as input mode
    GPIOC_BSRR |= GPIO_PIN_SET(PC5_PIN);             // Enable pull-up on PC5
}

根据前面基础知识,记得把KEY0和KEY1上拉,到这一步所有基础配置完成。

4.LED代码

h文件

#ifndef __LED_H
#define __LED_H

#ifdef __cplusplus
extern "C"
{
#endif

#include "stm32f103_gpio.h"

#define LED0_PIN   PA8_PIN
#define LED1_PIN   PD2_PIN

#define LED0_ON()  GPIOA_BSRR |= GPIO_PIN_RESET(LED0_PIN)
#define LED0_OFF() GPIOA_BSRR |= GPIO_PIN_SET(LED0_PIN)
#define LED1_ON()  GPIOD_BSRR |= GPIO_PIN_RESET(LED1_PIN)
#define LED1_OFF() GPIOD_BSRR |= GPIO_PIN_SET(LED1_PIN)

    void LEDALL_ON(void);
    void LEDALL_OFF(void);

#ifdef __cplusplus
}
#endif

#endif /* __LED_H */

c文件

#include "LED.h"

// Turn on LED0 (connected to PA8) and LED1 (connected to PD2)
void LEDALL_ON(void)
{
    LED0_ON();
    LED1_ON();
}

// Turn off LED0 (PA8) and LED1 (PD2)
void LEDALL_OFF(void)
{
    LED0_OFF();
    LED1_OFF();
}

5.KEY代码

h文件

#ifndef __KEY_H
#define __KEY_H

#ifdef __cplusplus
extern "C"
{
#endif

#include <stdint.h>

    // Function prototypes
    uint8_t KEY_Scan(void);
    void KEY_Process(void);

// Key definitions
#define KEY0_PIN PC5_PIN
#define KEY1_PIN PA15_PIN

#ifdef __cplusplus
}
#endif

#endif /* __KEY_H */

c文件

#include "KEY.h"
#include "LED.h"
#include "stm32f103_gpio.h"
#include <stdint.h>

uint8_t KEY_Scan(void)
{
    static uint8_t key_up = 1; // Flag for key release

    if (key_up && (GPIOC_PIN_Read(KEY0_PIN) == 0 || GPIOA_PIN_Read(KEY1_PIN) == 0))
    {
        for (volatile uint32_t i = 0; i < 1000; i++)
            ; // Delay for debounce
        key_up = 0;
        if (GPIOC_PIN_Read(KEY0_PIN) == 0)
            return KEY0_PIN; // KEY0 pressed
        else if (GPIOA_PIN_Read(KEY1_PIN) == 0)
            return KEY1_PIN; // KEY1 pressed
    }
    else if (GPIOC_PIN_Read(KEY0_PIN) && GPIOA_PIN_Read(KEY1_PIN))
    {
        key_up = 1;
    }

    return 0; // No key pressed
}

void KEY_Process(void)
{
    uint8_t key = KEY_Scan();

    if (key == KEY0_PIN) // KEY0 pressed
    {
        LEDALL_OFF();
    }
    else if (key == KEY1_PIN) // KEY1 pressed
    {
        LEDALL_ON();
    }
}

6.主函数

int main(void)
{
    GPIO_Init();
    LEDALL_OFF();
    while (1)
    {
        KEY_Process();
    }
    return 0;
}

7.调试和下载验证

没有按下KEY1时可以看到IDR15为1

按下KEY1后IDR15读取值为0,键值扫描函数成功返回键值KEY1_PIN

成功运行至LEDALL_ON,开发板上的LED0和LED1被成功点亮。

下载代码验证与调试结果一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值