STM32 GPIO输入学习笔记(二):深入解析寄存器操作
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被成功点亮。
下载代码验证与调试结果一致。
作者:重案组之虎达文西