查看: 5308|回复: 4

在百度空间上看到了阿南ARM训练班的课堂总结,转过来正好

[复制链接]
发表于 2009-7-27 12:28:20 | 显示全部楼层 |阅读模式
关键词: arm , 阿南 , 百度 , 课堂 , 训练班
阿南ARM训练班课堂总结(1)

2009年03月02日 12:47

由于距离等等一大堆方面的原因,是否参加阿南的训练班着实犹豫了一阵子,如今得以如愿,自然分外珍惜,写个总结,认真的缕一下头绪,有不对的地方也希望阿南以及其他前辈们指正一下....

个人一向喜欢从零开始学习或者思考一个问题,想当初买回开发板做的第一件事就是把Flash擦个干净,上了课之后,觉得这个“零”还得再往前追溯一点:一块2410,一块NandFlash-k9f1208,两块SDRAM-k4s561632,现在要搭建一个最小系统:

第一课,主要讲的就是分析硬件原理图。

一、如何看Datasheet?
首先要面对的自然是芯片的DataSheet,对于一个完全不了解的芯片,主要先看以下几个方面:

1).preview简介。大概的了解一下这款处理器有什么功能,包含了什么模块等等。

2).memory内存映射图。对于写程序的人来说,这个东西是相当重要的。

3).管脚的分布及其功能。阿南特别强调了某些管脚不可以随便悬空,如nWait用于延长总线周期,如果没有使用必须将其上拉才能释放总线。

4).其他的部分可以在今后的开发中用到了再有针对性地看。

二、最小系统基本构成
跟一个单片机系统差不多,只不过是单片机把存储器集成到芯片里面了,但是这里什么都得自己来。
1).MPU
2).Flash(NOR/NAND)
3).RAM(SDARM/DDR/DR2)
4).Power and Clock(PLL)
5).Reset circuit

三、SDARM接口设计
查看DataSheet,主要有以下几种管脚:
1).数据线DQ0~DQ15
这个与2410的数据总线对应连接就可以了。

2).行/列地址线A0~A12
由于2410是按照字节寻址,但SDRAM是16Bit半字宽度,所以连接时需要错开一位,将A0连到处理器的A1,而这个系统是通过两块k4s561632串联实现32Bit数据宽度,所以需要进行字对齐连接,将A0接到A2地址总线上,其他顺次接。

3).Bank地址线BA0~BA1
在2410的手册里面,有个SDRAM BANK ADDRESS PIN CONNECTION表,里面规定了不同组织结构的SDARM的Bank地址线。所以首先要明白存储器的组织结构,看K4S56163的芯片手册,标注:4M*16Bit*4Banks。
这表示每个Bank有4M大小,即由行列地址确定的存储单元个数为4M个,16Bit是位宽,即每个存储单元存储的数据大小为16bit半字,一共包含有4个Bank,所以这个SDRAM的总容量为32MB(256Mbit)。根据这个就可以确定Bank地址线为A[24:23]。
再说一下SDRAM的访问过程,大概是这样的:首先送出Bank地址,然后再依次送出行列地址确定需要访问的存储单元,最后进行数据交换。

4).控制引脚
由于2410集成了SDRAM的管理器,所以接到相应的管脚就是了,主要是行选通RAS,列选通CASE,写使能WE,LDQM和UDQM数据I/O屏蔽用于在读模式下控制输出缓冲,在写模式下屏蔽输入数据,CS片选接在nGS6,即是把SDARM挂接在Bank6上,另外还有时钟控制线,电源等。

四、NandFlash接口设计
NandFlash与NorFlash不同,它是非线性的存储器,所以它应该不是映射到2410的内存空间上的。2410有专门的NandFlash接口管脚,相对比较容易一点,八位数据线直接与处理器的数据总线相连,ALE地址允许,CLE命令允许,CS片选使能,读/写使能,R/B等几根线都有对应的管脚相连。由于NandFlash共用八位数据线传输地址和数据,所以2410上有个NCON引脚用于选择地址的周期,一般情况下3个周期就可以完成24位的寻址范围。
关于NandFlash的启动,如果将芯片设置成NandFlash启动,会有4K大小的SRAM映射在0x0地址处,上电后硬件自动将前4K大小的代码搬到这里,首先从SRAM中运行程序,在这4K代码里需要完成将其他代码拷贝到SDRAM中,最后令程序在SDRAM中运行。

五、电源、时钟和复位电路
提到一个,设计电源的时候先让外围电路通电,最后才让处理器上电,大概与正确的开电脑顺序是相同的,主要考虑的是万一处理器已复位完成开始运行时,但要访问的外围器件还未准备就绪,就有可能会发生意外。

大概先这么多吧,其他的想到再来补充......
 楼主| 发表于 2009-7-27 12:28:54 | 显示全部楼层
阿南ARM训练班课堂总结(2)

2009年03月11日 11:11

硬件PCB完成之后,需要首先对各个单元电路进行检测调试,这一课主要讲的就是硬件系统的调试。

一、电源、晶振及复位电路
在给PCB上电之前要确保电源供电的可靠性,排除短路问题等等。对照PCB版图找到相应的测点的位置。
在Protel99SE下,选择Tools->Preferences...(快捷键T+P)下的Show/Hide选项卡,这里可以选择隐藏一些显示,以利于观察布线,一般将覆铜(Polygons)隐藏。
在Browse PCB选项卡下可以选择查找特定的元件、节点等,直接输入元件(节点)名称选择或跳转到该元件的位置上。
按快捷键S+P在出现十字手形后单击某一条线路可以高亮显示所有与其相连的通路。按X+A取消所有选择。Shift+S可以隐藏或显示除当前层外的其他层布线。
排除了短路问题之后就可以上电测试了。使用万用表电压档依次测试一些关键节点的电压值,确保电源供电正确。晶振电路需要使用示波器观测波形。复位电路通过检测nRSTOUT引脚,在正常工作下输出为高电平,按下复位键后输出低电平。

二、检查内核
接入Multi-ICE仿真器看是否能检测到内核,如果不能检测到内核,原因可能是电源供电或晶振电路不正常,一些不能随便处理的引脚(空置时必须接高或低),仿真器接口线没有正确连接等等,相应排查各个出错的原因。

三、SDRAM接口电路调试
到上面那一步处理器基本可以工作了,接下来是要调试存储设备。通过AXD看是否能够正确读写SDRAM所在的位置。对于一个裸板,对应于存储器的寄存器设置是未定义的,所以要调试SDRAM存储器系统,首先应配置相关的特殊功能寄存器,使系统中的SDRAM能被正确访问。主要是BWSCON总线配置,以及各个Bank的配置寄存器BANKCONn。
这里使用三星官方提供的初始化文件ADS2410boot.ini。
打开AXD->System Views->Command Line Interface(快捷键Alt+L),
在命令行下输入obey .\ADS2410boot.ini。
完成配置后打开Memory窗口,定位到SDRAM所在的地址0x30000000,数据区应显示SDRAM中的内容。
双击其中的任一数据,输入新的值,若对应的存储单元能正确显示刚才输入的数据,则表明SDRAM存储器已能正常工作。

四、Flash接口电路的调试
Flash的调试直接通过烧写软件来完成,使用sjf2410和JTAG小板将程序(如流水灯)烧入Flash,看能否正常运行程序。
这里涉及了安装GiveIO将PC并口用作普通I/O口以及sjf2410的使用。
1. 安装GiveIO驱动
1).将GIVEIO.SYS拷贝到C:\WINDOWS\system32\drivers目录下。
2).依次点击 控制面板->添加硬件->下一步->是,…->下一步->添加新的硬件设备->手动从…->端口(COM/LPT)->从磁盘安装->浏览->保存文件giveio.inf的目录->完成。安装成功后可以在设备管理器端口中看到一个名为giveio端口。
2. sjf2410的使用
在命令行下进入软件所在的目录,输入sjf2410可以看到命令行格式,依次按照提示的步骤即可以完成Flash的烧写。

五、外围电路的测试
至此,一个最小系统基本可以正常工作了,其他外围电路的测试通过使用官方的测试程序来完成。
 楼主| 发表于 2009-7-27 12:29:42 | 显示全部楼层
阿南ARM训练班课堂总结(3)

2009年03月19日 10:31

第三课主要是分析启动代码和中断处理过程

之前有分析过44b0下的这个启动代码,差别不是非常大,今天再重新看了一遍。启动代码与Bootloader不同,主要是指进入C语言之前的汇编代码,网上都称为是bootloader的stage1,一般通用的内容包括:
1. 定义程序入口点
2. 设置异常向量表
3. 初始化存储系统
4. 初始化用户程序的执行环境
5. 初始化堆栈指针寄存器,改变处理器的模式
6. 设置FIQ/IRQ中断处理程序入口
7. 进入C程序

1)编译器选择
    GBLL     THUMBCODE
     [ {CONFIG} = 16
THUMBCODE SETL   {TRUE}
         CODE32
         |   
THUMBCODE SETL   {FALSE}
         ]
因为处理器分为16位 32位两种工作状态,程序的编译器也是分16位和32两种编译方式,所以这段程序用于根据处理器工作状态确定编译器编译方式,程序不难理解,主要解释一下符号“[     |     ]”的意思,上面的程序是指:
if({CONFIG} = 16 )
     { THUMBCODE SETL {TRUE}
       CODE32 }
else
       THUMBCODE SETL {FALSE}
还是没有不明白CONFIG怎么区分是16位还是32位?哪里决定它的取值?应该是编译器指定的这个值。

2)宏定义
        MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
     sub     sp,sp,#4        
     stmfd     sp!,{r0}        
     ldr      r0,=$HandleLabel
     ldr      r0,[r0]         
     str      r0,[sp,#4]      
     ldmfd    sp!,{r0,pc}   
     MEND
$HandlerLabel是宏的地址标号,HANDLER是宏名,$HandleLabel是宏的参数,$标号在宏指令展开时,标号会替换为用户定义的符号。在此后,所有遇到$HandlerLabel HANDLER $HandleLabel这种形式的表达式都会被展开成$HandlerLabel到MEND之间的函数。
例如:ADC_IRQ HANDLER HandleADC即代表如下函数,语句ldr pc,=ADC_IRQ的作用也就是跳转到这个函数:
ADC_IRQ
     sub     sp,sp,#4        
     stmfd     sp!,{r0}        
     ldr      r0,=HandleADC
     ldr      r0,[r0]         
     str      r0,[sp,#4]      
     ldmfd    sp!,{r0,pc}
这段程序用于把ADC中断服务程序的首地址装载到pc中,跳转到中断处理函数,称之为“加载程序”。HandleADC是一个地址标号,它的内容就是ADC中断服务程序的地址标号,即文件最后的那个表HandleADC # 4所示,将HandleADC # 4中的4换成中断服务程序的地址标号即是,程序在这里定义了一个数据区,存放各种中断服务程序的首地址。每个字空间都有一个标号,以Handle***命名。

3)寄存器及堆栈设置
按照上面的顺序,可以比较容易读懂启动代码的作用,主要就是通过设置特殊功能寄存器来达到对系统参数的设定。依次禁止看门狗,中断,设定时钟控制寄存器,存储器控制寄存器等等。
由于各个工作模式下的堆栈指针是相互独立的,所以要分别进入各个模式下设置其堆栈指针,基本上都差不多,比如未定义指令模式下的设置:
     mrs     r0,cpsr
     bic     r0,r0,#MODEMASK
     orr     r1,r0,#UNDEFMODE|NOINT
     msr     cpsr_cxsf,r1        
     ldr     sp,=UndefStack
UnderStack是在程序后面用UnderStack #   256建立的一个堆栈空间的首地址,这部分空间建立在RAM中,256字节空间的堆栈大小。

4)初始化用户程序的执行环境
之前在44B0里的启动代码里还有包括把ROM里的程序拷贝到RAM中并跳转到RAM运行程序,也就是把加载状态下的程序按照编译连接时的设置重新排布成运行时的程序状态,以达到符号能够正确连接的目的,这里是涉及到了所谓的映像文件,但是2410这里没有这一段,即程序的加载态就是它的运行态,所以要求烧写程序时必须要把它烧写在设置的RO地址上,否则程序将不能正确执行。下面这段程序实现RW数据初始化,只是把数据段复制到高地址,如果没有设置RW的话这段代码也不会执行。
     ;Copy and paste RW data/zero initialized data
     ldr     r0, =|Image$$RO$$Limit| ; Get pointer to ROM data
     ldr     r1, =|Image$$RW$$Base|   ; and RAM copy
     ldr     r3, =|Image$$ZI$$Base|  
   
     ;Zero init base => top of initialised data
     cmp     r0, r1       ; Check that they are different
     beq     %F2
1      
     cmp     r1, r3      
     ldrcc     r2, [r0], #4            
     strcc     r2, [r1], #4   
     bcc     %B1
2      
     ldr     r1, =|Image$$ZI$$Limit|
     mov     r2, #0
3      
     cmp     r3, r1       ; Zero init
     strcc     r2, [r3], #4
     bcc     %B3
b %F1(B1)的意思是在临近的地址标号跳转,F是向后寻找,B是向前寻找。

5)说说映象文件
用ADS编译产生的映像文件有.axf、.bin、.hex等等格式,就拿要直接烧进Flash里的.bin文件来说,在其他书上看到有这么句话“映像文件一般由域组成,域由最多三个输出段(RO,RW,ZI)组成,输出段又由输入段组成。”
对于这段话,前两句是比较好理解的,域就是整个映像文件,对于大部分程序来说就只有一个域,也就是烧进Flash里的那部分东东,称作加载域;输出段就是用AREA定义的RO,Rw,一般就这两个,拿前面的bootloader来说,整体框架是这样的:
     AREA     Init,CODE,READONLY     ;<--RO段
     ENTRY
Entry                          ;<--CODE部分

… …

SMRDATA DATA               ;<--DATA部分

… …

     AREA          RamData, DATA,             READWRITE     ;<--RW段

… …

然而对于输出段又由输入段组成却着实糊涂了好一阵,输入段是指源程序的代码(CODE)部分和数据(DATA)部分。上面这个框架中,在RO输出段的Entry下开始一系列的汇编指令操作,这个应该是CODE输入段,而SMRDATA DATA引领DCD用于开辟一片数据存储空间,这部分应该是DATA输入段,它与RW里的数据不同之处在于这部分数据不能被修改。
在ADS编译前在ARM-Linker里的Ro_Base和Rw_Base两个地址值,就是指两个输出段的起始地址,即程序是按照你设置的这种方式排布在内存中的,各个地址标号根据这两个值确定。然而,用Ultraedit打开bin文件却发现其实Rw是跟在Ro后面的,也就是说,这两个段并没有按照你设置的地址起始,由此引出映像文件的加载域和运行时域两个概念。
加载域就是用Ultraedit打开看到的程序最原始的状态,而运行时域则是程序在执行时按照你设定的方式排布的状态,显然,上面设置的两个地址是针对运行时域来设置的,程序要满足上面的设置才能正确连接。也就是程序开始阶段(加载域状态)是不能正确连接的,不过开始时不需要用到Rw里的数据,程序是可以运行的,因此必须在需要用到Rw数据之前把它拷贝到上面设置的位置上,这就是bootloader里初始化用户程序的执行环境部分的作用,把数据移动到正确的位置!
拷贝完Rw里的数据之后,所有的符号都可以正确连接,这时跳转到main函数里去执行程序就可以了。2410的这段启动代码没有进行Ro的拷贝,所以如果你把程序烧在0x0地址,那么Ro就必须设置成0x0,如果你设置成0x30000000,那么Ro就必须设置成0x30000000,如果Rw不设置,它将默认跟在Ro后面,否则就执行上面的搬迁代码,挪到正确的位置上。由于本系统是采用NandFlash启动的,最初的启动代码必须要在0x0处的SRAM里执行,所以,如果要把这段启动代码当作NandFlash的启动代码的话,Ro就必须设成0x0。

6).中断处理过程
要使用中断,首先需要清掉程序状态寄存器CPSR里的IRQ位,这个很容易被忽略了。再之后才是考虑与中断有关的相应寄存器.

这个几个寄存器比较容易弄混了:
SRCPND/SUBSRCPND:只要中断产生的条件满足,例如外部电平,定时溢出等等,SRCPND/SUBSRCPND的相应位就会被置位,它不管其他地方的设置如何,所以某一时刻可能有几个位同时被置位了(几个中断同时产生)。
INTMSK/INTSUBMSK:这个是中断屏蔽位,清零表示允许中断请求,默认是禁止了所有的中断请求。
INTPND:它表示处理器接下来就要去处理的那个中断,某一时刻只可能有一个位被置位。这个寄存器置位的必要条件是SRCPND/SUBSRCPND已经是1,而且INTMSK/INTSUBMSK相应位已经清零。
SRCPND/SUBSRCPND和INTPND都不会自动清零,要程序向相应的位写1才能清零,这个有点奇怪。

2410不支持中断嵌套,中断产生后处理器进入到IRQ模式,只有在等到这个中断处理完之后才能响应下一次中断。
如果同时产生多个中断,就涉及到了中断优先级的问题。SRCPND寄存器对应的32个中断源总共被分为6个组,每个组由一个ARBITER(0~5)对其进行管理。中断必须先由所属组的ARBITER(0~5)进行第一次优先级判断然后再到ARBITER6进行第二次判断。可以更改的只是组里的优先级顺序。
PRIORITY的各个位被分为两种类型,一种是ARB_MODE,另一种为ARB_SEL,拿ARBITER0来说,这个组一共包含了四种中断源:EINT0~EINT3,分别对应Req0~Req3,很明显ARB_SEL0就是决定了这四种中断的优先顺序,如果这个组里的两个中断同时产生,将会把排在前面的先传递给ARBITER06进行第二次判断。ARB_MODE0置1代表开启优先级次序旋转,当该位置为1之后,ARB_SEL0的值会在每处理完一次中断后顺次改变。
 楼主| 发表于 2009-7-27 12:29:59 | 显示全部楼层
中断处理流程
启动代码开始是一个异常向量表,这个向量表是固定的,由处理器决定,必须要放在0x0地址那个地方,这个跟51单片机的中断向量表相类似。
    b     ResetHandler  
     b     HandlerUndef     ;handler for Undefined mode
     b     HandlerSWI     ;handler for SWI interrupt
     b     HandlerPabort     ;handler for PAbort
     b     HandlerDabort     ;handler for DAbort
     b     .         ;reserved
     b     HandlerIRQ     ;handler for IRQ interrupt
     b     HandlerFIQ     ;handler for FIQ interrupt
当产生IRQ中断时,PC首先无条件地来到0x18这个地址处,这个0x18就是处理器决定的IRQ中断的入口地址,所以要在这个地址处放一条跳转指令b HandlerIRQ,PC接着跳转到HandlerIRQ地址标号处,这里存放着一个宏语句:
HandlerIRQ       HANDLER HandleIRQ
按照上面说的宏展开,其实是执行这么一段语句:
     sub     sp,sp,#4         ;留下堆栈的第一个位置
    stmfd     sp!,{r0}         ;保护R0因为后面要用R0传递值
    ldr      r0,=HandleIRQ   ;将HandleIRQ这个地址标号的值传如R0
     ldr      r0,[r0]          ;取存放在HandleIRQ里的那个值
    str      r0,[sp,#4]       ;把取到的值压入栈
    ldmfd    sp!,{r0,pc}      ;恢复R0并把PC指向HandleIRQ里存放的地址值
HandleIRQ里存放是什么值呢?代码最后有个这样的表,这个表就是在SDRAM里的另外一张异常向量表,这张表可以根据需要修改_ISR_STARTADDRESS的值来随意更改它的位置。
         ^    _ISR_STARTADDRESS
HandleReset      #    4
HandleUndef      #    4
HandleSWI        #    4
HandlePabort     #    4
HandleDabort     #    4
HandleReserved   #    4
HandleIRQ        #    4
HandleFIQ        #    4
这里实现结构化一片地址空间的目的,可见在HandleIRQ这里预留了4个字节的空间,但是这个空间里现在放的是什么东西呢?
在代码的初始化过程中有这么一段代码:
    ldr     r0,=HandleIRQ     ; Setup IRQ handler  
     ldr     r1,=IsrIRQ         
     str     r1,[r0]
原来是把IsrIRQ所在的地址值放到这个地方,那就是宏实现了把PC指向IsrIRQ的目的。程序来到IsrIRQ:
IsrIRQ  
     sub     sp,sp,#4        ;预留堆栈
     stmfd     sp!,{r8-r9}     ;保护R8,R9
   
     ldr     r9,=INTOFFSET   ;找出产生哪种中断
     ldr     r9,[r9]
     ldr     r8,=HandleEINT0
     add     r8,r8,r9,lsl #2
     ldr     r8,[r8]
     str     r8,[sp,#8]
     ldmfd     sp!,{r8-r9,pc}   ;将PC指向相应的中断处理地址
假如产生了EINT0中断来到了这里,那么PC将会跳转到HandleEINT0里存放的地址值,与上面的相同,程序里有这个表:
HandleEINT0        #    4
HandleEINT1        #    4
HandleEINT2        #    4
HandleEINT3        #    4
HandleEINT4_7     #    4
    .
    .
    .
这个表在2410addr.h头文件里也有对应的定义,指向的是同样的一块地方:
    .
    .
    .
#define pISR_EINT0      (*(unsigned *)(_ISR_STARTADDRESS+0x20))
#define pISR_EINT1      (*(unsigned *)(_ISR_STARTADDRESS+0x24))
#define pISR_EINT2      (*(unsigned *)(_ISR_STARTADDRESS+0x28))
    .
    .
    .
问题是HandleEINT0存放的又是什么值呢?这就需要在初始化EINT0的时候写上这么一句:
pISR_EINT0 = (unsigned )_IsrEINT0Service;
也就是把EINT0的中断处理函数的地址写到HandleEINT0地址处存放,那么到此PC就可以跳转到_IsrEINT0Service里去了,这里完成你所需要的中断处理过程。
 楼主| 发表于 2009-7-27 12:30:27 | 显示全部楼层
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们  -  服务条款  -  使用指南  -  站点地图  -  友情链接  -  联系我们
电子工程网 © 版权所有   京ICP备16069177号 | 京公网安备11010502021702
快速回复 返回顶部 返回列表