本篇介绍STM32芯片内部的系统架构篇章,整体内容比较偏底层,对于基础较差或者没有基础的同学,这一章节会比较费劲,可以先整体过一遍,然后等整体的内容学完了再回来看这个内容你会觉得对于单片机的理解更加的通透了。

第一章 总线架构

1.1 总线的概念

在通用计算机中也有总线的概念,这个总线和嵌入式中的总线非常类似,而在计算机中总线的定义如下

image-20250318155756566

总线的连接示意图如下

image-20250318155645895

1.2 总线的作用

在前面,我们已经知道了 STM32 芯片的内核是由 ARM 公司所设计的,ARM公司向其收取专利费用,而ST公司拿到授权后,则根据不同的产品定位,设计各种不同的外设,封装成不同的芯片。

而总线的作用就是用于连接内核 与 厂商的外设。

poYBAGDvn7CAPmlEAAGkWjtLppI154

1.3 总线矩阵

总线架构图

STM32F4-系统架构

系统架构图

image-20250318144430427

1.3.1 总线矩阵(核心枢纽)

总线矩阵用于主仲裁管理,仲裁采用循环调度算法控总线之间的访问仲裁管理,仲裁采用循环调度算法。总线之间交叉的时候如果有个圆圈则表示可以通信,没有圆圈则表示不可以通信。

作用:一个「交通调度中心」,让 7 个主控设备(如 CPU、DMA)通过 M0-M6 通道,并行访问 7 个被控设备(如内存、外设)。

优势:多主控同时操作互不冲突(例如 CPU 写 Flash 时,DMA 可同时读 SRAM)。

  • 主控总线 = 主动干活(CPU、DMA、USB 等发起操作)。
  • 被控总线 = 被动响应(内存、外设等提供资源)。
  • 总线矩阵 = 高效调度员,避免堵车。

1.3.2 主控总线(发起操作)

Cortex-M4 内核总线

  • I总线:CPU 取指令专用(如从 Flash 读取代码)。
    • 指令总线
  • D总线:CPU 读写数据专用(如访问内存或外设)。
    • 数据总线
  • S总线:CPU 调试和系统控制专用。
    • 系统总线

DMA1/DMA2 总线

  • DMA_MEM1/MEM2:DMA 控制器直接搬运内存数据(如 SRAM 之间传输)。
  • DMA_P1/P2:DMA 控制器与外设交互(如从 ADC 读取数据到内存)。

以太网/USB_HS 总线

  • 专用于高速外设:以太网和 USB 直接通过自己的 DMA 通道传输数据,不占用 CPU。

3.2.3 被控总线(响应操作)

内部存储

  • ICODE/DCODE总线:连接 CPU 指令和数据 Flash(存放程序代码)。
  • SRAM1/SRAM2:主内存(112KB + 16KB),供 CPU 和 DMA 共用。
  • CCM RAM(64KB):紧耦合内存,仅 CPU 可直接访问,速度最快。

外设总线

  • AHB1/AHB2:高速外设通道(如 GPIO、定时器)。
  • APB1/APB2:低速外设通道(如 UART、SPI),通过 AHB-APB 桥连接。

FSMC总线

  • 扩展外部存储器(如 SRAM、LCD 屏),通过地址映射直接访问。

第二章 存储器映射

在前面我们学习总线的时候了解到,被控总线是 FLASH \ RAM \ AHB 等外设上面,然后这些功能都被共同排列到了一个 4GB 的地址空间中,我们在进行STM32 编程的时候 其实就是对这些内存进行编程,操作的也是这些内存。

存储器映射 : 是指将物理存储器(如 RAM、ROM 等)按照特定的编码规则分配地址的过程。这个映射过程确保了 CPU 能够准确地访问到存储器中的每一个位置。在计算机系统中,每个存储单元都有一个唯一的地址,CPU 通过这个地址来定位和访问存储的数据或指令。

存储器本身不具有地址信息,它的地址是由芯片厂商或用户分配,给存储器分配地址的过程就称为存储器映射,具体见图 STM32F407 存储器映射 。如果给存储器再分配一个地址就叫存储器重映射。

程序存储器数据存储器寄存器输入输出端口被组织在同一个 4GB 的线性地址空间内。

数据字节以小端格式(低位在前的存储顺序)存放在存储器中。一个字里的最低地址字节被认为是该字的最低有效字节,而最高地址字节是最高有效字节。

2.1 存储空间

前面说的 4GB 线性地址空间之所以是 4GB,是由于 STM32 芯片内部的地址总线为 32 根所决定的。在32 位系统中,地址总线可以寻址的范围是 2 的 32 次方,即 4,294,967,296 个地址单元。由于每个内存单元的大小通常是一个字节(8 位),因此这个地址范围就对应着 4GB 的存储空间。

STM32 有 2 的 32 次方个排列方法,也就是说有 2 的 32 次方个地址。一个地址对应一个内存单元,而一个内存单元就是一个字节,则 2 的 32 次方个地址,则内存大小就是 2 的 32 次方个字节也就是 4GB 大小。

如下图:

存储器映射图

2.2 存储分区

在 4GB 的地址空间中,ARM 已经重新的平均分成了 8 个块,每个块也都规定了用途每个块的大小都有 512MB。

image-20250318164702883

在这 8 个 Block 里面,有 3 个块非常重要,也是最关心的三个块。

  • Block0 用来设计成内部 FLASH

  • Block1 用来设计成内部 RAM

  • Block2 用来设计成片上的外设

下面简单的介绍下这三个 Block 里面的具体区域的功能划分。

2.2.1 Block 0 分区

存储器 Block 0 内部区域功能划分

地址范围用途说明
0x0000 0000 ~ 0x000F FFFF别名区(Alias)取决于 BOOT 引脚,可以映射为 FLASH、系统存储器或 SRAM 的别名。
- 在启动时,CPU 会根据 BOOT 配置从这个区域(或者它的别名指向的实际存储区域)开始执行。
0x0010 0000 ~ 0x07FF FFFF保留(Reserved)- 预留区域,暂未使用或用于其他特殊功能。
0x0800 0000 ~ 0x080F FFFFFLASH- STM32F4 系列主 Flash,大小 1MB(以 64KB/128KB 为扇区粒度划分)。
- 用户的应用程序一般就放在这里。
0x0810 0000 ~ 0x0FFF FFFF保留(Reserved)- 预留区域,可能给更大容量的 Flash 或其它扩展使用。实际可用情况需参考不同型号的具体数据手册。
0x1000 0000 ~ 0x1000 FFFFCCM 数据 RAM- 容量 64KB,CPU 可直接通过 D 总线存取,不经过总线矩阵,属于高速 RAM。
0x1001 0000 ~ 0x1FFE FFFF保留(Reserved)- 预留区域。
0x1FFF 0000 ~ 0x1FFF 7A0F系统存储器 + OTP- 系统存储器:ST 出厂时烧写的 ISP 自举程序(ISP/Bootloader),用户无法改动;通过串口下载等方式升级固件时会用到此处。
- OTP 区域:其中 512 个字节只能写一次,用于存储用户数据;额外的 16 字节用于锁定对应的 OTP 数据块。
0x1FFF 7A10 ~ 0x1FFF 7FFF保留(Reserved)- 预留区域。
0x1FFF C000 ~ 0x1FFF C007选项字节(Option Bytes)- 用于配置读写保护、BOR 级别、软件/硬件看门狗以及器件处于待机/停止模式下的复位。
- 如果芯片“锁死”,可通过在 RAM 中手动修改相关寄存器位来解锁。
0x1FFF C008 ~ 0x1FFF FFFF保留(Reserved)- 预留区域。

可以看到,我们用户 FLASH 大小是 1024KB,这是对于我们使用的 STM32F407 来说,如果是其他型号,可能 FLASH 会更大,当然,如果 ST 喜欢,也是可以随时推出更大容量 的 STM32 单片机的,因为这里保留了一大块地址空间。还有,STM32 的出厂固化 BootLoader 占 用差不多 31KB FLASH 空间。

2.2.2 Block 1 分区

存储器 Block 1 内部区域功能划分

存储块功能地址范围容量说明
SRAM1主 SRAM0x2000 0000 ~ 0x2001 BFFF112 KBSTM32F4 系列主要的片上 RAM,供用户程序存储数据、堆栈等。
SRAM2辅 SRAM0x2001 C000 ~ 0x2001 FFFF16 KB与 SRAM1 逻辑上连贯,可分配专用任务或与 SRAM1 配合使用。
保留预留(Reserved)0x2002 0000 ~ 0x3FFF FFFF预留区域,不建议用户访问,可能为芯片其他功能/扩展预留。

这个块仅使用了 128KB 大小,用于 SRAM 访问,同时也有大量保留地址用于扩展。

SRAM(静态随机存取存储器)是用于存储程序运行时的临时数据和中间结果的核心存储单元。

2.2.3 Block 2 分区

第三个块是 Block 2,用于外设访问,STM32 内部大部分的外设都是放在这个块里面的,该存储块里面包括了 AHB1、AHB2、APB1 和 APB2 三个总线相关的外设。Block2 用于设计片内的外设,根据外设的总线速度不同,Block 被分成了 APB 和 AHB 两部分,其中 APB 又被分为 APB1 和 APB2。其功能划分如表所示:

存储器 Block2 内部区域功能划分

存储块功能地址范围备注
Block 2APB1 总线外设0x4000 0000 ~ 0x4000 77FFAPB1 部分外设寄存器区。
保留-0x4000 7800 ~ 0x4000 FFFF预留区域。
APB2 总线外设-0x4001 0000 ~ 0x4001 33FFAPB2 部分外设寄存器区。
保留-0x4001 3400 ~ 0x4001 37FF预留区域。
SYSCFG-0x4001 3800 ~ 0x4001 3BFF系统配置控制寄存器。
EXTI-0x4001 3C00 ~ 0x4001 3FFF外部中断/事件控制寄存器。
APB2 总线外设-0x4001 4000 ~ 0x4001 4BFFAPB2 其他外设寄存器区。
保留-0x4001 4000 ~ 0x4001 7FFF与上行地址区间重叠,疑似笔误或简化写法,实际需结合参考手册确认。
AHB1 总线外设-0x4002 0000 ~ 0x4002 23FFAHB1 部分外设寄存器区。
保留-0x4002 2400 ~ 0x4002 2FFF预留区域。
CRC-0x4002 3000 ~ 0x4002 33FFCRC 计算单元寄存器。
保留-0x4002 3400 ~ 0x4002 37FF预留区域。
RCC复位和时钟控制器0x4002 3800 ~ 0x4002 3BFF配置系统时钟、外设时钟、复位等。
AHB1 总线外设-0x4002 3C00 ~ 0x4007 FFFFAHB1 其他外设寄存器区。
保留-0x4002 9400 ~ 0x4FFF FFFF预留区域。
AHB2 总线外设-0x5000 0000 ~ 0x5006 0FFFAHB2 部分外设寄存器区。
保留-0x5006 0C00 ~ 0x5FFF FFFF预留区域。
AHB3 总线外设-0x6000 0000 ~ 0xA000 0FFFAHB3 部分外设寄存器区。
保留-0xA000 1000 ~ 0xBFFF FFFF预留区域。

同样可以看到,各个总线之间,都有预留地址空间,方便后续扩展。

第三章 寄存器映射

3.1 寄存器是什么

3.1.1 什么是寄存器

寄存器(Register)是单片机内部一种特殊的存储器,它可以实现对单片机各个功能的控制,在 STM32微控制器中,它作为 CPU 内部的小型存储区域,起着至关重要的作用。寄存器可以暂存指令、数据和地址,就像一个特殊的地址存放数据的地方。

例如,存放数据的寄存器可以直接存储某个引脚的高低电平数据,当需要读取这个数据时,就可以直接到这个寄存器所在的地方询问数据是多少。

不同的数据会存放在不同的寄存器中,通过地址来区分这些寄存器,就像不同的行李寄存在不同的店铺号一样。

指令/地址寄存器与数据寄存器类似,里面存放的都是 0 和 1,在特定的规定下,数据寄存器里面存放的 0 和 1 表示数据,指令寄存器里存放的表示指令。可以把寄存器类比为有特殊功能的地方,既然是个地方当然就有地址了,所以,可以把寄存器想象为特殊的地址。比如厨房可以类比为寄存器,那么厨房的特殊功能就是做饭;

仓库也是个寄存器,那么负责存东西就是仓库的特殊功能。需要某些功能的时候,就要操作某个寄存器。

由于寄存器资源极其宝贵,因此通常每个寄存器只包含一个或几个控制位来执行特定功能。对于STM32 来说,其寄存器是 32 位的,这意味着一个 32 位的寄存器可能包含多达 32 个控制功能相当于 32 个独立的开关。由于 STM32 微控制器的复杂性,其内部集成了数百个这样的寄存器,因此从整体上看,STM32的寄存器系统显得相当复杂。

3.1.2 分类寄存器

STM32 之所以拥有如此多的寄存器,主要是因为它内部集成了大量的外设。如果将这些寄存器按照外设进行分类,就会发现每个外设通常只对应几个或十几个寄存器。

从大方向来看,STM32 的寄存器可以大致分为两类:内核寄存器外设寄存器。内核寄存器与 CPU 的功能和操作紧密相关,而外设寄存器则用于配置和控制 STM32 的各种外部设备。

3.1.3 什么是寄存器映射

存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?寄存器到底是什么?

在存储器 Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共 32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。

我们可以找到每个单元的起始地址,然后通过 C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

3.1.2 外设寄存器

控制寄存器(xxx_CR):用来控制、配置外设的工作方式,例如 GPIO 端口模式寄存器(GPIOx_MODER),可以配置 GPIO 引脚为输入、输出、模拟等不同的工作模式。

状态寄存器(xxx_SR):存储了当前外设的工作状态,例如串口的状态寄存器(USART_SR),可以通过读取该寄存器的某些位来判断串口是否发送完成、是否接收到数据等。

数据寄存器(xxx_DR):用于存储外设进行输入输出的数据。比如 GPIO 端口的输入数据寄存器(GPIOx_IDR)用于读取 GPIO 引脚的输入状态,输出数据寄存器(GPIOx_ODR)用于设置 GPIO 引脚的输出状态。

位操作寄存器:针对某些需要对单个位进行操作的场景,STM32 提供了位操作寄存器。例如GPIOx_BSRR(设置 / 清除寄存器),可以对 GPIO 引脚的单个位进行置位和复位操作,方便了对特定引脚位的控制,而不必对整个寄存器进行操作。

3.2 外设寄存器映射

根据外设速度的不同,APB1 总线挂载低速外设,APB2 总线挂载高速外设。相应总线的地址空间是一个范围,其最低地址标志着该总线地址空间的开始。片上外设的地址空间通常从 APB1 总线的地址空间开始分配,但每个外设都有自己的基地址,用于在该总线地址空间内唯一标识和定位该外设。

查阅下方表格 可以通过 《STM32F4xx 中文参考手册.pdf》 的 <表2. STM32F4xx 寄存器边界地址>

把我们的外设单元 映射为 一个指定的地址

在刚刚 我们存储器映射 是将 存储单元 外设 AHB1 。。。

image-20250318133107455

image-20250318133156642

image-20250318133235872

3.3 外设寄存器操作

片上外设区分为四条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB 挂载低速外设,AHB 挂载高速外设。相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中 APB1 总线的地址最低,片上外设从这里开始,也叫外设基地址。

第五章 寄存器工程搭建

5.1 创建工作空间

在我们开始进行编程之前,我们需要对本地的文件进行管理,在后面工作中也是一样,好的文件管理对于我们提高工作效率有很好的帮助。

现在需要我们在电脑上建立一个STM32的工作空间 , 来用于存放我们在学习STM32这阶段所有的文件,框架如下。

image-20250318172503362

5.2 创建工程

5.2.1 创建工程

点击 KEIL 5 菜单栏上的 Project -> New uVision Project ...

image-20250319135854455

5.2.2 选择路径

选择路径 , 就选择我们前面新建好的LED 文件夹

image-20250319141336572

1.5.3 选择芯片

选择芯片型号,这里需要根据你实际使用的芯片来进行选择,这里我们选择 STM32F407IGH6 , 最快捷的方式是 直接搜索芯片。

如果没有型号咋办: 那就是你前面没有安装芯片包,在前面的章节有介绍如何安装芯片包

image-20250319141446848

1.5.4 关闭在线库

当我们点击确定后,会弹出 下面这个窗口 我们可以不用管,直接关闭即可

image-20250319141555596

1.5.6 添加文件

最后一步,我们需要在工程中添加以下三个文件:

  • main.c
  • stm32f4xx.h
  • startup_stm32f40xx.s

前两个文件(main.cstm32f4xx.h)可以直接在工程里创建,因为我们目前仍然使用寄存器编程

1、复制文件

startup_stm32f40xx.s 则需要从固件库中获取。它的路径如下:

FS_STM32F407IGH6_开发资料 > 04_芯片库 > STM32F4xx固件库 > 1. STM32F4xx固件库

└─ STM32F4xx_DSP_StdPeriph_Lib_V1.4.0
   └─ Libraries
      └─ CMSIS
         └─ Device
            └─ ST
               └─ STM32F4xx
                  └─ Source
                     └─ Templates
                        └─ arm
                           └─ startup_stm32f40xx.s

如下

image-20250319144042200

将文件复制到 工程目录下当中

image-20250319144448353

将文件添加到 工程 中 。

右键点击 Soutce Group 1 -> add Existing Files to Group 'Soutce Group 1'

image-20250319144539720

选择显示所有类型文件

image-20250319144839536

选择 工程中的 startup_stm32f40xx.s文件

image-20250319144923420

此时 左边的文件树中就已经有这个文件了。

image-20250319145001496

2、创建文件

右键点击 Soutce Group 1 -> add New Item to Group 'Soutce Group 1'

image-20250319145101083

然后创建main.c文件 ,操作如下

image-20250319145218564

重复第一步 添加stm32f4xx.h 文件,然后按下面的操作创建文件。

image-20250319145338342

5.3 配置工程

5.3.1 配置魔法棒

1、配置微库

这一步的配置工作很重要,很多人串口用不了 printf 函数,编译有问题,下载有问题,都是这个步骤的配置出了错。

选择微库的目的是为了在日后编写串口驱动的时候可以使用 printf 函数。而且有些应用中如果用了 STM32 的浮点运算单元 FPU,一定要同时开微库,不然有时会出现各种奇怪的现象。FPU 的开关选项在微库配置选项下方的“Single Precision”中,默认是开的。

image-20250319145622610

2、配置输出

Output 选项卡中把输出文件夹定位到我们工程目录下的 Objects 文件夹,如果想在编译的过程中生成 hex 文件,那么那 Create HEX File 选项勾上,具体见图配置 Output 选项卡 。

image-20250319145856721

3、下载配置

先确定开发板是否已经连接电脑 如下图

微信图片_20250319151329(1)

点击 Debug 选项 然后选择 ST-Link ... 选项

点击魔法棒开始配置

image-20250319152002643

进入 STLink 配置界面

image-20250319151905656

点击上方的 Flash Download

image-20250319152932603

但是此时 我们发现 在 下面这个白色框框中并没有东西,在这个里面我们是需要增加一个Flash 大小的,所以我们需要去为他增加一个flash 包。

QQ_1738760880171

先点击编译,然后点击下载

QQ_1738761115684

5.3.2 配置小扳手

1、配置编码格式

QQ_1738744713842

2、配置自动补全

QQ_1738757783500

Strut/Class Members:用于开启结构体/类成员提示功能。

Function Parameters:用于开启函数参数提示功能。

Symbols after xx characters:用于开启代码提示功能,即在输入多少个字符以后,提示匹配的内容。

Dynamic Syntax Checking:则用于开启动态语法检测,比如编写的代码存在语法错误的时候,出现相应的图标。

3、配置文本美化

配置缩进

QQ_1738757862431

修改每个元素风格

QQ_1738757978866

此外,还可以去下载别人的风格,可以去CSDN上面搜索 keil风格美化模板 或者 MDK风格美化

第六章 寄存器示例

6.1 总线基地址

总线的最低地址,挂载在该总线上的首个外设的地址

STM32 总线基地址与偏移表

总线名称总线基地址(十六进制)相对外设基地址的偏移(十六进制)
APB10x400000000x0
APB20x400100000x10000
AHB10x400200000x20000
AHB20x500000000x10000000
AHB30x600000000x20000000

APB1/APB2:

APB1:低速外设(如 TIM2-7、UART4/5)

APB2:高速外设(如 SPI1、ADC1、TIM1/8)

AHB1/AHB2/AHB3:

AHB1:核心外设(CPU、DMA、SRAM、GPIO)

AHB2:高速外设(USB、RNG)

AHB3:片外扩展(通过 FSMC/FMC 接口连接外部存储器)

地址计算示例

GPIOA 寄存器基地址(位于 APB2 总线):

  APB2基地址 + GPIOA偏移 = 0x40010000 + 0x0800 = 0x40010800

6.2 外设基地址

总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为“XX 外设基地址”。这里以 GPIO 为例,这里相对 APB1 总线的地址偏移,外设基地址减去 APB1 总线基地址 0x4002 0000 就是相对 APB1 总线的地址偏移,因为 GPIO 端口全是挂载在 APB1 总线。

6.3 外设寄存器

(AHB1总线基地址:0x40020000

外设名称外设基地址(十六进制)相对AHB1总线的地址偏移(十六进制)
GPIOA0x400200000x0
GPIOB0x400204000x0400
GPIOC0x400208000x0800
GPIOD0x40020C000x0C00
GPIOE0x400210000x1000
GPIOF0x400214000x1400
GPIOG0x400218000x1800
GPIOH0x40021C000x1C00

地址计算规则

外设基地址 = AHB1总线基地址 + 相对偏移量

例如:

GPIOD基地址 = `0x40020000` (AHB1基地址) + `0x0C00` (偏移) = `0x40020C00`

应用示例

// 通过指针访问GPIOC的ODR寄存器(偏移0x14)

#define GPIOC_ODR (*(volatile uint32_t*)(0x40020800 + 0x14))

GPIOC_ODR |= 0x00000001;  // 设置PC0引脚为高电平

6.4 寄存器描述

image-20250318215949933

6.4 .1 名称

GPIOx_MODER:其中 “x” 表示端口号(A、B、C…)。

含义:GPIO 端口模式寄存器,用于配置端口的输入/输出/复用/模拟等模式。

6.4.2 偏移地址

偏移地址:0x00(相对于 GPIO 外设基地址)

说明:在 STM32 中,每个 GPIO 端口通常都有一个基地址,MODER 寄存器相对于该基地址的偏移是 0x00;不同端口只需要在其基地址上加上该偏移即可访问对应的 MODER。

6.4.3 寄存器位表

寄存器总共占 32 位(bit 31 ~ bit 0),其中每两位对应一个引脚的配置:

image-20250318220740503

每个 MODERy[1:0] 负责 端口 y(y = 0..15)的模式设置。

6.4.4 位功能说明

MODERy[1:0]

配置端口 y 的 I/O 模式:

  • 00:输入模式(复位状态)
  • 01:通用输出模式
  • 10:复用功能模式(如 UART、SPI、I2C 等外设功能引脚)
  • 11:模拟模式

rw、r、w

等访问权限标记:

  • rw 表示可读可写
  • r 表示只读
  • w 表示只写

6.4.5 复位值

复位值:针对 STM32 的不同系列,GPIOx_MODER 的复位值一般是:

  • 端口 A:0xA800 0000
  • 端口 B:0x0000 0280
  • 其他端口:0x0000 0000

说明:这个值表示在芯片上电复位或系统复位后,各引脚所对应的模式默认状态;大多数端口默认是输入模式,部分端口(如端口 A、B)因特殊功能(如调试接口)而有不同的初始值。

  • 名称 用来标识寄存器的功能(GPIOx_MODER 表示配置 GPIO 端口模式)。

  • 偏移地址 指明该寄存器相对外设基地址的距离,用于快速定位寄存器。

  • 寄存器位表 描述了寄存器中每一位或每几位的具体含义和功能分配。

  • 位功能说明 列出了各字段可取值的解释,例如输入/输出/复用等模式。

  • **复位值 则告诉我们芯片在上电或复位后,寄存器中各位的默认状态。

6.5 GPIO寄存器

GPIO寄存器_GPIOx_MODER

GPIO寄存器_GPIOx_GPIOx_OTYPER_1

GPIO寄存器_GPIOx_GPIOx_OTYPER_2

GPIO寄存器_GPIOx_OSPEEDR

GPIO寄存器_GPIOx_PUPDR

GPIO寄存器_GPIOx_PUPDR_2

GPIO寄存器_GPIOx_ODR

6.6 寄存器基础操作

在寄存器操作中,我们将直接使用“硬地址”编程的方式:先根据芯片手册中的寄存器表获取寄存器的起始地址,然后通过指针运算直接访问该寄存器。

本示例以 GPIOF 模式寄存器(GPIOF_MODER)为例,地址为 0x40021400

6.6.1 硬地址基础写法

如果需要操作这个寄存器,首先要了解它的写法,如下所示:

// 实例如下
*(volatile unsigned int *)0x40021400 &= ~(0x03 << (8 * 2));

格式说明

  • 0x40021400:

    • 这是一个16 进制地址常量,对应 STM32 中某个硬件寄存器的起始地址。
  • (volatile unsigned int *)0x40021400

    • volatile 防止编译器优化: 该内存地址的内容可能在程序之外被改变,必须每次都真实访问硬件,而不能进行编译器优化。
    • unsigned int 在 32 位环境下通常占用 4 个字节,与 STM32 的寄存器宽度相匹配。
  • *(...)

    • 对这个指针进行解引用操作,表示“取出该指针指向地址处存放的 32 位数据”。
    • 如果用在右值(读操作)就会该寄存器的内容
    • 如果用在左值(写操作)就会新值到该寄存器。

6.6.2 置0操作

一位设置为 0

// 示例:清除第 4 位 (bit4)
*(volatile unsigned int *)0x40021400 &= ~(1 << 4);

多位设置为 0

// 示例:清除第 16~17 位 (bit16、bit17),即 Pin8 的模式配置位
*(volatile unsigned int *)0x40021400 &= ~(0x03 << 16);

6.6.3 置1操作

一位设置为1

// 示例:将第 4 位 (bit4) 置1
*(volatile unsigned int *)0x40021400 |= (1 << 4);

多位设置位1

// 示例:将第 16~17 位 (bit16、bit17) 置1
*(volatile unsigned int *)0x40021400 |= (0x03 << 16);

6.6.4 取反操作

一位取反

// 例如:翻转第 4 位 (bit4)
*(volatile unsigned int *)0x40021400 ^= (1 << 4);

多为取反

// 例如:翻转第 16~17 位 (bit16、bit17)
*(volatile unsigned int *)0x40021400 ^= (0x03 << 16);

6.6.5 读取操作

// 读取该寄存器内容并存放到变量 'regVal' 中
unsigned int regVal = *(volatile unsigned int *)0x40021400;

6.7 寄存器封装操作

为了方便理解和记忆,在编程中我们通常将总线基地址外设基地址封装为宏,并以其名称作为宏名进行引用。这样可以大幅减少使用裸地址的情况,提高代码的可读性和可维护性。

6.7.1 封装总线

/****************************** 外设寄存器 ******************************/
// 外设基地址
#define PERIPHERAL_BASE         ((unsigned int)0x40000000 )

// 总线基地址
#define PERIPHERAL_APB1         (PERIPHERAL_BASE)
#define PERIPHERAL_APB2         (PERIPHERAL_BASE + 0x10000)
#define PERIPHERAL_AHB1         (PERIPHERAL_BASE + 0x20000)
#define PERIPHERAL_AHB2         (PERIPHERAL_BASE + 0x10000000)

6.7.2 封装外设

/****************************** GPIO 寄存器 ******************************/

// GPIO 寄存器 基地址
#define GPIO_A_BASE             (PERIPHERAL_AHB1 + 0x0000)
#define GPIO_B_BASE             (PERIPHERAL_AHB1 + 0x0400)
#define GPIO_C_BASE             (PERIPHERAL_AHB1 + 0x0800)
#define GPIO_D_BASE             (PERIPHERAL_AHB1 + 0x0C00)
#define GPIO_E_BASE             (PERIPHERAL_AHB1 + 0x1000)
#define GPIO_F_BASE             (PERIPHERAL_AHB1 + 0x1400)
#define GPIO_G_BASE             (PERIPHERAL_AHB1 + 0x1800)
#define GPIO_H_BASE             (PERIPHERAL_AHB1 + 0x1C00)
#define GPIO_I_BASE             (PERIPHERAL_AHB1 + 0x2000)

// GPIO 寄存器 偏移
#define GPIOx_MODER              0x00
#define GPIOx_OTYPER             0x04
#define GPIOx_OSPEEDR            0x08
#define GPIOx_PUPDR              0x0C
#define GPIOx_IDR                0x10
#define GPIOx_ODR                0x14

// GPIO A 寄存器 地址
#define GPIOA_MODER             *(unsigned int *)(GPIO_A_BASE + GPIOx_MODER  )
#define GPIOA_OTYPER            *(unsigned int *)(GPIO_A_BASE + GPIOx_OTYPER )
#define GPIOA_OSPEEDR           *(unsigned int *)(GPIO_A_BASE + GPIOx_OSPEEDR)
#define GPIOA_PUPDR             *(unsigned int *)(GPIO_A_BASE + GPIOx_PUPDR  )
#define GPIOA_IDR               *(unsigned int *)(GPIO_A_BASE + GPIOx_IDR    )
#define GPIOA_ODR               *(unsigned int *)(GPIO_A_BASE + GPIOx_ODR    )

6.7.3 封装寄存器列表

/****************************** GPIO 寄存器 ******************************/

// GPIO 寄存器 基地址
#define GPIO_A_BASE             (PERIPHERAL_AHB1 + 0x0000)
#define GPIO_B_BASE             (PERIPHERAL_AHB1 + 0x0400)
#define GPIO_C_BASE             (PERIPHERAL_AHB1 + 0x0800)
#define GPIO_D_BASE             (PERIPHERAL_AHB1 + 0x0C00)
#define GPIO_E_BASE             (PERIPHERAL_AHB1 + 0x1000)
#define GPIO_F_BASE             (PERIPHERAL_AHB1 + 0x1400)
#define GPIO_G_BASE             (PERIPHERAL_AHB1 + 0x1800)
#define GPIO_H_BASE             (PERIPHERAL_AHB1 + 0x1C00)
#define GPIO_I_BASE             (PERIPHERAL_AHB1 + 0x2000)

// 定义数据格式
typedef unsigned int uint32_t;		// 无符号 32 位变量
typedef unsigned char uint16_t;		// 无符号 16 位变量

// 声明GPIO结构体
typedef struct 
{
    volatile uint32_t MODER   ; // 模式寄存器
    volatile uint32_t OTYPER  ; // 输出模式寄存器
    volatile uint32_t OSPEEDR ; // 速度寄存器
    volatile uint32_t PUPDR   ; // 上下拉寄存器
    volatile uint32_t IDR     ; // 输入寄存器
    volatile uint32_t ODR     ; // 输出寄存器
}GPIO_TypeDef;

// 定义GPIO结构体
#define GPIOA ((GPIO_TypeDef *)GPIO_A_BASE)
#define GPIOB ((GPIO_TypeDef *)GPIO_B_BASE)
#define GPIOC ((GPIO_TypeDef *)GPIO_C_BASE)
#define GPIOD ((GPIO_TypeDef *)GPIO_D_BASE)
#define GPIOE ((GPIO_TypeDef *)GPIO_E_BASE)
#define GPIOF ((GPIO_TypeDef *)GPIO_F_BASE)
#define GPIOG ((GPIO_TypeDef *)GPIO_G_BASE)
#define GPIOH ((GPIO_TypeDef *)GPIO_H_BASE)
#define GPIOI ((GPIO_TypeDef *)GPIO_I_BASE)

6.8 GPIO示例代码-地址操作

/*鸣谢:此代码由 小曹老师 提供灵感,最后由 小谭老师 完成*/

#include "stm32f4xx.h"                  // Device header

void SystemInit(void)
{
	
}

// 核心板
// PF8	D2
// 说明:低电平点亮
// 基地址:GPIO F	0x40021400

// 扩展板
// PC4	LED1
// PC3	LED2
// PC13	LED3
// 说明:高电平点亮
// 基地址:GPIO C	0x40020800

// GPIOx_MODER		地址偏移 0x00	输出模式 01
// GPIOx_OTYPER		地址偏移 0x04	推挽模式 0
// GPIOx_OSPEEDR	地址偏移 0x08	低速模式 00
// GPIOx_PUPDR		地址偏移 0x0C	上拉模式 01	下拉模式10
// GPIOx_ODR		地址偏移 0x14	高电平 1  低电平 0

// 时钟RCC		 基地址 0x40023800
// AHB1			偏移地址 0x30
// GPIOF		位5		置1 使能
// GPIOC		位2		置1 使能

void LED_Init_D2(void)
{
	// 启动时钟
	// RCC_AHB1ENR
	*(volatile unsigned int* ) 0x40023830 |= ( 0x01 << 5 );  // 设置  GPIOF 时钟使能

	// 设定模式		输出模式
	// GPIOx_MODER
	*(volatile unsigned int* ) 0x40021400 &= ~( 0x03 << (8 * 2) );	// 清空
	*(volatile unsigned int* ) 0x40021400 |= ( 0x01 << (8 * 2) );	// 设置   	01	输出模式

	// 输出模式		推挽模式
	// GPIOx_OTYPER
	*(volatile unsigned int* ) 0x40021404 &= ~( 0x01 << 8 );			// 设置 	0	推挽模式

	// 速度设置 	低速模式
	// GPIOx_OSPEEDR
	*(volatile unsigned int* ) 0x40021408 &= ~( 0x03 << (8 * 2) );	// 设置		00	低速模式

	// 上下拉		上拉模式
	// GPIOx_PUPDR
	*(volatile unsigned int* ) 0x4002140C &= ~( 0x03 << (8 * 2) );	// 清空
	*(volatile unsigned int* ) 0x4002140C |= ( 0x02 << (8 * 2) );	// 设置   	01	上拉模式

	// 初始电平		高电平
	// GPIOx_ODR
	*(volatile unsigned int* ) 0x40021414 |= ( 0x01 << 8 );	// 设置		1	高电平

}

void Write_D2(unsigned char start)
{
	// 高电平
	// GPIOx_ODR
	if(start == 1)
	{
		*(volatile unsigned int* ) 0x40021414 |= ( 0x01 << 8 );	// 设置		1	高电平
	}

	// 低电平
	// GPIOx_ODR
	if(start == 0)
	{
		*(volatile unsigned int* ) 0x40021414 &= ~( 0x01 << 8 );	// 设置		0	低电平
	}
	
}

void LED_Init_LED1(void)
{
	// 启动时钟
	// RCC_AHB1ENR
	*(volatile unsigned int* ) 0x40023830 |= ( 0x01 << 2 );  // 设置  GPIOF 时钟使能

	// 设定模式		输出模式
	// GPIOx_MODER
	*(volatile unsigned int* ) 0x40020800 &= ~( 0x03 << (4 * 2) );	// 清空
	*(volatile unsigned int* ) 0x40020800 |= ( 0x01 << (4 * 2) );	// 设置   	01	输出模式

	// 输出模式		推挽模式
	// GPIOx_OTYPER
	*(volatile unsigned int* ) 0x40020804 &= ~( 0x01 << 4 );		// 设置 	0	推挽模式

	// 速度设置 	低速模式
	// GPIOx_OSPEEDR
	*(volatile unsigned int* ) 0x40020808 &= ~( 0x03 << (4 * 2) );	// 设置		00	低速模式

	// 上下拉		上拉模式
	// GPIOx_PUPDR
	*(volatile unsigned int* ) 0x4002080C &= ~( 0x03 << (4 * 2) );	// 清空
	*(volatile unsigned int* ) 0x4002080C |= ( 0x02 << (4 * 2) );	// 设置   	01	上拉模式

	// 初始电平		高电平
	// GPIOx_ODR
	*(volatile unsigned int* ) 0x40020814 &= ~( 0x01 << 4 );	// 设置		1	高电平

}


void Write_LED1(unsigned char start)
{
	// 高电平
	// GPIOx_ODR
	if(start == 1)
	{
		*(volatile unsigned int* ) 0x40020814 |= ( 0x01 << 4 );	// 设置		1	高电平
	}

	// 低电平
	// GPIOx_ODR
	if(start == 0)
	{
		*(volatile unsigned int* ) 0x40020814 &= ~( 0x01 << 4 );	// 设置		0	低电平
	}
	
}


void LED_Init_LED2(void)
{
	// 启动时钟
	// RCC_AHB1ENR
	*(volatile unsigned int* ) 0x40023830 |= ( 0x01 << 2 );  // 设置  GPIOF 时钟使能

	// 设定模式		输出模式
	// GPIOx_MODER
	*(volatile unsigned int* ) 0x40020800 &= ~( 0x03 << (3 * 2) );	// 清空
	*(volatile unsigned int* ) 0x40020800 |= ( 0x01 << (3 * 2) );	// 设置   	01	输出模式

	// 输出模式		推挽模式
	// GPIOx_OTYPER
	*(volatile unsigned int* ) 0x40020804 &= ~( 0x01 << 3 );		// 设置 	0	推挽模式

	// 速度设置 	低速模式
	// GPIOx_OSPEEDR
	*(volatile unsigned int* ) 0x40020808 &= ~( 0x03 << (3 * 2) );	// 设置		00	低速模式

	// 上下拉		上拉模式
	// GPIOx_PUPDR
	*(volatile unsigned int* ) 0x4002080C &= ~( 0x03 << (3 * 2) );	// 清空
	*(volatile unsigned int* ) 0x4002080C |= ( 0x02 << (3 * 2) );	// 设置   	01	上拉模式

	// 初始电平		高电平
	// GPIOx_ODR
	*(volatile unsigned int* ) 0x40020814 &= ~( 0x01 << 3 );	// 设置		1	高电平

}


void Write_LED2(unsigned char start)
{
	// 高电平
	// GPIOx_ODR
	if(start == 1)
	{
		*(volatile unsigned int* ) 0x40020814 |= ( 0x01 << 3 );	// 设置		1	高电平
	}

	// 低电平
	// GPIOx_ODR
	if(start == 0)
	{
		*(volatile unsigned int* ) 0x40020814 &= ~( 0x01 << 3 );	// 设置		0	低电平
	}
	
}


void LED_Init_LED3(void)
{
	// 学生自己实现

}


void Write_LED3(unsigned char start)
{
	// 学生自己实现
}


int main(void)
{
	SystemInit();
	
	LED_Init_D2();
	LED_Init_LED1();
	LED_Init_LED2();
	LED_Init_LED3();
	
	while (1)
	{
		// 启动 D2 
		Write_D2(0);
		// 启动 LED1
		Write_LED1(1);
		// 启动 LED2
		Write_LED2(1);
		// 启动 LED3
		Write_LED3(1);
		
	}
}

6.9 GPIO示例代码-封装地址

stm32f4xx.h中编写以下内容

6.9.1stm32f4xx.h

#ifndef __STM32_F4XX_H__
#define __STM32_F4XX_H__


// STM32F407


// 基础寄存器
#define PERIPHERAL_BASE         ((unsigned int)0x40000000 )
#define PERIPHERAL_APB1         (PERIPHERAL_BASE)
#define PERIPHERAL_APB2         (PERIPHERAL_BASE + 0x10000)
#define PERIPHERAL_AHB1         (PERIPHERAL_BASE + 0x20000)
#define PERIPHERAL_AHB2         (PERIPHERAL_BASE + 0x10000000)

/****************************** GPIO 寄存器 ******************************/

// GPIO 寄存器 基地址
#define GPIO_A_BASE             (PERIPHERAL_AHB1 + 0x0000)
#define GPIO_B_BASE             (PERIPHERAL_AHB1 + 0x0400)
#define GPIO_C_BASE             (PERIPHERAL_AHB1 + 0x0800)
#define GPIO_D_BASE             (PERIPHERAL_AHB1 + 0x0C00)
#define GPIO_E_BASE             (PERIPHERAL_AHB1 + 0x1000)
#define GPIO_F_BASE             (PERIPHERAL_AHB1 + 0x1400)
#define GPIO_G_BASE             (PERIPHERAL_AHB1 + 0x1800)
#define GPIO_H_BASE             (PERIPHERAL_AHB1 + 0x1C00)
#define GPIO_I_BASE             (PERIPHERAL_AHB1 + 0x2000)

// 偏移地址
#define GPIOx_MODER              0x00
#define GPIOx_OTYPER             0x04
#define GPIOx_OSPEEDR            0x08
#define GPIOx_PUPDR              0x0C
#define GPIOx_IDR                0x10
#define GPIOx_ODR                0x14

// GPIO 寄存器
#define GPIOA_MODER             *(unsigned int *)(GPIO_A_BASE + GPIOx_MODER  )
#define GPIOA_OTYPER            *(unsigned int *)(GPIO_A_BASE + GPIOx_OTYPER )
#define GPIOA_OSPEEDR           *(unsigned int *)(GPIO_A_BASE + GPIOx_OSPEEDR)
#define GPIOA_PUPDR             *(unsigned int *)(GPIO_A_BASE + GPIOx_PUPDR  )
#define GPIOA_IDR               *(unsigned int *)(GPIO_A_BASE + GPIOx_IDR    )
#define GPIOA_ODR               *(unsigned int *)(GPIO_A_BASE + GPIOx_ODR    )

#define GPIOB_MODER             *(unsigned int *)(GPIO_B_BASE + GPIOx_MODER  )
#define GPIOB_OTYPER            *(unsigned int *)(GPIO_B_BASE + GPIOx_OTYPER )
#define GPIOB_OSPEEDR           *(unsigned int *)(GPIO_B_BASE + GPIOx_OSPEEDR)
#define GPIOB_PUPDR             *(unsigned int *)(GPIO_B_BASE + GPIOx_PUPDR  )
#define GPIOB_IDR               *(unsigned int *)(GPIO_B_BASE + GPIOx_IDR    )
#define GPIOB_ODR               *(unsigned int *)(GPIO_B_BASE + GPIOx_ODR    )

#define GPIOC_MODER             *(unsigned int *)(GPIO_C_BASE + GPIOx_MODER  )
#define GPIOC_OTYPER            *(unsigned int *)(GPIO_C_BASE + GPIOx_OTYPER )
#define GPIOC_OSPEEDR           *(unsigned int *)(GPIO_C_BASE + GPIOx_OSPEEDR)
#define GPIOC_PUPDR             *(unsigned int *)(GPIO_C_BASE + GPIOx_PUPDR  )
#define GPIOC_IDR               *(unsigned int *)(GPIO_C_BASE + GPIOx_IDR    )
#define GPIOC_ODR               *(unsigned int *)(GPIO_C_BASE + GPIOx_ODR    )

#define GPIOE_MODER             *(unsigned int *)(GPIO_E_BASE + GPIOx_MODER  )
#define GPIOE_OTYPER            *(unsigned int *)(GPIO_E_BASE + GPIOx_OTYPER )
#define GPIOE_OSPEEDR           *(unsigned int *)(GPIO_E_BASE + GPIOx_OSPEEDR)
#define GPIOE_PUPDR             *(unsigned int *)(GPIO_E_BASE + GPIOx_PUPDR  )
#define GPIOE_IDR               *(unsigned int *)(GPIO_E_BASE + GPIOx_IDR    )
#define GPIOE_ODR               *(unsigned int *)(GPIO_E_BASE + GPIOx_ODR    )

#define GPIOF_MODER             *(unsigned int *)(GPIO_F_BASE + GPIOx_MODER  )
#define GPIOF_OTYPER            *(unsigned int *)(GPIO_F_BASE + GPIOx_OTYPER )
#define GPIOF_OSPEEDR           *(unsigned int *)(GPIO_F_BASE + GPIOx_OSPEEDR)
#define GPIOF_PUPDR             *(unsigned int *)(GPIO_F_BASE + GPIOx_PUPDR  )
#define GPIOF_IDR               *(unsigned int *)(GPIO_F_BASE + GPIOx_IDR    )
#define GPIOF_ODR               *(unsigned int *)(GPIO_F_BASE + GPIOx_ODR    )


/****************************** RCC 寄存器 ******************************/

// RCC寄存器 基地址
#define RCC_BASE                (PERIPHERAL_BASE + 0x23800)

// RCC寄存器 启动时钟寄存器
#define RCC_AHB1ENR                *(unsigned int*)(RCC_BASE + 0x30)
#define RCC_AHB2ENR                *(unsigned int*)(RCC_BASE + 0x34)
#define RCC_APB1ENR                *(unsigned int*)(RCC_BASE + 0x40)
#define RCC_APB2ENR                *(unsigned int*)(RCC_BASE + 0x44)

#endif

6.9.2 main.c

main.c 中编写以下内容

#include "stm32f4xx.h"                  // Device header

void SystemInit(void)
{
	
}

void Delay_us(unsigned int us)
{
	while (us--)
	{
		unsigned int n = 16;
		while (n--)
		{
			
		}
	}
}

void LED_Init_LED3(void)
{
	// 启动时钟
	// RCC_AHB1ENR
	RCC_AHB1ENR |= ( 0x01 << 2 );  // 设置  GPIOF 时钟使能

	// 设定模式		输出模式
	// GPIOx_MODER
	GPIOC_MODER &= ~( 0x03 << (13 * 2) );	// 清空
	GPIOC_MODER |= ( 0x01 << (13 * 2) );	// 设置   	01	输出模式

	// 输出模式		推挽模式
	// GPIOx_OTYPER
	GPIOC_OTYPER &= ~( 0x01 << 13 );		// 设置 	0	推挽模式

	// 速度设置 	低速模式
	// GPIOx_OSPEEDR
	GPIOC_OSPEEDR &= ~( 0x03 << (13 * 2) );	// 设置		00	低速模式

	// 上下拉		上拉模式
	// GPIOx_PUPDR
	GPIOC_PUPDR &= ~( 0x03 << (13 * 2) );	// 清空
	GPIOC_PUPDR |= ( 0x02 << (13 * 2) );	// 设置   	01	上拉模式

	// 初始电平		高电平
	// GPIOx_ODR
	GPIOC_ODR &= ~( 0x01 << 13 );	// 设置		1	高电平

}


void Write_LED3(unsigned char start)
{
	// 高电平
	// GPIOx_ODR
	if(start == 1)
	{
		GPIOC_ODR |= ( 0x01 << 13 );	// 设置		1	高电平
	}

	// 低电平
	// GPIOx_ODR
	if(start == 0)
	{
		GPIOC_ODR &= ~( 0x01 << 13 );	// 设置		0	低电平
	}
	
}


int main(void)
{
	SystemInit();
	
	LED_Init_LED3();
	
	while (1)
	{
		// 启动 LED3
		Write_LED3(1);
	}
}

6.10 GPIO示例代码-构建函数库

6.10.1​ stm32f4xx.h

#ifndef __STM32_F4XX_H__
#define __STM32_F4XX_H__


// STM32F407



// 基础寄存器
#define PERIPHERAL_BASE         ((unsigned int)0x40000000 )
#define PERIPHERAL_APB1         (PERIPHERAL_BASE)
#define PERIPHERAL_APB2         (PERIPHERAL_BASE + 0x10000)
#define PERIPHERAL_AHB1         (PERIPHERAL_BASE + 0x20000)
#define PERIPHERAL_AHB2         (PERIPHERAL_BASE + 0x10000000)

// RCC寄存器 基地址
#define RCC_BASE                (PERIPHERAL_BASE + 0x23800)

// GPIO 寄存器 基地址
#define GPIO_A_BASE             (PERIPHERAL_BASE + 0x20000)
#define GPIO_B_BASE             (PERIPHERAL_BASE + 0x20400)
#define GPIO_C_BASE             (PERIPHERAL_BASE + 0x20800)
#define GPIO_D_BASE             (PERIPHERAL_BASE + 0x20C00)
#define GPIO_E_BASE             (PERIPHERAL_BASE + 0x21000)
#define GPIO_F_BASE             (PERIPHERAL_BASE + 0x21400)
#define GPIO_G_BASE             (PERIPHERAL_BASE + 0x21800)
#define GPIO_H_BASE             (PERIPHERAL_BASE + 0x21C00)
#define GPIO_I_BASE             (PERIPHERAL_BASE + 0x22000)

typedef unsigned int uint32_t;
typedef unsigned char uint16_t;

/****************************** GPIO 寄存器 ******************************/

typedef struct 
{
    volatile uint32_t MODER   ; // 模式寄存器
    volatile uint32_t OTYPER  ; // 输出模式寄存器
    volatile uint32_t OSPEEDR ; // 速度寄存器
    volatile uint32_t PUPDR   ; // 上下拉寄存器
    volatile uint32_t IDR     ; // 输入寄存器
    volatile uint32_t ODR     ; // 输出寄存器
}GPIO_TypeDef;


#define GPIOA ((GPIO_TypeDef *)GPIO_A_BASE)
#define GPIOB ((GPIO_TypeDef *)GPIO_B_BASE)
#define GPIOC ((GPIO_TypeDef *)GPIO_C_BASE)
#define GPIOD ((GPIO_TypeDef *)GPIO_D_BASE)
#define GPIOE ((GPIO_TypeDef *)GPIO_E_BASE)
#define GPIOF ((GPIO_TypeDef *)GPIO_F_BASE)
#define GPIOG ((GPIO_TypeDef *)GPIO_G_BASE)
#define GPIOH ((GPIO_TypeDef *)GPIO_H_BASE)
#define GPIOI ((GPIO_TypeDef *)GPIO_I_BASE)

#define GPIO_Pin_0      0
#define GPIO_Pin_1      1
#define GPIO_Pin_2      2
#define GPIO_Pin_3      3
#define GPIO_Pin_4      4
#define GPIO_Pin_5      5
#define GPIO_Pin_6      6
#define GPIO_Pin_7      7
#define GPIO_Pin_8      8
#define GPIO_Pin_9      9
#define GPIO_Pin_10      10
#define GPIO_Pin_11      11
#define GPIO_Pin_12      12
#define GPIO_Pin_13      13
#define GPIO_Pin_14      14


/****************************** RCC 寄存器 ******************************/

typedef struct {
    volatile uint32_t RCC_CR;         // 0x00
    volatile uint32_t RCC_PLLCFGR;    // 0x04
    volatile uint32_t RCC_CFGR;       // 0x08
    volatile uint32_t RCC_CIR;        // 0x0C
    volatile uint32_t RCC_AHB1RSTR;   // 0x10
    volatile uint32_t RCC_AHB2RSTR;   // 0x14
    volatile uint32_t RCC_AHB3RSTR;   // 0x18
    
    uint32_t RESERVED0;               // 0x1C (保留)

    volatile uint32_t RCC_APB1RSTR;   // 0x20
    volatile uint32_t RCC_APB2RSTR;   // 0x24

    uint32_t RESERVED1[2];            // 0x28 - 0x2C (保留)

    volatile uint32_t RCC_AHB1ENR;    // 0x30
    volatile uint32_t RCC_AHB2ENR;    // 0x34
    volatile uint32_t RCC_AHB3ENR;    // 0x38

    uint32_t RESERVED2;               // 0x3C (保留)
    volatile uint32_t RCC_APB1ENR;    // 0x40
    volatile uint32_t RCC_APB2ENR;    // 0x44
} RCC_TypeDef;


#define RCC  ((RCC_TypeDef *)RCC_BASE)

#endif

6.10.2 stm32f4_gpio.c

#include "stm32f4_gpio.h"

// 设置引脚为高电平
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    GPIOx->ODR |= (0x01 << GPIO_Pin);
}


// 设置引脚为低电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
{
    GPIOx->ODR &= ~ ( 0x01 << GPIO_Pin);
}

6.10.3 stm32f4_gpio.h

#ifndef __STM32F4_F4_GPIO_
#define __STM32F4_F4_GPIO_

#include "stm32f4xx.h"

// 设置引脚为高电平
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);


// 设置引脚为低电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

#endif

6.10.4 main.c

#include "stm32f4xx.h"                  // Device header

void SystemInit(void)
{
	
}

// 核心板
// PF8	D2
// 说明:低电平点亮
// 基地址:GPIO F	0x40021400

// 扩展板
// PC4	LED1
// PC3	LED2
// PC13	LED3
// 说明:高电平点亮
// 基地址:GPIO C	0x40020800

// GPIOx_MODER		地址偏移 0x00	输出模式 01
// GPIOx_OTYPER		地址偏移 0x04	推挽模式 0
// GPIOx_OSPEEDR	地址偏移 0x08	低速模式 00
// GPIOx_PUPDR		地址偏移 0x0C	上拉模式 01	下拉模式10
// GPIOx_ODR		地址偏移 0x14	高电平 1  低电平 0

// 时钟RCC		 基地址 0x40023800
// AHB1			偏移地址 0x30
// GPIOF		位5		置1 使能
// GPIOC		位2		置1 使能

void LED_Init_D2(void)
{
	// 启动时钟
	// RCC_AHB1ENR
	RCC->RCC_AHB1ENR |= ( 0x01 << 5 );  // 设置  GPIOF 时钟使能

	// 设定模式		输出模式
	// GPIOx_MODER
	GPIOF->MODER &= ~( 0x03 << (GPIO_Pin_8 * 2) );	// 清空
	GPIOF->MODER |= ( 0x01 << (GPIO_Pin_8 * 2) );	// 设置   	01	输出模式

	// 输出模式		推挽模式
	// GPIOx_OTYPER
	GPIOF->OTYPER &= ~( 0x01 << GPIO_Pin_8 );			// 设置 	0	推挽模式

	// 速度设置 	低速模式
	// GPIOx_OSPEEDR
	GPIOF->OSPEEDR &= ~( 0x03 << (GPIO_Pin_8 * 2) );	// 设置		00	低速模式

	// 上下拉		上拉模式
	// GPIOx_PUPDR
	GPIOF->PUPDR &= ~( 0x03 << (GPIO_Pin_8 * 2) );	// 清空
	GPIOF->PUPDR |= ( 0x02 << (GPIO_Pin_8 * 2) );	// 设置   	01	上拉模式

	// 初始电平		高电平
	// GPIOx_ODR
	GPIOF->ODR |= ( 0x01 << GPIO_Pin_8 );	// 设置		1	高电平

}

void Write_D2(unsigned char start)
{
	// 高电平
	// GPIOx_ODR
	if(start == 1)
	{
		GPIOF->ODR |= ( 0x01 << GPIO_Pin_8 );	// 设置		1	高电平
	}

	// 低电平
	// GPIOx_ODR
	if(start == 0)
	{
		GPIOF->ODR &= ~( 0x01 << GPIO_Pin_8 );	// 设置		0	低电平
	}
	
}



int main(void)
{
	SystemInit();
	
	// 初始化
	LED_Init_D2();

	
	while (1)
	{
		// 启动 D2 
		GPIO_ResetBits( GPIOF , GPIO_Pin_8 );
	}
}