GPIO模式
- 输入浮空
- 输入上拉
- 输入下拉
- 模拟输入
- 推挽输出
- 开漏输出
- 推挽复用输出
- 开漏复用输出
一篇介绍推挽与开漏输出的区别的文章 链接
I/O端口位的基本结构
配置表
输入浮空/上拉/下拉配置
- 输出缓冲器被禁止
- 施密特触发输入被激活
- 根据输入配置(上拉,下拉或浮动)的不同,弱上拉和下拉电阻被连接
- 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器
- 对输入数据寄存器的读访问可得到I/O状态
输出配置
- 输出缓冲器被激活
- 开漏模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将端口置于高阻状态(P-MOS从不被激活)。
- 推挽模式:输出寄存器上的’0’激活N-MOS,而输出寄存器上的’1’将激活P-MOS。
- 施密特触发输入被激活
- 弱上拉和下拉电阻被禁止
- 出现在I/O脚上的数据在每个APB2时钟被采样到输入数据寄存器
- 在开漏模式时,对输入数据寄存器的读访问可得到I/O状态
- 在推挽式模式时,对输出数据寄存器的读访问得到最后一次写的值
复用功能配置
- 在开漏或推挽式配置中,输出缓冲器被打开
- 内置外设的信号驱动输出缓冲器(复用功能输出)
- 施密特触发输入被激活
- 弱上拉和下拉电阻被禁止
- 在每个APB2时钟周期,出现在I/O脚上的数据被采样到输入数据寄存器
- 开漏模式时,读输入数据寄存器时可得到I/O口状态
- 在推挽模式时,读输出数据寄存器时可得到最后一次写的值
模拟输入配置
- 输出缓冲器被禁止;
- 禁止施密特触发输入,实现了每个模拟I/O引脚上的零消耗。施密特触发输出值被强置
为’0’; - 弱上拉和下拉电阻被禁止;
- 读取输入数据寄存器时数值为’0’。
位段
首先需要明确下,位段,位带和别名区这三个名词
-
位段:STM32用户参考手册使用的名字
-
位带:CortexM3参考手册使用的
-
别名区:地址总线上用来位访问地址区域,
所以说,位段和位带是一个意思,是不同手册的不同叫法。
位带别名区把每个比特膨胀成一个 32 位的字
处理器存储器映射包括两个 bit-banding 区域。它们分别为 SRAM 和外设存储区域中的最低的 1MB。这些 bit-band 区域将存储器别名区的一个字映射为 bit-band 区的一个位。
Cortex-M3 存储器映射有 2 个 32MB 别名区,它们被映射为两个 1MB 的 bit-band 区。
- 对 32MB SRAM 别名区的访问映射为对 1MB SRAMbit-band 区的访问。
- 对 32MB 外设别名区的访问映射为对 1MB 外设 bit-band 区的访问。
支持位带操作的两个内存区的范围是:
0x2000 0000~0x200F FFFF
SRAM 区中最低1MB区域
位带别名区起始地址0x2200 0000
0x4000 0000~0x400F FFFF
片上外设区中的最低1MB区域
位带别名区起始地址0x4200 0000
下面的映射公式给出了别名区中的每个字是如何对应位带区的相应位的:
bit_word_addr = bit_band_base + (byte_offset×32) + (bit_number×4)
其中:
- bit_word_addr是别名存储器区中字的地址,它映射到某个目标位。
- bit_band_base是别名区的起始地址。
- byte_offset是包含目标位的字节在位段里的序号
- bit_number是目标位所在位置(0-7)
注意:别名字的位[31:1]在 bit-band 位上不起作用,仅第0位起作用。写入 0x01 与写入 0xFF 的效果相同。写入0x00 与写入 0x0E 的效果相同。
读别名区的一个字返回 0x01 或 0x00。0x01 表示 bit-band 区中的目标位置位。0x00 表示
目标位清零。位[31:1]将为 0。
采用大端格式时,对 bit-band 别名区的访问必须以字节方式。否则访问值不可预知。
代码实现
#define BITBAND(addr, bit) (*(volatile unsigned long*)((addr & 0xF0000000U) + 0x2000000U + ((addr & 0xFFFFFFU) << 5U) + (bit << 2U)))
- (addr & 0xF0000000U) + 0x2000000U
区分SRAM还是外设,如果是外设,结果为4,再加0x2000000就等于0x4200000,0x42000000就是外设别名位带区。如果是SRAM,结果为2,再加上0x2000000就等于0x22000000,0x22000000就是SRAM别名位带区。 - addr & 0xFFFFFFU
屏蔽了最高2位,相当于减去0x20000000或者0x40000000。因为位带区的有效范围是1M,即0x100000,这样子就做到了低6位有效。
当你使用位带功能时,要访问的变量必须用 volatile 来定义。因为 C 编译器并不知道同一个比特可以有两个地址。所以就要通过 volatile,使得编译器每次都如实地把新数值写入存储器,而不再会出于优化的考虑。
STM32位带代码
以STM32F103C8T6为例,GPIO输出输入的位带代码。
#ifndef __BITBAND_H_
#define __BITBAND_H_
#include "stm32f1xx.h"
/* 位带别名区计算公式 */
#define BITBAND(addr, bit) (*(volatile unsigned long*)((addr & 0xF0000000U) + 0x2000000U + ((addr & 0xFFFFFFU) << 5U) + (bit << 2U)))
/* 输出寄存器 */
#define GPIOA_ODR_ADDR (GPIOA_BASE + 0x0CU)
#define GPIOB_ODR_ADDR (GPIOB_BASE + 0x0CU)
#define GPIOC_ODR_ADDR (GPIOC_BASE + 0x0CU)
#define GPIOD_ODR_ADDR (GPIOD_BASE + 0x0CU)
#define GPIOE_ODR_ADDR (GPIOE_BASE + 0x0CU)
/* 输入寄存器 */
#define GPIOA_IDR_ADDR (GPIOA_BASE + 0x08U)
#define GPIOB_IDR_ADDR (GPIOB_BASE + 0x08U)
#define GPIOC_IDR_ADDR (GPIOC_BASE + 0x08U)
#define GPIOD_IDR_ADDR (GPIOD_BASE + 0x08U)
#define GPIOE_IDR_ADDR (GPIOE_BASE + 0x08U)
/* GPIO输出 */
#define PAout(n) BITBAND(GPIOA_ODR_ADDR, n)
#define PBout(n) BITBAND(GPIOB_ODR_ADDR, n)
#define PCout(n) BITBAND(GPIOC_ODR_ADDR, n)
#define PDout(n) BITBAND(GPIOD_ODR_ADDR, n)
#define PEout(n) BITBAND(GPIOE_ODR_ADDR, n)
/* GPIO输入 */
#define PAin(n) BITBAND(GPIOA_IDR_ADDR, n)
#define PBin(n) BITBAND(GPIOB_IDR_ADDR, n)
#define PCin(n) BITBAND(GPIOC_IDR_ADDR, n)
#define PDin(n) BITBAND(GPIOD_IDR_ADDR, n)
#define PEin(n) BITBAND(GPIOE_IDR_ADDR, n)
#endif /* __BITBAND_H_ */
GPIO推挽输出demo
选用单片机STM32F103C8T6
GPIO推挽输出实现LED灯亮灭翻转实验
原理图
PC13控制LED的亮灭
stm32CubeMX配置
源代码
主要代码如下
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* 采用位带操作实现LED翻转 */
PCout(13) = !PCin(13);
/* 采用HAL库操作实现LED翻转 */
//HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(1000);//延时1000毫秒
}
/* USER CODE END 3 */
工程文件下载链接
参考资料
- STM32F10x-中文参考手册
- Cortex-M3权威指南(CN)
- Cortex-M3技术参考手册(CN)
参考资料下载链接