8.2第1个ARM裸板程序及引申(下)
第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语言的指针操作:
①所有的变量在内存中都有一块区域;
②可以通过变量/指针操作内存;

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意思是先减后存,按 高编号寄存器存在高地址 存。

举例2:
ldmia sp, (fp,ip,pc)

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地址处继续执行。
过程中的内存数据情况:

前面那个例子,汇编调用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灯循环的程序,步骤如下:
这里暂时用不到看门狗,先关闭看门狗,从参考手册可知,向0x53000000寄存器写0即可关闭看门狗;
设置内存的栈,通过写读操作来判断是Nand Flash还是Nor Flash;
设置GPFCON让GPF4/5/6配置为输出引脚;
循环点灯,依次设置GPFDAT寄存器;
完整代码如下:


led.c


2440里面有很多寄存器,如果每次对不同的寄存器进行查询和操作会很麻烦,因此可以先提前定义成宏,做成一个头文件,每次调用就行。
再举一个按键控制LED的程序,,步骤如下:
这里暂时用不到看门狗,先关闭看门狗,从参考手册可知,向0x53000000寄存器写0即可关闭看门狗;
设置内存的栈,通过写读操作来判断是Nand Flash还是Nor Flash;
设置GPFCON让GPF4/5/6配置为输出引脚;
设置3个按键引脚为输入引脚;
循环执行,读取按键引脚值,点亮对应的led灯;
完整代码如下:



视频教程👇
