STM32基础-IO 简介

一、IO 简介

STM32 的 IO 采用分组管理(GPIOA、GPIOB…),每组 16 个 IO…

1. STM32 IO 的 8 种模式

  • 输入浮空
  • 输入上拉
  • 输入下拉
  • 模拟输入
  • 开漏输出
  • 推挽输出
  • 推挽式复用功能
  • 开漏复用功能

对应的枚举类型定义:

typedef enum{
    GPIO_Mode_AIN = 0x0,            //模拟输入
    GPIO_Mode_IN_FLOATING = 0x04,   //浮空输入
    GPIO_Mode_IPD = 0x28,           //下拉输入
    GPIO_Mode_IPU = 0x48,           //上拉输入
    GPIO_Mode_Out_OD = 0x14,        //开漏输出
    GPIO_Mode_Out_PP = 0x10,        //通用推挽输出
    GPIO_Mode_AF_OD = 0x1C,         //复用开漏输出
    GPIO_Mode_AF_PP = 0x18          //复用推挽
}GPIOMode_TypeDef;

更详细的内容请访问: STM32 中 GPIO 的8种模式!

2. 设置 IO(库函数)

每个 IO 口可以自由编程, 但 IO 口寄存器必须要按 32 位字被访问。

2.1 GPIO 初始化函数

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
  • GPIO_TypeDef* :用来指定 GPIO。取值范围为 GPIOA~GPIOG;
  • GPIO_InitType* :初始化参数结构体指针
typedef struct{
    uint16_t GPIO_Pin;
    GPIOSpeed_TypeDef GPIO_Speed;
    GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;

一个例子:

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;           //LED0-->PB.5 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   //速度 50MHz
GPIO_Init(GPIOB, &GPIO_InitStructure);              //根据设定参数配置 GPIO

其中 GPIO 的速度也是通过枚举类型定义:

typedef enum{
    GPIO_Speed_10MHz = 1,
    GPIO_Speed_2MHz,
    GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

2.2 读取 IO 端口数据

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

函数的参数同上,无需多说。

一个例子,需要读GPIOA Pin_5的电平状态,就可以这样做:

GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);

返回值是 1(Bit_SET)或 0(Bit_RESET)

2.3 改变 IO 端口状态

  • 控制 IO 口输出状态
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);

该函数一般用来一次性设置一个GPIO的多个端口值。

  • 将 IO 端口置为高
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
  • 将 IO 端口置为低
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)

二、端口复用和重映射

1. 端口复用

STM32 有很多的内置外设,这些外设的外部引脚都是与 GPIO 复用的

一个 GPIO 如果可以复用为内置外设的功能引脚, 那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。
例如, STM32F103ZET6 有 5 个串口, 串口 1 的引脚对应的 IO 为 PA9, PA10。PA9, PA10 默认功能是 GPIO, 所以当 PA9, PA10 引脚作为串口 1 的 TX, RX 引脚使用的时候,那就是端口复用。

2. 重映射

为了使不同器件封装的外设 IO 功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。

每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,在 STM32 中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。
STM32基础-IO 简介_第1张图片
由上表可知,默认情况下串口 1 复用的是 PA9,PA10。假设在某个项目中这两个引脚已经被占用,但又要用到串口 1 ,此时就可以将 TX 和 RX 重新映射到引脚 PB6 和 PB7 上去。
注意,使用重映射功能时,需要额外使能AFIO功能时钟,然后调用重映射函数。

使能 AFIO 时钟:
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
开启重映射:
    GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

要了解更多,请看:STM32的AFIO时钟什么时候需要开启

三、IO 的位带操作

1. 什么是 IO 位带操作

位带操作简单来说就是把每个比特膨胀为一个 32 位的字, 当访问这些字的时候就达到了访问比特的目的。 假设一个寄存器有 32 位,那么可以把每一个比特位映射到一个 32 位地址上。这样,当我们访问其中的地址时就达到访问对应比特位的目的。当我们需要往一个比特位写 1 时,只需要往其对应的地址写 1 即可。

2. 位带操作的实现

正点原子帮我们实现了一种…

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((
addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO 口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO 口操作,只对单一的 IO 口!
//确保 n 的值小于 16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr, n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr, n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr, n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr, n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr, n) //输出 
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr, n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr, n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr, n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr, n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr, n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr, n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr, n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr, n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr, n) //输入

有了以上代码就可以很方便的使用 IO 口了。当我们需要让 GPIOA 的第 6 个 IO 输出高, 就可以这样:

PAout(5) = 1;    //0-> 第一个 IO 口, 5->第六个 IO 口;

当我们要获得 GPIOA 的第 6 个 IO 口的状态时,就可以直接读取 PAin(5)。

if(PAin(5) == 1)
{
    ;
}

位带操作PBin(n)等同于库函数 GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_n)。用库函数实现的好处是在各个 STM32 芯片上面的移植性非常好, 不需要修改任何代码。 用位带操作的好处是简洁。

四、使用 IO

1. 一般情况

  • 使能 IO 端口时钟。调用函数RCC_APB2PeriphClockCmd();
  • 初始化 IO 参数。调用函数GPIO_Init();
  • 使用 IO

2. 端口复用

  • 使能 IO 端口时钟。RCC_APB2PeriphClockCmd();
  • 使能复用的外设时钟。 如, 复用为串口的引脚, 则需要使能串口时钟。 根据外设的不同调用不同的库函数。
  • 端口模式配置。在 IO 复用位内置外设功能引脚的时候,必须设置 GPIO 端口的模式,至于在复用功能下 GPIO 的模式是怎么对应的,这个可以查看手册《STM32 中文参考手册 V10》 P110 的表格“8.1.11 外设的 GPIO 配置”。

3. 重映射

重映射我们同样要使能复用功能的时候讲解的 2 个时钟外,还要使能 AFIO 功能时钟,然后要调用重映射函数。

  • 使能 IO 端口时钟。调用函数RCC_APB2PeriphClockCmd();
  • 使能复用的外设时钟。 根据外设的不同调用不同的库函数。
  • 使能AFIO时钟。RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
  • 开启重映射。GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

注:本文参考正点原子《STM32F1 开发指南(精英版)-库函数版本_V1.0》

你可能感兴趣的:(STM32)