ARM嵌入式Linux裸机开发---汇编点亮LED

导读:本文是裸机开发的第一篇,介绍裸机代码最基本的框架:关看门狗、设置栈、设置时钟、初始化串口、初始化SDRAM、实现重定位、清除BSS段并实现流水灯效果。使用的开发板是基于韦东山老师的JZ2440,SOC是三星的S3C2440芯片,外接了SDRAM,nandFlash和NorFlash

目录

  • 1、 start.S汇编源代码
  • 2、main.c
  • 3、uart串口相关
  • 4、sdram.lds链接脚本
  • 5、Makefile
  • 6、编译结果:

1、 start.S汇编源代码

代码从_start标志处开始运行,start.S主要完成关看门狗,设置栈,设置时钟,初始化串口,初始化SDRAM,重定位,清除BSS以及实现流水灯效果

//流水灯相关寄存器
#define GPFCON   0x56000050
#define GPFDAT   0x56000054
//看门狗寄存器
#define WTCON  	 0x53000000
//时钟
#define LOCKTIME 0x4C000000
#define CLKDIVN  0x4C000014
#define MPLLCON	 0x4C000004
//设置时钟关闭某些模块
#define CLKCON	 0x4C00000C

.text
.global _start

_start:
	/*1、关看门狗*/
	ldr r0, =WTCON
	ldr r1, =0
	str r1, [r0]
	
	/*2、设置栈*/
	ldr sp, =0x40000000+4096 //nor启动
	mov r1, #0
	ldr r0, [r1]	//备份0地址的值,以便恢复
	str r1, [r1]	//将0写入0地址
	ldr r2, [r1]	//再将0地址值读出来
	cmp r1, r2
	moveq sp, #4096	//如果相等则证明代码在内部SRAM运行,是nand启动
	streq r0, [r1]	//写入成功,将备份值写入0地址,恢复原来的数据
	
	
	/* 3、设置时钟,提高CPU运行速度
	 * UPLL时钟给UCLK使用,MPLL时钟给FCLK,HCLK,PCLK使用
	 * FCLK = 400M		HCLK = FCLK/4		PCLK = FCLK/8
	 */
	ldr r0, =LOCKTIME    //设置UPLL和MPLL锁定时间
	ldr r1, =0xFFFFFFFF  //手册默认值
	str r1, [r0]

	ldr r0, =CLKDIVN	//设置DIVN_UPLL、HDIVN和PDIVN分频系数
	ldr r1, =0x5		//UCLK = UPLL clock,HCLK = FCLK/4 when CAMDIVN[9]=0,PCLK = HCLK/2.
	str r1, [r0]		//PLL锁定时间之后才会起效
	
	mrc p15,0,r0,c1,c0,0	//读协处理器
	orr r0,r0,#0xc0000000   //R1_nF:OR:R1_iA
	mcr p15,0,r0,c1,c0,0	//写协处理器,设置CPU工作于异步模式
	
	ldr r0, =MPLLCON	//设置外部晶振12M,CPU主频400M
	ldr r1, =(92<<12)|(1<<4)|(1<<0)
	str r1, [r0]		//HDIVN不为0时,必须设置CPU工作为异步模式,否则使用HCLK
	
	/*4、初始化串口*/
	bl uart0_init  //跳转到C语言初始化串口函数,在uart.c文件中
	
	/*5、打开icache*/
	//bl openIcache
	
	/*6、初始化SDRAM*/
	bl sdram_init  //跳转到C语言初始化内存函数,在main.c文件中
	
	/*7、代码重定位到SDRAM内存*/
	bl copysdram  //跳转到C语言重定位函数,在main.c文件中
	
	/*8、清除BSS段*/
	bl clean_bss  //跳转到C语言清除BSS函数,在main.c文件中

	ldr pc, =sdram //读内存指令,将sdram的链接地址赋给PC,跳转到SDRAM中执行
sdram:	
	//9、主函数测试串口,按q退出
	bl main
	
	//10、流水灯效果,GPF4 GPF5 GPF6引脚分别接了三个LED,输出高电平即可点亮LED
	//GPF4 GPF5 GPF6配置LED引脚为输出模式
	ldr r0, =GPFCON  
	ldr r1, =0x1500
	str r1, [r0]  //将0x1500写入GPFCON寄存器中
	
	ldr r0, =GPFDAT
flag:
	/**第一种流水灯效果**/
	ldr r1, =((1<<4) | (1<<5) | (1<<6)) //全灭
    str r1, [r0]  //将值写入GPFDAT寄存器中
    bl delay  //延时
	ldr r1, =((0<<4) | (1<<5) | (1<<6)) //第一个灯亮
	str r1, [r0]
	bl delay
	ldr r1, =((0<<4) | (0<<5) | (1<<6)) //第1和第二个灯亮
	str r1, [r0]
	bl delay
	ldr r1, =((0<<4) | (0<<5) | (0<<6))  //全亮
	str r1, [r0]
	bl delay
	/**第二种流水灯效果**/
	ldr r1, =~(1<<6)  //第三个灯亮
	str r1, [r0]
	bl delay
	ldr r1, =~(1<<5)  //第二个灯亮
	str r1, [r0]
	bl delay
	ldr r1, =~(1<<4)  //第一个灯亮
	str r1, [r0]
	bl delay
	
	b flag   //死循环点灯

/**延时函数**/
delay:
	ldr r2, =150000
	mov r3, #0
delay_loop:
	sub r2, r2, #1		//减1
	cmp r2, r3			//cmp会影响Z标志位
	bne delay_loop		//不相等则跳转,继续循环,否则执行下一条语句
	mov pc, lr			//函数调用返回

2、main.c

该文件放了串口测试main函数,以及SDRAM初始化、重定位和清除BSS段代码

/*SDRAM初始化相关寄存器*/
#define BWSCON   *(volatile unsigned int *)0x48000000
#define BANKCON6 *(volatile unsigned int *)0x4800001C
#define BANKCON7 *(volatile unsigned int *)0x48000020
#define REFRESH	 *(volatile unsigned int *)0x48000024
#define BANKSIZE *(volatile unsigned int *)0x48000028
#define MRSRB6   *(volatile unsigned int *)0x4800002C
#define MRSRB7   *(volatile unsigned int *)0x48000030

int main(void)
{
	char c;
	volatile unsigned char *sdram = (volatile unsigned char *)0x30000000; //sdram内存起始地址,也是链接地址
	volatile unsigned char *download = (volatile unsigned char *)0;  //下载起始地址

	//重定位测试
	putchar(*sdram); //输出内存地址和0地址的值
	putchar(*nand);
	putchar('\n');

	if(*sdram == *download) //*sdram原本等于'0',重定位后SDRAM起始地址的值应该和0地址中的值一样
		puts("\r\nSDRAM Copy Test Successfull!\r\n");  //重定位成功  \r\n表示会到换行且会到行首
	else
		puts("SDRAM Copy Test Error!\r\n");  //重定位失败
	
	while (1)  //测试串口输入和输出,终端向串口发送什么,开发板就返回什么,按q则推出
	{
		c = getchar(); //接受一个字符
		putchar(c);    //输出一个字符
		if(c == 'q')   //按q退出,执行LED闪烁程序
			break;
		else if(c == '\r')
			putchar('\n');
	}

	return 0;
}

//初始化SDRAM
void sdram_init(void)
{
	volatile unsigned char *sdram = (volatile unsigned char *)0x30000000;
	BWSCON = 0x22000000; //bank6和bank7使用32位数据位,低于32位操作时不屏蔽某几位数据,直接读32位数据
	BANKCON6 = 0x18001;  //bank6 bank7内存类型位SDRAM,行地址和列地址发出时间间隔位2个时钟周期(100M)20ns,列地址9位
	BANKCON7 = 0x18001;  //硬件只连接了bank6
	REFRESH  = 0x8404f5; //内存刷新Trp=2clock Tsrc=5clock Refresh=64ms/8kb=7.8us
	BANKSIZE = 0xb1;     //连续突发访问,可以使用时钟使能SDRAM休眠模式,64M空间
	MRSRB6   = 0x20;     //收到列地址后等两个时钟才返回数据
	MRSRB7   = 0x20;
	*sdram = '0';
	if(*sdram == '0')
		puts("\r\nSDRAM initial OK!\r\n");  //写成功
	else
		puts("SDRAM Write Failed!\r\n"); //写失败证明SDRAM初始化失败
}

//SDRAM重定位
void copysdram(void)
{
	extern int __code_start, __bss_start;  //获取连接脚本的符号地址
	volatile unsigned int *dest = (volatile unsigned int *)&__code_start;
	volatile unsigned int *end = (volatile unsigned int *)&__bss_start;
	volatile unsigned int *src = (volatile unsigned int *)0; //程序最开始下载到0地址

	while (dest < end) //将代码段和数据段从0地址复制到链接脚本指定的链接地址中,也就是SDRAM 0x30000000地址;
	{
		*dest++ = *src++;
	}
	puts("Copy to SDRAM OK!\r\n");
}

//清除BSS
void clean_bss(void)
{
	extern int _end, __bss_start; //从lds链接脚本文件中获得BSS起始和结束位置
	volatile unsigned int *start = (volatile unsigned int *)&__bss_start; //使用指针操作要对符号取地址
	volatile unsigned int *end = (volatile unsigned int *)&_end;
	while (start <= end)
	{
		*start++ = 0; //清零
	}
	puts("Clean BSS OK!\r\n");
}

3、uart串口相关

串口初始化、串口接收和发送功能实现

//串口参数设置相关寄存器
#define GPHCON		*(volatile unsigned int *)0x56000070
#define GPHUP		*(volatile unsigned int *)0x56000078
#define UCON0		*(volatile unsigned int *)0x50000004
#define UBRDIV0		*(volatile unsigned int *)0x50000028
#define ULCON0		*(volatile unsigned int *)0x50000000

//串口收发相关寄存器
#define UTXH0		*(volatile unsigned int *)0x50000020
#define URXH0		*(volatile unsigned int *)0x50000024
#define UTRSTAT0	*(volatile unsigned int *)0x50000010

/* 串口初始化115200,8n1 */
void uart0_init(void)
{
	/* 1、设置引脚用于串口 */
	GPHCON &= ~((3<<4) | (3<<6));/* GPH2,3用于TxD0, RxD0 */
	GPHCON |= ((2<<4) | (2<<6));
	GPHUP &= ~((1<<2) | (1<<3));  /* 使能内部上拉 */
	
	/* 2、设置波特率
	 * UBRDIVn = (int)( UART clock / ( buad rate x 16) ) –1 = ( 50000000 / ( 115200 x 16) ) –1 = 26
	 * UART时钟使用PCLK(50M)
	 */
	UCON0 = 0x00000005; //时钟使用PCLK,中断或查询模式收发
	UBRDIV0 = 26;

	/* 3、设置数据格式 */
	ULCON0 = 0x00000003; //8个数据位, 无较验位, 1个停止位
}

//发送一个字符
void putchar(const char c)
{
	while (!(UTRSTAT0 & (1<<2))); //等待数据发送完成
	UTXH0 = c;  //发送数据
}

//接受一个字符
char getchar(void)
{
	while (!(UTRSTAT0 & (1<<0))); //查询等待有可接收的数据
	return URXH0; //接受数
}

//发送字符串
void puts(const char *s)
{
	while (*s)
	{
		putchar(*s);
		s++;
	}
}

uart.h

#ifndef _UART_H
#define _UART_H
/*串口相关函数申明*/
void uart0_init(void);
int putchar(int c);
int getchar(void);
int puts(const char *s);

#endif

4、sdram.lds链接脚本

用于指定链接地址,指定代码段,数据段,BSS段等,并声明了一些标签,方便C语言调用

SECTIONS
{
	. = 0x30000000;

	__code_start = .;

	. = ALIGN(4);
	.text      :
	{
	  *(.text)
	}

	. = ALIGN(4);
	.rodata : { *(.rodata) }

	. = ALIGN(4);
	.data : { *(.data) }

	. = ALIGN(4);
	__bss_start = .;
	.bss : { *(.bss) *(.COMMON) }
	_end = .;
}

5、Makefile

all:
	arm-linux-gcc -c -o start.o start.S
	arm-linux-gcc -c -o uart.o uart.c
	arm-linux-gcc -c -o main.o main.c
	arm-linux-ld -T sdram.lds start.o main.o uart.o -o led.elf  #生成elf格式可执行文件
	arm-linux-objcopy -O binary -S led.elf led.bin  #将elf文件转换成可以烧录的bin文件
clean:
	rm *.bin *.o *.elf

6、编译结果:

ARM嵌入式Linux裸机开发---汇编点亮LED_第1张图片
点击下载源码

你可能感兴趣的:(嵌入式Linux,嵌入式,linux)