ok6410linux开发环境搭建,飞凌嵌入式知识汇021期:OK6410裸机程序之开始模板(Linux环境)...
手中有OK6410開發板,一直想試試通過Linux來做做裸機開發,在網絡論壇上也搜過一些資料作參考,整理了一下并做了解釋或改動,希望這些東西可以大家分享下。
裸機程序的構成
基本的裸機程序由啟動代碼和C函數文件構成。而啟動代碼包括:硬件設備初始化、調用C函數。
本次分析中代碼文件有:
start.S?匯編寫的啟動代碼
commom.h?一些通用的函數
irq.c?中斷初始化,中斷處理等
regs.h
s3c6410的寄存器,用到的寄存器在本文件里聲明/定義
sdram.c?關于sdram內存初始化等操作
time.c?對于系統時鐘的設置,如鎖相環等
main.c?這個就是主函數了,主程序就在這編寫,這里可以寫一個流水燈程序
main.lds?該文件為鏈接腳本,描述了各個輸入文件的各個section怎樣映射到輸出文件的各個section中,還控制輸出文件中section和符號等的內存布局。
Makefile?makefile文件
1、 學習啟動代碼有助于我們以后開發uboot,uboot的啟動代碼跟裸機的相近。
下面把start.S代碼貼出來,其中代碼中也有注釋。
@**************************************
@ File: start.S
@ Function: cpu initial and jump to c program
@**************************************
.extern main
.text
.global _start
_start:
b?reset?@
when reset, cpu jump to 0 address
b?halt?@ldr?pc,
_undefined_instruction
b?halt?@ldr?pc,
_software_interrupt
b?halt?@ldr?pc,
_prefetch_abort
b?halt?@ldr?pc,
_data_abort
b?halt?@ldr?pc,
_not_used
ldr?pc, _irq
b?halt?@ldr?pc,
_fiq
_irq:
.word vector_irq
vector_irq:
ldr?sp, =
0x54000000?@ save location
sub?lr, lr, #4
stmdb?sp!, {r0-r12, lr}
bl?do_irq?@
deal with exception
@ backing out
ldmia?sp!, {r0-r12, pc}^
reset:
ldr?r0, =
0x70000000?@ Peripheral port base address
orr?r0, r0, #0x13
mcr?p15,0,r0,c15,c2,4?@
256M
ldr?r0, =
0x7e004000?@ watchdog register address
mov?r1, #0x0
str?r1,
[r0]?@ write 0, disable
watchdog
ldr?sp,
=1024*8?@ set stack, notice:
can't larger than 8K
bl?clock_init?;初始化系統時鐘
bl?sdram_init?;初始化SDRAM
adr?r0,
_start?@ get _start's current
address: 0
ldr?r1, =
_start?@ _start's link
address
ldr?r2, =
bss_start?@ bss section's begining link
address
cmp?r0, r1
beq?clean_bss?;重定位
copy_loop:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r1, r2
bne copy_loop
clean_bss:
ldr?r0, = bss_start
ldr?r1, = bss_end
mov?r3, #0
cmp?r0, r1
ldreq?pc, =
on_ddr?;清BSS段
clean_loop:
str?r3, [r0], #4
cmp?r0, r1
bne?clean_loop
ldr?pc, = on_ddr
on_ddr:
bl?irq_init?@
initial IRQ
@mrs?r0, cpsr
bic?r0, r0, #0x9f
orr?r0, r0, #0x10
msr?cpsr,
r0?@
enter user mode
ldr?sp, = 0x57000000
@bl?main?@
call c program's main function
ldr pc, =
main?;調用C函數
halt:
b?halt
啟動代碼的一般流程如下:
. 硬件相關設置:把外設基地址告訴CPU(ARM11特用)
(分析:ldr r0, = 0x70000000
其中0x70000000是外設的基地址,從6410的datasheet的第二章存儲器映射一章可以找到
orr r0, r0, #0x13
指r0中的值是256,代表256M,這是ARM11規定的,具體在ARM11datasheet中)
. 關看門狗
(分析:ldr r0, = 0x7e004000 加載地址0x7e004000上的數據放入r0中
mov r1, #0x0
str r1, [r0] 將r1中的數據存儲到r0指向的存儲單元中——把看門狗寄存器寫0)
.設置堆棧(后面要調用c函數,調用函數就要先設置棧,片內8K內存)
.初始化時鐘
.初始化SDRAM
.重定位
.清BSS段
.調用C函數
2、 commom.h共用的頭文件
里面編寫了一些方便的函數,都是對寄存器的某位或多位進行操作的函數,縮短我們寫代碼的時間,下面貼出來,可以自己分析下。
#ifndef __COMMON_H
#define __COMMON_H
#define vi *( volatile unsigned int *
)
#define set_zero( addr, bit ) ( (vi
addr) &= ( ~ ( 1 <<
(bit) ) ) )
#define set_one( addr, bit ) ( (vi addr) |= ( 1
<< ( bit ) ) )
#define set_bit( addr, bit, val ) ( (vi
addr) = (( vi
addr)&=(~(1<
) | ( (val)<
#define set_2bit( addr, bit, val ) (
(vi addr) = (( vi
addr)&(~(3<
| ( (val)<
#define set_nbit( addr, bit,
len,?val ) \
( (vi addr) = ((( vi addr)&(~((
((1<
)<
(val)<
#define get_bit( addr, bit ) ( (( vi
addr ) & ( 1 << (bit)
)) > 0?)
#define get_val( addr, val ) ( (val) =
vi addr )
#define read_val( addr ) ( vi ( addr ) )
#define set_val( addr, val ) ( (vi addr) = (val) )
#define or_val( addr, val ) ( (vi addr) |= (val) )
///
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
// function declare
int delay( int );
#endif
3、 irq.c
這是一個中斷初始化和中斷處理函數文件。
下面對ARM異常進行介紹:
中斷也是異常的一種,ARM處理器用7中工作模式:
(1)用戶模式(user)?usr?正常的執行模式
(2)快速中斷模式(FIQ)?fiq?高優先級中斷產生進入的模式(高速數據傳輸等情況使用)
(3)外部中斷模式(IRQ)?irq?低優先級中斷產生進入的模式(一般的外部中斷)
(4)特權模式(Superviser)?svc?復位或軟中斷,供操作系統使用的保護模式
(5)數據訪問中止模式(Abort)?abt?存取數據異常,用于虛擬存儲或存儲保護
(6)未定義指令模式(undefine)und?當執行未定義指令時進入的模式
(7)系統模式(system)?sys?運行特權級操作系統任務
中斷過程:
(1)系統上電,CPU處于svc模式
(2)如果發生中斷,那么CPU進入IRQ模式;R13、R14切換到自己的R13、R14;跳到相應的中斷向量地址
如何進行中斷編程?
(1)中斷初始化
a. 設置中斷源(也就是配置引腳模式)
b. 設置中斷控制器(參照6410datasheet中斷控制器一章)
c. 打開總中斷開關(設置CPSR)
4、 regs.h
這是一個6410中聲明和定義寄存器的,需要哪個寄存器就在該文件當中定義,在后面的文件當中直接調用即可。
5、 sdram.c
下面我們來看一下6410核心板DDR的原理圖
從圖中我們可以看到,該核心板有兩個DDR級聯而得,每個有16位,兩個一共32位。每個DDR有15根地址線,而2^15=32K,內存芯片K4X1G163PC的datasheet可以看出為每片64M*16
= 1G bit,15根地址線必然不夠,所以地址應該是分兩次發出來的。
BA0和BA1可以訪問4塊bank,而它提供13條行地址和10條列地址。而6410提供了DDR控制器,只要控制DDR控制器即可。關于初始化DDR:
(1)地址線設置
(2)告訴位寬
(3)設置時序
可參照s3c6410的datasheet:設置DDR控制器
初始化DDR芯片,
參照代碼,初始化DDR的順序
#include "common.h"
#define
MEMCCMD?0x7e001004
#define P1REFRESH?0x7e001010
#define P1CASLAT?0x7e001014
#define MEM_SYS_CFG?0x7e00f120
#define P1MEMCFG?0x7e00100c
#define P1T_DQSS?0x7e001018
#define P1T_MRD?0x7e00101c
#define P1T_RAS?0x7e001020
#define P1T_RC?0x7e001024
#define P1T_RCD?0x7e001028
#define P1T_RFC?0x7e00102c
#define P1T_RP?0x7e001030
#define P1T_RRD?0x7e001034
#define P1T_WR?0x7e001038
#define P1T_WTR?0x7e00103c
#define P1T_XP?0x7e001040
#define P1T_XSR?0x7e001044
#define P1T_ESR?0x7e001048
#define P1MEMCFG2?0X7e00104c
#define P1_chip_0_cfg?0x7e001200
#define
P1MEMSTAT?0x7e001000
#define P1MEMCCMD?0x7e001004
#define P1DIRECTCMD?0x7e001008
#define HCLK?133000000
#define
nstoclk(ns)?(ns/( 1000000000/HCLK)+1)
void sdram_init( void )
{
// tell dramc to configure
set_val(MEMCCMD, 0x4 );
// set refresh
period
set_val( P1REFRESH, nstoclk(7800) );
// set timing
para
set_val( P1CASLAT, ( 3
<< 1 ) );?set_val( P1T_DQSS, 0x1 );?//
0.75 - 1.25
set_val( P1T_MRD, 0x2 );
set_val( P1T_RAS, nstoclk(45) );
set_val( P1T_RC, nstoclk(68) );
u32 trcd = nstoclk(
23 );
set_val( P1T_RCD, trcd | (( trcd - 3 )
<< 3 ) );
u32 trfc = nstoclk( 80 );
set_val( P1T_RFC, trfc | ( ( trfc-3 )
<< 5 )
);?u32 trp = nstoclk( 23 );
set_val( P1T_RP, trp | ( ( trp - 3 )
<< 3 ) );
set_val( P1T_RRD, nstoclk(15) );
set_val( P1T_WR, nstoclk(15) );
set_val( P1T_WTR, 0x7 );
set_val( P1T_XP, 0x2 );
set_val( P1T_XSR, nstoclk(120) );
set_val( P1T_ESR, nstoclk(120) );
// set mem cfg
set_nbit( P1MEMCFG, 0, 3, 0x2
);
set_nbit( P1MEMCFG, 3, 3, 0x2
);?set_zero( P1MEMCFG, 6
);?set_nbit( P1MEMCFG, 15, 3, 0x2 );
set_nbit( P1MEMCFG2,
0, 4, 0x5 );
set_2bit( P1MEMCFG2, 6, 0x1
);?set_nbit( P1MEMCFG2, 8, 3, 0x3 );
set_2bit( P1MEMCFG2, 11, 0x1 );
set_one(
P1_chip_0_cfg, 16 );
// memory 初始化
set_val( P1DIRECTCMD, 0xc0000 ); // NOP
set_val( P1DIRECTCMD, 0x000 );?// 預充電
set_val( P1DIRECTCMD, 0x40000 );// 自刷新
set_val( P1DIRECTCMD, 0x40000 );// 自刷新
set_val( P1DIRECTCMD, 0xa0000 ); // EMRS
set_val( P1DIRECTCMD, 0x80032 ); // MRS
set_val( MEM_SYS_CFG,
0x0 );
//
設置內存控制器為"運行"狀態?set_val( P1MEMCCMD, 0x000 );
// wait ready
while( !(( read_val( P1MEMSTAT )
& 0x3 ) == 0x1));
}
6、 time.c
對系統時鐘進行初始化設置,而對于6410的晶振是12M,需要通過一系列的變頻,分頻來產生500~600M的時鐘。對時鐘t進行設置需要參照6410的datasheet中的系統控制器一章的時鐘體系。
初始化設置系統時鐘,無非就是對相應的寄存器進行設置,設置分頻等,下面說幾個知識點:
圖中ARMCLK是ARM11的CPU時鐘,一般設置為532MHz
HCLK為133MHz,一般為NandFlash和DDR提供時鐘,PCLK為67MHz
SCLK為某些特殊設備提供時鐘
當系統上電,晶振開始起振,不可能一下子從12M就變為532M,需要一段緩沖的時間,這段時間稱為LOCKTIME,如下圖所示:
#define APLL_LOCK (*((volatile unsigned long *)0x7E00F000))
#define MPLL_LOCK (*((volatile unsigned long *)0x7E00F004))
#define EPLL_LOCK (*((volatile unsigned long *)0x7E00F008))
#define
OTHERS?(*((volatile unsigned long *)0x7e00f900))
#define CLK_DIV0?(*((volatile unsigned long *)0x7E00F020))
#define
ARM_RATIO?0?#define HCLKX2_RATIO 4?#define HCLK_RATIO?0?#define PCLK_RATIO?1?#define MPLL_RATIO?0
#define APLL_CON?(*((volatile unsigned long
*)0x7E00F00C))
#define APLL_CON_VAL?((1<<31) | (266
<< 16) | (3
<< 8) | (1))
#define MPLL_CON?(*((volatile unsigned long *)0x7E00F010))
#define MPLL_CON_VAL?((1<<31) | (266
<< 16) | (3
<< 8) | (1))
#define CLK_SRC?(*((volatile unsigned long *)0x7E00F01C))
void clock_init(void)
{
APLL_LOCK = 0xffff;
MPLL_LOCK = 0xffff;
EPLL_LOCK = 0xffff;
OTHERS &= ~(0xc0);
while((OTHERS & 0xf00) != 0);
CLK_DIV0 =
(ARM_RATIO) | (MPLL_RATIO << 4)
| (HCLK_RATIO
<< 8) | (HCLKX2_RATIO
<< 9)
| (PCLK_RATIO
<< 12);
APLL_CON = APLL_CON_VAL;?MPLL_CON = MPLL_CON_VAL;
CLK_SRC = 0x03;
}
下面對時鐘設置步驟進行說明:
(1)設置LockTime,包括APLL_LOCK,MPLL_LOCK,EPLL_LOCK,一般設置為默認值即可,也可以不用設置,因為它復位后即為默認值。
(2)設置為異步模式,當CPU時鐘和內存時鐘不相等的時候,需要設置為異步模式,主要是設置寄存器OTHERS,然后在查詢相對應位是否為0,一直等待設置完畢。
(3)然后沿著上圖的時鐘體系設置PLL寄存器的值
7、 main.c
主函數文件可以自己去寫,實現何種功能由C來完成
8、?main.lds
該裸機程序的鏈接腳本
SECTIONS {
. =
0x50000000;?//當前地址
. = ALIGN(4);
.text?:
{?//段名稱,放置所有文件的代碼段
start.o (.text)
time.o (.text)
irq.o (.text)
led.o (.text)
}
. =
ALIGN(4);?//4位對齊
.rodata?: {
* (.rodata)
}
. = ALIGN(4);
.data?: {
* (.data)
}
. = ALIGN(4);
bss_start =
.;?//bss段開始處
.bss?:
{?//放置所用bss段
* (.bss)
}
bss_end =
.;?//bss段結束處
}
以前寫簡單的程序,可以不用DDR,只將程序放在6410的8K片內ram運行即可,但是程序很大時,那就難以在片內ram中運行程序了。就需要用到SDRAM,這就得涉及到鏈接地址。
一個程序可分為下面幾個部分:
(1)代碼段(text):就是我們寫的代碼,指令
(2)數據段(data):有初始值的全局變量或靜態變量
(3)Bss段(Bss):未初始化或初始值為0的全局變量或靜態變量
分析反匯編文件我們得出:訪問全局變量使用的是鏈接地址來訪問的。在系統上電后,系統會自動的把NandFlash中的前8K程序拷貝到片內8K內存當中去,而一個程序要執行,應該位于鏈接地址。當程序的鏈接地址不等于當前地址時,就需要重定位,將程序拷貝到相應的鏈接地址中去執行。
位置無關碼:相對跳轉指令,不訪問全局變量。下面看一下重定位代碼:
adr?r0,
_start?;獲得_start的當前地址:
0
ldr?r1, =
_start?; _start的鏈接地址
ldr?r2, =
bss_start?; bss 段的氣質鏈接地址
cmp?r0,
r1?; compare isnot equal
beq?clean_bss
copy_loop:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r1, r2
bne copy_loop
clean_bss:
ldr?r0, = bss_start
ldr?r1, = bss_end
mov?r3, #0
cmp?r0, r1
ldreq?pc, = on_ddr
clean_loop:
str?r3, [r0], #4
cmp?r0, r1
bne?clean_loop
ldr?pc, = on_ddr
在分析過程中,我們可參照反匯編文件來分析
9、 Makefile
在Linux下開發,了解Makefile也是很有必要,下面是Makefile代碼:
CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
CFLAGS = -Wall -Os -fno-builtin-printf
export CC LD AR OBJCOPY OBJDUMP
CFLAGS
objs := start.o time.o sdram.o irq.o
main.o
led.bin : $(objs)
$(LD) –Tmain.lds -o main_elf $^
$(OBJCOPY) -O binary -S main_elf $@
$(OBJDUMP) -D -m arm main_elf >
main.dis
%.o : %.c?$(CC) $(CFLAGS) -c -o $@
$<
%.o : %.S
$(CC) $(CFLAGS) -c -o $@
$<
clean:
rm -f *.dis *.bin *_elf *.o
對于Makefile的理解學習可以在網上搜索一下《跟我一起寫 Makefile》這個文檔,看著寫的不錯。
歡迎關注飛凌嵌入式官方微信:
總結
以上是生活随笔為你收集整理的ok6410linux开发环境搭建,飞凌嵌入式知识汇021期:OK6410裸机程序之开始模板(Linux环境)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python - 接入钉钉机器人
- 下一篇: 在Linux内核层面集成图形界面,技德操