# 一、流水灯

{
	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)
  • 功能:引脚作为输出使用,可通过ODRBSRR寄存器主动输出高电平(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。