导读:本文是裸机开发的第一篇,介绍裸机代码最基本的框架:关看门狗、设置栈、设置时钟、初始化串口、初始化SDRAM、实现重定位、清除BSS段并实现流水灯效果。使用的开发板是基于韦东山老师的JZ2440,SOC是三星的S3C2440芯片,外接了SDRAM,nandFlash和NorFlash
代码从_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 //函数调用返回
该文件放了串口测试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");
}
串口初始化、串口接收和发送功能实现
//串口参数设置相关寄存器
#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
用于指定链接地址,指定代码段,数据段,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 = .;
}
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