STM32 的 IO 采用分组管理(GPIOA、GPIOB…),每组 16 个 IO…
对应的枚举类型定义:
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种模式!
每个 IO 口可以自由编程, 但 IO 口寄存器必须要按 32 位字被访问。
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
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;
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)
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
该函数一般用来一次性设置一个GPIO的多个端口值。
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
STM32 有很多的内置外设,这些外设的外部引脚都是与 GPIO 复用的
一个 GPIO 如果可以复用为内置外设的功能引脚, 那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。
例如, STM32F103ZET6 有 5 个串口, 串口 1 的引脚对应的 IO 为 PA9, PA10。PA9, PA10 默认功能是 GPIO, 所以当 PA9, PA10 引脚作为串口 1 的 TX, RX 引脚使用的时候,那就是端口复用。
为了使不同器件封装的外设 IO 功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。
每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,在 STM32 中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。
由上表可知,默认情况下串口 1 复用的是 PA9,PA10。假设在某个项目中这两个引脚已经被占用,但又要用到串口 1 ,此时就可以将 TX 和 RX 重新映射到引脚 PB6 和 PB7 上去。
注意,使用重映射功能时,需要额外使能AFIO功能时钟,然后调用重映射函数。
使能 AFIO 时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
开启重映射:
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
要了解更多,请看:STM32的AFIO时钟什么时候需要开启
位带操作简单来说就是把每个比特膨胀为一个 32 位的字, 当访问这些字的时候就达到了访问比特的目的。 假设一个寄存器有 32 位,那么可以把每一个比特位映射到一个 32 位地址上。这样,当我们访问其中的地址时就达到访问对应比特位的目的。当我们需要往一个比特位写 1 时,只需要往其对应的地址写 1 即可。
正点原子帮我们实现了一种…
#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 芯片上面的移植性非常好, 不需要修改任何代码。 用位带操作的好处是简洁。
RCC_APB2PeriphClockCmd();
GPIO_Init();
RCC_APB2PeriphClockCmd();
重映射我们同样要使能复用功能的时候讲解的 2 个时钟外,还要使能 AFIO 功能时钟,然后要调用重映射函数。
RCC_APB2PeriphClockCmd();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
注:本文参考正点原子《STM32F1 开发指南(精英版)-库函数版本_V1.0》