做DSP开发的人,迟早会撞上内存不够用的墙。28335虽然带浮点单元,可内部那块SARAM总共也就三十几K字,跑个FFT、存一组采集数据、塞个查找表,说满就满。这时候最顺手的解法就是走XINTF外扩SRAM。这个实验在普中DSP28335开发攻略里被放在第28章,看起来只是“写数据、读数据、校验”,但背后牵扯到总线时序、存储映射、CMD文件分段这些真正决定项目能不能跑稳的东西。今天我就按这块板子的实际配置,把XINTF外扩SRAM从原理到验证完整捋一遍,适合正在照着实验做但没吃透原理的读者,也适合想把XINTF用在自己项目里的人。1. 为什么非要用XINTF外扩SRAM:28335的内存账和接口布局1.1 内部RAM到底有多大,哪些场景会不够先算算28335的家底。芯片内部SARAM分为M0、M1和L0到L7几块,M0、M1各1K×16位,L0到L7加在一起约32K×16位,总共可用RAM大约34K字,换算成字节就是68KB左右。日常写个控制循环、PID、简单的状态机,这点空间绰绰有余,可一旦涉及数据密集型应用,立刻露馅。我实际碰到过的场景有三个。第一个是傅里叶变换,做256点FFT时,输入序列、输出序列、旋转因子表、中间缓冲叠在一起,内部RAM就开始紧张,更不要说做4096点。第二个是多通道ADC连续采集,我需要缓存几百个点的电压波形,再丢给上位机处理,一开双缓冲,内存直接翻倍。第三个是查表类算法,比如电机控制里的SVPWM在线计算,为了跑实时性,把几组曲线预先算好放在RAM里,启动时直接查表,这时候RAM占用轻松超过内部容量。很多人遇到内存不够,第一反应是省着用,把数组缩小、把缓冲区复用。这能缓解一部分问题,但一旦算法规模上来,终究要走到外部扩展这一步。XINTF外扩SRAM的意义就在这里:它不是把程序变大,而是给运行时数据一个更大的临时仓库,让大数组和双缓冲不再需要抠抠搜搜。为什么不直接扩Flash?因为Flash用来放代码和常量,运行时的变量、栈、堆必须放在可读写的RAM里。外部Flash虽然也能存数据,但写入寿命和速度都远不如SRAM,所以在需要频繁读写的场景,外扩SRAM是更合理的储备方案。1.2 XINTF的外设定位:六个Zone、片选与地址映射XINTF全称External Interface,是28335片上的异步外部总线接口,类似ARM平台上的存储器控制器。它负责把CPU发出的访问请求转换成外部存储器需要的片选、读写、地址和数据信号,软件上只需要访问指定的地址,硬件会自动完成握手。28335对外部空间按Zone分区管理,每个Zone由独立的片选信号XZCS0n、XZCS1n、XZCS2n、XZCS6n、XZCS7n控制。没用到的Zone不会产生片选,因此不会误动作。常用映射如下:Zone起始地址典型容量片选信号常见用途Zone 00x00004000起独立分配XZCS0n低速外设接口Zone 10x00008000起独立分配XZCS1n低速外设接口Zone 20x0000C000起独立分配XZCS2n外设接口Zone 60x100000起最大512K×16位XZCS6n外扩SRAMZone 70x200000起最大128K×16位XZCS7n外扩Flash做外扩SRAM实验时,几乎所有人都选Zone 6,因为它的地址空间够大,片选对应关系清晰,而且和内部RAM、Boot ROM的地址没有冲突。访问Zone 6范围内的任意地址时,28335会自动拉低XZCS6n,选中接在总线上的SRAM芯片,不需要软件额外操作GPIO去控制片选,这也是XINTF比普通IO模拟总线省心的根本原因。还有一个容易忽略的点:XINTF引脚和很多GPIO是复用的。跑实验前要在GPIO功能选择寄存器里把对应引脚切到XINTF功能,否则总线信号根本出不来。这一步通常由初始化代码完成,但如果你是照着旧工程改,要特别确认引脚复用配置没被别的驱动覆盖。2. 硬件先行的片外SRAM实验:从芯片手册到电路连线2.1 典型外扩SRAM的选型与引脚行为这块开发板上常见的SRAM型号是IS61LV25616,容量256K×16位,也就是4Mb,3.3V供电,访问时间典型值在12ns左右。引脚结构很简单:A0到A17共18根地址线,I/O0到I/O15共16根数据线,加上CE片选、OE读使能、WE写使能、UB高字节使能、LB低字节使能,以及电源和地。理解这颗芯片的关键是UB和LB。IS61LV25616本质上按字节组织,UB控制高字节(I/O8到I/O15)的读写,LB控制低字节(I/O0到I/O7)的读写。当UB和LB同时为低时,芯片按16位整字访问,一次读写同时操作两个字节;只拉低其中一个,就变成8位访问模式。这种设计让一颗芯片既能当16位存储器用,也能拆成两个8位存储器用,非常灵活。在28335外扩SRAM实验中,我们的目标就是让CPU直接访问16位数据,所以UB和LB在硬件上直接接低电平,让它们始终有效。选这颗芯片还有一个实际好处:它和TI官方文档里的外扩时序参数能对得上,照着数据手册的Read Cycle、Write Cycle时序图配XINTF时序,非常顺手。如果你手上的板子换成了其他SRAM,比如容量更大的IS61LV51216,引脚排布基本类似,只是A18、A19多出两根地址线,接线原则不变。真正需要重新核对的是访问时间参数,这会影响后面的时序配置,后面会细说。2.2 28335与SRAM的接线逻辑和字寻址细节拿典型板载接线来说,28335的XINTF信号和IS61LV25616的对应关系如下:28335引脚SRAM引脚说明XA0 ~ XA17A0 ~ A17地址总线XD0 ~ XD15I/O0 ~ I/O15数据总线XZCS6nCEZone 6片选XRDnOE读使能XWE0nWE写使能低电平UB、LB16位整字访问这个连线很容易看懂,但有一个底层概念必须搞清楚:28335的地址是16位字地址,不是字节地址。C28x系列CPU的最小寻址单位是16位字,所以外部地址总线XA0对应的就是“第几个16位字”,而不是像ARM那样对应字节。外部SRAM的A0地址线也是按16位字组织,两者天然对齐,所以不需要做任何地址移位。举个具体例子。CPU访问地址0x100000时,XA0到XA17输出全0,选中SRAM的第0个字。访问0x100001时,XA0变1,选中第1个字。换句话说,28335地址加1,对应SRAM地址加1,访问的就是下一个16位数据。你在代码里写一个Uint16数组指针,指针加1跳过一个Uint16,正好和硬件行为完全一致。很多从ARM转过来的人一开始容易懵,因为ARM的地址最小单位是字节,访问16位数据时地址要偶数递增,DSP却不需要。想通这一点,写地址循环的时候就不会出现“隔一个地址跳一个数据”之类的低级错误。2.3 关于XA/XA和字节使能的一个常见误区调试时有两类问题算得上经典误区。第一类是有人看到28335有XWE0n和XWE1n两个写使能引脚,第一反应是“这是不是要分别控制高字节和低字节”,于是把XWE0n接UB、XWE1n接LB,结果怎么写都不对。实际上,28335在16位外部数据总线模式下,访问16位数据时产生的写脉冲同时体现在XWE0n和XWE1n上,两个信号不需要在SRAM端分开接。板子原理图通常把XWE0n接到SRAM的WE,UB、LB直接拉低,这样CPU每次写操作都会整字写入,简单可靠。不要画蛇添足去处理字节使能。第二类是地址线高位和片选的混淆。有人会把XA18、XA19也接到SRAM上,想“多扩展一点空间”,却忘了28335的Zone 6片选XZCS6n只在0x100000到0x17FFFF范围内有效。如果SRAM容量超过Zone 6剩余空间,或者地址线超过了芯片实际内部地址位数,高地址位就会悬空,访问地址和物理存储单元的对应关系错乱。正确做法是:SRAM有多少根地址线就接多少根,多出来的地址位不要乱接,片选信号只走XZCS6n。总结一句话:硬件连线以“16位数据总线、Zone 6片选、UB和LB接地”为基准,不要自己发挥。只要线和地址对得上,后面的软件时序配置才有意义。3. 配置XINTF的完整思路:分频、时序与使能序列3.1 先做时钟分频:XTIMCLK怎么选XINTF本身有一套独立的时钟逻辑,时钟源是系统时钟SYSCLKOUT,也就是CPU主频。28335最高能跑到150MHz,但在配置XINTF时可以先决定把XINTF时钟设为SYSCLKOUT,还是SYSCLKOUT的一半,对应的是XINTFCNF2寄存器里的XTIMCLK位。为什么要纠结这个分频?因为外部SRAM的访问速度是固定的,IS61LV25616的访问时间典型12ns,而150MHz下XINTF时钟周期只有6.67ns。时序参数一个周期对应6.67ns,想凑出足够的访问窗口,active阶段至少得给2个周期,加上lead和trail,整次访问的周期数会比较多。如果把XTIMCLK设为1,XINTF时钟降到75MHz,周期变成13.3ns,同样一个周期能提供更长的时间窗口,时序余量会宽裕很多。我自己的经验是:外扩SRAM实验阶段优先选择XTIMCLK等于SYSCLKOUT的一半,先把功能跑通,再回头优化速度。追求性能时再尝试全速模式,但此时必须严格检查SRAM的建立时间、保持时间是否满足。还有一点要提醒:28335的SYSCLKOUT取决于PLL配置。老例程里PLL可能只配到100MHz甚至更低,新工程可能直接150MHz。同样写一个active等于7,实际等待时间完全不同。所以看时序参数之前,先确认SYSCLKOUT到底是多少,否则算出来的时间全是空中楼阁。另外,XINTF外设时钟使能位PCLKCR0里的XINTFENCLK也要提前打开。这个位控制XINTF模块时钟,不打开的话,后面无论怎么配置XTIMING,总线都不会响应外部访问。3.2 lead/active/trail三段时序怎么和SRAM手册对账XINTF把一次外部访问拆成三段:lead是片选有效后到读写信号有效前的准备时间,active是读写信号有效的核心窗口,trail是读写信号结束后到片选撤销前的保持时间。三段都和时钟周期数相关,配置寄存器里按周期数写,所以真正要算的是“几个周期够用”。典型配置代码如下,按照Zone 6对应的XTIMING6寄存器设置:XintfRegs.XTIMING6.bit.XWIDTH = 0; // 0表示16位数据总线 XintfRegs.XTIMING6.bit.XRDLEAD = 3; // 读建立:3个XINTF时钟周期 XintfRegs.XTIMING6.bit.XRDACTIVE = 7; // 读有效:7个周期 XintfRegs.XTIMING6.bit.XRDTRAIL = 3; // 读保持:3个周期 XintfRegs.XTIMING6.bit.XWRLEAD = 3; // 写建立:3个周期 XintfRegs.XTIMING6.bit.XWRACTIVE = 7; // 写有效:7个周期 XintfRegs.XTIMING6.bit.XWRTRAIL = 3; // 写保持:3个周期这个配置不是拍脑袋写的。当XTIMCLK设为SYSCLKOUT/2且SYSCLKOUT为150MHz时,XINTF时钟75MHz,周期约13.3ns。active阶段7个周期就是93.1ns,远大于IS61LV25616的12ns典型访问时间,所以读操作肯定能在有效窗口内稳定返回数据。整体一次访问大约(3+7+3)*13.3=173ns,虽然不算快,但对于SRAM验证实验完全足够。计算总访问时间有个简单公式:总访问时间 = (LEAD + ACTIVE + TRAIL) * T_XINTFCLK 读取数据有效时间 = ACTIVE * T_XINTFCLK配置时用SRAM手册里的tAA(地址访问时间)、tRC(读周期时间)、tWC(写周期时间)做对账。SRAM手册给出的是最小时间要求,你要保证算出来的active阶段时间不小于tAA,整段lead加active加trail不小于tRC或tWC。如果实际跑起来偶发错误,优先加大active,再考虑加大lead和trail。另外要注意XWIDTH字段。28335外部总线支持16位和32位两种宽度,用IS61LV25616这种16位芯片时XWIDTH必须设0。有些例程会把XWIDTH设成1,那是对应外部接了32位存储器的板子,套到16位SRAM上会直接读写错乱。3.3 别忘了那几行“神秘”的使能汇编配置完XTIMING,很多人觉得就完事了,结果一运行,访问外部RAM要么卡死,要么读回来全是0xFF。问题往往出在一个容易被忽略的环节:XINTF接口在配置变化后,需要执行一次特殊的总线操作序列才能真正使能。TI官方例程里,InitXintf函数的最后通常有一段看起来像“随便写了几个数”的代码,实际上是通过连续几次对外部接口的访问,让XINTF内部状态机完成初始化。这步操作的原理比较复杂,简单说就是XINTF的时序参数虽然已经写进寄存器,但总线状态机需要一次真实访问来同步和启动。很多从旧工程复制代码的人只复制了XTIMING配置部分,漏掉了后面的使能序列,于是XINTF完全不动。标准做法是直接从CCS安装目录下的DSP2833x_Xintf.c文件里找到InitXintf函数,保留完整的初始化逻辑,不要自己删减。如果你用的是普中开发板配套例程,XINTF初始化函数一般已经封装好,调用后不要在后面再重复写XTIMING去覆盖它的配置。这里区分两个概念:前面说的PCLKCR0.XINTFENCLK是给模块供时钟,后面这段访问序列是激活外部总线工作状态,两者缺一不可。只开时钟不激活总线,外部RAM就是一块“电压正常的死芯片”。4. 把变量放进外部SRAM:CMD文件与工程配合4.1 在CMD里给外部RAM画一块区域XINTF配置好了,硬件上CPU访问0x100000地址就能读写外部SRAM。但如果你想在C语言里直接用数组名访问外部RAM,光有硬件还不够,必须让链接器知道有一块外部RAM区域可以用来放段。这就要改CMD文件。CMD是链接命令文件,负责告诉链接器芯片的存储布局以及代码数据段放在哪里。以内扩SRAM实验为例,在CMD的MEMORY里新增一块区域:MEMORY { PAGE 1: ZONE6SRAM : origin = 0x100000, length = 0x40000 }length填0x40000,也就是256K字,正好匹配IS61LV25616的容量。如果你的SRAM是512K字,就把length改成0x80000。随后把自定义段和这块区域绑定:SECTION { .ext_ram : > ZONE6SRAM, PAGE 1 }这样,凡是放进.ext_ram段的变量,链接器会自动分配到外部RAM地址空间。需要注意PAGE的选择。外部RAM既可以放PAGE 1的变量数据,也可以放PAGE 0的只读常量,具体看用途。常见的做法是把可读写变量放PAGE 1,符合大家平时使用RAM的习惯。改CMD文件有个必须守住的底线:外部RAM区域不能和内部RAM、外设寄存器空间重叠。如果origin和length算出来覆盖到别的有效区域,链接器不一定会报错,但程序运行起来数据会被莫名覆盖,排查起来非常痛苦。4.2 直接地址访问和大数组访问的两种验证方式验证外扩SRAM有两种常见写法,各有适用场景。第一种是直接地址访问,定义指针指向0x100000,然后循环读写:volatile Uint16 *extRam = (volatile Uint16 *)0x100000; Uint16 i; Uint16 error = 0; for (i = 0; i < 0x1000; i++) { extRam[i] = i; } for (i = 0; i < 0x1000; i++) { if (extRam[i] != i) { error = 1; break; } }这种方式的优点是直观,不依赖CMD文件,哪怕链接脚本还没改,也能立刻验证XINTF总线通不通。实验初期建议先用它跑一遍,把问题限定在硬件和XINTF配置层面。第二种是数组加段绑定,用#pragma把数组放到自定义段里:#pragma DATA_SECTION(extBuf, ".ext_ram") Uint16 extBuf[0x1000];这样写的好处是代码更贴近真实工程,后续要搬大块数据到外部RAM时,直接声明数组就能用。劣势是如果CMD文件没改对,链接器可能把数组放回内部RAM而不报错,造成“以为在测外部RAM、实际测的是内部RAM”的假象。判断方法就是打开编译生成的.map文件,搜extBuf的地址,确认落在0x100000到0x13FFFF范围内。两种方式建议结合使用。先指针验证硬件,再数组验证工程配置,哪一个环节出错都能快速定位。全量测试时注意边界,比如从0x100000写到0x13FFFF再回来读,特别检查最后一个地址,很多数据线问题都藏在最高地址位翻转附近。5. 实测过程中的异常现象与排查经验5.1 读回全FF:先查外设时钟和片选映射最经典的现象是程序跑完,读回来的数据全是0xFF,写进去的数据像没写一样。遇到这种情况,不要急着怀疑SRAM芯片坏了,按顺序查三个地方。第一查外设时钟,看PCLKCR0里的XINTFENCLK是否为1。很多从旧工程复制的初始化函数里没有打开这个位,XINTF模块根本不在工作状态。第二查引脚复用,打开GPIO功能选择寄存器,确认XA、XD、XZCS6n、XRDn、XWE0n这些引脚都配置成了XINTF功能,而不是普通GPIO。有些板载LED驱动或者按键扫描代码会把邻近引脚重新配成GPIO,覆盖掉XINTF配置。第三查访问地址,确认代码里polling的地址范围落在Zone 6的0x100000到0x13FFFF或0x17FFFF之间。如果地址落在未映射区域,片选不会拉低,SRAM自然不响应。排查顺序很重要,先查时钟和映射,再动硬件。我见过有人一上来就换SRAM芯片,结果问题只是没开外设时钟,白白折腾了大半天。还有一种伪装成“全FF”的情况:代码里加了编译器优化,循环写操作被优化掉了。此时读出来的全是上电初值,看似全是FF。解决方法是给外部RAM指针加上volatile,告诉编译器这些地址有硬件副作用,不要随意合并或删除访问。5.2 偶发性写读不一致:时序余量不足的典型特征比“全FF”更折磨人的是偶发性错误。现象是跑小范围测试没问题,跑全量测试偶尔读回来一个错数据,再跑一次又对,错误地址看起来没什么规律。这种问题绝大多数是时序余量不足。SRAM的建立时间、保持时间有一个最小要求,如果XINTF的lead、active、trail配置得太紧凑,芯片在某些电气条件下就会偶发采样失败。温度变化、电源纹波、总线电容都会影响边界,所以小范围测试时运气好全过,数据量一上来就暴露。处理办法简单粗暴:把时序参数整体加大。优先加大active,因为这是数据从SRAM到DSP的总线导通窗口;再把lead和trail各加1到2个周期。如果XTIMCLK用的是SYSCLKOUT全速,可以直接改成半速,让每个周期的时间拉长,通常立刻见效。偶尔也会有另一种情况:错误地址集中在某些bit上,比如总是bit11读错。这种通常是地址线或数据线虚焊、排线接触不良,属于硬件问题,和时序无关。区分方法是把同样的测试重复几次,如果错的bit固定不变,优先怀疑焊接和连线,而不要继续折腾寄存器。5.3 数据放错地的迷惑行为:编译段分配陷阱数组方式验证时,有人会遇到一个特别迷惑的现象:外部RAM明明能通过指针方式正常读写,但声明的数组在运行时却不像外部RAM,要么数据不持久,要么和别的变量冲突。问题几乎都出在段分配上。最常见的是#pragma DATA_SECTION和CMD文件名不一致,比如代码里写“.ext_ram”,CMD里写“.ext_ram_data”,链接器找不到匹配段,变量就会按照默认规则放回内部RAM。另一种情况是CMD文件有多个,工程链接时只包含了其中一个,你改的那个根本没参与链接。排查方法非常直接:编译后打开.map文件,找到你的数组符号,看它落在哪个地址区间。如果地址是0x00xxxx或者0x80xxxx,说明它还在内部RAM,段绑定没有生效。地图文件是调试这类问题最好的朋友,比猜代码效率高得多。还有一种隐蔽情况是数组长度超过CMD定义的范围。比如SRAM只有256K字,你在CMD里length写0x40000,但数组申请了0x50000个字,链接器会报溢出错误。不过有些工程配置不严谨,选择了“忽略溢出警告”,结果数组尾部写到外部RAM之外的未映射空间,运行时间歇性掉数据。所以我建议数组测试先从0x1000这样的小规模开始,确认段布局正确后再扩展到目标容量。5.4 用仿真器观察总线的检查顺序如果代码和配置都查过,问题还是反复出现,就得借助CCS的调试功能。在CCS的Memory Browser窗口里直接输入0x100000,能查看外部RAM当前内容,写值后立即刷新能判断总线是否正常工作。这个操作比跑全量循环定位问题快得多,适合在第一次上电时验证基本通路。另一个实用技巧是在访问外部RAM的代码前后设置断点,然后查看相关寄存器的值,确认XTIMING6被正确写入,以及XINTFCNF2里的分频位设置符合预期。仿真器连接正常的话,读寄存器值非常直观,几个关键字段一对比就知道配置有没有生效。如果手头有逻辑分析仪或示波器,可以抓XZCS6n、XRDn、XWE0n这几个信号,观察一轮访问过程中lead、active、trail的时间长度是否和寄存器配置一致。这种方法能直接看到时序波形,排查硬件问题时最有说服力。不过对大多数实验来说,稳定读写通过就已经够了,不必强制上仪器。我第一次做这个实验的时候,卡在最简单的写读回上,程序跑完读回来全是FF,查了半天才发现是PCLKCR0没使能。后来又把时序调得很紧,结果小范围对、大范围错,最后老老实实把XTIMCLK降到半速才稳下来。折腾完最大的体会是:XINTF外扩SRAM的调试不要靠猜,先查时钟,再看映射,然后确认CMD段位置,最后才动时序。按这个顺序走,大多数问题都能在半小时内解决。
做DSP开发的人,迟早会撞上内存不够用的墙。28335虽然带浮点单元,可内部那块SARAM总共也就三十几K字,跑个FFT、存一组采集数据、塞个查找表,说满就满。这时候最顺手的解法就是走XINTF外扩SRAM。这个实验在普中DSP28335开发攻略里被放在第28章,看起来只是“写数据、读数据、校验”,但背后牵扯到总线时序、存储映射、CMD文件分段这些真正决定项目能不能跑稳的东西。今天我就按这块板子的实际配置,把XINTF外扩SRAM从原理到验证完整捋一遍,适合正在照着实验做但没吃透原理的读者,也适合想把XINTF用在自己项目里的人。1. 为什么非要用XINTF外扩SRAM:28335的内存账和接口布局1.1 内部RAM到底有多大,哪些场景会不够先算算28335的家底。芯片内部SARAM分为M0、M1和L0到L7几块,M0、M1各1K×16位,L0到L7加在一起约32K×16位,总共可用RAM大约34K字,换算成字节就是68KB左右。日常写个控制循环、PID、简单的状态机,这点空间绰绰有余,可一旦涉及数据密集型应用,立刻露馅。我实际碰到过的场景有三个。第一个是傅里叶变换,做256点FFT时,输入序列、输出序列、旋转因子表、中间缓冲叠在一起,内部RAM就开始紧张,更不要说做4096点。第二个是多通道ADC连续采集,我需要缓存几百个点的电压波形,再丢给上位机处理,一开双缓冲,内存直接翻倍。第三个是查表类算法,比如电机控制里的SVPWM在线计算,为了跑实时性,把几组曲线预先算好放在RAM里,启动时直接查表,这时候RAM占用轻松超过内部容量。很多人遇到内存不够,第一反应是省着用,把数组缩小、把缓冲区复用。这能缓解一部分问题,但一旦算法规模上来,终究要走到外部扩展这一步。XINTF外扩SRAM的意义就在这里:它不是把程序变大,而是给运行时数据一个更大的临时仓库,让大数组和双缓冲不再需要抠抠搜搜。为什么不直接扩Flash?因为Flash用来放代码和常量,运行时的变量、栈、堆必须放在可读写的RAM里。外部Flash虽然也能存数据,但写入寿命和速度都远不如SRAM,所以在需要频繁读写的场景,外扩SRAM是更合理的储备方案。1.2 XINTF的外设定位:六个Zone、片选与地址映射XINTF全称External Interface,是28335片上的异步外部总线接口,类似ARM平台上的存储器控制器。它负责把CPU发出的访问请求转换成外部存储器需要的片选、读写、地址和数据信号,软件上只需要访问指定的地址,硬件会自动完成握手。28335对外部空间按Zone分区管理,每个Zone由独立的片选信号XZCS0n、XZCS1n、XZCS2n、XZCS6n、XZCS7n控制。没用到的Zone不会产生片选,因此不会误动作。常用映射如下:Zone起始地址典型容量片选信号常见用途Zone 00x00004000起独立分配XZCS0n低速外设接口Zone 10x00008000起独立分配XZCS1n低速外设接口Zone 20x0000C000起独立分配XZCS2n外设接口Zone 60x100000起最大512K×16位XZCS6n外扩SRAMZone 70x200000起最大128K×16位XZCS7n外扩Flash做外扩SRAM实验时,几乎所有人都选Zone 6,因为它的地址空间够大,片选对应关系清晰,而且和内部RAM、Boot ROM的地址没有冲突。访问Zone 6范围内的任意地址时,28335会自动拉低XZCS6n,选中接在总线上的SRAM芯片,不需要软件额外操作GPIO去控制片选,这也是XINTF比普通IO模拟总线省心的根本原因。还有一个容易忽略的点:XINTF引脚和很多GPIO是复用的。跑实验前要在GPIO功能选择寄存器里把对应引脚切到XINTF功能,否则总线信号根本出不来。这一步通常由初始化代码完成,但如果你是照着旧工程改,要特别确认引脚复用配置没被别的驱动覆盖。2. 硬件先行的片外SRAM实验:从芯片手册到电路连线2.1 典型外扩SRAM的选型与引脚行为这块开发板上常见的SRAM型号是IS61LV25616,容量256K×16位,也就是4Mb,3.3V供电,访问时间典型值在12ns左右。引脚结构很简单:A0到A17共18根地址线,I/O0到I/O15共16根数据线,加上CE片选、OE读使能、WE写使能、UB高字节使能、LB低字节使能,以及电源和地。理解这颗芯片的关键是UB和LB。IS61LV25616本质上按字节组织,UB控制高字节(I/O8到I/O15)的读写,LB控制低字节(I/O0到I/O7)的读写。当UB和LB同时为低时,芯片按16位整字访问,一次读写同时操作两个字节;只拉低其中一个,就变成8位访问模式。这种设计让一颗芯片既能当16位存储器用,也能拆成两个8位存储器用,非常灵活。在28335外扩SRAM实验中,我们的目标就是让CPU直接访问16位数据,所以UB和LB在硬件上直接接低电平,让它们始终有效。选这颗芯片还有一个实际好处:它和TI官方文档里的外扩时序参数能对得上,照着数据手册的Read Cycle、Write Cycle时序图配XINTF时序,非常顺手。如果你手上的板子换成了其他SRAM,比如容量更大的IS61LV51216,引脚排布基本类似,只是A18、A19多出两根地址线,接线原则不变。真正需要重新核对的是访问时间参数,这会影响后面的时序配置,后面会细说。2.2 28335与SRAM的接线逻辑和字寻址细节拿典型板载接线来说,28335的XINTF信号和IS61LV25616的对应关系如下:28335引脚SRAM引脚说明XA0 ~ XA17A0 ~ A17地址总线XD0 ~ XD15I/O0 ~ I/O15数据总线XZCS6nCEZone 6片选XRDnOE读使能XWE0nWE写使能低电平UB、LB16位整字访问这个连线很容易看懂,但有一个底层概念必须搞清楚:28335的地址是16位字地址,不是字节地址。C28x系列CPU的最小寻址单位是16位字,所以外部地址总线XA0对应的就是“第几个16位字”,而不是像ARM那样对应字节。外部SRAM的A0地址线也是按16位字组织,两者天然对齐,所以不需要做任何地址移位。举个具体例子。CPU访问地址0x100000时,XA0到XA17输出全0,选中SRAM的第0个字。访问0x100001时,XA0变1,选中第1个字。换句话说,28335地址加1,对应SRAM地址加1,访问的就是下一个16位数据。你在代码里写一个Uint16数组指针,指针加1跳过一个Uint16,正好和硬件行为完全一致。很多从ARM转过来的人一开始容易懵,因为ARM的地址最小单位是字节,访问16位数据时地址要偶数递增,DSP却不需要。想通这一点,写地址循环的时候就不会出现“隔一个地址跳一个数据”之类的低级错误。2.3 关于XA/XA和字节使能的一个常见误区调试时有两类问题算得上经典误区。第一类是有人看到28335有XWE0n和XWE1n两个写使能引脚,第一反应是“这是不是要分别控制高字节和低字节”,于是把XWE0n接UB、XWE1n接LB,结果怎么写都不对。实际上,28335在16位外部数据总线模式下,访问16位数据时产生的写脉冲同时体现在XWE0n和XWE1n上,两个信号不需要在SRAM端分开接。板子原理图通常把XWE0n接到SRAM的WE,UB、LB直接拉低,这样CPU每次写操作都会整字写入,简单可靠。不要画蛇添足去处理字节使能。第二类是地址线高位和片选的混淆。有人会把XA18、XA19也接到SRAM上,想“多扩展一点空间”,却忘了28335的Zone 6片选XZCS6n只在0x100000到0x17FFFF范围内有效。如果SRAM容量超过Zone 6剩余空间,或者地址线超过了芯片实际内部地址位数,高地址位就会悬空,访问地址和物理存储单元的对应关系错乱。正确做法是:SRAM有多少根地址线就接多少根,多出来的地址位不要乱接,片选信号只走XZCS6n。总结一句话:硬件连线以“16位数据总线、Zone 6片选、UB和LB接地”为基准,不要自己发挥。只要线和地址对得上,后面的软件时序配置才有意义。3. 配置XINTF的完整思路:分频、时序与使能序列3.1 先做时钟分频:XTIMCLK怎么选XINTF本身有一套独立的时钟逻辑,时钟源是系统时钟SYSCLKOUT,也就是CPU主频。28335最高能跑到150MHz,但在配置XINTF时可以先决定把XINTF时钟设为SYSCLKOUT,还是SYSCLKOUT的一半,对应的是XINTFCNF2寄存器里的XTIMCLK位。为什么要纠结这个分频?因为外部SRAM的访问速度是固定的,IS61LV25616的访问时间典型12ns,而150MHz下XINTF时钟周期只有6.67ns。时序参数一个周期对应6.67ns,想凑出足够的访问窗口,active阶段至少得给2个周期,加上lead和trail,整次访问的周期数会比较多。如果把XTIMCLK设为1,XINTF时钟降到75MHz,周期变成13.3ns,同样一个周期能提供更长的时间窗口,时序余量会宽裕很多。我自己的经验是:外扩SRAM实验阶段优先选择XTIMCLK等于SYSCLKOUT的一半,先把功能跑通,再回头优化速度。追求性能时再尝试全速模式,但此时必须严格检查SRAM的建立时间、保持时间是否满足。还有一点要提醒:28335的SYSCLKOUT取决于PLL配置。老例程里PLL可能只配到100MHz甚至更低,新工程可能直接150MHz。同样写一个active等于7,实际等待时间完全不同。所以看时序参数之前,先确认SYSCLKOUT到底是多少,否则算出来的时间全是空中楼阁。另外,XINTF外设时钟使能位PCLKCR0里的XINTFENCLK也要提前打开。这个位控制XINTF模块时钟,不打开的话,后面无论怎么配置XTIMING,总线都不会响应外部访问。3.2 lead/active/trail三段时序怎么和SRAM手册对账XINTF把一次外部访问拆成三段:lead是片选有效后到读写信号有效前的准备时间,active是读写信号有效的核心窗口,trail是读写信号结束后到片选撤销前的保持时间。三段都和时钟周期数相关,配置寄存器里按周期数写,所以真正要算的是“几个周期够用”。典型配置代码如下,按照Zone 6对应的XTIMING6寄存器设置:XintfRegs.XTIMING6.bit.XWIDTH = 0; // 0表示16位数据总线 XintfRegs.XTIMING6.bit.XRDLEAD = 3; // 读建立:3个XINTF时钟周期 XintfRegs.XTIMING6.bit.XRDACTIVE = 7; // 读有效:7个周期 XintfRegs.XTIMING6.bit.XRDTRAIL = 3; // 读保持:3个周期 XintfRegs.XTIMING6.bit.XWRLEAD = 3; // 写建立:3个周期 XintfRegs.XTIMING6.bit.XWRACTIVE = 7; // 写有效:7个周期 XintfRegs.XTIMING6.bit.XWRTRAIL = 3; // 写保持:3个周期这个配置不是拍脑袋写的。当XTIMCLK设为SYSCLKOUT/2且SYSCLKOUT为150MHz时,XINTF时钟75MHz,周期约13.3ns。active阶段7个周期就是93.1ns,远大于IS61LV25616的12ns典型访问时间,所以读操作肯定能在有效窗口内稳定返回数据。整体一次访问大约(3+7+3)*13.3=173ns,虽然不算快,但对于SRAM验证实验完全足够。计算总访问时间有个简单公式:总访问时间 = (LEAD + ACTIVE + TRAIL) * T_XINTFCLK 读取数据有效时间 = ACTIVE * T_XINTFCLK配置时用SRAM手册里的tAA(地址访问时间)、tRC(读周期时间)、tWC(写周期时间)做对账。SRAM手册给出的是最小时间要求,你要保证算出来的active阶段时间不小于tAA,整段lead加active加trail不小于tRC或tWC。如果实际跑起来偶发错误,优先加大active,再考虑加大lead和trail。另外要注意XWIDTH字段。28335外部总线支持16位和32位两种宽度,用IS61LV25616这种16位芯片时XWIDTH必须设0。有些例程会把XWIDTH设成1,那是对应外部接了32位存储器的板子,套到16位SRAM上会直接读写错乱。3.3 别忘了那几行“神秘”的使能汇编配置完XTIMING,很多人觉得就完事了,结果一运行,访问外部RAM要么卡死,要么读回来全是0xFF。问题往往出在一个容易被忽略的环节:XINTF接口在配置变化后,需要执行一次特殊的总线操作序列才能真正使能。TI官方例程里,InitXintf函数的最后通常有一段看起来像“随便写了几个数”的代码,实际上是通过连续几次对外部接口的访问,让XINTF内部状态机完成初始化。这步操作的原理比较复杂,简单说就是XINTF的时序参数虽然已经写进寄存器,但总线状态机需要一次真实访问来同步和启动。很多从旧工程复制代码的人只复制了XTIMING配置部分,漏掉了后面的使能序列,于是XINTF完全不动。标准做法是直接从CCS安装目录下的DSP2833x_Xintf.c文件里找到InitXintf函数,保留完整的初始化逻辑,不要自己删减。如果你用的是普中开发板配套例程,XINTF初始化函数一般已经封装好,调用后不要在后面再重复写XTIMING去覆盖它的配置。这里区分两个概念:前面说的PCLKCR0.XINTFENCLK是给模块供时钟,后面这段访问序列是激活外部总线工作状态,两者缺一不可。只开时钟不激活总线,外部RAM就是一块“电压正常的死芯片”。4. 把变量放进外部SRAM:CMD文件与工程配合4.1 在CMD里给外部RAM画一块区域XINTF配置好了,硬件上CPU访问0x100000地址就能读写外部SRAM。但如果你想在C语言里直接用数组名访问外部RAM,光有硬件还不够,必须让链接器知道有一块外部RAM区域可以用来放段。这就要改CMD文件。CMD是链接命令文件,负责告诉链接器芯片的存储布局以及代码数据段放在哪里。以内扩SRAM实验为例,在CMD的MEMORY里新增一块区域:MEMORY { PAGE 1: ZONE6SRAM : origin = 0x100000, length = 0x40000 }length填0x40000,也就是256K字,正好匹配IS61LV25616的容量。如果你的SRAM是512K字,就把length改成0x80000。随后把自定义段和这块区域绑定:SECTION { .ext_ram : > ZONE6SRAM, PAGE 1 }这样,凡是放进.ext_ram段的变量,链接器会自动分配到外部RAM地址空间。需要注意PAGE的选择。外部RAM既可以放PAGE 1的变量数据,也可以放PAGE 0的只读常量,具体看用途。常见的做法是把可读写变量放PAGE 1,符合大家平时使用RAM的习惯。改CMD文件有个必须守住的底线:外部RAM区域不能和内部RAM、外设寄存器空间重叠。如果origin和length算出来覆盖到别的有效区域,链接器不一定会报错,但程序运行起来数据会被莫名覆盖,排查起来非常痛苦。4.2 直接地址访问和大数组访问的两种验证方式验证外扩SRAM有两种常见写法,各有适用场景。第一种是直接地址访问,定义指针指向0x100000,然后循环读写:volatile Uint16 *extRam = (volatile Uint16 *)0x100000; Uint16 i; Uint16 error = 0; for (i = 0; i < 0x1000; i++) { extRam[i] = i; } for (i = 0; i < 0x1000; i++) { if (extRam[i] != i) { error = 1; break; } }这种方式的优点是直观,不依赖CMD文件,哪怕链接脚本还没改,也能立刻验证XINTF总线通不通。实验初期建议先用它跑一遍,把问题限定在硬件和XINTF配置层面。第二种是数组加段绑定,用#pragma把数组放到自定义段里:#pragma DATA_SECTION(extBuf, ".ext_ram") Uint16 extBuf[0x1000];这样写的好处是代码更贴近真实工程,后续要搬大块数据到外部RAM时,直接声明数组就能用。劣势是如果CMD文件没改对,链接器可能把数组放回内部RAM而不报错,造成“以为在测外部RAM、实际测的是内部RAM”的假象。判断方法就是打开编译生成的.map文件,搜extBuf的地址,确认落在0x100000到0x13FFFF范围内。两种方式建议结合使用。先指针验证硬件,再数组验证工程配置,哪一个环节出错都能快速定位。全量测试时注意边界,比如从0x100000写到0x13FFFF再回来读,特别检查最后一个地址,很多数据线问题都藏在最高地址位翻转附近。5. 实测过程中的异常现象与排查经验5.1 读回全FF:先查外设时钟和片选映射最经典的现象是程序跑完,读回来的数据全是0xFF,写进去的数据像没写一样。遇到这种情况,不要急着怀疑SRAM芯片坏了,按顺序查三个地方。第一查外设时钟,看PCLKCR0里的XINTFENCLK是否为1。很多从旧工程复制的初始化函数里没有打开这个位,XINTF模块根本不在工作状态。第二查引脚复用,打开GPIO功能选择寄存器,确认XA、XD、XZCS6n、XRDn、XWE0n这些引脚都配置成了XINTF功能,而不是普通GPIO。有些板载LED驱动或者按键扫描代码会把邻近引脚重新配成GPIO,覆盖掉XINTF配置。第三查访问地址,确认代码里polling的地址范围落在Zone 6的0x100000到0x13FFFF或0x17FFFF之间。如果地址落在未映射区域,片选不会拉低,SRAM自然不响应。排查顺序很重要,先查时钟和映射,再动硬件。我见过有人一上来就换SRAM芯片,结果问题只是没开外设时钟,白白折腾了大半天。还有一种伪装成“全FF”的情况:代码里加了编译器优化,循环写操作被优化掉了。此时读出来的全是上电初值,看似全是FF。解决方法是给外部RAM指针加上volatile,告诉编译器这些地址有硬件副作用,不要随意合并或删除访问。5.2 偶发性写读不一致:时序余量不足的典型特征比“全FF”更折磨人的是偶发性错误。现象是跑小范围测试没问题,跑全量测试偶尔读回来一个错数据,再跑一次又对,错误地址看起来没什么规律。这种问题绝大多数是时序余量不足。SRAM的建立时间、保持时间有一个最小要求,如果XINTF的lead、active、trail配置得太紧凑,芯片在某些电气条件下就会偶发采样失败。温度变化、电源纹波、总线电容都会影响边界,所以小范围测试时运气好全过,数据量一上来就暴露。处理办法简单粗暴:把时序参数整体加大。优先加大active,因为这是数据从SRAM到DSP的总线导通窗口;再把lead和trail各加1到2个周期。如果XTIMCLK用的是SYSCLKOUT全速,可以直接改成半速,让每个周期的时间拉长,通常立刻见效。偶尔也会有另一种情况:错误地址集中在某些bit上,比如总是bit11读错。这种通常是地址线或数据线虚焊、排线接触不良,属于硬件问题,和时序无关。区分方法是把同样的测试重复几次,如果错的bit固定不变,优先怀疑焊接和连线,而不要继续折腾寄存器。5.3 数据放错地的迷惑行为:编译段分配陷阱数组方式验证时,有人会遇到一个特别迷惑的现象:外部RAM明明能通过指针方式正常读写,但声明的数组在运行时却不像外部RAM,要么数据不持久,要么和别的变量冲突。问题几乎都出在段分配上。最常见的是#pragma DATA_SECTION和CMD文件名不一致,比如代码里写“.ext_ram”,CMD里写“.ext_ram_data”,链接器找不到匹配段,变量就会按照默认规则放回内部RAM。另一种情况是CMD文件有多个,工程链接时只包含了其中一个,你改的那个根本没参与链接。排查方法非常直接:编译后打开.map文件,找到你的数组符号,看它落在哪个地址区间。如果地址是0x00xxxx或者0x80xxxx,说明它还在内部RAM,段绑定没有生效。地图文件是调试这类问题最好的朋友,比猜代码效率高得多。还有一种隐蔽情况是数组长度超过CMD定义的范围。比如SRAM只有256K字,你在CMD里length写0x40000,但数组申请了0x50000个字,链接器会报溢出错误。不过有些工程配置不严谨,选择了“忽略溢出警告”,结果数组尾部写到外部RAM之外的未映射空间,运行时间歇性掉数据。所以我建议数组测试先从0x1000这样的小规模开始,确认段布局正确后再扩展到目标容量。5.4 用仿真器观察总线的检查顺序如果代码和配置都查过,问题还是反复出现,就得借助CCS的调试功能。在CCS的Memory Browser窗口里直接输入0x100000,能查看外部RAM当前内容,写值后立即刷新能判断总线是否正常工作。这个操作比跑全量循环定位问题快得多,适合在第一次上电时验证基本通路。另一个实用技巧是在访问外部RAM的代码前后设置断点,然后查看相关寄存器的值,确认XTIMING6被正确写入,以及XINTFCNF2里的分频位设置符合预期。仿真器连接正常的话,读寄存器值非常直观,几个关键字段一对比就知道配置有没有生效。如果手头有逻辑分析仪或示波器,可以抓XZCS6n、XRDn、XWE0n这几个信号,观察一轮访问过程中lead、active、trail的时间长度是否和寄存器配置一致。这种方法能直接看到时序波形,排查硬件问题时最有说服力。不过对大多数实验来说,稳定读写通过就已经够了,不必强制上仪器。我第一次做这个实验的时候,卡在最简单的写读回上,程序跑完读回来全是FF,查了半天才发现是PCLKCR0没使能。后来又把时序调得很紧,结果小范围对、大范围错,最后老老实实把XTIMCLK降到半速才稳下来。折腾完最大的体会是:XINTF外扩SRAM的调试不要靠猜,先查时钟,再看映射,然后确认CMD段位置,最后才动时序。按这个顺序走,大多数问题都能在半小时内解决。