您的位置:首页 > 编程语言

S3C2416裸机开发系列三_启动代码以及流水灯c代码

2014-03-16 10:25 375 查看

S3C2416裸机开发系列三

启动代码以及流水灯c代码

象棋小子 1048272975
启动代码是处理器上电复位后最先运行的一段代码。主要是用来把处理器初始化到一个确定的状态,为c运行环境作好准备。如设置异常向量表,初始化系统时钟,初始化外部内存,把用户代码拷贝到外部内存,初始化栈,清0全局变量区,静态变量区等。与体系结构相关的部分,只能用汇编来写。由于S3C2416/50/51目前在各个编译器中都还没有启动代码文件,因此笔者在MDK下主要参考S3C2440的启动代码来写一个S3C2416的启动代码。

1. 启动代码流程

启动代码的内容,顺序等都没有特定的要求,只要能达到初始化c运行环境即可。这里笔者说明一下,编写的启动代码应该是地址无关的,即链接到任存储器地址都是可以运行的。如S3C2416在nand boot/Norboot时用户的第一条代码是在0x0处执行的,而在IROM boot时,用户的第一条代码是在0x40000000处执行的,启动代码对不同的启动模式都是要能正确运行的。此外,启动代码文件内容应该是板级无关的,不同的开发板,只要是同一处理器(S3C2416/50/51),启动代码文件都是适用的。即板级的代码如GPIO口的初始化不适合放在启动代码中实现。此处笔者只以笔者的启动代码思路来作讲解。

1.1. 异常向量表

处理器在上电复位完成后,第一条代码进入的是异常向量表中对应的复位向量地址。对于arm,这个异常向量表的地址通常是0x00000000偏移处,如发生复位,则arm进入0x0处的复位向量地址,发生IRQ中断则进入0x18处的IRQ异常向量地址。异常向量表就是用来记录各个异常进入时的代码处理位置。

1.2. 关看门狗

复位代码最先做的事应该是关看门狗,因为如果看门狗打开的话,在启动代码进行初始化过程中是无法喂狗的,可能造成处理器一直不停复位。

1.3. 关闭所有中断

启动代码未完成时,各个状态都还不是确定的,如果有中断打开并引起中断异常,可能造成代码跑飞。

1.4. 初始化系统时钟

一般来说,处理器复位后都是运行在一个较低速的时钟下,为加快启动,通常尽可能快地设置处理器的各个时钟。

1.5. 初始化外部内存

除了Nor flash可以直接执行代码外,其它的代码存储器如nand flash、sd/mmc都是不能直接执行代码的。因此需要初始化外部内存,用来存储代码,变量。(本章节为避免复杂化,暂时不讲解,注释掉调用DDR2的初始化及用户代码的搬移)

1.6. 用户代码拷贝到外部内存

对于代码存储在nand、sd/mmc等不能直接执行代码的存储器,初始代码是一定需要把用户代码从这些设备读入到特定的内存中执行的。而对于Nor flash可直接执行代码的存储器,通常为了提高性能,也是会把代码从Nor flash读出,在内存中执行的。

1.7. MMU初始化

MMU的初始化不总是必须的,主要是为提高性能,开D-cache必须开MMU。如果是IROM SD/MMC或IROM NAND方式运行代码,用到了中断,则也必须开MMU,因为IROM启动在0x0地址处为IROM固化代码,需要对中断向量表重新映射到RAM。(本章节为避免复杂化,暂时不讲解,注释掉MMU的初始化)

1.8. 初始化栈

不管是用汇编还是用c编写代码,栈是一定要分配及初始化的。arm7/arm9有七种工作模式,每种模式的栈均应该设置,最后是进入用户模式。

1.9. 跳转到c入口

进入c入口之前是需要初始化c环境的,如清0全局变量、静态变量区等。此处为保持跟MDK的其它arm启动代码一致,直接跳转到__main,利用编译器的库函数来完成c环境的初始化,最后才是真正的用户main函数。启动代码完成后,最后是用绝对地址来跳转到c入口__main的。

2. 启动代码的实现

笔者对启动代码都是有一定的注释,有些寄存器的设置是需要参考芯片数据手册,S3C2416数据手册,arm架构以及汇编语法学习笔者在之前的文章已给出相关资料的链接,欢迎下载学习。只要加入了启动代码,就可以任意用c语言来开发S3C2416了。

;/*******************************************************/

;/* s3c2416.s: start code forsamsung s3c2416/50/51(arm9) */

;/********************************************************/

; Clock setting(External Crystal12M):

; MPLLCLK = 800M, EPLLCLK = 96M

; ARMCLK = 400M, HCLK = 133M

; DDRCLK = 266M, SSMCCLK = 66M,PCLK = 66M

; HSMMC1 = 24M

; Standard definitions of Mode bitsand Interrupt (I & F) flags in PSRs

Mode_USR EQU 0x10

Mode_FIQ EQU 0x11

Mode_IRQ EQU 0x12

Mode_SVC EQU 0x13

Mode_ABT EQU 0x17

Mode_UND EQU 0x1B

Mode_SYS EQU 0x1F

; when I bit is set, IRQ isdisabled

I_Bit EQU 0x80

; when F bit is set, FIQ isdisabled

F_Bit EQU 0x40

; Stack Configuration

UND_Stack_Size EQU 0x00000020

SVC_Stack_Size EQU 0x00000020

ABT_Stack_Size EQU 0x00000020

FIQ_Stack_Size EQU 0x00000100

IRQ_Stack_Size EQU 0x00000400

USR_Stack_Size EQU 0x00001000

ISR_Stack_Size EQU (UND_Stack_Size + SVC_Stack_Size + \

ABT_Stack_Size+ FIQ_Stack_Size + \

IRQ_Stack_Size)

AREA STACK, NOINIT, READWRITE, ALIGN=3

Stack_Mem SPACE USR_Stack_Size

__initial_sp SPACE ISR_Stack_Size

Stack_Top

; Heap Configuration

Heap_Size EQU 0x00000200

AREA HEAP, NOINIT, READWRITE, ALIGN=3

__heap_base

Heap_Mem SPACE Heap_Size

__heap_limit

; Internal Memory Base Addresses

IRAM_BASE EQU 0x40000000

; Watchdog Timer Base Address

WT_BASE EQU 0x53000000

; Interrupt Register Base Address

INT_BASE EQU 0x4A000000

INTMSK1_OFS EQU 0x08

INTSUBMSK_OFS EQU 0x1C

INTMSK2_OFS EQU 0x48

; Clock Base Address

CLOCK_BASE EQU 0x4C000000

LOCKCON0_OFS EQU 0x00

LOCKCON1_OFS EQU 0x04

MPLLCON_OFS EQU 0x10

EPLLCON_OFS EQU 0x18

CLKSRC_OFS EQU 0x20

CLKDIV0_OFS EQU 0x24

CLKDIV1_OFS EQU 0x28

CLKDIV2_OFS EQU 0x2C

;----------------------- CODE-------------------------------------------

PRESERVE8

; Area Definition and Entry Point

; Startup Code must be linked first at Address at which it expects to run.

AREA RESET, CODE, READONLY

; ENTRY

ARM

Vectors B Reset_Handler

LDR PC, Undef_Addr

LDR PC, SWI_Addr

LDR PC, PAbt_Addr

LDR PC, DAbt_Addr

LDR PC, Notuse_Addr

LDR PC, IRQ_Addr

LDR PC, FIQ_Addr

Reset_Addr DCD Reset_Handler

Undef_Addr DCD Undef_Handler

SWI_Addr DCD SWI_Handler

PAbt_Addr DCD PAbt_Handler

DAbt_Addr DCD DAbt_Handler

Notuse_Addr DCD 0 ;Reserved Address

IRQ_Addr DCD IRQ_Handler

FIQ_Addr DCD FIQ_Handler

Undef_Handler B Undef_Handler

SWI_Handler B SWI_Handler

PAbt_Handler B PAbt_Handler

DAbt_Handler B DAbt_Handler

IRQ_Handler B IRQ_Handler

FIQ_Handler B FIQ_Handler

EXPORT Reset_Handler

Reset_Handler

;/*******************************************************************/

; 看门狗关闭

LDR R0, =WT_BASE

LDR R1, =0

STR R1, [R0]

;/*******************************************************************/

; 关闭所有外设中断

LDR R0, =INT_BASE

LDR R1, =0xFFFFFFFF

STR R1, [R0, #INTMSK1_OFS]

STR R1, [R0, #INTMSK2_OFS]

STR R1, [R0, #INTSUBMSK_OFS]

;/********************************************************************/

; 系统时钟设置

LDR R0, =CLOCK_BASE

LDR R1, =3600

; MPLL锁定时间大于300us,以外部晶振12M计

STR R1, [R0, #LOCKCON0_OFS]

LDR R1, =3600

; EPLL锁定时间大于300us

STR R1, [R0, #LOCKCON1_OFS]

; PLL锁定时间设小了也应该不会有致命的问题,只是改变PLL输出后,

; 过早地输出不稳定的时钟给system

; MPLL(或外部旁路分频输出)通过ARM分频器输出ARM clock(533M),通过预分频器输

; 出给HCLK(133M), DDRCLK(266M),SSMCCLK(Memory Controllers,133M),PCLK(66M)

; 设置PCLK=HCLK/2,SSMCCLK=HCLK/2,设置MPLL输出时钟800M,ARM clock分频比设2,

; 得到ARM clock 400M,预分频器分频比设为3,输出266M后再HCLK分频器2分频输出

; 给HCLK=266M/2=133M,HCLKDIV[1:0],PREDIV[5:4],ARMDIV[11:9],

; ARMCLK Ratio=(ARMDIV+1),HCLKRatio=(PREDIV+1)*(HCLKDIV+1)

LDR R1,=(0x1<<0)+(1<<2)+(1<<3)+(0x2<<4)+(0x1<<9)

STR R1,[R0, #CLKDIV0_OFS]

; EPLL(或外部旁路分频输出)通过各自的分频器输出给SPI(CLKSRC可选由MPLL供),

; DISP,I2S,UART,HSMMC1,USBHOST

LDR R1,=(0x0<<4)+(0x3<<6)+(0x0<<8)+(0x0<<12)+ \

(0x0<<16)+(0x0<<24)

STR R1, [R0, #CLKDIV1_OFS]

; HSMMC0时钟由EPPL供,SPI时钟可由MPLL供

LDR R1, =(0x0<<0)+(0x0<<6)

STR R1, [R0, #CLKDIV2_OFS]

; 设置EPLL输出96M,

;MDIV=32,PDIV=1,SDIV=2,Fout=((MDIV+(KDIV/2^16))*Fin)/(PDIV*2^SDIV),KDIV=0

LDR R1,=(2<<0)+(1<<8)+(32<<16)+(0x0<<24)+(0x0<<25)

STR R1, [R0, #EPLLCON_OFS]

; 给EPLLCON写入值并打开EPLL,此时EPLL clock锁定不输出,

; 延时LOCKCON1个时钟稳定后才输出时钟

; 外部晶振12M,设置MPLL输出为800M,

;MDIV=400,PDIV=3,SDIV=1,Fout=(MDIV*Fin)/(PDIV*2^SDIV)

LDR R1,=(1<<0)+(3<<5)+(400<<14)+(0x0<<24)+(0x0<<25)

STR R1, [R0, #MPLLCON_OFS]

; 给MPLLCON写入值并打开MPLL,此时MPLL clock锁定不输出,

; 延时LOCKCON0个时钟稳定后才输出时钟

LDR R1, =(1<<4)+(1<<6)

; 时钟源设置MPLL和EPLL输出

STR R1,[R0, #CLKSRC_OFS]

;/************************************************************************/

; 外部内存控制设置

; IMPORT ERAM_Init

; BLX ERAM_Init ; 外部RAM初始化

; LDR SP, =Stack_Top

; RAM初始化后调整栈指针到外部RAM

;/************************************************************************/

; 拷贝用户代码到RAM

; IMPORT CopyCodeToRAM

; BLX CopyCodeToRAM

;/************************************************************************/

; MMU初始化

; IMPORT MMU_Init

; BLX MMU_Init

;/*************************************************************************/

; 堆栈初始化

LDR R0, =Stack_Top

; Enter Undefined Instruction Mode and set its Stack Pointer

MSR CPSR_c, #Mode_UND:OR:I_Bit:OR:F_Bit

MOV SP, R0

SUB R0, R0, #UND_Stack_Size

; Enter Abort Mode and set its Stack Pointer

MSR CPSR_c, #Mode_ABT:OR:I_Bit:OR:F_Bit

MOV SP, R0

SUB R0, R0, #ABT_Stack_Size

; Enter FIQ Mode and set its Stack Pointer

MSR CPSR_c, #Mode_FIQ:OR:I_Bit:OR:F_Bit

MOV SP, R0

SUB R0, R0, #FIQ_Stack_Size

; Enter IRQ Mode and set its Stack Pointer

MSR CPSR_c, #Mode_IRQ:OR:I_Bit:OR:F_Bit

MOV SP, R0

SUB R0, R0, #IRQ_Stack_Size

; Enter Supervisor Mode and set its Stack Pointer

MSR CPSR_c, #Mode_SVC:OR:I_Bit:OR:F_Bit

MOV SP, R0

SUB R0, R0, #SVC_Stack_Size

; Enter System Mode and set its Stack Pointer

MSR CPSR_c, #Mode_SYS

MOV SP, R0

SUB SL, SP, #USR_Stack_Size

; 是否使用了KEIL的微库

IF :DEF:__MICROLIB

EXPORT __initial_sp

ELSE

MOV SP, R0

SUB SL, SP, #USR_Stack_Size

ENDIF

;/***********************************************************************/

; 绝对地址跳转到c入口

IMPORT __main

LDR R0, =__main

BX R0

IF :DEF:__MICROLIB

EXPORT __heap_base

EXPORT __heap_limit

ELSE

; User Initial Stack & Heap

AREA |.text|, CODE, READONLY

IMPORT __use_two_region_memory

EXPORT __user_initial_stackheap

__user_initial_stackheap

LDR R0, = Heap_Mem

LDR R1, =(Stack_Mem + USR_Stack_Size)

LDR R2, = (Heap_Mem + Heap_Size)

LDR R3, = Stack_Mem

BX LR

ENDIF

END

3. 流水灯c代码

启动代码写好后,就可以运行用户的main函数了。本章节的启动代码暂未实现外部内存的初始化以及用户代码的拷贝,因此可运行的代码实际是在Steppingstone中的,只有8k大小,因此本章学习用的代码不要超过8k。

#include "s3c2416.h"

// IO port for controling LEDs

#define LED2 (13)// GPE13 LED2

#define LED3 (11)// GPE11 LED3

#define LED4 (13)// GPL13 LED4

#define LED5 (12)// GPE12 LED5

#define LED6 (2) // GPG2 LED6

#define LED7 (15)// GPA15 LED7

// c调用汇编查看ATPCS

__asm void Delay_ms(unsigned int nCount)

{

//延时1ms,共延时nCount(R0) ms

Delay1

LDR R1, =100000 // Arm clock为400M

Delay2

SUBS R1, R1,#1 // 一个Arm clock

BNE Delay2 // 跳转会清流水线,3个Arm clock

SUBS R0, R0, #1 // 调用者确保nCount不为0

BNE Delay1

BX LR

}

void Gpio_LED2(unsigned char On)

{

if (!On) {

rGPEDAT&= ~(1<<LED2);

} else {

rGPEDAT|= (1<<LED2);

}

}

void Gpio_LED3(unsigned char On)

{

if (!On) {

rGPEDAT&= ~(1<<LED3);

} else {

rGPEDAT|= (1<<LED3);

}

}

void Gpio_LED4(unsigned char On)

{

if (!On) {

rGPLDAT&= ~(1<<LED4);

} else {

rGPLDAT|= (1<<LED4);

}

}

void Gpio_LED5(unsigned char On)

{

if (!On) {

rGPEDAT&= ~(1<<LED5);

} else {

rGPEDAT|= (1<<LED5);

}

}

void Gpio_LED6(unsigned char On)

{

if (!On) {

rGPGDAT&= ~(1<<LED6);

} else {

rGPGDAT|= (1<<LED6);

}

}

void Gpio_LED7(unsigned char On)

{

if (!On) {

rGPADAT&= ~(1<<LED7);

} else {

rGPADAT|= (1<<LED7);

}

}

void Gpio_Init()

{

// GPA15 LED7output

rGPACON &=~(1<<LED7);

// GPG2 LED6output

rGPGCON &=~(0x03 << (LED6<<1));

rGPGCON |=(0x01 << (LED6<<1));

// GPE11,12,13LED3,LED5,LED2 output

rGPECON &=~((0x03<<(LED3<<1)) | (0x03<<(LED5<<1))

|(0x03<<(LED2<<1)));

rGPECON |=((0x01<<(LED3<<1)) | (0x01<<(LED5<<1))

|(0x01<<(LED2<<1)));

// GPL13 LED4output

rGPLCON &=~(0x03 << (LED4<<1));

rGPLCON |=(0x01 << (LED4<<1));

}

int main()

{

Gpio_Init();

while(1) {

Gpio_LED2(0);

Gpio_LED3(0);

Gpio_LED4(0);

Gpio_LED5(0);

Gpio_LED6(0);

Gpio_LED7(0);

Delay_ms(1000);

Gpio_LED2(1);

Delay_ms(1000);

Gpio_LED2(0);

Gpio_LED3(1);

Delay_ms(1000);

Gpio_LED3(0);

Gpio_LED4(1);

Delay_ms(1000);

Gpio_LED4(0);

Gpio_LED5(1);

Delay_ms(1000);

Gpio_LED5(0);

Gpio_LED6(1);

Delay_ms(1000);

Gpio_LED6(0);

Gpio_LED7(1);

Delay_ms(1000);

Gpio_LED7(0);

}

}

4. 链接文件

链接器可以把多个目标文件及库文件链接成一个可执行的镜像文件。对于arm开发,通常通过链接文件来指定内存中代码和数据的存放位置。MDK用scatter文件作为链接脚本,由于我们需要从sd卡启动,代码是需要在0x40000000的Steppingstone中执行,因此我们设置链接文件链接地址为0x40000000,8k大小,变量放在IRAM 64k的剩余区域。在Linker下取消点取Use Memory Layout from Target Dialog,在Scatter File中点Edit即可编辑分散加载文件了(图4-1)。链接文件设置见图4-2。



图4-1 使用scatter分散加载文件



图4-2 scatter加载文件设置

5. 代码烧写启动

5.1. 打开SdBoot工具,把编译器生成的二进制可执行代码s3c2416_Startup.bin转换成sd卡启动的代码格式,工具生成转换好的二进制可执行代码s3c2416_Startup_1.bin,SdBoot工具使用请参考上一章节。

5.2. 把转换好格式的可执行代码s3c2416_Startup_1.bin通过IROM_Fusing_Tool.exe烧写进sd卡。

5.3. 设置成sd卡启动,即可运行流水灯代码。

附注:

此章节的MDK工程,包含源码。

http://pan.baidu.com/s/1eQzfnME
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: