1 CI是做什么的?Cascade in for daisy chain(官方说法:级联XX?找不到合适的词汇形容它)
其实它类似于复位信号,你的设计不使用级联工作方式的话,它就是复位信号,可以和系统上其它外设的复位信号接在一起(低有效)。你的系统一加电,首先要对系统初始化,外围扩展器件也要一一复位初始化,这时候,给它一个复位就行了。
如果用一个IO口来控制CI,IO得长期保持高电平。复位的时候设为低电平,再设为高电平并且后面一直保持着。SPBA01B在CI变低一瞬间,开始复位初始化,在CI变高的时候恢复正常。
2 复位之后,设置它的ID号,就是要给它一个名份,在级联方式下,要靠ID号来识别第几个SPBA01B被选中了。就像一夫多妻制下的“大太太”、“二姨太”、“三姨太”等等,得有个辈分头衔的顺序,一个压着一个,免得你争我夺乱了套。它是在SPBA01B的 P_0DH_BMIVolumeID ($000D)寄存器内定义的,通过写入指令来确定。
单个不级联的SPBA01B是不是就默认自己是老大呢?错!!!你还是得给它写一个ID号,不然它拒绝工作。呵呵!没名份它是不干的(多现实啊!天下有多少个傻女人为心爱的男人默默奉献一切不计名份,结果男人发达之后,嫌她人老珠黄一脚踹!无耻啊!我们一定要做有责任心的好男人,但我不反对多交几个女朋友!!!)。
3 配置工作方式,它必须要明确知道自己应该做啥工作,是IO扩展呢?还是访问存储器?这个要设置 P_23H_BEXConfig ($0023)中的各个相关位,参考一下DataSheet吧,写得挺细的呢!
4 上面三项工作做完之后,它就可以正式开始工作了。注意啊:上面三项没做好的话,它甩都不甩你。如果SPBA01B不睬你,那就要考虑一下上面三个步骤哪里出了差错。
5 它是如何工作的呢?(就是工作原理啊,明白了吧!)
首先,它的肚量只有64K“单元”(字节,为何不直接用字节描述,而是用单元呢?后面慢慢道来。),这是它内部自己用的地址空间(地址线A0-A15,分为高低两个字节分别写入对应的锁存器),和外部扩展用于访问存储器的地址线A0-A21不是一回事,别混淆了。
然后它又把自己的64K单元空间分成几段,每段的用途各不相同:
第一段:0x00 - 0x31 是它的寄存器段,所有的控制寄存器全部在这里呢;
第二段:0x4000 - 0x7FFF,16K单元的范围映射到外部访问的存储器上;
第三段:0x8000 - 0xBFFF,又是16K单元映射到外部的存储器上;你可能好奇,干嘛不和上面的16K单元合起来统称32K单元呢,呵呵!还是在后面再告诉你吧。
第四段:0xC000 - 0xFFFF,据称是给RAM留的,它的例子里是这么讲的,但现在是空的,啥用没有!
所以,你通过访问它不同的地址空间来切换它的功能。当你设定的地址是访问它的0x00-0x31单元,就肯定是对它内部寄存器操作了,它的数据线就和CPU的总线挂在一起了。而它的外部存储器扩展信号,像片选CE#、读信号RE#啊等等全部为高,扩展的存储器不能被访问。
当你访问0x4000-0xBFFF单元时,就会在它的存储器控制引脚上产生对应的CE#,OE#或WE#等信号,这时候就该外部的存储器工作了。而SPBA01B自己的数据线会彻底悬浮,和CPU数据线脱离。
按照DataSheet的描述,它最大只能扩展访问 4M Byte,我扩展的28F128J3却有16M Byte,怎么做到的呢?呵呵!现在到解释“单元”这个疑问的时候了。
虽然SPBA01B内部是8bit结构的,但由于SPEC061A是16bit单片机,而且SPBA01B只管送出扩展的地址线和控制线,和数据线却无关,所以我把存储器的 D0 - D15 数据线挂到单片机的IOA0 - IOA15上,这样访问的数据量大了一倍,可以访问到8M Byte,然后SPBA01B在4M Byte扩展模式下还多了一个IO口可以用,我用它作为最高的一根地址线,最终访问量达到了16M Byte,所以,我前面的描述使用了“单元”,而不是“字节”。
6 DataSheet上是这样说的,外部存储器被分为32KB为一段,映射到SPBA01B的0x4000 - 0xBFFF地址空间内......我郑重警告你!这是骗你的!严重骗你的!非常不负责任的骗你的!!!
其实,外部存储器是分作16K一段的,这就是我上面为何不把 0x4000 - 0xBFFF 说成32K单元,而是一定要分成两块的重要原因!可能是IC的设计工程师偷懒(对不起哦!我说话挺直!),所以把编写软件搞复杂了。
你通过SPBA01B访问外部扩展存储器的时候,那个0x0000 - 0x8000指定的32K线性单元空间不能通过简单的加上偏移量0x4000就能访问了,而是要把它平分为两块,它们的块顺序上下颠倒过来访问(每个块内的地址单元是不需要颠倒的,还是正常顺序!!!)!!!???
好奇怪哦?要分成 0x0000-0x3FFF 和 0x4000-0x7FFF 这两段,第一段单元空间必须要加上 0x8000 的偏移量才能正确访问,变成了 0x8000 - 0xBFFF ,第二段不用作任何改动直接访问即可。奇怪吧???其实一点也不奇怪,因为这样设计在逻辑电路上实现起来最简单,通过几个最原始的逻辑门电路就能实现,如果整个32K线性单元全部平移,就要动用加法器或类似的复杂逻辑电路了,不仅设计电路麻烦,成本也增加了。
硬件工程师经常喜欢这样搞,还沾沾自喜:“嘿嘿!我又省了几毛钱了......”,软件工程师跟在后面倒霉了,被复杂的逻辑关系绕的头昏眼花,还喃喃自语:“我咋这么笨啊.....”。呵呵!老板们可能要吹胡子瞪眼了:“成本最重要!当然应该这么做了!”。可是,从性价比上看不是样样都划算的!硬件工程师只需要多花几小时就能做好的功能,却因为偷懒省掉的零件,可能需要软件工程师几周的工作量来弥补,甚至留下难以察觉的BUG在产品售出后才发现,拖延上市和产品召回的损失远远大于省下成本的n倍!!!
所以,这里郑重宣布,SPBA01B的外部扩展存储器,是16K一段的,绝对不能当作32K来用,否则:访问扩展的SRAM绝对没问题,FLASH和ROM就惨啦!!!!!!
7 用于存储器扩展的SPBA01B配置内部寄存器的要点。就是关键一点,段寄存器的bit7一定要置为“1”!!!就是你算出来段编号写入段寄存器之前,一定要记得“或”运算0x80!!!看看Datasheet上的“must be1”这行字,我就是开始没注意它,存储器的CE#(片选信号)死都不肯出来,就算OE#和WE#跑的再欢也是没用的。
8 最后,最关键的部分:如何对SPBA01B进行读写操作?很重要,很让人糊涂,很容易让人暴跳如雷!呵呵!真的不骗你。
IC工程师对这个双线操作设计很得意、很自豪,因为很节省资源!?......其实这种操作真的很讨厌!SPBA01B用MC1和MC0输入信号来产生四个访问状态:锁定高地址(MC1=1、MC0=1)、锁定低地址(MC1=1、MC0=0)、读操作(MC1=0、MC0=1)、写操作(MC1=0、MC0=0)。
工作原理就是三个步骤:1.先把准备访问SPBA01B内部单元的高8位地址送到它的高地址锁存器;2.然后再轮到低8位锁存器;3.最后你再进行需要的读或写操作。
麻烦就在于它没有片选信号,这两根状态线肯定导致了SPBA01B每时每刻肯定是在某一种工作状态下,你对数据线的每个操作都要小心,免得干扰SPBA01B或者被它干扰!!!
你可能会这样设计你的软件,下面我用伪码描述工作原理,请注意!别真的在IDE里面编译它:
1. IOA_Data = Adder_H_8; // 送上高八位地址数据
2. MC1 = 1; // 将状态设置为锁存高地址
3. MC0 = 1;
4. IOA_Data = Adder_L_8; // 送上低八位地址数据
5. MC1 = 1; // 将状态设置为锁存低地址
6. MC0 = 0;
7. IOA_Data = Data; // 送上准备写入的数据
8. MC1 = 1; // 将状态设置写数据
9. MC0 = 1
好咯!写完咯!成功咯!!!真的吗???慢着!!!!!!!!!!!!!!!!!!!!!
如果你的软件真的这样写就完蛋了,就像准备到东方,却一直向西方走,而且不是在地球上,还能绕回来。就像困在一个无边的二维世界里,永远到不了你想去的地方。
为何呢?因为数据并没有正确锁存到地址寄存器,而且写入扩展存储器或者SPBA01B寄存器的数据是随机数!
这种情况是怎么产生的呢?就是因为SPBA01B采用了上升沿锁存机制。纯做软件的朋友可能不太明白这个工作原理,学过逻辑电路的朋友就明白了。不明白不要紧,看看我们应该怎样正确的做就行了。
上面的伪代码有两个毛病,正确的软件编写方式应该如下:
1. MC1=1; // 首先设定为写高地址模式
2. MC0=1;
3. IOA_Data = Adder_H_8; // 送上高八位地址数据
4. MC1=1; // 再设为写低地址模式,注意:由于在这里信号线的跳变,高八位地址同时被正确锁存到高地址寄存器中了
5. MC0=0;
6. IOA_Data = Adder_L_8; // 再送上低八位地址
7. MC1=0; // 设定为写数据模式,注意:由于在这里信号线的跳变,低八位地址同时被正确锁存到高地址寄存器中了
8. MC0=0;
9. IOA_Data = Data; // 送上准备写入的数据。!!!!!注意:这里很关键的一点,很多人会忽略掉!!!!!数据是送上数据线了,但是还没有开始真正的写操作!!!!!需要将信号线跳变一下,确保写操作开始执行,数据正确写入该去的地方!!!!!
10. MC1=1; // 这里就是跳变信号,确保上面的写操作正确执行。
11. MC0=1;
你可能会担心,信号又跳回锁存高地址模式,会不会造成不良影响啊???那么~~~ 接吻会不会怀孕啊???其实这就是告诉你的答案!当然不会有影响啦!因为只要MC1和MC0保持那个状态不变,指定的模式也就没有真正开始执行!所以不会改变高地址。两个人拖拖手、亲亲嘴,你别开始玩真刀真枪的,哪里会有BB啊???
烦啊!你明白为何我讨厌这种双线操作了吧。有没有办法关闭这个讨厌的四个指定状态呢?免得不小心对SPBA01B误操作了。有,肯定有,而且方法很多,但不花钱的办法可能只有一个,和老太太的裹脚布一样,又臭又长。记得前面说过的ID号吗?就它管用,往这个寄存器里面写一个和它不一致的ID号,它就立刻休息了。需要它的时候,再写回它自己的ID号就行了,它立马又活蹦乱跳了。这个功能是设计给级联的需求使用的,这里可以借用,明白了吧!如果不懂级联是什么?那么好好看看DataSheet就知道了。
假如你嫌烦,不愿看DataSheet,那我就简单介绍一下:SPBA01B是可以串接起来工作的,最多可以串七个,它们共享MC1和MC0控制线。为了保证它们正常工作不打架,每个SPBA01有自己的专用ID号,不能和其它重复(否则那就乱来了)。第一个SPBA01B的CI信号接到CPU的IO上,然后有个CO信号接到下一片SPBA01B的CI上,该片的Co再接到第三级的CI上,如此这般...一直连到第七个。
当CPU开始对SPBA01B复位时,第一片复位了,等待CPU写一个ID号给它,写完成后,这个ID号在后面的工作中就不能改了,直到你关闭电源(再用CI信号来次复位后重写呢?我忘记做试验了,有兴趣的朋友试试看吧)。然后,如果你再次写入ID号的时候,它开始比较,如果ID号相同,表示你确定要求它是活动的,否则,你是选择另外的SPBA01B,这时候,它开始传递CI信号到第二个SPBA01B(通过它的CO到第二片的CI),然后它睡了。第二个开始确认,如果没写过ID,就把ID号当作自己的名份保存起来(快抢啊!谁愿意做名份最小的?)。如果自己是写过ID的,那么它就比较ID号,重复第一片同样的工作......这样就把所有的ID号权设置完了,最后设置完的保持活动状态,其它全部睡着了。幸好SPBA01B不会吃醋,不然下面几级全部饿死了......!!!如果你需要唤醒某个开始工作,则写ID号到ID号寄存器,对上号的马上醒来,刚才那个又睡去了!!!
终于写完了,希望对各位有帮助!!!