# 一、流水灯
{
int i;
unsigned int* p_sfr;
unsigned char led_pin;
/** 使能GPIOC口时钟 */
p_sfr = (void*)0x40023830; /**< RCC_AHB1ENR([4]P117) */
*p_sfr = 0x04ul;
/** GPIOC0引脚设为输出模式 */
p_sfr = (void*)0x40020800; /**< GPIOC_MODER([4]P157) */
*p_sfr = 0x5555ul;
p_sfr = (void*)0x40020818;
//初始化全灭
*p_sfr = 0x0000FFul;
while(1)
{
for(led_pin = 0; led_pin <= 7; led_pin++)
{
//ul代表32位无符号整型
/*
0000 0000 0000 0000 / 0000 0000 0000 0001
-> 0000 0000 0000 0001 / 0000 0000 0000 0000
*/
//左移16位
*p_sfr = 1ul << (led_pin + 16);
for(i = 0; i < 1500000; i++);
*p_sfr = 1ul << led_pin;
}
}
}
一共4个16位,每个16位控制两个: 0x 00 00 / 00 00 / 00 00 / 00 00
*p_sfr = 0x5555ul;
# 1. 00:输入模式(Input mode)
- 功能:引脚作为输入使用,可读取外部电平信号(高 / 低)或模拟信号(需配合其他寄存器)。
- 典型应用:
- 连接按键(读取按键按下 / 松开的电平);
- 接收外部传感器的数字信号(如高低电平触发信号);
- 作为 ADC 输入(需配合
PUPDR寄存器配置上下拉,避免浮空)。
# 2. 01:通用输出模式(General purpose output mode)
- 功能:引脚作为输出使用,可通过
ODR或BSRR寄存器主动输出高电平(1)或低电平(0)。 - 典型应用:
- 控制 LED 亮灭(如之前的流水灯);
- 驱动继电器、蜂鸣器等外设;
- 输出数字控制信号(如高低电平控制其他模块)。
# 3. 10:复用功能模式(Alternate function mode)
- 功能:引脚被复用为片上外设的专用引脚(而非通用 GPIO),由外设(而非 GPIO 寄存器)直接控制。
- 典型应用:
- UART 的 TX/RX 引脚(复用为串口收发);
- SPI 的 SCLK/MOSI/MISO 引脚(复用为 SPI 通信);
- TIM 的 PWM 输出引脚(复用为定时器功能)。
- 注意:复用功能模式需配合
AFR(复用功能选择寄存器)选择具体的外设(例如AF0对应系统功能,AF7可能对应 USART1 等)。
# 4. 11:模拟模式(Analog mode)
- 功能:引脚用于模拟信号传输,此时 GPIO 的数字功能(输入缓冲、输出驱动)被关闭,避免数字电路干扰模拟信号。
- 典型应用:
- ADC 输入通道(读取模拟电压,如温度传感器、电位器信号);
- DAC 输出通道(输出模拟电压);
- 模拟信号传输线(如音频信号、模拟传感器信号)。
# gpioC高低电平
高16位和低16位
C0-C15 高八位输出低电平,低八位输出高电平
00 00 /00 00
*p_sfr = 0xFFul;//高电平全灭
while(1)
{//全亮
*p_sfr = 0xFF0000ul;
for(i = 0; i < 1500000; i++);
//全灭
*p_sfr = 0xFFul;
for(i = 0; i < 1500000; i++);
}
BSRR(Bit Set/Reset Register)是 GPIO 的位设置 / 清除寄存器,32 位长度,功能分为两部分:
- 低 16 位(bit0~bit15):置位位(Set)。某一位写 1 时,对应引脚输出高电平(点亮 LED,假设 LED 是高电平驱动)。
- 高 16 位(bit16~bit31):清除位(Reset)。某一位写 1 时,对应引脚输出低电平(熄灭 LED)。
规则:控制引脚x(x 是 0~15 的引脚编号)时:
- 置位(高电平):
BSRR = 1 << x(操作低 16 位)。 - 清除(低电平):
BSRR = 1 << (x + 16)(操作高 16 位)。 - -若同一引脚的低 16 位(置位)和高 16 位(清除)同时写
1(例如 bit0 和 bit16 同时为 1),则清除操作优先,最终引脚输出低电平。
# RCC时钟
p_sfr = (void*)0x40023830; /**< RCC_AHB1ENR([4]P117) */
*p_sfr = 0x04ul;
//通过指针访问数组
//RCC->AHB1ENR |=RCC_AHB1ENR_GPIOCEN
在HAL 或 CMSIS 底层头文件中,如stm32f4xx.h会定义所有外设的基地址
#define RCC ((RCC_TypeDef *) RCC_BASE)
RCC_BASE是 RCC 寄存器组的起始地址。RCC是一个 指向 RCC 寄存器结构体的指针。- 只有当
RCC是一个结构体变量(非指针)时,才能写成RCC.AHB1ENR RCC_TypeDef是一个结构体类型,里面定义了 RCC 的所有寄存器,如:
{
__IO uint32_t CR;
__IO uint32_t PLLCFGR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t AHB1RSTR;
__IO uint32_t AHB2RSTR;
uint32_t RESERVED0[2];
__IO uint32_t AHB1ENR;
__IO uint32_t AHB2ENR;
...
} RCC_TypeDef;
1.RCC_AHB1ENR_GPIOCEN通常是一个宏,定义了 GPIOC 时钟使能位 对应的掩码(通常是 1<<2)
2.|= 是为了 只修改对应的位,不破坏其他外设的时钟状态。
# 二、启动代码的堆栈区
| 名称 | 功能 | 定义位置 | 特点 |
|---|---|---|---|
| Stack(栈) | 函数调用与中断现场保存 | 启动文件定义 | 自动分配、向下增长 |
| Heap(堆) | 动态内存分配 | 启动文件定义 | 手动管理、向上增长 |
| RAM | 存放数据、堆、栈 | 链接脚本 | 可读可写 |
| FLASH | 存放程序代码与常量 | 链接脚本 | 只读 |
| MSP | 主堆栈指针 | 复位后默认 | 中断与系统使用 |
| PSP | 进程堆栈指针 | RTOS 使用 | 每任务独立 |
读取 __initial_sp → 设置 MSP →
读取 Reset_Handler → 跳转执行 →
SystemInit() 初始化时钟 →
调用 main() →
程序正式运行
在程序运行时,堆栈(Stack) 是一块在 SRAM(内部数据存储器) 里分配的内存区域,用来:
- 保存函数调用时的返回地址;
- 保存局部变量;
- 保存中断现场(寄存器状态);
- 临时数据的压栈和出栈。
可以理解为:
“堆栈区是 CPU 的工作台,用来暂存数据、记录函数调用关系。”
# 2、堆栈区在启动时的作用
在 ARM Cortex-M 系列(如 STM32)中,系统上电复位后的第一件事,就是通过启动代码(startup 文件)设置堆栈指针。
启动代码文件,通常命名为:
startup_stm32f4xx.s startup_stm32h7xx.s startup_stm32f1xx.s
“是汇编文件“
# 3、启动代码中的堆栈初始化
上电复位 → 从地址 0x0000_0000 读取 __initial_sp → 写入 MSP → 从地址 0x0000_0004 读取 Reset_Handler 地址 → 跳转执行启动代码
# 4、堆栈的内存位置(RAM中)
在链接脚本(.ld 文件或 .scf 文件)中,堆栈区一般放在 SRAM 顶部:
例:STM32F407(128KB SRAM)
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
堆栈布局通常如下(内存从低到高):
| 地址 | 区域 | 说明 |
|---|---|---|
| 0x2001FFFF | 堆栈顶(__initial_sp) | 栈从高地址向低地址增长 |
| ↓ | Stack 区域 | 保存函数调用、中断数据 |
| ... | Heap 区域 | 动态内存(malloc等) |
| 0x20000000 | 静态数据(.data/.bss) | 全局变量、初始化数据 |
# 5、MSP 和 PSP:两个堆栈指针
Cortex-M 内核其实有两个独立的堆栈指针:
| 名称 | 全称 | 用途 |
|---|---|---|
| MSP | Main Stack Pointer | 启动时默认使用,用于中断和异常 |
| PSP | Process Stack Pointer | 用户任务、RTOS 线程使用 |
RTOS(如 FreeRTOS)会切换到 PSP 来给每个任务独立的栈。
# 三、最小系统
stm32F411RC
电源电路
外部高速和低速时钟电路(可选)
复位电路
BOOT电路
# 四、寄存器
{
__IO uint32_t MODER; /*!< GPIO port mode register, Address offset: 0x00 */
__IO uint32_t OTYPER; /*!< GPIO port output type register, Address offset: 0x04 */
__IO uint32_t OSPEEDR; /*!< GPIO port output speed register, Address offset: 0x08 */
__IO uint32_t PUPDR; /*!< GPIO port pull-up/pull-down register, Address offset: 0x0C */
__IO uint32_t IDR; /*!< GPIO port input data register, Address offset: 0x10 */
__IO uint32_t ODR; /*!< GPIO port output data register, Address offset: 0x14 */
__IO uint32_t BSRR; /*!< GPIO port bit set/reset register, Address offset: 0x18 */
__IO uint32_t LCKR; /*!< GPIO port configuration lock register, Address offset: 0x1C */
__IO uint32_t AFR[2]; /*!< GPIO alternate function registers, Address offset: 0x20-0x24 */
} GPIO_TypeDef;
每个成员对应 GPIO 端口的一个特定功能寄存器,__IO 通常是 volatile 的宏定义(确保编译器不优化对硬件寄存器的访问,因为寄存器值可能被硬件异步修改),uint32_t 表示 32 位寄存器,注释中的 “Address offset” 表示该寄存器相对于 GPIO 端口基地址的偏移量(用于硬件地址映射)。
MODER:GPIO 端口模式寄存器
配置引脚的工作模式(输入 / 输出 / 复用功能 / 模拟输入),每个引脚占 2 个 bit,可独立配置。OTYPER:GPIO 端口输出类型寄存器
配置输出引脚的类型(推挽输出 / 开漏输出),每个引脚占 1 个 bit。OSPEEDR:GPIO 端口输出速度寄存器
配置输出引脚的速率(低速 / 中速 / 高速 / 超高速),影响信号切换速度和功耗,每个引脚占 2 个 bit。PUPDR:GPIO 端口上下拉寄存器
配置引脚的上下拉状态(无上拉下拉 / 上拉 / 下拉),避免引脚悬空,每个引脚占 2 个 bit。IDR:GPIO 端口输入数据寄存器
读取该寄存器可获取引脚的当前输入电平(0 或 1),每个 bit 对应一个引脚的输入状态。ODR:GPIO 端口输出数据寄存器
写入该寄存器可设置引脚的输出电平(0 或 1),每个 bit 对应一个引脚的输出状态(但直接修改可能有 “读 - 改 - 写” 风险)。BSRR:GPIO 端口位设置 / 复位寄存器
用于原子操作(无 “读 - 改 - 写” 风险)地设置或清除引脚输出电平:高 16 位用于复位(清 0),低 16 位用于置位(置 1)。LCKR:GPIO 端口配置锁定寄存器
用于锁定引脚的配置(防止意外修改),一旦锁定,需复位才能重新配置。AFR[2]:GPIO 交替功能寄存器
当引脚配置为 “复用功能”(通过MODER设置)时,通过该寄存器选择具体的复用功能(如连接 UART、SPI 等外设),AFR[0]对应引脚 0-7,AFR[1]对应引脚 8-15,每个引脚占 4 个 bit。

