S3c2410软件调试总结
7、8月份的时候自己用空余时间制作了一块S3c2410+Cyclone EP1C6的开发版,由于没有时间就一直扔在那里,最近空了下来,我用了3个星期的时间埋头对开发板对了全面的调试,我的工作重点是放在 bootloader、ucos-ii等相对比较简单的程序,借此来完成对硬件模块的测试以及对S3c2410的熟悉。


    很高兴,现在各个模块已经全部打通,我个人自己写的Nor Flash bootloader、Nand Flash bootloader、ucos-ii移植代码、网络驱动以及修改的2410test程序包都顺利跑起来了。

    本着和大家熟悉以及互相交流学习的目的,我想把我这3个星期以来的调试总结和大家共享。虽然相对来说都是比较简单的东西,但是我觉得这些东西对于帮助我们认识嵌入式系统、熟悉ARM还是很有用的,特别是对初学者来说。

我的文章中有任何不妥或者错误的地方,希望大家能够指正! 随时欢迎各位嵌入式爱好者和我联系: yantouxie@163.com

目录:
S3c2410软件调试总结(一) 硬件平台介绍

S3c2410软件调试总结(二) ADS下C语言的入口方式和ROM镜像文件的生成

S3c2410软件调试总结(三) 2410启动代码分析

S3c2410软件调试总结(四) Nor Flash Bootloader

S3c2410软件调试总结(五) Nand Flash Bootloader

S3c2410软件调试总结(六) RTL8019调试心得

S3c2410软件调试总结(七) S3c2410 DMA介绍

ADS下C语言的入口方式和ROM镜像文件的生成

这部分介绍下ADS下如何生成可以运行的ROM镜像文件,我们知道当程序下载到flash中运行的时候,对于RW、ZI数据就存在着两个环境,一个 load环境,一个是exec环境,有时候由于速度的需要RO数据也要重新加载,那么对RO数据也是有两个环境。编译器产生ROM镜像文件时候,这三块数 据的存放依次为RO、RW、ZI,并且地址空间时连续的。但是到了运行的时候,RW数据必须被拷贝到SDRAM(SRAM)中以支持读写,这就是我们所谓 的运行环境。那么就要有一段代码去完成这个任务,在本章中我们介绍如何生成这段代码。

玩过2410的朋友都知道2410初始化代码中有一段搬运RW和ZI初始化的代码,没错,它确实能够在一定程度上完成上面所说的任务,只要我们在生 成二进制可执行代码的时候在编译器链接项的地方填写正确的RO&RW地址,(比如RO = 0, RW = 0x30000000), 那么将程序下到 NOR flash的零地址并从nor flash启动,启动代码会将RW&ZI数据弄到0x30000000,程序就能跑起来了。

但是各位有没有想过,怎么把RO代码弄到SDRAM中(有时候这是必须的,比方后面我将提到用nor flash的bootloader烧写nor flash)?如果直接设RO=0x30000000,那么这段代码下载到0地址肯定跑不起来,除非是ROPI,这个要求就高了。这里我们有必要从介绍 ADS中规定的C语言入口开始,ADS中从初始化汇编代码跳到main函数有两种方式,main和__main:

1,在__main入口的模式下,汇编代码的指令为 b  __main, 编译器在跳转到main之前还要作一系列的工作,这其中就包括对运行环境的初始化,在<ADS COMPILE GUIDE>中提到: copies nonroot(RO&RW) execution regions from load addr to exec addr, and Zeros ZI region. 借助编译器,我们就可以定义更为复杂的运行环境,这里要用到scatter文件(.scf),比如我们要的目标运行环境是:将启动代码以外的所有代码都 拷贝到SDRAM的初始地址中运行,比且把RW段设在0x30800000,那么对应的scf文件如下:
  
   FLASH 0x0  0x200000
   {
    EXEC1 0x0 0x200000
    {
     2410init.o(Init, +First) 
     __main.o(+RO)   ; copy code     
     * (Region$$Table)       ; RO/RW addresses to copy
     * (ZISection$$Table)    ; ZI addresses to zero
    }
    EXEC2 0x30000000 0x00800000
    {
     *(+RO)
    }
    SDRAM 0x30800000 0x00800000
    {
     *(+RW,+ZI)
    }
   }
  ;Sections named Region$$Table and ZISection$$Table which contain the addresses of the code/data to be copied.

当然,在这种模式下,有些入口函数必须自己重定义,比如__user_initial_stackheap,具体参见ADS文档。

2, main入口模式即简单的跳转,这里起始不用“main”这个名字也无所谓。那么编译器不会作任何的初始化,所有运行环境的建立都要靠 我们自己,这就是大家看到的那段搬运代码存在的理由。但是它实现一些简单的运行环境是可取的,如果用scf定义的复杂环境,虽然我 相信是可以做到的,但是可能会比较麻烦。我还没深究。

另外,这里提一下semihost,因为我们在看ADS的东西的时候经常出现这个词,我也一直受其困扰。这里我简单说一下自己的见解,semihost 仅仅是一种调试手段,它的机理就是利用MULTI_IDE等工具捕捉目标环境运行过程中产生的值为0x123456的SWI中断,然后向上位机的ADS 软件发送对应的调试信息。对于我们最后的应用代码来说,都是nonsemihost类型的。如果我们在调试中使用semihost,那么只要在最后重定义 ADS中的一些使用到的库函数(比如fputc),代码就可以从semihost向nonsemihost的类型转变。不过到目前为止,我还没体会到 semihost的威力。

2410启动代码分析

这一章主要对目前广泛流行的2410启动代码进行分析:S3C2410的初始化代码主要涉及到对系统主要模块的配置、运行环境的建立、系统时钟、MMU等模块的配置,下面按执行顺序依次都各个部分进行分析:

程序入口:(ResetHandler)
  在程序一开始,首先进行的一些操作主要保证初始化程序能够顺利的运行, 因此主要包括关闭WDT、中断,配置锁相环等。

配置memory接口
   memory接口是确保数据访问正确的基本保障,此处主要配置SFR寄存器中0x48000000开始的memory接口寄存器组,  确保每个bank的位宽、访问类型(waitable)以及时序参数正确。如果没有特别的要求,一般来说时序参数使用默认值即可。

初始化堆栈
   ARM有6种运行模式,必须为每一种模式提供独立的堆栈空间,在堆栈设置之前是不能进行C函数的调用的。ARM的堆栈模式 是从高地址递减的,我的所有代码统一将堆栈的首地址设在0x33ff8000处,往低依次为FIQ、IRQ、Abort、Undef、SVC,其中
  SVC和User模式不予区分。堆栈大小一般可在头文件或者当前文件中修改。

运行空间的初始化
   这段代码主要完成两个功能,一是将RW数据搬运到RW空间(我们生成ROM镜像时,RW数据是跟在RO数据之后的),二是 初始化ZI数据段。当然,这段代码存在的前提是代码的运行环境只是标准的两段式:一段RO空间和一段RW空间;并且在C程序
  入口时没有调用编译器的链接库(__main)。后者已经提供相应的功能,并且支持更加复杂的运行环境定义(使用SCF文件),
  (关于这一点,我在介绍ADS中C代码的启动模式时已经详细介绍)。

__rt_lib_init
   在ADS1.2的环境中,如果在C入口没有调用编译器的链接库(__main),那么在C程序一开始要调用该函数以初始化运行时的函数库,以保证对ADS提供的某些库函数能够正常调用。从这个函数开始,我们已经在C语言环境下了。

MMU初始化
   2410的MMU支持1级&2级地址映射,在我们目前大部分应用中均采用1级section模式的地址映射,一个section的大小为1M,也就是说从逻辑地址到物理地址的转变是这样的一个过程:
   一个32位的地址,高12位决定了该地址在页表中的index,这个index的内容决定了该逻辑section对应的物理section;  低20位决定了该地址在section中的偏移(index)。
   因此从0x0~0xffffffff的地址空间总共可以分成0x1000(4K)个section,页表中每项的大小为32个bit,因此页表的大小为0x4000(16K)。在我的代码中所有程序的页表统一存放在地址0x33ff8000。
   每个页表项的内容如下:
 
          bit: 31                              20 19     12 11  10 9 8          5  4  3  2  1  0
   content:  Section对应的物理地址     NULL     AP   0  Domain   1  C B  1  0
  
      最低两位(10)是section分页的标识。
      AP:Access Permission,区分只读、读写、SVC&其它模式。
   Domain:每个section都属于某个Domain,一个有16个Domain,每个Domain的属性由CP15的R3寄存器控制。 在我得所有程序中,都只包含两个Domain,一个是SFR地址以下(包括SFR)的空间,可访问;  另一个是SFR以上的空间,不可访问。
   C、B:这两位决定了该section的cache&write buffer属性,这与该段的用途(RO or RW)有密切关系。不同的用途要做不同的设置。

       C     B                         具体含义
        0    0   无cache,无写缓冲,任何对memory的读写都反映到ASB总线上。 

                   对 memory 的操作过程中CPU需要等待。
        0    1   无cache,有写缓冲,读操作直接反映到ASB总线上。写操作CPU将数据写

                   入 到写缓冲后继续运行,由写缓冲进行ASB操作。
       1    0    有cache,写通模式,读操作首先考虑cache hit;写操作时直接将数据写入

                   写缓冲,如果同时出现cache hit,那么也更新cache。
      1    1    有cache,写回模式,读操作首先考虑cache hit;写操作也首先考虑cache,

                  如果hit,则只修改cache,并将cache对应半行的dirty比特置位;如果miss,

                   则写入写缓冲,触发ASB总线操作。
              
            在我的程序中内存空间的分配统一采用了文末的MEMORY图。虽然MMU只是使用了逻辑地址到物理地址的linear transfer(值不改变),但是由于MMU能够引入cache&write buffer,因此系统性能有很大的提高!

配置时钟比、重新设置PLL
   2410内部有三个时钟:FCLK、HCLK、PCLK,分别供CPU、AHB总线和APB总线使用,为了降低功耗,一般都选择周期比为1:2:4的合理配置。 同时将PLL配置为运行环境时钟,一般都达到最高202M。

IO初始化
   将IO口配置为对应的功能选项,同时一般会点亮相应的LED灯。
  
中断初始化
   2410的内存空间没有remap的机制,应该中断入口时钟位于零地址。因此中断服务机制可以描述如下:
    首先,不管使用那种启动方式,必须确保一下代码段位于内存的0x0地址:
     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
    除ResetHandler外,其余各项都是由如下的宏定义的一段代码:
       HandlerFIQ   HANDLER  HandleFIQ
     MACRO
     $HandlerLabel  HANDLER  $HandleLabel
     $HandlerLabel
      sub sp,sp,#4            ;decrement sp(to store jump address)
      stmfd sp!,{r0}        ;PUSH the work register to stack
      ldr     r0,=$HandleLabel  ;load the address of HandleXXX to r0
      ldr     r0,[r0]            ;load the contents
      str     r0,[sp,#4]     ;store the contents(ISR) of HandleXXX to stack
      ldmfd   sp!,{r0,pc}     ;POP the work register and pc(jump to ISR)
        MEND
      这段代码的含义是通过堆栈将中断向量表中的内容赋给PC指针(如HandleFIQ是存放着FIQ服务程序入口地址的地址),自然程序就跳到相应的入口地址。
   可见,中断向量表存放的是各个中断服务程序的入口地址,它是用来被加载的,而并不是可执行代码。为了统一,所有示例程序都将中断向量表放在0x33ffff00开始的地址,并根据入口地址依次排列。
   需要注意的是如果各种模式的服务程序用C语言定义,那么类型必须用__irq定义,以保证能够正确返回。
  
初始化串口
     串口统一选用UART0,模式采用115200、1bit STOP、No Parity。
 
最后跳转到我们自己的应用程序!

附:我得程序所使用的地址空间结构以及MMU中C、B的设置:

      Blank Area: RW_FAULT   0x5b000000 ~ 0xffffffff
     
      Sram & SFR: NCNB           0x40000000 ~ 0x4affffff
     
      Blank Area: RW_FAULT    0x34000000 ~ 0x3fffffff

      Int_Vec, Stack, MTT: CNB  0x33f00000 ~ 0x33ffffff
     
      SDRAM Download: NCNB   0x31000000 ~ 0x33efffff
     
      SDRAM Exec RW: CB         0x30800000 ~ 0x30ffffff
     
      SDRAM Exec R CNB        0x30000000 ~ 0x307fffff
     
      Bank5, FPGA: NCNB          0x28000000 ~ 0x2fffffff
     
      Bank4, FPGA: NCNB            0x20000000 ~ 0x27ffffff
     
      Bank3, Bottom NIC: NCNB   0x18000000 ~ 0x1fffffff
     
      Bank2, Bottom Flash: CNB  0x10000000 ~ 0x17ffffff
     
      Bank1, Bottom Sram: CNB   0x08000000  ~ 0x0fffffff
     
      Bank0, Flash or Sram: CNB  0x00000000 ~ 0x07ffffff


Nor Flash Bootloader
  
这是我着手写的第一个程序,我的想*是让这个程序同时支持通过串口对Nand 和 Nor FLASH的烧写,如果不进行任何烧写,那么就跳到Nor Flash的第二个section启动应用程序,这样一来,即使脱离JTGA,我也可以使用串口进行盲调。

由于有现成的初始化文件和flash烧写的示例程序,开发起来还比较快。当然也遇到了一些问题,一开始连flash的device ID都读不出来,后来发现我指针没有定义成volatile类型,flash的操作时序被编译器优化了;再者,在对Nor Flash进行操作时,bank0在MMU中的类型一定要设为NCNB,这样比较保险。

遇到最大的问题就是下面的了,一开始我用jtag把程序下载到0x30000000的地方运行,对Nor Flash的烧写完全正常,但是当把程序下载到Nor Flash中启动运行后,再对Nor Flash的section 2进行烧写时,就出现了问题。所幸没多久我就意识到了问题,将程序放在Nor Flash中运行,同时有对同一片flash进行操作,那么操作时序势必会被CPU的指令读取时序所破坏,因此程序必须搬运到SDRAM中运行。

但是启动地址有必须是零地址,所以我采用了前文提到的scatter文件的方*,将非必要的代码全部搬到sdram中运行,scf文件格式就是前文中的那个。当然采用了__main的入口,调用了ADS的链接库,让它帮忙建立程序的运行环境。

至此,Nor Flash Bootloader可以顺畅无忧的实现其功能了。

Nand Flash Bootloader
  
因为Nor flash bootloader已经实现了对Nand Flash的烧写,因此在Nand Flash Bootloader中实现flash烧写并不是我的目的,况且,S3C2410运行在NAND BOOT模式下的时候,4K的SRAM位于0地址,上电时刻Nand Flash中block 0的前8个page的数据自动加载到SRAM后开始运行,Nor flash这个时候是不可见的。 因此,我做Nand Flash Bootloader的目的简单而又直接,就是把block1开始的若干个block数据加载到sdram首地址,然后PC跳到那里运行应用程序就可以 了。比方说我把编译好的ucos-ii代码放在block1,那么ucos-ii就可以跑起来了。

因此制作Nand Flash一个最重要的问题就是真个程序必须小于4K。应用程序应该是一个完整的应用代码,只是在编译时RO的起始地址应该定位成0x30000000, 如果直接用JTAG将其下载到对应地址,程序照样能够跑起来(当然零地址要有中断向量入口程序)。这里我偷懒了一下,将应用程序的中断向量表地址和 Nand Flash Bootloader设得完全相同,那样应用程序就可以借用bootloader的中断跳转程序以实现中断的正确跳转,当然应用程序也有自己相应的跳转代 码,但是这段代码位于SDRAM起始地址,是不会被执行的;至于堆栈,应用程序在自己得初始化代码中可以重新设置堆栈。

在Nand Flash的硬件方面,我开发板使用的是K9F5608(32M),相对于K9S208(64M),后者的地址需要写四次才能全部送出,而前者只要三次就 够了,2410的引脚中专门有nCON控制地址送出的次数。因此当硬件在这两者之间变化时,既要注意外部电路图的接*,又要注意软件代码的正确性。

RTL8019调试心得

一开始接触8019真的是让我头晕,首先我没有一点网络基础,另外,8019的datasheet称不上最烂也算是极品了。当初作PCB的时候选用8019主要是因为价格便宜以及lbbbb做过,能够提供源代码&技术支持。最后能搞定,我觉得还是很有成就感的。

8109AS的运行模式包括跳线模式、非跳线模式和PnP模式,PnP模式是在电脑上使用的即插即用模式,因此这里我们可以不予考虑。8109AS 的IO寄存器符合NE2000标准,分为4个page,其中page3是8109AS自己定义的寄存器。所谓跳线模式,是指8019AS I/O寄存器page3中的大部分配置寄存器(CONFIGn)的值是在上电复位时刻确定的,来源是在RESET上升沿时捕捉到的一些外部引脚的电平值。 在非跳线模式下,这些寄存器值的配置由外部EEPROM 93C46完成。配置寄存器在运行过程中大部分值时不能改变的。

目前驱动程序目前只实现了最基本的收发功能。片内16K的SRAM划分如下:40~46:发送缓冲区1;46~4c:发送缓冲区2;4c~80:接收缓冲区。

另外我在调试中发现片内的SRAM是不可按地址读的,虽然我在原理图上也象CS8900A那样连了mem_wr&mem_rd,但是似乎不能访问,希望哪位高人能够给我一个明确的回答。

起初作硬件了时候我加了93C46,想使用非跳线模式,JP脚就悬空在那里。后来93C46买不到,就一直空着,虽然8019的初始化没有出问题,但是对这种不洋不土的模式,我还是心有余悸,因此将JP脚接到了5V电源,板上唯一的飞线就是这么来礟OSThttp://bbs.edw.com.cn/S**ePost.asp?A   现在还有一个郁闷的问题就是linux 2.4.18是不支持8019的。天下的2410开发板都采用8900a也就是这个道理。所以,我还要完成驱动!!!

S3c2410 DMA介绍
  
之所以要介绍DMA,因为它对性能太重要了!只有活用了DMA,CPU的性能才能上去!S3c2410有四个DMA,每个DMA支持工作方式基本相同,但支持的source Dest可能略有不同,具体见Datasheet。

这里具体DMA CONTROL寄存器(DCON)的配置说明,进而引出DMA的各种工作方式。

Atomic transfer:指的是DMA的单次原子操作,它可以是Unit模式(传输1个data size),也可以是burst模式(传输4个data size),具体对应DCON[28]。

Data Size:指的是单次原子操作的数据位宽,8、16、32,具体对应DCON[21:20]。

Request Source:DMA请求的来源有两种,软件&硬件模块,由DCON[23]控制;当为前者时,由软件对DMASKTRIG寄存器的位0置位触发一次 DMA 操作。当为后者时,具体来源由DCON[26:24]控制,不同硬件模块的某时间触发一次DMA操作,具体要见不同的硬件模块。
  
DMA service mode:DMA的工作模式有两种,单一服务模式&整体服务模式。前一模式下,一次DMA请求完成一项原子操作,并且transfer count的值减1。后一模式下,一次DMA请求完成一批原子操作,直到transfer count等于0表示完成一次整体服务。具体对应DCON[27]。

RELOAD:在reload模式下,当transfer count的值变为零时,将自动加src、dst、TC的值加载到CURR_DST、CURR_SRC、CURR_TC,并开始一次新的DMA传输。该模 式一般和整体服务模式一起使用,也就是说当一次整体服务开始后,src、dst、TC的值都已经被加载,因此可以更改为下一次

   服务的地址,2410说明文档中建议加入以下语句来判断当前的服务开始,src、dst、TC的值可以被更改了:while((rDSTATn & 0xfffff) == 0) ;

Req&Ack:DMA请求和应答的协议有两种,Demard mode 和 Handshake mode。两者对Request和Ack的时序定义有所不同:在Demard模式下,如果

   DMA完成一次请求如果Request仍然有效,那么DMA就认为这是下一次DMA请求;在Handshake模式下,DMA完成一次请求后等待Request信号无效,然后把ACK也置无效,再等待下一次Request。这个设计外部DMA请求时可能要用到。

传输总长度:DMA一次整体服务传输的总长度为:
    Data Size × Atomic transfer size × TC(字节)。

后记 & 特别感谢
 
第一次写这么洋洋洒洒的文章,想必垃圾成分一定很高,不过能有那么一点点有用的东西,我也就很高兴了。毕竟自己这么一路走过来,真的是满有感触的。

在此,特别要感谢版上twentyone朋友对我的大力帮助,在高人的指点下,我长进不小,另外我用的他开发的JTAG代理软件XJTAG,个人觉得性能相当不错,各位在用简易JTAG口的,强烈推荐大家尝试:http://www.twentyone.blogchina.com/

走到这一步,我只能说精彩的生活刚刚开始,接下来我会在linux和FPGA上作一些文章,这两方面的工作我都刚刚开始,远不够深入啊!非常乐意和大家一起探讨、学习、交流,让我们一起进步!