type
slug
status
summary
icon
category
date
tags
password

1 嵌入式硬件系统的基本组成

notion image
  • Processor
    • ARM (Advanced RISC Machine)
      • 32bit RISC (Reduced Instruction Set Computer)
      • 启动模式
      • 寄存器
      • 指令集
  • Bus
    • ARM AMBA (Advanced Microcontroller Bus Architecture) bus
      • AHB (Advanced High-performance Bus)
        • 高性能的总线接口,用于连接CPU和需要高速数据传输的设备,如SRAM、LCD控制器等。
      • APB (Advanced Peripheral Bus)
        • 连接外围设备的总线接口,通常用于连接速度要求较低的设备,如定时器、UART等。
        • Bridge:这是一个桥接器,用于连接AHB总线和APB总线,允许不同速度的设备之间进行通信。
        notion image
    • PCI bus(外围部件互连总线)
      • CPCI
      • PCIE

1.1 7种模式运行下的寄存器

notion image

1.2 CPSR布局

notion image

1.2.1 控制”大端”和”小端”之间的条件

  • 什么是大端和小端?
    • 当你把一个多字节的数据(比如 0x12345678)放入内存时,有两种存法:
    • 存储方式
      高字节在哪?
      举例(0x12345678)
      大端(Big Endian)
      高位在低地址
      0x12 0x34 0x56 0x78
      小端(Little Endian)
      低位在低地址
      0x78 0x56 0x34 0x12
      举个例子:内存从地址 0x1000 开始存储 0x12345678
      大端0x1000=0x12, 0x1001=0x34, 0x1002=0x56, 0x1003=0x78
      小端0x1000=0x78, 0x1001=0x56, 0x1002=0x34, 0x1003=0x12
      notion image
      notion image
  • 为什么会有两种方式?
    • 不同的CPU架构采用不同的存储方式:Intel x86、ARM 默认使用小端,部分网络协议使用 大端(如 TCP/IP 中的“网络字节序”)

1.2.2 CPSR各位详解

notion image
Bit 范围
含义
31
N(负号标志)
结果为负
30
Z(零标志)
结果为0
29
C(进位标志)
有进位/借位
28
V(溢出标志)
有溢出
27-8
保留位
7
I(IRQ中断禁止位)
IRQ(普通中断)屏蔽位,1=禁止IRQ中断
6
F(FIQ中断禁止位)
FIQ(快速中断)屏蔽位,1=禁止FIQ中断
5
T(Thumb状态标志)
T=1:Thumb 模式(使用16位指令) T=0:ARM 模式(使用32位指令)
4-0
模式位(M4~M0,表示当前CPU模式)
模式名
值(M4~M0)
描述
User
10000 (0x10)
普通用户模式
FIQ
10001 (0x11)
快速中断模式
IRQ
10010 (0x12)
普通中断模式
Supervisor
10011 (0x13)
管理模式(启动后进入)
Abort
10111 (0x17)
异常模式(数据访问错误)
Undefined
11011 (0x1B)
未定义指令异常
System
11111 (0x1F)
特权用户模式
notion image
  • R15在ARM状态下通常用作程序计数器(PC,Program Counter)。THUMB指令集使用16位的指令,因此地址需要对齐到半字(16位)。
  • 在ARM状态下,R15的低2位总是0,而高30位用于存储下一条指令的地址。
  • 在THUMB状态下,R15的最低位总是0,而高31位用于存储下一条指令的地址。
 

1.3 字节/半字/字对齐

  • 对齐规则:
    • 字节对齐:无限制。
    • 半字对齐:地址是偶数。
    • 字对齐:地址是4的倍数。
 

1.4 中断返回时的指令(涉及流水线)

notion image
notion image
 
 

2 Practice1: 代码拷贝

notion image
  • 内存布局图(从 LOW 到 HIGH):Norflash(低地址)、……(中间是代码段)、RAM(高地址)
  • r1: ResetHandler 起始地址(Norflash),r2: 目标地址 text_start(RAM 中),r2: 目标地址 text_start(RAM 中)
    • 所以目标是把 Flash 中的代码段从 r1 复制到 RAM 中 r2r3 之间。
  • 背景知识:为什么要复制代码段到 RAM?
    • Flash是非易失存储器,只能执行代码,访问速度较慢,不能被频繁写入/修改。而RAM 是易失存储器,访问速度快,可读写。为了更快执行,有些代码会在启动时从Flash复制到 RAM,再从RAM运行。
  • 汇编代码逐句解释
    • ADR r1, ResetHandler:这条指令的目的是获取 ResetHandler 的地址。由于 ResetHandler 是一个标签,它的地址是相对于当前指令的。因此,使用 ADR 伪指令可以直接计算出这个相对地址。
    • LDR r2, =text_startLDR r3, =bss_start:这两条指令的目的是获取 text_startbss_start 的地址。这两个地址通常是在链接器脚本中定义的,它们表示代码段和数据段的开始位置。由于这些地址是绝对的,而不是相对于当前指令的,因此需要使用 LDR 伪指令来加载这些地址。
    • 因为在拷贝代码之前ResetHandler是存放在Flash中的,而此时PC在Flash中取址,所以此时所有的在Flash中的标号都应该使用相对寻址获取(所有的标号函数在编译的时候都是一个相对地址,而在经过连接之后就会变成一个绝对地址。但是就算是绝对地址也还是使用ADR指令来获取,因为如果使用LDR指令的话就要在缓冲池中存储这个标号的地址了)
    • 而text与bss都是在连接脚本中指定的绝对地址,所以这里需要使用LDR指令获取
    • 说白了就是,ADR指令用于获取相对地址(如函数名,标号啥的);而LDR指令用于获取立即数或者绝对地址(如链接脚本中指定的标号)
    • 或者换一句话说,LDR是完全根据编译后生成的绝对地址确定的标号地址(就是完全相对于链接脚本中指定的运行域来确定的),而ADR指令是要区分当前代码的位置(就是需要关心当前代码是在运行域还是加载域的,在这两个域上使用ADR指令的结果是不一样的。所以如果运行域与加载域完全相同,那么就会出现LDR指令与ADR指令计算地址相同的情况)
    • 循环复制copyloop
    • 步骤
      地址
      动作
      1
      r1 是 Flash 中代码段的起始地址(例如 ResetHandler
      从 Flash 中读出 4 字节放入 r0
      2
      r2 是 RAM 中复制目标区域的起始地址(例如 .text 段)
      r0 的数据写入 RAM 中的 r2 地址
      3
      然后 r1 和 r2 各自自动 +4,表示处理下一个字
      4
      如果 r2 达到了 bss_start,就说明复制完了
      5
      否则继续循环,直到全部复制完成
  • 为什么需要这样做?
    • 图下方红字问题
      Why? If the code segment is in the 0 sector of a FLASH disk...?
      如果代码已经在Flash的0地址扇区了,为什么还要复制?
    • CPU复位后从 Flash 执行,但有些代码必须在 RAM 执行:
      • RAM,执行更快;有些函数必须是可读写的(如全局变量初始化、BSS清零等)
      • 有些代码(如中断向量表、C库函数)必须在 RAM 中运行
      • Flash 可能是只读,不能动态修改执行位置的数据
  • 拓展知识点
    • BSS段(bss_start):未初始化的全局/静态变量区,通常也在启动代码中清零。
    • .text 段(text_start):程序代码段,通常是 Flash 中复制过来的。
    • ResetHandler:复位处理函数,是 ARM 启动的第一个执行点。
    • ADR vs LDR
      • ADR 用于计算地址(当前PC附近的标签)
        • ADR 是 PC 相对地址计算(PC + offset)
          • Label 的地址(而不是内容) 计算出来,存入 r0。这个地址是通过当前 PC 和 Label 之间的**偏移量(offset)**计算的。假设:当前 PC = 0x1000Label 距离 PC 往后偏移 0x20,那么执行完这一条语句之后相当于r0 = PC + 0x20; // 即 r0 = 0x1020。不会访问内存,纯靠PC和偏移计算。只能访问距离 PC 一定范围内的地址(例如 ±4KB 之内,取决于平台)。
      • LDR 用于加载常量地址(全局变量地址/远距离符号)
        • LDR r0, =Label 是访问常量池(literal pool)
        • 这不是字面意思“从label地址加载数据”,而是一个汇编语法糖,它实际上:在常量池中保存了 label 的地址(一个32位的立即数),然后让 r0 去读取这个值。
        • 指令中并没有直接包含 Label 的值或地址。而是访问一块内存(叫做 literal pool)中事先放好的数据。这个值是是链接器(linker)根据**链接脚本中“运行地址”**决定的!
        • 可以访问任何内存地址或常量,几乎无限制。会引入一次实际的内存读取(和 ADR 不同)。
      • 什么时候用哪个?
        • 想要得到一个地址?ADR 更快更省空间(如果地址在范围内)。
        • 想要加载一个常量或者远距离地址?LDR =xxx 更通用
      • 总结对比
        • 特性
          ADR
          LDR =xxx
          本质
          PC + offset
          访问常量池(literal pool)
          获取内容
          获取地址(Label的地址)
          获取地址或数据
          是否访问内存
          否(纯寄存器操作)
          是(一次内存读取)
          地址范围
          有限(±4KB左右)
          无限(取决于常量池放在哪)
          编译器优化
          高效
          灵活但开销略大
  • 总的来说就是需要注意:获取ResetHandler的地址是通过ADR指令获取的
    • ADR r1, ResetHandler
    •  
notion image
  • 中断向量表有两张:一级中断向量表中存放的是跳转指令,而二级中断向量表中存放的是ISR的地址。这段代码应该在启动文件中,并且上面的这两张表合起来才能被称为中断向量表(跳转表+注册表)
  • 另外,这里的LDR PC,地址实际上就是从该地址处取出值存放在PC中(这里的LDR指令就不是伪指令了),而VECTORTABLE中存放的就是ISR的地址。当一个中断或者异常发生了,这里就会跳转到Hal那个表中对应的中断向量(所以这个HALVECTR_START才是真正的中断向量表,下面那张表就只是在注册中断,或者说像是一个文字缓冲池,如果没有注册表的话,标号跳转又需要使用ADR指令,而ADR指令又不能将地址读取到PC寄存器中)
  • 这里使用LDR指令+注册表的形式就相当于是手动实现了LDR伪指令的PC长跳转功能
notion image
  • 中断返回需要结合流水线分析
 
 

3 Practice2: 中断向量表拷贝

notion image
💡
  • 背景:为什么要复制中断向量表?
    • ARM 启动后,中断向量表必须在地址 0x00000000/或者某些处理器可配置的位置(如 0xFFFF0000但这块内存可能是Flash(可执行不可改)/或者 SRAM(可执行可改)
    • 你的中断向量表(放在 .vector 段)链接脚本中设置的运行地址是 0x00000000,但程序实际运行时是从Flash(比如 0x08000000)中加载的(即链接器把中断向量表加载进了 Flash 的 0x08000000)。所以,中断向量表最初并不在目标地址(运行地址)处,你必须手动把它从 Flash中复制到运行地址
  • 分析这段代码:
    • LDR r3, [r0], #4LDR是从内存中读取一个字(4字节)到寄存器。[r0]:从r0指向的地址读数据(例如 Flash 地址)。#4:这是“后递增”寻址模式:先用 r0 当前地址访问内存,然后让 r0 自动加 4(指向下一个地址)
      • r0 = 0x08000000,该地址在 Flash 中,Flash 中 0x08000000 的值是 0xDEADBEEF
      • 执行这句后:r3 = 0xDEADBEEF。r0 = 0x08000004(地址增加了 4)
    • STR r3, [r2], #4STR:把寄存器里的数据写入内存[r2]:往 r2 当前地址写入 r3 的值。#4:写完之后,r2 += 4,移动到下一个目标地址。
      • r2 = 0x00000000(SRAM 中,运行地址)r3 = 0xDEADBEEF
      • 执行这句后:把 0xDEADBEEF 写入地址 0x00000000,然后 r2 = 0x00000004
    • 拓展知识点
      • 操作
        语法
        含义
        不变地址
        [r0]
        只访问,不变
        后递增
        [r0], #4
        访问 r0 → 然后 r0 += 4
        前递增
        [r0, #4]!
        r0 += 4 → 然后访问新地址
        偏移访问,不改变地址
        [r0, #4]
        访问 r0+4,但 r0 本身不变
    • 首先需要知道的是,中断向量表的拷贝是在代码拷贝之后的(从连接脚本中看到的,32的拷贝是先拷贝了数据段,然后再将中断向量表放在最低地址处)
    • 经过代码拷贝之后,加载域中的代码就被拷贝到运行域了,这个时候LDR与ADR指令获取的地址就是完全相同的了(此时PC也跳转到了运行域中),这个时候就可以直接使用LDR指令了:
      • notion image
    • 需要注意的是这里还多了一步判断,就是判断中断向量表是不是已经被放在0地址处了,如果是的话就没有必要再次进行拷贝了,就可以直接执行real_code了。
       
       

      4 ARM指令集和应用

      4.1 常见的ARM指令集

      notion image
      • 分支指令(Branch)
        • 功能B 指令用于无条件跳转到指定标签(Label)的地址。
        • 地址范围:跳转范围为当前程序计数器(PC)的 ±32MB(ARM模式下,指令编码支持26位偏移量,左移2位后覆盖此范围)。
        • 编码格式:
          • 位 31-28:条件码(如 EQNE,无条件跳转时默认为 AL)。
          • 位 27-24:操作码(1010 表示 B 指令)
          • 位 23-0:24 位有符号偏移量,左移 2 位后得到实际偏移地址。
      • 数据处理指令(Data Process)
        • SUB 指令:减法运算,r0 = r1 - 5语法SUB <目标寄存器>, <操作数1>, <操作数2>
        • ANDS 指令: 功能:按位与操作,并更新条件标志(S 后缀(CPSR 中的N、Z、C、V 位)。
        • ADDEQ 指令:条件加法(EQ表示相等时执行),若条件满足(Z=1),则 r5 = r5 + r6
      • 数据处理指令(Data Process)
        • MRS 指令 : 将 SPSR(Saved Program Status Register)的值读取到通用寄存器。
        • MSR 指令 : 将通用寄存器的值写入 SPSR/CPSR(Current Program Status Register)。
        • CPSR_C 表示仅修改控制位(特权模式下允许修改)。操作状态寄存器需在特权模式(如内核态)下执行。
      notion image
      • 内存访问(Memory access)
        • LDR r0,[r1]:这条指令用于从内存中加载一个字(word)到寄存器r0。具体来说,它将地址r1处的字加载到r0中。
        • STRNEB r2,[r3,r4]:这条指令在条件码NE(Not Equal,不等)为真时执行。它将寄存器r2的低字节(bottom byte)存储到由r3和r4相加得到的地址中,并将结果存储在r3中(Z=0表示不更新标志位)。
        • STMFD sp!,{r0-r7,lr}:这条指令将寄存器r0到r7以及链接寄存器lr(Link Register)存储到堆栈中,然后更新堆栈指针sp。sp!表示在存储后自动更新sp。
        • LDMFD sp!,{r0-r7,pc}:这条指令从堆栈中加载寄存器r0到r7以及程序计数器pc的值,然后更新堆栈指针sp。sp!表示在加载后自动更新sp。
      • 协处理器(Companion processor)
        • LDC p3, c4, [r0]:这条指令用于加载协处理器寄存器。它从内存地址r0处加载数据到协处理器p3的寄存器c4中。
      • 异常处理(Exception process)
        • SWI 0x02:这条指令用于触发一个软件中断(Software Interrupt)。SWI(Software Interrupt)指令允许程序请求操作系统的服务,0x02是中断号,用于指定请求的具体服务。
      notion image
      • 算术逻辑单元(ALU)操作与移位操作
        • MOV r1, r0, LSL #3:这条指令展示了如何在一条指令中同时进行ALU操作和移位操作。MOV指令通常用于将数据从一个寄存器移动到另一个寄存器,或者从寄存器移动到内存,或从内存移动到寄存器。在这个例子中,LSL #3表示逻辑左移(Logical Shift Left)3位。因此,这条指令将寄存器r0的内容逻辑左移3位,并将结果存储在寄存器r1中。
      • 其他类型的指令
        • BKPT 0x??BKPT是断点指令(Breakpoint),用于在调试过程中暂停程序执行。0x??是一个占位符,代表一个具体的16位立即数,这个数值可以指定断点的类型或相关的调试信息。当程序执行到这条指令时,它会触发一个断点异常,允许调试器介入并检查程序的状态。
       

      4.2 应用案例

      notion image
      • 原来的代码:循环执行10次,每次将r2累加r3的值。
      • 若去掉SUBS和BNE :r1不再递减,循环无法终止,导致无限循环。无分支条件判断,程序流无法跳转。类比于去掉C语言中的i++和i<2023,循环条件无法检查和更新,同样导致无限循环。
      • 标志位和流水线机制
        • NZCV 是 ARM 架构中 CPSR(Current Program Status Register)程序状态寄存器 的 4 个重要标志位,用于记录最近一次算术或逻辑运算的结果,用来影响条件执行(比如条件跳转指令)。
        • 这些位会被如 ADDSUBCMPMOVSANDS 等带 S 后缀的指令自动更新。
          • 位名
            英文全称
            含义
            N
            Negative
            结果为负时置 1(最高位为 1)【只关注第31位】
            Z
            Zero
            结果为 0 时置 1
            C
            Carry
            加法产生进位,或减法未借位时置 1
            V
            Overflow
            有符号运算溢出时置 1
        • ARM 指令可带 条件后缀,判断 NZCV 结果后执行。
          • 条件后缀
            含义
            等价条件
            EQ
            相等
            Z = 1
            NE
            不等
            Z = 0
            MI
            负数
            N = 1
            PL
            正数
            N = 0
            CS/HS
            ≥(无符号)
            C = 1
            CC/LO
            <(无符号)
            C = 0
            GT
            >(有符号)
            Z = 0 且 N = V
            LT
            <(有符号)
            N ≠ V
      • SUBS r1, r1, #0x01:将寄存器r1的值减1,并更新程序状态寄存器(CPSR)中的标志位,包括Z标志位。如果r1减1后的结果为0,则Z标志位被设置为1。
      • BNE LOOP:检查Z标志位。如果Z标志位为0(表示r1减1后的结果不为0,NE),则跳转回LOOP标签处,继续执行循环。如果Z标志位为1(表示r1减1后的结果为0),则不跳转,继续执行后面的指令,循环结束。
      notion image
      • ORR r0, r0, #(PSR_I_BIT | PSR_F_BIT)
        • 这条指令对r0进行逻辑或操作,设置PSR中的I位(中断禁止位)和F位(快速中断禁止位)。这两个位用于控制中断的使能状态。
        • PSR_I_BITPSR_F_BIT通常分别对应于CPSR的第7位和第6位。
      • MSR CPSR_c, r0
        • 这条指令将r0中的值写回到CPSR的控制位(C位)。
      notion image
      • 这个是协处理器指令,这张幻灯片展示了如何在ARM处理器中通过一系列指令来禁用缓存和内存管理单元(MMU)。和MRS和MSR有异曲同工之妙。
      • MCR p15, 0, r0, c7, c5, 0
        • 使用协处理器指令(MCR)来使指令缓存(I Cache)失效。p15是协处理器的编号,0是协处理器的类型,r0是要写入的数据(在这里是0),c7是协处理器寄存器的编号,c5是该寄存器内的偏移量,0是操作的选项。
      • MCR p15, 0, r0, c7, c6, 0
        • 同样使用MCR指令来使数据缓存(D Cache)失效。
      • MRC p15, 0, r0, c1, c0, 0
        • 使用协处理器指令(MRC)来获取控制寄存器的值。c1是控制寄存器的编号,c0是该寄存器内的偏移量。
      • BIC r0, r0, #(CTRL_M_BIT | CTRL_C_BIT)
        • 使用位清除指令(BIC)来清除控制寄存器中的内存管理单元(MMU)和数据缓存(D Cache)使能位。CTRL_M_BITCTRL_C_BIT分别是控制寄存器中MMU和D Cache使能位的位置。
      • BIC r0, r0, #CTRL_I_BIT
        • 再次使用BIC指令来清除控制寄存器中的指令缓存(I Cache)使能位。
      • MCR p15, 0, r0, c1, c0, 0
        • 将修改后的控制寄存器值写回,从而禁用MMU、D Cache和I Cache。
      • CTRL_M_BIT:位0,用于控制MMU的使能。| TRL_C_BIT:位2,用于控制D Cache的使能。| CTRL_I_BIT:位12,用于控制I Cache的使能。

      5 Boot of ARM

      • Internal boot (from ROM)
      • External boot (from Norflash or ……)
      Chapter2:Kernel of ERTOSChapter4:Software System
      Loading...
      🐟🐟
      🐟🐟
      在坚冰还盖着北海的时候,我看到了怒放的梅花
      最新发布
      Chapter4:Software System
      2025-5-21
      Chapter3:Hardware System
      2025-5-21
      Chapter2:Kernel of ERTOS
      2025-5-21
      Chapter1:Introduction
      2025-5-21
      Hi3861 & 服创 & 计设
      2025-5-17
      1-1-1 用Keil点亮一盏小灯
      2025-5-16
      公告
      🎉NotionNext 3.15已上线🎉
      -- 感谢您的支持 ---
      👏欢迎更新体验👏