Skip to content

STM32 基础概念

本教程将带你了解 STM32 微控制器的核心概念,帮助你建立坚实的嵌入式开发基础。我们将以 STM32F103C8T6(Blue Pill 开发板)为例进行讲解。

目录


STM32 系列简介

什么是 STM32?

STM32 是意法半导体(STMicroelectronics)推出的一系列 32 位微控制器,基于 ARM Cortex-M 处理器内核。STM32 系列以其高性能、低功耗和丰富的外设而广受欢迎,是嵌入式开发的首选之一。

STM32 系列分类

STM32 系列按性能和应用场景分为多个系列:

系列内核特点典型应用
STM32F0Cortex-M0入门级,低成本简单控制、家电
STM32F1Cortex-M3主流型,性价比高工业控制、消费电子
STM32F4Cortex-M4高性能,带 DSP电机控制、音频处理
STM32F7Cortex-M7超高性能图形界面、高级控制
STM32L多种内核低功耗电池供电设备

STM32F103C8T6 规格详解

我们使用的 STM32F103C8T6 属于 F1 系列,俗称"Blue Pill"(蓝色药丸),是一款非常适合入门学习的开发板。

核心参数

参数数值说明
内核ARM Cortex-M332 位 RISC 处理器
最高主频72 MHz本模板配置为 64 MHz
Flash 存储64 KB存储程序代码
SRAM20 KB运行时数据存储
GPIO 引脚37 个通用输入/输出
定时器3 个通用 + 1 个高级用于计时和 PWM
通信接口USART×3, SPI×2, I2C×2串行通信
ADC2 个 12 位 ADC模数转换
封装LQFP4848 引脚封装

命名规则解读

STM32F103C8T6
│    │   ││││└─ 温度范围:-40~85°C
│    │   │││└── 封装:LQFP48
│    │   ││└── Flash 容量:64KB (8 = 64KB)
│    │   │└── 引脚数:48 引脚 (C = 48)
│    │   └── 型号:主流型
│    └── 子系列:F103 (增强型)
└── 意法半导体 32 位微控制器

ARM Cortex-M3 架构概述

什么是 ARM Cortex-M3?

Cortex-M3 是 ARM 公司专门为微控制器设计的 32 位处理器内核,具有以下特点:

  • 32 位架构:数据总线、地址总线均为 32 位
  • RISC 设计:精简指令集,指令执行效率高
  • Thumb-2 指令集:混合使用 16 位和 32 位指令,代码密度高

Cortex-M3 核心特性

1. 寄存器组

Cortex-M3 有 16 个通用寄存器(R0-R15):

寄存器用途
R0-R7通用寄存器,低 8 个
R8-R12通用寄存器,高 5 个
R13 (SP)堆栈指针
R14 (LR)链接寄存器,保存返回地址
R15 (PC)程序计数器

2. NVIC(嵌套向量中断控制器)

NVIC 是 Cortex-M3 的中断管理核心:

  • 支持 240 个外部中断
  • 支持 16 级可编程优先级
  • 支持中断嵌套(高优先级中断可打断低优先级)
  • 低延迟中断响应(12 个时钟周期)

3. SysTick 定时器

系统定时器,用于产生周期性中断:

c
// 在模板中,SysTick 用于 HAL 库的延时功能
void SysTick_Handler(void) { 
    HAL_IncTick();  // 更新系统滴答计数
}

为什么选择 Cortex-M3?

对于初学者来说,Cortex-M3 有以下优势:

  1. 易于学习:指令集简洁,寄存器数量适中
  2. 实时性好:NVIC 提供确定性的中断响应
  3. 生态完善:大量开发工具和库支持
  4. 成本低廉:适合教学和原型开发

存储器映射

什么是存储器映射?

存储器映射是指将物理存储器(Flash、SRAM)和外设寄存器映射到统一的地址空间。Cortex-M3 使用 32 位地址总线,可寻址 4GB 空间。

STM32F103 存储器映射结构

地址范围              | 区域          | 大小    | 用途
---------------------|---------------|---------|------------------
0x0000 0000 - 0x1FFF FFFF | Code         | 512 MB  | Flash/系统存储器
0x2000 0000 - 0x3FFF FFFF | SRAM         | 512 MB  | SRAM 区域
0x4000 0000 - 0x5FFF FFFF | Peripheral   | 512 MB  | 外设寄存器
0x6000 0000 - 0x9FFF FFFF | External     | 1 GB    | 外部扩展
0xA000 0000 - 0xDFFF FFFF | Reserved     | 1 GB    | 保留
0xE000 0000 - 0xFFFF FFFF | System       | 512 MB  | 系统区域

Flash 存储器(程序存储区)

Flash 用于存储程序代码和常量数据:

区域地址范围大小说明
主存储器0x0800 0000 起64 KB用户程序
系统存储器0x1FFF 00002 KBBootloader
选项字节0x1FFF F800-配置选项

重要概念:程序从 Flash 中执行,CPU 直接从 Flash 取指令。

SRAM(数据存储区)

SRAM 用于存储运行时数据:

  • 地址范围:0x2000 0000 起
  • 大小:20 KB
  • 用途
    • 全局变量
    • 静态变量
    • 堆栈(Stack)
    • 堆(Heap)

外设寄存器区域

外设寄存器映射在 0x4000 0000 开始的区域:

外设地址示例:
├── GPIOA: 0x4001 0800 - 0x4001 0BFF
├── GPIOB: 0x4001 0C00 - 0x4001 0FFF
├── GPIOC: 0x4001 1000 - 0x4001 13FF
├── USART1: 0x4001 3800 - 0x4001 3BFF
└── TIM1: 0x4001 2C00 - 0x4001 2FFF

时钟系统

为什么时钟如此重要?

时钟是微控制器的"心跳",所有操作都依赖时钟信号。正确的时钟配置是程序正常运行的基础。

STM32F103 时钟源

STM32F103 有多个时钟源:

时钟源频率说明
HSI8 MHz内部高速振荡器(出厂校准)
HSE8-25 MHz外部高速振荡器(晶振)
LSI40 kHz内部低速振荡器(看门狗)
LSE32.768 kHz外部低速振荡器(RTC)

时钟树结构

                    ┌─────────────────────────────────────────┐
                    │              时钟源                      │
                    └─────────────────────────────────────────┘

        ┌─────────────┬───────────────┼───────────────┬─────────────┐
        ▼             ▼               ▼               ▼             │
    ┌───────┐     ┌───────┐       ┌───────┐       ┌───────┐         │
    │  HSI  │     │  HSE  │       │  LSI  │       │  LSE  │         │
    │ 8MHz  │     │外部晶振│       │ 40kHz │       │32.768kHz│       │
    └───┬───┘     └───┬───┘       └───────┘       └───────┘         │
        │             │                                               │
        │    ┌────────┴────────┐                                      │
        │    │                 │                                      │
        ▼    ▼                 ▼                                      │
    ┌───────────────┐    ┌───────────────┐                          │
    │      PLL       │    │   CSS         │                          │
    │  (锁相环)      │    │ (时钟安全)     │                          │
    │  ×4/×8/×9...×16│   └───────────────┘                          │
    └───────┬───────┘                                                 │
            │                                                         │
            ▼                                                         │
    ┌───────────────┐                                                 │
    │   SYSCLK      │  ← 系统时钟(最高 72 MHz)                       │
    │   系统时钟    │                                                 │
    └───────┬───────┘                                                 │
            │                                                         │
    ┌───────┴───────┬─────────────────┬─────────────────┐            │
    ▼               ▼                 ▼                 ▼            │
┌─────────┐   ┌─────────┐       ┌─────────┐       ┌─────────┐       │
│  AHB    │   │  APB1   │       │  APB2   │       │  USB    │       │
│  总线   │   │  总线   │       │  总线   │       │  时钟   │       │
│ ≤72MHz  │   │ ≤36MHz  │       │ ≤72MHz  │       │  48MHz  │       │
└────┬────┘   └────┬────┘       └────┬────┘       └─────────┘       │
     │             │                 │                               │
     ▼             ▼                 ▼                               │
┌─────────┐   ┌─────────┐       ┌─────────┐                         │
│ GPIO    │   │ USART   │       │ ADC     │                         │
│ SRAM    │   │ TIM2-7  │       │ TIM1    │                         │
│ Flash   │   │ I2C     │       │ SPI1    │                         │
│ DMA     │   │ CAN     │       │ GPIO    │                         │
└─────────┘   └─────────┘       └─────────┘                         │

模板中的时钟配置

让我们看看模板项目中的时钟配置(system.c):

c
void SystemClock_Config(void) {
  RCC_OscInitTypeDef osc = {0};
  osc.OscillatorType = RCC_OSCILLATORTYPE_HSI;  // 使用 HSI 内部振荡器
  osc.HSIState = RCC_HSI_ON;                    // 开启 HSI
  osc.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;  // 默认校准值
  osc.PLL.PLLState = RCC_PLL_ON;                // 开启 PLL
  osc.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;  // HSI/2 作为 PLL 输入
  osc.PLL.PLLMUL = RCC_PLL_MUL16;               // PLL 倍频 ×16
  HAL_RCC_OscConfig(&osc);                      // 配置振荡器

时钟计算

HSI = 8 MHz
PLL 输入 = HSI / 2 = 4 MHz
PLL 输出 = 4 MHz × 16 = 64 MHz
系统时钟 SYSCLK = 64 MHz

继续看总线时钟配置:

c
  RCC_ClkInitTypeDef clk = {0};
  clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                  RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;   // 使用 PLL 作为系统时钟
  clk.AHBCLKDivider = RCC_SYSCLK_DIV1;          // HCLK = 64 MHz
  clk.APB1CLKDivider = RCC_HCLK_DIV2;           // APB1 = 32 MHz
  clk.APB2CLKDivider = RCC_HCLK_DIV1;           // APB2 = 64 MHz
  HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_2);   // 配置时钟
}

总线时钟总结

总线时钟源分频最终频率连接的外设
AHBSYSCLK164 MHzGPIO, DMA, Flash
APB1HCLK232 MHzUSART2/3, TIM2-7, I2C
APB2HCLK164 MHzUSART1, SPI1, ADC, TIM1

为什么 APB1 要降频?

APB1 总线最高只能运行在 36 MHz,所以必须分频。这是硬件限制,设计时需要注意。


GPIO 基础

什么是 GPIO?

GPIO(General Purpose Input/Output,通用输入/输出)是微控制器与外部世界交互的基本接口。每个 GPIO 引脚都可以配置为输入或输出模式。

GPIO 的基本功能

  1. 输出模式:控制 LED、继电器、蜂鸣器等
  2. 输入模式:读取按键、传感器信号等
  3. 复用功能:USART、SPI、I2C 等通信接口

GPIO 工作模式

STM32 的 GPIO 支持 8 种工作模式:

输入模式(4 种)

模式说明典型应用
浮空输入无上拉/下拉,高阻态外部信号读取
上拉输入内部上拉电阻,默认高电平按键检测(低电平有效)
下拉输入内部下拉电阻,默认低电平按键检测(高电平有效)
模拟输入模拟信号输入ADC 采样

输出模式(4 种)

模式说明典型应用
推挽输出可输出高/低电平,驱动能力强LED 控制
开漏输出只能拉低,需外部上拉I2C 通信
复用推挽外设控制输出USART TX
复用开漏外设控制开漏I2C SDA/SCL

推挽输出 vs 开漏输出

推挽输出(Push-Pull):
        VCC

    ┌────┴────┐
    │         │
   ┌┴┐       ┌┴┐
   │ │ P-MOS │ │ N-MOS
   └┬┘       └┬┘
    │         │
    └────┬────┘

       引脚 ────► 可输出高电平或低电平

开漏输出(Open-Drain):
        VCC

        ┌┴┐ 外部上拉电阻
        │ │
        └┬┘

    ┌────┴────┐
    │         │
   ┌┴┐       ┌─┐
   │ │ N-MOS │ │ (无 P-MOS)
   └┬┘       └─┘

    └────┬────┘

       引脚 ────► 只能拉低,释放后由上拉电阻拉高

模板中的 GPIO 配置

让我们分析模板项目中的 GPIO 配置(main.c):

c
// 1. 定义 GPIO 初始化结构体
GPIO_InitTypeDef def;

// 2. 配置 GPIO 模式
def.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出模式

// 3. 选择引脚
def.Pin = GPIO_PIN_13;           // PC13 引脚

// 4. 配置速度
def.Speed = GPIO_SPEED_FREQ_LOW; // 低速(省电)

// 5. 配置上拉/下拉
def.Pull = GPIO_PULLDOWN;        // 下拉电阻

// 6. 使能 GPIO 时钟
__HAL_RCC_GPIOC_CLK_ENABLE();

// 7. 初始化 GPIO
HAL_GPIO_Init(GPIOC, &def);

// 8. 设置初始状态
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);

GPIO 操作函数

HAL 库提供了以下 GPIO 操作函数:

c
// 写引脚状态
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);    // 输出高电平
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);  // 输出低电平

// 翻转引脚状态
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);  // 高→低 或 低→高

// 读取引脚状态
GPIO_PinState state = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13);
// 返回 GPIO_PIN_SET 或 GPIO_PIN_RESET

LED 闪烁示例

模板中的主循环实现了 LED 闪烁:

c
while (1) {
    HAL_Delay(1000);                    // 延时 1000ms = 1秒
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);  // 翻转 LED 状态
}

注意:Blue Pill 开发板上的 LED 连接在 PC13 引脚,低电平点亮(板载 LED 是共阳极接法)。


中断基础

什么是中断?

中断是一种机制,允许外部事件或内部事件"打断"正在执行的程序,转而去执行特定的处理函数(中断服务程序),处理完成后返回原程序继续执行。

中断的作用

  1. 实时响应:及时处理紧急事件
  2. 提高效率:避免轮询等待,CPU 可以做其他事情
  3. 降低功耗:CPU 可以休眠,等待中断唤醒

NVIC(嵌套向量中断控制器)

Cortex-M3 的 NVIC 管理所有中断:

  • 优先级:0-15 级,数值越小优先级越高
  • 嵌套:高优先级中断可以打断低优先级中断
  • 向量表:中断服务程序的地址表

常见中断类型

类型说明示例
外部中断GPIO 引脚触发按键检测
定时器中断定时器溢出周期性任务
通信中断USART/SPI/I2C数据收发
ADC 中断转换完成模拟量采集
系统中断SysTick系统滴答

SysTick 中断示例

模板中使用了 SysTick 中断:

c
// SysTick 中断服务程序
void SysTick_Handler(void) { 
    HAL_IncTick();  // 更新 HAL 库的系统滴答计数
}

这个中断每 1ms 触发一次(由 HAL_Init() 配置),用于:

  • HAL_Delay() 延时功能
  • HAL_GetTick() 获取系统运行时间

中断优先级配置

c
// 设置中断优先级分组
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

// 配置外部中断优先级
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);  // 抢占优先级0,子优先级0
HAL_NVIC_EnableIRQ(EXTI0_IRQn);           // 使能中断

HAL 库概念

什么是 HAL 库?

HAL(Hardware Abstraction Layer,硬件抽象层)库是 ST 官方提供的驱动库,它将底层寄存器操作封装成易用的 API 函数。

HAL 库的优势

特点说明
可移植性同一 API 可用于不同 STM32 系列
易用性无需了解寄存器细节
安全性内置参数检查
兼容性支持 RTOS 和裸机开发

HAL 库 vs 寄存器操作

寄存器方式(直接操作):

c
// 直接操作寄存器点亮 LED
GPIOC->BSRR = GPIO_PIN_13;        // 置位(输出高电平)
GPIOC->BRR = GPIO_PIN_13;         // 复位(输出低电平)

HAL 库方式(推荐):

c
// 使用 HAL 库函数
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

HAL 库模块结构

stm32f1xx_hal_conf.h 可以看到 HAL 库的模块化设计:

c
// HAL 模块启用/禁用配置
#define HAL_MODULE_ENABLED           // HAL 核心模块
#define HAL_GPIO_MODULE_ENABLED      // GPIO 模块
#define HAL_RCC_MODULE_ENABLED       // 时钟控制模块
#define HAL_UART_MODULE_ENABLED      // UART 模块
#define HAL_TIM_MODULE_ENABLED       // 定时器模块
#define HAL_ADC_MODULE_ENABLED       // ADC 模块
// ... 更多模块

模块化优势

  • 只编译需要的模块,减少代码体积
  • 未使用的模块可以注释掉,节省 Flash 空间

HAL 库常用函数模式

HAL 库遵循一致的命名和调用模式:

c
// 1. 初始化结构体
XXX_InitTypeDef config;
config.参数1 = 值1;
config.参数2 = 值2;

// 2. 使能时钟
__HAL_RCC_XXX_CLK_ENABLE();

// 3. 调用初始化函数
HAL_XXX_Init(&config);

// 4. 使用功能函数
HAL_XXX_Function(...);

HAL 库初始化流程

模板中的初始化顺序:

c
int main() {
    // 1. HAL 库初始化
    HAL_Init();
    
    // 2. 系统时钟配置
    SystemClock_Config();
    
    // 3. 外设初始化
    // ... GPIO, UART, etc.
    
    // 4. 主循环
    while (1) {
        // 应用程序代码
    }
}

HAL_Init() 做了什么?

c
HAL_Init() {
    // 1. 设置中断优先级分组
    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
    
    // 2. 配置 SysTick 为 1ms 中断
    SysTick_Config(SystemCoreClock / 1000);
    
    // 3. 初始化 HAL 库内部状态
    // ...
}

总结

核心概念回顾

概念要点
STM32F103C8T672MHz Cortex-M3, 64KB Flash, 20KB SRAM
Cortex-M332位 RISC, Thumb-2 指令集, NVIC 中断控制器
存储器映射Flash(程序), SRAM(数据), 外设寄存器
时钟系统HSI/HSE → PLL → SYSCLK → AHB/APB 总线
GPIO8 种模式,推挽/开漏输出,上拉/下拉输入
中断NVIC 管理,优先级嵌套,实时响应
HAL 库硬件抽象层,简化外设编程

学习路径建议

  1. 掌握基础:GPIO 输入/输出、延时、LED 控制
  2. 理解时钟:时钟源、PLL、总线分频
  3. 学习通信:USART 串口通信
  4. 深入定时器:定时中断、PWM 输出
  5. 探索 ADC:模拟信号采集
  6. 进阶主题:DMA、RTOS、低功耗

下一步学习

  • GPIO 深入教程(即将推出)
  • 时钟系统详解(即将推出)
  • 中断与 NVIC(即将推出)

附录:常用缩写对照

缩写英文全称中文含义
GPIOGeneral Purpose Input/Output通用输入/输出
HALHardware Abstraction Layer硬件抽象层
RCCReset and Clock Control复位和时钟控制
NVICNested Vectored Interrupt Controller嵌套向量中断控制器
PLLPhase Locked Loop锁相环
HSIHigh Speed Internal内部高速振荡器
HSEHigh Speed External外部高速振荡器
AHBAdvanced High-performance Bus高级高性能总线
APBAdvanced Peripheral Bus高级外设总线
Flash-闪存(程序存储器)
SRAMStatic Random Access Memory静态随机存取存储器

Built with VitePress