欢迎光临散文网 会员登陆 & 注册

8.2第1个ARM裸板程序及引申(下)

2020-08-16 18:32 作者:韦东山  | 我要投稿

第006节_编程知识_字节序_位操作

字节序:

假设int a = 0x12345678;

前面说了16进制每位是4个bit,在内存中,是以8个bit作为1byte进行存储的,因此0x12345678中每两位作为1byte,其中0x78是低位,0x12是高位。

在内存中的存储方式有两种:

0x12345678的低位(0x78)存在低地址,即方式1,叫做小字节序(Little endian);

0x12345678的高位(0x12)存在低地址,即方式2,叫做大字节序(Big endian);

一般的arm芯片都是小字节序,对于2440可以设置某个寄存器,让整个系统使用大字节序或小字节序,它默认使用小字节序。


位操作:

1. 移位

左移:

int a = 0x123;  int b = a<<2;--> b=0x48C

右移:

int a = 0x123;  int b = a>>2;--> b=0x48

左移是乘4,右移是除4;


2. 取反 原来问0的位变1,原来为1的位变0;

int a = 0x123; int b = ~a;a=2


3. 位与

1 & 1 = 1

1 & 0 = 0 

0 & 1 = 0 

0 & 0 = 0

int a = 0x123; int b = 0x456; int c = a&b;--> c=0x2


4. 位或

1 | 1 = 1

1 | 0 = 1

0 | 1 = 1

0 | 0 = 0

int a = 0x123; int b = 0x456; int c = a|b;--> c=0x577


5. 置位 把a的bit7、8置位(变为1)

int a = 0x123; int b = a|(1<<7)|(1<<8);--> c=0x1a3


6. 清位 把a的bit7、8清位(变为0)

int a = 0x123; int b = (a& ~(1<<7))&(~(1<<8));--> c=0x23


置位和清位在后面寄存器的操作中,会经常使用。



第007节_编写C程序控制LED

C语言的指针操作:

①所有的变量在内存中都有一块区域;

②可以通过变量/指针操作内存;

Chapter8 lesson7 001.png

TYPE *p = val1;

*p = val2;


把val2写入地址val1的内存中,写入sizeof(TYPE)字节;

TYPE *p = addr; 

*p = val;


把val写入地址addrd的内存,,写入sizeof(TYPE)字节;


a. 我们写出了main函数, 谁来调用它? b. main函数中变量保存在内存中, 这个内存地址是多少? 答: 我们还需要写一个汇编代码, 给main函数设置内存, 调用main函数


led.c源码:

start.S源码:

Makefile源码:

最后将上面三个文件放入Ubuntu主机编译,然后烧写到开发板即可。



第008节_几条汇编指令_bl_add_sub_ldm_stm

⑥ADD/SUB 加法/减法

举例1:

add r0,r1,#4

效果为

r0=r1+4;


举例2:

sub r0,r1,#4

效果为

r0=r1-4;


举例3:

sub r0,r1,r2

效果为

r0=r1-r2;


⑦BL (Brarch and Link)带返回值的跳转 跳转到指定指令,并将返回地址(下一条指令)保存在lr寄存器;


⑧LDM/STM 读内存,写入多个寄存器/把多个寄存器的值写入内存

可搭配的后缀有 过后增加(Increment After)、预先增加(Increment Before)、过后减少(Decrement After)、预先减少(Decrement Before);

举例1:

stmdb sp!, (fp,ip,lr,pc)

假设Sp=4096。 db意思是先减后存,按 高编号寄存器存在高地址 存。 

Chapter8 lesson8 001.png

举例2:

ldmia sp, (fp,ip,pc)

Chapter8 lesson8 002.png


009节_解析C程序的内部机制

003_led.c内部机制分析:

start.S:

①设置栈;

②调用main,并把返回值地址保存到lr中;

led.c的main()内容:

①定义2个局部变量;

②设置变量;

③return 0;

问题:

①为什么要设置栈?

因为c函数要用。


②怎么使用栈?

a.保存局部变量;

b.保存lr等寄存器;


③调用者如何传参数给被调用者?

④被调用者如何传返回值给调用者?

⑤怎么从栈中恢复那些寄存器?


在arm中有个ATPCS规则,约定r0-r15寄存器的用途。

r0-r3:调用者和被调用者之间传参数;

r4-r11:函数可能被使用,所以在函数的入口保存它们,在函数的出口恢复它们;


下面分析个实例 start.S:

led.c:

将前面的程序反汇编得到led.dis如下:


分析上面的汇编代码:

开发板上电后,将从0地址开始执行,即开始执行

mov sp, #4096:设置栈地址在4k RAM的最高处,sp=4096;

bl    c <main>:调到c地址处的main函数,并保存下一行代码地址到lr,即lr=8;

mov ip, sp:给ip赋值sp的值,ip=sp=4096

stmdb sp!, {fp, ip, lr, pc}:按高编号寄存器存在高地址,依次将pc、lr、ip、fp存入sp-4中;

sub fp, ip, #4:fp的值为ip-4=4096-4=4092;

sub sp, sp, #8:sp的值为sp-8=(4096-4x4)-8=4072;

mov r3, #1442840576:r3赋值0x5600 0000; 

add r3, r3, #80:r3的值加0x50,即r3=0x5600 0050;

str r3, [fp, #-16]:r3存入[fp-16]所在的地址,即地址4076处存放0x5600 0050;

mov r3, #1442840576:r3赋值0x5600 0000; 

add r3, r3, #84:r3的值加0x54,即r3=0x5600 0054;

str r3, [fp, #-20]:r3存入[fp-20]所在的地址,即地址4072处存放0x5600 0054;

ldr r2, [fp, #-16]:r2取[fp-16]地址处的值,即[4076]地址的值,r2=0x5600 0050;

mov r3, #256:r3赋值为0x100;

str r3, [r2]:将r3写到r2内容所对应的地址,即0x5600 0050地址处的值为0x100;;对应c语言*pGPFCON = 0x100;;

ldr r2, [fp, #-20]:r2取[fp-20]地址处的值,即[4072]地址的值,r2=0x5600 0054;

mov r3, #0:r3赋值为0x00;

str r3, [r2]:将r3写到r2内容所对应的地址,即0x5600 0054地址处的值为0x00;对应c语言*pGPFDAT = 0;

mov r3, #0:r3赋值为0x00;

mov r0, r3:r0=r3=0x00;

sub sp, fp, #12:sp=fp-12=4092-12=4080;

ldmia sp, {fp, sp, pc}:从栈中恢复寄存器,fp=4080地址处的值=原来的fp,sp=4084地址处的值=4096,pc=4088地址处的值=8,随后调到0x08地址处继续执行。


过程中的内存数据情况:

Chapter8 lesson9 001.jpg


前面那个例子,汇编调用main.c并没有传递参数,这里修改下c程序,让其传递参数。

start.S:

led.c:

led.elf:

简单分析下反汇编:

 mov sp, #4096:设置栈地址在4k RAM的最高处,sp=4096;


 mov r0, #4:r0=4,作为参数;


 bl 58 <led_on>:调到58地址处的led_on函数,并保存下一行代码地址到lr,即lr=8;在led_on中会使用到r0;


 ldr r0, [pc, #12]:r0=[pc+12]处的值=[c+12=20]的值=0x186a0=1000000,作为参数;


 bl 24 <delay>:调用24地址处的delay函数,并保存下一行代码地址到lr,即lr=24;在delay中会使用到r0;


 mov r0, #5:r0=5,作为参数;


 bl 58 <led_on>:调到58地址处的led_on函数,并保存下一行代码地址到lr,即lr=58;在led_on中会使用到r0;



010节_完善LED程序_编写按键程序

在上一节视频里,我们编写的程序代码是先点亮led1,然后延时一会,再点亮led2,进入死循环。

但在开发板上的实际效果是led1先亮,延时一会,led2再亮,然后一会之后,led1再次亮了。

这和我们的设计的代码流程不吻合,这是因为2440里面有个看门狗定时器,开发板上电后,需要在一定时间内“喂狗”(设置相应的寄存器),否则就会重启开发板。

之所以这样设计,是为了让芯片出现死机时,能够自己复位,重新运行。


这里我们写个led灯循环的程序,步骤如下:

  1. 这里暂时用不到看门狗,先关闭看门狗,从参考手册可知,向0x53000000寄存器写0即可关闭看门狗;

  2. 设置内存的栈,通过写读操作来判断是Nand Flash还是Nor Flash;

  3. 设置GPFCON让GPF4/5/6配置为输出引脚;

  4. 循环点灯,依次设置GPFDAT寄存器;

完整代码如下:

led.c

2440里面有很多寄存器,如果每次对不同的寄存器进行查询和操作会很麻烦,因此可以先提前定义成宏,做成一个头文件,每次调用就行。

再举一个按键控制LED的程序,,步骤如下:

  1. 这里暂时用不到看门狗,先关闭看门狗,从参考手册可知,向0x53000000寄存器写0即可关闭看门狗;

  2. 设置内存的栈,通过写读操作来判断是Nand Flash还是Nor Flash;

  3. 设置GPFCON让GPF4/5/6配置为输出引脚;

  4. 设置3个按键引脚为输入引脚;

  5. 循环执行,读取按键引脚值,点亮对应的led灯;


完整代码如下:

视频教程👇

韦东山升级版全系列嵌入式免费视频_快速入门篇


8.2第1个ARM裸板程序及引申(下)的评论 (共 条)

分享到微博请遵守国家法律