1.1 概述章-CPU架构

  • 99%的手机50%的CPU都是基于ARM的。只卖架构知识产权IP
  • 里面有Cortex-A(手机常用)
  • Cortex-R(用的少)
  • Cortex-M(低功耗)内核架构设计:
  • 指令集:指令集(ISA)就是 CPU 能懂的全部"单词"的集合——软件用这些单词写程序,CPU 按这些单词干活
  • Cortex-M是Thumb指令集,在上面跑ARM指令集会出问题。Thumb 是 ARM 指令集的"压缩版"——每条指令从 32 位缩到 16 位,牺牲了部分功能,但大幅节省 Flash 空间。Thumb-2 又加回了 32 位指令,形成了现在的 16/32 位混合模式。
  • STM32F103这次不讲外设的应用,讲内核操作系统应用。
  • M3核移植到M7核是省事的,高版本兼容低版本

2.1 编译章 编译工具链

  • 以前我们都是通过集成开发环境IDE进行一键编译,会忽略很多细节。
  • 编译工具链常见:ARM公司自研发的keil5(收费),GCC(GNU)STM公司的cubeMX
  •  GNU 编译器套件,你把 C 代码丢给它,它产出机器码。
  • C语言编译器有一个也叫gcc,注意区分
  •  STM32CubeIDE叫集成开发环境,是GCC编译工具链(将.c文件转换成机器码可执行文件)+cubeMX(生成.c文件)

2.2 写一段最简单的程序

  • 先创建.s(GCC)文件。去模拟int a=1;语句的汇编(汇编效率最高和CPU直接对话)
  • 为什么不用汇编语言?可移植性封装性都不如高级语言
  • 根据汇编手册去对照编译出.o文件(二进制中间文件),通过链接过后的才是可执行文件
  • 通过hex转换成.hex文件可以更清晰看见可执行文件的结果。
  • 烧录:把编译好的机器码二进制数据写到CPU存储器中。方式:USB,蓝牙,WIFI后2者是无线通讯方式,专业术语叫OTA升级

2.3 反汇编

图增加了一些细节:

  • 反汇编:逆向的过程,把已经转换的机器码转换为汇编代码。.s到.o文件是把汇编代码转为机器码。
  • 反汇编作用:将商品的机器码获取出来,然后反汇编为汇编代码,可以看见主要的算法逻辑
  • 工具:反汇编器(有命令)将.bin文件转成.s。.hex也可反汇编 ,.o以及.elf都可以反汇编
  • .elf 是裸机/Linux 的可执行文件,.exe 是 Windows 的可执行文件(不可写进MCU)。
  • 高级语言.c到.s汇编文件,但是可以将汇编代码转为C语言吗?不是很好的效果。

启动流程

  • 我们写的程序是怎么开始执行的呢?STM32怎么启动的
  • 程序计数器PC从中断向量表的第二项获取初始值
  • 初始化中断向量表:初始化堆栈指针 MSP,PC程序计数器:PC 寄存器里永远存着下一条即将被 CPU 取指执行的指令的内存地址。从0地址处启动

伪代码:

  • GNU 伪操作是你写在 .s 文件里让汇编器执行的命令
  • GNU 伪操作 = 以 . 开头的指令,不是给 CPU 看的,是给汇编器看的。

STM32启动原理

  • 芯片用户可以根据实际需求来选择启动方式,通过映射到0地址处。ARM公司只是提供了从0地址处开始启动STM32

Id链接脚本

  • 链接脚本 = 告诉链接器「哪个节区往哪放」的地图

CPU寄存器

  • 寄存器CPU内部,存储器CPU外部,存储器比寄存器慢4个指令周期左右
  • SMP多核处理器,一个核就有一组寄存器
  • R13:SP堆栈指针
  • R14:LR链接寄存器
  • R15:PC程序计数寄存器
  • R0-R12:通用寄存器
  • 一般自己写寄存器汇编代码,都是在基础寄存器上面写,因为一些指令无法在特殊寄存器上面生效
  • 我们的汇编指令叫GNU(另一个对比就是ARM汇编指令)

PC寄存器R15

  • CPU执行程序的本质就是靠PC寄存器不断的更新,更新频率由时钟决定

SP堆栈指针寄存器R13

  • 堆栈指针 SP 就是 CPU 用来记"当前栈顶在哪"的寄存器——PUSH 时它递减,POP 时它递增,始终指向栈上最新存的数据
  • push递减是因为是向下计数,POP取(读)数据

LR链接寄存器R14

  • CPU 自动帮你记"这个函数执行完了跳回哪"的专用寄存器。
  • 还有一个重要作用:记录异常返回值

存储系统&嵌入式编程的本质

  • 右边不是CPU存储系统,是存储器介质设备,通过通讯外设进行与CPU连接
  • 嵌入式编程的本质:嵌入式编程本质上就是在操作存储器地址空间单元。每一条 C 语句最终都溯源到汇编,所以 STM32 编程 = 查手册找地址 → 往地址写值 → 硬件就按你的设定工作。 HAL 库只是把这一层封装了,让你不用记地址。

读写存储器

  • ++i和i++谁的效率高:没有编译优化下不一定,有加编译优化下一般是等效率的
  • 电压钳位电路:把I/O输入控制在VDD和VSS之间,大于VDD上面就导通,小于VSS下面就导通,想中间过去只能满足在VDD和VSS之间。
  • 嵌入式编程就是在读写存储器

中断的本质

  • 抢占型调度机制

程序崩溃的本质

  • 程序崩溃的本质 = CPU 执行了它无法理解的指令或访问了不该碰的地址,触发了硬件级的异常捕获机制

高阶调试专题

  • int a=1;其中a放在内存,左值可读可写,掉电丢失,1右值只能读,放在flash中,掉电不丢失
  • 内存控制器是厂家定义的
  • CPU里面有专门做计算的ALU(计算电路模块),寄存器叫存储电路模块

韦东山

调试专题

  • 在Keil5里面点击调试,会自动帮我们下载好程序,故需要先进行连接板子
  • ST-Link 核心价值是 下载 + 调试,其中调试(断点/单步/看变量)是它的不可替代价值——串口下载做不到。走SWD调试。Debug模式必须要连接ST-Link
  • Ctrl+B可以去掉所有断点,也可以选择在断点处运行多少次
  • Step Out(步出,快捷键 Shift+F8)
    作用:如果你已经进入了某个函数内部,按这个按钮会一次性执行完当前函数剩下的所有代码,然后自动跳回到 “调用这个函数的那一行” 的下一行。比如你不小心按 F7 进到了 HAL 库的长函数里,或者你看完了函数前半段逻辑确认没问题,想快速退回到主循环里,就按 Shift+F8 直接跳出当前函数

ARM架构

  • volatile主要是优化作用,编译器看你没用这个变量或者函数,就会优化掉变成一个空函数。导致一些问题
  • SP 的"值"是一个内存地址;而这个地址"处"存的是栈顶数据。 地址处我们去查找RAM对应的存储单元
  • 第一条:变了就乱套了,后续程序都出错
  • 第二条:那么其他寄存器,由谁来保证不变呢?
  • 上张截图是从LR寄存器的二进制位数得到
  • Cortex-M3 内核没有 FPU,不支持硬浮点
  • 栈存在RAM里面。
  • 寄存器里存的是"32 位的值"(4字节)——可能是数字、地址、字符、状态标志,CPU 不关心它是什么类型,代码说它是啥就是啥。
  • STMFD = 一次把多个寄存器按顺序压进栈,类似于PUSH,举例:STMFD SP!, {R4-R11, LR}顺序:编号小的寄存器存低地址;LR 被压进栈里备用,!SP = 压完更新 SP
  • 关于无调式器打印出来的东西:自己创建一个内存空间做打印栈,然后自己写汇编代码让软件存入其他寄存器(相比栈帧的寄存器)
  • 怎么知道我们程序崩溃呢?调试全速运行然后跳转到handle函数
  • 首先看PC地址,如果搜素不到,或者乱七八糟就看LR寄存器的值,就找到崩溃在哪个函数里面
  • 疑似LR判断条件:
  • core 文件 = 程序死机瞬间的"现场快照",把崩溃那一刻的寄存器、栈、内存全部存下来,事后离线分析。
  • GDB又是啥?GDB 和 Keil 调试是同一个东西的两个形态
  • GDB = GNU Debugger,Linux 世界的"Keil 调试器"——只是它没有图形界面,全靠敲命令。你学的东西它全能干,还多一个绝活:离线分析 core 文件

C语言

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐