关于S3C2440时钟设置的理解
2008-10-2422:36:32| 分类: 工作技术-嵌入式|字号 订阅
关于S3C2440时钟设置的理解
1)FLCK、HCLK和PCLK的关系
S3C2440有三个时钟FLCK、HCLK和PCLK
手册上说P7-8写到:
FCLK is used byARM920T,内核时钟,主频。
HCLK is used forAHB bus, which is used by the ARM920T, the memory controller, the interruptcontroller, the LCD controller, the DMA and USB host block. 也就是总线时钟,包括USB时钟。
PCLK is used forAPB bus, which is used by the peripherals such as WDT, IIS, I2C, PWM timer, MMCinterface,ADC, UART, GPIO, RTC and SPI.即IO接口时钟,例如串口的时钟设置就是从PCLK来的;
那么这三个时钟是什么关系呢?
这三个时钟通常设置为1:4:8,1:3:6的分频关系,也就说如果主频FLCK是400MHz,按照1:4:8的设置,那么HLCK是100MHz,PLCK是50MHz
寄存器CLKDIVN表明并设置了这三个时钟的关系
如果CLKDIVN设置为0x5,那么比例即为1:4:8,前提是CAMDIVN[9]为0.
2)输入时钟FIN与主频FCLK的关系
现代的CPU基本上都使用了比主频低的多的时钟输入,在CPU内部使用锁相环进行倍频。对于S3C2440,常用的输入时钟FIN有两种:12MHz和16.9344MHz,那么CPU是如何将FIN倍频为FCLK的呢?
S3C2440使用了三个倍频因子MDIV、PDIV和SDIV来设置将FIN倍频为MPLL,也就是FCLK
MPLL=(2*m*FIN)/(p*2^s)where m=(MDIV+8), p=(PDIV+2), s=SDIV
寄存器MPLLCON就是用来设置倍频因子的
理论上,你可以通过设置该寄存器来实现不同的频率,然而,由于实际存在的各种约束关系,设置一个适当的频率并不容易,手册上列出了一些常用频率的表格,
例如,假设输入时钟FIN=16.9344M,MDIV=110, PDIV=3, SDIV=1,
利用上面的公式,FCLK=2*16.9344*(110+8)/((2+3)*2)=399.65
3)关于USB时钟
S3C2440有两个锁相环,一个主锁相环MPLL提供给FCLK的,另外一个UPLL是提供给USB时钟(48MHz)的,与MPLL一样,UPLL的产生也是通过UPLLCON寄存器设置分频因子得到,计算公式稍有不同:
UPLL=(m*FIN)/(p*2^s)where m=(MDIV+8), p=(PDIV+2), s=SDIV,同样,可以通过查表得到一个合适的值。
最后值得一提的是,在CLKDIVN的第三位DIVN_UPLL用来设置USB时钟UCLK和UPLL的关系,如果UPLL已经是48Mhz了,那么这一位应该设置为0,表示1:1的关系,否则是1:2的关系
4)最后,给出我的BCNG2440开发板的时钟设置
FIN=16.9344
MDIV:110 HDIV:3SDIV:1,比例为1:4:8
CLKDIVN: 5
因此,FCLK=399.65MHZ HCLK=99.91MHZPCLK=49.95MHZ
2008-10-2419:06:26| 分类: 工作技术-嵌入式|字号 订阅
使用BCNG2440开发板,该板配置了AM29LV160DB的FLASH。
目标:实现vxworks下的tffs文件系统。
1、使用最新的bsp,构建一个新的工程,然后在vxworks配置选项中增加tffs的支持,以及tffs show的支持,重新编译内核,发现bsp目录下自动拷贝了一个新的systffs.c文件,然后报告编译错误,
E:\Tornado2.2\target\config\bcng2440\sysTffs.c:116:69:#error "sysTffs : Verify system mac
ros andfunction before first use"
不知道error宏是干什么用的,只好把这句话删掉。
哦,让第一次编译一定出现出错,提醒你进行各种宏定义,
先不管这么多的东西,把修改地址
#define FLASH_BASE_ADRS 0x00000000
重新编译,能够成功,下载,结果如下:
-> tffsShow
0: ****communication failed with error -1 ****
value = 48 =0x30 = '0'
->tffsShowAll
TFFS Version2.2
0: ****communication failed with error -1 ****
value = 48 =0x30 = '0'
把原始代码下的wamdmtd.c程序拷贝到BSP目录,在工程中添加改文件,重新编译内核,显示找不到.h文件,后来修改:
#include"tffs/flflash.h"
#include"tffs/backgrnd.h"
才能编译通过,同时修改代码,增加对我们的flash的支持,
#defineAm29LV160_FLASH 0x0149 /*added by xiyong*/
vol.type == Am29LV160_FLASH )
重新编译,下载,错误一样,在
进一步跟踪,发现根本没有进入flwAmdMTDIdentify函数:
更换wamdmtd.c文件名为ximtd.c
修改flwAmdMTDIdentify为xyflwAmdMTDIdentify,并且修改
MTDidentifyRoutinemtdTable[] =
#ifdef INCLUDE_MTD_WAMD
xyflwAmdMTDIdentify,
#endif
即使是这样,也没有调用xyflwAmdMTDIdentify
如何才能让tffs去调用自己的flash读写函数呢,是不是默认中已经有了一个库,去调用默认的库了。
后来,发现在vxworks内核配置中不能配置支持WAMD,并且在systffs中不能定义支持WAMD,否则会死机。
进一步证实,如果名字不改变为xyflwAmdMTDIdentify,也会死机,看来ximtd.c中的所有的接口函数都需要重新命名。
2、总结现在的情况
1)在BSP目录下Makefile包含ximtd.o
MACH_EXTRA =ximtd.o
2)systffs.c文件增加
#define INCLUDE_MTD_AM29LV160
去掉其他flash的驱动
3)tffsconfig.c文件修改
FLStatusxyflwAmdMTDIdentify (FLFlash vol);
函数申明,并修改
MTDidentifyRoutinemtdTable[] =
#ifdef INCLUDE_MTD_ AM29LV160
xyflwAmdMTDIdentify,
#endif
4)ximtd.c文件修改
#defineAm29LV160_FLASH 0x0149 /*added by xiyong*/
FLStatusxyflwAmdMTDIdentify (FLFlash vol)
{……
vol.type ==Am29LV160_FLASH ) /*xiyongadd here*/
vol.chipSize = 0x200000l *vol.interleaving;
5)重新生成vxworks内核,
去掉MMU的配置,添加tffs的组建,FTL,以及tffsshow等组件,编译下载,现在的现象是:
xiyong debug:tffsShowAllaa
-> TFFSVersion 2.2 noOfDrives 1
xiyongdebug:flwAmdMTDIdentify
xiyongdebug:flwAmdMTDIdentify vol.type is 49
0: ****communication failed with error -1 ****
终于调用了自己的函数
现在的问题是flash ID读出现了问题,本来应该读出0x0149,现在是高位不见了。
3、继续调试
2007-1-18
使用以前的8260的amd的代码,发现
vol.interleaving= 2需要改为1
现在能够显示了
->tffsShowAll
xiyongdebug:tffsShowAllaa
TFFS Version2.2 noOfDrives 1
0: socket=RFA:type=0x149, unitSize=0x10000, mediaSize=0x200000
value = 49 =0x31 = '1'
但是运行
tffsSysFormat死机
经过反复的调试,发现在擦除和写的操作中都需要关闭中断。
->sysTffsFormat
Debug: 5entering AMD MTD identification routine.
Debug:Identified AMD or Fujitsu flash media.
Debug: 5entering AMD MTD identification routine.
Debug:Identified AMD or Fujitsu flash media.
Debug: entering AMD MTD flwAmdMTDEraseroutine firstBlock 8 numOfBlocks 1.
Debug: flwAmdMTDErase flashPtr 80000
Debug: AMD MTD flwAmdMTDErase end.
Debug: entering AMD MTD flwAmdMTDWriteroutine address 80000 length 4c.
Debug: 2 flwAmdMTDWrite routine unlockAddr180aaa unlockAddr2 80554 data is 13 3 43 49 53
46 30 046 54
Debug: entering AMD MTD flwAmdMTDWriteroutine address 80014 length 2.
Debug: 2 flwAmdMTDWrite routine unlockAddr180aaa unlockAddr2 80554 data is 8 0 0 0 8 0 0
0 7c f4
Debug: entering AMD MTD flwAmdMTDEraseroutine firstBlock 9 numOfBlocks 1.
Debug: flwAmdMTDErase flashPtr 90000
Debug: AMD MTD flwAmdMTDErase end.
Debug: entering AMD MTD flwAmdMTDWriteroutine address 90000 length 4c.
Debug: 2 flwAmdMTDWrite routine unlockAddr190aaa unlockAddr2 90554 data is 13 3 43 49 53
46 30 046 54
Debug: entering AMD MTD flwAmdMTDWriteroutine address 90014 length 2.
Debug: 2 flwAmdMTDWrite routine unlockAddr190aaa unlockAddr2 90554 data is 9 0 ff 33 9 0
0 0 7c f4
………….
Debug: entering AMD MTD flwAmdMTDWriteroutine address 800fc length 4.
Debug: 2 flwAmdMTDWrite routine unlockAddr180aaa unlockAddr2 80554 data is fe ff ff ff 4
0 2a 0 0 2e 4
Debug: entering AMD MTD flwAmdMTDWriteroutine address 85c00 length 200.
Debug: 2 flwAmdMTDWrite routine unlockAddr180aaa unlockAddr2 80554 data is 0 0 0 0 0 0 0
0 0 0
Debug: entering AMD MTD flwAmdMTDWriteroutine address 800fc length 4.
Debug: 2 flwAmdMTDWrite routine unlockAddr180aaa unlockAddr2 80554 data is 40 2a 0 0 40
2a 0 0 2e 4
value = 0 = 0x0
-> d 0x80000
00080000: 0313 4943 4653 0030 5446 314c 30300100 *..CISF0.FTL100..*
00080010: 0001 0000 0008 1009 0008 0018 3a000016 *.............:..*
00080020: 0000 0001 0017 ff00 0000 0000 00000000 *................*
00080030: 0044 0000 ffff ffff ffff ffff ffffffff *D...............*
00080040: ffff ffff 0030 0000 0030 0000 d240ffff *....0...0...@...*
00080050: d440 ffff d640 ffff d840 ffff da40ffff *@...@...@...@...*
00080060: dc40 ffff de40 ffff e040 ffff e240ffff *@...@...@...@...*
00080070: e440 ffff e640 ffff e840 ffff ea40ffff *@...@...@...@...*
00080080: ec40 ffff ee40 ffff f040 ffff f240ffff *@...@...@...@...*
00080090: f440 ffff f640 ffff f840 ffff fa40ffff *@...@...@...@...*
000800a0: fc40 ffff fe40 ffff 0040 0000 02400000 *@...@...@...@...*
000800b0: 0440 0000 0640 0000 0840 0000 0a400000 *@...@...@...@...*
000800c0: 0c40 0000 0e40 0000 1040 0000 12400000 *@...@...@...@...*
000800d0: 1440 0000 1640 0000 1840 0000 1a400000 *@...@...@...@...*
000800e0: 1c40 0000 1e40 0000 2040 0000 22400000 *@...@...@..@"..*
000800f0: 2440 0000 2640 0000 2840 0000 2a400000 *@$..@&..@(..@*..*
value = 0 = 0x0
-> usrTffsConfig (0, 0,"/tffs0/");
Debug: 5entering AMD MTD identification routine.
Debug:Identified AMD or Fujitsu flash media.
value = 0 = 0x0
-> devs
drv name
0 /null
1 /tyCo/0
1 /tyCo/1
5 bcng:
6 /vio
3 /tffs0/
value = 0 = 0x0
->
对于ARM CPU来说,flash的操作,必须要关闭中断,
以前在powerpc上并没有这个问题。
4、MMU的tffs调试
2007-1-19
修改了flash的基地址为0xf0000000,然后调用tffsShow,报错;
还是首先修改flash.c应用测试程序,碰到的问题有:
1)
#define_WR(addr,data) *((U16*)(FlashBaseAddr+(addr<<1)) )=(U16)data
#define_RD(addr) (*( (U16*)(FlashBaseAddr+(addr<<1)) ))
增加一个基地址,但是基地址不能左移,否则就会报错;
发现只能读,而不能写,原来是syslib.c中对于flash的定义为禁止写的,打开即可。
2)这个时候发现MMU下的tffs已经实现了。
现在有一个问题,就是bootrom中的BSP对flash基地址的定义为0,而在vxworks中的定义为0xf0000000;
实际上,vxworks没有必要启动tffs,除非是实现自我更新。
LAN91C111 VXWORKS驱动设计详细过程(适合嵌入式初学者)
2008-08-0223:51:50| 分类: 工作技术-嵌入式|字号 订阅
一. 概述
根据项目需求,需要设计一个嵌入式数据转发板,CPU读取FPGA的采集到的数据,然后通过网络进行转发,使用TCP协议,最终的转发速率要求在12Mbps以上。另外,该设计需要满足工业级温度要求。操作系统使用vxworks。
以前设计的BCNG2440开发板,采用的是DM9000网卡芯片,DM9000网卡芯片是商业级的温度范围,因此,使用了SMC公司的LAN91C111芯片。与DM9000一样,LAN91C111也是一个10M/100M自适应的网卡芯片,然而,功能比DM9000多,内存管理比DM9000复杂,编程涉及的东西也多。更重要的是,LAN91C111使用的人较少,网络资料也较少,开发难度因此大了许多。
电路连接上,LAN91C111使用S3C2440的nGCS4片选线,基地址为0x20000000,中断号为EINT4,32位的数据线,不使用EEPROM,使用内部PHY。
有过调试vxworks下DM9000、8260网卡驱动的经验,也有过linux下调试网卡的经验,自觉得调试应该会比较顺利的。
调试的基本步骤是:1)寄存器基本读写调试(配合硬件调试)2)网卡寄存器配置与初始化调试3)基本的发送调试4)基本的接收调试5)中断调试6)和操作系统结合起来调试7)应用程序测试。
然而,好事多磨,原计划一个月的调试托了两个多月才调试完,历经了些曲折,利用假期把他些出来,希望对大家有所帮助,尤其是对于嵌入式的初学者。
从这个调试记录中可以看到,嵌入式的调试不但需要细致细心,而且需要毅力恒心,只要坚持下去,总是能够找到办法的。我想做任何事情都是如此。
二. 设计代码
从smc公司网站上下载的vxworks驱动代码为x86平台的,而我们的CPU为S3C2440需要进行代码移植。
代码修改总结如下:
1)添加修改底层的IO读写函数
/*xiyongadd the lower layer read and write function*/
#define sysInByte(port) (*((volatile char *)(port)))
#define sysInWord(port) (*((volatile short*)(port)))
#define sysInLong(port) (*((volatile long*)(port)))
#define sysOutByte(addr,value) (*((volatile char*)(addr)) = value)
#define sysOutWord(addr,value) (*((volatile short*)(addr)) = value)
#define sysOutLong(addr,value) (*((volatile long*)(addr)) = value)
2)添加对cpu的内存bank设置
/*xiyong add cpu memorybank control*/
BWSCON=0x221a99a2;
BANKCON4=((0<<13)+(1<<11)+(3<<8)+(0<<6)+(0<<4)+(3<<2)+0);
3)添加对CPU中断和管腿的设置
voidcpuForLAN911Init(void)
{
rGPFCON &=0xfeff; /* set pin GPF4 as EINT4 [9:8]=10*/
rGPFCON |=0x200;
/* high level triggered,[18..16]=001 high level*/
rEXTINT0 &=0xfff9ffff;
rEXTINT0 |= 0x10000;
rSRCPND =(rSRCPND|(1<<4));/*clear status for int4-7*/
rEINTPEND =(rEINTPEND|(1<<4));/*clear status forint4*/
rINTPND =(rINTPND|(1<<4));/*clear status for int4-7*/
rEINTMASK=(rEINTMASK&(~(1<<4) )); /*enableeint4*/
}
STATUSlan91c111Start
(
END_OBJ *pEnd /* device ID */
)
{
LAN91C111END_DEVICE *pDrvCtrl =(LAN91C111END_DEVICE *)pEnd;
/*xiyong add here, forinterrupt*/
cpuForLAN911Init();
SYS_INT_ENABLE(pDrvCtrl);
4)修改几个函数的ushort类型为ulong类型
UINTReadPhyRegister( USHORT IOBase, UCHAR phyaddr, UCHAR phyreg);
VOIDWritePhyRegister( USHORT IOBase, UCHAR phyaddr, UCHAR phyreg, ULONG phyregdat );
LOCALUSHORT InputMDO( USHORT IOAddress );
LOCALVOID OutputMDO( USHORT IOAddress, UCHAR lev );
LOCALUINT DetectPHY(LAN91C111END_DEVICE *Adapter ,USHORT IOBase);
5)修改confignet.h中的load参数:
#defineSMC911_LOAD_STRING "0x20000300:0x4:0x4:0x2:0x3100"
6)修改中断宏定义
/*xiyongmodify here*/
#defineSYS_INT_ENABLE(pDrvCtrl) { intEnable(pDrvCtrl->ilevel);}
#defineSYS_INT_DISABLE(pDrvCtrl) { intDisable(pDrvCtrl->ilevel);}
#defineLAN91C111_DEV_NAME "smc" /*xiyong modify here*/
8)修改发送中断,屏蔽netjobadd函数
//if(pDrvCtrl->pTxReadIndex->pMblk!= NULL)
// netJobAdd((FUNCPTR)lan91c111Send,(int)pDrvCtrl,0,0,0,0);
9)修改smc.h
定义
TX_PACKET0x20
RX_PACKET0x20
10)syslib.c添加对LAN91C111的内存映射,采用的是实地址映射
…..
{
(void *)0x20000000, /* LAN91C111 */
(void *) 0x20000000,
ROUND_UP (SZ_4M,PAGE_SIZE),
VM_STATE_MASK_VALID |VM_STATE_MASK_WRITABLE | VM_STATE_MASK_CACHEABLE,
VM_STATE_VALID |VM_STATE_WRITABLE | VM_STATE_CACHEABLE_NOT
},
……
三. 调试记录
1. 硬件和寄存器读写调试
预计两三天完成此步骤,实际用了一周多。
一般来说,每个芯片都有一个ID号,寄存器测试就是看是否能够将此ID号读出来,如果能够读出来,说明硬件的基本连接是正确的。
如果没有读出来,可能的原因有:1)硬件设计有问题,连接不正确2)器件有问题,买到了坏的芯片3)焊接的原因,例如虚焊,或短路4)软件的原因,例如读写时序配置不正确。
所以如果ID号读不出来,是相当麻烦的事情,因为各种原因都有可能,这也是嵌入式调试的难点所在,需要软硬件都非常的熟悉才行。
在设计硬件的时候,参考了网络上一些DSP和LAN91C111的电路图,基本上都是16位的,而我们为了加快访问速度,使用了32位的接口,没有现成的例子,只能自己推理了,设计的时候,觉得应该没有问题了。而当调试出了问题的时候,就开始怀疑自己的设计是否正确。
下面,我们来看看LAN91C111 datasheet手册。
LAN91C111芯片颇为复杂,与CPU的接口,可以同步传输,也可以异步传输,可以以8/16/32位的方式访问,手册上给出了三种总线接口例子,有两个是是用于同步传输的(使用了LCLK的时钟信号),还有一个是16位的ISA总结接口的,我们的设计与此类似。
各个信号的管腿接法如下:
LAN91C111 CPU S3C2440
A1 (悬空)
A2-A15 接CPU的A2-A15
D0-D31 接CPU的D0-D31
nRD 接CPU的nOE
nWR 接CPU的new
AEN 接CPU的nGCS4
INTR0 接CPU的EINT4
ARDY 接CPU的nWAIT
nBE0-nBE3 接CPU的WBE0-WBE3;
RESET CPU的nRESET通过反向得到
不使用的几个管腿的处理
nCYCLE、W/nR 、nRDYRTN、 LCLK 接VCC
nADS 接GND
还有几个信号悬空即可。
反复了检查了电路图,认为问题不大,唯一的就是16位和32位的区别了,网络上有个帖子上好像说LAN91C111的寄存器只能通过16位的方式访问,把我吓了一跳。
。。。。。下午使用仿真器调试,终于是能够读出ID号了,原来是寄存器设置错误。
BANKCON4=((0<<13)+(3<<11)+(7<<8)+(0<<6)+(0<<4)+(0<<2)+0);
也就是说片选必须要在OE发送之前!!!
此外,nADS低电平,LCLK高电平。
2008-06-2122:51:48| 分类: 工作技术-嵌入式|字号 订阅
2006年下半年,我们在自己设计的BCNG2440开发板上移植了VXWORKS。移植的过程参考了网络上一些BSP代码,与现有的代码不同的是,我们的BSP实现了内存的重映射,中断向量的重新分配;以及cache和MMU的开启。移植的目的是用于一个数据采集系统,ARM从FPGA读取解调数据,通过100M网络发送到服务器。经过测试,使用UDP发送速率达到了43Mbps,使用TCP发送速率为20Mbps。之前,我们在类似的项目中使用了MPC8260为CPU,当时TCP最高速率也只达到了20多Mbps,因此,我们认为在某些项目中使用ARM替代MPC8260是可行的。
移植的过程分为一下几个部分:
1)异常处理
2)中断
3)MMU
4)DM9000网卡驱动
5)启动参数保存问题
6)CPU设置
7)其他问题
通常来讲,在嵌入式系统启动之初,CPU将从0地址处(或ffff0000处)开始执行代码,因此初始时将ROM映射在0(或ffff0000)地址处,当进行完必要的初始化并启动异常处理机制后,会将RAM映射到0(或ffff0000)地址处,而将ROM映射到其它地址。
这是因为发生异常时,CPU会跳转到0地址开始处执行异常向量表。由于RAM的访问速度远远高于ROM,因此将RAM映射到0地址后,可以减少异常处理的延迟时间还有其他好处,动态和灵活等。
地址重映射可通过不同的方式实现,例如MPC8260可通过重新为每个memory bank 分配地址空间来实现,对于AT91RM9200,可通过设置其独有的“REMAP”控制位来实现。
ARM的体系结构规定在异常发生时,要从0 地址开始处读取相应的处理指令,然而S3C2440A的固定地址空间管理方法在VxWorks里会遇到问题。因为从硬件上讲,S3C2440A的地址空间是不能重分配的,它也不支持所谓的REMAP功能,一旦硬件连线决定了其RAM基地址为0x30000000(nGCS6),0地址上为ROM(nGCS0),就无法再更改。因此必须采用其他办法来解决异常向量表的访问问题。VxWorks管理的RAM中异常向量表结构如下图:
对于BOOTROM来说,不会使用到MMU,访问地址0就是访问ROM,因此需要将异常向量表建立在启动ROM的开始处。
基本思想是在Flash存储器的起始地址硬编码异常入口,仿vxWorks建立异常向量表。异常发生时,经Flash存储器入口,跳转到自定义函数,再跳转到RAM中异常入口,再调用vxWorks提供的异常处理函数。中断处理流程和中断向量表如下图示意。新异常向量表和原VxWorks设计完全一样。
romInit.c(下面是处理IRQ异常的例子,其它见源代码):
_romInit:
B cold
B _romUndef
B _romSwi
B _romPrefetch
B _romDataAbort
B _romReserved
B _romIRQ
B _romFIQ
_ARM_FUNCTION(romIRQ)
_romIRQ:
sub sp, sp, #4
stmfd sp!, {r0}
ldr r0, L$_promIRQ
ldr r0, [r0]
str r0, [sp, #4]
ldmfd sp!, {r0, pc}
L$_promIRQ:
.long S3C_EXC_BASE + 20
/* exceptionbase */
#defineS3C_EXC_BASE 0x30000100
config.c中定义:
#defineS3C_EXC_BASE 0x30000100
sysLib.c:
添加以下函数声明
voids3cExcVecSet(void);
IMPORTVOIDFUNCPTR excEnterUndef;
IMPORTVOIDFUNCPTR excEnterPrefetchAbort;
IMPORTVOIDFUNCPTR excEnterDataAbort;
IMPORTVOIDFUNCPTR excEnterSwi;
IMPORTVOIDFUNCPTR intEnt;
添加函数s3cExcVecSet()
voids3cExcVecSet(void)
{
int i;
i = (int)&excEnterUndef;
*((volatileint*)(S3C_EXC_BASE + 0x0)) = i;
i =(int)&excEnterSwi;
*((volatileint*)(S3C_EXC_BASE + 0x4)) = i;
i =(int)&excEnterPrefetchAbort;
*((volatileint*)(S3C_EXC_BASE + 0x8)) = i;
i =(int)&excEnterDataAbort;
*((volatileint*)(S3C_EXC_BASE + 0xc)) = i;
i =(int)&intEnt;
*((volatileint*)(S3C_EXC_BASE + 0x14)) = i;
return;
}
在sysHwInit()中调用s3cExcVecSet()
voidsysHwInit(void)
{
/* install theIRQ/SVC interrupt stack splitting routine */
_func_armIntStackSplit= sysIntStackSplit;
#ifdefINCLUDE_SERIAL
/* initialisethe serial devices */
sysSerialHwInit(); /*initialise serial data structure */
#endif /* INCLUDE_SERIAL */
s3cExcVecTblInstall();
s3cExcVecSet();
}
简单的描述上述代码的过程:当异常发生时,以IRQ异常为例,PC指针首先跳转到0x14处,读取指令“B _romIRQ”,然后进入到_romIRQ函数,此函数作用是将S3C_EXC_BASE + 20地址里面的值赋给PC寄存器(S3C_EXC_BASE + 20地址里面存放vxWorks的IRQ处理函数intEnt的地址,这由s3cExcVecSet建立),从而跳转到inrEnt函数处理IRQ异常。
_romIRQ函数比较复杂,其作用是将S3C_EXC_BASE + 20地址里面的值赋给PC寄存器,此段处理有一定的技巧,先将当前寄存器的内容及RAM中的中断向量的地址入栈,再从堆栈中将中断向量地址装载到PC,寄存器内容也从堆栈中恢复过来。
其实不经过堆栈直接将RAM中中断向量的地址装载到PC应该也可以,但是有人在论坛中发贴说这样做存在问题,我没有验证过,但是北理他们就是这样做的(但也通过r0转了一次),除此之外我所见到的所有s3c44b0和s3c2410的BSP都是采用前面描述的做法。
对于vxWorks映象,如果不采用MMU或者使用MMU但是进行平坦地址映射,ROM仍处于地址0,因此使用和BOOTROM同样的方式处理异常向量即可,无需对代码做其他修改。但是如果通过MMU进行非平坦地址映射(即SDRAM映射到地址0,以加快异常处理速度,ROM映射到某一空闲地址处,如0xf0000000),则需要在SDRAM起始地址处建立异常向量跳转表。
已经知道,在vxWorks内存的0x0100偏移处存在异常函数处理指针(由s3cExcVecSet()建立的),因此需要在SDRAM起始地址处填写如下代码(反汇编后形式):
ldr pc,=0xf00000fc /* 复位,此时无用,don't care */
ldr pc,=0x00000100
ldr pc,=0x00000104
ldr pc,=0x00000108
ldr pc,=0x0000010c
ldr pc,=0x00000110 /* 保留,don't care */
ldr pc,=0x00000114
ldr pc,=0x00000118 /* vxWorks不使用FIQ,don't care */
获取上面汇编代码对应的机器码0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4(至于为什么全是一样的数据,我也没搞很清除,估计应该是ldr的实现机理决定的)。
将这8个DWORD写入到SDRAM起始地址处即完成异常向量跳转表的创建;
在sysLib.c中添加函数
voids3cExcVecTblInstall()
{
int i;
long excVecTbl[]={0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4,0xe59ff0f4};
for(i=0;i<8;i++)
{
*((volatile unsignedlong*)(LOCAL_MEM_LOCAL_ADRS + i*4)) = excVecTbl[i];
}
return;
}
并在sysHwInit()中调用s3cExcVecSet()
voidsysHwInit(void)
{
/* install theIRQ/SVC interrupt stack splitting routine */
_func_armIntStackSplit= sysIntStackSplit;
#ifdefINCLUDE_SERIAL
/* initialisethe serial devices */
sysSerialHwInit(); /*initialise serial data structure */
#endif /* INCLUDE_SERIAL */
s3cExcVecTblInstall();
s3cExcVecSet();
}
其他一些说明:
对于异常向量表的建立和处理,本应该由vxWorks来完成,事实上,在bootConfig.c中的usrIni ()函数中,有intVecBaseSet()和excVecInit()函数调用,不过对于ARM体系的CPU来说,由于不存在vectorbase register,因此intVecBaseSet实际上是一个空操作(分析汇编可看出就是一个return);而由于调用excVecInit()函数时,处于0x100(异常函数处理指针)地址处的是ROM(此函数调用在MMU启用之前,因此即使在vxWorks映象中启用MMU并进行非平坦地址映射,0x100还是对应ROM),因此尽管excVecInit运行了,但是没起到实际作用,也就是没能在SDRAM偏移0x100处建立异常函数处理指针。
s3cExcVecSet()和rom中写入的异常向量编码(对于BOOTROM和vxworks平坦地址映射)以及s3cExcVecTblInstall(对于vxworks非平坦地址映射)实际上起到了取代excVecInit()的作用。
从bootConfig.c代码中可以看出,intVecBaseSet()和excVecInit ()之后紧接着调用了sysHwInit (),因此在sysHwInit调用s3cExcVecSet和s3cExcVecTblInstall。
(更多关于异常向量的内容可参见 “44B0x的BSP是如何调成的”以及Amine的“S3C44B0X VxWorks BSP 移植笔记”)。
ARM没有专门的中断控制器,对于中断的操作只能通过操作有限的几个寄存器实现。并且由于不同厂家生产的不同型号的ARM处理器提供的中断寄存器是不一样的,因此vxWorks没有为ARM提供中断控制的库函数,这样一来,就需要在BSP里面自己编写中断控制程序。
在我们的BSP里面,中断控制程序位于s3c2440aIntrCtl.c,程序比较简单,主要是进行中断寄存器的初始化以及实现并注册三个回调函数。但是由于中断控制实现的特殊性,有必要说明编写ISR时要注意的问题,建议编写ISR时此节必看。
1.关于中断enable/disable
对于s3c2440a,enable/disable中断本质上就是对相应中断的掩码位进行操作(INTMSK寄存器),但是在应用程序中应该调用intEnable()和intDisable()函数来实现,而不是直接操作INTMSK寄存器。
这是因为调用intEnable()和intDisable()时会通过回调函数最终调用到s3c2440aIntLvlEnable()和s3c2440aIntLvlDisable(),从源代码中可以看见,存在一个全局变量s3c2440aIntLvlEnabled,它记录了当前打开和关闭的中断的情况,enable和disable操作都是现对这个变量相应位赋值,然后将其赋给INTMSK寄存器。如果用写地址方式直接修改INTMSK寄存器,那么下一次调用intEnable()或intDisable()时会使直接修改的控制位会到以前的情况,从而使得中断控制操作无效。
这个问题在我调试中断时遇到过,当时使用直接操作INTMSK寄存器的方式,发现外部中断INT4-7对应的比特位在使能后不久就被disable了,后来使用ADS跟踪执行汇编代码,发现当执行printf语句时,由于printf向控制台打印信息会产生串口中断,在串口中断执行过程中使得INT4-7对应的比特位被disable掉,这才发现时直接操作INTMSK寄存器惹得祸。
2.带有子中断的中断操作
如串口中断等,存在发送中中断、接受中断和错误中断3个子中断。以串口中断为例,当中断发生时,会先执行s3c2440aIntLvlVecChk()函数(不是很清楚这个过程,但是跟踪汇编代码可以肯定这点),在s3c2440aIntLvlVecChk里面首先将UART中断的3个子中断全部禁止掉(通过设置INTSUBMSK对应控制位),然后清除SRCPND和INTPND寄存器中相应的比特位。接下来就进入到ISR,在ISR里面,通过读取SUBSRCPND寄存器,判断当前发生的是哪一个子中断,然后进行相应的处理并清除SUBSRCPND寄存器中相应的比特位,最后设置INTSUBMSK寄存器打开需要的子中断。
从上面的流程可以看出,中断的禁止与重新打开实际上是对子中断掩码进行操作来完成的,并没有涉及到INTMSK的操作(intEnable/intDisable)。
对于其它带有子中断的中断类型,建议也按照这个方式进行处理,具体可以参考串口驱动的相关代码。另外,由于目前不清楚其他带有子中断的中断类型的具体情况,因此s3c2440aIntLvlVecChk()函数里只有串口中断处理的相关代码,其它的情况暂时留空,如果需要编写相应的ISR,切记完善s3c2440aIntLvlVecChk()函数。
3.外部中断EINT4_7和EINT8_23
在INTMSK、INTPND和SRCPND寄存器里面,外部中断EINT4_7和EINT8_23分别只对应一个控制位,然后在EINTMASK、EINTPND再对具体是那一个外部中断进行处理。
与带有子中断的中断操作的中断操作类似,当某一外部中断产生时(以EINT6为例),先执行s3c2440aIntLvlVecChk()函数,在s3c2440aIntLvlVecChk里面首先将EINTMASK中EINT4~EINT7对应的控制位全部禁止掉,然后清除SRCPND和INTPND寄存器中相应的比特位。接下来在ISR里面进行了需要的操作之后,清除EINTPEND寄存器相应比特位,然后设置EINTMASK以打开EINT6的中断。
目前的操作是针对EINT4_7或EINT8_23中仅有一个外部中断发生的情况。以EINT4_7为例,如果存在多个外部中断源可能产生中断,则情况比较复杂,对这种情况没有做过测试,因此也无法得到切实可用的代码,但是可能的操作描述如下,仅供将来参考:在s3c2440aIntLvlVecChk()中禁止EINT4~EINT7对应的控制位(EINTMASK中)之前,先使用一个全局变量(例如eintMaskStatus)保存EINTMASK中EINT4~EINT7的当前值。对于ISR来说,需要使用一个总的ISR处理EINT4~EINT7所有的中断,在进入该ISR后,先通过读取EINTPND判断当前发生的是哪一个外部中断,接下来在完成相应操作之后清除EINTPND相应比特位,最后根据eintMaskStatus的值恢复EINTMASK中EINT4~EINT7的值(即打开相应的掩码位)。
这样做是因为EINT4~EIN T7显然处于同一优先级,当EINT4(例如)来中断时,必须禁止EINT5~EINT7。采用全局变量eintMaskStatus(在ISR所在源文件中要用extern声明)是为了确保不会误disable其它需要的外部中断。另外,在EINT4_7的ISR结束处恢复各个相应外部中断掩码时要注意不能干扰EINT8_23对应的外部中断掩码,反之亦然,以免产生误操作。
4.其它的中断(包括外部中断0、1、2、3)
其它的中断(包括外部中断0、1、2、3)在INTMSK、INTPND和SRCPND寄存器里面都有自己的控制位,因此操作相对简单。此时,若发生中断,由于s3c2440aIntLvlVecChk()中清除了SRCPND和INTPND寄存器中相应的比特位,因此在ISR中无需再做其它操作。不过在进入和退出ISR时加上intEnable和intDisable似乎也没什么问题。
5.例子
对于带有子中断的中断操作可以参照串口中断。
对于外部中断EINT4_7和EINT8_23,可以参照网络驱动(eint7)和intTest.c(eint6)。在intTest.c中,eintTestInit()、eTestInt()、eintTest()是外部中断6的测试代码。若在BOOTROM中进行测试,在syslib.c中合适位置处添加#include"intTest.c",在bootConfig.c的合适位置处添加eintTest()的声明,并在usrRoot()函数的最后一行代码(taskSpawn……)之前添加eintTest()的调用。另外必须在CPLD中将BOTTON1指定到EINT6(EINT6 <= not BOTTON1),并焊接RP552、D551和S551。当BOOTROM进行倒数计数时按任意键进入命令行模式,此时按botton 1可以在超级中断看见打印信息“interrupt:eTestInt6,entered”。对于其它的中断,以外部中断2为例,可以参考intTest.c(eint2),其中eintTestInit2()、eTestInt2()、eintTest2()是外部中断2的测试代码。若在BOOTROM中进行测试,在syslib.c中合适位置处添加#include"intTest.c",在bootConfig.c的合适位置处添加eintTest2()的声明,并在usrRoot()函数的最后一行代码(taskSpawn……)之前添加eintTest2()的调用。另外必须在CPLD中将BOTTON2指定到EINT2(EINT2 <= not BOTTON2),并焊接RP552、D552和S552。当BOOTROM进行倒数计数时按任意键进入命令行模式,此时按botton 2可以在超级中断看见打印信息“interrupt:eTestInt2,entered”。
MMU只在vxWorks中才使用,BOOTROM不会用到。在S3C2440A中使用MMU,需要异常向量处理的配合,详见“异常向量”相关部分的说明。
启用MMU时,要在TORNADO里面vxWorks组建配置串口包含hardwareàmemoryàMMUàMMU Modeàbasic MMU support或full MMU support。
sysLib.c中的sysPhysMemDesc可配置是使用平坦地址映射还是非平坦地址映射,使用非平坦地址映射时,由于异常向量跳转表存在SDRAM中,访问速度快于ROM,因此有利于提高系统性能。
若使用MMU时,需要正确配置sysPhysMemDesc数组。对任意内存空间的访问,必须在sysPhysMemDesc配置相应的项。若添加新的外设,该外设对应的内存空间必须在sysPhysMemDesc中配置。
网络芯片采用DAVICOM的DM9000E。驱动程序采用DAVICOM提供的v1.11版本,但是他们给的驱动是DM9000A并且基于x86的,移植自linux,并且其版本最后修正时间06年4月,存在不少问题,因此这方面的工作主要是对他们的驱动进行修改个完善,以适应我们的系统。
1.由于原来的驱动是基于x86的,因此对于DM9000_IN_ADDR、DM9000_IN_ADDR、DM9000_OUT_ADDR、DM9000_IN_BYTE、DM9000_OUT_BYTE、DM9000_IN_WORD、DM9000_OUT_WORD、DM9000_OUT_CHAR、DM9000_IN_CHAR这些宏需要重新定义。
2.关于CPU中相关寄存器的初始化。DM9000E所占用的memory bank的访问属性在BSP中的romInit中已经设置,在其驱动里面的不再做相关工作。为DM9000分配外部中断号、设置外部中断产生方式等操作在cpuForDM9000Init()函数里定义,该函数在dm9000Start()中调用。另外在dm9000Stop()中调用cpuForDm9000disable(),作用是关闭eint7对应的外部中断掩码。
3.在configNet.h中将DM9K_LOAD_STRING改为"0x4:0x4",这是CPU中EINT4-7的中断级别,另外删除dm9000end.c中dm9000Parse()函数里面关于IO Base的部分。因为arm不存在IO访问的说法。
4.原来的驱动是从SROM中获取MAC地址,我们不使用MAC地址,因此在程序里面定义全局变量DM9KdefaultMacAddr,用来记录MAC地址,并在dmfe_reset_dm9000()里面将其赋给dev->enetAddr。需要注意的是,当在以太网上使用多块板子时,要注意为每块板子修改MAC地址(重烧BOOTROM和重编译vxWorks),否则多块板子会使用同一个MAC地址,从而出现网络不同的情况。
5.32bit对齐问题。原来的驱动是基于x86的,x86不存在对齐问题,因为x86硬件可以自动将程序中没有对齐的地方对齐。但是移植到ARM上就有问题。主要是typedef struct end_device{…}END_DEVICE这个结构。它的最后一个成员txBuf没有处于32bit对齐的位置,这样在将其强制类型转化成word时(dmfe_start_xmit()函数发送数据时)会访问到非法数据。解决办法是在END_DEVICE结构体的txBuf成员以前添加
UCHAR dummyBytes[3];
从而使得txBuf处于32bit对齐的位置。DummyBytes不做其它用途。
typedef structend_device
{
END_OBJ end; /* The class we inherit from. */
END_ERR err;
UINT IOBase;
int unit; /* unit number */
int ivec; /* interrupt vector */
int ilevel; /* interrupt level */
long flags; /* Our local flags. */
UCHAR enetAddr[6]; /* ethernet address */
CACHE_FUNCS cacheFuncs; /* cache function pointers */
CL_POOL_ID pClPoolId; /* cluster pool */
BOOL rxHandling; /* rcv task is scheduled */
UCHAR io_mode; /* 0:word, 2:byte */
char tx_pkt_cnt;
char device_wait_reset;
UWORD queue_pkt_len;
UCHAR reg0; /* registers saved */
UCHAR nic_type; /* NIC type */
UCHAR op_mode; /* PHY operation mode */
UCHAR mcastFilter[8]; /* multicast filter */
UCHAR dummyBytes[3]; /* dummyBytes, for 32-bit align */
UCHAR txBuf[ETHERMTU +EH_SIZE + 6 + 64 ];
} END_DEVICE;
6.中断处理。在ISR里面要将x86中断操作的内容删掉,然后加入ARM下处理外部中断的操作。原理可过程可参考“中断”一节的内容,另外,目前的操作是当ISR退出时先开CPU中断,再开DM9000E的片上中断,不知道反过来行不行,会不会有什么影响。
7.发送处理。通过对比发现,原来的驱动移植自linux,但是移植的却不完全,一个很大的问题体现在发送处理上面。
DM9000E有两个发送缓存,当发送一个缓存中的数据时,可以向另一个缓存写数据,以加快传送速度。而DAVICOM提供的驱动里面,只用到了一个缓存:
static intdmfe_start_xmit( PKT skb, END_DEVICE dev) /*原驱动程序发送函数*/
{
… …
… …
… …
dev->queue_pkt_len = skb->len;
dev->tx_pkt_cnt ++;
/* Set TX lengthto DM9000 */
DM9000_OUT_CHAR( 0xfc, skb->len& 0xff );
DM9000_OUT_CHAR( 0xfd, (skb->len>> 8) & 0xff );
/* Issue TX pollingcommand */
DM9000_OUT_CHAR( 0x2, 0x01); /*Cleared after TX complete */
return 0;
}
这样以来,要是CPU向DM9000E发送数据的速度较快时,一个缓冲区会忙不过来,就会出现“txfull”,然后网卡工作不正常,无法继续进行通信。为了实现稳定告诉的通信,需要修改发送过程,参考linux下DM9000E的驱动程序,对vxWorks驱动修改如下:
static intdmfe_start_xmit( PKT *skb, END_DEVICE *dev)
{
… …
… …
… …
/* TX control: First packetimmediately send, second packet queue */
if (dev->tx_pkt_cnt == 0)
{
/* First Packet */
dev->tx_pkt_cnt ++;
/* Set TX lengthto DM9000 */
DM9000_OUT_CHAR( 0xfc, skb->len& 0xff );
DM9000_OUT_CHAR( 0xfd, (skb->len>> 8) & 0xff );
/* Issue TX pollingcommand */
DM9000_OUT_CHAR( 0x2, 0x01); /*Cleared after TX complete */
}
else /* Second packet */
{
dev->queue_pkt_len= skb->len;
dev->tx_pkt_cnt++;
}
return 0;
}
还要修改dm9000Int()
static voiddm9000Int( END_DEVICE *pDrvCtrl ) /* interrupting device */
{
… …
… …
… …
/* TODO - handle transmit interrupts*/
if( isr_status & 2 )
{
DRV_LOG (DRV_DEBUG_INT, "Got anTX interrupt!", pDrvCtrl->tx_pkt_cnt, 2, 3, 4, 5,6);
DRV_SP( DRV_PK,"x" );
DM9000_IN_CHAR(0x1, TX_comple_status);
/* if (TX_comple_status & 0x4)pDrvCtrl->tx_pkt_cnt--;
if (TX_comple_status & 0x8)pDrvCtrl->tx_pkt_cnt--; */
if (TX_comple_status & 0xc)
{
/* One packet sent complete */
pDrvCtrl->tx_pkt_cnt--;
/* Queue packet check & send */
if (pDrvCtrl->tx_pkt_cnt > 0)
{
DM9000_OUT_CHAR( 0xfc,pDrvCtrl->queue_pkt_len & 0xff );
DM9000_OUT_CHAR( 0xfd, (pDrvCtrl->queue_pkt_len>> 8) & 0xff );
DM9000_OUT_CHAR( 0x2, 0x01 );
}
}
}
… …
… …
… …
}
修改后,不会再出现网络不稳定的情况(并且可以ping大包了)。使用赵海涛的程序测试网络速度(TCP),发现使用MMU时(非平坦地址映射),可达22Mbps左右;关闭MMU但是使能cache时,略大于10Mbps;同时关闭MMU和cache,只有略大于6Mbps的速度。使用平坦地址映射MMU模式下的网络速度没有测试。
8.延时问题。linux的驱动采用uDelay进行延时,但是DAVICON提供的vxWorks驱动对这个问题处理不好,从而使得延时过长,影响运行速度。考虑到默认情况下taskDelay(1)延时0.01s,已远大于DM9000任何操作要求的最小延时,因此在需要延时的地方改为taskDelay(1)。