1. ARM926EJ-S处理器:嵌入式领域的经典计算核心
在嵌入式系统开发领域,尤其是那些对成本、功耗和实时性有严苛要求的工业控制、消费电子和网络设备中,选择一颗合适的处理器核心是项目成败的关键。从业十几年,我经手过不少基于ARM架构的项目,而ARM9系列,特别是ARM926EJ-S,绝对算得上是“老兵”中的常青树。它可能没有Cortex-A系列那么高的主频和炫酷的多核特性,但其稳定、可靠、易于掌控的特性,使其在大量成熟且对成本敏感的产品中依然占据着重要地位。
AM1806这颗由德州仪器(TI)推出的SoC,其心脏正是一颗ARM926EJ-S。它不仅仅是一个CPU,更是一个完整的子系统,包含了缓存、内存管理单元(MMU)、总线接口以及专用的内部存储器。理解这个子系统,是驾驭整个AM1806芯片、进行底层驱动开发、操作系统移植乃至性能优化的基础。本文将带你深入ARM926EJ-S的内核,从指令集、内存管理一直剖析到它与SoC内其他模块如何高效协同工作,其中会穿插大量实际调试和配置中的“干货”与“避坑指南”。
2. ARM926EJ-S核心架构深度解析
ARM926EJ-S属于ARMv5TEJ架构的处理器,是ARM9家族的代表。它的设计目标非常明确:在有限的硅片面积和功耗预算下,为运行多任务操作系统(如Linux、Windows CE)提供足够的性能支撑。其“哈佛架构”设计——即指令和数据拥有独立的总线和缓存,是它高效执行的关键。
2.1 核心组件与总线接口
ARM926EJ-S核心子系统是一个高度集成的模块,其组成远不止一个算术逻辑单元(ALU)。我们可以将其拆解为几个关键部分:
- 整数核心(ARM9EJ-S Core):这是执行指令的引擎,采用5级流水线(取指、译码、执行、存储/写回),支持ARM和Thumb指令集,并包含了用于Java字节码加速的Jazelle技术硬件扩展。
- 内存管理单元(MMU):这是运行现代操作系统的基石。它负责将程序使用的“虚拟地址”转换为实际的“物理地址”,同时进行内存访问权限检查。AM1806的MMU支持1MB(段)、64KB(大页)、4KB(小页)和1KB(微页)多种页面大小,提供了极大的灵活性。
- 缓存与写缓冲区:
- 指令缓存(I-Cache):16KB,4路组相联。用于缓存频繁执行的指令,极大减少从低速外部存储器取指的开销。
- 数据缓存(D-Cache):16KB,4路组相联。支持“写通”和“写回”两种策略,可由MMU按内存区域配置。写回策略能显著提升对同一地址多次写入的性能。
- 写缓冲区:包含一个16字(64字节)的数据缓冲区和4个地址缓冲区。当CPU向可缓存或缓冲区使能的内存区域写入数据时,数据会先进入写缓冲区,CPU无需等待实际写入内存完成即可继续执行,从而隐藏存储延迟。
- 协处理器15(CP15):这是程序员控制核心功能的关键接口。通过
MRC(从协处理器读到ARM寄存器)和MCR(从ARM寄存器写到协处理器)指令,在特权模式下(如SVC、SYS)可以配置和控制MMU、缓存、TCM(尽管AM1806未实现TCM)以及系统功能。 - AHB总线接口:ARM926EJ-S通过两个独立的AMBA AHB总线与外界通信——一个用于指令取指(I-AHB),一个用于数据访问(D-AHB)。这种分离进一步避免了指令和数据流在总线上的竞争。在AM1806中,这两个接口最终连接到SoC内部的交换中心资源(SCR)网络。
实操心得:在启动代码或Bootloader中,最早需要操作的往往就是CP15。例如,在使能MMU或缓存之前,必须无效化(Invalidate)整个TLB和缓存,否则可能因为缓存中残留的旧地址映射或数据,导致程序跑飞。一个常见的操作序列是:关闭MMU和缓存 -> 无效化TLB -> 无效化I-Cache和D-Cache -> 配置页表 -> 使能MMU和缓存。
2.2 操作状态与处理器模式
理解处理器的“状态”和“模式”是理解其异常处理和系统调度的前提。
- ARM状态与Thumb状态:这是指令集宽度状态。ARM状态执行32位定长指令,性能高;Thumb状态执行16位定长指令,代码密度高(通常能减少30%以上)。使用
BX或BLX指令(目标地址最低位为1)可在两种状态间切换。在实际项目中,我们通常用Thumb状态编译大部分应用代码以节省Flash空间,而用ARM状态编译对性能敏感的中断服务例程或关键算法循环。 - 处理器模式:ARM有7种运行模式,不同模式拥有独立的堆栈指针(SP)和部分备份寄存器(如R13, R14),这为操作系统提供了硬件级的隔离和保护。
- 用户模式(USR):非特权模式,运行普通应用程序。
- 特权模式:包括系统模式(SYS)、管理模式(SVC)、中断模式(IRQ)、快速中断模式(FIQ)、中止模式(ABT)和未定义模式(UND)。这些模式可以访问所有系统资源,执行特权指令(如操作CP15)。
- 上电或复位后,处理器进入管理模式(SVC)。应用程序通过发起“软件中断(SWI)”或使用“SVC”指令来触发异常,从而陷入内核态(SVC模式)以请求操作系统服务。
注意事项:FIQ模式有5个额外的私有寄存器(R8_fiq-R12_fiq),这允许FIQ中断处理程序在进入时无需保存这些寄存器,从而实现了极低延迟的中断响应。在设计实时性要求极高的系统时,应将最紧急、最频繁的中断分配给FIQ。IRQ则用于一般中断。
2.3 异常与中断向量表
当发生异常(如中断、非法指令、内存访问错误)时,处理器会强制跳转到固定的内存地址执行,这些地址构成了“异常向量表”。ARM926EJ-S的异常优先级从高到低为:复位 > 数据中止 > FIQ > IRQ > 预取指中止 > 未定义指令 = SWI。
在AM1806中,通过配置CP15的c1寄存器,将VINITHI信号置为高,使得异常向量表基地址位于0xFFFF0000。这个地址映射到了ARM内部的8KB RAM起始处。这是一个非常重要的设计细节!这意味着在系统启动初期,你需要将异常向量表(通常是一系列跳转指令LDR PC, =Handler_XXX)拷贝或编程到这个RAM区域。
表:ARM异常向量表(VINITHI=1时基地址为0xFFFF0000)
| 偏移地址 | 异常类型 | 进入模式 | I位状态 | F位状态 | 典型处理内容 |
|---|---|---|---|---|---|
| 0x00 | 复位(Reset) | SVC | 1(禁用) | 1(禁用) | 初始化堆栈、CP15、时钟、内存控制器,跳转到C入口 |
| 0x04 | 未定义指令 | UND | 1 | 不变 | 报告错误或进行指令模拟(如软件浮点) |
| 0x08 | 软件中断(SWI) | SVC | 1 | 不变 | 系统调用入口,根据SWI号提供内核服务 |
| 0x0C | 预取指中止 | ABT | 1 | 不变 | 处理指令取指失败(如MMU权限错误) |
| 0x10 | 数据中止 | ABT | 1 | 不变 | 处理数据访问失败(如MMU权限错误、总线错误) |
| 0x14 | 保留 | — | — | — | 通常放置一条死循环指令(B .)以防误跳转 |
| 0x18 | 普通中断(IRQ) | IRQ | 1 | 不变 | 通用外设中断服务例程分发入口 |
| 0x1C | 快速中断(FIQ) | FIQ | 1 | 1 | 高优先级、低延迟中断服务,通常处理定时器或通信 |
避坑指南:在编写启动代码时,务必确保在使能任何中断(清除CPSR的I/F位)之前,正确的异常处理程序地址已经就位于向量表中。否则,一旦发生中断,PC会跳转到一个未初始化的地址,导致系统立即崩溃。此外,向量表中的每条指令恰好占4字节,所以通常是一条跳转指令。在RAM中设置向量表比在ROM中更灵活,因为允许动态修改。
3. 内存管理单元(MMU)与地址转换实战
MMU是现代操作系统的守护神。它主要完成两项工作:地址翻译和访问保护。对于运行Linux这类操作系统,MMU是必须开启的。
3.1 地址转换流程与TLB
ARM926EJ-S的MMU采用两级页表转换。CPU核心发出的是虚拟地址(VA),MMU将其转换为修改后的虚拟地址(MVA),主要用于缓存查找(Cache使用MVA作为索引和标签)。最后,MMU通过查询页表,将MVA转换为物理地址(PA),用于访问实际的内存或外设。
转换过程简述:
- CPU发出虚拟地址(VA)。
- VA被转换为MVA(对于单任务系统,通常MVA=VA)。
- MMU用MVA查询TLB(快表)。TLB是页表项(PTE)的缓存,命中则直接获得物理地址和权限信息。
- 若TLB未命中,则MMU启动“页表遍历”硬件过程,从内存中的两级页表里查找对应的PTE,将其加载到TLB,并完成转换。
- 根据PTE中的权限位(AP)和域(Domain)设置,检查此次访问是否合法。若非法,则触发“预取指中止”或“数据中止”异常。
- 若合法,则发出物理地址(PA)进行访问。
TLB管理:TLB是性能关键。ARM926EJ-S提供通过CP15寄存器c8来管理TLB的指令:
Invalidate Entire TLB:使整个TLB失效。通常在修改页表(如创建新映射、修改权限)后必须执行。Invalidate TLB Entry by MVA:根据MVA使特定的TLB条目失效,更精细。Lockdown:通过CP15寄存器c10,可以将关键的TLB条目锁定,防止被换出,保证实时性。
3.2 页表配置与内存属性
页表条目(PTE)不仅包含物理页帧号,还定义了内存区域的属性,这对缓存和缓冲行为有决定性影响:
- C位(Cachable)和B位(Bufferable):
C=1, B=1:Write-Back模式。写入操作先到D-Cache,仅当缓存行被替换时才写回内存。性能最高。C=1, B=0:Write-Through模式。写入同时更新D-Cache和内存。保证一致性,性能稍低。C=0, B=1:Non-cachable but Bufferable。数据不缓存,但可进入写缓冲区。适用于外设寄存器映射的内存区域(如UART的TX寄存器),写入可以缓冲以提高效率。C=0, B=0:Strongly-ordered。数据不缓存也不缓冲,访问严格按程序顺序执行。用于必须立即生效的硬件控制寄存器。
实操心得:在配置AM1806的内存映射时,需要特别注意不同内存区域的
C/B位设置。例如:
- 片上RAM(0x8000 0000):通常设置为
C=1,B=1(Write-Back),以获得最佳性能。- DDR2 SDRAM(0xC000 0000):同样设置为
C=1,B=1。- 外设寄存器区域(如0x01C0 0000开始的CFG空间):必须设置为
C=0, B=0或C=0, B=1。绝对不能设置为可缓存,否则对寄存器的读写可能被缓存或合并,导致无法正确控制硬件,引发难以调试的时序错误。- EMIFA/NOR Flash:如果从XIP(就地执行),则指令区域
C=1,B=0,数据区域C=0,B=1。如果只是存储数据,则C=0,B=1。
4. 缓存与写缓冲区机制详解
缓存是弥补CPU与主存速度差距的关键。ARM926EJ-S的16KB I-Cache和D-Cache采用4路组相联结构,行大小为8字(32字节)。
4.1 缓存工作模式与维护
- I-Cache:通常是只读的,工作在“直读”模式。当CPU取指未命中时,缓存控制器会从内存读取一整行(32字节)填充缓存。
- D-Cache:支持两种策略,由MMU页表项的
C/B位控制:- Write-Through:写命中时,同时更新缓存和主存;写未命中时,可能采用“写分配”或“写不分配”策略(通常由CP15配置)。
- Write-Back:写命中时,只更新缓存,并将该行标记为“脏”(Dirty);写未命中时,通常采用“写分配”策略,即先加载整行到缓存再修改。当脏行需要被替换时,才写回主存。
缓存一致性维护是嵌入式开发中的难点。在以下场景,必须手动管理缓存:
- DMA操作:当外设(如EDMA3)直接与内存交换数据,而该内存区域是可缓存的时,就会出现问题。CPU可能只在缓存中有数据的最新副本,而DMA从内存读到的是旧数据;或者DMA将新数据写入内存,但CPU缓存中的是旧数据。解决方案是:在DMA读取前,清理(Clean)对应内存区域的D-Cache(将脏数据写回内存);在DMA写入后,无效化(Invalidate)对应区域的D-Cache,迫使CPU从内存重新读取。
- 自修改代码:如果程序修改了正在执行的指令(如JIT编译),需要先清理D-Cache(确保修改写回内存),然后无效化I-Cache,最后执行一条
ISB(指令同步屏障)指令。 - 启动初期:在使能缓存前,必须无效化整个I-Cache和D-Cache。
CP15提供了丰富的缓存维护指令(通过c7寄存器操作),可以按虚拟地址(VA)或组/路索引来清理和无效化缓存,非常灵活。
4.2 写缓冲区策略
写缓冲区对于提升写性能至关重要。它像一个先进先出的队列,CPU可以快速将写数据提交给它,然后继续执行,由写缓冲区负责在后台完成对内存或外设的实际写入。
- 对于可缓存、可缓冲的写回区域:写操作先进入D-Cache。当缓存行被替换时,整行数据(如果脏了)才进入写缓冲区,最终写回内存。
- 对于不可缓存但可缓冲的区域(如某些外设):每次写操作直接进入写缓冲区。
- 对于强序区域:写操作绕过写缓冲区,直接访问总线。
常见问题排查:如果你发现对某个外设寄存器的写入操作似乎“延迟”了或者顺序不对,首先要检查该内存区域的
B位是否被错误地设为0(强序),或者该外设总线本身是否不支持写缓冲。在某些对时序极其敏感的操作中(如先写命令寄存器再写数据寄存器),可能需要在两次写操作之间插入DSB(数据同步屏障)指令,强制清空写缓冲区,确保前一次写完成后再进行下一次。
5. AM1806系统互联与内存空间规划
AM1806是一个复杂的SoC,ARM926EJ-S作为主控核心,需要通过高效、有序的互联结构与众多外设和存储资源通信。其核心是交换中心资源(SCR)网络。
5.1 系统互联矩阵解析
AM1806的互联结构是一种多层的交叉开关(Crossbar)网络,允许多个主设备(Master)并发地访问不同的从设备(Slave),极大提升了系统整体带宽并降低了延迟。
- 主设备(Master):能够主动发起读写交易的模块。包括:ARM(通过I/D AHB)、两个EDMA3控制器(共3个传输控制器TC)、HPI、LCD控制器、PRU子系统、USB2.0、uPP、VPIF等。
- 从设备(Slave):被动响应读写请求的模块。主要是各种存储器和外设寄存器,如:ARM内部RAM/ROM、128KB共享RAM、DDR2控制器、EMIFA接口以及所有外设(如UART、SPI、I2C等)的配置寄存器组。
表:AM1806主要主设备访问权限简表(基于典型配置)
| 主设备 | ARM RAM/ROM | 128KB RAM | DDR2 | EMIFA | 外设组 (CFG) |
|---|---|---|---|---|---|
| ARM (I/D) | ✓ | ✓ | ✓ | ✓ | ✓ |
| EDMA3_0_TC0/TC1 | ✗ | ✓ | ✓ | ✓ | ✓ |
| EDMA3_1_TC0 | ✗ | ✓ | ✓ | ✓ | ✓ |
| PRU0/PRU1 | ✓ | ✓ | ✓ | ✓ | ✓ |
| HPI | ✗ | ✓ | ✓ | ✓ | ✓ (部分) |
| USB2.0, uPP, VPIF | ✗ | ✓ | ✓ | ✗ | ✗ |
| LCDC | ✗ | ✓ | ✓ | ✗ | ✗ |
从上表可以看出:
- ARM内部RAM/ROM是私有的,只有ARM核心和PRU可以访问,这为Bootloader和关键中断向量表提供了安全空间。
- 128KB片上RAM和DDR2是共享资源,几乎所有主设备都能访问,是数据交换的主要舞台。
- EDMA3是数据搬运的引擎,它可以高效地在内存与外设间、内存与内存间传输数据,解放CPU。
- 某些高速外设(如USB、uPP)的DMA只能访问共享RAM和DDR2,不能直接访问低速的EMIFA或外设配置总线,这符合其高带宽特性。
5.2 统一内存映射与地址空间
AM1806采用统一的内存映射,即从所有主设备的视角看,系统中每个物理资源(内存、外设)都出现在一个固定的、统一的地址范围内。这极大简化了软件编程。
关键内存区域划分(举例):
0x0000 0000 - 0x01BF FFFF: 通常未使用或保留。注意:ARM的VINITHI复位后为1,向量表在0xFFFF0000,而非0x00000000。0x01C0 0000 - 0x01FF FFFF:外设配置空间(CFG)。所有外设(如UART、SPI、Timer、PLL等)的寄存器都映射到这个区域。访问这些地址就是读写外设寄存器。0x8000 0000 - 0x8001 FFFF:ARM内部8KB RAM。通常用于存放异常向量表和关键的栈或数据。0x8002 0000 - 0x8003 FFFF:ARM内部64KB ROM。可能存放TI的Bootloader或安全启动代码。0x8000 0000 - 0x8001 FFFF(另一视图):128KB共享片上RAM。这是一个独立的物理内存,地址可能与ARM内部RAM重叠或相邻,具体需查数据手册。它是系统中性能最高的内存,常被用作关键数据缓冲区或实时任务栈。0xC000 0000 - 0xDFFF FFFF:DDR2/mDDR SDRAM控制器映射区域。这是主程序运行和数据存储的主要区域。0xE000 0000 - 0xE00F FFFF:EMIFA接口映射区域。连接外部NOR Flash、NAND Flash或异步SRAM。
配置要点:在编写链接脚本(Linker Script)时,必须根据这个内存映射来规划代码和数据的存放位置。例如,
.text(代码段)通常放在DDR2中;.data(初始化数据)和.bss(未初始化数据)也放在DDR2;而中断栈或需要极快访问的数据可以放到128KB共享RAM中。Bootloader的初始阶段代码可能需要链接到内部RAM执行。
6. 内存保护单元(MPU)的应用与配置
在复杂的多主设备系统中,防止一个失控的主设备(如某个DMA引擎或协处理器)错误地覆盖关键内存区域至关重要。AM1806集成了两个MPU:MPU1保护128KB共享RAM,MPU2保护DDR2区域。
6.1 MPU工作原理
MPU不像MMU那样使用页表,它通过一组可编程的地址范围寄存器和属性寄存器来工作。每个范围可以设置:
- 起始地址和结束地址:定义受保护的内存区域。
- 允许的访问者ID(AID):AM1806的每个主设备都有一个固定的“特权ID”(见输入材料表5-3)。例如,ARM的ID是0,HPI的ID是3。在MPPA寄存器中,可以为每个范围设置一个位图,指定哪些ID可以访问。
- 访问权限:针对“超级用户”和“用户”模式,独立设置读(R)、写(W)、执行(X)权限。
当一个主设备发起访问时,MPU会检查地址落在哪个(或哪些)范围内,然后检查该主设备的ID是否在允许列表中,最后检查访问类型是否符合权限。任何一步失败,都会触发保护错误中断(MPU_PROT_ERR_INT)或地址错误中断(MPU_ADDR_ERR_INT),并记录违规的详细信息(地址、ID、操作类型)。
6.2 实战配置示例
假设在一个系统中,我们需要保护一段位于DDR2中、用于关键通信数据的区域(0xC1000000 - 0xC100FFFF),只允许ARM核心(ID=0)和EDMA3_0_TC0(ID继承自其CC)进行读写,不允许任何设备执行代码,且不允许HPI(ID=3)访问。
配置MPU2(保护DDR2)的步骤可能如下:
- 选择一个可编程范围(例如范围0)。
- 设置范围起始地址寄存器(
MPSAR0)为0xC1000000。 - 设置范围结束地址寄存器(
MPEAR0)为0xC100FFFF。 - 配置内存保护页属性寄存器(
MPPA0):AID0位(对应ARM)置1。AID?位(对应EDMA3_0_CC0/TC0,需查手册确定具体位)置1。AID3位(对应HPI)清0。SR(超级用户读)和SW(超级用户写)置1。SX(超级用户执行)、UR、UW、UX全部清0。
- 使能MPU2。
这样,如果HPI试图访问该区域,或者任何设备试图从该区域取指,MPU都会立即产生中断,系统可以在中断服务程序中记录错误、复位外设或采取其他安全措施。
重要提示:MPU的配置通常在系统初始化早期、使能任何DMA引擎之前完成。它是对系统稳定性的最后一道硬件防线。在调试DMA相关的内存覆盖问题时,如果怀疑是非法访问,配置MPU来监控可疑区域是非常有效的定位手段。同时,要留意MPU的粒度(MPU1为1KB,MPU2为64KB),确保保护范围对齐。