基于uboot的2410调试平台的实现

2010年04月01日 17:31    发布者:lelee007
--免烧写nand flash & 不用仿真机

哈哈,平台!

名字起的有点大,列位看官莫笑。这年头,就那么回事。

如果内容对您有什么用或者帮助,鄙人甚感欣慰;如果没什么大用,首先,您水平肯定在鄙人之上,其次,就当是看闲书了,虽然笔者笔风不是那么诙谐。

其实这个东西对于熟悉ARM的人来说,真不是什么难事,太小case了。确实,笔者自己也感觉不过是雕虫小技而已。但是为什么要写出来了?!因为笔者也是从51转到ARM上来的,开始的时候对这个问题困惑了很久,而且就是找不到答案。同时总结出一个问题,不要迷信网络!网上并非要什么有什么!!!RMB,很明显无法从网上找到。如果从网上实在找不到答案,那么潜心研究研究,可能比网上“盲目”乱找收获更大。这里之所以给“盲目”二字打上个小引号是因为,这里的盲目并非指您不知道要找什么而去找,而是指您不知道网上到底存不存在您要找的东东,就比如笔者最近总是时不时在网上找关于X11R7.5下ATI显卡驱动的解决方案,其实AMD官方都没给出答案,民间又怎么折腾了?如果没有,您还要浪费时间跟那死找,..........不过经过笔者本次撰述,您就可以在网上找到答案了,KAKA~~~~~~~

其实要笔者写点东西也确实难,笔者自己也知道废话多,但是木有办法,真正内容就那么点,不扯点废话,湊不齐篇章。

废话到此为止,下面言归正传。

关于这个东东,开始的时候,笔者是因为木有钱买仿真机,而且被一遍一遍的烧写nand flash折腾的很烦躁,因为nand flash的烧写速度并不像下载到SRAM或者SDRAM里边那么快。而且相当nand的寿命有限,烧写有风险,每次都是heart hard-beating下完成的,生怕nand挂了或者CPU挂了,sigh......生亦何哀,死亦何苦。有痛如斯,夫复何求?!无奈当时对于ARM的MMU还不是很熟悉,而且当时是一边上班一边业余折腾,遇到问题了就有点躁。痛定思痛,长痛不如短痛!咬着牙花了一晚上把MMU看了两遍,结果发现有好几种配置方式,让人抓狂哇!哈哈,想想当时真的很傻很天真,就因为有多种配置方式,段式,页式,页式还分个粗细,就不知道到底该用哪个更合适,后来想到linux下用哪个方式咱就用哪个方式,然后抱着这个想法去看linux内核代码,结果不了了之--没看明白,HOHO~~~~~~~~不过后来是在一个关于ARM MMU的例程中找到了定心丸,就用段式映射,这个最简单!当时还不知道看SAMSUNG的代码,很多代码都是网上杂七杂八搜罗过来的。原理弄明白,方案定下来之后,事情就好办多了,一步一步实施就是了,无非是代码出问题了再调试。

废话是不是有点多?不过笔者应该是把事情的背景都交代的一清二楚了。接下来就是具体的方案了,请各位看官务必擦亮眼睛,精彩不容错过!

原理其实是这样的,首先移植一个可以用的uboot,至少要包含tftp和go命令,然后将其烧到nand flash里边,每次系统上电的时候能顺利运行uboot;然后我们将编译链接好的目标代码通过uboot下载到SDRAM里边,再从uboot里边go到我们自己的程序去运行。
实施过程中遇到的几个问题如下:

1、代码的存储位置和运行位置的问题
2、中断向量表的位置问题
3、中断入口配置

第一个问题中关于两个位置的问题,这应该是连接器要处理的,这个问题不是这里的阐述重点,有兴趣的可以参考《arm学习报告》系列文档,里边基本讲的非常详细,而且不像GNU Ld那么长篇大论。虽然这个问题不是咱的重点,但是多少对咱是有影响的,不然.............讲讲到底怎么影响咱的是正事,废话就不扯了,嘿嘿,因为,废话已经扯了很多了,GAGA~~~~~~~~~。因为从原理上来看,我们自己编写的程序用这种方式来调试的话,就不可能再放到0地址开始,让系统自动加载了,因此存储地址和运行地址都不能直接用默认的0了,这个地址需要我们在链接脚本里边亲自指定一下。为了节省大家时间,笔者在尼度给俩例子吧,一个是源代码里边的链接脚本文件,一个是链接脚本的书写规则。

SECTIONS {
.text
0x30004000
:
{
head.o
clock.o
init.o
led.o
serial.o
timer0.o
mmu.o
interrupt.o
main.o
}
}

这个是链接脚本,其中的 0x30004000地址前面的text是指如下内容全是文本段。关于文本段如果您不想看别的资料,就简单的理解成是代码段吧。实际也是代码的运行地址,更确切的说是运行地址的开始,就是我们目标代码的入口地址。链接以后,程序在执行时的一些相对跳转中,这个地址就是个基地址了。如果在把程序从别的介质加载到运行内存(SDRAM)时,地址发生了错误,有些程序就无法正常执行,这就是位置相关和位置无关代码的区别。

下面是我注释的一个lds文件的书写规则,估计大多数看官都能很快看明白,当然,能把lds的书写规则系统的研究研究,绝对是大有裨益。



可能以上内容讲的不够详细,但是木有办法,如果这些东西对于您理解这些东西来说还不够的话,那么强烈建议您认真阅读下《arm学习报告》系列文档,共有3篇,《arm学习报告001》、《arm学习报告002》、《arm学习报告003》,因为那个里边对这个分析的是比较到位的,而且篇幅并不大,绝对值得品味的,笔者也就不再好意思再搁这废话了。

之所以讲以上关于链接的问题,就是因为我们的程序最后不可能放到0地址,然系统一上电就自动去运行,而是要放到SDRAM里边去,然后从uboot里边go过去。

如果是一个非常简单的程序,不涉及中断的,那么只讲讲上面的内容,加上笔者推荐的几篇文档,差不多足够了。但是这样玩起来就太没意思了,只够点个流水灯而已!如果加上中断,那差不多就把ARM的体系全弄明白了吧。接下来就切入ARM的中断。
ARM的中断体系实际上也不复杂,向量中断一共就那么8个,reset一个,几乎是个CPU都会有这玩意,就像男人的撒尿工具。然后就是什么未定义的一个,软中断,预取终止,数据终止,中间还有个未使用的,后面就生外部中断和快中断了。这8个里边我们用到最多的也就是reset、外部中断这两个,连快中断都比较少用。

reset就是一个入口,CPU在上电的时候先找她!如果您有什么工作希望CPU在上电之后就做的话,您也找她!

外部中断的入口用处是非常强大的,因为一切外部中断源都要经过他。2410上的外部中断实际上是这样安排的,首先在系统的向量中断中安排了外部中断这一级,然后在外部中断中又安排了下一级的中断表,这一级就不再是向量式的了,但是这一级的中断入口都是隔4个字节放置一个,即每个入口的地址用4个字节来描述。这就是2410的二级中断表。第二级的中断表其实每个地址也是固定对应一个中断源的。算了,还是废话少说,上代码。

_start:
@ 0x00: 中断向量表并非从0地址开始放置,因为我们使用的直接SDRAM调试时,中断入口是需要通过MMU来映射的
@Reset:
b
Reset
@直接在SDRAM中调试的话,实际是不使用Reset的,因此一Reset,硬件系统将从nand flash中读取uboot来执行,所以此处实际是个空语句处理
@ 0x04: Undefined instruction exception
HandleUndef:
b
HandleUndef
@ 0x08: Software interrupt exception
HandleSWI:
b
HandleSWI
@ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort)
HandlePrefetchAbort:
b
HandlePrefetchAbort
@ 0x10: Data Access Memory Abort
HandleDataAbort:
b
HandleDataAbort
@ 0x14: Not used
HandleNotUsed:
b
HandleNotUsed
@ 0x18: IRQ(Interrupt Request) exception
ldr
pc,HandleIRQAddr
@
ldr
pc,=HandleIRQ
@ 0x1c: FIQ(Fast Interrupt Request) exception
HandleFIQ:
b
HandleFIQ

这几行是最基本的,不用讲也该明白。
该文章有附件资料,如需下载请访问 电脑版

网友评论

lelee007 2010年04月01日
接下来就是咱们的二级中断表了。

        HandleEINT0:
       .long      0
       HandleEINT1:
       .long      0
       HandleEINT2:
       .long      0
       HandleEINT3:
       .long      0
       HandleEINT4_7:
       .long      0
       HandleEINT8_23:
       .long      0
       HandleRSV6:
       .long      0
       HandleBATFLT:
       .long      0
       HandleTICK:
       .long      0
       HandleWDT:
       .long      0
       HandleTIMER0:
@   .long      Timer0_Handle
       .long      0
       HandleTIMER1:
       .long      0
       HandleTIMER2:
       .long      0
       HandleTIMER3:
       .long      0
       HandleTIMER4:
       .long      0
       HandleUART2:
       .long      0
       HandleLCD:
       .long      0
       HandleDMA0:
       .long      0
       HandleDMA1:
       .long      0
       HandleDMA2:
       .long      0
       HandleDMA3:
       .long      0
       HandleMMC:
       .long      0
       HandleSPI0:
       .long      0
       HandleUART1:
       .long      0
       HandleRSV24:
       .long      0
       HandleUSBD:
       .long      0
       HandleUSBH:
       .long      0
       HandleIIC:
       .long      0
       HandleUART0:
       .long      0
       HandleSPI1:
       .long      0
       HandleRTC:
       .long      0
       HandleADC:
       .long      0

这个表实际上仅仅是声明了一个long型的量,先占上4个字节,具体的内容将由我们自己来填写。填写什么内容好了?!当然是对应的中断的处理函数了。从标号可以看出来,这些是用来处理中断的,你说不填写中断处理函数,填什么了?!至于中断函数怎么填,这个是C语言的问题了。

光有这俩表是远远不够的。因为还有很多问题没解决。首先,表放到那儿?其次,中断来了怎么找这个表?其实这俩问题合起来也算是一个问题,系统如果知道表在哪儿了,那肯定能找到;但是也不一定就真的只是一个问题,如果你放的位置不正确,那么就算系统知道在哪儿,他如果跑不过去,那也不算找到了!

表放哪儿的问题很好解决,2410手册里有讲,向量中断表只有两种放法,低地址和高地址!低地址就是从0开始存放,高地址就是从0XFFFF0000开始存放。这个是通过配置协处理器寄存器实现的。有两种放法,有两种放法?有两种放法?!倒塌!!!哪种放法好?哪种更合适?到底怎么放好啊~~~~~~~~~~~~~别急,看需求,看习惯。如果你不启用MMU,高端是没法用的,因为你的系统肯定不会在高端地址处配置一个ROM。那就只能用低端了呗。如此以来,可以把表放到2410的小石头里边去了。看来不开启MMU也挺好,就那么一种选择,省去不少事。不过笔者这里要介绍的是开启MMU之后的用法。当然开启MMU之后就比这个灵活多了,您想用高端用高端,想用低端用低端。只需要将您选定的地址映射到物理内存中就O了,比如咱想放低端,就是0开始的地址,那咱用MMU把逻辑0地址给映射到SDRAM的0X30F00000,那么在程序启动之后就把中断表copy到0X30F00000处,然后配置MMU映射表,最后开启MMU,O了!放高端的话,以此类推。当然具体地址咱可以随便定,但是也不能太随便了,免得自己给自己找麻烦。MMU映射表的代码可以在附件中找到,就是MMU.c中。Copy中断表的代码这里可以列出来

void       copy_vectors_to_high()//by lelee,用于SDRAM调试的时候,copy中断向量表,实际是从SDRAM里copy,而不再是从nand copy 了

{
       unsigned int i = 0;      
       for(i = 0;i < 128;i++){
              (*(unsigned int *)(0x33ff0000 + (i << 2)))=(*(unsigned int *)(0x30004000 + (i << 2)));
       }
}

就这么简单,中断表搞定了。

最后一个问题就是中断入口的配置问题了。这里是外部中断扩展出来的真正的外部中断源的入口配置问题,这个很关键。中断处理流程请查找2410手册,不过这里可以简略介绍,省去您找手册的麻烦。

HandleIRQ:
       sub  lr, lr, #4         @计算返回地址
       stmdb    sp!,        { r0-r12,lr }  @保存使用到的寄存器
@added from a-nan
       sub  sp,sp,#4        @堆栈指针减4,空出一个单元,以便后面放入PC需要的字 reserved for PC
       stmfd     sp!,{r8-r9}           @r8,r9入栈,此时存储r8,r9的两个堆栈单元下面的一个单元是空的
@   ldr   r9,=INTOFFSET       @INTOFFSET寄存器中存放中断源号,标明是哪个中断,CHIP规定死的,相当于查表。
       ldr   r9,=0x4A000014
       ldr   r9,           @
@   ldr   r8,=HandleEINT0      @入口可以随便放,与中断向量表没有关系
       ldr   r8,=0xffff0024
       add r8,r8,r9,lsl #2       @中断号乘以4,然后加上HandleEINT0,得到的将是该中断的入口
       ldr   r8,
       str   r8,             @把得到的中断向量的内容(timer0Handler的地址)放入最开始空出的堆栈单元,由于这个单元在r8,r9下面,所以位置是sp+8
       ldr   lr,    =int_return   @设置返回地址      
      ldmfd    sp!,{r8-r9,pc}      @把堆栈顶部的三个单元分别出栈到r8,r9,和pc,此时pc会跳转到中断向量里存储的地址,也就是timer0Handler
@added from a-nan
@   ldr   lr,    =int_return   @设置返回地址      
@   ldr   pc, =Timer0_Handle @调用中断处理函数,在interrupt.c中
int_return:
       ldmia     sp!,        { r0-r12,pc }^      @中断返回, ^表示将spsr的值复制到cpsr

我的解释看明白了没有?嘿嘿

实际上外部中断是用向量中断里的一个中断来扩展的,然后有用于扩展的寄存器,比如 INTOFFSET,里边的10进制数表明了是顺序为多少的中断发生了,外部中断来了之后,先读该寄存器,然后判断往哪儿跳。跳的过程就是先把 INTOFFSET读到r9里边去,然后把二级表的开始地址读到r8里边去,接着将r9里边的值乘以4再加上r8里边的值,结果还是放到r8中,r8中的值就是我们最后要跳转的目的地址,然后压栈,跳转就是靠弹栈实现的,将r8的值弹到pc里边去,下一条指令的时候,就跳到r8的值指定的地址进行取指译码之类的操作了。后面的几条指令就是设置中断返回地址了。

最后再废话一点点,关于二级表的位置。void     copy_vectors_to_high()这个函数里边实现了将向量中断表和二级中断表一起copy到高端地址。在copy的时候,MMU木有开启,中断也给屏蔽了,所以看代码可以看出,copy过程中使用的地址都是物理地址。二级表的填充就很easy了,对应的各个表项地址都声明了对应的名字,由预定义完成的,如何填写,这也是C语言的问题。不赘述。由于二级表可以在程序中随意配置,所以也可以叫动态配置的二级中断表。比如拿代码中的timer0来作例子说事,我们可以在初始化函数中这个样子:

void Timer0_init()
{
       TCFG0 = 119;     //Prescaler0 = 119              
       TCFG1 = 0x03;   //Select MUX input for PWM Timer0:divider=16
//     TCNTB0 = 3125;       //0.5秒钟触发一次中断
//     TCNTB0 = 13020;     //0.5秒钟触发一次中断
       TCNTB0 = 26040;     //0.5秒钟触发一次中断
//     TCNTB0 = 3255;
//     TCON |=  (1<<1);     //Timer 0 manual update
       TCON = 0x02;
       TCON = 0x09;    /*Timer 0 auto reload on
                       Timer 0 output inverter off
                       清"Timer 0 manual update"
                       Timer 0 start */
//     ISR_TIMER0 + 4 = (unsigned int)Timer0_Handle;
       ISR_TIMER0 = (unsigned int)Timer0_Handle;
}

然后在 Timer0_Handle()函数中再将 ISR_TIMER0的内容给换了,比如:

void Timer0_Handle()
{
       /*   
       if(INTOFFSET == 10){
              GPBDAT = ~(GPBDAT & (0xf << 7));
       //     timer0_flag = !timer0_flag;
       }
       */
       if (timer0_flag == 1)
              {
                     timer0_flag = 0;
              }
       else
              {
                     timer0_flag = 1;
                     ISR_TIMER0 = (unsigned int)Timer0_Handle001;//动态配置中断服务程序入口
              }

       if(timer0_flag){
              //wait(100000);
              //led_red_on();
              led_grn_on();
              //wait(100000);
              led_red_off();
              //led_grn_on();
              printk("This is Timer0_Handle!!!\n\r");
       }
       else{
              led_red_on();
              led_grn_off();
       }
       //清中断
       SRCPND = 1 << INTOFFSET;
       INTPND = INTPND;      
}

差不多,就这么着,可劲的折腾吧。

最后总结一下,其实很简单,如果对编译链接理解透了,同时对MMU和2410的中断表的放置熟悉了,就足够折腾出这玩意了。有了这个,咱调试起来就方便了,新增加的程序,或者其他要使用的中断,加进来就是了。程序编译完了,通过uboot DOWN到SDRAM的0x30004000地址处,这个是链接地址,程序的入口在这里,入了口之后,此地址之前的16K空间是预备后来初始化MMU时存放映射表的。因为要16K的空间存放映射表,所以程序入口选择放在SDRAM的16K地址之后开始存放。SDRAM的开始地址是0X30000000。DOWN完之后再从uboot里边go 0x30004000,好了,接下来就不再是运行uboot了,运行的就是咱自己的程序了,跟uboot半点关系都木有了。这么下来,是不是发现很简单?!确实是雕虫小技。

附件中是相关代码,其中有readme.txt,里边有告诉怎么操作。如果感兴趣又没全看明白的,给俺发邮件,最好是搁公社里边白话。
lelee007 2010年04月01日
晕翻,插入的图片位置不对头
huizijingxin 2010年04月01日
丽丽gg牛
忘情天书 2010年04月01日
赞一个!
f.luo 2010年04月01日
丫丫的,原本以为3月都在几大牛人都现身了,我四月再冒泡泡,现在看来亏了!再等等!
lelee007 2010年04月01日
有一点东西大意了,代码里边关于LED控制的GPIO

哈哈,如果板子上的配置跟俺不一样的,可得把代码仔细改改了
dddg 2010年04月01日
:D   这个语文水平,有待提高...  还是肉皮语文好一些.
lelee007 2010年04月01日
咱是抛砖引玉:lol:victory:
linux_Ultra 2010年04月01日
对于三星24xx这个分支uboot 一直都不用mmu和caches吧?
龙龙 2010年04月01日
这么有技术含量的东西也放灌水菜园里~~拆迁办的呢!!!赶紧的!!{:4_83:}
f.luo 2010年04月01日
嗯,等待拆迁办的出现。
lelee007 2010年04月01日
uboot中,MMU是没开启,cache那部分的代码没研究过
admin 2010年04月02日
这是lele的参赛文章?
lelee007 2010年04月02日
恩啦,哈哈,写的比较糙,见笑了
老郭 2010年04月02日
昨天没看到。
俺把你那些没有的空行删掉了,这样更紧凑些。图片也插到地方了。
很不错,和阿南、RP的风格不一样
lelee007 2010年04月02日
娃哈哈,还是老郭识货哇,嘿嘿

每段的缩进没了
lelee007 2010年04月03日
跟rp说的过了泼水节似的
f.luo 2010年04月03日
放假嘛,大多去玩了。
lelee007 2010年04月03日
哈哈哈哈

看看放假之后还有木有人过来翻
2010年04月07日
不怎么明白啊 看来俺水平太菜了
唉 得学学ARM了
原野之狼 2010年04月07日
得 忘了登陆了
backup 2010年04月12日
好文章,
不过要在SDRAM中调试,为什么不直接用JTAG接口+H-JTAG呢?
弄个SDRAM初始化脚本即可。
51rf 2010年04月14日
好,贊一个!
lelee007 2010年04月15日
to backup,H-JTAG也得要钱哦,呵呵

这个直接把所有的硬件都去了,还可以在SDRAM里边调试

当然如果有调试工具的话,肯定比木有工具方便
f.luo 2010年04月16日
技术帖,力顶。
lelee007 2010年04月18日
哈哈,多谢大家捧场

这几天光顾看那几帖了

不过貌似大家对这个不怎么感冒哇,还是故事片有引力