1. 项目概述与核心挑战
在嵌入式系统开发中,处理器与外部存储设备的接口设计往往是决定系统稳定性和性能的关键一环。尤其是在面对像Compact Flash(CF)卡这类遵循PCMCIA/ATA标准,但时序要求又相对特殊的设备时,如何利用处理器内置的通用内存控制器实现精准的时序控制,就成了一个既考验硬件功底又考验软件配置能力的经典问题。
我最近在为一个基于MPC8560 PowerQUICC III处理器的工业控制平台设计Compact Flash存储接口时,就完整地走了一遍这个流程。MPC8560的本地总线(Local Bus)控制器功能强大,特别是其用户可编程状态机(UPM),为生成复杂的、非标准的控制时序提供了可能。但官方文档往往只给出寄存器配置的推荐值,对于背后的设计逻辑、参数计算的来龙去脉,以及实际调试中可能遇到的坑,却着墨不多。这篇文章,我就结合自己的实战经验,把MPC8560通过UPM驱动Compact Flash卡的完整过程拆解清楚,从硬件连接到寄存器配置,再到UPM RAM阵列的编程,最后分享几个关键的调试技巧和设计变体思路。
这个设计的核心目标是:在MPC8560的66 MHz本地总线上,以PC Card ATA内存模式、8位数据总线的方式,稳定可靠地访问Compact Flash卡。这不仅仅是把线连上、把寄存器配好那么简单,你需要深入理解CF卡的时序规范,并将其“翻译”成UPM能够理解和执行的状态序列,同时还要处理好字节序、信号映射等细节问题。
2. 硬件连接与信号映射解析
硬件连接是这一切的基础,连接错了,后续的软件配置都是空中楼阁。我们的设计基于一个明确的系统需求:不需要热插拔支持,使用8位数据接口。这简化了设计,省去了隔离缓冲器。
2.1 核心信号连接方案
首先看关键的控制和状态信号。CF卡的-CE1(片选1)直接连接到MPC8560的LCS2(本地总线片选2)引脚。-OE(输出使能)和-WE(写使能)则分别由UPM控制的通用引脚LGPL1和LGPL2来驱动。这里选择UPM来控制-OE和-WE,而不是用固定的LWE或LOE信号,是因为CF卡的读写时序不对称,且对建立、保持时间有特定要求,UPM可以更灵活地控制这些信号的边沿。
-REG信号用于区分属性内存和公共内存访问,我们将其连接到锁存器输出的地址线A16(对应锁存前的LAD[16])。当-REG=0时,访问属性内存(8位,仅偶地址);-REG=1时,访问公共内存(8位或16位)。由于我们采用8位模式,-CE2引脚直接上拉至高电平,将其禁用。
地址总线A[10:0]通过一片74LVTH373之类的锁存器与MPC8560的LAD[0:31]相连,由LALE(地址锁存使能)信号控制锁存时机。这是本地总线复用地址/数据架构的标准做法。
几个状态信号通过GPIO管理:RESET连接到一个GPIO,用于对CF卡进行上电或软件复位;-CD1和-CD2(卡检测)也连接到GPIO,可以通过轮询或配置为中断输入来检测卡是否插入;READY信号同样连接GPIO,用于在复位后查询卡是否就绪。虽然我们使用的SanDisk卡不输出-WAIT信号,但为了兼容其他可能支持此信号的CF卡,仍将-WAIT引脚连接到LGPL4/UPWAIT,并在UPM中使能等待功能。
注意:电源与电平兼容性:MPC8560的I/O环电压为3.3V,与CF卡的3.3V操作模式完全兼容。因此,必须将CF卡的
-VS1引脚接地,以选择3.3V操作模式。VCC和GND的连接务必稳定可靠,CF卡在读写瞬间电流可能较大,电源走线要足够宽,并就近放置去耦电容。
2.2 字节序与数据线映射的“坑”
这是连接阶段最容易出错的地方。MPC8560是**大端(Big-Endian)处理器,而CF卡遵循PC架构,是小端(Little-Endian)**设备。这意味着对于16位数据(0x1234),MPC8560认为高位字节0x12存储在低地址,而CF卡认为低位字节0x34存储在低地址。
在我们的8位接口设计中,问题相对简单,因为我们只使用数据总线D[7:0]。我们将其直接连接到MPC8560的LAD[0:7]。但你必须清醒地认识到,当软件读取一个16位变量(例如CF卡中的某个配置字)时,它实际上是通过两次8位访问完成的。处理器内核看到的数据字节顺序,需要软件(驱动程序)在必要时进行交换,以符合小端格式的理解。例如,从CF卡属性内存读出的两个字节[0x78, 0x56](地址递增),在MPC8560的内存中可能被存储为[0x78, 0x56](大端视图),但实际表示的值应该是0x5678(小端解释)。驱动程序需要在读取后手动交换这两个字节。
如果未来扩展到16位模式,字节序问题会更复杂。通常有两种连接方案:“地址不变”和“数据不变”。“地址不变”方案保持地址线一一对应,但需要软件负责所有16位数据的字节交换。“数据不变”方案通过交叉连接数据线(例如将CF卡的D[15:8]接LAD[0:7],D[7:0]接LAD[8:15]),使得处理器看到的数据字节顺序与CF卡内部一致,但代价是8位寄存器访问(任务文件)变得麻烦,需要屏蔽高字节。在项目初期就必须根据软件协议栈的兼容性做出选择。
3. 关键寄存器配置详解
硬件连接妥当后,就需要通过配置MPC8560的内存控制器寄存器,来定义LCS2这片“地址领地”的访问规则。这就像给一片新划分的疆域制定法律。
3.1 基址与选项寄存器(BR2/OR2):划定地盘
BR2(Base Register 2)和OR2(Option Register 2)共同定义了LCS2片选所响应的地址范围及其基本属性。
BR2寄存器关键位:
- BA (Bit 0-16): 基地址。这决定了你的CF卡映射到处理器地址空间的哪个位置。例如,设置为
0xF000_0000,那么所有对该地址区域的访问都会激活LCS2。 - PS (Bit 19-20): 端口大小。设置为
01,代表8位端口,这与我们的硬件设计匹配。 - MSEL (Bit 24-25): 机器选择。这是关键!必须设置为
100,表示该片选区域由UPMA(用户可编程状态机A)来控制其访问时序。这是我们能自定义时序的前提。 - V (Bit 31): 有效位。只有在配置完
BR2、OR2以及UPM相关寄存器后,才能将此位置1,否则访问不会生效。
OR2寄存器关键位:
- AM (Bit 0-16): 地址掩码。与
BR2的BA配合,决定地址范围的大小。例如,AM设置为0xFFF0_0000(掩码高20位),BA设置为0xF000_0000,那么LCS2将响应0xF000_0000到0xF00F_FFFF的地址访问,共1MB空间。对于CF卡,这个空间通常足够了。 - BMT (Bit 16-23): 总线监视超时。设置为一个足够大的值,例如
0xFF,表示超时时间为BMT × 8个时钟周期。这用于防止总线挂死,如果一次访问在指定周期内没有完成(例如CF卡未响应),控制器将终止访问并报告错误。对于慢速设备,这个值要设大。
3.2 本地总线全局配置(LBCR/LCRR):设定交通规则
LBCR和LCRR寄存器配置影响整个本地总线上的所有设备,配置时需统筹考虑。
LBCR (Local Bus Configuration Register):
- LDIS (Bit 0): 必须为0,启用本地总线控制器。
- LPBSE (Bit 14): 必须为0。此位控制
LGPL4/UPWAIT引脚是否用于奇偶校验。我们将其用作UPM的等待输入(UPWAIT),因此必须禁用奇偶校验功能。 - BI (Bit 23): 建议设置为1,禁止突发访问。CF卡不支持突发传输,所有访问都应作为单次访问执行。
LCRR (Local Bus Clock Ratio Register):
- CLKDIV (Bit 28-31):本地总线时钟分频器。这是计算所有时序的基准。示例中系统时钟(SYSCLK)为266 MHz,目标本地总线时钟(LCLK)为66 MHz。因此,
CLKDIV = 266 / 66 ≈ 4,设置为0b0100。务必确认你的实际系统时钟,这个值不对,所有基于时钟周期的计算都会出错。一个本地总线时钟周期T = 1 / (SYSCLK / CLKDIV)。例如,266 MHz / 4 = 66.5 MHz,周期约为15 ns。 - EADC (Bit 14-15): 外部地址延迟周期。对于CF卡访问,建议设置为
01(1个周期延迟)。这控制了地址在LALE有效后多久才在总线上有效,有助于满足CF卡的地址建立时间要求。
3.3 UPM模式寄存器(MAMR):启动状态机引擎
MAMR寄存器配置UPM-A的工作模式。
- RFEN (Bit 1): 设置为0。CF卡不需要刷新操作。
- UWPL (Bit 4): 等待信号极性。如果CF卡的
-WAIT信号被连接(我们连接了),且其为低电平有效,则此位应设置为1。 - GPL4 (Bit 13): 关键位!必须设置为1。这将
LGPL4引脚的功能强制设置为UPWAIT输入,允许UPM根据CF卡发出的等待信号来延长访问周期。 - RLF/WLF (Bit 14-21): 读/写循环字段。它们定义了UPM在完成一次读或写操作后,返回到空闲状态(状态0)前需要插入的额外时钟周期数。初始可以设置为
0101(5个周期),后续可以根据实际波形调整,用于满足CF卡读写周期时间tRC和tWC的要求。
4. UPM RAM阵列编程:绘制时序波形图
这是整个设计的精髓,也是最考验功力的部分。UPM本质上是一个可编程的时序状态机,其行为完全由我们写入到一段64x32位的RAM阵列中的内容决定。我们需要根据CF卡的时序参数,计算出每个控制信号(LGPL1->-OE,LGPL2->-WE,LCS2->-CE1)在每个时钟周期(状态)应该是什么电平。
4.1 从时序表到时钟周期数
首先,要仔细研读CF卡的数据手册,找到关键时序参数。以常见的250 ns版本SanDisk卡为例,我们关注最苛刻的几组参数:
读操作关键参数(以较慢的属性内存为例):
tRC(读周期时间): Min = 300 nstACC(地址访问时间): Max = 300 nstOE(输出使能访问时间): Max = 150 nstOH(输出保持时间): Min = 20 ns (地址保持时间)
写操作关键参数:
tWC(写周期时间): Min = 250 nstWP(写脉冲宽度): Min = 150 nstDS(数据建立时间): Min = 80 nstDH(数据保持时间): Min = 30 ns
我们的本地总线时钟周期T = 15 ns。接下来,将时间要求转换为时钟周期数(向上取整):
- 读周期最少需要
300 ns / 15 ns = 20个周期。 - 写周期最少需要
250 ns / 15 ns ≈ 16.67,取整为17个周期。 - 写脉冲宽度最少需要
150 ns / 15 ns = 10个周期。 - 输出使能访问时间最大
150 ns / 15 ns = 10个周期,意味着在-OE有效后,最多10个周期内数据必须有效。我们设计时,通常在-OE有效后再等待足够周期(如3-4个周期)去采样数据,这个“足够”要小于10周期。
4.2 构建UPM状态序列
UPM的状态由RAM数组中的“字”(Word)定义,每个字对应一个时钟周期。每个字的32个位控制着不同的信号和行为。我们需要设计两个序列:读单次请求(RSS)和写单次请求(WSS)。
读序列(RSS)设计思路:
- 状态0 (Idle): 所有控制信号无效(高电平)。
LCS2(-CE1)为高,LGPL1(-OE)为高,LGPL2(-WE)为高。 - 启动访问(状态1-2): 在状态1,UPM根据访问类型(读/写)跳转到相应子序列。对于读访问,它跳转到RSS序列的起始字(例如字0)。在状态2,我们通常先拉低
LCS2(片选有效),并输出有效地址(通过LAD和LALE)。 - 建立时间(状态3-4): 保持
LCS2有效,但-OE仍为高。这满足了地址和片选相对于-OE的建立时间tSU要求。可能需要持续2-3个周期(30-45 ns > 规范要求)。 - 发出读命令(状态5-...): 拉低
LGPL1(-OE有效)。同时,UPM会在这个周期将内部数据总线方向切换为输入。 - 数据采样与等待(状态...): 保持
-OE有效,并等待足够的周期以满足tACC和tOE。可以在状态6或7设置WAEN位为1,使能等待功能。如果CF卡拉低了UPWAIT(-WAIT),UPM会暂停在当前状态,直到UPWAIT变高。这对于兼容不同速度的卡非常有用。 - 结束读操作(状态N-1, N): 在采样到数据后(通常由UPM自动在
-OE撤销前的某个周期采样),先拉高-OE,再经过一个或多个周期(满足保持时间tOH)后拉高LCS2。最后,通过LOOP和LAST位控制状态机返回到空闲状态0。
写序列(WSS)设计思路类似,但核心是控制-WE(LGPL2)的宽度和与数据、地址的时序关系:
- 同样从空闲状态开始,跳转到WSS序列。
- 先拉低
LCS2,输出地址和数据(UPM将数据驱动到LAD上)。 - 在满足地址和数据相对于
-WE的建立时间tSU后,拉低-WE。 - 保持
-WE低电平至少10个周期(150 ns)。 - 拉高
-WE,再保持数据和地址一段时间(满足保持时间tDH和tAH),最后拉高LCS2。
4.3 寄存器配置与RAM数组示例
根据上述分析和官方文档的参考,以下是一组可工作的配置示例。请注意,实际值需根据你的具体时钟和CF卡型号微调。
/* 假设LBCR、LCRR已按前文配置好 */ /* 配置BR2和OR2,将CF卡映射到地址0xF0000000开始,大小为1MB (0xFFF00000) */ /* BR2 - Base Register 2 for LCS2 */ MEMORY_BR2 = 0xF0000001; /* BA=0xF0000000, PS=8-bit, MSEL=UPM, V=1 */ /* OR2 - Option Register 2 for LCS2 */ MEMORY_OR2 = 0xFFF00000; /* AM=0xFFF00000 (1MB mask), BMT=0xFF */ /* 配置MAMR - UPM Mode Register */ MAMR = 0x0000A033; /* GPL4=1 (UPWAIT input), UWPL=1 (active low), RLF=5, WLF=5, DS=1 */ /* 编程UPMA RAM数组 - 读单次请求 (RSS) 序列 */ /* 每个UPM命令字控制一个时钟周期的行为。这里是一个简化的示例,实际有64个字。*/ /* 索引 0-7: RSS 序列 */ upm_ram[0] = 0x0FF0F800; /* State 0: Idle, all signals high, goto state 1 */ upm_ram[1] = 0x0FF0F800; /* State 1: Decode, goto RSS start (word 0) or WSS start */ upm_ram[2] = 0x0C3CF800; /* State 2: Assert LCS2 (-CE1), address out */ upm_ram[3] = 0x0C3CF800; /* State 3: Hold address and LCS2, wait setup */ upm_ram[4] = 0x0C3CF800; /* State 4: Continue hold */ upm_ram[5] = 0x0C3CF400; /* State 5: Assert LGPL1 (-OE), start data input */ upm_ram[6] = 0x0C3CF400 | (1<<21); /* State 6: Keep -OE low, WAEN=1 (enable wait) */ upm_ram[7] = 0x0FF0F800; /* State 7: Deassert -OE, then deassert LCS2, loop to state 0 */ /* 编程UPMA RAM数组 - 写单次请求 (WSS) 序列 */ /* 索引 32-39: WSS 序列 (假设从字32开始) */ upm_ram[32] = 0x0FF0F800; /* State 0: Idle */ upm_ram[33] = 0x0FF0F800; /* State 1: Decode */ upm_ram[34] = 0x0C3CF800; /* State 2: Assert LCS2, address out, data out */ upm_ram[35] = 0x0C3CF800; /* State 3: Hold */ upm_ram[36] = 0x0C3CF200; /* State 4: Assert LGPL2 (-WE) */ upm_ram[37] = 0x0C3CF200; /* State 5: Hold -WE low */ upm_ram[38] = 0x0C3CF200; /* State 6: Hold -WE low (meet tWP) */ upm_ram[39] = 0x0FF0F800; /* State 7: Deassert -WE, then deassert LCS2, loop to state 0 */实操心得:UPM编程工具:飞思卡尔(现恩智浦)曾提供一个名为“UPM Programming Tool”的Excel表格或小软件,它可以图形化地设计时序波形,并自动生成UPM RAM数组的十六进制值。虽然现在可能很难找到官方下载,但其设计思路极具参考价值。你可以自己画一个类似的时序图,横轴是时钟周期(状态),纵轴是每个信号(
LCS2,LGPL1,LGPL2,LALE, 数据方向等),然后根据CF卡时序规范填充每个格子(高/低/无关),最后将这个表格“翻译”成UPM命令字。这个过程虽然繁琐,但能让你彻底理解UPM的工作原理。
5. 设计变体与优化思路
上述8位内存模式设计是一个基础且可靠的方案。但在实际项目中,你可能需要更高的性能或不同的功能,以下是一些可行的变体:
5.1 16位数据总线接口
如果追求更高的数据吞吐率,可以采用16位模式。硬件上,需要将CF卡的D[15:0]全部连接到处理器的LAD[0:15](注意字节序映射问题),并将-CE1和-CE2短接,共同由一个片选(如LCS2)控制。此时,公共内存的数据将在16位总线上传输,而属性内存访问仍只使用D[7:0]。关键点:CF卡要求16位访问时地址A0必须为0(即字对齐访问)。在UPM编程时,需要确保16位访问的地址线A0输出为低。
5.2 双UPM优化时序
在8位或16位模式下,属性内存的访问时序(尤其是读周期tRC)通常比公共内存慢。如果使用同一个UPM序列,为了兼容更慢的属性内存,公共内存的访问速度会被拖慢。
一个高级的优化方案是使用两个UPM(例如UPMA和UPMB)。将属性内存和公共内存映射到两个不同的、但地址范围有重叠的片选上(例如,都用LCS2的地址范围,但通过-REG信号区分)。然后:
- 将一个UPM(如UPMA)编程为满足属性内存的慢速时序,其输出的
-OE/-WE控制信号连接到CF卡。 - 将另一个UPM(如UPMB)编程为满足公共内存的快速时序。
- 将
-CE1连接到一个独立的LGPLx引脚(如LGPL5),并由两个UPM共同控制。当访问属性内存地址时(-REG=0),由UPMA驱动LGPL5(-CE1)为低,并输出其慢速的-OE/-WE波形。当访问公共内存地址时(-REG=1),由UPMB驱动LCS2(-CE2,此时与-CE1短接)为低,并输出其快速的-OE/-WE波形。
这种方法实现了时序的最优化,但硬件连接和UPM编程更为复杂,需要仔细处理两个UPM对同一组控制信号的协同。
5.3 True IDE模式
如果你设计的系统需要完全模拟标准的IDE硬盘接口,可以使用True IDE模式。此模式下,CF卡的-ATA_SEL(Pin 9)需要在复位期间接地。接口信号变为-CS0、-CS1、-IORD、-IOWR等。此时,可以将任务文件寄存器访问(-CS0)和数据寄存器访问(-CS1)分别映射到两个不同的UPM和地址空间。True IDE模式通常能获得更好的兼容性(尤其与现成的IDE驱动),且支持更快的PIO模式。
6. 调试技巧与常见问题排查
理论配置完成后,真正的挑战在于调试。没有逻辑分析仪,调试UPM时序如同盲人摸象。
必备工具:逻辑分析仪。你需要一个至少4通道(推荐8通道以上)的逻辑分析仪,抓取LCLK、LCS2、LGPL1(-OE)、LGPL2(-WE)、LALE以及一根数据线LDAT0和地址线LAD16(-REG)的波形。
调试步骤与常见问题:
初始化失败,无法进入UPM模式:
- 检查:
BR2的MSEL位是否设置为100(UPM)?BR2和OR2的V位是否在最后才置1?LBCR的LDIS是否为0? - 检查:UPM RAM数组是否已正确写入到MPC8560的UPM RAM区域?写入后是否需要执行特定的同步指令(如
isync)?
- 检查:
读操作失败,读回全0或全F:
- 看波形:重点看
-OE信号。它是否在LCS2有效并经过足够建立时间后才变低?低电平持续时间是否足够长(满足tOE)?在-OE撤销前,数据总线LDAT[0:7]上是否有数据变化? - 检查地址:
LALE锁存时刻的地址LAD[0:31]是否正确?锁存后的地址线A[10:0]和-REG是否稳定? - 检查字节序:如果你读的是16位数据,是否发生了字节交换?尝试交换高低字节后再解读。
- 检查
UPWAIT:如果CF卡支持-WAIT,测量LGPL4引脚。UPM是否因等待信号一直为低而卡在某个状态?检查MAMR中UWPL极性设置是否正确。
- 看波形:重点看
写操作失败,数据未被写入:
- 看波形:重点看
-WE信号。其低电平脉冲宽度tWP是否足够(通常需150ns以上)?数据LDAT[0:7]在-WE上升沿之前是否已建立足够长时间(tDS)?在-WE上升沿之后是否保持足够时间(tDH)? - 检查
-OE干扰:在写周期内,-OE是否始终保持为高?意外的-OE低电平会导致总线冲突。
- 看波形:重点看
性能不达标或间歇性错误:
- 检查时序余量:用逻辑分析仪测量所有关键参数(如
tRC,tACC,tWP,tDS等),并与CF卡数据手册中的最小值/最大值对比,确保有足够的余量(建议20%以上)。特别是在高低温环境下。 - 调整UPM序列:如果
tRC或tWC不满足,增加UPM序列中的等待状态(通过LOOP指令或增加状态数)。如果建立/保持时间不足,调整地址、数据有效与-OE/-WE边沿的相对位置。 - 检查电源和信号完整性:CF卡插座接触是否良好?电源电压在负载下是否稳定?信号线上是否有过冲或振铃?必要时在数据线和地址线上串联小电阻(如22欧姆)阻尼。
- 检查时序余量:用逻辑分析仪测量所有关键参数(如
一个宝贵的经验:在UPM RAM编程时,为读和写序列的末尾多增加几个“空状态”(所有信号无效),再跳转回空闲状态。这相当于在两次访问之间插入空闲周期,可以避免因UPM状态机切换过快而导致的时序违例,系统稳定性会大大增强。这可以通过设置MAMR中的DS(Disable timer)字段或直接在RAM序列中插入额外的WAIT状态来实现。
最后,驱动程序在访问CF卡时,务必先通过GPIO检查-CD1和-CD2确认卡已插入,并在发送任何命令前,先通过GPIO控制RESET引脚对卡进行复位,并轮询READY信号直到卡就绪。这些看似简单的步骤,是保证系统可靠性的基石。