嵌入式学习(二)——刷机和led实验

嵌入式学习(二)——刷机和led实验,第1张

目录


一、刷机和裸机实验

1.1 刷机步骤

1.2 交叉编译链

1.2.1 环境变量配置


二、led实验

2.1 实验准备

2.2 实验开始

2.2.1 Makefile

2.2.2 mkv210_image.c

2.2.3 start.s

2.2.4 write2sd


三、添加看门狗

3.1 实验准备

3.2 添加看门狗


四、设置栈和调用c语言

4.1 四种栈

4.2 栈的介绍

4.3 栈指针的设置

4.4 调用c语言实现led流水灯效果

4.4.1 start.S

4.4.2 led.c


五、iCache

5.1 cache

5.2 iCache的设置


六、重定位

6.1 为什么要重定位

6.1.1 大部分的指令是位置有关编码

6.1.2 链接地址和运行地址

6.1.3 S5PV210启动过程

6.1.4 源码到可执行文件的步骤

6.1.5 程序段

6.2 重定位

6.2.1 start.S

6.2.2 link.lds


七、SDRAM初始化*

7.1 SDRAM原理图



一、刷机和裸机实验

        裸机实验进行下载的实质就是将自己的程序替代了S5PV210启动中的BL1代码,裸机实验主要是用来调试程序,通过USB或者SD卡实现程序的下载。


USB:使用dnw进行下载

SD卡:需要先将uboot擦除,再插卡

1.1 刷机步骤

        1.破坏iNand中的bootloader部分,让其从SD卡2启动(插入的卡)

        2.制作SD卡,使用刷卡工具将uboot.bin读入SD卡中

        3.将fastboot放置到一个方便访问的位置,将需要刷机系统的三个文件放置到同一文件夹

        4.打开SecureCRT,在boot中输入fastboot命令,在Windows中打开命令窗,切换到fastboot文件夹后,输入fastboot devices检查设备是否连接

        5.进行烧录

使用fastboot烧录烧录linux+QT:在cmd下使用以下三个命令来完成烧录

fastboot flash bootloader linuxQT/uboot.bin      烧ubootfastboot

fastboot flash kernel linuxQT/zImage-qt              烧linux kernel

fastboot flash system linuxQT/rootfs_qt4.ext3        烧rootfs

    6.在uboot下输入help可得到各个命令详解,输入printenv检查参数是否正确, uboot的参数设置:set bootcmd 'movi read kernel 30008000; bootm 30008000'

    7.重启系统,fastboot reboot

1.2 交叉编译链

        在Linux系统上对arm进行编程,需要用到交叉编译链,交叉编译链安装步骤。


1. 创建共享文件夹,将软件放置进去,共享文件夹在虚拟机Linux中的/mnt/hgfs目录下

2.  将文件复制到当前目录下(cp -rf /mnt/hgfs/vm_share/ arm-linux-gcc-4.6.4-arm-x86_64.tar.bz2 ./),Ubuntu18.04系统下安装视频中的软件一直失败,网上方法都不管用,最后下载了arm-linux-gcc-4.6.4版本

3. 解压后在bin目录下执行arm-linux-gcc -v,能够看到版本说明安装完成

4.  配置环境变量。


1.2.1 环境变量配置

        显示当前环境变量

echo $PATH

        将工具链导出到环境变量,但关掉该终端后,重新打开命令失效

export PATH=export PATH=/usr/local/arm/opt/TuxamitoSoftToolchains/arm-arm1176jzfssf-linux-gnueabi/gcc-4.6.4/bin:$PATH

        用vi 编辑器打开 /root 目录下的隐藏文件 .bashrc

export PATH=export PATH=/usr/local/arm/opt/TuxamitoSoftToolchains/arm-arm1176jzfssf-linux-gnueabi/gcc-4.6.4/bin:$PATH

        重新打开terminal即可


二、led实验 2.1 实验准备

        实验前需要知道如何去做,要查询的东西。


需要清楚实验需要达成的目标,相关的硬件配置。


实验目的:实现流水灯效果

原理图分析:

        查询底板原理图,当GPJ0位于低电平的时候led灯亮起,可通过对GPJ0引脚的配置实现流水灯的效果。


数据手册查询:

        根据GPJ0查询S5PV210_UM_REV1文件,找到相应的寄存器GPJ0CON与GPJ0DAT,

GPJ0CON:地址为0xE020_0240,将引脚设置为输出模式,共32位。


 GPJ0DAT:地址为 0xE020_0244,设置电平,共8位。


2.2 实验开始

实验开始前有四个文件:Makefile、mkv210_image.c、start.S、write2sd。


2.2.1 Makefile

        Makefile文件组织了整个项目的执行,从源文件生成可执行文件led.elf,再制作镜像。


led.bin: led.o 
	arm-linux-ld -Ttext 0x0 -o led.elf $^             # 编译led可执行文件,位置在0x0
	arm-linux-objcopy -O binary led.elf led.bin       # 以led.elf为原材料制作镜像
	arm-linux-objdump -D led.elf > led_elf.dis        # 生成反汇编文件
	gcc mkv210_image.c -o mkx210           # 该.c文件是在Linux上执行,生成mkx210可执行文件
	./mkx210 led.bin 210.bin               # 由led.bin得到210.bin   
	
%.o : %.S
	arm-linux-gcc -o $@ $< -c

%.o : %.c
	arm-linux-gcc -o $@ $< -c 

.PHONY clean: 

clean:
	rm *.o *.elf *.bin *.dis mkx210 -f
2.2.2 mkv210_image.c

        该文件的主要作用是由usb启动时使用的led.bin制作得到由sd卡启动的镜像210.bin。


        对启动方式进行分析,USB启动时内部BL0读取BL1不用校验,直接从0xd0020010处执行;SD卡执行时需要一个校验信息。


整个程序首先申请一个16KB的buffer,然后将内容填充,将填充好的buffer写入到210.bin,即对led.bin加了一个校验头,得到210.bin。


2.2.3 start.s

         该文件具体描述了如何以汇编语言实现led的设置。


#define GPJ0CON 0xE0200240
#define GPJODAT 0xE0200244

.global _start                  // 让外部能看到_start
_start:
    // 第一步:把0x11111111写入0xE0200240(GPJ0CON)位置
    ldr r0, =0x11111111
    ldr r1, =GPJ0CON
    str r0, [r1]                    // 寄存器间接寻址,把r0中的数写入到r1中的数为地址的内存中去
 
    // 流水灯实现
flash: 
    // 第二步:LED1亮
    ldr r0, =(0<<3) | (1<<4) | (1<<5)
    ldr r1, =GPJODAT
    str r0, [r1]                                     
    
    // 第三步:延时
    bl delay                // 使用bl进行函数调用
    
    // 第四步:LED2亮
    ldr r0, =(1<<3) | (0<<4) | (1<<5)
    ldr r1, =GPJODAT
    str r0, [r1]    
    
    // 第五步:延时
    bl delay
    
    // 第六步:LED3亮
    ldr r0, =(1<<3) | (1<<4) | (0<<5)
    ldr r1, =GPJODAT
    str r0, [r1]    
      
    bl delay
    
    b flash

// 延时函数
delay:
    ldr r2, =10000000
    ldr r3, =0x0
delay_loop:
    sub r2, r2, #1
    cmp r2, r3              // cmp会影响Z标志位,如果r2 = r3,则Z=1
    bne delay_loop          // ne后缀,若Z=0,执行delay_loop,当Z=1时,跳出循环
    mov pc, lr              // 函数调用返回
2.2.4 write2sd

        通过该文件将210.bin烧录进sd卡。


#!/bin/sh
sudo dd iflag=dsync oflag=dsync if=210.bin of=/dev/sdb seek=1

三、添加看门狗 3.1 实验准备

实验目的:设置看门狗的开关。


在s5pv210的BL0段代码中,实际上已经将看门狗关闭,所有本实验无任何现象。


看门狗:现实中因为一些外界环境,电子设备经常会跑飞或者死机。


看门狗是SoC内部的一个定时器,定好时间之后看门狗定时器会去计时,到了时间需要置位看门狗,否则系统会被强制复位。


原理图:由于看门狗属于内部外设,且没有外部相关的原件与他有关,所以不需要原理图分析。


数据手册:

        控制看门狗开关的寄存器应为WTCON,继续查询。


        WTCON寄存器第5位为使能看门狗,地址为0xE270_0000。


3.2 添加看门狗

        由于实际执行的功能并没有多大改变,因此在start.S中添加看门狗的开关即可。


#define GPJ0CON	0xE0200240
#define GPJ0DAT	0xE0200244

#define WTCON	0xE2700000

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]

// 其后的功能代码与前面一样

四、设置栈和调用c语言

        C语言的运行:C语言的运行需要一定的条件,这些条件由汇编来提供,C语言运行时主要是需要栈。


C语言中的局部变量都是用栈来实现的。


如果我们汇编部分没有给C部分预先设置合理合法的栈地址,那么C代码中定义的局部变量就会落空,整个程序就死掉了。


4.1 四种栈

        栈有四种状态:满栈、空栈、减栈、增栈。


这四种状态可组合成四种不同的栈,满增栈、满减栈、空增栈、空减栈。


满栈:进栈先移动指针再存; 出栈先出数据再移动指针

空栈:进栈先存再移动指针; 出栈先移动指针再出数据

减栈:进栈指针向下移动;     出栈指针向上移动

增栈:进栈指针向上移动;     出栈指针向下移动

        在ARM中,ATPCS(ARM关于程序应该怎么实现的一个规范)要求使用满减栈,所以不出意外都是用满减栈。


4.2 栈的介绍

        在ARM中37个寄存器中,每种模式下都有自己的独立的SP寄存器(r13),为什么这么设计?
        问题分析:如果各种模式都使用同一个SP,那么就意味着整个程序( *** 作系统内核程序、用户自己编写的应用程序)都是用一个栈的。


你的应用程序如果一旦出错(譬如栈溢出),就会连累 *** 作系统的栈也损坏,整个 *** 作系统的程序就会崩溃。


这样的 *** 作系统设计是非常脆弱的,不合理的。



        解决方案:各种模式下用不同的栈。


我的 *** 作系统内核使用自己的栈,每个应用程序也使用自己独立的栈,这样各是各的,一个损坏不会连累其他人。


设置栈调用c语言:

        我们现在要设置栈,不可能也没有必要去设置所有的栈,我们先要找到自己的模式,然后设置自己的模式下的栈到合理合法的位置,即可。


把模式设置为SVC,再直接 *** 作SP。


但是因为我们复位后就已经是SVC模式了,所以直接设置SP即可。



注意:系统在复位后默认是进入SVC模式的

4.3 栈指针的设置

实验分析:

        栈必须是当前一段可用的内存(可用的意思是这个地方必须有被初始化过可以访问的内存,而且这个内存只会被我们用作栈,不会被其他程序征用)。



        当前CPU刚复位(刚启动),外部的DRRAM尚未初始化,目前可用的内存只有内部的SRAM(因为它不需初始化即可使用)。


因此我们只能在SRAM中找一段内存来作为SVC的栈。


数据手册:

        SRAM相关数据可查询启动的数据手册,即s5pv210_irom_applicationnote,得到内存图。


在SRAM已经设置了SVC栈的存储空间,由于s5pv210采用的是满减栈,SVC栈应该设置为0xd0037D80。


4.4 调用c语言实现led流水灯效果

        添加c语言进行实验,需要增加一个c语言编写的.c文件,并且在start.S中做出相应的修改。


4.4.1 start.S
#define WTCON 0xE2700000
#define SVC_STACK   0xD0037D80

.global _start                  // 让外部能看到_start
_start:
    // 第一步,关看门狗,向WTCON的bit5写入0即可
    ldr r0, =0x0
    ldr r1, =WTCON
    str r0, [r1]
    
    // 第二步:设置svc 栈
    ldr sp, =SVC_STACK

    // 从这里之后就可以开始调用c程序了
    bl led_blink    // c语言的一个函数
    
// 汇编最后的死循环
    b .
4.4.2 led.c
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244

#define rGPJ0CON *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT)

void delay(void);

// 该函数实现一个led闪烁
void led_blink(void)
{
    // led初始化,将GPJ0CON设置为输出模式
    // volatile unsigned int *p = (unsigned int *)GPJ0CON;
    // *p = 0x11111111;
    
    // *((volatile unsigned int *)GPJ0CON) = 0x11111111;
    rGPJ0CON = 0x11111111;
     
    while(1)
    {
        // led亮
        //*((volatile unsigned int *)GPJ0DAT) = (0<<3) | (0<<4) | (0<<5);
        rGPJ0DAT = (0<<3) | (0<<4) | (0<<5);
        
        // 延时
        delay();
        
        // led灭
        // *((volatile unsigned int *)GPJ0DAT) = (1<<3) | (1<<4) | (1<<5);
        rGPJ0DAT = (1<<3) | (1<<4) | (1<<5);
        
        // 延时        
        delay();     
    }
}

void delay(void)
{
    volatile unsigned int i = 100000;   // volatile让编译器不要优化,这样才能真正的消耗时间,实现delay
    while(i--);
}


五、iCache 5.1 cache

        cache是一种内存, 叫高速缓存,寄存器和ddr之间的速度差异太大,ddr速度远不能满足寄存器需要(不能满足CPU需要,没有cache会拉低整个系统的整体速度)
从容量来说:cpu < 寄存器 < cache < DDR
从速度来说:  cpu > 寄存器 > cache  > DDR 

5.2 iCache的设置

        根据第四节的代码,只对start.S进行修改,增加了开/关iCache的内容。


#define WTCON 0xE2700000

#define SVC_STACK   0xD0037D80

.global _start                  // 让外部能看到_start
_start:
    // 第一步,关看门狗,向WTCON的bit5写入0即可
    ldr r0, =0x0
    ldr r1, =WTCON
    str r0, [r1]
    
    // 第二步:设置svc 栈
    ldr sp, =SVC_STACK
    
    // 第三步:开/关icache
    mrc p15,0,r0,c1,c0,0;     // 读出cp15的c1到r0中 
    // bic r0, r0, #(1<<12)        // bit12置0  关icache
    // orr r0, r0, #(1<<12)        // bit12置1  开icache
    mcr p15,0,r0,c1,c0,0;
    
    // 从这里之后就可以开始调用c程序了
    
    bl led_blink    // c语言的一个函数
    
    
// 汇编最后的死循环
    b .

六、重定位 6.1 为什么要重定位

        链接地址和运行地址有时候必须不相同,而且还不能全部用位置无关码,这时候只能重定位。


具体介绍见下。


6.1.1 大部分的指令是位置有关编码

        位置无关编码(PIC,position independent code):汇编源文件被编码成二进制可执行程序时编码方式与位置(内存地址)无关。



        位置有关编码:汇编源码编码成二进制可执行程序后和内存地址是有关的。


对比:位置无关代码适应性强,放在哪里都能正常运行;位置有关代码就必须运行在链接时指定的地址上,适应性差。


位置无关码有一些限制,不能完成所有功能,有时候不得不使用位置有关代码。


6.1.2 链接地址和运行地址

        链接地址:链接时指定的地址(为Makefile中用-Ttext,或者链接脚本)
        运行地址:程序实际运行时地址(实际运行时内存加载到的位置)

        对于位置有关代码来说:最终执行时的运行地址和编译链接时给定的链接地址必须相同,否则一定出错。


之前的裸机程序中,Makefile中用 -Ttext 0x0 来指定链接地址是0x0。


这意味着我们认为这个程序将来会放在0x0这个内存地址去运行。



        但是实际上我们运行时的地址是0xd0020010(我们用dnw下载时指定的下载地址)。


这两个地址看似不同,但是实际相同。


这是因为S5PV210内部做了映射,把SRAM映射到了0x0地址去。


6.1.3 S5PV210启动过程

        三星推荐的启动方式:bootloader必须小于96KB并大于16KB,假定bootloader为80KB,启动过程是这样子:先开机上电后BL0运行,BL0会加载外部启动设备中的bootloader的前16KB(BL1)到SRAM中去运行,BL1运行时会加载BL2(bootloader中80-16=64KB)到SRAM中(从SRAM的16KB处开始用)去运行;BL2运行时会初始化DDR并且将OS搬运到DDR去执行OS,启动完成。



        uboot实际使用的方式:uboot大小随意,假定为200KB。


先开机上电后BL0运行,BL0会加载外部启动设备中的uboot的前16KB(BL1)到SRAM中去运行,BL1运行时会初始化DDR,然后将整个uboot搬运到DDR中,然后用一句长跳转(从SRAM跳转到DDR)指令从SRAM中直接跳转到DDR中继续执行uboot直到uboot完全启动。


uboot启动后在uboot命令行中去启动OS。


6.1.4 源码到可执行文件的步骤

源码到可执行程序的步骤:预编译、编译、链接、strip和objcopy(后两个可选)
        预编译:预编译器执行。


譬如C中的宏定义就是由预编译器处理,注释等也是由预编译器处理的。



        编译:编译器来执行。


把源码.c .S编程机器码.o文件。



        链接:链接器来执行。


把.o文件中的各函数(段)按照一定规则(链接脚本来指定)累积在一起,形成可执行文件。



        strip: strip是把可执行程序中的符号信息给拿掉,以节省空间。


(Debug版本和Release版本)
        objcopy:由可执行程序生成可烧录的镜像bin文件。


6.1.5 程序段

        段的概念:段就是程序的一部分,我们把整个程序的所有东西分成了一个一个的段,给每个段起个名字,然后在链接时就可以用这个名字来指示这些段。


段名分为2种,一种是编译器链接器内部定好的,先天性的名字,代码段、数据段、bss段;一种是程序员自己指定的、自定义的段名。



代码段:(.text),又叫文本段,代码段其实就是函数编译后生成的东西
数据段:(.data),数据段就是C语言中有显式初始化为非0的全局变量
bss段 :(.bss),又叫ZI(zero initial)段,就是零初始化段,对应C语言中初始化为0的全局变量。


        C语言中全局变量如果未显式初始化,值是0。


本质就是C语言把这类全局变量放在了bss段,从而保证了为0。


C运行时环境如何保证显式初始化为非0的全局变量的值在main之前就被赋值了?就是因为它把这类变量放在了.data段中,而.data段会在main执行之前被处理(初始化)。


6.2 重定位

        目标:在SRAM中将代码从0xd0020010重定位到0xd0024000。


        在原先基础上,增加了一个link.lds文件,修改了Makefile和start.S文件。


Makefile文件中将 arm-linux-ld -Ttext 0x0 -o led.elf $^ 改为

                        arm-linux-ld -Tlink.lds -o led.elf $^ 
6.2.1 start.S
#define WTCON		0xE2700000

#define SVC_STACK	0xd0037d80

.global _start					// 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
	// 第1步:关看门狗(向WTCON的bit5写入0即可)
	ldr r0, =WTCON
	ldr r1, =0x0
	str r1, [r0]
	
	// 第2步:设置SVC栈
	ldr sp, =SVC_STACK
	
	// 第3步:开/关icache
	mrc p15,0,r0,c1,c0,0;			// 读出cp15的c1到r0中
	//bic r0, r0, #(1<<12)			// bit12 置0  关icache
	orr r0, r0, #(1<<12)			// bit12 置1  开icache
	mcr p15,0,r0,c1,c0,0;
    
    
	
	// 第4步:重定位
	// adr指令用于加载_start当前运行地址
	adr r0, _start  		// adr加载时就叫短加载		
	// ldr指令用于加载_start的链接地址:0xd0024000
	ldr r1, =_start     // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载	
	// bss段的起始地址
	ldr r2, =bss_start	// 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可
	cmp r0, r1			// 比较_start的运行时地址和链接地址是否相等
	beq clean_bss		// 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss
						// 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位
						// 重定位完成后继续执行clean_bss。


// 用汇编来实现的一个while循环 copy_loop: ldr r3, [r0], #4 // 源 str r3, [r1], #4 // 目的 这两句代码就完成了4个字节内容的拷贝 cmp r1, r2 // r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2 bne copy_loop // 清bss段,其实就是在链接地址处把bss段全部清零 clean_bss: ldr r0, =bss_start ldr r1, =bss_end cmp r0, r1 // 如果r0等于r1,说明bss段为空,直接下去 beq run_on_dram // 清除bss完之后的地址 mov r2, #0 clear_loop: str r2, [r0], #4 // 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址), cmp r0, r1 // 然后r0 = r0 + 4 bne clear_loop run_on_dram: // 长跳转到led_blink开始第二阶段 ldr pc, =led_blink // ldr指令实现长跳转 // 从这里之后就可以开始调用C程序了 //bl led_blink // bl指令实现短跳转 // 汇编最后的这个死循环不能丢 b .

6.2.2 link.lds
SECTIONS
{
	. = 0xd0024000;
	
	.text : {
		start.o
		* (.text)
	}
    		
	.data : {
		* (.data)
	}
	
	bss_start = .; 
	.bss : {
		* (.bss)
	}
	
	bss_end  = .;	
}

七、SDRAM初始化* 7.1 SDRAM原理图

        开发板原理图上使用的是K4T1G164QQ,但是实际开发板上贴的不是这个,是另一款。


但是这两款是完全兼容的,进行软件编程分析的时候完全可以参考K4T1G164QQ的文档。


        S5PV210共有2个内存端口(就好象有2个内存插槽)。


再结合查阅数据手册中内存映射部分,可知:两个内存端口分别叫DRAM0和DRAM1:
DRAM0:内存地址范围:0x20000000~0x3FFFFFFF(512MB),对应引脚是Xm1xxxx
DRAM1:内存地址范围:0x40000000~0x7FFFFFFF(1024MB),对应引脚是Xm2xxxx

 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/578229.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-11
下一篇 2022-04-11

发表评论

登录后才能评论

评论列表(0条)

保存