参考资料:《STM32F4xx参考手册》
STM32F407有7x16个GPIO,如何设计寄存器可以方便管理?
考虑最简单的情况
假设只有1个GPIO,只能读取外部的电平状态,则只需要一个只读寄存器,命名为 GPIO_IDR ,其中用一个bit0就可以表示输入的高低电平。输入的情况并不需要写寄存器,权限可以设为只读。
bit | 功能 | 读写权限 |
---|---|---|
0 | 输入电平状态 | r |
[31:1] | 保留 |
多个GPIO的读写
当有多个GPIO时,仍然采用1个bit表示1个GPIO的方式,则32位的寄存器最多只能表示32个GPIO,那么更多的GPIO则需要多个寄存器。实际上相当于把GPIO分成了多个组,每个组32个GPIO。
实际上8个GPIO一组、16个GPIO一组也是完全可行的。8个GPIO一组会造成寄存器浪费较多,每个寄存器有24bit是无用的,而32个GPIO一组虽然没有浪费,但是不方便功能细分,采取折中方案16个GPIO一组,并采用ABCDEFG命名。于是GPIOA的寄存器 GPIOA_IDR 如下。
bit | 功能 | 读写权限 |
---|---|---|
[15: 0] | 输入电平状态 | r |
[31:16] | 保留 |
增加输出功能
GPIO不仅能输入,而且要可以输出高低电平,那么用同样的方法增加一个寄存器 GPIOx_ODR 来表示。考虑到可能会有读取输出电平的场景,权限设为可读可写。
bit | 功能 | 读写权限 |
---|---|---|
[15: 0] | 输出电平状态 | rw |
[31:16] | 保留 |
多个模式的管理
有了输入和输出两个模式后,如何判断GPIO目前是输入还是输出状态。应考虑再引入一个寄存器 GPIOx_MODER ,每个bit用来表示1个GPIO目前的工作模式,如下:
bit | 功能 | 读写权限 |
---|---|---|
[15: 0] | 0:输入模式 1:输出模式 |
rw |
[31:16] | 保留 |
但是GPIO可能需要复用成更多的模式,如I2C、SPI、UART等模式,模式超过3个时,1个bit无法表示。此时考虑将常用的输入输出单独出来,其他模式再使用额外的寄存器选择模式。GPIOx_MODER 的功能变成下表:
bit | 功能 | 读写权限 |
---|---|---|
[1: 0] | 0:输入模式 1:输出模式 2:复用模式 |
rw |
[3: 2] | 0:输入模式 1:输出模式 2:复用模式 |
rw |
…… | …… | …… |
[31:30] | 0:输入模式 1:输出模式 2:复用模式 |
rw |
当模式选为复用模式时,复用功能寄存器的值被采用,考虑到复用功能接近16种,需要4个bit表示,那么每组16个GPIO则需要64个bit,使用2个寄存器:GPIO 复用功能低位寄存器 GPIOx_AFRL 、GPIO复用功能高位寄存器 GPIOx_AFRH 。两个寄存器分别表示0-7,8-15各GPIO的实际复用功能。
bit | 功能 | 读写权限 |
---|---|---|
[3: 0] | 实际复用的功能 | rw |
[7: 4] | 实际复用的功能 | rw |
…… | …… | …… |
[31:28] | 实际复用的功能 | rw |
配置成复用的功能之后,如I2C模式,I2C相关的设置分离出来更有条理,将不再属于GPIO模块的一部分,应设置单独的I2C寄存器来管理。
输出模式的简化
寄存器的访问最小单位都是字节,假设需要让GPIOA的第0个GPIO设为高电平,若采用以下方式访问:
GPIOA_ODR = 1;
虽然第0个GPIO设为高电平成功了,但是同组的其他的GPIO都被设成了低电平。那么如果只想修改1个bit。则需要把其他bit的状态维持不变,应当采用(读——改——写)三个步骤。
u32 tmp = GPIOA_ODR;
tmp = tmp | 0x01;
GPIOA_ODR = tmp;
每次修改都要执行这三个步骤比较麻烦,为了方便使用,增加1个寄存器 GPIOx_BSRR 专门用来做修改。此时不需要读的功能,权限设置为只写。
- 当写入0时,维持原来的值不变,
- 当写入1时,将输出变为高电平。
此时需要16个bit,同理再增加16个bit实现设置低电平的功能。
- 当写入0时,维持原来的值不变,
- 当写入1时,将输出变为低电平。
最终 GPIOx_BSRR 的功能如下。
bit | 功能 | 读写权限 |
---|---|---|
[15: 0] | 0:维持值不变 1:输出高电平 |
w |
[31:16] | 0:维持值不变 1:输出低电平 |
w |
使用寄存器 GPIOx_BSRR 只需要一个写步骤就能完成修改。如:
GPIOA_BSRR = 1 << (9 + 0); //修改A组的第9个GPIO为高电平
GPIOA_BSRR = 1 << (9 + 16); //修改A组的第9个GPIO为低电平
输出模式的类型
GPIO常用的输出模式有推挽输出和开漏输出两种,特点如下:
- 推挽输出:有强高低电平驱动能力
- 开漏输出:只有低电平驱动能力 ,高电平驱动能力取决于上拉电阻,可实现线与功能。
增加一个寄存器 GPIOx_OTYPER 就可以完成两种输出类型的区分。
bit | 功能 | 读写权限 |
---|---|---|
[15: 0] | 0:推挽输出 1:开漏输出 |
rw |
[31:16] | 保留 |
上下拉电阻配置
提供一个默认的上下拉电阻的配置,可以简化外围电路的设计。增加一个上下拉寄存器 GPIOx_PUPDR ,允许用户配置GPIO为无拉、上拉或下拉。
bit | 功能 | 读写权限 |
---|---|---|
[1: 0] | 0:无拉 1:上拉 2:下拉 |
rw |
[3: 2] | 0:无拉 1:上拉 2:下拉 |
rw |
…… | …… | …… |
[31:30] | 0:无拉 1:上拉 2:下拉 |
rw |
输出速率的配置
输出速率是指GPIO电平变化的最大频率。输出频率越高,噪声越大,能耗越高,电磁干扰越强。
最合适的输出速率应该是在满足基本需求的情况下尽可能的低。问题是无法预测用户会使用什么速率,只能划分为几个常见档位供用户选择。
GPIO输出速度寄存器 GPIOx_OSPEEDR ,功能如下:
bit | 功能 | 读写权限 |
---|---|---|
[1: 0] | 0:低速 1:中速 2:快速 3:高速 |
rw |
[3: 2] | 0:低速 1:中速 2:快速 3:高速 |
rw |
…… | …… | …… |
[31:30] | 0:低速 1:中速 2:快速 3:高速 |
rw |
注:2 MHz(低速) 、25 MHz(中速)、50 MHz(快速)
30 pF 时为 100 MHz(高速)(15 pF 时为 80 MHz 输出(最大速度))
其他功能
GPIO 端口配置锁定寄存器 GPIOx_LCKR,非必须功能,不做深入学习。