打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
S3c2410软件调试总结之我见
S3c2410软件调试总结
这部分介绍下ADS下如何生成可以运行的ROM镜像文件,我们知道当程序下载到flash中运行的时候,对于RW、ZI数据就存在着两个环境,一个load 环境,一个是exec环境,有时候由于速度的需要RO数据也要重新加载,那么对RO数据也是有两个环境。编译器产生ROM镜像文件时候,这三块数据的存放依次为RO、RW、ZI,并且地址空间时连续的。但是到了运行的时候,RW数据必须被拷贝到SDRAM(SRAM)中以支持读写,这就是我们所谓的运行环境。那么就要有一段代码去完成这个任务,在本章中我们介绍如何生成这段代码。
玩过2410的朋友都知道2410初始化代码中有一段搬运RW和ZI初始化的代码,没错,它确实能够在一定程度上完成上面所说的任务,只要我们在生成二进制可执行代码的时候在编译器链接项的地方填写正确的 RO&RW地址,(比如RO =&nbsp0,&nbspRW =&nbsp0x30000000), 那么将程序下到 &nbspNOR&nbspflash的零地址并从nor&nbspflash启动,启动代码会将RW&ZI数据弄到 0x30000000,程序就能跑起来了。
但是各位有没有想过,怎么把RO代码弄到SDRAM中(有时候这是必须的,比方后面我将提到用nor&nbspflash的bootloader烧写nor&nbspflash)?如果直接设RO=0x30000000,那么这段代码下载到0地址肯定跑不起来,除非是ROPI,这个要求就高了。这里我们有必要从介绍ADS中规定的C语言入口开始,ADS中从初始化汇编代码跳到main函数有两种方式,main和__main:
1,在__main入口的模式下,汇编代码的指令为& nbspb&nbsp__main, 编译器在跳转到main之前还要作一系列的工作,这其中就包括对运行环境的初始化,在< ADS&nbspCOMPILE&nbspGUIDE>中提到:&nbspcopies&nbspnonroot (RO&RW)&nbspexecution&nbspregions&nbspfrom&nbspload&nbspaddr&nbspto&nbspexec&nbspaddr,&nbspand&nbspZeros& nbspZI&nbspregion. 借助编译器,我们就可以定义更为复杂的运行环境,这里要用到scatter文件(.scf),比如我们要的目标运行环境是:将启动代码以外的所有代码都 拷贝到SDRAM的初始地址中运行,比且把RW段设在0x30800000,那么对应的scf文件如下:
FLASH&nbsp0x0&nbsp0x200000
{
EXEC1&nbsp0x0&nbsp0x200000
{
2410init.o(Init, +First)
__main.o(+RO) &nbspcopy&nbspcode
* (Region$$Table) &nbspRO/RW&nbspaddresses&nbspto&nbspcopy
* (ZISection$$Table) &nbspZI&nbspaddresses&nbspto&nbspzero
}
EXEC2&nbsp0x30000000&nbsp0x00800000
{
*(+RO)
}
SDRAM&nbsp0x30800000&nbsp0x00800000
{
*(+RW,+ZI)
}
}
;Sections&nbspnamed&nbspRegion$$Table&nbspand&nbspZISection$$Table&nbspwhich&nbspcontain&nbspthe&nbspaddresse
s&nbspof&nbspthe&nbspcode/data&nbspto&nbspbe&nbspcopied.
当然,在这种模式下,有些入口函数必须自己重定义,比如__user_initial_stackheap,具体参见ADS文档。
2, &nbspmain入口模式即简单的跳转,这里起始不用“main”这个名字也无所谓。那么编译器不会作任何的初始化,所有运行环境的建立都要 * 我们自己,这就是大家看到的那段搬运代码存在的理由。但是它实现一些简单的运行环境是可取的,如果用scf定义的复杂环境,虽然我 相信是可以做到的,但是可能会比较麻烦。我还没深究。
另外,这里提一下semihost,因为我们在看ADS的东西的时候经常出现这个词,我也一直受其困扰。这里我简单说一下自己的见解,semihost 仅仅是一种调试手段,它的机理就是利用MULTI_IDE等工具捕捉目标环境运行过程中产生的值为0x123456的SWI中断,然后向上位机的ADS 软件发送对应的调试信息。对于我们最后的应用代码来说,都是nonsemihost类型的。如果我们在调试中使用semihost,那么只要在最后重定义&nbspADS中的一些使用到的库函数(比如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,其中VC和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:&nbsp31&nbsp20&nbsp19&nbsp12&nbsp11&nbsp10&nbsp9&nbsp8&nbsp5&nbsp4&nbsp3&nbsp2&nbsp1&nbsp0
content: &nbspSection对应的物理地址&nbspNULL&nbspAP&nbsp0& nbspDomain&nbsp1&nbspC&nbspB&nbsp1&nbsp0
最低两位(10)是section分页的标识。
AP:Access&nbspPermission,区分只读、读写、SVC&其它模式。
Domain:每个section都属于某个Domain,一个有16个Domain,每个Domain的属性由CP15的R3寄存器控制。 在我得所有程序中,都只包含两个Domain,一个是SFR地址以下(包括SFR)的空间,可访问; 另一个是SFR以上的空间,不可访问。C、B:这两位决定了该section的cache&write&nbspbuffer属性,这与该段的用途(RO&nbspor&nbspRW)有密切关系。不同的用途要做不同的设置。
C&nbspB 具体含义
0&nbsp0 无cache,无写缓冲,任何对memory的读写都反映到ASB总线上。
对&nbspmemory 的操作过程中CPU需要等待。
0&nbsp1 无cache,有写缓冲,读操作直接反映到ASB总线上。写操作CPU将数据写入 到写缓冲后继续运行,由写缓冲进行ASB操作。1&nbsp0 有cache,写通模式,读操作首先考虑cache&nbsphit;写操作时直接将数据写入
写缓冲,如果同时出现cache&nbsphit,那么也更新cache。1&nbsp1 有cache,写回模式,读操作首先考虑cache&nbsphit;写操作也首先考虑cache,
如果hit,则只修改cache,并将cache对应半行的dirty比特置位;如果miss,则写入写缓冲,触发ASB总线操作。
在我的程序中内存空间的分配统一采用了文末的MEMORY图。虽然MMU只是使用了逻辑地址到物理地址的linear&nbsptransfer (值不改变),但是由于MMU能够引入cache&write&nbspbuffer,因此系统性能有很大的提高!
配置时钟比、重新设置PLL
2410内部有三个时钟:FCLK、HCLK、PCLK,分别供CPU、AHB总线和APB总线使用,为了降低功耗,一般都选择周期比为1:2:4的合理配置。 同时将PLL配置为运行环境时钟,一般都达到最高202M。
IO初始化
将IO口配置为对应的功能选项,同时一般会点亮相应的LED灯。
中断初始化
2410的内存空间没有remap的机制,应该中断入口时钟位于零地址。因此中断服务机制可以描述如下:
首先,不管使用那种启动方式,必须确保一下代码段位于内存的0x0地址:
b&nbspResetHandler
b&nbspHandlerUndef handler&nbspfor&nbspUndefined&nbspmode
b&nbspHandlerSWI handler&nbspfor&nbspSWI&nbspinterrupt
b&nbspHandlerPabort handler&nbspfor&nbspPAbort
b&nbspHandlerDabort handler&nbspfor&nbspDAbort
b&nbsp. reserved
b&nbspHandlerIRQ handler&nbspfor&nbspIRQ&nbspinterrupt
b&nbspHandlerFIQ handler&nbspfor&nbspFIQ&nbspinterrupt
除ResetHandler外,其余各项都是由如下的宏定义的一段代码:
HandlerFIQ&nbspHANDLER&nbspHandleFIQ
MACRO
$HandlerLabel&nbspHANDLER $HandleLabel
$HandlerLabel
sub&nbspsp,sp,#4 decrement&nbspsp(to&nbspstore&nbspjump&nbspaddress)
stmfd&nbspsp!,{r0} PUSH&nbspthe&nbspwork&nbspregister&nbspto&nbspstack
ldr&nbspr0,=$HandleLabel load&nbspthe&nbspaddress&nbspof&nbspHandleXXX&nbspto&nbspr0
ldr&nbspr0,[r0] load&nbspthe&nbspcontents
str&nbspr0,[sp,#4] store&nbspthe&nbspcontents(ISR)&nbspof&nbspHandleXXX&nbspto&nbspstack
ldmfd&nbspsp!,{r0,pc} POP&nbspthe&nbspwork&nbspregister&nbspand&nbsppc(jump&nbspto&nbspISR)
MEND
这段代码的含义是通过堆栈将中断向量表中的内容赋给PC指针(如HandleFIQ是存放着FIQ服务程序入口地址的地址),自然程序就跳到相应的入口地址。
可见,中断向量表存放的是各个中断服务程序的入口地址,它是用来被加载的,而并不是可执行代码。为了统一,所有示例程序都将中断向量表放在0x33ffff00开始的地址,并根据入口地址依次排列。
需要注意的是如果各种模式的服务程序用C语言定义,那么类型必须用__irq定义,以保证能够正确返回。
初始化串口
串口统一选用UART0,模式采用115200、1bit&nbspSTOP、No&nbspParity。
最后跳转到我们自己的应用程序!
附:我得程序所使用的地址空间结构以及MMU中C、B的设置:
Blank&nbspArea:&nbspRW_FAULT&nbsp0x5b000000 ~&nbsp0xffffffff
Sram &&nbspSFR:&nbspNCNB&nbsp0x40000000 ~&nbsp0x4affffff
Blank&nbspArea:&nbspRW_FAULT&nbsp0x34000000 ~&nbsp0x3fffffff
Int_Vec,&nbspStack,&nbspMTT:&nbspCNB&nbsp0x33f00000 ~&nbsp0x33ffffff
SDRAM&nbspDownload:&nbspNCNB&nbsp0x31000000 ~&nbsp0x33efffff
SDRAM&nbspExec&nbspRW:&nbspCB&nbsp0x30800000 ~&nbsp0x30ffffff
SDRAM&nbspExec&nbspR&nbspCNB&nbsp0x30000000 ~&nbsp0x307fffff
Bank5,&nbspFPGA:&nbspNCNB&nbsp0x28000000 ~&nbsp0x2fffffff
Bank4,&nbspFPGA:&nbspNCNB&nbsp0x20000000 ~&nbsp0x27ffffff
Bank3,&nbspBottom&nbspNIC:&nbspNCNB&nbsp0x18000000 ~&nbsp0x1fffffff
Bank2,&nbspBottom&nbspFlash:&nbspCNB&nbsp0x10000000 ~&nbsp0x17ffffff
Bank1,&nbspBottom&nbspSram:&nbspCNB&nbsp0x08000000 ~&nbsp0x0fffffff
Bank0,&nbspFlash&nbspor&nbspSram:&nbspCNB&nbsp0x00000000 ~&nbsp0x07ffffff
Nor&nbspFlash&nbspBootloader
这是我着手写的第一个程序,我的想法是让这个程序同时支持通过串口对Nand 和&nbspNor&nbspFLASH的烧写,如果不进行任何烧
写,那么就跳到Nor&nbspFlash的第二个section启动应用程序,这样一来,即使脱离JTGA,我也可以使用串口进行盲调。
由于有现成的初始化文件和flash烧写的示例程序,开发起来还比较快。当然也遇到了一些问题,一开始连flash的device&nbspID都读
不出来,后来发现我指针没有定义成volatile类型,flash的操作时序被编译器优化了;再者,在对Nor&nbspFlash进行操作时,bank0
在MMU中的类型一定要设为NCNB,这样比较保险。
遇到最大的问题就是下面的了,一开始我用jtag把程序下载到 0x30000000的地方运行,对Nor&nbspFlash的烧写完全正常,但是当
把程序下载到Nor&nbspFlash中启动运行后,再对Nor&nbspFlash的section&nbsp2进行烧写时,就出现了问题。所幸没多久我就意
识到了问题,将程序放在 Nor&nbspFlash中运行,同时有对同一片flash进行操作,那么操作时序势必会被CPU的指令读取时序所破
坏,因此程序必须搬运到 SDRAM中运行。
但是启动地址有必须是零地址,所以我采用了前文提到的scatter文件的方法,将非必要的代码全部搬到sdram中运行,scf文件格式
就是前文中的那个。当然采用了__main的入口,调用了ADS的链接库,让它帮忙建立程序的运行环境。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ARM论坛 - ARM技术论
[转]嵌入式C语言程序的运行
arm image 中的链接变量
启动代码和中断处理过程
简单的启动代码(Startup.s)分析
uboot编译连接脚本文件uboot.lds
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服