当前位置: 首页 > news >正文

STM32F103C8T6单片机内部执行原理及启动流程详解

引言:为什么深入理解STM32启动流程很重要?

STM32F103C8T6作为嵌入式开发中最常用的单片机之一,其内部执行原理启动流程是理解嵌入式系统底层运行机制的核心。无论是开发Bootloader、调试HardFault异常,还是优化系统启动速度,都需要对这两部分有深入掌握。本文将从硬件架构到软件执行,全方位解析STM32F103C8T6的工作原理,配合代码示例和实战技巧,帮助你彻底搞懂单片机从"上电"到"运行main函数"的全过程。

一、STM32F103C8T6内部执行原理

1.1 核心架构:Cortex-M3内核与哈佛结构

STM32F103C8T6基于ARM Cortex-M3内核,采用哈佛架构Harvard Architecture ),将指令存储和 数据存储分离为两条独立总线:

I-Code总线:专门用于取指(32位宽),可一次读取两条16位Thumb指令

D-Code总线:专门用于数据访问(32位宽),支持字节/半字/字操作

这种架构的优势在于指令读取和数据访问可并行执行,大幅提升运行效率。相比传统51单片机的冯·诺依 曼结构(指令和数据共享总线),哈佛结构在高主频下的性能优势尤为明显。

1.2 三级流水线: 指令执行的"工厂流水线"

Cortex-M3内核采用三级流水线设计,将指令执行分为三个阶段并行处理:

1. 取指(Fetch从Flash或指令缓存读取指令,由预取单元(Prefetch Unit)完成,支持指令预取缓冲

2. 解码(Decode解析指令操作码和操作数,生成控制信号

3. 执行(Execute由ALU(算术逻辑单元)、乘法器等执行运算,访问寄存器或存储器

流水线工作时序

• 时钟周期1:指令1取指

• 时钟周期2:指令1解码,指令2取指

• 时钟周期3:指令1执行,指令2解码,指令3取指

这种并行处理使Cortex-M372MHz主频下可实现1.25 DMIPS/MHz的性能(约90 DMIPS )。

1.3 存储器系统: Flash RAM与地址映射

STM32F103C8T6的存储器资源如下:

Flash:64KB(地址范围:0x08000000~0x0800FFFF),用于存储程序代码和常量

SRAM:20KB(地址范围:0x20000000~0x20004FFF),用于存储变量和堆栈

存储器映射规则

Cortex-M3支持4GB地址空间,STM32将其划分为多个区域:

• 0x00000000~0x1FFFFFFF:代码区(Flash/系统存储器/SRAM,通过启动模式映射)

• 0x20000000~0x3FFFFFFF:SRAM区

• 0x40000000~0x5FFFFFFF:外设寄存器区(APB1/APB2/AHB外设)

• 0xE0000000~0xE00FFFFF: 内核外设区(NVIC、SysTick等)

1.4 总线架构:AHBAPB总线矩阵

STM32采用多级总线架构,通过总线矩阵( Bus Matrix )协调各主设备( CPU DMA)对从设备 Flash SRAM、外设)的访问:

 AHB总线(Advanced High-performance Bus最高72MHz,连接高性能外设(Flash、SRAM、 DMA、LCD控制器等)

 APB1总线:最高36MHz,连接低速外设(USART2/3、I2C、SPI2等)

 APB2总线:最高72MHz,连接高速外设(GPIO、USART1、SPI1、ADC等)

1.5 时钟系统: 从晶振到外设的" 时间管理者"

STM32的时钟系统是最复杂也最核心的部分之一,支持5种时钟源:

HSI:内部高速RC振荡器(8MHz,精度±1%)

HSE:外部高速晶振(4~16MHz,通常接8MHz)

LSI:内部低速RC振荡器(40kHz,用于独立看门狗)

LSE:外部低速晶振(32.768kHz,用于RTC)

PLL:锁相环倍频器(输入可接HSI/2、HSE或HSE/2,倍频2~16倍,最高输出72MHz)

典型时钟树配置HSE=8MHz ):

HSE → PLL输入(不分频) PLL倍频9  PLL输出72MHz → 作为SYSCLK(系统时钟)

• AHB分频1 → HCLK=72MHz(CPU主频)

• APB1分频2 → PCLK1=36MHz

• APB2分频1 → PCLK2=72MHz

二、 STM32F103C8T6启动流程详解

2.1 启动模式: BOOT引脚如何决定程序从哪里启动?

STM32的启动模式由BOOT0BOOT1引脚的电平决定,共三种模式:

BOOT1

BOOT0

启动模式

映射地址

用途

X

0

主Flash启动

0x08000000

正常运行用户程 序(默认模式)

0

1

系统存储器启动

0x1FFFF000

通过串口下载程 序(ISP模式)

1

1

SRAM启动

0x20000000

调试临时程序

(掉电不保存)

关键细节

• 复位时BOOT引脚电平被锁存,修改后需复位生效

• 主Flash启动时,0x00000000地址被映射到0x08000000(Flash首地址)

• 系统存储器启动时,执行ST出厂预置的Bootloader(支持USART1下载)

2.2 复位序列: 上电后CPU首先做什么?

STM32上电或复位( NRST引脚拉低)后,硬件自动执行以下步骤:

1. 初始化状态寄存器:清除中断标志,设置默认优先级

2. 读取向量表前两项

从0x00000000读取MSP初始值(栈顶指针)

从0x00000004读取复位向量(Reset_Handler函数地址)

3. 跳转执行Reset_Handler:CPU将PC指针设置为复位向量地址,开始执行启动代码

2.3 启动文件解析: startup_stm32f10x_md.s的秘密

启动文件(汇编编写)是连接硬件复位和C语言环境的桥梁,以  startup_stm32f10x_md .s 

(中等容量 设备)为例,主要完成以下工作:

2.3.1 堆栈定义

Stack_Size      EQU     0x00000400  ; 栈大小1KBAREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size  ; 分配栈空间
__initial_sp    ; 栈顶地址(栈从高地址向低地址生长)Heap_Size       EQU     0x00000200   ; 堆大小512BAREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base     ; 堆起始地址
Heap_Mem        SPACE   Heap_Size   ; 分配堆空间
__heap_limit    ; 堆结束地址

 

2.3.2 中断向量表

AREA    RESET, DATA, READONLYEXPORT  __VectorsEXPORT  __Vectors_EndEXPORT  __Vectors_Size__Vectors       DCD     __initial_sp               ; 0: 栈顶指针DCD     Reset_Handler              ; 1: 复位中断DCD     NMI_Handler                ; 2: NMI中断DCD     HardFault_Handler          ; 3: 硬件错误中断; ... 其他中断向量(共68个)
__Vectors_End__Vectors_Size  EQU     __Vectors_End - __Vectors  ; 向量表大小

2.3.3 复位处理函数( Reset_Handler

复位后执行的第一个函数,负责初始化硬件和C环境:

Reset_Handler   PROCEXPORT  Reset_Handler             [WEAK]IMPORT  SystemInit                ; 引入系统初始化函数IMPORT  __main                    ; 引入C库初始化函数; 1. 设置栈指针(已由硬件读取__initial_sp完成); 2. 初始化数据段(.data)ldr     r0, =_sdata               ; 数据段目标地址(RAM)ldr     r1, =_edata               ; 数据段结束地址ldr     r2, =_sidata              ; 数据段源地址(Flash)movs    r3, #0
LoopCopyDataInit:cmp     r0, r1                    ; 复制未完成?ittt    ltldrlt   r4, [r2], #4              ; 从Flash读取数据strlt   r4, [r0], #4              ; 写入RAMblt     LoopCopyDataInit          ; 循环复制; 3. 初始化BSS段(清零)ldr     r2, =_sbss                ; BSS段起始地址ldr     r4, =_ebss                ; BSS段结束地址movs    r3, #0
LoopFillZerobss:cmp     r2, r4                    ; 清零未完成?itt     ltstrlt   r3, [r2], #4              ; 写入0blt     LoopFillZerobss           ; 循环清零; 4. 调用SystemInit配置系统时钟bl      SystemInit; 5. 调用__main初始化C库,最终跳转到main函数bl      __mainENDP

关键步骤解析

数据段(.data)复制:将Flash中存储的已初始化全局变量复制到RAM

BSS段清零:将未初始化全局变量在RAM中清零

SystemInit:配置系统时钟(默认使用HSI,可修改为HSE+PLL=72MHz)

 __main:C库函数,初始化堆和栈,调用全局构造函数(C++),最终跳转到用户

2.4 SystemInit函数: 时钟配置的核心

SystemInit函数(位于system_stm32f10x.c)负责系统时钟初始化,默认配置如下:

void SystemInit(void) {/* 复位RCC寄存器到默认状态 */RCC->CR |= 0x00000001U;                  // 使能HSIRCC->CFGR &= 0xF8FF0000U;                // 复位时钟配置寄存器RCC->CR &= 0xFEF6FFFFU;                  // 关闭HSE、CSS、PLLRCC->CR &= 0xFFFBFFFFU;                  // 关闭HSE旁路RCC->CFGR &= 0xFF80FFFFU;                // 复位PLL配置/* 配置向量表偏移(默认在Flash) */
#ifdef VECT_TAB_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; // 向量表在SRAM
#elseSCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;// 向量表在Flash(0x08000000)
#endif
}

默认时钟:HSI(8MHz)作为系统时钟,未启用PLL,如需72MHz需修改SetSysClock函数:

static void SetSysClockTo72(void) {RCC->CR |= RCC_CR_HSEON;                 // 使能HSEwhile((RCC->CR & RCC_CR_HSERDY) == 0);   // 等待HSE就绪RCC->CFGR |= RCC_CFGR_PLLSRC_HSE;        // PLL输入=HSERCC->CFGR |= RCC_CFGR_PLLMULL9;          // PLL倍频9倍(8MHz*9=72MHz)RCC->CR |= RCC_CR_PLLON;                 // 使能PLLwhile((RCC->CR & RCC_CR_PLLRDY) == 0);   // 等待PLL就绪FLASH->ACR |= FLASH_ACR_PRFTBE;          // 使能Flash预取FLASH->ACR &= ~FLASH_ACR_LATENCY;FLASH->ACR |= FLASH_ACR_LATENCY_2;       // Flash等待周期=2(72MHz时)RCC->CFGR |= RCC_CFGR_SW_PLL;            // 系统时钟=PLL输出while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 等待切换完成
}

三、代码示例与实战解析

3.1 启动流程验证: 通过LED观察启动阶段

硬件连接 LEDPC13(低电平点亮)

代码实现

// main.c
#include "stm32f10x.h"// 延时函数
void Delay(__IO uint32_t nCount) {while(nCount--) {}
}int main(void) {// 使能GPIOC时钟(APB2外设)RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;// 配置PC13为推挽输出GPIOC->CRH &= ~(GPIO_CRH_MODE13 | GPIO_CRH_CNF13);GPIOC->CRH |= GPIO_CRH_MODE13_0;  // 输出模式,最大速度10MHzwhile (1) {GPIOC->ODR ^= GPIO_ODR_ODR13;   // 翻转PC13电平Delay(0xFFFFF);                 // 延时}
}

启动阶段分析

1. 上电后LED不亮(系统初始化阶段)

2. SystemInit执行完毕后,LED开始闪烁(main函数执行)

3. 若LED不闪烁,可能是时钟配置错误或启动文件选择不当(需使用  startup_stm32f10x_md .s

3.2 中断向量表重映射: RAM启动时的配置

当使用SRAM启动或IAP升级时,需将向量表重映射到RAM

// 在SystemInit或main中配置
void VectorTableRemap(void) {// 将Flash中的向量表复制到RAM(0x20000000)uint32_t *pSrc = (uint32_t*)0x08000000;    // Flash向量表起始地址uint32_t *pDest = (uint32_t*)0x20000000;   // RAM向量表起始地址uint32_t i;for(i = 0; i < 68; i++) {                  // 复制68个中断向量pDest[i] = pSrc[i];}// 配置VTOR寄存器(向量表偏移)SCB->VTOR = 0x20000000;                    // 向量表基地址=RAM起始地址
}

3.3 启动时间优化: 72ms15ms的实战技巧

默认启动时间:约72ms(含Flash擦写、C库初始化等)

优化方法

1. 关闭不必要的外设时钟

在SystemInit中仅使能必要外设时钟(如GPIO、USART)

2. 优化Flash访问

使能预取缓冲区(FLASH_ACR_PRFTBE=1),设置正确等待周期(72MHz时=2)

3. 跳过C库初始化

若不使用全局构造函数,可在启动文件中直接跳转到main:

; 在Reset_Handler中替换bl __main为bl main
bl SystemInit
bl main        ; 直接调用main,跳过__libc_init_array

4. 使用HSI快速启动

若对时钟精度要求不高,使用HSI(8MHz)可避免HSE晶振启动延时

四、常见问题与调试技巧

4.1 启动失败排查清单

现象

可能原因

解决方案

程序无反应

BOOT0引脚接高电平(进入 ISP模式)

将BOOT0接地,复位芯片

HardFault异常

栈溢出或非法内存访问

增大栈大小(Stack_Size), 查指针操作

现象

可能原因

解决方案

时钟配置后死机

PLL倍频过高(超过72MHz)

重新计算PLL参数,确保输出 ≤72MHz

全局变量初始化失败

.data或.bss段地址配置错误

检查链接脚本( .ld)中的RAM 地址和大小

4.2 使用ST-Link调试启动过程

1. 查看寄存器状态

复位后暂停,查看  SP 是否等于 ___initial_sp,  PC 是否指向  Reset_Handler 

2. 设置断点

在  Reset_Handler SystemInitmain 函数设置断点,观察执行流程

3. 查看内存

检查  0x20000000(RAM起始地址)是否已复制.data段数据,.bss段是否清零

总结与扩展

本文详细解析了STM32F103C8T6的内部执行原理(哈佛架构、三级流水线、存储器映射、时钟系统)和启动流程(复位序列、启动模式、启动文件、SystemInit),并通过代码示例展示了实战应用。掌握这些知识后,你可以:

  1. 开发自定义Bootloader,实现IAP固件升级
  2. 优化系统启动速度,满足实时性要求
  3. 快速定位HardFault等底层异常

推荐扩展阅读:

  1. 《STM32F103参考手册(RM0008)》:深入了解寄存器配置
  2. 《Cortex-M3权威指南》:理解内核架构和异常处理
  3. STM32CubeMX:图形化配置时钟和外设,自动生成初始化代码

如果觉得本文对你有帮助,欢迎点赞+关注,后续将带来更多STM32底层开发实战内容!如有疑问,可在评论区留言讨论~ 

附录:关键地址速查表

名称

地址范围

用途

Flash

0x08000000~0x0800FFFF

程序代码存储

SRAM

0x20000000~0x20004FFF

变量和堆栈

向量表(默认)

0x08000000~0x08000107

中断服务函数地址数组

RCC寄存器

0x40021000~0x400213FF

时钟控制寄存器

GPIO寄存器

           0x40010800~0x40010BFF (GPIOC)

GPIO控制寄存器

http://www.dtcms.com/a/272170.html

相关文章:

  • vue3实现pdf文件预览 - vue-pdf-embed
  • 力扣热门算法题 127.单词接龙,128.最长连续序列,130.被围绕的区域
  • MySQL数据库基础教程:从安装到数据操作
  • 快速合并多个CAD图形为单一PDF文档的方法
  • 常见 Docker 错误及解决方法
  • (vue)前端区分接口返回两种格式,一种Blob二进制字节流,一种常规JSON,且将blob响应转为json
  • 基于Catboost算法的茶叶数据分析及价格预测系统的设计与实现
  • 多元函数的切平面与线性近似:几何直观与计算方法
  • 高数附录(1)—常用平面图形
  • 《O-PAS™标准的安全方法》白皮书:为工业自动化系统筑起安全防线
  • msf复现永恒之蓝
  • 每日一SQL 【各赛事的用户注册率】
  • Datawhale AI 夏令营:基于带货视频评论的用户洞察挑战赛 Notebook(下篇)
  • 流媒体服务
  • SIMATIC S7-1200的以太网通信能力:协议与资源详细解析
  • x86架构CPU市场格局
  • WIFI协议全解析05:WiFi的安全机制:IoT设备如何实现安全连接?
  • PHP安全编程实践系列(三):安全会话管理与防护策略
  • 【运维】串口、网络一些基本信息
  • 【超详细】CentOS系统Docker安装与配置一键脚本(附镜像加速配置)
  • Pinia 笔记:Vue3 状态管理库
  • 双模秒切,体验跃迁!飞利浦EVNIA双模游戏显示器27M2N6801M王者降临!
  • UnrealEngine5游戏引擎实践(C++)
  • 如何将多个.sql文件合并成一个:Windows和Linux/Mac详细指南
  • 字节 Seed 团队联合清华大学智能产业研究院开源 MemAgent: 基于多轮对话强化学习记忆代理的长文本大语言模型重构
  • 为了安全应该使用非root用户启动nginx
  • 相机:以鼠标点为中心缩放(使用OpenGL+QT开发三维CAD)
  • [Xmos] Xmos架构
  • 从面向对象编程语言PHP转到Go时的一些疑惑?
  • 同时部署两个不同版本的tomcat要如何配置环境变量