arm裸板驱动总结(makefile+lds链接脚本+裸板调试)
在裸板2440中,当我们使用nand启动时,2440会自动将前4k字节复制到内部sram中,如下图所示:
然而此时的SDRAM、nandflash的控制时序等都还没初始化,所以我们就只能使用前0~4095地址,在前4k地址里来初始化SDRAM,nandflash,初始化完成后,才能将nandflash的4096至后面的地址内容存放到SDRAM里去.
而裸板驱动的步骤如下所示:
- 1.写makefile
- 2.写lds链接脚本 (供makefile调用)
- 3.写真正要执行的文件代码,比如初始化nand,sdram,串口等
为什么要写lds链接脚本?
首先lds链接脚本的作用就是将多个*.o文件的各个段链接在一起,告诉链接器这些各个段存放的地址先后顺序,它的好处就是,确保裸板2440的前4k地址里存放的是初始化SDRAM,nandflash的内容
1.写makefile
(参考makefile初步制作:http://www.cnblogs.com/lifexy/p/7065175.html)
在写裸板之前首先要来写Makefile,如下所示:
- objs := head.o init.o nand.o main.o
- //定义objs变量,表示obj文件,包含生成boot.bin目标文件需要的依赖文件, 使用$(objs)就可以使用这个变量了
- //‘:=’:有关位置的等于(比如:”x:=a y:=$(x) x:=b”,那么y的值取决于当时位置的a,而不是b)
- //‘=’:无关位置的等于(比如:”x=a y=$(x) x=b”,那么y的值永远等于最后的b ,而不是a)
- nand.bin : $(objs) //冒号前面的是表示目标文件, 冒号后面的是依赖文件,这里是将所有*.o文件编译出nand.bin可执行文件
- arm-linux-ld -Tnand.lds -o nand_elf $^ //将*.o文件生成nand_elf链接文件
- //-T:指向链接脚本, $^:指向所有依赖文件,
- arm-linux-objcopy -O binary -S nand_elf $@ //将nand_elf链接文件生成nand.bin文件
- //$@:指向目标文件:nand.bin
- //-O :选项,其中binary就是表示生成的文件为.bin文件
- arm-linux-objdump -D -m arm nand_elf > nand.dis //将nand.bin文件反汇编出nand.dis文件
- //-D :反汇编nand.bin里面所有的段, -m arm:指定反汇编文件的架构体系,这里arm架构
- %.o:%.c //冒号前面的是目标文件,冒号后面的是依赖文件,%.o表示所有.o文件,
- arm-linux-gcc -Wall -c -O2 -o $@ $< //将*.c文件生成*.o文件
- //$<:指向第一个依赖文件, 也就是.c文件
- //$@:指向目标文件,也就是.o文件
- //-Wall:编译若有错,便打印警告信息 -O2:编译优化程度为2级
- %.o:%.S
- arm-linux-gcc -Wall -c -O2 -o $@ $< //将*.S文件生成*.o文件
- clean: //输入make clean,即进入该项,来删除所有生成的文件
- rm -f nand.dis nand.bin nand_elf *.o //通过rm命令来删除
2.写lds链接脚本
(参考lds脚本解析: http://www.cnblogs.com/lifexy/p/7089873.html)
- SECTIONS {
- . = 0x30000000; //指定当前的链接地址=0x30000000
- .text : {
- head.o(.text) //添加第一个目标文件,里面会调用这些函数
- init.o(.text) //添加第二个目标文件,里面存放关看门狗,初始化SDRAM等函数
- nand.o(.text) //添加第三个目标文件,里面存放初始化nand函数
- *(.text) // *(.text) 表示添加剩下的全部文件的.text代码段
- }
- .rodata ALIGN() : {*(.rodata)} //指定只读数据段
- .data ALIGN() : { *(.data) } //指定读写数据段, *(data):添加所有文件的数据段
- __bss_start = .; //把__bss_start赋值为当前地址位置,即bss段的开始位置
- .bss ALIGN() : { *(.bss) *(COMMON) } //指定bss段,里面存放未被使用的变量
- __bss_end = .; //把_end赋值为当前地址位置,即bss段的结束位置
- }
上面的链接地址=0x30000000,表示程序运行的地方应该位于0x30000000处,0x30000000就是我们的SDRAM基地址,而一上电后,nand的前4k地址会被2440自动装载到内部ram中,所以我们初始化了sdram和nand后,就需要把程序所有内容都复制到链接地址0x30000000上才行
2.1为什么要在bss
段的前后设置两个
符号__bss_start, __bss_end?
定义__bss_start和__bss_end符号,是用来程序开始之前将这些未定义的变量清0,节省内存
且__bss_start -0x30000000就等于该bin文件的字节大小,实现动态复制
2.3为什么链接地址在0x30000000处,为什么在初始化sdram和nand之前,还能运行前4k地址的内容?
我们先来看看head.S第一个目标文件,就知道了:
- .text @设置代码段
- @函数disable_watch_dog, memsetup, init_nand, nand_read_ll在init.c中定义
- ldr sp, = @设置堆栈
- bl disable_watch_dog @关WATCH DOG
- bl memsetup @初始化SDRAM
- bl nand_init @初始化NAND Flash
- ldr sp,=0x34000000 @64Msdram,所以设置栈SP=0x34000000,避免堆栈溢出
@nand_read_ll函数需要3个参数:- ldr r0, =0x30000000 @. 目标地址=0x30000000,这是SDRAM的起始地址
- mov r1, # @. 源地址 =
- ldr r2, =__bss_start
- sub r2,r2,r0 @. 复制长度= __bss_start-0x30000000
- bl nand_read @调用C函数nand_read,将nand的内容复制到SDRAM中
- ldr lr, =halt_loop @设置返回地址
- ldr pc, =main @使用ldr命令 绝对跳转到SDRAM地址上
- halt_loop: @若main函数跳出后,便进入死循环,避免程序跑飞
- b halt_loop
(参考位置无关码(bl)与绝对位置码(ldr): http://www.cnblogs.com/lifexy/p/7117345.html)
从上面代码来看,可以发现在复制数据到sdram之前,都是使用的相对跳转命令bl,bl是一个位置无关码,也就是说无论该代码放在内存的哪个地址,都能正确运行.
而ldr就是绝对跳转命令,是一个绝对位置码,当一上电时,我们的链接地址0x30000000上是没有程序的,因为程序都存在nand flash上,也就是0地址上,而如果在复制数据到sdram之前,使用ldr去执行的话,就会直接跳转到0x30000000上,就会运行出错.
而且在复制数据到sdram之前,执行的代码里都不能用静态变量、全局变量、以及数组,因为这些初始值量的地址与位置有关的,必须将nand的内容复制到sdram地址中,才能用.
2.4比如,下面memsetup ()函数,就是个会出错的函数
其中的mem_cfg_val[]数组的内存是存在链接地址0x30000000上,就是与位置有关,在未复制内容之前使用将会出错
- #define MEM_CTL_BASE 0x48000000 //SDRAM寄存器基地址
- void memsetup()
- {
- int i = ;
- unsigned long *p = (unsigned long *)MEM_CTL_BASE;
- /* SDRAM 13个寄存器的值 */
- unsigned long const mem_cfg_val[]={ 0x22011110, //BWSCON
- 0x00000700, //BANKCON0
- 0x00000700, //BANKCON1
- 0x00000700, //BANKCON2
- 0x00000700, //BANKCON3
- 0x00000700, //BANKCON4
- 0x00000700, //BANKCON5
- 0x00018005, //BANKCON6
- 0x00018005, //BANKCON7
- 0x008C07A3, //REFRESH
- 0x000000B1, //BANKSIZE
- 0x00000030, //MRSRB6
- 0x00000030, //MRSRB7
- };
- for(; i < ; i++)
- p[i] = mem_cfg_val[i];
- }
如下3个图所示,通过反汇编来看,上面的数组内容都是存在SDRAM的链接地址上面的rodata段0x300005d0里,在我们没有初始化SDRAM,复制数据到SDRAM之前,这些数据是无法读取到的
图1,使用bl跳到相对地址0x30000094处:
图2,使用ldr,使ip跳到绝对地址0x300005d0:
图3,0x300005d0里保存的.redata只读数据段,也就是 mem_cfg_val[]的内容:
2.5所以要修改memsetup ()函数为以下才行:
- #define MEM_CTL_BASE 0x48000000 //SDRAM寄存器基地址
- void memsetup()
- {
- unsigned long *p = (unsigned long *)MEM_CTL_BASE;
- /* 设置SDRAM 13个寄存器的值 */
- p[] =0x22011110, //BWSCON
- p[] =0x00000700, //BANKCON0
- p[] =0x00000700, //BANKCON1
- p[] =0x00000700, //BANKCON2
- p[] = 0x00000700, //BANKCON3
- p[] =0x00000700, //BANKCON4
- p[] =0x00000700, //BANKCON5
- p[] =0x00018005, //BANKCON6
- p[] = 0x00018005, //BANKCON7
- p[] =0x008C07A3, //REFRESH
- p[] =0x000000B1, //BANKSIZE
- p[] = 0x00000030, //MRSRB6
- p[] =0x00000030, //MRSRB7
- }
通过反汇编来看,可以看到这些赋值,都是靠mov,add等命令来加加减减拼出来的
如下图,我们以上面的代码p[0] =0x22011110为例:
3.在裸板中调试有以下几步
3.1点灯法:
- LED_SHOW:
- ldr r0, =0x56000050
- ldr r1, =(<<(*)) @设置GPFCON寄存器的GPF4为输出引脚
- str r1, [r0]
- ldr r0, =0x56000054 @GPFDAT寄存器
- ldr r1, = @设置GPF4=,亮灯
- ldr r2, =(<<) @设置GPF4=,灭灯
- LED_LOOP: @死循环闪灯
- str r1, [r0] @亮灯
- bl DELAY
- str r2, [r0] @灭灯
- bl DELAY
- b LED_LOOP
- DELAY: @延时
- ldr r3,=
- :
- sub r3, r3, #
- cmp r3, #
- bne 1b
- mov pc, lr @跳出循环 PS:寄存器之间赋值只能用mov
在调试汇编中:就可以使用 “b LED_SHOW”,若LED闪烁,便说明程序已跑过,通过点灯来定位程序在哪出错,
缺点在于需要多次烧写才能得出结果,调试非常麻烦
3.2串口打印
首先需要通过寄存器来初始化串口
在2440中,当没有初始化MPLLCON和CLKDIVN寄存器时,所有的时钟都由12Mhz晶振提供,所以PCLK=12MHZ,则波特率最高就是57600,因为UBRDIV0=12000000/(57600*16-1)=13.02,所以串口代码如下所示:
- #define S3C_PCLK 12000000 // PCLK初始值为12MHz
- #define S3C_UART_CLK PCLK // UART0的时钟源设为PCLK
- #define S3C_UART_BAUD_RATE 57600 // 波特率
- #define S3C_UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
- #define S3C_GPHCON (*(volatile unsigned long *)0x56000070)
- #define S3C_GPHDAT (*(volatile unsigned long *)0x56000074)
- #define S3C_GPHUP (*(volatile unsigned long *)0x56000078)
- /*UART registers*/
- #define S3C_ULCON0 (*(volatile unsigned long *)0x50000000)
- #define S3C_UCON0 (*(volatile unsigned long *)0x50000004)
- #define S3C_UFCON0 (*(volatile unsigned long *)0x50000008)
- #define S3C_UMCON0 (*(volatile unsigned long *)0x5000000c)
- #define S3C_UTRSTAT0 (*(volatile unsigned long *)0x50000010)
- #define S3C_UTXH0 (*(volatile unsigned char *)0x50000020)
- #define S3C_URXH0 (*(volatile unsigned char *)0x50000024)
- #define S3C_UBRDIV0 (*(volatile unsigned long *)0x50000028)
- #define TXD0READY (1<<2)
- #define RXD0READY (1)
- void uart0_init(void)
- {
- S3C_GPHCON |= 0xa0; // GPH2,GPH3用作TXD0,RXD0
- S3C_GPHUP = 0x0c; // GPH2,GPH3内部上拉
- S3C_ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
- S3C_UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
- S3C_UFCON0 = 0x00; // 不使用FIFO
- S3C_UMCON0 = 0x00; // 不使用流控
- S3C_UBRDIV0 = S3C_UART_BRD; // 波特率为115200
- }
- /*
- * 发送一个字符
- */
- void putc(unsigned char c)
- {
- /* 等待,直到发送缓冲区中的数据已经全部发送出去 */
- while (!(S3C_UTRSTAT0 & TXD0READY));
- /* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
- S3C_UTXH0 = c;
- }
- /*
- * 打印一串数字
- * num:数据
- */
- void putnum(unsigned long num) //0xFFFF FFFF (7:0)
- {
- int i ,start=;
- unsigned char c;
- uart0_init();
- for(i=;i<;i++);
- putc('');
- putc('x');
- for(i=;i>=;i--) //从[7:0]中打印数字,去除有效数字前面的0
- {
- c=( num >> (i*) )&0xf;
- if(c!=)
- {
- if(c>)
- putc(c-+'A'); //打印A~F
- else
- putc(c+''); //打印1~9
- if(!start) start=; //start=1,说明为有效数字
- }
- else if((start||!i)&&c==) //若是有效数字,便打印0,且在个位上时,不管是否有效都要打印
- {
- putc('');
- }
- }
- putc('\r');
- putc('\n');
- }
在调试汇编中,就可以使用:
- mov r0,#0x100 //参数等于0x100
- bl putnum //调用打印函数
即可打印0x100数字, 能快速定位出程序在哪出错
在c中,直接通过调用函数即可
3.3 使用JTAG调试器
JTAG用于芯片的测试与程序调试,JTAG位于CPU内部,当CPU收发引脚上的数据时,都会通过JTAG单元,而JTAG单元会从CPU内部引出TMS,TCK,TDI,TDO,四个引脚,便可以通过OpenJTAG调试器连接电脑USB,而另一端连接这些JTAG脚来控制CPU
OpenJTAG可以实现:
- 读写某个地址上的数据
- 将文件下载到2440的某个地址上,或读取出某个地址到文件中
- 查询CPU当前状态、中断CPU运行、恢复CPU运行、复位CPU等
- 设置CPU的地址断点,比如设置为0x30000000,当CPU运行到这个地址时,便会停止运行
断点在调试中分为两种:
硬件断点,在2240中,共有两个硬件断点,也就是最多设置两个硬件断点
软件断点,可以设置无数个断点
1)为什么软件断点可以设置无数个?
实际JTAG后台会把每个需要暂停的地址断点里的数据复制到指定地址里,并赋为某个特殊值(如deeedeee),然后CPU运行时,当某个变量=这个特殊值(如deeedeee),便知道到了软件断点,并从指定地址里把原来的值换回去,然后暂停运行
注意:
由于软件断点,会后台保存断点数据到另一个地址中,前提要必须保证地址可直接读写,所以在nor flash,nand flash下则无法实现调试,若链接地址在SDRAM地址上,则软件断点的地址必须设置在SDRAM初始化后的地址上
3.3.1.通过OCD对JATG进行命令行调试
1)安装OpenOCD
OpenOCD:既可以烧写nor flash,也可以烧写nand flash,并可以通过JTAG调试器来进行调试
接上OpenJTAG,并安装OpenJTAG驱动
2)使用OpenOCD工具连接OpenJTAG调试器
如上图所示:
步骤1,选择jtag类型,CPU类型.
步骤2,点击连接按钮
步骤3,可以看到2440只支持2个硬件断点
其中,work dir 就是需要烧写的文件根目录, 或读取CPU某个地址内容到文件的文件根目录
3)然后通过telent控制台进行调试
telent的主要目的,就是发送命令行给连接的OpenJTAG调试器,然后OpenJTAG通过命令来对CPU进行操作
首先,在win7下,若没打开telnet客服端:
点击开始 ->控制面板-> 程序和功能-> 打开或关闭Windows功能->打开“telnet客服端”
然后在cmd控制台下,输入 “telnet 127.0.0.1 4444”命令,进入telent控制台,如下图所示:
4)接下来便可以通过命令行来实现调试(需要参考反汇编文件,来实现调试)
常用的命令如下所示:
poll
查看当前状态
halt
暂停CPU运行
step
单步执行,如果指定了 address,则从 address 处开始执行一条指令
reg
显示CPU的r0、r1、r2、sp、lr、pc等寄存器的值(需要halt后才能看到)
resume [addr]
恢复CPU运行,若指定了地址,便从指定地址运行(需要halt后才能使用)
例如: resume 0 //从0地址运行
md<w|h|b> <addr> [size]
read读地址,读出size个内容,w:字,h:半字,b:字节.如下图所示:
mw<w|h|b> <addr> <size>
word写地址,写入size个内容,使用方法和上面类似
(PS:不能直接读写nand和nor上的地址,只能读写2440的内部地址(4096),若SDRAM已初始化,也可以实现读写)
load_image <file> <address>
将文件<file>载入地址为 address 的内存,格式有“bin”, “ihex”、 “elf”
例如:
- load_image led.bin //烧写led.bin到0地址
(PS:该文件的目录位于之前在OpenOCD工具的界面里的work dir里)
dump_image <file> <address> <size>
将内存从地址 address 开始的 size 字节数据读出,保存到文件<file>中
bp <addr> <length> [hw]
在地址 addr 处设置断点,hw 表示硬件断点,length为指令集字节长度,,若未指定表示软件断点,比如: stm32是2个字节长,2440是4个字节长,部分MCU拥有多套指令集,长度不固定,如下图所示:
rbp <addr>
删除地址 addr 处的断点
bp
打印断点信息
3.3.2通过GDB对JATG实现源码级别的调试
在linux中,使用arm-linux-gdb软件
在win7中,则使用arm-none-eabi-gdb软件
使用GDB工具,就不需要像上个OCD调试那么麻烦了
1)比如说,想在“int i=0;”处打上断点:
OCD调试:
就需要查看调试的反汇编文件,找到i=0所在的运行地址,然后通过命令在地址上打断点
GDB调试:
则可以直接在i=0处的源码上打断点,后台会通过带调试信息的编译文件,来找到i=0处的运行地址,并向OpenOCD发送打断点命令
2)上面的带调试信息的编译文件又是怎么来的?
通过Makefile里的arm-linux -g 来的, -g:表示生成的编译文件里包含gdb调试信息
然后我们将上面第1节的Makefile修改,如下图:
3)使用gdb之前,需要保证:
- 1.调试的源码里面的内容必须位于同一个链接地址上, 各个段也要分开存储,调试的链接脚本和上面第2节的类似,
- 2.如果程序的链接地址是SDRAM, 使用openocd初始化SDRAM
4)常用命令如下所示(以调试上图的nand_elf文件为例):
arm-none-eabi-gdb nand_elf
启动GDB,指定调试文件为nand_elf
target remote 127.0.0.1:3333
与OpenOCD建立连接
load
载入nand_elf调试文件
break [file]:[row]
打断点,比如:
- break main.c: //在main.c文件的第21行处打断点
info br
查看断点
delete <num>
删除第几个断点,如下图所示:
c
恢复程序运行,若使用load后,使用c便是启动程序, 按ctrl+c便暂停运行
step
单步执行
monitor <cmd...>
调用OCD的命令使用,比如 :
- monitor resume //使用OCD的resume命令,使程序从0地址运行
quit
退出
(PS:也可以通过eclipse平台软件来调用GDB,GDB最终转换为命令行,再调用OCD来实现调试,如下图所示)
arm裸板驱动总结(makefile+lds链接脚本+裸板调试)的更多相关文章
- 裸板驱动总结(makefile+lds链接脚本+裸板调试)
在裸板2440中,当我们使用nand启动时,2440会自动将前4k字节复制到内部sram中,如下图所示: 然而此时的SDRAM.nandflash的控制时序等都还没初始化,所以我们就只能使用前0~40 ...
- makefile使用.lds链接脚本以及 $@ ,$^, $,< 解析
先来分析一个简单的.lds链接脚本 例1,假如现在有head.c init.c nand.c main.c这4个文件: 1.1 首先创建链接脚本nand.lds: SECTIONS { firtst ...
- makefile使用.lds链接脚本以及 $@ ,$^, $,< 解析【转】
转自:http://www.cnblogs.com/lifexy/p/7089873.html 先来分析一个简单的.lds链接脚本 例1,假如现在有head.c init.c nand.c main. ...
- [转]Linux下的lds链接脚本详解
转载自:http://linux.chinaunix.net/techdoc/beginner/2009/08/12/1129972.shtml 一. 概论 每一个链接过程都由链接脚本(lin ...
- Linux下的lds链接脚本简介
转载:http://hubingforever.blog.163.com/blog/static/171040579201192472552886/ 一. 概论 每一个链接过程都由链接脚本(lin ...
- Linux下的lds链接脚本详解【转】
转自:http://www.cnblogs.com/li-hao/p/4107964.html 转载自:http://linux.chinaunix.net/techdoc/beginner/2009 ...
- Linux下的lds链接脚本详解
1. 概论2. 基本概念3. 脚本格式4. 简单例子5. 简单脚本命令6. 对符号的赋值7. SECTIONS命令8. MEMORY命令9. PHDRS命令10. VERSION命令11. 脚本内的表 ...
- Linux下的lds链接脚本简介(二)
七. SECTIONS命令 SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section: 如何将输入section合为输出section; 如何把输出section ...
- u-boot 用哪个lds链接脚本
顶层Makefile文件中 : ifndef LDSCRIPT #LDSCRIPT := $(srctree)/board/$(BOARDDIR)/u-boot.lds.debug ifd ...
随机推荐
- ASP.NET写的一个博客系统
由于域名闲置,正好也有服务器空间,短期内开发了一个博客系统. 大家都来谈 http://www.djdlt.com 目前是开放注册,免费发布.(限于空间有限,图片还是尽量少传些) 网站架构: ASP ...
- MVC+Nhibernate+spring.net(二)
在上一篇文章中我们已经把数据查了出来,现在我们来完善一下:前台使用easyui 首先我们将NHelper类完善一下 public class EmpDal { public IList<Emp& ...
- python做数据分析pandas库介绍之DataFrame基本操作
怎样删除list中空字符? 最简单的方法:new_list = [ x for x in li if x != '' ] 这一部分主要学习pandas中基于前面两种数据结构的基本操作. 设有DataF ...
- this与$(this)对象
this与$(this)对象.前者是Javascript对象,而后者是jQuery是对象.两者分清楚,它们只能使用自己的方法.Javascript对象使用Javascript的方法,jQuery对象使 ...
- 【Vue】浅谈Vue不同场景下组件间的数据交流
浅谈Vue不同场景下组件间的数据“交流” Vue的官方文档可以说是很详细了.在我看来,它和react等其他框架文档一样,讲述的方式的更多的是“方法论”,而不是“场景论”,这也就导致了:我们在阅读完 ...
- Windows下安装配置爬虫工具Scrapy及爬虫环境
爬虫工具Scrapy在Mac和Linux环境下都相对好装,但是在Windows上总会碰到各种莫名其妙的问题.本文记录下Scrapy在Window上的安装过程. 本文是基于Python2.7及Windo ...
- [学习笔记]普通平衡树Splay
哈哈哈哈哈哈哈终于会打\(splay\)啦 现在我来发一下\(splay\)的讲解吧 小蒟蒻由于码风与他人不同,所以自己找了上百篇码风诡异的\(splay\)合成的,感谢\(zcysky\)的代码与我 ...
- C#使用七牛云存储上传下载文件、自定义回调
项目需要将音视频文件上传服务器,考虑并发要求高,通过七牛来实现. 做了一个简易的压力测试,同时上传多个文件,七牛自己应该有队列处理并发请求,我无论同时提交多少个文件,七牛是批量一个个排队处理了. 一个 ...
- django~项目的文件位置的重要性
前几天我犯了个很低级的错误 就是把文件的地址放错地方了~~ 我把templates文件放进mysite文件里面了 和templatetags文件同级了 所以一直报错 说找不到模板的文件 实际上te ...
- (转)Python3 模块3之 Urllib之 urllib.parse、urllib.robotparser
原文:https://blog.csdn.net/qq_36148847/article/details/79153738 https://blog.csdn.net/zly412934578/art ...