友善之臂第一个裸板驱动LED程序的详细分析(新手向)

本文详细分析了在友善之臂开发板上驱动LED灯亮的首个程序,涵盖Makefile、start.s和mkv210_image.c等关键文件的解释。介绍了S5PV210处理器的启动流程,包括IROM初始化、校验和计算以及bin文件头部的添加。通过执行make和write2sd脚本,将程序烧录到SD卡并启动。
摘要由CSDN通过智能技术生成

第一个程序,驱动LED灯亮。包含4个文件:Makefile,make命令批处理文件。write2sd是一个脚本文件。start.s汇编文件,驱动开发板亮灯的逻辑实现。mkv210_image.c文件,它的的作用就是用来给原始的bin文件添加头部。

Makefile(听说linux区分大小写,所以首M还是大写的好)

代码:

led.bin: start.o 
	arm-linux-ld -Ttext 0x0 -o led.elf $^
	arm-linux-objcopy -O binary led.elf led.bin
	arm-linux-objdump -D led.elf > led_elf.dis
	gcc mkv210_image.c -o mkmini210	./mkmini210 led.bin 210.bin 
	
%.o : %.S
	arm-linux-gcc -o $@ $< -c

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

clean:
	rm *.o *.elf *.bin *.dis mkmini210 -f

其中

“$@”可代表目标文件的完整名称

“$<”可代表第一个文件的依赖文件的名称

“$^"可代表所有不重复的依赖文件

.o文件:目标文件

.s/.S文件:汇编语言原始程序

.h文件:预处理文件(头文件)

.c:c原始程序文件

.cc/.C/.cxx:c++原始程序文件

.m:Objective.c原始程序文件

.i:已经预处理过的c原始程序

.ii:已经预处理过额c++原始程序文件

.a/.so:编译后的库文件


%.o(要生成的文件) : %.S(依赖文件)
                                          arm-linux-gcc -o $@ $< -c(操作)

表示对所有的".S"汇编文件编译成“.o”文件。“-c”(compile)参数进行预处理、编译、汇编操作 , "-o" 参数用于指定输出的文件,后面紧接着的就是文件名

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

表示对所有的".c"汇编文件编译成“.o”文件。

arm-linux-ld -Ttext 0x0 -o led.elf $^
arm-linux-ld 连接命令,这里表示将“所有不重复的依赖文件”连接成led.elf这个文件,此可执行文件的代码段的起始地址为0x00000000(从此处开始执行)

“-Ttext 0x0”设置代码段的起始地址为0x00000000;

arm-linux-objcopy -O binary led.elf led.bin
arm-linux-objcopy 复制一个目标文件的内容到另一个文件中,可以用于不同格式文件之间的格式转换

参数:

.-l bfdname(格式名)或—input-target=bfdname
用来指明源文件的格式,bfdname是BFD库中描述的标准格式名,如果没指明,则arm-linux-objcopy自己分析
-O bfdname 输出的格式(binary二进制)
-F bfdname 同时指明源文件,目的文件的格式
-R sectionname 从输出文件中删除掉所有名为sectionname的段
-S 不从源文件中复制重定位信息和符号信息到目标文件中
-g 不从源文件中复制调试符号到目标文件中

arm-linux-objdump -D led.elf > led_elf.dis

常用来显示二进制文件信息,和查看反汇编代码。

'>' 表示将这个程序的反汇编程序写入到led_elf.dis这个文件中,在终端中不显示出来.
当你打开led_elf.dis这个文件时就会看到上面命令的输出的反汇编程序了。
至于led_elf.dis是什么文件,我也不知道,网上也没有详细的讲解
<span class="m"></span>“./” 表示当前路径 linux下 “.” 是当前目录 “ ..” 是父目录

参数:

-b bfdname 指定目标码格式

-d 反汇编可执行段

-D 反汇编所有段

-EB,-EL指定字节序

-f 显示文件的整体头部摘要信息

-h 显示目标文件中各个段的头部摘要信息

-I 显示支持的目标文件格式和CPU架构

-j name显示指定section的信息

-m machine 指定反汇编目标文件时使用的架构


start。s:

.globl _start

_start:
	// 设置GPJ2CON的bit[0:15],配置GPJ2_0/1/2/3引脚为输出功能
	ldr r1, =0xE0200280	//0xE0200280的地址上是三星这款芯片的GPJ2CON,外围连得是led					
	ldr r0, =0x00001111	//4个都设为“1”,输出模式
	str r0, [r1]		//str,按一个字长写入。把r0中的内容写到r1里内容表示的地址上去

	mov r2, #0x1000
led_blink:
	// 设置GPJ2DAT的bit[0:3],使GPJ2_0/1/2/3引脚输出低电平,LED亮
	ldr r1, =0xE0200284 	//0xE0200284的地址上是三星这款芯片的GPJ2DAT				
	mov r0, #0		//赋值“0”,让它亮
	str r0, [r1]		

	// 延时
	bl delay	//bl,带返回的跳转						

	// 设置GPJ2DAT的bit[0:3],使GPJ2_0/1/2/3引脚输出高电平,LED灭
	ldr r1, =0xE0200284 					
	mov r0, #0xf		//赋值“1”,让它灭
	str r0, [r1]

	// 延时
	bl delay		

	sub r2, r2, #1		//要这样亮r2=1000次
	cmp r2,#0		
	bne led_blink		//

halt:
	b halt			//程序最后的死循环

delay:
	mov r0, #0x100000
delay_loop:
	cmp r0, #0	//cmp,比较但不改变比较的两个数的,只影响标志位。
	sub r0, r0, #1	//sub,减。r0=r0-1
	bne delay_loop	//bne,不为零跳转
	mov pc, lr	//lr:链接寄存器,储存子程序返回地址(保存现场用)

mkv210_image。c:

(该段描述来至手册)从三星提供的S5PV210文档《S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf》以及芯片手册《S5PV210_UM_REV1.1.pdf》得知,S5PV210启动时,会先运行内部IROM中的固化代码进行一些必要的初始化,执行完后硬件上会自动读取NAND Flash或sd卡等启动设备的前16K的数据到IRAM中,这16K数据中的前16byte中保存了一个叫校验和的值,拷贝数据时S5PV210会统计出待运行的bin文件中含‘1’的个数,然后和校验和做比较,如果相等则继续运行程序,否则停止运行。所以所有在S5PV210上运行的bin文件都必须具有一个16byte的头部,该头部
第- 11 –页
中需包含校验和信息,mkv210_image.c由arm9论坛上的网友提供,它的的作用就是用来给原始的bin文件添加头部。 mkv210_image.c的核心工作如下:
第一步 分配16k的buffer;
第二步 将led.bin读到buffer的第16byte开始的地方;
第三步 计算校验和,并将校验和保存在buffer第8~11byte中;
第四步 将16k的buffer拷贝到210.bin中;
 校验和统计的关键代码如下: a = Buf + SPL_HEADER_SIZE; for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++) checksum += (0x000000FF) & *a++; 代码mkv210_image.c已经包含相当丰富的注释,很容易就可以读懂,用户可以自行阅读。

/* 在BL0阶段,Irom内固化的代码读取nandflash或SD卡前16K的内容,
 * 并比对前16字节中的校验和是否正确,正确则继续,错误则停止。
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define BUFSIZE                 (16*1024)			//定义buffer区大小
#define IMG_SIZE                (16*1024)			//定义IMG区大小
#define SPL_HEADER_SIZE         16					//定义SPL_HEADER所占大小
#define SPL_HEADER              "S5PC110 HEADER  "	//定义SPL_HEADER的内容

int main (int argc, char *argv[])
{
	FILE		*fp;					//
	char		*Buf, *a;				//buf是指向buffer区的指针
	int		BufLen;						//
	int		nbytes, fileLen;			//fileLen文件大小
	unsigned int	checksum, count;	//
	int		i;							//
	
	// 1. 3个参数
	if (argc != 3)
	{
		printf("Usage: mkbl1 <source file> <destination file>\n");
		return -1;
	}

	// 2. 分配16K的buffer
	BufLen = BUFSIZE;
	Buf = (char *)malloc(BufLen);		//动态分配内存空间,malloc返回的指针赋给BUF
	if (!Buf)							//如果Buf没有得到结果
	{
		printf("Alloc buffer failed!\n");
		return -1;
	}
	//memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
	/*
			void *memset(void *s, int ch, size_t n);
			函数解释:将s中前n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
		eg:
			char buffer[]="Helloworld\n";
			printf("Buffer before memset:%s\n",buffer);
			memset(buffer,'*',strlen(buffer));
			printf("Buffer after memset:%s\n",buffer);
		结果:
			Buffer before memset:Helloworld
			Buffer after memset:***********
	*/
	memset(Buf, 0x00, BufLen);			//这里只是简单的对buffer区全部清零
	
	// 3. 读源bin到buffer
	// 3.1 打开源bin
	//fopen函数用来打开一个文件.file+open
	/*
	函数原型:FILE * fopen(const char * path,const char * mode);
返回值:文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回NULL,并把错误代码存在errno中。
eg:
			FILE *fp = NULL;
			char tmp[100];
			fp = fopen("/opt/C_lanuage/fopen_fread/tmp.txt", "r");
			if (NULL == fp)
			{
				printf("file open Fail!\n");
				return -1;
			}
			fread(tmp, 1, 100, fp);
		    printf("%s\n", tmp);
 
			fclose(fp);
		    fp = NULL;
一般而言,打开文件后会做一些文件读取或写入的动作,若打开文件失败,接下来的读写动作也无法顺利进行,所以一般在fopen()后作错误判断及处理。
参数说明:
参数path字符串包含欲打开的文件路径及文件名,参数mode字符串则代表着流形态。
mode有下列几种形态字符串:
r 以只读方式打开文件,该文件必须存在。
r+ 以可读写方式打开文件,该文件必须存在。
rb+ 读写打开一个二进制文件,允许读写数据,文件必须存在。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
wb 只写打开或新建一个二进制文件;只允许写数据。
wb+ 读写打开或建立一个二进制文件,允许读和写。
ab+ 读写打开一个二进制文件,允许读或在文件末追加数据。
wx 创建文本文件,只允许写入数据.[C11]
wbx 创建一个二进制文件,只允许写入数据.[C11]
w+x 创建一个文本文件,允许读写.[C11]
wb+x 创建一个二进制文件,允许读写.[C11]
w+bx 和"wb+x"相同[C11]
以x结尾的模式为独占模式,文件已存在或者无法创建(一般是路径不正确)都会导致fopen失败.文件以操作系统支持的独占模式打开.[C11]
上述的形态字符串都可以再加一个b字符,如rb、w+b或ab+等组合,加入b 字符用来告诉函数库以二进制模式打开文件。如果不加b,表示默认加了t,即rt,wt,其中t表示以文本模式打开文件。由fopen()所建立的新文件会具有S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)权限,此文件权限也会参考umask值。
有些C编译系统可能不完全提供所有这些功能,有的C版本不用"r+","w+","a+",而用"rw","wr","ar"等,读者注意所用系统的规定。
二进制和文本模式的区别
1.在windows系统中,文本模式下,文件以"\r\n"代表换行。若以文本模式打开文件,并用fputs等函数写入换行符"\n"时,函数会自动在"\n"前面加上"\r"。即实际写入文件的是"\r\n" 。
2.在类Unix/Linux系统中文本模式下,文件以"\n"代表换行。所以Linux系统中在文本模式和二进制模式下并无区别。
打开方式总结:各种打开方式主要有三个方面的区别:
①打开是否为二进制文件,用“b”标识。
②读写的方式,有以下几种:只读、只写、读写、追加只写、追加读写这几种方式。
③对文件是否必 须存在、以及存在时是清空还是追加会有不同的响应。
		
	*/
	fp = fopen(argv[1], "rb");			//以读二进制的方式打开argv[1](main运行时传入的第一个参数)里表示的文件,成功就返回指向该流的文件指针(读文件内容的指针)
	if( fp == NULL)						//如果没有这个文件
	{
		printf("source file open error\n");
		free(Buf);						//释放Buf
		return -1;
	}
	// 3.2 获取源bin长度
	fseek(fp, 0L, SEEK_END);			//文件指针移到结尾
	/*
	int fseek(FILE *stream, long offset, int fromwhere);fseek 用于二进制方式打开的文件,移动文件读写指针位置.
	fseek(in,-1L,1);   -- 文件流in, 零点为当前指针位置,SEEK_CUR 就是 1,  -1L -- 文件指针回退1个字节int fseek( FILE *stream, long offset, int origin );
	第一个参数stream为文件指针
	第二个参数offset为偏移量,整数表示正向偏移,负数表示负向偏移
	第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
	SEEK_SET: 文件开头
	SEEK_CUR: 当前位置
  SEEK_END: 文件结尾
  其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2.
  简言之:
  fseek(fp,100L,0);把fp指针移动到离文件开头100字节处;
  fseek(fp,100L,1);把fp指针移动到离文件当前位置100字节处;
   fseek(fp,100L,2);把fp指针退回到离文件结尾100字节处。

	*/
	fileLen = ftell(fp);				// ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数
	fseek(fp, 0L, SEEK_SET);			//文件指针移到开头
	// 3.3 源bin长度不得超过16K-16byte
	count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))//文件大小要小于IMG_SIZE - SPL_HEADER_SIZE所剩下的大小
		? fileLen : (IMG_SIZE - SPL_HEADER_SIZE);
	// 3.4 buffer[0~15]存放"S5PC110 HEADER  "
	/*
	void *memcpy(void *dest, const void *src, size_t n);
	从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中
	eg:
		char src[] ="******************************";
		char dest[]="abcdefghijlkmnopqrstuvwxyz0123as6";
		printf("destination before memcpy:%s\n",dest);
		memcpy(dest,src,strlen(src));
		printf("destination after memcpy:%s\n",dest);
	结果:
		destination before memcpy:abcdefghijlkmnopqrstuvwxyz0123as6
		destination after memcpy: ******************************as6
	*/
	memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE);//从SPL_HEADER拷贝SPL_HEADER_SIZE个字节,到Buf中
	// 3.5 读源bin到buffer[16]
	nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp);//从fp中读入count个数据到Buf + SPL_HEADER_SIZE处
	/*
	size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
	从一个stream文件流中读数据,最多读取count个元素,每个元素size字节,如果调用成功返回实际读取到的元素个数,如果不成功或读到文件末尾返回 0。
	 参 数
		buffer  用于接收数据的内存地址
		size	要读的每个数据项的字节数,单位是字节
		count	要读count个数据项,每个数据项size个字节.
		stream	输入流
	 返回值
		实际读取的元素个数。如果返回值与count不相同,则可能文件结尾或发生错误。从ferror和feof获取错误信息或检测是否到达文件结尾。
	*/
	if ( nbytes != count )//如果实际读取的数据没有count个时
	{
		printf("source file read error\n");
		free(Buf);
		fclose(fp);
		return -1;
	}
	fclose(fp);			//关闭文件fp

	// 4. 计算校验和
 	// 4.1 从第16byte开始统计buffer中共有几个1
	a = Buf + SPL_HEADER_SIZE;
	for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++)
		checksum += (0x000000FF) & *a++;
	// 4.2 将校验和保存在buffer[8~15]
	a = Buf + 8;
	*( (unsigned int *)a ) = checksum;

	// 5. 拷贝buffer中的内容到目的bin
	// 5.1 打开目的bin
	fp = fopen(argv[2], "wb");
	if (fp == NULL)
	{
		printf("destination file open error\n");
		free(Buf);
		return -1;
	}
	// 5.2 将16k的buffer拷贝到目的bin中
	a = Buf;
	/*
	size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
	注意:这个函数以二进制形式对文件进行操作,不局限于文本文件
	返回值:返回实际写入的数据块数目
	(1)buffer:是一个指针,对fwrite来说,是要获取数据的地址;
	(2)size:要写入内容的单字节数;
	(3)count:要进行写入size字节的数据项的个数;
	(4)stream:目标文件指针;
	(5)返回实际写入的数据项个数count。
	说明:写入到文件的哪里? 这个与文件的打开模式有关,如果是w+,则是从file pointer指向的地址开始写,替换掉之后的内容,文件的长度可以不变,stream的位置移动count个数;如果是a+,则从文件的末尾开始添加,文件长度加大。
			fseek对此函数有作用,但是fwrite[1]  函数写到用户空间缓冲区,并未同步到文件中,所以修改后要将内存与文件同步可以用fflush(FILE *fp)函数同步。
	eg:
	 FILE *fp;
    char msg[] = "file content";
    char buf[20];
    fp = fopen("d:\\a\\a.txt","w+");
    if (NULL == fp)
    {
        printf("The file doesn't exist!\n");
        return -1;
    }
    fwrite(msg,strlen(msg),1,fp);//把字符串内容写入到文件
    fseek(fp,0,SEEK_SET);//定位文件指针到文件开始位置
    fread(buf,strlen(msg),1,fp);//把文件内容读入到缓存
    buf[strlen(msg)] = '\0';//删除缓存内多余的空间
    printf("buf = %s\n",buf);
    printf("strlen(buf) = %d\n",strlen(buf));
	
	*/
	nbytes	= fwrite( a, 1, BufLen, fp);
	if ( nbytes != BufLen )
	{
		printf("destination file write error\n");
		free(Buf);
		fclose(fp);
		return -1;
	}

	free(Buf);
	fclose(fp);

	return 0;
}

write2sd文件:

#!/bin/sh
sudo dd iflag=dsync oflag=dsync if=210.bin of=/dev/sdb seek=1
将sd卡插入PC,在linux终端执行如下命令:

# cd 1.led_s

# make

# chmod 777 write2sd

# ./write2sd

执行make后会生成210.bin文件,执行./write2sd后210.bin文件会被烧写到sd卡的扇区1中,sd卡的起始扇区为0,一个扇区的大小为512byte,sd启动时,IROM里的固化代码是从扇区1开始拷贝代码的。

write2sd是一个脚本文件,内容如下: #!/bin/sh sudo dd iflag=dsync oflag=dsync if=210.bin of=/dev/sdb seek=1 dd是一个读写命令,if是输入,of是输出,seek表示从扇区1开始读写。

注:sdb为本文档编写时sd卡的设备节点,请根据自己的实际情况进行修改,后面将不再提及该注意事项。
------------------------------后面有可能会遇到的问题------------------------------------------

首先make前,你要保证你安装好了arm-linux-gcc。手册有装的方法

如果是用别人提供的代码,请注意权限问题

执行write2sd前,注意你的sd卡是否已挂载,挂载设备名称是不是/dev/sdb,如不是,请注意修改write2sd

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值