简介:面向基于ARM7TDMI内核的AT91M40800嵌入式开发者,压缩包聚焦外部总线接口EBI的驱动配置,解决外部存储器(SRAM、DRAM、闪存等)接入时的时序与信号控制问题。资源共3个文件,以inc与h源码文件为主,另含txt说明文档,整体约2KB,体量精简,适合快速查阅关键实现。已有100人学习下载。内容围绕EBI控制器展开,可看到初始化、读写周期、地址映射、数据传输与错误处理等典型代码片段,宏定义方式也有助理解寄存器访问封装。对于正在调试外部总线或准备在自研板卡上扩展存储器的工程师,这份压缩包能提供直接可参考的写法与排错思路,结合txt文档说明,可对AT91M40800的EBI使用形成较完整认识。
1. AT91M40800 的 EBI:一块老片子的外部总线为什么值得单独写一篇
做板级 bring-up 的人多半遇到过这种场景:手里一片 AT91M40800,核心频率不高、内部资源也紧,但板子上要挂一颗 NOR Flash 做启动,再接一片 SRAM 跑临时数据。功能听起来不复杂,真正把板子点亮才发现问题全出在一个叫 EBI 的外设上——外部总线接口。它不是简单地把地址线、数据线、读写信号拉出来就完事,片选怎么映射、读时序给几个等待周期、总线空闲算不算进访问周期,每一个参数都会让系统要么死在取指阶段、要么在高速读写时偶发错位。这篇就把 EBI 从寄存器配置到波形验证的完整路径拆开讲,适合正在做 AT91M40800 板级调试的开发者,也适合想把老方案里的 EBI 部分重新看一遍的工程师。
2. 拆解 AT91M40800 的 EBI:片选、地址映射和时序参数决定成败
EBI 的设计思想并不复杂:外部总线就是一条把 CPU 的存储访问转成对外部器件读写的通道,但 CPU 不知道外面挂的是 Flash 还是 SRAM,不知道对方需要等多久才能把数据放到总线上,更不知道两次访问之间要不要留喘息时间。这些信息全部通过寄存器告诉 EBI,每个片选对应一组配置,访问哪个地址区间就自动套用哪组参数。搞懂 EBI,先搞懂片选映射,再搞懂时序参数,最后才是写初始化代码。
2.1 片选与地址空间:EBI 怎么把 4 个 bank 分给 Flash 和 SRAM
AT91M40800 的 EBI 一般提供多个片选,常见的是 4 个,分别叫 NCS0 到 NCS3。每个片选对应一段连续的地址区间,CPU 访问这段地址时,对应的片选信号自动拉低,同时地址线上的地址、读写控制信号同步生效。这个机制看起来像片选译码,实际上的关键点在于:你不需要在电路里额外加译码器去判断地址范围,地址区间的划分已经在 EBI 内部完成。
不同型号在同一地址上的分配略有差异,但设计习惯通常是一致的:NCS0 挂启动用的 NOR Flash,因为在大多数启动模式下,CPU 复位后第一条指令就是从 NCS0 对应的地址取;NCS1 挂 SRAM 或者第二个 Flash;剩下的片选留给外设寄存器、CPLD 逻辑或者第二块 SRAM。实际板卡上最典型的分配是:
| 片选 | 典型外设 | 访问地址区间(示例) | 说明 |
|---|---|---|---|
| NCS0 | NOR Flash | 0x01000000 起 | 启动首选,BMS 或启动引脚决定复位后是否先访问这里 |
| NCS1 | SRAM | 0x02000000 起 | 数据变量、堆栈、临时缓冲区 |
| NCS2 | 外设寄存器 | 0x03000000 起 | 通过 CPLD 译码的各类外设 |
| NCS3 | 第二片 SRAM 或预留 | 0x04000000 起 | 少见,但存在 |
地址区间是否真的按这个顺序,要看具体芯片手册里的 EBI 映射图,但片选和地址区间一一对应这个原则在所有同类芯片里是一致的。调试时最容易犯的错不是选错片选,而是访问地址落在两个区间的边界上,比如本意访问 NCS1 的 SRAM,地址算到了 NCS2 的区间,结果总线行为看起来像设备不存在。
地址线参与译码的方式也要注意。区分片选靠的是高位地址,低位地址全部送出去给外部器件寻址。所以外挂 Flash 容量不用太大时,多余的高位地址线会变成未连接状态,这时候访问同一个物理地址会有多个镜像地址,写的时候没问题,读的时候也察觉不到,但如果软件里用指针扫描存储器查找某个特征值,可能扫出多个重复区域,误判外设存在。
2.2 等待周期怎么选:默认值不是给你的
EBI 的每个片选寄存器里通常都有等待状态字段,一般分成建立等待周期和保持等待周期,再加上一个总线空闲周期。新手最容易犯的错误是直接拿复位后的默认值去访问外部设备,因为默认值往往是针对最保守的外部器件设计的,访问速度慢得离谱,但系统能跑。等到想优化性能,开始把等待周期往下压,就会发现系统在某个临界点突然崩溃,而且崩溃位置不固定。
等待周期的取值依据只有一个:外部器件的时序参数手册。以 SRAM 为例,手册里会给出地址建立到数据有效的最大时间,也就是 tAA,以及片选有效到数据有效的 tACE,读信号有效到数据有效还需要 tOE。用系统时钟周期去折算这些时间,再加上一点余量,就是等待周期至少应该设的值。比如时钟 40MHz,一个周期 25ns,某颗 SRAM 的 tAA 是 55ns,那 READ 等待状态至少 3 个周期,因为 2 个周期只有 50ns,不够;3 个周期是 75ns,留了 20ns 余量。
比等待周期更容易被忽略的是总线空闲周期。它表示一次总线访问结束到下一次访问开始之间,总线至少保持空闲的时钟数。外部器件在 CS 信号拉高之后,地址线、数据线通常不会立刻释放,甚至内部还在进行写数据的最后保持,如果紧接着发起下一次访问,数据线可能发生冲突。常见的做法是:读写速度越快的器件,空闲周期反而要给足,因为器件内部恢复时间不一定和访问速度成正比。另一个经验是:如果总线上挂了 CPLD 或者慢速逻辑器件,TDF 字段务必多给几个周期,这类器件的数据保持时间经常比 SRAM 还差。
2.3 EBI 寄存器配置顺序:先配片选,再使能外部总线
EBI 的寄存器不多,常见的是片选控制寄存器加一个总线空闲控制寄存器,有些型号还会有总线配置寄存器。配置顺序看起来无所谓,但实际调试中,有人一上来先开启某个总线的全局使能或者先改空闲周期,再去配片选,结果配置过程中总线处于半开状态,此时一旦发生取指或者中断访问,行为完全不可控。稳妥的顺序是先关闭不必要的中断,避免配置中途被打断;然后逐个配置片选寄存器的字段,包括数据总线宽度、等待周期、空闲周期;最后再让 EBI 的总线相关配置生效。
下面是一个典型的配置顺序参考:
| 步骤 | 配置对象 | 要确认的参数 | 失败时的现象 |
|---|---|---|---|
| 1 | 系统状态 | 关闭中断,避免配置期间跳进异常处理 | 配置中途被中断,寄存器写一半 |
| 2 | 片选寄存器组 | 数据宽度、等待周期、空闲周期 | 外部器件完全无法访问 |
| 3 | 总线空闲周期 | TDF 数值,匹配最慢器件 | 高速读写偶发错数据 |
| 4 | 启动相关配置 | BMS 或启动引脚对应的片选 | 复位后取指失败,调试器连不上 |
这个顺序背后有一条原则:任何一步操作,都不应该让 EBI 处于“自己还没有被完整描述,但已经被允许访问外部总线”的状态。先描述清楚,再开放访问,能避免绝大多数的稳定性和不可复现问题。
提示:片选寄存器里的数据总线宽度字段必须和实际硬件一致。内部数据总线是 16 位时,如果外设是 8 位,EBI 会在内部完成拆分;如果这里配置错误,会出现“数据全对但地址错位”的诡异现象,非常难排查。
3. 把 EBI 参考工程跑起来:最小初始化代码、回读测试和波形验证
很多参考工程解压出来就是一堆寄存器初始化的源码,看起来不长,但第一次移植到自己的板卡上,往往要改的地方比想象中多。原因在于 EBI 配置和硬件接线是强耦合的:片选挂哪个地址、数据线接 8 位还是 16 位、等待周期按什么时钟频率折算,都必须和原理图一致。三步走完 EBI 验证:先写出最小初始化,再做数据线回读测试,最后拿示波器看波形。
3.1 最小初始化代码:直接操作寄存器,不引入额外的封装层
拿到的参考工程里通常已经有一份寄存器基地址的头文件,直接基于它写裸机初始化就行。这里给一个通用度较高的写法,EBI 相关寄存器用宏定义占位,实际地址替换成目标芯片头文件里的名字即可。初始化目标是:NCS0 挂 16 位 NOR Flash,NCS1 挂 16 位 SRAM,外部总线时钟按 40MHz 折算,等待周期相对保守。
/* ebi_init.c: AT91M40800 EBI 最小初始化示例 */ #include "project_hw.h" /* 包含 EBI 寄存器基地址和字段位定义 */ #define EBI_BASE 0xFFE00000UL /* 视图基地址,按实际芯片头文件为准 */ #define CSR0 (*(volatile uint32_t *)(EBI_BASE + 0x00)) #define CSR1 (*(volatile uint32_t *)(EBI_BASE + 0x04)) #define BFCR (*(volatile uint32_t *)(EBI_BASE + 0x1C)) /* 字段宏示例:数据宽度、等待周期、空闲周期 --- 位偏移以手册为准 */ #define EBI_DBW_16 (0x3u << 0) /* 16位数据总线 */ #define EBI_WS_RD_SET (3u << 8) /* 读建立等待 3 个时钟 */ #define EBI_WS_WR_SET (3u << 12) /* 写建立等待 3 个时钟 */ #define EBI_TDF (2u << 28) /* 总线空闲周期 2 个时钟 */ void ebi_init(void) { /* 1. 配置 NCS0: NOR Flash, 16位总线, 3等待周期 */ CSR0 = EBI_DBW_16 | EBI_WS_RD_SET | EBI_WS_WR_SET | EBI_TDF; /* 2. 配置 NCS1: SRAM, 16位总线, 2等待周期, TDF=1 */ CSR1 = EBI_DBW_16 | (2u << 8) | (2u << 12) | (1u << 28); /* 3. 总线空闲周期全局配置, 取各片选中的最差值 */ BFCR = (2u << 0); /* 全局 TDF = 2 个时钟 */ }这段代码里面,CSR0和CSR1分别是片选 0 和片选 1 的控制寄存器。等待周期的字段分布在不同芯片上不一定相同,所以示例里用<<8、<<12这类占位方式,实际移植时务必对照芯片手册里的寄存器位定义去替换,不能直接抄字段值。第 3 步配置全局BFCR,它的作用是兜底:当某个片选自己的空闲周期配得比全局值小时,以全局值为准,这样可以防止后续新加的片选忘记配 TDF 而在总线上产生冲突。
参数方面,NOR Flash 的读等待给 3 个周期比较保守,适合验证阶段;SRAM 一般比 Flash 快,给 2 个周期。如果实际测量时发现数据稳定,可以逐步缩到 1 个周期,但建议不要把 TDF 减到 0,除非示波器上确认过两个连续访问之间没有任何总线交叠。
3.2 回读测试:写一个伪随机 pattern,验证每一条数据线和地址线
初始化完成后,别急着跑系统,先做一次裸机存储器验证。最忌讳的是只写一个固定值比如0xAA55然后读回来比对,固定值只能验证通路存在,无法发现数据线短路和高低字节交叉。常见做法是准备几个 pattern:全 0、全 1、0xAAAA、0x5555、单 bit 翻转、伪随机序列,逐步扫描目标地址区间。
/* ebi_test.c: SRAM 回读测试,定位数据线/地址线故障 */ #include <stdint.h> #include <stdio.h> #define SRAM_BASE 0x02000000UL /* NCS1 对应地址区间 */ #define TEST_SIZE 0x8000 /* 测试 32KB 区域 */ #define PATTERN 0xA55A5AA5UL /* 带 bit 翻转的稳定 pattern */ int memory_readback_test(void) { volatile uint32_t *mem = (volatile uint32_t *)SRAM_BASE; uint32_t failures = 0; uint32_t expect = PATTERN; /* 先全片写 pattern, 再全片读回, 避免边写边读干扰总线 */ for (uint32_t i = 0; i < TEST_SIZE / 4; i++) { mem[i] = expect; } for (uint32_t i = 0; i < TEST_SIZE / 4; i++) { uint32_t actual = mem[i]; if (actual != expect) { failures++; printf("FAIL addr=0x%08X expected=0x%08X actual=0x%08X\n", (uint32_t)(SRAM_BASE + i * 4), expect, actual); } } if (failures == 0) { printf("SRAM readback PASS, area=0x%08X size=0x%X\n", (uint32_t)SRAM_BASE, TEST_SIZE); } return (failures == 0) ? 0 : -1; }这个测试刻意采用“先写完、再整体读”的流程,原因在于逐地址写读会把写缓冲和读时序混在一起,一旦出错很难判断是写通路问题还是读通路问题。分开之后,写阶段出错表现为大量读回值异常且位置集中,而读阶段出错则表现为位置分散。注释里写0xA55A5AA5这类带交替 bit 的数值,是因为它能在 32 位数据线上暴露相邻数据线短路,比如 D0 和 D1 之间短路会让读回值出现特定的 bit 合并。
TEST_SIZE的大小应当根据实际 SRAM 容量调整,只需要覆盖最靠前的一部分就够做最基础的验证。如果测试中报错集中出现在偶数地址或奇数地址,那就是数据总线高低字节连接有问题,去核对原理图上数据线的编号;如果报错地址呈现 0x20000、0x40000 这样的倍差偏移,那是地址线错位,比如 A0 错接成 A1,这类问题在手工焊接的板卡上很常见。
3.3 波形验证:示波器上要看的不是“有没有波形”,而是时序差值
寄存器配置和回读测试都通过,只说明当前情况下能工作,不代表时序余量合理。EBI 这类接口最怕的是“刚好能跑”,因为温度一变、电压一降、器件批次一换就可能失效。示波器上需要确认的,是实际波形比器件手册要求的时序多出多少余量。
用示波器测量时抓住两个关键点:读访问时从片选拉低到数据线上出现有效数据的间隔,以及一次读结束到下一次片选拉低之间的空闲时间。前者对应等待周期是否足够,后者对应 TDF 是否设置了合理的空闲周期。建议测量时用一个循环读同一地址的程序反复跑,否则单次访问难以抓到稳定的波形。
| 测量项 | 示波器接法 | 期望结果 |
|---|---|---|
| 片选到数据有效 | CH1 接 NCS0,CH2 接 D0,触发在 NCS0 下降沿 | 数据稳定时间 > 器件手册最小 tAA |
| 写脉冲宽度 | CH1 接 NWE,参考 GND | 宽度 > 器件手册最小写脉冲 |
| 两次访问间隔 | CH1 接 NCS1,测两次下降沿间隔 | 大于手册要求的总线恢复时间 |
如果不方便抓数据线,也可以直接抓 NCS 信号的周期变化。实际调参中,波形确认后通常要做一次“临界扫描”:把等待周期从大到小逐步调整,每档都跑一遍回读测试,找到首次出现错误的那一档,再回退一到两个周期作为交付值。这个过程看着繁琐,但能一次性把电源波动和温度变化的余量提前预留出来。
4. EBI 踩坑记录:AT91M40800 外部总线调试中的 5 个真实问题
EBI 的问题有个共同特征:不是完全不能用,而是“大部分时候能用,特定条件下翻车”。下面这 5 个问题来自我调试不同板卡时遇到的典型情况,现象和原因都做了归纳,供参考。遇到类似现象时,按顺序排查通常能省下一个下午。
4.1 现象:复位后外部 Flash 首地址读回全 0xFF
上电后调试器直接连不上,用示波器看 CS0 根本没有拉低,或者拉低但数据线一直是 0xFF。原因多半是启动模式的引脚状态没对上:AT91M40800 的启动方式由外部引脚或配置决定,如果引脚被上下拉电阻定到了“从内部启动”或者“片选无效”的状态,外部 Flash 根本不会被访问。另一个常见原因是外部 Flash 本身处于保护状态,部分 NOR Flash 上电默认把所有扇区锁住,CPU 读第一个扇区会读到全 0xFF,表现和没有片选几乎一样。
解决方法是先确认启动引脚的电平和设计意图一致,再检查 Flash 的复位命令序列,部分器件需要通过特定的解锁命令把所有扇区打开。调试阶段建议先用编程器通过 JTAG 或专用接口把 Flash 内容读一遍,排除 Flash 自身的问题,再去查 EBI。
4.2 现象:16 位总线宽度下只有偶数地址数据正确
回读测试时发现奇数地址全错,或者 32 位 pattern 读回来变成两个 16 位杂凑值。这通常不是 EBI 寄存器的问题,而是硬件上高低字节控制信号没接对。16 位外部总线访问 8 位字节时,地址最低位用来区分高低字节,对应的字节选择信号必须正确连接到存储器件的 UB/LB 引脚,比如 BHE、BLE 这类引脚如果悬空,奇数地址访问永远落到同一个物理字节上。
排查时先量两个字节选择信号在访问奇数地址和偶数地址时电平是否发生变化。如果完全没有变化,优先怀疑引脚虚焊或者跳线接错。软件上也可以临时把片选寄存器改成 8 位总线宽度,如果改成 8 位后所有字节都能正确读回,基本断定是 16 位字节选择通道有硬件断线。
4.3 现象:CS1 上挂的 SRAM 能读不能写,读出来全是上一个写入的值
读操作正常,写操作不报错,但下次读回时数据不变。这类问题最隐蔽,因为总线读通路没问题,容易让人觉得 EBI 配置是好的。真正原因是写时序不满足 SRAM 的要求:写信号宽度不够,或者地址和数据在写信号结束前就撤掉了。部分 SRAM 对写周期的最小宽度有严格限制,如果 EBI 里写等待周期只给了 1 个时钟,外部 SRAM 根本来不及锁存数据。
解决方法是把写等待周期单独调大,不要只调读等待。EBI 在读和写上是分开配置的,很多人只调了读时序,写时序一直用默认值。把写等待从 1 加到 3,再用回读测试验证通常就能解决。这个坑的教训是:回读测试通过不等于写测试通过,验证程序里必须包含“先写特定值、再读回判断”的环节。
4.4 现象:提高系统时钟后数据偶发跳变,位置不固定
低频率下跑几个小时没问题,把主频调整到较高档位后,SRAM 回读测试开始出现跳变,有时是几百次访问里错一次,有时是某个地址频繁出错。这种偶发问题不是单一原因,两个因素叠加最常见:等待周期是按旧的时钟频率算的,频率提高后实际等待时间变短,略微低于器件手册要求;另外高频下 PCB 走线的信号质量下降,NCS 或 NWE 边缘出现抖动,在时序刚好卡在临界值时就会被放大。
处理手段分两步。第一步重新按新时钟频率折算等待周期和空闲周期换算,务必手工计算而不是沿用旧配置;第二步去检查数据线、片选线有没有串联端接电阻,长走线或未端接的走线在频率提高后反射会明显增强。软件上把等待周期再放余量,硬件上在片选信号和时钟信号上预留端接位置,是最稳妥的做法。
4.5 现象:热重启后外部总线访问失败,冷启动却正常
冷启动时一切正常,按复位键热启动后 EBI 访问外部设备失败,或者偶尔失败。这个现象看着像时序问题,根源往往在复位时序上:复位释放瞬间,CPU 立刻开始取指,但外部 Flash 片选信号和地址稳定需要一定时间,如果复位释放时 GPIO 或者 CPLD 尚未完成自身复位,EBI 在复位窗口内访问就可能没有响应。
常见的解决方向有两个。一是确认复位信号是否给了外部器件足够的复位保持时间,Flash 和 CPLD 的复位释放时间是否晚于 CPU 复位释放,必要时用 RC 延时或复位芯片实现延迟释放。另一个方向是检查软件里复位后是否立即执行了 EBI 初始化,如果外部 Flash 因为某种原因跑到了非默认波特率或进入了异步等待,热启动时需要重新初始化才能访问。实际调试中在复位后初始化末尾加一段延时,往往就能避开这个窗口。
5. 把 EBI 时序余量收一收:只跑通不算完工,验证三个边界才算
EBI 配置完毕,回读测试也通过,很多板子就在这一步直接交付了。但在量产或者长期运行的场景里,“能跑”和“可靠”之间还差三个边界验证。第一个是电压边界:外部存储器在电压偏高或偏低时,读写时间会变长,如果等待周期是按标称电压算的,电压跌落时就会出现偶发错误;用可调电源把板级电压从下限到上限扫一遍,每档跑一轮回读测试,记录首次失败电压点,把它和实际系统允许的电压范围做比较。第二个是温度边界:环境温度升高对信号边沿和器件时序都有影响,调试阶段可以在芯片附近用可控方式做升温验证,如果高温下原本的余量消失,说明等待周期和 TDF 需要再往上加。第三个是高速边界:把主频提到目标值甚至略高,连续跑随机 pattern 测试至少半小时,出现错误立即降低等待周期配置再测,直到找到分界点。
最后一个习惯值得一提:每次修改 EBI 参数后,把原来的等待周期数值和对应的波形截图记录下来。EBI 调试很容易陷入“改一个值、测一轮、没发现问题、再过几天又出现”的循环,没有基准记录的情况下任何变化都难以追踪。我自己的做法是在代码里放版本注释,同时用逻辑分析仪保存一份当前时序的波形文件,后续改动只基于这份波形做增量调整。这样反复折腾几次,整套时序参数的余量就能做到心里有数。希望帮到你。
本文还有配套的精品资源,点击获取