我的第一个ARM汇编程序(霓虹灯~~~)

开始基于S3C2440学习ARM裸机,记录一下第一个ARM汇编程序(比起8086的指令,ARM的指令要难一些,当然ARM指令比起IA32也是稍有逊色,毕竟越是技术更新换代,所需功能越多,复杂度也就越大)

1、纯汇编基础版:

@
@ Time:2019年4月1日23:15:07 
@ Author:Apollon_krj
@ 所有的三个LED点灯(GPF4~GPF6):1为灭,0为亮
@ 流水灯
@ 
@ arm的函数调用与返回,与8086差别还是蛮大的
@ (主要是当前不知道有什么比较类似于x86的call、ret指令,以下指令也是为了一个目的而去搜的)
@ 后者用stack存放函数地址,前者则直接是lr和pc两个寄存器来搞
@ 8086更趋向与软件逻辑,是两步:先call,再ret
@ 而arm则更随心所欲一些,是三步:先保存返回地址,再修改pc指针,执行完恢复pc指针
@ 即call指令相当于“LDR lr, =label_ret”、“LDR lr,=label_call”两条指令
@ 而ret指令相当于“MOV pc,lr”一条指令,也即8086“MOV pc,ss:[sp]”
@ 注意:ARM中,pc寄存器的值,永远是当前执行指令地址+8,而不是当前指令地址
@ 因为:当前执行指令地址为A,A+4指令已经开始被解析,而A+8指令已经开始被读取(即PC)
@

.text
.global _start

_start:
	LDR r0, =0x00				@ 0000 0000,亮亮亮 
	LDR lr, =one_light_delay	@ 点灯完成后的地址,即返回地址
	LDR pc, =_led_light			@ 点灯指令起始地址,等价于 B _led_light
one_light_delay:
	LDR lr, =two_light			@ 延时完成后的指令地址
	B _delay					@ 延时指令起始地址
	
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@	
two_light:
	LDR r0,	=0x70				@ 0111 0000,灭灭灭
	LDR lr, =two_light_delay
	B _led_light
two_light_delay:
	LDR lr, =_start				@ 死循环
	B _delay
	
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
_led_light:
	LDR r1, =0x56000050
	LDR r2, =0x1500				@ choose GPF:0001 0101 0000 0000,选中三个GPIO的输入功能
	LDR r3, [r1]				@ 获取原来的设置
	ORR r2, r3					@ 将新配置(GPF4~GPF6 output)增加上去
	STR r2, [r1]

	LDR r1, =0x56000054
	MOV r2, r0					@ set bit:r0
	STR r2, [r1]
	MOV pc, lr					@ 回复函数返回地址,接着执行
	
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
_delay:
	LDR r3, =0x100				@ 延时256 * 256 = 65536个指令周期
loop_ext:
	LDR r4, =0x100
loop_int:
	sub r4, r4, #1
	cmp r4, #0
	BNE loop_int				@ 内层循环条件判断
	
	sub r3, r3, #1
	cmp r3, #0
	BNE loop_ext				@ 外层循环条件判断
	MOV pc, lr					@ 循环结束,返回执行
	

查看一下elf文件部分内容和bin文件的机器码:
我的第一个ARM汇编程序(霓虹灯~~~)_第1张图片
可以看出,elf文件的组织形式,有一部分连续的地址存放代码,文件末尾前一部分地址存放符号表,虽然没有看多ELF文件的具体组织形式,但是也和PE文件格式差不了太多。
我的第一个ARM汇编程序(霓虹灯~~~)_第2张图片

2、带按键调时Asm/C混合版:

2.1、用到的寄存器描述:

①用到了五个寄存器,GPFCON、GPFDAT、GPGCON、GPGDAT、WTDOG寄存器(用来控制输出点亮LED、按键输入控制延时(非中断版))
我的第一个ARM汇编程序(霓虹灯~~~)_第3张图片
在这里插入图片描述

在这里插入图片描述
②GPFDAT、GPGDAT数据寄存器,用来存放pins的输入/输出的数据。
我的第一个ARM汇编程序(霓虹灯~~~)_第4张图片
我的第一个ARM汇编程序(霓虹灯~~~)_第5张图片
③看门狗的控制寄存器:
由于看门狗会在一段时间后复位,使得程序重新运行(流水灯看起来效果可能不连贯,如果延时恰好和复位时间一样,看起来可能没有什么不一样),所以因此需要关闭看门狗的复位功能,或者disable看门狗的定时器。其他bit不再生效。
我的第一个ARM汇编程序(霓虹灯~~~)_第6张图片
注:S3C2440看门狗定时器的计算方法为:

t_watchdog = 1/[ PCLK / (Prescaler value + 1) / Division_factor ]

2.2、原理图:

我的第一个ARM汇编程序(霓虹灯~~~)_第7张图片

2.3、工程目录结构和Makefile:

①目录结构:

Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/03_LED_Nor_Nand_WatchDogTimer_Key$ tree -a
.
├── all.bin
├── all.dis
├── all.elf
├── include
│   └── soc_s3c2440
│       ├── soc_s3c2440_field.h
│       ├── soc_s3c2440_reg.h
│       └── soc_s3c2440_reg_operator.h
├── led.c
├── .led.d
├── led.i
├── led.o
├── Makefile
├── start.o
└── start.S

②Makefile:

INCLUDE := -I ./include/soc_s3c2440/
CFLAGS := $(INCLUDE) 

OBJS = start.o led.o
C_SOURCE = led.c
DEP_FILES := $(patsubst %.c,.%.d,$(C_SOURCE))
DEP_FILES := $(wildcard $(DEP_FILES))

#注意,非命令语句不要Tab缩进
ifneq ($(DEP_FILES),)
include $(DEP_FILES)
endif

all:$(OBJS)
	#链接两个目标文件到一个.elf文件
	#特别注意两个.o文件的顺序,当我把main生成的.o放在前面时其结果是不对的!
	@arm-linux-ld -Ttext 0 $^ -o [email protected]
	#由目标代码生成机器码的bin文件
	@arm-linux-objcopy -O binary -S [email protected] [email protected]
	#由目标代码反汇编得出标准汇编指令(非伪汇编指令)
	@arm-linux-objdump -D [email protected] > [email protected]
	
start.o:start.s
	@arm-linux-gcc -c -o $@ $<	
	
%.o:%.c
	#记录一下预处理结果中对宏的处理,便于观察
	@arm-linux-gcc -E $< $(CFLAGS) -o $(patsubst %.c,%.i,$<)
	#分别编译,生成目标文件,依赖文件存为隐藏文件
	@arm-linux-gcc -c $< $(CFLAGS) -o $@ -MD -MF $(patsubst %.c,.%.d,$<)


.PHONY:clean
clean:
	@rm *.bin *.elf *.o *.dis *.i
	
.PHONY:clean_dep
clean_dep:
	@rm -f $(DEP_FILES) && echo "delete depend file success!" || echo "delete depend files failure!"

2.4、关键部分代码记录:

①汇编程序选择关闭看门狗复位功能和选择Nor启动/Nand启动并设置栈底到高内存地址,设置函数入口地址等:

@
@ Time:2019年4月5日09:40:31
@ Author:Kangruojin
@
.text
.global _start

_start:
	@disable watchDog Timer reset function
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	@auto choose start-up by Nor flash or Nand flash use Software
	@According to the position of hardware switch choose to change
	mov r1, #0
	ldr r0, [r1]				@Backup to save old values
	str r1, [r1]				@write 0 to addr 0
	ldr r2, [r1]				@read 0 from addr 0
	cmp r1, r2					@compare r1 with r2(Values at different times of addr 0)
	ldr sp, =0x40000000+4096	@set stack bottom for start-up by Nor flash
	moveq sp, #4096				@if is starp-up by Nand,change stack bottom is 4096
	streq r0, [r1]				@recover values before address 0 of backup

	@led light for C language
	bl led_light
	
_halt:
	b _halt

②功能函数(C部分):

/*
 * Description:
 *     三个按键控制流水灯的延时时间,有增加、减少、回复默认时间三种操作
 *     start.s中存在关闭看门狗和自动选择Nand启动/Nor启动并设置栈的操作
 * Time:2019年4月5日00:18:02
 * Author:Kangruojin
 * Version:1.2
*/

#include 

void delay(volatile int time)
{
	while(time--);
}

void init_config()
{
	/* 清空GPF0、GPF2、GPF4~GPF6、GPG3的控制信息,同时设置了GPF0、GPF2、GPG3为输入引脚 */
	GPFCONr &= ~(GPFCON_GPF0_CONF_BITSf \
				| GPFCON_GPF2_CONF_BITSf \
				| GPFCON_GPF4_CONF_BITSf \
				| GPFCON_GPF5_CONF_BITSf \
				| GPFCON_GPF6_CONF_BITSf);
	GPGCONr &= ~(GPGCON_GPG3_CONF_BITSf);

	/* 配置GPF0、GPF2为输入引脚,GPG3为输入引脚,00;配置GPF4~GPF6为输出引脚 */
	GPFCONr |= ((GPFCON_OUTPUT_BITS << 8) \
				| (GPFCON_OUTPUT_BITS << 10) \
				| (GPFCON_OUTPUT_BITS << 12));
	/* 配置GPF0、GPF2、GPG3为输入引脚,上面清空过后,相当于已经配置为输入了,不需要再配一次 */
}

void led_running()
{
	#define DEFAULT_DELAY_TIME		(25000)
	#define DEFAULT_INC_DEALY_TIME	(2000)
	#define DEFAULT_ADD_DEALY_TIME	(2000)
	#define DELAY_TIME_MIN			(5000)
	#define DELAY_TIME_MAX			(50000)
	/*
	 * 注意,此处的临时变量是实时更新的,所以防止CPU做优化,加上volatile,防止被不期望的优化
	 * CPU可能最开始还在取寄存器的值,到后来取了几次发现没变化就不取了,所以被坑了很久
	 */
	volatile unsigned int gpfTemp=0;
	volatile unsigned int gpgTemp=0;

	int delay_time = DEFAULT_DELAY_TIME; 
	unsigned int i = 0;
	unsigned int len = 0;
	const unsigned char led_info[] = {
		0x00, 0x40, 0x60, 0x70, 0x30, 0x10, 0x00, 0x70,
		0x00, 0x70, 0x00, 0x70, 0x00, 0x10, 0x30, 0x70, 
		0x60, 0x40, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70
	};
	len = sizeof(led_info) / sizeof(led_info[0]);

	/* 设置led为流水灯点亮 */
	while(1)
	{
		for(i = 0; i < len; i++)
		{
			gpfTemp = GPFDATr;
			gpgTemp = GPGDATr;

			/* 默认是高电平,按下按键为低电平 */
			if(_TEST_BIT_IS_ZERO(gpfTemp, 0)){
				if(delay_time < DELAY_TIME_MAX){			/* 上限 */
					delay_time += DEFAULT_ADD_DEALY_TIME;	/* 按键EINT0为增加延时 */
				}
			}
			if(_TEST_BIT_IS_ZERO(gpfTemp, 2)){
				if(delay_time > DELAY_TIME_MIN){			/* 下限 */
					delay_time -= DEFAULT_INC_DEALY_TIME;	/* 按键EINT2为减少延时 */
				}
			}
			if(_TEST_BIT_IS_ZERO(gpgTemp, 3)){
				delay_time = DEFAULT_DELAY_TIME;			/* 按键EINT11为回复默认延时 */
			}

			/* 
			 * 要点:不要用或运算直接给这3bit赋值,否则经过几次,这3bit就全被置为1了,不会再改变
			 * 非要或运算的话,每次需要清空,接着立即去或运算, 	 但是灯的切换过程,总会有点闪烁不定
			 * 所以,直接去获取新的状态,然后根据获取的每一位状态设置新的每一位状态即可。
			 */
			__SET_BIT(GPFDATr, 4, __GET_BIT(led_info[i], 4));
			__SET_BIT(GPFDATr, 5, __GET_BIT(led_info[i], 5));
			__SET_BIT(GPFDATr, 6, __GET_BIT(led_info[i], 6));

			delay(delay_time);
		}
	}
}
void led_light()
{
	init_config();
	led_running();
}


③部分寄存器宏定义与字段宏定义:

/* 寄存器定义头文件:soc_s3c2440_reg.h */
#ifndef _SOC_S3C2440_REG_H_
#define _SOC_S3C2440_REG_H_

#define __REG_VALUE(addr) (*((volatile unsigned int *)(addr)))

/* The suffix 'r' means that the macro is a register. */
#define WTCONr	__REG_VALUE(0x53000000)		/* watch dog register */

#define GPFCONr	__REG_VALUE(0x56000050)		/* GPF config register */
#define GPFDATr	__REG_VALUE(0x56000054)		/* GPF data register */
#define GPFUPr	__REG_VALUE(0x56000058)		/* GPF pins Pull-up constrol register,0 is ebable */

#define GPGCONr	__REG_VALUE(0x56000060)		/* GPG config register */
#define GPGDATr __REG_VALUE(0x56000064)		/* GPG data register */
#define GPGUPr	__REG_VALUE(0x56000068)		/* GPG pins Pull-up constrol register,0 is enable */
#endif /* _SOC_S3C2440_REG_H_ */


/* 寄存器部分字段定义头文件:soc_s3c2440_field.h */
#ifndef _SOC_S3C2440_FIELD_H_
#define _SOC_S3C2440_FIELD_H_

/* The suffix 'f' means that the macro is the bits of a field in a register. */

/* GPFCON register fields bits */
#define GPFCON_GPF0_CONF_BITSf	(0x3 << 0)
#define GPFCON_GPF1_CONF_BITSf 	(0x3 << 2)
#define GPFCON_GPF2_CONF_BITSf 	(0x3 << 4)
#define GPFCON_GPF3_CONF_BITSf 	(0x3 << 6)
#define GPFCON_GPF4_CONF_BITSf 	(0x3 << 8)
#define GPFCON_GPF5_CONF_BITSf 	(0x3 << 10)
#define GPFCON_GPF6_CONF_BITSf 	(0x3 << 12)
#define GPFCON_GPF7_CONF_BITSf 	(0x3 << 14)

#define GPFCON_INPUT_BITS	(0x0)
#define GPFCON_OUTPUT_BITS	(0x1)
#define GPFCON_EINT_BITS	(0x2)			/* EINT0~EINT7 at GPFCON GPF0~GPF7 */
#define GPFCON_REVERSE_BITS	(0x3)

/* GPFDAT register fields bits */
#define GPFDAT_GPF0_DATA_BITSf	(0x1 << 0)
#define GPFDAT_GPF1_DATA_BITSf	(0x1 << 1)
#define GPFDAT_GPF2_DATA_BITSf	(0x1 << 2)
#define GPFDAT_GPF3_DATA_BITSf	(0x1 << 3)
#define GPFDAT_GPF4_DATA_BITSf	(0x1 << 4)
#define GPFDAT_GPF5_DATA_BITSf	(0x1 << 5)
#define GPFDAT_GPF6_DATA_BITSf	(0x1 << 6)
#define GPFDAT_GPF7_DATA_BITSf	(0x1 << 7)

//......GPGCON、GPGDAT略
#endif

④操作宏函数:

#ifndef _SOC_S3C2440_REG_OPERATOR_
#define _SOC_S3C2440_REG_OPERATOR_

#include 
#include 

#define __GET_BIT(sValue, bitPos)		(((sValue) >> (bitPos)) & (0x01))
#define __SET_BIT(sValue, bitPos, setBit)	((setBit) ? ((sValue) |= ((0x01) << (bitPos))) \
														: ((sValue) &= ~((0x01) << (bitPos))))

#define _TEST_BIT_IS_ZERO(sValue, bitPos)	(__GET_BIT(sValue, bitPos) ? (0) : (1))
#define _TEST_BIT_IS_ONE(sValue, bitPos)	(__GET_BIT(sValue, bitPos) ? (1) : (0))

#endif

2.5、可以看一下预处理信息:

# 1 "led.c"
# 1 ""
# 1 ""
# 1 "led.c"
# 10 "led.c"
# 1 "./include/soc_s3c2440/soc_s3c2440_reg_operator.h" 1

# 1 "./include/soc_s3c2440/soc_s3c2440_reg.h" 1
# 5 "./include/soc_s3c2440/soc_s3c2440_reg_operator.h" 2
# 1 "./include/soc_s3c2440/soc_s3c2440_field.h" 1
# 6 "./include/soc_s3c2440/soc_s3c2440_reg_operator.h" 2
# 11 "led.c" 2

void delay(volatile int time)
{
 while(time--);
}

void init_config()
{
 (*((volatile unsigned int *)(0x56000050))) &= ~((0x3 << 0) | (0x3 << 4) | (0x3 << 8) | (0x3 << 10) | (0x3 << 12));
 (*((volatile unsigned int *)(0x56000060))) &= ~((0x3 << 6));
 (*((volatile unsigned int *)(0x56000050))) |= (((0x1) << 8) | ((0x1) << 10) | ((0x1) << 12));
}

void led_running()
{
# 45 "led.c"
 volatile unsigned int gpfTemp=0;
 volatile unsigned int gpgTemp=0;

 int delay_time = (25000);
 unsigned int i = 0;
 unsigned int len = 0;
 const unsigned char led_info[] = {
  0x00, 0x40, 0x60, 0x70, 0x30, 0x10, 0x00, 0x70,
  0x00, 0x70, 0x00, 0x70, 0x00, 0x10, 0x30, 0x70,
  0x60, 0x40, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70
 };
 len = sizeof(led_info) / sizeof(led_info[0]);

 while(1)
 {
  for(i = 0; i < len; i++)
  {
   gpfTemp = (*((volatile unsigned int *)(0x56000054)));
   gpgTemp = (*((volatile unsigned int *)(0x56000064)));

   if(((((gpfTemp) >> (0)) & (0x01)) ? (0) : (1))){
    if(delay_time < (50000)){
     delay_time += (2000);
    }
   }
   if(((((gpfTemp) >> (2)) & (0x01)) ? (0) : (1))){
    if(delay_time > (5000)){
     delay_time -= (2000);
    }
   }
   if(((((gpgTemp) >> (3)) & (0x01)) ? (0) : (1))){
    delay_time = (25000);
   }
   (((((led_info[i]) >> (4)) & (0x01))) ? (((*((volatile unsigned int *)(0x56000054)))) |= ((0x01) << (4))) : (((*((volatile unsigned int *)(0x56000054)))) &= ~((0x01) << (4))));
   (((((led_info[i]) >> (5)) & (0x01))) ? (((*((volatile unsigned int *)(0x56000054)))) |= ((0x01) << (5))) : (((*((volatile unsigned int *)(0x56000054)))) &= ~((0x01) << (5))));
   (((((led_info[i]) >> (6)) & (0x01))) ? (((*((volatile unsigned int *)(0x56000054)))) |= ((0x01) << (6))) : (((*((volatile unsigned int *)(0x56000054)))) &= ~((0x01) << (6))));

   delay(delay_time);
  }
 }
}
void led_light()
{
 init_config();
 led_running();
}

你可能感兴趣的:(S3C2440)