(二)裸机汇编--点亮LED

目标:点亮LED

1、查数据手册

硬件图中,找到LED灯对应的GPIO
(二)裸机汇编--点亮LED_第1张图片
(二)裸机汇编--点亮LED_第2张图片
从二极管方向看出,端口输出低电平时,电流经过,LED点亮。

再到数据手册查找对应的寄存器
(二)裸机汇编--点亮LED_第3张图片
(二)裸机汇编--点亮LED_第4张图片
(二)裸机汇编--点亮LED_第5张图片
GPBCON:模式选择,GPIO配置可以有3中模式,输入、输出、DAC
GPBDAT:数据位
GPBUP:上拉禁能标志

2、纯汇编程序

LED1对应的是GPB5
(1)配置GPB5为输出引脚,给GPBCON寄存器写入(1 << 10)
(2)设置GPB5输出低电平,给GPBDAT寄存器写入(1 << 5)
(3)GPBUP保持默认

/*
 * LED.S
 * LED1:GPB5
 */

.text
.global _start

_start:

/* 配置GPF4为输出引脚
 * 把0x400写到地址0x56000010
 */
	ldr r1, =0x56000010	/* 0x56000010比较大,使用伪指令ldr,将0x56000010写入r1 */
	ldr r0, =0x400		/* mov r0, #0x400  带#表示立即数 */
	str r0, [r1]		/* r1作为地址,将r0的数据存放到r1地址对应位置 */


/* 设置GPB5输出低电平 
 * 把0写到地址0x56000014
 */
	ldr r1, =0x56000014
	ldr r0, =0	/* mov r0, #0 */
	str r0, [r1]

	/* 死循环 */
halt:
	b halt

3、编译程序

编译常用的命令在gcc/ld/objcopy/objdump命令中整理。
在终端中输入命令:

jw@pc:~/w/02-led$ arm-linux-gcc -c -o led_on.o LED.S 
jw@pc:~/w/02-led$ arm-linux-ld -Ttext 0 led_on.o -o led_on.elf
jw@pc:~/w/02-led$ arm-linux-objcopy -O binary -S led_on.elf led_on.bin
jw@pc:~/w/02-led$ ls -l
总用量 20
-rwxrwxr-x 1 jw jw    36 5月  18 22:06 led_on.bin
-rwxrwxr-x 1 jw jw 33502 5月  18 22:05 led_on.elf
-rw-rw-r-- 1 jw jw   671 5月  18 22:05 led_on.o
-rwxrw-rw- 1 jw jw   522 5月  18 22:00 LED.S

也可以使用makefile:

all:
	arm-linux-gcc -c -o led_on.o LED.S
	arm-linux-ld -Ttext 0 led_on.o -o led_on.elf
	arm-linux-objcopy -O binary -S led_on.elf led_on.bin
	arm-linux-objdump -D led_on.elf > led_on.dis
clean:
	rm *.bin *.o *.elf

4、烧写程序

使用JLink往开发板的NOR FLASH中烧写这个程序
烧写过程与上一篇文章(一)MINI2440–恢复出厂相同。
烧写成功后,重启,led1被点亮了。

5、常用的汇编指令小结

ARM是RISC结构,在ARM架构下, 数据从内存到CPU之间的移动只能通过LDR/STR指令来完成. 而MOV只能在寄存器之间移动数据,或者把立即数移动到寄存器中,并且数据的长度不能超过8位

比如想把数据从RAM中某处读取到CPU寄存器中,只能使用ldr

ldr(load)

ldr r0,=label 用于加载立即数或一个地址值到指定寄存器中

  • 如果label是立即数: ldr r0,=0X123 ;将0X123存入r0中

  • 如果name是个标识符: ldr r0,=label_1 ;将label_1所指向的地址值存入r0中

ldr r0,[r1] ;将R1地址中的值存到r0中

ldr r1,[r2,#16] ;将(r2+16)地址中的内容存到r1中

ldr r1,[r2],#4 ;将r2地址中的内容存到r1中,同时r2=r2+4

str(store)

str r1,[r2] ; 将r1中的值存到r2所指定的地址中

str r1,[r2,#4] ;将r1中的值存到r2+4所指定的地址中

str r1,[r2],#4 ;将r1中的值存到r2所指定的地址中, 同时r2=r2+4

6、汇编+C

如果要使用C语言,则需要为C程序搭建环境,主要是配置堆栈,然后调用main函数。

而堆栈的栈顶位置随着Nand启动、非Nand启动而有所不同,对于MINI2440的内存空间,可以参考:

s3c2440启动过程分析

s3c2440启动过程详解 (主要讲启动时nand nor的地址映射)

  • 为什栈顶是4096而不是4095?
    可用栈空间大小是4096字节,sp始终指向最后一个进入堆栈的信息所在的单元。
    那么,入栈时,SP先减,再拷贝数据入栈;出栈时,先拷贝数据出栈,SP再加。栈空时,有SP=Stack.size;栈满时,有SP=0。
  • 为什么栈顶是4096而不是0?
    因为栈是从高地址向低地址生长

汇编程序,主要为C程序搭建运行环境和调用C:

/* Start.S */
.text
.global _start

_start:
	/* 关闭看门狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 设置内存: sp 栈 */
	/* 分辨是nor/nand启动
	 * 写0到0地址, 再读出来
	 * 如果得到0, 表示0地址上的内容被修改了
	 * 说明0地址对应片内sram,因为片内sram不用经过我们配置就可以被读写, 这就是nand启动模式
	 * 否则就是nor启动
	 */
	mov r1, #0
	ldr r0, [r1] /* 读出原来的值备份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND启动 */
	ldr sp, =0x40000000+4096 /* 先假设是nor启动 */
	moveq sp, #4096  /* nand启动 */
	streq r0, [r1]   /* 恢复原来的值 */


	/* 调用main */
	bl main

halt:
	b halt

C程序操作寄存器:

/* led.c */
int main()
{
	unsigned int *pGPBCON = (unsigned int *)0x56000010;
	unsigned int *pGPBDAT = (unsigned int *)0x56000014;

	/* 配置GPB5为输出引脚 */
	*pGPFCON = 0x400;
	
	/* 设置GPB5输出0 */
	*pGPFDAT = 0;

	return 0;
}

Makefile

all:
	arm-linux-gcc -c -o led.o led.c
	arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 start.o led.o -o led.elf
	arm-linux-objcopy -O binary -S led.elf led.bin
	arm-linux-objdump -D led.elf > led.dis
clean:
	rm *.bin *.o *.elf *.dis
	

你可能感兴趣的:(MINI2440)