使用寄存器编程进行GPIO输出(PA0口输出为例)

在STM32的GPIO编程中,其本质就是通过操作寄存器来进行I/O口的控制,在STM32中,存在着许多外设且每个外设都有其对应的寄存器。

要想进行寄存器操作,首先需要知道寄存器的地址,在STM32参考手册 2.3 存储器映像 中有个寄存器组起始地址表,此处我截取本文所需的寄存器地址信息,在查阅参考手册得知,GPIOA外设是挂载在APB2总线上的,所以我们需要操作APB2外设时钟的使能寄存器以及GPIOA寄存器,操作寄存器的代码形式:*(uint32_t *)(基址+偏移量) = 位操作数。

image-20240706013156611

配置时钟信号

在STM32中,由于存在许多的外设,操作外设寄存器需要有时钟信号,如果所有外设均默认开启时钟将会造成资源浪费,因此为了能按需控制外设,在操作外设寄存器之前,首先需要进行时钟配置:

查阅手册得知,RCC寄存器基址为 0x40021000,APB2外设时钟使能寄存器偏移地址为 0x18,由于需要开启GPIOA时钟,因此IOPAEN位为1。

方法一:赋值操作,直接对寄存器进行赋值

1
*(uint32_t *)(0x40021000 + 0x18) = 4

方法二:位操作(与或非)

1
*(uint32_t *)(0x40021000 + 0x18) |= (1<<2); // 将寄存器址与0100相与,将第2位置1,其他位不变
image-20240706011218628

GPIO工作模式配置

在配置完成时钟后,还要先配置GPIO的工作模式,如本次采用GPIOA的PA0进行输出,所以查阅端口配置低位寄存器,如下图,在低寄存器的32位中,每4位对应每一个端口,如需使用PA0端口进行推挽输出操作,则CNF0=00,MODE0=11(这里选择50MHz),即十六进制码为 0x3,所以具体操作代码如下:

方法一:赋值操作,直接对寄存器进行赋值

1
2
// GPIOA寄存器基址为:0x40010800,端口配置低寄存器基址:0x00
*(uint32_t *)(0x40010800 + 0x00) = 0x3// 如果是配置PA1,则为0x30,PA2则为0x300

方法二:位操作(与或非)

1
2
3
4
5
// 如配置PA0口的工作模式,即操作0~3位,二进制值为0011
*(uint32_t *)(0x40010800 + 0x00) &= ~(1<<3); // 相与置0
*(uint32_t *)(0x40010800 + 0x00) &= ~(1<<2); // 相与置0
*(uint32_t *)(0x40010800 + 0x00) |= (1<<1); // 相或置1
*(uint32_t *)(0x40010800 + 0x00) |= (1<<0); // 相或置1
image-20240706021022099

GPIO口输出

在前面进行基本配置后,便可以进行端口输出,对应的端口输出数据寄存器偏移地址为:0xC,基于GPIOA基址:0x40010800,在进行编程时此16位可以十六进制进行赋值,每4位对应十六进制的1位,如:

  • 仅将PA0输出高电平,则为0x1;
  • 仅将PA0输出低电平,则为0xfffe。

方法一:赋值操作,直接对寄存器进行赋值

1
*(uint32_t *)(0x40010800 + 0x00) = 0xfffe; // PA0输出低电平

方法二:位操作(与或非)

1
*(uint32_t *)(0x40010800 + 0x00) &= ~(1<<0); // PA0输出低电平

image-20240706021240702

完整代码

实现PA0和PA1都输出低电平

直接赋值方法

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdint.h>

int main(void)
{
// 配置APB2使能时钟
*(uint32_t *)(0x40021000 + 0x18) = 4; // GPIOA使能
// 配置GPIO口工作模式
*(uint32_t *)(0x40010800 + 0x00) = 0x33; // 配置PA0,PA1为推挽输出,速度50MHz
// GPIO输出
*(uint32_t *)(0x40010800 + 0x0C) &= ~(1<<0); // PA0和PA1输出低电平

while(1); // 保持运行状态
}

位操作(与或非)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdint.h>

int main(void)
{
// 配置APB2使能时钟
*(uint32_t *)(0x40021000 + 0x18) |= (1<<2); // 将寄存器址与0100相与,将第2位置1,其他位不变

// 配置GPIO口工作模式,如配置PA0口的工作模式,即操作0~3位,二进制值为0011
*(uint32_t *)(0x40010800 + 0x00) &= ~(1<<3); // 相与置0
*(uint32_t *)(0x40010800 + 0x00) &= ~(1<<2); // 相与置0
*(uint32_t *)(0x40010800 + 0x00) |= (1<<1); // 相或置1
*(uint32_t *)(0x40010800 + 0x00) |= (1<<0); // 相或置1

// GPIO输出
*(uint32_t *)(0x40010800 + 0x0C) = 0xfffC; // PA0和PA1输出低电平

while(1); // 保持运行状态
}

直接赋值:直接对寄存器进行赋值操作,会改变寄存器所有的位,会影响该寄存器的其他外设。

位操作:对寄存器的值进行与、或、非等操作,能精确到寄存器的对应位进行操作,而不影响其他位。