一、什么是外设可以把STM32芯片想象成一台微型的电脑内核 就像是电脑的CPU负责执行代码和进行运算。外设 就像电脑的屏幕、键盘、风扇等负责各种功能模块。学习STM32开发本质上就是学习如何配置和使用这些丰富的外设来实现你想要的功能。二、STM32常见的外设分类与举例STM32的外设非常丰富可以大致分为以下几类外设类别子类型功能描述通信接口UART异步串口通信基础通信方式用于连接GPS模块、蓝牙模块、电脑串口调试等场景。I2C双向两线制串行总线适用于连接多个低速设备如温湿度传感器、加速度计、EEPROM存储器等。SPI高速全双工串行总线支持高速数据传输常用于连接Flash存储器、SD卡、显示屏、无线模块等。USB通用串行总线支持USB设备被电脑识别、USB主机连接U盘或USB OTG角色切换模式。CAN控制器局域网总线具备强抗干扰能力和可靠性主要应用于汽车和工业领域。以太网提供有线网络连接功能满足需要网络通信的嵌入式设备需求。定时器基本定时器用于产生精确延时和定时中断。通用定时器功能丰富支持基本定时、PWM输出控制舵机、直流电机速度、LED亮度调节、输入捕获测量脉冲宽度、频率如编码器信号、输出比较产生特定频率和占空比的波形。高级定时器功能最强大适用于电机控制、数字电源等复杂领域可生成带死区控制的互补PWM信号。模拟外设ADC模数转换器将外部模拟电压信号如传感器输出转换为数字值供内核处理例如读取电位器位置、光照强度、温度传感器电压。DAC数模转换器将数字值转换为模拟电压输出可用于生成特定波形如正弦波、三角波。比较器快速比较两个模拟电压大小输出数字信号高/低常用于过流保护、阈值检测等场景。数据管理DMA直接存储器访问可在不占用CPU的情况下在外设和内存之间直接搬运数据例如ADC转换完成后自动将数据传输至RAM数组提升系统效率。输入/输出GPIO通用输入输出端口可配置为输入模式读取按键状态、输出模式控制LED亮灭或复用为其他外设功能如UART的TX引脚。安全与保护看门狗独立定时器用于监测程序运行状态。程序正常时需定期“喂狗”若程序崩溃无法喂狗看门狗会自动复位芯片使系统恢复。三、时钟树和系统结构时钟树可以理解为一个“时钟信号的配送网络”。它有几个关键的“水源”时钟源通过一系列的“开关和阀门”多路选择器、分频器、倍频器将不同频率和特性的时钟信号“配送”到各个“用水单位”内核、外设等使这些“用水单位”内核、外设等能正常工作。控制这些“开关和阀门”的模块称之为时钟控制RCC。在《STM32F10xxx参考手册中文版.pdf》第 56 页有对时钟树的详细介绍在我们使用 STM32CubeMX 新建工程时其实已经对这个时钟树进行配置了。下面为STM32CubeMX配置时钟树后生成的代码入口函数了解就行四、STM32F10xxx系统结构框图了解完时钟树后我们就可以大概了解芯片的系统结构了。在《STM32F10xxx数据手册》第11页有对STM32F10xxx系统结构框图有描述。五、启动文件作用启动文件是用汇编语言编写的是芯片上电后执行的第一段代码奠定了整个程序运行的基石。注意不同型号的STM32芯片启动文件不全一样。六、启动文件的核心作用这个文件主要完成以下关键任务初始化堆栈指针构建中断向量表执行系统初始化跳转到主程序提供中断服务程序的默认入口七、详细代码分析1. 堆栈配置Stack_Size EQU 0x400 ; 定义栈大小为 1KB AREA STACK, NOINIT, READWRITE, ALIGN3 Stack_Mem SPACE Stack_Size ; 栈顶地址初始化MSP的值 __initial_spEQU 类似于C语言的 #define定义常量。AREA 声明一个段。STACK是段名NOINIT表示不初始化为0READWRITE可读可写ALIGN3表示按8字节对齐。SPACE 分配一段指定大小的内存空间。__initial_sp 链接器会使用这个符号的值作为主堆栈指针的初始值。同理堆区配置Heap_Size EQU 0x200 ; 定义堆大小为 512字节 AREA HEAP, NOINIT, READWRITE, ALIGN3 __heap_base Heap_Mem SPACE Heap_Size __heap_limit堆用于动态内存分配如malloc、free。2. 中断向量表这是启动文件最核心的部分。AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD __initial_sp ; 地址0: 初始栈顶地址 DCD Reset_Handler ; 地址4: 复位中断服务程序 DCD NMI_Handler ; 地址8: NMI Handler DCD HardFault_Handler ; 地址12: Hard Fault Handler ... ; 更多系统异常 DCD WWDG_IRQHandler ; 外部中断0 (Window Watchdog) DCD PVD_IRQHandler ; 外部中断1 (PVD) ... ; 所有外部中断AREA RESET, DATA, READONLY 定义一个名为RESET的只读数据段存放向量表。EXPORT __Vectors 导出__Vectors符号让链接器知道向量表的起始位置。DCD 分配一个32位的字4字节内存并初始化。向量表结构第一个条目必须是初始栈顶指针。Cortex-M内核上电后首先从地址0x00000000取出这个值赋给MSP。第二个条目是复位向量即Reset_Handler的地址。上电后内核从地址0x00000004取出这个值并跳转到该地址执行。后续条目依次是各种异常和中断的入口地址。3. 复位中断服务程序这是上电后执行的第一段代码。Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, SystemInit BLX R0 LDR R0, __main BX R0 ENDPPROC/ENDP 定义一个过程函数。[WEAK] 弱定义。如果别处没有定义同名的强符号则使用此定义。这里允许用户在C代码中自己实现一个Reset_Handler。IMPORT 声明外部符号类似于C语言的extern。执行流程LDR R0, SystemInit 将SystemInit函数的地址加载到寄存器R0。BLX R0跳转并执行SystemInit()。这个函数通常由ST提供在system_stm32f1xx.c中它负责配置时钟、初始化等关键系统配置。不过用STM32CubeMX生成工程时时钟配置已经在SystemClock_Config()函数中实现,SystemInit()函数不做配置了。LDR R0, __main 将__main的地址加载到R0。注意这个__main不是你的C代码里的main函数它是C库中的一个函数由编译器提供。BX R0 跳转到__main。__main会完成以下工作复制初始化数据从Flash到SRAM例如初始化全局变量。将未初始化的内存区域清零例如.bss段。调用C库初始化如果使用。最后调用用户的main()函数。4. 默认中断服务程序NMI_Handler PROC EXPORT NMI_Handler [WEAK] B . ; 无限循环 ENDP HardFault_Handler\ PROC EXPORT HardFault_Handler [WEAK] B . ENDP ...对于所有异常和中断这里都提供了一个默认实现一个什么都不做的无限循环(B .)。它们都被标记为[WEAK]。这意味着如果你在C代码中自己实现了一个同名的函数例如 void TIM2_IRQHandler(void) { ... }链接器会优先使用你的函数从而覆盖这个默认的空实现。这就是我们自定义中断服务函数的方式函数名不能改但是函数里面执行的代码我们可自定义。5. 堆栈初始化用于非微库IF :DEF:__MICROLIB ... ; 如果使用微库直接使用上面定义的符号 ELSE IMPORT __use_two_region_memory EXPORT __user_initial_stackheap __user_initial_stackheap LDR R0, Heap_Mem ; 返回堆起始地址 LDR R1, (Stack_Mem Stack_Size) ; 返回栈结束地址 LDR R2, (Heap_Mem Heap_Size) ; 返回堆结束地址 LDR R3, Stack_Mem ; 返回栈起始地址 BX LR ENDIF这部分是为了支持标准的C库而非微库Microlib进行堆栈初始化。__user_initial_stackheap 函数会被C库调用它需要返回堆和栈的边界地址。