1.dnw工具
软件的功能是通过USB连接开发板和电脑,然后从主机下载文件镜像到开发板中去烧录系统。下载时需要安装dnw的USB驱动、连接USB线、设置dnw下载内存地址。设置裸机下载地址download address为0xd0020010(因为USB启动做裸机实验时,不需要16字节的校验头,所以直接下载到该地址)。
注:安装驱动前需要禁用数字签名(百度),安装成功后dnw软件中USB:OK。
2.USB启动配合dnw工具下载进行裸机实验
当检测到设置的是USB启动时,S5PV210就会从USB口连接主机下载启动,在裸机实验中必须一直按着电源键才能保持开机。USB启动方式主要是用来调试程序的,这里是把裸机程序当做BL1来使用。(只有裸机阶段使用dnw)
3.一般用USB下载来调试裸机程序比较方便,但是有时候电脑使用 dnw会蓝屏,这用SD卡下载调试是不错的选择。
4.不登陆到系统,仅在uboot下如何擦除第一个扇区?
在三秒启动时,进入uboot。输入movi write u-boot 0x30000000,回车。(把内存(DDR)里0x30000000地址开始的东西写入uboot中,猜测是全0,因为内存里什么也没有)
注:help movi可以查找movi的用法。
5.SD卡和USB启动优劣势对比,如果不蓝屏,建议用USB下载方便。
6.linux中安装软件的特点
(1)apt-get install vim,在线安装。
(2)自己下载安装包来安装,缺点是不知道安装包和系统版本是否匹配。
(3)用源代码安装。
7.交叉编译工具链(arm-linux-gcc)的安装
交叉编译工具链arm-2009q3是三星开发S5PV210时使用的这个版本的交叉编译工具链。采用方式2安装。①在虚拟机root的/usr/local下创建arm文件夹。②将需要安装的压缩包arm-2009q3.tar.bz2放入Windows中的共享文件夹。③从共享文件夹中复制到arm目录下,解压。
解压完成后,真正的应用程序在arm/arm-2009q3.tar.bz2/bin中。
在bin目录下执行./arm-none-linux-gnueabi-gcc -v查看到版本号,即表示安装成功。(如果No such file or directory,需要安装兼容32位工具的兼容包)
https://blog.youkuaiyun.com/zjy900507/article/details/78719876?ops_request_misc=%25257B%252522request%25255Fid%252522%25253A%252522160921213316780308349323%252522%25252C%252522scm%252522%25253A%25252220140713.130102334.pc%25255Fall.%252522%25257D&request_id=160921213316780308349323&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-12-78719876.pc_search_result_no_baidu_js&utm_term=Ubuntu20.04x64%E5%AE%89%E8%A3%8532%E4%BD%8D%E5%85%BC%E5%AE%B9%E5%8C%85
8.为什么要安装交叉编译工具链,gcc和arm-linux-gcc有什么区别?
gcc是在linux下将代码编译成可执行的二进制文件,但是同样的二进制文件放到Windows或arm中却不一定能实现同样的功能,arm-linux-gcc表示编译的环境的linux,编译的结果可以使用于arm系统中。
9.环境变量是什么?
环境变量是操作系统的全局变量。每一个环境变量对操作系统来说是唯一的,名字和所代表的意义也是唯一的。linux可以有很多环境变量,其中一部分是系统自带的,还有一些是我们自己扩充的。
10.环境变量PATH
PATH是系统自带的,含义是系统在查找可执行程序时会搜索的路径范围。在这里修改PATH变量,export PATH=$PATH:/usr/local/arm/arm-2009q3.tar.bz2/bin,将工具链导出到环境变量。这样就可以在其他目录下执行arm-none-linux-gnueabi-gcc -v。
原PATH
新PATH
但是关掉这个terminal终端再重新打开后会发现PATH又恢复到原来的状态,是因为之前的操作仅对当前终端生效。解决方法:在~/.bashrc中,添加export PATH=/usr/local/arm/arm-2009q3/bin:$PATH(此环境变量的更改仅对当前root用户生效,对其他用户是不生效的)。当终端被打开时会自动执行.bashrc文件,所以添加后需要退出该终端重新打开新的终端就可以应用了。
打开新终端测试
11.为工具链创建符号链接
在此bin文件夹中输入ln arm-none-linux-gnueabi-gcc -s arm-linux-gcc。以后可以用简短的arm-linux-gcc来代替前者。
测试
该命令显示为arm-none-linux-gnue-abi-gcc的符号链接
在Windows中制作好脚本mk-arm-linux-.sh,复制到bin文件夹下,更改其为权限为可执行(chmod a+x)。输入./mk-arm-linux-.sh把其他命令也简单化。
12.Makefile使用
makefile是用来管理工程的,在一个正式软件项目中,有很多.c和.h文件,如果要编译,则gcc a.c b.c c.c d.c e.c f.c -o exe,每次编译都要输入很长的命令,很麻烦,这时可以用Makefile来解决。
makefile的一些基本概念
·目标:定格写,加冒号,后面是依赖
·依赖:用来生成目标的原材料
·命令:命令前用Tab不用空格
Makefile示例
(1)新建a.c
(2)新建b.c
(3)新建makefile
(4)运行makefile生成exe文件,运行exe
注:如果make如下,可以单独执行make exe或make clean;而执行make的效果等同于执行第一个目标。
13.makefile实例讲解(真正的项目)
上图为makefile文件的代码。裸机和真正的项目中的makefile是把程序的编译和链接过程分开,编译使用gcc,链接使用ld(平时gcc a.c -o exe实际上把编译和链接一步完成了)
arm-linux-gcc -o $@
<
−
c
/
/
−
c
表
示
只
编
译
不
链
接
;
自
动
变
量
< -c //-c表示只编译不链接;自动变量
<−c//−c表示只编译不链接;自动变量@和$<分别表示%.o和%.s。%.o表示将所有的.S和.c文件编译为.o文件。
通过ld链接器得到操作系统的可执行文件.elf(-Text0表示代码段起始地址);但是在裸机中需要可以烧写的二进制文件(即镜像image),因此需要通过arm-linux-objcopy来用.elf生成.bin;arm-linux-objdump是用来反编译(反汇编)得到汇编代码.dis;gcc mkv210_image.c -o mk210中.c程序不是在开发板上运行的,而是在linux中执行,所以用gcc不用arm-linux-gcc;./mk210 led.bin 210.bin中后两个是参数,程序执行的功能是从led.bin得到210.bin(210.bin是SD卡启动时的裸机镜像;led.bin是USB启动时的镜像)
下图为此makefile文件的使用。
14.mk210_image.c文件详解
回顾210启动过程:先执行内部iROM中的BL0,BL0执行完后判断从USB还是SD卡启动。USB启动时直接下载镜像到0xd0020010去执行;SD卡启动时,BL0读取SD的包括校验头的完整镜像,BL0根据实际镜像计算校验和checksum和完整镜像的头部进行比对,如果对则执行BL1,否则启动失败(从内置eMMC启动也是一样要检查校验和)
此程序的功能是从led.bin得到含校验头的210.bin。校验过程通过SD卡的镜像算出校验和和原先linux中该程序算出的校验头进行比对,如果一样则继续执行,这是为了验证SD卡程序是否出错。(是s5pv210特有的)
下图显示了16个字节的分配,前四个字节为BL1 size unit:byte,BL1大小为16K,第三段的四个字节为校验和,第二和第四段都为0。
mk210_image.c代码如下:
/*
* mkv210_image.c的主要作用就是由usb启动时使用的led.bin制作得到由sd卡启动的镜像210.bin
*
* 本文件来自于友善之臂的裸机教程,据友善之臂的文档中讲述,本文件是一个热心网友提供,在此表示感谢。
*/
/* 在BL0阶段,Irom内固化的代码读取nandflash或SD卡前16K的内容,
* 并比对前16字节中的校验和是否正确,正确则继续,错误则停止。
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define BUFSIZE (16*1024) //BL1的大小是固定的16K
#define IMG_SIZE (16*1024)
#define SPL_HEADER_SIZE 16 //校验头的大小为16字节
//#define SPL_HEADER "S5PC110 HEADER "
#define SPL_HEADER "****************"
int main (int argc, char *argv[]) //argc和argv是main函数的形参,argc指传了几个参数(包含程序./mkx210本身),argv指传的每个参数是什么,makefile中./mkx210 led.bin 210.bin,表示argc=3,argv[0]=”./mkx210”,argv[1]=”led.bin”,argv[2]=”210.bin”。
{
FILE *fp;
char *Buf, *a;
int BufLen;
int nbytes, fileLen;
unsigned int checksum, count;
int i;
// 1. 检查是否为3个参数
if (argc != 3)
{
printf("Usage: %s <source file> <destination file>\n", argv[0]);
return -1;
}
// 2. 分配16K的buffer
BufLen = BUFSIZE;
Buf = (char *)malloc(BufLen); //使用malloc来分配16K的buffer
if (!Buf)
{
printf("Alloc buffer failed!\n");
return -1;
}
memset(Buf, 0x00, BufLen); //堆内存清零
// 3. 读源bin到buffer
// 3.1 打开源bin
fp = fopen(argv[1], "rb"); //用二进制只读(rb)的方式来打开
if( fp == NULL) //校验打开是否失败
{
printf("source file open error\n");
free(Buf);
return -1;
}
// 3.2 获取源bin长度
fseek(fp, 0L, SEEK_END); // 定位到文件尾
fileLen = ftell(fp); // 得到文件长度
fseek(fp, 0L, SEEK_SET); // 再次定位到文件头
// 3.3 源bin长度不得超过16K-16byte
count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))
? fileLen : (IMG_SIZE - SPL_HEADER_SIZE); //写入的长度,如果超过16K-16则多余部分会被去掉
// 3.4 buffer[0~15]存放"S5PC110 HEADER "
memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE); //用memcpy去16字节"S5PC110 HEADER "来占位(共16位,用”****************”亦可)
// 3.5 读源bin到buffer[16]
nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp); //把内容读进来从Buf+16位置开始,长度为count
if ( nbytes != count )
{
printf("source file read error\n");
free(Buf);
fclose(fp);
return -1;
}
fclose(fp); //写完关闭
// 4. 计算校验和(内存中的内容按字节为单位相加的和为校验和,指针类型为char *)
// 4.1 从第16byte开始统计buffer中共有几个1
// 4.1 从第16byte开始计算,把buffer中所有的字节数据加和起来得到的结果
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; // Buf是210.bin的起始地址,+8表示向后位移8个字节,也就是说写入到第9个字节(写到第三段checksum中)
*( (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;
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;
}
工作流程分析:申请一个16KB大小的buffer,把所有内容按照各自的位置填进去,最终把填好的buffer写入到文件210.bin中形成镜像。
工作步骤:(1)检验用户传参是否为3个;(2)分配16KB的buffer并且填充为0(比较大的内存都用malloc去分配堆内存)(3)……
glibc读写文件接口:linux中要读取一个文件,可以使用fopen打开文件,fread读取文件,读完之后fclose关闭文件。要写文件用fwrite来写。这些函数是glibc的库函数,在linux中用man 3 可以查找。如果你本身就知道这些函数的用法,只是记不起来可以man查找;如果你本身根本就不会用这些接口,建议先去baidu。
15.GPIO通用输入输出
GPIO是芯片的部分引脚,可以编程控制它的工作模式或电压高低。要操作这些GPIO,必须通过设置他们的寄存器。
GPJ0相关的寄存器有以下:(操控LED点亮主要用前两个)
GPJ0CON, (GPJ0 control)GPJ0控制寄存器,用来配置各引脚的工作模式
GPJ0DAT, (GPJ0 data)当引脚配置为input/output模式时,寄存器的相应位和引脚的电平高低相对应。
GPJ0PUD, (pull up down)控制引脚内部弱上拉、下拉,一般不用
GPJ0DRV, (driver)配置GPIO引脚的驱动能力
GPJ0CONPDN,(记得是低功耗模式下的控制寄存器)
GPJ0PUDPDN (记得是低功耗模式下的上下拉寄存器)
16.LED的点亮
当正极已经确定3.3V,负极接在SoC引脚GPIO上,可通过编程控制负极输出低电平使之产生电压差,点亮LED。
操控led点亮步骤(1)操控GPJ0CON寄存器,选中output模式;(2)操控GPJ0DAT寄存器,相应的位设置为0
下图为GPJ0CON,有8个引脚,作为ARM的寄存器,它是32位的,32/8=4,则平均每个引脚分到4位来控制引脚的工作模式。如GPJ0_3对应bit12-bit15,写入0b0001时为输出模式。
下图为GPJ0DAT,有8个引脚。
由上两张图可见GPJ0CON地址为0xE0200240,GPJ0DAT地址为0xE0200244,因为是32位的寄存器,所以地址应该加4(1个地址是1个字节,8位)
拓展:
·存储单元一般应具有存储数据和读写数据的功能,以8位二进制作为一个存储单元,也就是一个字节。每个单元有一个地址,是一个整数编码,可以表示为二进制整数。程序中的变量和主存储器的存储单元相对应。变量的名字对应着存储单元的地址,变量内容对应着单元所存储的数据。存储地址一般用十六进制数表示,而每一个存储器地址中又存放着一组二进制(或十六进制)表示的数,通常称为该地址的内容。
·在计算机中最小的信息单位是bit,也就是一个二进制位,8个bit组成一个Byte,也就是字节。一个存储单元可以存储一个字节,也就是8个二进制位。计算机的存储器容量是以字节为最小单位来计算的,对于一个有128个存储单元的存储器,可以说它的容量为128字节。
操作部分:
(1)在2.leds_s中复制1.leds_s的工程管理文件、校验和制作文件、脚本文件,新建led.S(名字应与makefile中相同)
(2)由于GPJ0CON3-5都是0b0001=0x1输出模式,这里可直接把8个位都写成1,即把0x11111111写入0xE0200240位置,可把GPJ0DAT的32个位都写成0(虽然只有8个位有效且只用其中的3个位),即把0x0写入0xE0200244位置。
所有的汇编程序开头都要写个标号,标志程序的入口,这里为start。
(3)用make编译得到led.bin和210.bin等文件
(4)可以选择led.bin或210.bin。这里连接USB线,打开dnw软件设置地址和端口,USB port→transmit选择led.bin。可以看到开发板上有3个灯变得特别亮。
17.使用位运算实现led的复杂点亮
(1)3.leds_s:
·可以用宏定义来定义寄存器名字(把程序中的地址换成宏):
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
·死循环可以写成b .
·在make的时候出现了警告,虽不影响实验现象,但可以通过加.global _start把_start链接属性改为外部消除警告(_start是汇编的入口,汇编程序的开始)
·更改设计:把4号led设为0,其他两个为1,不相关的也为0
(2)4.leds_s
·为更显示更清晰的思路,使用位运算更改16行为ldr r0, =((1<<3) | (0<<4) | (1<<5))
18.闪烁LED 6.leds_s
主程序是一个死循环,类似于main函数,其他函数必须写在循环主体后面。编写delay时,初始化要写在循环体外。汇编中调用函数用bl,子函数最后用mov pc, lr来返回。
sub ax,bx表示ax减bx的结果放到ax中。
cmp会影响Z标志位,cmp后r1减r2,如果等于0则Z标志位为1,表示两者相等。
·//cmp同bne搭配,如果r1≠r2,则跳转到copy_loop,否则向下执行
cmp r1,r2
bne copy_loop
·//cmp同beq搭配,如果r1=r2,则跳转到copy_loop,否则向下执行
cmp r1,r2
bne copy_loop
19.流水灯闪烁 7.leds_s
这里改三处即可
ldr r0, =((0<<3) | (1<<4) | (1<<5))或ldr r0, =~(1<<3)
ldr r0, =((1<<3) | (0<<4) | (1<<5))或ldr r0, =~(1<<4)
ldr r0, =((1<<3) | (1<<4) | (0<<5))或ldr r0, =~(1<<5)
20.反汇编工具objdump
arm-linux-objdump -D led.elf > led_elf.dis。-D表示反汇编,左边是可执行程序,右边是反汇编得到的汇编程序。反汇编的原因:(1)逆向破解。(2)调试程序时,反汇编可以有助于理解程序(尤其理解链接脚本、链接地址等概念时)。(3)把c语言编译成可执行程序后反汇编得到汇编代码,有助于理解c语言和汇编的关系,有助于深入理解c语言。
反汇编的格式(汇编assembly,反汇编dissembly )
标号地址、标号名字、指令地址、指令机器码、指令机器码反汇编得到的指令。如下图。
反汇编的时候得到的指令地址是链接器考虑了链接脚本之后得到的地址(-Text0),而我们写代码时通过指定连接脚本来让链接器给我们链接合适的地址。
但是有时候我们写的链接脚本有误(或者我们不知道这个链接脚本会怎么样),这时候可以通过看反汇编文件来分析这个链接脚本的效果,看是不是我们想要的,如果不是可以改了再看。