目录
二、转移序列(如 )和转换说明符(如在printf()中使用的%d、%c等)、printf修饰符实例、scanf()函数输入的方式、重定向输入及EOF的使用
2、函数与二维数组 zoppo是一个二维数组的名字,如何理解*((*zoppo+2)+1)等价于zoppo[2][1]?
四、字符串---是否可以使用str[0]='F';修改字符串str中的第一个字符?
3、裸板程序开发之LED"流水灯"---使用汇编和C语言实现(启动代码使用汇编实现,点灯使用C语言实现)
4、汇编文件中调用C语言实现的函数,并向C语言实现的函数传递四个以内的参数参数、传递多于四个的参数
6、改变系统时钟实验(晶振原来给CPU提供的是12MHz,经过APLL后可以达到532MHz)---使用汇编实现 该实验的Makefile文件书写方法 Makefile文件的书写方法
7、改变系统时钟实验(晶振原来给CPU提供的是12MHz,经过APLL后可以达到500MHz)---使用C语言实现
8、串口实验 带.h文件的Makefile文件袋额书写方法以及Makefile文件执行顺序的解析:带.h和不带.h问的Makefile文件书写方法是一样的
9、链接地址和uboot重定位 存储设备总结 链接地址举例(在6410内部进行重定位)
10、6410片外内存DDR初始化和重定位到DDR内的地址运行
11、NandFlash实验---如果生成的.bin文件超过了6410片内内存(8K)的大小,那么此时可以直接从NandFlash芯片中将程序拷贝到DDR中 时序图的讲解和使用
一、基本类型和打印方法
直接上代码吧
1 #include <stdio.h> //for printf()、scanf()、getchar()、putchar() 2 #include <stdin.h> //for int32_t、int64_t等 3 #include <string.h> //for strlen() 4 #include <limits.h> //for INT_MAX、INT_MIN等使用宏定义的常量 5 #include <ctype.h> //for isalnum()、isdigit()、tolower()、toupper()等函数 6 int main(){ 7 /*int*/ 8 int ten=100; 9 printf("dec=%d;octal=%o;hex=%x ",ten,ten,ten); //dec=100;octal=144;hex=64 10 printf("dec=%d;octal=%#o;hex=%#x ",ten,ten,ten); //dec=100;octal=0144;hex=0x64 加上#表示要显示各进制数的前缀:八进制前缀0、0x和0X 11 12 /*浮点数*/ 13 float some1 = 2.0*3.0; //编译器默认2.0和3.0为double类型,将乘积结果转换为float然后赋值给some1 14 float some2 = 2.0f*3.0f; //在2.0和3.0后面加上f或F可以覆盖编译器的默认设置 15 long double some3 = 2.0L; //L表示long double 16 float aboat = 32000.0; 17 double abet = 2.14e9; 18 printf("%f can be written %e ",aboat,aboat); //32000.000000 can be written 3.200000e+04 其中%e表示打印指数计数法的浮点数;%f可以表示float和double类型的 19 20 /*字符*/ 21 char ch = 'A'; 22 char ch2 = 65; //正确但是不提倡,此时ch2='A',因为A的ASCII码是65 23 printf("the code for %c is %d ",ch,ch); //the code for A is 65 24 25 /*字符串---以空字符 结尾的char型数组*/ 26 char name[40]; //只可以存储39个字符,必须留一个位置给 27 scanf("%s",name); //由于name本身就是一个地址,所以不用使用&name,%s对应于字符串输入输出 28 int size = sizeof(name); //name占用的空间的大小,size=40 29 int letters = strlen(name); //数组name中包含的字符的个数,letters的大小为name中包含的字符的个数,不包含空字符 30 int weight; 31 scanf("%d",&weight); //此时才需要写成&weight 32 33 /*字符串和指针*/ 34 const char m1[40] = "I am xiaoming."; //使用数组创建字符串 35 const char m2[] = "Hello,How are you"; //不指名数组的大小,让编译器通过查找字符串末尾的空字符去计算字符的个数 36 const char *m3 = "Tommorow will be better!"; //使用指针创建字符串 37 const char *m4 = m1; 38 39 40 /*使用scanf()给数组输入数字或字符*/ 41 int score[10]; 42 for(int i=0;i<10;++i){ 43 scanf("%d",&score[i]); //输入方式可以是连续使用键盘输入:99 98 97 96 95 94...等十个数字有换行也可 44 } 45 46 /*getchar()和putchar()的使用*/ 47 char ch; 48 getchar(ch); //等价于scanf("%c",&ch); 49 putchar(ch); //等价于printf("%c",ch); 50 51 /*ctype.h中函数测试*/ 52 char ch; 53 getchar(ch); 54 isalnum(ch); //如果ch是字母或数字则返回true,否则返回false 55 isalpha(ch); //如果ch是字母则返回true,否则返回false 56 isdigit(ch); //如果ch是数字则返回true,否则返回false 57 islower(ch); //如果ch是小写字母则返回true,否则返回false 58 isupper(ch); //如果ch是大写字母则返回true,否则返回false 59 isblank(ch); //如果ch是标准的空白字符(空格、 、v或 等)则返回true,否则返回false 60 tolower(ch); //如果ch是大写字符,则返回ch对应的小写字符;否则返回原始参数 61 toupper(ch); //如果ch是小写字符,则返回ch对应的大写字符;否则返回原始参数 62 63 /*条件运算符: ?:*/ 64 epression1 ? epression2 : expression3; //如果expression1为true,则执行expression2;否则执行expression3 65 66 /*指针与多维数组*/ 67 int zippo[4][2] = {{1,2},{3,4},{5,6},{7,8}}; 68 int (*pz)[2]; //pz指向一个包含2个int型值的数组 69 int * pax[2]; //创建一个数组pax,该数组内包含两个int型指针 70 pz=zippo; //zippo[0][0]等价于pz[0][0] 71 72 /* 73 1)zippo是该数组元素的首地址。在本例中zippo是一个包含两个元素({1,2})的首地址,所以zippo是包含两个int大小对象的地址; 74 2)因为zippo是数组元素的首地址,zippo[0]表示的就是{1,2},所以zippo和&zippo[0]是等价的; 75 3)因为zippo[0]表示的就是{1,2},所以zippo[0]和&zippo[0][0]是等价的,所以zippo[0]是占用一个int大小对象的地址 76 4)由于zippo指向的对象占用了两个int的大小,所以zippo+1是{3,4}的首地址; 77 5)由于zippo[0]指向的对象占用了一个int的大小,所以zippo[0]+1指向了{1,2}中2的地址; 78 理解*((*zoppo+2)+1): 79 zippo:二维数组首元素的地址(每个元素都内含2各int类型元素的一维数组) 80 zippo+2:二维数组第3个元素(即一维数组)的地址 81 *(zippo+2):二维数组第3个元素(即一维数组)的首元素(一个int类型的值)的地址,5 82 *(zippo+2)+1:二维数组第3个元素(即一维数组)的第二个元素(一个int类型的值)的地址,即6的地址 83 *(*(zippo+2)+1):二维数组第3个元素(即一维数组)的第二个元素的值(即6),等价于zippo[2][1] 84 */ 85 86 /*移位运算符*/ 87 //number << n 表示将number乘以2的n次幂 88 //number >>n 如果number非负,则将number除以2的n次幂 89 90 return 0; 91 }
二、转移序列(如 )和转换说明符(如在printf()中使用的%d、%c等)、printf修饰符实例、scanf()函数输入的方式、重定向输入及EOF的使用
1、转移序列
a 警报
退格
换行
把光标移动到当前行的开始处
水平制表符
v 垂直制表符
2、转换说明符
%a 浮点数、十六进制和p计数法
%A 浮点数、十六进制和p计数法
%c 单个字符
%d 有符号十进制
%e 浮点数,e计数法
%E 浮点数,e计数法
%f 浮点数,十进制计数法
%o 无符号八进制整数
%i 有符号十进制(和%d功能相同)
%p 指针
%s 字符串
%u 无符号十进制整数
%x 无符号十六进制整数,使用十六进制数of
%X 无符号十六进制整数,使用十六进制数oF
%% 打印一个百分号
3、printf修饰符实例
%4d 表示打印4各有符号十进制整数
%5.2f 表示整数部分打印5个数字,小数部分打印2个数字
%zd z表示是size_t类型的值,size_t是sizeof返回值的类型
%ld 表示long int
%lld 表示long long int
%Ld 表示long double
3、scanf()函数输入的方式
scanf()每次读取一个字符,跳过所有的空白字符,直到遇到第一个非空白字符才开始读取。以scanf("%d",&num)为例,因为要读取一个整数,所以scanf()希望发现一个数字或者+、-,如果已经找到一个数字或一个+或-,它便开始保存该字符,并读取下一个字符。scanf()不断的读取和保存字符,直到遇到非数字字符。最后scanf()计算已读取的数字,并放入指定的变量中。
例1:
scanf("%d,%d",&a,*b);
scanf()将上面的代码解释乘用户需要输入一个数字、一个逗号、一个数字,因此用户以下的输入方式都是合法的(前提是第一个数字后必须有一个逗号):
88,121
88 ,121
例2:
scanf("%d ,%d",&a,*b);
以下输入均合法(因为空格相当于空白字符):
88,121
88 ,121
88 , 121
4、重定向输入及EOF的使用
假如有如下的c代码:
1 //echo_eof.c---重复输入直到文件尾 2 #include <stdio.h> 3 int main(){ 4 char ch; 5 while((ch=getchar()) != EOF){ 6 putchar(ch); 7 } 8 return 0; 9 }
假如编译echo_eof.c文件后生成的可执行文件尾echo_eof,现在有一个txt文件mywords.txt需要输入,就可以在Linux下使用重定向的方式输入:
1 echo_eof<mywords
1)EOF是定义在stdio.h中的一个宏定义,EOF=-1
2)在c语言中,使用getchar()检测到读到文件的结尾时候返回一个EOF,scanf()读到文件尾时也会返回一个EOF
5、重定向输出
echo_eof>mywords2
假如已经有一个mywords2文件,那么会擦除源文件中的内容并输入ehco_eof可执行文件输出的内容;
假如没有该文件,那么会首相创建mywords2,然后再写入。
6、组合重定向
echo_eof < mywords >savewords
先读mywords中的内容,然后保存到savewords中
echo_eof >savewords < mywords >savewords 也可,因为命令与重定向运算符的顺序无关
7、重定向运算符需要遵循的规则:
1)重定向输入运算符<和重定向输出运算符>都必须连接一个可执行文件和一个数据文件;
2)重定向输入运算符<不能读取多个文件的输入,重定向输出运算符>也不能输出到多个文件;
3)文件名和运算符之间的空格不是必须的。
三、函数和多维数组
1、函数与一维数组
直接上代码吧:
1 //函数与一维数组 2 #include <stdio.h> 3 #define SIZE 10 4 5 int sum1(int arr[],int n); //数组表示法 6 int sum2(int *start,int *end); //指针表示法 7 int sum3(int *arr,int n); 8 9 int main(){ 10 int marble[SIZE]={1,2,3,4,5,6,7,8,9,0}; 11 12 int s1 = sum1(marble,SIZE); //数组名字做为实参 13 int s2 = sum2(marble,marble+SIZE); //数组名字做为实参 14 int s3 = sum3(marble,SIZE); //数组名字做为实参 15 16 return 0; 17 } 18 19 int sum1(int arr[],int n){ //一维数组作为形参 20 int tot = 0; 21 for(int i=0;i<n;++i){ 22 tot += arr[i]; 23 } 24 return tot; 25 } 26 27 int sum2(int *start,int *end){ //一维数组指针作为形参 28 int tot = 0; 29 while(start<end){ 30 tot += (*start); 31 ++start 32 } 33 return tot; 34 } 35 36 int sum3(int *arr,int n){ //一维数组指针作为形参 37 int tot = 0; 38 for(int i=0;i<n;++i){ 39 tot += arr[i]; 40 } 41 return tot; 42 }
2、函数与二维数组
直接上代码吧
1 //函数和多维数组 2 #include <stdio.h> 3 #define ROWS 3 4 #define COLS 4 5 6 void sum_rows(int ar[][COLS],int rows); //二维数组作为形参 7 void sum_cols(int ar[][COLS],int rows); 8 int sum2d(int(*ar)[COLS],int rows); 9 10 int main(){ 11 int junk = {{2,4,6,8},{3,5,7,9},{12,10,8,6}}; 12 sum_rows(junk,ROWS); 13 sum_cols(junk,ROWS); 14 int allSum = sum2d(junk,ROWS); 15 16 return 0; 17 } 18 19 void sum_rows(int ar[][COLS],int rows){ 20 int r,c,tot; 21 for(r=0;r<rows;++r){ 22 tot = 0; 23 for(c=0;c<COLS;++c){ 24 tot += arr[r][c]; 25 } 26 printf("row %d: sum = %d ",r,tot); 27 } 28 } 29 30 void sum_cols(int ar[][COLS],int rows){ 31 int r,c,tot; 32 for(c=0;c<COLS;++c){ 33 tot = 0; 34 for(r=0;r<ROWS;++r){ 35 tot += arr[r][c]; 36 } 37 printf("col %d: sum = %d ",c,tot); 38 } 39 } 40 41 int sum2d(int(*ar)[COLS],int rows){ 42 int r,c; 43 int tot = 0; 44 for(r=0;r<ROWS;++r){ 45 tot = 0; 46 for(c=0;c<COLS;++c){ 47 tot += arr[r][c]; 48 } 49 } 50 return tot; 51 } 52 53 //注意:以下函数的声明不正确: 54 int sum2(int ar[][],int rows); //错误声明 55 int sum2(int ar[3][4],int rows); //有效声明,但会将3忽略 56 int sum4d(int (*ar)[12][20][30],int rows); //有效声明,ar指向一个12*20*30的int型数组 57 58 //变长数组--这里的变长指的是可以使用变量来执行数组的维度,不是可以改变数组的维度大小,如: 59 int r=5,c=4; 60 double arr[r][c]; //变长数组,C99/C11支持 61 int sum2d(int r,int c,double arr[r][c]); //需要注意的是定义arr的时候使用到了变量r和c,所以r和c的声明必须写在arr的声明的前边 62 int sum2d(int,int,double arr[*][*]); //可以省略r和c,但是在arr中必须使用*来代替
zoppo是一个二维数组的名字,如何理解*((*zoppo+2)+1)等价于zoppo[2][1]?
理解*((*zoppo+2)+1):
zippo:二维数组首元素的地址(每个元素都内含2各int类型元素的一维数组)
zippo+2:二维数组第3个元素(即一维数组)的地址
*(zippo+2):二维数组第3个元素(即一维数组)的首元素(一个int类型的值)的地址,5
*(zippo+2)+1:二维数组第3个元素(即一维数组)的第二个元素(一个int类型的值)的地址,即6的地址
*(*(zippo+2)+1):二维数组第3个元素(即一维数组)的第二个元素的值(即6),等价于zippo[2][1]
四、字符串---是否可以使用str[0]='F';修改字符串str中的第一个字符?
对于以下代码:
1 //字符串 2 //1、问题的引入:对于一下C语句,是否可以使用pt[0]='F';进行修改字符串? 3 char *pt="Klingon"; 4 //2、字符串基础知识的引入: 5 #include <stdio.h> 6 #define MSG "I'm special"; 7 8 int main(){ 9 char ar[] = MSG; 10 const char* pt = MSG; 11 12 printf("address of "I'm special": %p ","I'm special"); //(1)第一次打印"I'm special"的地址 13 printf("address of ar: %p ",ar); //%p表示打印一个地址,如果报错可以替换为%u或%lu 14 printf("address of ar: %p ",pt); 15 printf("address of ar: %p ",MSG); //(2)第二次打印"I'm special"的地址 16 printf("address of "I'm special": %p ","I'm special"); //(3)第三次打印"I'm special"的地址 17 18 return 0; 19 }
1、以上代码的输出结果:
address of "I'm special": 0x100000f10 结果1
address of ar: 0x7fff5fbff858
address of pt: 0x100000f10
address of MSG: 0x100000f10 结果2
address of "I'm special": 0x100000f10 结果3
2、该输出结果说明:
1)对于数组形式初始化字符串:字符串存储在静态存储区,但是在程序开始运行才会为该数组分配内存(因为没有给ar指定大小),然后才会讲静态存储区的字符串拷贝到数组。注意:此时字符串有两个副本,一个是存储在静态内存中的字符串字面量;一个是存储在ar中的字符串。所以对于输出结果中,ar的地址和MSG的地址不一样。
2)对于指针形式初始化字符串:只是把字符串首字符的地址拷贝给了指针,所以pt的值和MSG的值是一样的。
3)对于结果1、结果2、结果3。虽然I'm special在两个printf()中出现了两次,但是地址相同,且和MSG一样,表明编译器可以把多次使用相同字符串存储在一处或多处。
3、了解了上面的信息,尤其是输出结果的第三条之后,我们就可以回到标题中的问题了,对于如下代码:
1 char *pt="Klingon"; 2 pt[0]='F'; 3 printf("Klingon "); 4 printf("%s ",pt);
输出的结果可能是:
Flingon
Flingon
如果编译器允许使用这种单词副本表示法,并允许pt[0]修改为'F',那么讲影响所有使用该字符串的代码。因此建议把指针初始化字符穿时使用const修饰符:
const char *pt="Klingon";
五、裸板程序开发
1、裸板程序开发之LED实验
基础知识:
PC启动顺序:启动BIOS--->BIOS引导进入操作系统--->识别分区--->启动应用程序;
Linux启动循序:启动BootLoader--->BootLoader引导进入操作系统--->挂接跟文件系统--->启动应用程序
BootLoader即裸板程序
LED程序:start.S:
查阅手册得到GPMCON和GPMDAT地址图片:
查阅手册得到设置GPMCON第12-15位(即GPM3)设置为0001即可将GPM3设置为输出状态
查阅手册可知将GPMDAT的第三位设置为0,即将GPM3设置为0
需要注意的是,以上查阅收测的方法,可以在pdf中搜索关键字,如GPMCON、GPMDAT等进行搜索得到。
将以上程序start.S放到Linux系统中使用arm-linux-gcc进行编译生成led.bin:
arm-linux-gcc -c -o start.o start.S #其中-c表示编译不链接、-o start.o表示输出文件为start.o,最后是依赖文件start.S
arm-linux-ld -Ttext 0 -o led.elf start.o #其中arm-linux-ld表示链接;-Ttext 0表示代码段从0开始;-o led.elf表示输出文件为led.elf;最后依赖文件为start.o
arm-linux-objcopy -o binary led.elf led.bin #将led.elf文件转换为二进制文件led.bin
arm-linux-objcopy -o led.elf > led.dis #生成反汇编文件led.dis
然后将led.bin从Linux系统中拿到windows下,打开openocd这个软件,依次执行下面的命令:
halt
nand probe 0 查看是否连接到开发板
nand erase 0 0 0x20000 其中第一个0表示擦除第0个flash,第二个0表示从地址0开始,到0x20000结束
nand write 0 led.bin 0 写第0个flash的0地址去
reset 回车即可在开发板看到程序执行结果
2、裸板程序开发之LED“流水灯”
汇编代码:start.S:
所以该程序的效果是:
(1)4个LED先全亮(将r0的初值赋值给GPMDAT寄存器,此时r0=0000);
(2)然后GPM0灭,其余全亮(r0=0001);
(3)然后GPM0、GPM1灭,其余全亮(r0=0011);
(4)然后GPM2灭,其余全亮(r0=0100);
(5)然后GPM0、GPM2灭,其余全亮(r0=0101);
(6)然后GPM1、GPM2灭,其余全亮(r0=0110);
(7)然后GPM0、GPM1、GPM2灭,其余全亮(r0=0111);
(8)然后GPM3灭,其余全亮(r0=1000);
(9)然后GPM0、GPM3灭,其余全亮(r0=1001);
(10)然后GPM0、GPM3灭,其余全亮(r0=1001);
(11)然后GPM1、GPM3灭,其余全亮(r0=1010);
(12)然后GPM0、GPM1、GPM3灭,其余全亮(r0=1011);
(13)然后GPM2、GPM3灭,其余全亮(r0=1100);
(14)然后GPM0、GPM2、GPM3灭,其余全亮(r0=1101);
(15)然后GPM1、GPM2、GPM3灭,其余全亮(r0=1110);
(16)然后GPM0、GPM1、GPM2、GPM3全灭(r0=1111),此时r0等于十进制15,然后此时r0再加1就等于16,满足cpm指令的条件,然后执行moveq指令,将r0赋值为0,重复第一步--灯全亮。
汇编相关知识:
(1)bne和bnq区别
联系:都是跳转指令;
区别:
bne例子:
cmp r1, r2 //如果r1≠r2,则执行bne指令;否则,跳过bne指令继续执行下面的指令
bne copy_loop
bnq例子:
cmp r1, r2 //如果r1=r2,则执行bne指令;否则,跳过bne指令继续执行下面的指令
bne copy_loop
3、裸板程序开发之LED"流水灯"---使用汇编和C语言实现(启动代码使用汇编实现,点灯使用C语言实现)
直接上代码:
start.S上面图片清晰版:
led.c上面图片清晰版:
但是上面的*gpmcon=0x1111这种写法不好,原因在于这一句只是设置了GPMCON寄存器的0-15位,但是GPMCON一共有20位,这一句将GPMCON寄存器其他的位全设置为0了,而十六进制的0代表了将16-20位置对应的GPM4和GPM5全设置为输入状态了,最好改为下面的写法:
*gpmcon = (*gpmcon & ~0xffff) | 0x1111可以保持GPMCON寄存器的16-20位保持不变的原因:
有关解释:
(1)应用程序和裸板程序
在应用程序中是自己直接洗恶一个main函数,然后由启动代码去调用main函数;
在裸板程序中,要自己写启动代码,然后自己在启动代码中去调用C函数;其中启动代码包括:硬件设置、设置栈、调用C函数
(2)为何要设置栈?为何sp=8*1024
因为C函数中的局部变量都是保存在栈中的,而如果栈sp指向了6410(cpu)保存代码的地址,那就有可能会将这个地址对应的内容破坏掉,所以要设置栈,将sp指向的地址设置栈的方法是让sp指向一块不用的内存就可以了
视频中使用的6410开发板,外接了Nand Flash,当设置为Nand启动的时候,程序就烧到了Nand Flash中,一上电的时候,Nand Flash中0-8K的内容,就会
被硬件原原本本的复制到6410开发板内存的0-8K地址处,对于ARM系列的CPU,一上电,CPU是从0地址开始运行的,所以6410内0-8K地址对应的内容是不能改变的,
由于我们的程序非常小,不会超过8K,所以要将sp指向地址8*1024处即可;如果我们写的程序超过了8K,要使用DDR,这个以后再说。
(3)如何理解下面的代码?
1 volatile unsigned long *gpmcon = (volatile unsigned long *)0x7F008820; 2 *gpmcon = 0x1111;
以前学习的指针的用法
1 int a; 2 int *p = &a; //p=某个地址 3 *p = 1;
和下面的代码效果是一样的:
1 int a; 2 a = 1;
那么GPMCON寄存器的地址是0x7F008820,那创建一个gpmcon指针,让这个指针等于这个地址不就好了,即:
1 int *gpmcon = 0x7F008820; 2 *gpmcon = 0x1111;
如果有警告,可以修改如下:
1 int *gpmcon = (int*)0x7F008820; //(int*)的目的是将int型的0x7F008820转换为int*类型的 2 *gpmcon = 0x1111;
加上volatile:
1 volatile int *gpmcon = (volatile int*)0x7F008820; //(int*)的目的是将int型的0x7F008820转换为int*类型的 2 *gpmcon = 0x1111;
然后把int变为unsigned long:
1 volatile unsigned long *gpmcon = (volatile unsigned long*)0x7F008820; //(int*)的目的是将int型的0x7F008820转换为int*类型的 2 *gpmcon = 0x1111;
如何要加上volatile?如下面的代码:
1 int main(){ 2 int a; 3 a = 1; 4 printf("Hello "); 5 }
执行printf("Hello ");并没有用到变量a,所以编译器会将a=1优化掉,如果不想让编译器优化,那就加上volatile
4、汇编文件中调用C语言实现的函数,并向C语言实现的函数传递四个以内的参数参数、传递多于四个的参数
(1)传递的参数在4个以内
(2)传递的参数大于4个
5、C语言文件中调用汇编文件中实现的函数、
汇编文件中实现的delay函数:
在C语言文件中调用在汇编文件中实现的delay函数
6、改变系统时钟实验(晶振原来给CPU提供的是12MHz,经过APLL后可以达到532MHz)--汇编语言实现
启动文件代码start.S(主程序):
1 .globl _start 2 _start: 3 4 /* 硬件相关的设置 */ 5 /* Peri port setup */ 6 ldr r0, =0x70000000 7 orr r0, r0, #0x13 8 mcr p15,0,r0,c15,c2,4 @ 256M(0x70000000-0x7fffffff) 9 10 /* 关看门狗 */ 11 /* 往WTCON(0x7E004000)写0 */ 12 13 ldr r0, =0x7E004000 14 mov r1, #0 15 str r1, [r0] 16 17 /* 设置时钟 */ 18 bl clock_init 19 20 /* 设置栈 */ 21 ldr sp, =8*1024 22 bl main 23 halt: 24 b halt
下面是两个子函数对应的代码:
(1)子函数一:clock_init.S汇编文件:
1 .globl clock_init 2 3 clock_init: 4 5 /* 1.设置LOCK_TIME */ 6 ldr r0, =0x7E00F000 /* APLL_LOCK */ 7 ldr r1, =0x0000FFFF 8 str r1, [r0] 9 10 str r1, [r0, #4] /* MPLL_LOCK */ 11 str r1, [r0, #8] /* EPLL_LOCK */ 12 13 #define OTHERS 0x7e00f900 14 @ set async mode /* 当CPU时钟 != HCLK时,要设为异步模式 */ 15 ldr r0, =OTHERS 16 ldr r1, [r0] 17 bic r1, #0xc0 18 str r1, [r0] 19 20 loop1: /* 等待,直到CPU进入异步模式 */ 21 ldr r0, =OTHERS 22 ldr r1, [r0] 23 and r1, #0xf00 24 cmp r1, #0 25 bne loop1 26 27 /* SYNC667 */ 28 /* MISC_CON[19] = 0 */ 29 30 #define ARM_RATIO 0 /* ARMCLK = DOUTAPLL / (ARM_RATIO + 1) */ 31 #define HCLKX2_RATIO 1 /* HCLKX2 = HCLKX2IN / (HCLKX2_RATIO + 1) */ 32 #define HCLK_RATIO 1 /* HCLK = HCLKX2 / (HCLK_RATIO + 1) */ 33 #define PCLK_RATIO 3 /* PCLK = HCLKX2 / (PCLK_RATIO + 1) */ 34 #define MPLL_RATIO 0 /* DOUTMPLL = MOUTMPLL / (MPLL_RATIO + 1) */ 35 ldr r0, =0x7E00F020 /* CLK_DIV0 */ 36 ldr r1, =(ARM_RATIO) | (MPLL_RATIO << 4) | (HCLK_RATIO << 8) | (HCLKX2_RATIO << 9) | (PCLK_RATIO << 12) 37 str r1, [r0] 38 39 /* 2.配置时钟 */ 40 /* 2.1 配置APLL */ 41 /* 2.1.1 设置APLL 42 * 2.1.2 MUXAPLL 43 * 2.1.3 SYNC667 44 * 2.1.4 DIVAPLL 45 */ 46 #define APLL_CON_VAL ((1<<31) | (266 << 16) | (3 << 8) | (1)) 47 ldr r0, =0x7E00F00C 48 ldr r1, =APLL_CON_VAL 49 str r1, [r0] /* APLL_CON, FOUTAPL = MDIV * Fin / (PDIV*2^SDIV) = 266*12/(3*2^1) = 532MHz */ 50 51 /* 2.2 配置MPLL */ 52 /* 2.2.1 设置MPLL 53 * 2.2.2 MUXMPLL 54 * 2.2.3 SYNCMUX 55 * 2.2.4 SYNC667 56 * 2.2.5 HCLKX2_RATIO 57 * 2.2.6 PCLK_RATIO 58 */ 59 #define MPLL_CON_VAL ((1<<31) | (266 << 16) | (3 << 8) | (1)) 60 ldr r0, =0x7E00F010 61 ldr r1, =MPLL_CON_VAL 62 str r1, [r0] /* MPLL_CON, FOUTMPL = MDIV * Fin / (PDIV*2^SDIV) = 266*12/(3*2^1) = 532MHz */ 63 64 /* 3.选择PLL的输出作为时钟源 */ 65 ldr r0, =0x7E00F01C 66 ldr r1, =0x03 67 str r1, [r0] 68 69 mov pc, lr
该文件的第17行需要改为下面的代码:
bic r1, r1 ,#0xc0 /*1100 0000 */
(2)子函数二:led.c
1 void delay() 2 { 3 volatile int i = 0x10000; 4 while (i--); 5 } 6 7 int main() 8 { 9 int i = 0; 10 11 volatile unsigned long *gpmcon = (volatile unsigned long *)0x7F008820; 12 volatile unsigned long *gpmdat = (volatile unsigned long *)0x7F008824; 13 14 /* gpm0,1,2,3设为输出引脚 */ 15 *gpmcon = 0x1111; 16 17 while (1) 18 { 19 *gpmdat = i; 20 i++; 21 if (i == 16) 22 i = 0; 23 delay(); 24 } 25 26 return 0; 27 }
由于和以前写的led.c是一样的,此处就不再做过多的解释。
然后将上面的三个文件拖到linux中,使用Makefile进行编译生成led.bin文件,Makefile文件内容如下:
led.bin: start.o clock.o led.o arm-linux-ld -Ttext 0 -o led.elf start.o clock.o led.o arm-linux-objcopy -O binary led.elf led.bin arm-linux-objdump -D led.elf > led.dis %.o : %.S arm-linux-gcc -o $@ $< -c %.o : %.c arm-linux-gcc -o $@ $< -c clean: rm *.o led.elf led.bin led.dis
下面加上几个寄存器的地址和对应的位的图片,从6410芯片手册上copy而来
(1)为何要设置系统时钟
(2)设置系统时钟的原理图
(3)APLL、MPLL和EPLL是啥?
(4)设置系统时钟的原理图中的OM[0]=0是通过硬件来实现的,如下图:
(5)设置CPU为异步模式的OTHERS寄存器地址和各自位表示的含义
(6)APLL_CON、MPLL_CON、EPLL_CON寄存器的地址图
(7)APLL_CON、MPLL_CON各自的位的含义
7、改变系统时钟实验(晶振原来给CPU提供的是12MHz,经过APLL后可以达到500MHz)---使用C语言实现
启动文件代码start.S(主程序):
1 .globl _start 2 _start: 3 4 /* 硬件相关的设置 */ 5 /* Peri port setup */ 6 ldr r0, =0x70000000 7 orr r0, r0, #0x13 8 mcr p15,0,r0,c15,c2,4 @ 256M(0x70000000-0x7fffffff) 9 10 /* 关看门狗 */ 11 /* 往WTCON(0x7E004000)写0 */ 12 13 ldr r0, =0x7E004000 14 mov r1, #0 15 str r1, [r0] 16 17 /* 设置栈 */ 18 ldr sp, =8*1024 19 20 /* 设置时钟 */ 21 bl clock_init 22 23 bl main 24 halt: 25 b halt
下面是两个子函数对应的代码:
(1)子函数一:clock_init.c文件:
1 #define APLL_LOCK (*((volatile unsigned long *)0x7E00F000)) 2 #define MPLL_LOCK (*((volatile unsigned long *)0x7E00F004)) 3 #define EPLL_LOCK (*((volatile unsigned long *)0x7E00F008)) 4 5 #define OTHERS (*((volatile unsigned long *)0x7e00f900)) 6 7 #define CLK_DIV0 (*((volatile unsigned long *)0x7E00F020)) 8 9 #define ARM_RATIO 0 /* ARMCLK = DOUTAPLL / (ARM_RATIO + 1) */ 10 #define HCLKX2_RATIO 4 /* HCLKX2 = HCLKX2IN / (HCLKX2_RATIO + 1) = 100MHz */ 11 #define HCLK_RATIO 0 /* HCLK = HCLKX2 / (HCLK_RATIO + 1) = 100MHz */ 12 #define PCLK_RATIO 1 /* PCLK = HCLKX2 / (PCLK_RATIO + 1) = 50MHz */ 13 #define MPLL_RATIO 0 /* DOUTMPLL = MOUTMPLL / (MPLL_RATIO + 1) */ 14 15 16 #define APLL_CON (*((volatile unsigned long *)0x7E00F00C)) 17 #define APLL_CON_VAL ((1<<31) | (250 << 16) | (3 << 8) | (1)) 18 19 #define MPLL_CON (*((volatile unsigned long *)0x7E00F010)) 20 #define MPLL_CON_VAL ((1<<31) | (250 << 16) | (3 << 8) | (1)) 21 22 #define CLK_SRC (*((volatile unsigned long *)0x7E00F01C)) 23 24 void clock_init(void) 25 { 26 APLL_LOCK = 0xffff; 27 MPLL_LOCK = 0xffff; 28 EPLL_LOCK = 0xffff; 29 30 /* set async mode 当CPU时钟 != HCLK时,要设为异步模式 */ 31 OTHERS &= ~0xc0; 32 while ((OTHERS & 0xf00) != 0); 33 34 CLK_DIV0 = (ARM_RATIO) | (MPLL_RATIO << 4) | (HCLK_RATIO << 8) | (HCLKX2_RATIO << 9) | (PCLK_RATIO << 12); 35 36 APLL_CON = APLL_CON_VAL; /* 500MHz */ 37 MPLL_CON = MPLL_CON_VAL; /* 500MHz */ 38 39 CLK_SRC = 0x03; 40 }
(2)子函数二:led.c文件:
1 void delay() 2 { 3 volatile int i = 0x10000; 4 while (i--); 5 } 6 7 int main() 8 { 9 int i = 0; 10 11 volatile unsigned long *gpmcon = (volatile unsigned long *)0x7F008820; 12 volatile unsigned long *gpmdat = (volatile unsigned long *)0x7F008824; 13 14 /* gpm0,1,2,3设为输出引脚 */ 15 *gpmcon = 0x1111; 16 17 while (1) 18 { 19 *gpmdat = i; 20 i++; 21 if (i == 16) 22 i = 0; 23 delay(); 24 } 25 26 return 0; 27 }
和以前的代码一样了
Makefile文件
1 led.bin: start.o clock.o led.o 2 arm-linux-ld -Ttext 0 -o led.elf start.o clock.o led.o 3 arm-linux-objcopy -O binary led.elf led.bin 4 arm-linux-objdump -D led.elf > led.dis 5 6 %.o : %.S 7 arm-linux-gcc -o $@ $< -c 8 9 %.o : %.c 10 arm-linux-gcc -o $@ $< -c 11 12 clean: 13 rm *.o led.elf led.bin led.dis
和上一个是一样的
8、串口实验
1、串口实验的原理
2、6410芯片中串口的基本介绍及基本原理
3、启动文件(主函数):start.S
1 .globl _start 2 _start: 3 4 /* 硬件相关的设置 */ 5 /* Peri port setup */ 6 ldr r0, =0x70000000 7 orr r0, r0, #0x13 8 mcr p15,0,r0,c15,c2,4 @ 256M(0x70000000-0x7fffffff) 9 10 /* 关看门狗 */ 11 /* 往WTCON(0x7E004000)写0 */ 12 13 ldr r0, =0x7E004000 14 mov r1, #0 15 str r1, [r0] 16 17 /* 设置栈 */ 18 ldr sp, =8*1024 19 20 /* 设置时钟 */ 21 bl clock_init 22 23 bl main 24 halt: 25 b halt
4、main.c中的main()函数 :在main.c文件中
1 #include "uart.h" 2 3 int main() 4 { 5 char c; 6 7 init_uart(); 8 9 while (1) 10 { 11 c = getchar(); 12 putchar(c+1); 13 } 14 15 return 0; 16 }
5、子函数:clock.c中的clock_init()函数
1 void putchar(char c); 2 char getchar(void); 3 void init_uart(void);
1 #define ULCON0 (*((volatile unsigned long *)0x7F005000)) 2 #define UCON0 (*((volatile unsigned long *)0x7F005004)) 3 #define UFCON0 (*((volatile unsigned long *)0x7F005008)) 4 #define UMCON0 (*((volatile unsigned long *)0x7F00500C)) 5 #define UTRSTAT0 (*((volatile unsigned long *)0x7F005010)) 6 #define UFSTAT0 (*((volatile unsigned long *)0x7F005018)) 7 #define UTXH0 (*((volatile unsigned char *)0x7F005020)) 8 #define URXH0 (*((volatile unsigned char *)0x7F005024)) 9 #define UBRDIV0 (*((volatile unsigned short *)0x7F005028)) 10 #define UDIVSLOT0 (*((volatile unsigned short *)0x7F00502C)) 11 12 #define GPACON (*((volatile unsigned short *)0x7F008000)) 13 14 15 void init_uart(void) 16 { 17 GPACON &= ~0xff; 18 GPACON |= 0x22; 19 20 /* ULCON0 */ 21 ULCON0 = 0x3; /* 数据位:8, 无较验, 停止位: 1, 8n1 */ 22 UCON0 = 0x5; /* 使能UART发送、接收 */ 23 UFCON0 = 0x01; /* FIFO ENABLE */ 24 25 UMCON0 = 0; 26 27 /* 波特率 */ 28 /* DIV_VAL = (PCLK / (bps x 16 ) ) - 1 29 * bps = 57600 30 * DIV_VAL = (66500000 / (115200 x 16 ) ) - 1 31 * = 35.08 32 */ 33 UBRDIV0 = 35; 34 35 /* x/16 = 0.08 36 * x = 1 37 */ 38 UDIVSLOT0 = 0x1; 39 40 } 41 42 char getchar(void) 43 { 44 while ((UFSTAT0 & (1<<6)) == 0 && (UFSTAT0 & 0x3f) == 0); 45 return URXH0; 46 } 47 48 void putchar(char c) 49 { 50 while ((UFSTAT0 & (1<<14)) == 1); 51 UTXH0 = c; 52 }
上面的uart.c中存在bug!!!:将上面的第13行改为:
#define GPACON (*((volatile unsigned long *)0x7F008000))
第51行改为:
while (UFSTAT0 & (1<<14));
没有bug的代码如下:
1 #define ULCON0 (*((volatile unsigned long *)0x7F005000)) 2 #define UCON0 (*((volatile unsigned long *)0x7F005004)) 3 #define UFCON0 (*((volatile unsigned long *)0x7F005008)) 4 #define UMCON0 (*((volatile unsigned long *)0x7F00500C)) 5 #define UTRSTAT0 (*((volatile unsigned long *)0x7F005010)) 6 #define UFSTAT0 (*((volatile unsigned long *)0x7F005018)) 7 #define UTXH0 (*((volatile unsigned char *)0x7F005020)) 8 #define URXH0 (*((volatile unsigned char *)0x7F005024)) 9 #define UBRDIV0 (*((volatile unsigned short *)0x7F005028)) 10 #define UDIVSLOT0 (*((volatile unsigned short *)0x7F00502C)) 11 12 #define GPACON (*((volatile unsigned long *)0x7F008000)) 13 14 15 void init_uart(void) 16 { 17 GPACON &= ~0xff; 18 GPACON |= 0x22; 19 20 /* ULCON0 */ 21 ULCON0 = 0x3; /* 数据位:8, 无较验, 停止位: 1, 8n1 */ 22 UCON0 = 0x5; /* 使能UART发送、接收 */ 23 UFCON0 = 0x01; /* FIFO ENABLE */ 24 25 UMCON0 = 0; 26 27 /* 波特率 */ 28 /* DIV_VAL = (PCLK / (bps x 16 ) ) - 1 29 * bps = 57600 30 * DIV_VAL = (66500000 / (115200 x 16 ) ) - 1 31 * = 35.08 32 */ 33 UBRDIV0 = 35; 34 35 /* x/16 = 0.08 36 * x = 1 37 */ 38 UDIVSLOT0 = 0x1; 39 40 } 41 42 char getchar(void) 43 { 44 while ((UFSTAT0 & (1<<6)) == 0 && (UFSTAT0 & 0x3f) == 0); 45 return URXH0; 46 } 47 48 void putchar(char c) 49 { 50 while (UFSTAT0 & (1<<14)); 51 UTXH0 = c; 52 }
6、clock_init()函数中用到的寄存器介绍
6.1首先查看串口0的Tx0和Rx0接在了那个GPIO引脚,找到了然后配制该引脚为串口输入/输出模式
然后在手册中搜索GPACON,对GPACON寄存器进行配制
6.2 串口行控制寄存器ULCON0
uart.c文件中的配置方法
根据手册做上面的配制,手册上的信息:
6.3串口控制寄存器---UCON0寄存器
手册上的配置方法信息:
6.4串口缓冲区使能寄存器UFCON0
6.5串口流量控制寄存器配置
6.6 波特率设置寄存器 UBRDIV0寄存器
6.5 如何写发送和接受串口数据的函数?使用串口状态寄存器---UFSTAT0
1 uart.bin: start.o main.o uart.o clock.o 2 arm-linux-ld -Ttext 0 -o uart.elf $^ 3 arm-linux-objcopy -O binary uart.elf uart.bin 4 arm-linux-objdump -D uart.elf > uart.dis 5 6 %.o : %.S 7 arm-linux-gcc -o $@ $< -c 8 9 %.o : %.c 10 arm-linux-gcc -o $@ $< -c 11 12 clean: 13 rm *.o uart.elf uart.bin uart.dis
和不带.h文件的Makefile文件书写方法是一样的
9、链接地址和uboot重定位
9.1基础知识
/* 数据手册上会有说明,位置不是我们想往哪里下载就往哪里下载的,下载错了启动的时候就找不到代码的位置在哪,一般是下载到如下存储设备中:1、内部自带的FLASH, 2、外扩NAND FLASH(EMMC), 3、外扩NORFLASH, 4、SD卡,(存储地址) 1、大部分单片机(stm32)是下载到内部自带FLASH中,起始地址是严格指定的(如0x08000000) 2、大部分运行安卓或者linux的ARM一般是下载到SD卡或者外扩的FLASH(EMMC)中,然而在SD卡或者EMMC中的起始位置是严格规定的(比如第0块开始存放,或者跳过一块,从第1块位置开始存放) */
上图中是不包括FLASH的,flash是由ROM发展而来(ROM的缺点是只可以写一次,也有可擦除的如EEPROM),flash是一种非易失闪存技术,用来存储少量的代码,它的优点是可擦除和可再编程,任何写flash的操作都必须先对flash进行擦除,,可以分为NOR FLASH 和NAND FLASH,他们是由不同的公司生产而来。NOR FALSH的读取和我们常见的SDRAM是一样的,因此NOR FLASH的成本更低;NAND FLASH 比较廉价
EEPROM和FLASH的区别:EEPROM全盘擦除、FALSH可以指定为孩子擦除。
DDR DRAM是DRAM的一种。
SSD(固态硬盘)是由控制单元、存储单元(FLASH芯片、DRAM)组成。SSD如下图:
机械硬盘:盘片、磁头、盘片转轴、控制电机组成。---由此可见非常大了,都有电机了。。。机械硬盘如下图:
RAM可以分为SRAM和DRAM,SRAM速度更高、价格更高。可应用在内存中,直接与CPU交互;
ROM由于只可以编程一次,因此可以用来存储系统BIOS等,近年来FLASH逐渐取代了ROM;
问题2:从哪个位置去加载代码,加载到哪里去运行?
/* 1、以上存储设备分两种,一种是可以直接运行代码的存储设备(内部自带FLASH、外扩NORFLASH),一种是不能直接寻址运行代码的设备(SD卡,NANDFLASH、EMMC) 2、一般芯片都有启动引脚,我们需要根据我们下载代码的位置来正确配置启动引脚的高低电平,这样上电才能正确启动代码 3、对于可以直接运行代码的存储设备,前期不需要加载代码,而是直接从该设备的起始位置运行代码,至于后期是否需要加载代码到其它位置运行就看程序员的期望运行地址(链接脚本里面编写的链接地址),如果没啥期望(用编译软件默认的链接地址,而编译软件默认的链接地址和实际运行地址都是一样的)那就不需要做任何拷贝代码动作,代码从头跑到尾(stm32等单片机之类的芯片就是这种情况),如果程序员希望一部分代码在其他位置运行,就需要修改后期代码的链接地址而不是一直系统默认地址,在前期代码中把后期代码拷贝到理想位置,然后长跳转到理想位置去运行(此情况如下:我们有时候觉得FLASH速度太慢,需要把代码拷贝到RAM中运行)(代码重定位) 4、对于不可以直接运行代码的存储设备,前期芯片内部的固件会把一部分代码加载到默认的内存位置(内部RAM)运行,为什么是一部分呢,因为此情况一般是代码很大(比如跑linux系统)内部RAM资源有限,所以只能加载一部分代码到内部RAM,另外一部分大多数代码就需要加载到外扩的RAM中运行,所以程序员自己编写链接脚本(链接地址)希望把后面的一部分代码能加载到他期望的地址去运行,并且程序员编写前期运行的代码(位置无关码)中包含拷贝函数,拷贝函数把后一部分代码拷贝到理想位置(代码重定位),在运行完前期代码后实现长跳转,跳转到理想的链接位置去运行 总结: 从哪里加载代码根据启动引脚指定,加载到哪里运行代码分前期和后期,前期运行基本都是加载到默认地方运行,后期代码根据自己需求位置来加载运行(大部分单片机还是系统默认,大部分跑大型系统的ARM后期需要进行代码重定位) */
问题3:如何理解存储地址?
/* 就是代码下载到存储器(FLASH)的位置 */
问题4:如何理解链接地址?
/* 这个地址就是链接脚本里面指定的代码的运行位置(程序员期望的运行地址) 比如我们希望代码将来加载到我们外扩的内存中运行(而不是一直在刚上电系统指定的位置运行) */
问题5:如何理解运行地址?
/* 就是当前程序正在运行的地址(PC) 理论来讲我们可以把代码加载到内存的任何位置然后跳转到相应位置运行,至于会不会出问题,就看我们的链接地址是否和当前运行地址是否一致并且运行的是位置有关码还是位置无关码,如果地址不一致且是位置有关码可能会出现错误(看后面的位置有关码和无关码的解释) */
大部分哈弗结构单片机(包括stm32)是把不需要另外加载代码到RAM中运行的,上电是直接从我们下载代码的位置(FLASH)运行
一般单片机对于代码来讲:存储地址 = 链接地址 = 运行地址
问题6:如何理解位置有关码和位置无关码?
/* 机器代码里面包含了变量和函数的地址,到底是根据绝对链接地址取值或者是跳转还是根据PC当前位置加偏移地址取值或者跳转分为位置有关码和位置无关码: 1、按我的理解就是,位置无关码就是这条代码和绝对链接地址无关,跳转或者取值所需地址都是当前PC+偏移地址,这样总是能找到正确位置,所以里面的地址是相对地址,也可以理解为动态地址,运行地址改变那这个地址也改变,所谓你变我也变 2、而位置有关码里面是绝对链接地址,每次根据链接地址去取值,如果变量或者是函数正好在链接地址处存放,那么可以正确找到位置实现跳转或者是取值,如果变量或者函数被加载到ram的位置和我们链接的地址位置不一样,那么去链接地址取值或者跳转肯定会出现错误 */
问题7:什么是重定位?
/* 简述:我们希望后期代码在我们指定的位置运行(比如为了加快代码运行速度,把代码从FLASH上面加载到外部RAM中运行),我们会在前期代码中用位置无关码拷贝代码到链接地址处,然后长跳转到链接地址处运行,这样一个过程就是代码重定位 问题1:为什么一开始不直接把代码加载到RAM运行呢? 回答:一般我们外扩的RAM是需要通过代码初始化才能正常工作的,所以前期是不能直接加载到RAM中运行的 问题2:stm32可不可以偏要把代码放到内部RAM运行呢? 回答:肯定是可以的,但是要设置好链接地址让编译器按你的链接地址编译,并且在前期编写拷贝函数把代码拷贝到链接地址(RAM)中运行 */
9.2视频中链接地址的讲解
1、为什么要使用链接地址和DDR?
2、为何要重定位?(从下一节的代码进行理解)
3、6410片内内存不够用了,此时使用了DDR,那么在DDR中从哪里开始存放程序呢?此时要使用链接地址为DDR指定存放代码的地址
9.3 链接地址举例(在6410内部进行重定位)
结论:
1、运行时地址:就在从运行时地址起始位置(包括起始位置)往后排都是运行时地址。
2、链接地址:就是从链接地址起始位置(包括起始位置)往后排都是链接地址。
3、对于pc而言,当你直接读取pc的值时访问的是运行时地址,而当你读取[pc]的值时访问的是链接地址。
4、adr r0, _start ;加载的是运行时地址; ldr r1, =_start加载的是链接地址。
整个过程是这样的:
1、程序在运行之前先编译链接,链接完了之后,每句程序都对应一个链接地址(就像你反汇编看到的那样,链接地址的起始地址是程序员在.lds文件中设定的,并以此链接地址为起始,每个程序对应的链接地址依次向下递增)。而链接地址的起始地址其实位置往往是DDR的起始位置。
2、但是一开始程序是在SRAM上运行的,运行地址的起始位置往往就是SRAM的起始位置。那么一开始运行的代码它的
链接地址和运行地址是不同的,但是代码本身不知道这件事,而程序员明白,所以这段代码只能是位置无关码,主要负责一些必要的初始化和搬运(重定位)。直到
搬运完成,pc的值还是运行时地址的值。也就是说pc不等于[pc].
3、通过绝对跳转修改PC的值为当前链接地址的值:
ldr pc, =on_sdram @ 跳到SDRAM中继续执行,及让pc = [pc]
如果用相对跳转就是当前运行时地址加上一个偏移量,而在那个地方可能并没有内存和程序。
如: bl on_sdram @通过PC + 偏移量完成,此时PC运行是运行时地址。这样就无法到DDR上运行程序了。
4、此后,可以认为运行时地址和链接地址相等(pc = [pc]),或者说不需要运行时地址和链接地址这两个概念了。除非你还想重定位程序。
以上参考博客:https://www.cnblogs.com/douzi2/p/4970632.html?utm_source=tuicool
在6410内部进行重定位的代码:
1 .globl _start 2 _start: 3 4 /* 硬件相关的设置 */ 5 /* Peri port setup */ 6 ldr r0, =0x70000000 7 orr r0, r0, #0x13 8 mcr p15,0,r0,c15,c2,4 @ 256M(0x70000000-0x7fffffff) 9 10 /* 关看门狗 */ 11 /* 往WTCON(0x7E004000)写0 */ 12 13 ldr r0, =0x7E004000 14 mov r1, #0 15 str r1, [r0] 16 17 /* 重定位 */ 18 adr r0, _start /* 伪指令,取_start的当前地址 */ 19 ldr r1, =_start /* 链接地址 */ 20 ldr r2, =bss_start 21 cmp r0, r1 22 beq clean_bss 23 copy_loop: 24 ldr r3, [r0], #4 25 str r3, [r1], #4 26 cmp r1, r2 27 bne copy_loop 28 29 /* 清bss段 */ 30 clean_bss: 31 ldr r0, =bss_start 32 ldr r1, =bss_end 33 mov r2, #0 34 clean_loop: 35 str r2, [r0], #4 36 cmp r0, r1 37 bne clean_loop 38 39 /* 设置栈 */ 40 ldr sp, =8*1024 41 ldr pc, =main 42 bl main 43 halt: 44 b halt
1 void delay() 2 { 3 volatile int i = 0x10000; 4 while (i--); 5 } 6 7 volatile int i = 0; 8 9 volatile int j = 0x12345678; 10 volatile int k = 0; 11 volatile int g; 12 13 int main() 14 { 15 16 volatile unsigned long *gpmcon = (volatile unsigned long *)0x7F008820; 17 volatile unsigned long *gpmdat = (volatile unsigned long *)0x7F008824; 18 19 /* gpm0,1,2,3设为输出引脚 */ 20 *gpmcon = 0x1111; 21 if (k != 0) 22 return; 23 while (1) 24 { 25 *gpmdat = i; 26 i++; 27 if (i == 16) 28 i = 0; 29 delay(); 30 } 31 32 return 0; 33 }
1 SECTIONS 2 { 3 . = 0x1000; 4 .text : { 5 start.o 6 * (.text) 7 } 8 9 .data : { 10 * (.data) 11 } 12 13 bss_start = .; /* 0x1150 */ 14 .bss : { 15 * (.bss) 16 } 17 bss_end = .; 18 }
led.bin: start.o led.o arm-linux-ld -T leds.lds -o led.elf start.o led.o arm-linux-objcopy -O binary led.elf led.bin arm-linux-objdump -D led.elf > led.dis start.o : start.S arm-linux-gcc -o start.o start.S -c led.o : led.c arm-linux-gcc -o led.o led.c -c clean: rm *.o led.elf led.bin led.dis -f
附加一些图片
1、查看反汇编文件的时候,pc的值等于多少的方法:
2、在start.S文件中的20行,ldr r1,=_start中为啥r1=0x1000?如下图的反汇编文件:
3、在start.S文件中的19行,adr r0, _start指令中为啥r0的值是0x0000?
4、str指令的用法
10、6410片外内存DDR初始化和重定位到DDR内的地址运行
初始化用的代码:
1 .globl _start 2 _start: 3 /* 硬件相关的设置 */ 4 /* Peri port setup */ 5 ldr r0, =0x70000000 6 orr r0, r0, #0x13 7 mcr p15,0,r0,c15,c2,4 @ 256M(0x70000000-0x7fffffff) 8 9 /* 关看门狗 */ 10 /* 往WTCON(0x7E004000)写0 */ 11 12 ldr r0, =0x7E004000 /* 伪指令 */ 13 mov r1, #0 14 str r1, [r0] 15 16 bl clock_init 17 18 /* 为调用C函数准备环境 */ 19 ldr sp, =8*1024 20 bl sdram_init /* 在sdram_init()函数中不可以使用全局变量、静态变量 */ 21 22 /* 重定位代码 */ 23 /* 把程序的代码段、数据段复制到它的链接地址去 */ 24 adr r0, _start /* 获得_start指令当前所在的地址 : 0*/ 25 ldr r1, =_start /* _start的链接地址 0x50000000 */ 26 27 ldr r2, =bss_start /* bss段的起始链接地址 */ 28 29 cmp r0,r1 30 beq clean_bss 31 32 copy_loop: 33 ldr r3, [r0], #4 34 str r3, [r1], #4 35 cmp r1, r2 36 bne copy_loop 37 38 39 /* 把BSS段对应的内存清零 */ 40 clean_bss: 41 ldr r0, =bss_start 42 ldr r1, =bss_end 43 mov r3, #0 44 cmp r0, r1 45 beq on_ddr 46 clean_loop: 47 str r3, [r0], #4 48 cmp r0, r1 49 bne clean_loop 50 51 on_ddr: 52 /* 调用C函数 */ 53 ldr pc, =main /* pc等于main的链接地址 */
1 #ifndef __COMMON_H 2 #define __COMMON_H 3 4 #define vi *( volatile unsigned int * ) 5 6 #define set_zero( addr, bit ) ( (vi addr) &= ( ~ ( 1 << (bit) ) ) ) 7 #define set_one( addr, bit ) ( (vi addr) |= ( 1 << ( bit ) ) ) 8 9 #define set_bit( addr, bit, val ) ( (vi addr) = (( vi addr)&=(~(1<<(bit))) ) | ( (val)<<(bit) ) ) 10 11 #define set_2bit( addr, bit, val ) ( (vi addr) = (( vi addr)&(~(3<<(bit))) ) | ( (val)<<(bit) ) ) 12 13 #define set_nbit( addr, bit, len, val ) 14 ( (vi addr) = ((( vi addr)&(~(( ((1<<(len))-1) )<<(bit)))) | ( (val)<<(bit) ) )) 15 16 #define get_bit( addr, bit ) ( (( vi addr ) & ( 1 << (bit) )) > 0 ) 17 18 #define get_val( addr, val ) ( (val) = vi addr ) 19 #define read_val( addr ) ( vi ( addr ) ) 20 #define set_val( addr, val ) ( (vi addr) = (val) ) 21 #define or_val( addr, val ) ( (vi addr) |= (val) ) 22 23 /////////////////////////////// 24 25 typedef unsigned char u8; 26 typedef unsigned short u16; 27 typedef unsigned int u32; 28 29 // function declare 30 31 int delay( int ); 32 33 #endif /* __COMMON_H */
1 #include "common.h" 2 3 #define MEMCCMD 0x7e001004 4 #define P1REFRESH 0x7e001010 5 #define P1CASLAT 0x7e001014 6 #define MEM_SYS_CFG 0x7e00f120 7 #define P1MEMCFG 0x7e00100c 8 #define P1T_DQSS 0x7e001018 9 #define P1T_MRD 0x7e00101c 10 #define P1T_RAS 0x7e001020 11 #define P1T_RC 0x7e001024 12 #define P1T_RCD 0x7e001028 13 #define P1T_RFC 0x7e00102c 14 #define P1T_RP 0x7e001030 15 #define P1T_RRD 0x7e001034 16 #define P1T_WR 0x7e001038 17 #define P1T_WTR 0x7e00103c 18 #define P1T_XP 0x7e001040 19 #define P1T_XSR 0x7e001044 20 #define P1T_ESR 0x7e001048 21 #define P1MEMCFG2 0X7e00104c 22 #define P1_chip_0_cfg 0x7e001200 23 24 #define P1MEMSTAT 0x7e001000 25 #define P1MEMCCMD 0x7e001004 26 #define P1DIRECTCMD 0x7e001008 27 28 29 #define HCLK 133000000 30 31 #define nstoclk(ns) (ns/( 1000000000/HCLK)+1) 32 33 int sdram_init( void ) 34 { 35 // tell dramc to configure 36 set_val( MEMCCMD, 0x4 ); 37 38 // set refresh period 39 set_val( P1REFRESH, nstoclk(7800) ); 40 41 // set timing para 42 set_val( P1CASLAT, ( 3 << 1 ) ); 43 set_val( P1T_DQSS, 0x1 ); // 0.75 - 1.25 44 set_val( P1T_MRD, 0x2 ); 45 set_val( P1T_RAS, nstoclk(45) ); 46 set_val( P1T_RC, nstoclk(68) ); 47 48 u32 trcd = nstoclk( 23 ); 49 set_val( P1T_RCD, trcd | (( trcd - 3 ) << 3 ) ); 50 u32 trfc = nstoclk( 80 ); 51 set_val( P1T_RFC, trfc | ( ( trfc-3 ) << 5 ) ); 52 u32 trp = nstoclk( 23 ); 53 set_val( P1T_RP, trp | ( ( trp - 3 ) << 3 ) ); 54 set_val( P1T_RRD, nstoclk(15) ); 55 set_val( P1T_WR, nstoclk(15) ); 56 set_val( P1T_WTR, 0x7 ); 57 set_val( P1T_XP, 0x2 ); 58 set_val( P1T_XSR, nstoclk(120) ); 59 set_val( P1T_ESR, nstoclk(120) ); 60 61 // set mem cfg 62 set_nbit( P1MEMCFG, 0, 3, 0x2 ); /* 10 column address */ 63 64 /* set_nbit: 把从第bit位开始的一共len位消零,然后把这几位设为val */ 65 66 set_nbit( P1MEMCFG, 3, 3, 0x2 ); /* 13 row address */ 67 set_zero( P1MEMCFG, 6 ); /* A10/AP */ 68 set_nbit( P1MEMCFG, 15, 3, 0x2 ); /* Burst 4 */ 69 70 set_nbit( P1MEMCFG2, 0, 4, 0x5 ); 71 set_2bit( P1MEMCFG2, 6, 0x1 ); /* 32 bit */ 72 set_nbit( P1MEMCFG2, 8, 3, 0x3 ); /* Mobile DDR SDRAM */ 73 set_2bit( P1MEMCFG2, 11, 0x1 ); 74 75 set_one( P1_chip_0_cfg, 16 ); /* Bank-Row-Column organization */ 76 77 // memory init 78 set_val( P1DIRECTCMD, 0xc0000 ); // NOP 79 set_val( P1DIRECTCMD, 0x000 ); // precharge 80 set_val( P1DIRECTCMD, 0x40000 );// auto refresh 81 set_val( P1DIRECTCMD, 0x40000 );// auto refresh 82 set_val( P1DIRECTCMD, 0xa0000 ); // EMRS 83 set_val( P1DIRECTCMD, 0x80032 ); // MRS 84 85 set_val( MEM_SYS_CFG, 0x0 ); 86 87 // set dramc to "go" status 88 set_val( P1MEMCCMD, 0x000 ); 89 90 // wait ready 91 while( !(( read_val( P1MEMSTAT ) & 0x3 ) == 0x1)); 92 }
1 void delay(volatile int count) 2 { 3 volatile int i = count; 4 while(i) 5 { 6 i--; 7 } 8 } 9 10 //int i = 0xf; /* 位于数据段 */ 11 int i = 0; /* 位于BSS段 */ 12 volatile const int j = 0x12345678; /* 位于只读数据段 */ 13 //volatile int k=0; /* bss段 */ 14 15 int main() 16 { 17 /* 配置GPMCON让GPM0,1,2,3作为输出引脚 */ 18 volatile unsigned long *gpmcon = (volatile unsigned long *)0x7F008820; 19 volatile unsigned long *gpmdat = (volatile unsigned long *)0x7F008824; 20 21 *gpmcon &= ~(0xffff); 22 *gpmcon |= 0x1111; 23 24 /* 循环点亮这4个LED */ 25 while (1) 26 { 27 *gpmdat &= ~0xf; 28 *gpmdat |= i; 29 i++; 30 delay(20000); 31 if (i == 16) 32 i = 0; 33 } 34 35 return 0; 36 }
1 #define APLL_LOCK 0x7e00f000 2 #define MPLL_LOCK 0x7e00f004 3 #define EPLL_LOCK 0x7e00f008 4 #define LOCK_TIME 0xffff 5 6 #define OTHERS 0x7e00f900 7 8 #define CLK_DIV0 0x7e00f020 9 10 #define CLK_SRC 0x7e00f01c 11 12 .text 13 .global clock_init 14 clock_init: 15 16 @ set the lock time to max 17 ldr r0, =LOCK_TIME 18 ldr r1, =APLL_LOCK 19 str r0, [r1] 20 ldr r1, =MPLL_LOCK 21 str r0, [r1] 22 ldr r1, =EPLL_LOCK 23 str r0, [r1] 24 25 @ set async mode 26 ldr r0, =OTHERS 27 ldr r1, [r0] 28 bic r1, #0xc0 29 str r1, [r0] 30 31 loop1: 32 ldr r0, =OTHERS 33 ldr r1, [r0] 34 and r1, #0xf00 35 cmp r1, #0 36 bne loop1 37 38 @ set the divider 39 40 #define DIV_VAL ( (0)|(1<<4)|(1<<8)|(1<<9)|(3<<12) ) 41 ldr r0, =CLK_DIV0 42 ldr r1, =DIV_VAL 43 str r1, [r0] 44 45 @ set APLL, MPLL, EPLL 46 #define SDIV 1 47 #define PDIV 3 48 #define MDIV 266 49 #define PLL_ENABLE ( 1 << 31 ) 50 #define APLL_VAL ( (SDIV<<0)|(PDIV<<8)|(MDIV<<16)|(PLL_ENABLE) ) 51 #define MPLL_VAL APLL_VAL 52 #define EPLL0_VAL ( (2<<0)|(1<<8)|(32<<16)|PLL_ENABLE) 53 #define EPLL1_VAL ( 0 ) 54 55 #define APLL_CON 0x7e00f00c 56 #define MPLL_CON 0x7e00f010 57 #define EPLL_CON0 0x7e00f014 58 #define EPLL_CON1 0x7e00f018 59 60 ldr r0, =APLL_CON 61 ldr r1, =APLL_VAL 62 str r1, [r0] 63 64 ldr r0, =MPLL_CON 65 ldr r1, =MPLL_VAL 66 str r1, [r0] 67 68 ldr r0, =EPLL_CON0 69 ldr r1, =EPLL0_VAL 70 str r1, [r0] 71 72 ldr r0, =EPLL_CON1 73 ldr r1, =EPLL1_VAL 74 str r1, [r0] 75 76 @ select the source 77 ldr r0, =CLK_SRC 78 mov r1, #7 79 str r1, [r0] 80 81 mov pc, lr 82
1 SECTIONS 2 { 3 . = 0x50000000; 4 .text : 5 { 6 start.o 7 * (.text) 8 } 9 10 . = ALIGN(4); 11 .rodata : 12 { 13 * (.rodata) 14 } 15 16 . = ALIGN(4); 17 .data : 18 { 19 * (.data) 20 } 21 22 . = ALIGN(4); 23 24 bss_start = . ; /* 0x50000450 */ 25 26 .bss : 27 { 28 * (.bss) /* i */ 29 * (.common) 30 } 31 32 bss_end = . ; /* 0x50000450 */ 33 }
1 led.bin : start.o clock.o sdram.o led.o 2 arm-linux-ld -T led.lds -o led.elf $^ 3 arm-linux-objcopy -O binary led.elf led.bin 4 arm-linux-objdump -D led.elf > led.dis 5 6 %.o : %.S 7 arm-linux-gcc -g -c -O2 -o $@ $^ 8 9 %.o : %.c 10 arm-linux-gcc -g -c -O2 -o $@ $^ 11 12 clean: 13 rm -f *.o *.bin *.elf *.dis
1、链接文件中的链接地址确定方法
2、DDR初始化文件sdram.c中的解析:
43-47行的解析:
3、启动文件start.S和led.lds文件的解析
11、NandFlash实验---如果生成的.bin文件超过了6410片内内存(8K)的大小,那么此时可以直接从NandFlash芯片中将程序拷贝到DDR中
(1)NandFlash连接原理图
(2)NandFlash芯片手册上关于读的时序图
(3)NandFlash内部结构原理图
(4)从NandFlash将程序拷贝到DDR的启动文件程序讲解
(5)NandFlash初始化函数nand_int函数的讲解_涉及到如何看时序图来确定一些时间参数以及对应的时序图的讲解