STM32 嵌入式学习笔记(一):点个灯泡的疑问汇总

STM32 嵌入式学习笔记(一):点个灯泡的疑问汇总

这是我在看江协科技的stm32入门教程的学习过程记录,是学习过程中我产生的问题和deepseek给我的回答再让deepseek总结后的内容。大致完整可能缺少一些细节。主要用作个人回头复习,若能真够帮到可能的读者就更好了。这里面没有具体步骤。

一、存储体系:RAM、ROM 与寄存器

1.1 RAM 是什么?

全称:Random Access Memory,随机存取存储器。

核心特征

  • 掉电丢失(易失性)
  • 读写速度极快(纳秒级)
  • 可随机访问任意地址

在 STM32 里的身份:内存。你写 int a = 5;,这个 a 在通电运行时的物理存放位置就是 RAM。

编译输出的对应关系

编译字段 存放内容 物理位置
Code 函数的机器码 Flash
RO-data const 常量 Flash
RW-data 已初始化全局变量(如 int b=5; 先存 Flash 备份,运行时复制到 RAM
ZI-data 未初始化全局变量(如 int c; RAM(上电清零,不占 Flash)

1.2 ROM 是什么?

全称:Read-Only Memory,只读存储器。

核心特征

  • 掉电保留(非易失性)
  • 在常规运行中不易改写或不能改写

在 STM32 里的身份:Flash(闪存)。

关键区分:传统 ROM 是“出厂固化只读”。但在 STM32 里,ROM 就是 Flash——可以通过烧录器擦除重写。之所以还叫“ROM”,是因为程序固化后,CPU 主要去它来执行指令。


1.3 Flash 为什么叫“闪存”?与硬盘有什么区别?

为什么叫“闪”:发明者发现它擦除时能瞬间整块清空,像闪光灯一样“唰”一下。命名来源是东芝同事觉得这个动作很像相机的 Flash。

与硬盘的区别

对比维度 STM32 里的 Flash 电脑机械硬盘 电脑固态硬盘
物理形态 芯片内部硅片 磁头+旋转磁盘 独立 Flash 颗粒+主控
能否就地执行代码 能(XIP) 不能,必须读到 RAM 不能,必须读到 RAM
容量 几十KB–2MB 1TB–20TB 128GB–4TB
读写单位 字节/字 扇区(512字节) 页/块

核心区别:STM32 的 Flash 支持 XIP(eXecute In Place,就地执行)。CPU 的程序计数器直接指向 Flash 的物理地址(0x08000000)取指令执行,不需要先把程序搬到 RAM。


1.4 为什么已经有了能写的 Flash,还要区分 RAM 和 ROM?

因为物理定律的限制:

对比维度 SRAM(单片机里的 RAM) Flash(单片机里的 ROM)
构成原理 6 个晶体管组成锁存器 1 个带浮栅的特殊晶体管
写入机制 纯电路翻转,瞬间完成 隧道效应,需要高压轰击绝缘层
写入耗时 纳秒级 毫秒级(慢几百万倍)
写入寿命 无限次 1万–10万次
成本/容量比 极高(1KB 占很大面积) 极低(1MB 很便宜)

结论:如果把 Flash 当 RAM 用,每次 i++ 都要等毫秒级擦除,跑几万次芯片就物理磨损报废。所以必须分工:程序睡在 Flash,运行时变量在 RAM 里快速翻腾。活干完了,关键数据再慢慢写回 Flash。

这个设计的专业术语:缓存-持久化模型,嵌入式里叫 Shadow RAM(影子内存)。


1.5 寄存器到底是什么?

寄存器不是 Flash,也不是 RAM。寄存器是一排被映射到地址空间的微型闸刀开关。

它的物理本质是 D 触发器——用两个非门首尾相接形成正反馈环,一个电平一旦进去就会永远在环里转圈,从而被“锁存”住。

对比项 Flash / RAM(存储器) 寄存器(D 触发器)
角色比喻 仓库货架 控制台闸刀开关面板
核心目的 存数据 控制电路
写入行为 “把货放到第3排第5列” “把第3号闸刀推上去”

寄存器如何控制硬件:不是“触发”,而是持续施加影响。寄存器对应位为 1,电平立刻持续输出,硬件电路根据这个电平决定工作状态。你改成 0,状态立刻改变。


1.6 电脑里有寄存器吗?

有。

第一类:CPU 内核寄存器(EAX、EBX、R0、R1 等)。仅在 CPU 内部,用于运算,对外不可见。

第二类:外设配置寄存器。和 STM32 一模一样,控制 USB、SATA、网卡等外设。只是这些操作被封装在操作系统内核驱动里,应用程序接触不到。

为什么电脑的驱动看起来像软件:因为 Windows/Linux 有内核分层。应用程序要操作硬件必须通过系统调用,由内核验证权限后让驱动去写寄存器。这层层封装让你感觉像是在调用软件服务,而不是在扳闸刀。STM32 裸机上没有这层管家,你就是上帝,直接写物理地址。


1.7 从软件地址到硬件电平的完整链路

问题:你写 *((volatile uint32_t *)0x4001080C) = 1;,这个“地址”究竟靠什么找到物理寄存器?

答案:地址译码器。

完整流程

  1. 编译:你的 C 代码变成一条 STR 指令,地址和数据分别加载到寄存器。
  2. CPU 执行:把地址放到地址总线,把数据放到数据总线,发出“写”控制信号。
  3. 地址译码器:一堆组合逻辑电路监视地址总线。当地址落在 0x400108000x40010FFF 范围时,输出片选信号选中 GPIOA 外设;再根据偏移选中具体寄存器(如 ODR)。
  4. 数据写入:数据总线上的 32 位值直接拍进对应寄存器的 32 个 D 触发器。
  5. 电平改变:触发器的输出端物理电平改变,通过输出驱动器送到引脚焊盘。

整个流程从软件指令到物理电平,只需一个总线时钟周期(约几十纳秒)。


1.8 RAM、Flash、寄存器的地址形式

形式完全一样——都是 32 位二进制数。 Cortex-M3 把 4GB 寻址空间画成一张地图:

地址范围 区域 用途
0x000000000x1FFFFFFF Code 区 Flash 在这里
0x200000000x3FFFFFFF SRAM 区 RAM 在这里
0x400000000x5FFFFFFF 外设区 寄存器在这里
0xE00000000xFFFFFFFF 系统区 NVIC、SysTick 等

传递渠道:CPU 访问这三者用同一套指令(LDR/STR),走同一套总线(地址总线+数据总线)。区别只在于地址落在哪个区间,地址译码器根据高位自动选通对应设备。


二、时钟系统:芯片的心跳

2.1 为什么点灯要先“开时钟”?

STM32 把所有外设的时钟都默认关着——为了省电。每个外设门口有一个总闸(RCC->APB2ENR 的对应位)。你写 RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; 就是推上 GPIOC 的时钟总闸。闸一开,系统时钟涌入 GPIOC 内部,所有寄存器被激活。闸一关,外设内部时钟被封锁在门外,电路纹丝不动,零功耗。这称为时钟门控

2.2 时钟树:时钟是怎么分到各外设的

外部晶振 (8MHz)
      │
   HSE 输入
      │
  ┌─ PLL ──┐        ← 树干分叉,倍频到 72MHz
  │         │
  ▼         ▼
SYSCLK   其他时钟源
  │
  ├─ AHB 总线 ──┐    ← 分到各大分支
  │             │
HCLK(CPU核)  HCLK(DMA等)
  │
  ▼
APB1 / APB2          ← 再分到外设总线
  │        │
TIM2,    GPIOA,
USART2   USART1
  │
  ▼
RCC->APB2ENR 的每一位 ← 每根树枝末端都有独立总闸

这棵树是芯片出厂时在硅片上铺设好的。你能做的就是控制每个末端的开关。

2.3 系统时钟本身的功耗能优化吗?

能,有三个维度:

优化维度 具体手段 省电原理
降频 切换系统时钟源,降低 HCLK 频率越低,动态功耗线性下降
关核 执行 WFI/WFE 让 CPU 内核休眠 CPU 自身时钟被门控切断,外设时钟仍在
换源 从 PLL 切换到 HSI 或 HSE 直接分频 PLL 是模拟电路,功耗远高于晶振直出

三种低功耗模式

模式 关了什么 唤醒方式 功耗
睡眠 只关 CPU 内核时钟 任意中断或事件 CPU 部分省电
停机 系统时钟树大面积停摆 外部中断或 RTC 微安级
待机 几乎全关,只保留备份域 WKUP 引脚或复位 最低,唤醒等同复位

三、GPIO 引脚:从丝印到寄存器

3.1 从 PC13 找到该操作的寄存器和位

命名规则P = Port(端口),C = 端口编号,13 = 引脚号。

翻译步骤

  1. 外设PC13GPIOC。基地址 0x40011000
  2. 时钟使能RCC->APB2ENR 里的 IOPCEN 位。时钟控制的最小单位是整个端口,不能单开一个引脚。
  3. 配置寄存器:引脚 13 在 8–15 范围,归 CRH 管,占第 23–20 位。
  4. 数据寄存器ODR 的第 13 位直接对应该引脚的高低电平。

为什么配置寄存器只分 CRL 和 CRH:一个端口 16 个引脚,每个引脚需要 4 个配置位。一个 32 位寄存器只能装 8 个引脚(32÷4=8)。所以 CRL 管 0–7,CRH 管 8–15。

4 位配置位拆解

  • 低 2 位:MODE(输入/输出,输出时决定速度)
  • 高 2 位:CNF(具体电路模式)

为什么 PC 口只引出 PC13、PC14、PC15:STM32F103C8T6 只有 48 个引脚,不可能把四组 64 个引脚全引出。C 口只引了这三个,因为它们属于备份域,由 VBAT 独立供电。


3.2 为什么要区分这么多 GPIO 模式?

输入模式(4 种)——只管读电平,引脚呈高阻态:

模式 电路状态 应用场景
模拟输入 切断数字电路,直连 ADC 采集电压、麦克风信号
浮空输入 引脚悬空,由外部完全决定 读取数字传感器
上拉输入 内部接约 40kΩ 电阻到 3.3V,默认读 1 按键(省外部电阻)
下拉输入 内部接约 40kΩ 电阻到 GND,默认读 0 需要默认低电平的开关

输出模式(4 种)——驱动外部负载:

模式 电路特征 应用场景
推挽输出 上管推高、下管拉低。驱动能力强 点亮 LED、给外设供电
开漏输出 只有下管能拉低。要输出高必须外接上拉电阻 I2C 总线、电平转换
复用推挽输出 推挽电路,由内部外设(定时器、串口)自动控制 USART_TX、PWM 输出
复用开漏输出 开漏电路,由内部外设自动控制 I2C 的 SDA/SCL

为什么需要上拉/下拉:浮空引脚什么都不接时,电平是飘的,受电磁干扰乱跳。上下拉电阻给引脚一个确定的默认状态。

为什么 I2C 必须用开漏:多设备共用一根数据线。如果都用推挽,一个设备拉高一个拉低,直接短路烧坏。开漏电路线只拉低不推高,高电平靠公用的上拉电阻提供,天然防冲突。

推挽和开漏的点灯接法

  • LED 另一端接地 → 必须推挽输出(需要主动输出电流)
  • LED 另一端接 3.3V → 推挽和开漏都能亮(只要能把低电平拉下来灌电流即可)

3.3 外设复用引脚的两个时钟使能

复用功能引脚需要两个时钟使能:

  1. GPIO 端口的时钟:让引脚电路通电
  2. 外设本身的时钟:让外设内部逻辑跑起来

配置要点

  • 输出复用(TIM1 的 PWM、USART_TX):CNF 配成复用推挽输出
  • 输入复用(USART_RX、SPI_MISO):配成浮空输入或上拉输入
  • 控制权交给外设后,ODR 不再由你的代码直接控制

四、通信总线:USART、I2C、SPI

4.1 总览

这三者都是串行通信(数据一位一位走单线),但机制完全不同:

对比维度 USART I2C SPI
线数 2 根(TX/RX) 2 根(SDA/SCL) 4 根(SCK/MOSI/MISO/CS)
时钟线 (靠波特率硬猜) (SCL) (SCK)
设备选择 点对点(天然) 软件寻址(发地址) 硬件片选(拉低 CS)
速度 慢(<2Mbps) 较慢(100k–3.4Mbps) 快(可达 50Mbps)
全双工 是(同时收发) 否(半双工)
比喻 打电话 老师点名 流水线拉电闸

4.2 为什么 UART 不能像 I2C 那样多条并联?

电气层面:UART 的 TX 是推挽输出,两个 TX 并在一起一个拉高一个拉低会短路。而 I2C 的 SDA 是开漏输出,所有设备只拉低不推高,高电平靠公用上拉电阻提供,天然防冲突。

协议层面:UART 没有地址机制。多个从机同时回数据,主机不知道谁在说。

STM32 推荐做法:挂多个 UART 设备时,用硬件串口数量解决问题(芯片有 3–5 个 USART),而不是并联。

唯一例外:加 RS485 收发芯片后可以实现一主多从。

4.3 I2C 怎么挂多个设备?

同一总线上所有设备的 SDA 接在一起,SCL 接在一起。主机发起通信时先发地址。所有从机都收到,但只有地址匹配的那个才响应,其他保持沉默。

物理上怎么连:用面包板的电源轨(横向连通的长条孔),单片机的 I2C 引脚接过去,再从电源轨分出杜邦线接到各从设备。PCB 上就是铜线走线分叉。

4.4 SPI 怎么挂多个设备?

SCK、MOSI、MISO 三根线所有设备共用。每个从机单独一根 NSS(片选线)连到主机的不同 GPIO。主机拉低谁的 NSS 就和谁通信。未被选中的从机其 MISO 呈高阻态,不干扰总线。

4.5 引脚缩写全称

I2C

  • SDA:Serial Data Line
  • SCL:Serial Clock Line

SPI

  • SCK:Serial Clock
  • MOSI:Master Output, Slave Input
  • MISO:Master Input, Slave Output
  • NSS(CS):Negative Slave Select,低电平有效的从机选择

4.6 同步与异步

  • 异步通信:没有独立的时钟线。双方约定好波特率,接收方靠起始位的下降沿启动内部定时器,按固定节奏在比特中心点采样。
  • 同步通信:有一根专门的时钟线(SCL/SCK)传递节拍信号。

为什么 USART 有 S(同步)却总当 UART 用:同步模式需要额外占用 CLK 引脚,且电脑、GPS、蓝牙等外设本身不带时钟线。所以绝大多数场景把 USART 配置为异步模式,只用 TX/RX。


五、中断:CPU 的“紧急响应机制”

5.1 中断是什么?

中断就是让 CPU 能响应外部事件的硬件机制。

没有中断时,CPU 必须不停轮询(反复问“按键按了吗?”),效率极低。有了中断,CPU 可以忙自己的事甚至休眠,外设事件发生硬件自动通知,CPU 暂停当前工作转去处理,处理完返回,仿佛什么都没发生。

5.2 中断全流程

  1. 中断请求:外设通过内部连线向 NVIC 发信号。
  2. 优先级仲裁:NVIC 按优先级排队,选出最高的发给 CPU。
  3. 硬件压栈:CPU 自动把 xPSR, PC, LR, R12, R3–R0 共 8 个寄存器压入栈。
  4. 查向量表跳转:硬件根据中断号,从向量表取出对应中断服务函数的入口地址,装入 PC。
  5. 执行中断服务函数:你写在 stm32f10x_it.c 里的代码。
  6. 硬件出栈返回:函数末尾执行 BX LR,硬件自动把 8 个寄存器值弹回,PC 恢复原位。

你做的工作只是往空壳函数里填业务逻辑,剩下全是硬件自动完成。

5.3 堆栈和中断

为什么中断必须用栈:只存一个“状态”不够,必须把当前运行中用到的寄存器全部原封不动保存(PC、状态寄存器、中间结果等)。Cortex-M3 硬件自动完成 8 个寄存器的压栈。

栈存在哪里:RAM。启动文件第一行 Stack_Size EQU 0x00000400 就是给它划空间。不能放 Flash,因为 Flash 写入太慢。

5.4 中断向量表

本质:一张“跳转地址表”,是 Flash 最开头的一个地址数组。物理位置在 0x08000000

内容

  • 第一项:栈顶地址(SP 初始值)
  • 第二项:复位中断入口(Reset_Handler
  • 第三项:NMI 中断入口
  • ……后面是几十个外设中断入口

工作原理:发生中断时,硬件根据中断编号自动计算偏移 中断号 × 4,从向量表取出地址装入 PC。这是纯硬件电路写死的逻辑,没有软件参与。

向量表和普通函数调用的类比:普通函数调用是在编译时把函数地址填进 BL 指令;中断响应是运行时由硬件触发查表跳转。本质都是“取一个地址,跳过去执行”。

5.5 复位中断

复位中断(Reset_Handler)是启动文件里的第一个中断服务函数。特殊之处在于:芯片刚刚上电,所有寄存器不确定,根本没有“现场”需要保存。它的工作是:

  1. 把 RW-data 从 Flash 搬到 RAM
  2. 清零 ZI-data 段
  3. 调用 SystemInit()(配置时钟)
  4. 调用 main()

把它算进中断体系是为了利用统一的中断向量表机制,避免额外设计一套“启动入口”。

5.6 中断服务函数的规范

  • 不能有返回值
  • 不能有参数
  • 必须短小精悍(不宜做耗时操作)
  • 函数名必须与启动文件里向量表定义的符号完全一致

5.7 主函数和中断的协作模式

“中断是速记员,主函数是业务后厨。”

  • 中断里:只做最少的事(收字节放缓冲区、把标志位置 1、清中断标志),立刻退出。
  • 主循环里:轮询标志位和缓冲区,做耗时处理(解析命令、计算、更新屏幕)。

为什么主函数必须有死循环:单片机裸机没有操作系统。main() 一旦返回,CPU 程序计数器不知道该指向哪里,会执行到非法区域跑飞死机。while(1) 是硬件必须的兜底机制,不是偷懒。


六、启动文件与编译系统

6.1 启动文件(startup_stm32f10x_md.s

后缀 .s:汇编语言源文件。必须用汇编写,因为要操作寄存器、设栈指针、建向量表,这些 C 语言不能直接表达。

启动文件的完整功能清单

序号 功能 具体做什么 为什么必须由它做
分配栈空间 Stack_Size EQU 0x00000400 C 语言运行前提
分配堆空间 Heap_Size EQU 0x00000200 malloc 需要的内存池
构建中断向量表 按 Cortex-M3 规范排列所有中断入口地址 CPU 硬件强制从 0x08000000 读取
实现 Reset_Handler 搬数据、清零、调 SystemInit()、调 main() 搭建 C 语言运行环境
声明默认中断函数(弱定义) [WEAK] 关键字定义空占位符 防止意外中断死机,允许用户重写覆盖

本质:从硬件复位到 main() 被调用之间,唯一在跑的代码。没有它,STM32 就是一坨硅片。


6.2 .h.c 的编译链接模型

.h 文件:说明书(函数声明)。告诉编译器有哪些函数可用,长什么样。不参与编译,不进入最终固件。

.c 文件:车间(函数实体)。存放真正的代码逻辑,需要被编译成 .o 文件。

分开的原因:方便多人协作和代码复用。你只需给别人说明书(.h)和编译好的库(.lib),他就能调用你的函数。

编译链接全流程

  1. 预处理#include.h 内容原封不动复制粘贴进 .c
  2. 编译:编译器看到函数声明,做类型检查,生成一条带空白地址的 BL 指令。
  3. 链接:链接器在所有 .o 文件里按函数名字匹配,找到实体地址,填进空白。

.h 文件在链接阶段完全不参与,已经丢弃。

普通函数调用 vs 中断响应

对比项 库函数调用 中断响应
触发方式 代码主动写调用语句 硬件信号被动触发
地址填入时机 链接时 链接时(固化在向量表)
运行时行为 直接跳转 硬件压栈 → 查表 → 跳转

6.3 Keil 工程管理:物理文件夹 vs 逻辑分组

物理文件夹:文件在硬盘的真实位置,编译器不关心。

Keil 工程分组:告诉编译器“这个项目包含哪些源文件”,不在分组里的文件不会被编译。

添加 .c 进分组:告诉 Keil“这个文件要编译”。

配置 Include Paths:告诉编译器“去这些文件夹里找头文件”。这是 Keil 使用的强制标准操作,不是可选项。因为库文件在独立的文件夹(如 Library/inc),和 main.c 不在同一目录。

如果不声明路径会怎样#include "stm32f10x.h" 时编译器先在 main.c 所在文件夹找,找不到就报错 cannot open source input file


6.4 USE_STDPERIPH_DRIVER

作用:条件编译的总开关。

库头文件 stm32f10x.h 里有:

#ifdef USE_STDPERIPH_DRIVER
  #include "stm32f10x_conf.h"
#endif

这个暗号是库作者预先写死的。只有定义了这个特定字符串,锁才打开,所有外设驱动头文件才被包含。

在魔术棒 Define 框里填一次,等于告诉编译器:“编译本工程所有 .c 文件时,都自动定义好 USE_STDPERIPH_DRIVER。” 这样每个文件都单独满足条件编译的条件,步调一致。

能否用代码实现:可以在每个源文件最开头写 #define USE_STDPERIPH_DRIVER,但必须写在 #include "stm32f10x.h" 之前,且每个文件都得写一遍。Define 框省去了这个麻烦。


6.5 stm32f10x_conf.h 的作用

这是一个“总道具清单”。它内部用 #include 包含了所有外设驱动的头文件,默认全部注释掉。你需要哪个外设,就把哪行的注释去掉。本质上,它就是一条条普通的 #include 语句,只是起集中配置文件的作用。

最终决定固件体积的:不是你引入了多少头文件,而是你实际调用了哪些函数。链接器按需提取,没调用的函数不会被塞进最终二进制文件。


6.6 Build、Rebuild、Translate

操作 做什么 什么时候用
Translate 只编译当前激活的单个 .c 文件,不链接 快速检查语法
Build 只编译修改过的文件,然后链接(增量编译) 日常开发
Rebuild 不管改没改,全部从头编译再链接 改魔术棒配置、交作业前

Build 为什么快:库文件编译一次后生成 .o,只要你没改它就不再重新编译,只编译你改动的文件然后直接链接。这就是“增量编译”。

6.7 编译生成的中间文件

扩展名 作用 能否删除
.o 编译后的二进制机器码+符号表,链接器拼成固件的原料 不能手动删
.d 记录该源文件 #include 了哪些头文件,用于增量编译判断 可删,自动重建
.crf 交叉引用文件,支持“跳转到定义”功能 可删,自动重建
.s C 代码对应的汇编代码,给你看的 可删,自动重建
.hex/.bin 最终烧录固件 成品,要留着

七、寄存器操作与 C 语言相关

7.1 RCC->APB2ENR 的本质

RCC 是一个指向结构体的指针常量:

#define RCC ((RCC_TypeDef *) 0x40021000)

RCC->APB2ENR 的地址 = 0x40021000 + 结构体成员 APB2ENR 的偏移量(0x18) = 0x40021018

你写 RCC->APB2ENR = 0x00000010;,编译器把它翻译成 STR R1, [0x40021018],数据总线上的 32 位值直接拍进对应寄存器。

7.2 位掩码宏

#define RCC_APB2ENR_IOPCEN  ((uint32_t)0x00000010)

就是 0x00000010 的宏替身。IOPC = IO Port C,EN = Enable。

为什么不用十进制写:手册上所有寄存器位掩码都是十六进制标注,用十六进制可直接对照手册。

7.3 位运算操作符

写法 效果
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; 第 4 位置 1(开时钟),其余位不变
RCC->APB2ENR &= ~RCC_APB2ENR_IOPCEN; 第 4 位清 0(关时钟),其余位不变
RCC->APB2ENR ^= RCC_APB2ENR_IOPCEN; 第 4 位翻转

|= 是“按位或后赋值”,&= ~ 是“按位与取反后赋值”。它们只改动目标位,其余位安全保留。

位运算符只适用于整型。但所有整型数据在内存中都以二进制存储,编译器自动把十六进制常量换算成二进制位模式参与运算。


7.4 uint32_t 是什么

无符号 32 位整数的标准化写法(定义在 <stdint.h>)。STM32 所有寄存器都是 32 位宽,必须用它保证位宽精确匹配。

7.5 枚举(enum)的作用

给整数常量起有意义的名字。GPIO_Speed_50MHz 在代码里看到就知道是 50MHz 速度,而没有枚举就得查手册才知道数字 3 代表什么。

自动递增规则:枚举成员如果不显式赋值,值等于前一个成员的值 + 1。

参数校验宏IS_GPIO_SPEED(SPEED) 这种宏专门用于判断传入参数值是否在合法范围内,配合 assert_param() 在函数开头做参数合法性检查。


八、调试、烧录与其他

ST-Link:连接电脑 USB 和芯片调试口的硬件工具。

SWD(Serial Wire Debug):两线调试协议(SWDIO + SWCLK)。JTAG 是它的老前辈,占 5 个脚,在 STM32 上已基本被 SWD 取代。

Keil 里的 Port 选项:选择用 SW 还是 JTAG 协议通信。直接选 SW 就好,大多数报错是因为 JTAG 兼容问题。

烧录和调试共用同一接口:点 Download 是把程序灌进 Flash;点 Debug 是在程序跑起来后能随时暂停、单步、看变量值。这两样走的是同一条物理通路。


8.2 软件工程调试 vs 嵌入式调试

纯软件 C 程序:点运行,结果在黑窗口里显示;操作系统和驱动帮你把硬件全包办了。

STM32 程序:Keil 只编译并告诉你语法对不对。灯亮不亮、电平对不对,必须烧进芯片看实际效果。可以借助 Debug 模式看寄存器值辅助排查,但终究要硬件验证。编译通过只是第一步,硬件跑通才是真本事。


九、核心哲学:从砂子到软件的抽象栈

硅片上刻出固定的逻辑电路 → 指令编码是这些电路的触发口令 → 程序是一串口令序列 → 编译器把你的想法翻译成这些口令。

整个数字系统设计思想就是分层封装、逐层抽象:

物理电路 → 指令集 → 汇编 → 编译器 → 高级语言 → 操作系统 → 应用软件

每一层向上提供人类易于理解的接口,向下屏蔽底层硬件细节。你学的 STM32 裸机卡在 C 语言和寄存器之间,所以你能同时看到高级语言的便利性和底层硬件的赤裸真相。

模块化设计的代价:你看到的“性能浪费”,是工程设计里用空间和效率换来的通用性、可维护性与可靠性。没有这些浪费,就没有 STM32 这种人人都能玩得起的通用单片机。


笔记终。持续更新。

posted @ 2026-05-05 00:00  AX的档案柜  阅读(0)  评论(0)    收藏  举报