1.启动程序start.S
启动程序相关代码与前一章中断的启动程序共用,所以这里不再重复讲解。
2.系统时钟初始化和打印各时钟的频率
(1)系统时钟初始化
01 #include "uart.h"
02
03 #define APLL_LOCK*((volatile unsigned int *)0xE0100000)
04 #define MPLL_LOCK*((volatile unsigned int *)0xE0100008)
05 #define EPLL_LOCK*((volatile unsigned int *)0xE0100010)
06#define VPLL_LOCK*((volatile unsigned int *)0xE0100020)
07
08 #define APLL_CON0*((volatile unsigned int *)0xE0100100)
09 #define MPLL_CON*((volatile unsigned int *)0xE0100108)
10 #define EPLL_CON0*((volatile unsigned int *)0xE0100110)
11 #define VPLL_CON*((volatile unsigned int *)0xE0100120)
12
13 #define CLK_SRC0*((volatile unsigned int *)0xE0100200)
14 #define CLK_SRC4*((volatile unsigned int *)0xE0100210)
15
16 #define CLK_DIV0*((volatile unsigned int *)0xE0100300)
17 #define CLK_DIV4*((volatile unsigned int *)0xE0100310)
18 /*设置目标时钟频率(手册推荐值):
19* ARMCLK=1000MHz, HCLK_MSYS=200MHz, PCLK_MSYS=100MHz
20* PCLK_PSYS =66.7MHz, HCLK_DSYS=166.75MHz, PCLK_DSYS=83.375MHz,
21* HCLK_PSYS =133.44MHz
22*/
23 void clock_init(void)
24 {
25/* 1.设置PLL锁定值(默认值可以不设置) */
26APLL_LOCK = 0x0FFF;
27MPLL_LOCK = 0x0FFF;
28EPLL_LOCK = 0x0FFF;
29VPLL_LOCK = 0x0FFF;
30/* 2.设置PLL的PMS值(使用手册推荐值),并使能PLL
31*PMSEN*/
32APLL_CON0= (3 <<8)|(125<<16)|(1<<0)|(1<<31);/* FOUT_APLL = 1000MHz */
33MPLL_CON= (12<<8)|(667<<16)|(1<<0)|(1<<31);/* FOUT_MPLL = 667MHz */
34EPLL_CON0 = (3<<8) |(48 <<16)|(2<<0)|(1<<31);/* FOUT_EPLL = 96MHz */
35VPLL_CON= (6<<8) |(108<<16)|(3<<0)|(1<<31);/* FOUT_VPLL = 54MHz */
36/* 3.等待PLL锁定*/
37while (!(APLL_CON0 & (1<<29)));
38while (!(MPLL_CON& (1<<29)));
39while (!(EPLL_CON0 & (1<<29)));
40while (!(VPLL_CON& (1<<29)));
41/* 4.时钟源的设置
42* APLL_SEL[0] :1 = FOUTAPLL
43* MPLL_SEL[4] :1 = FOUTMPLL
44* EPLL_SEL[8] :1 = FOUTEPLL
45* VPLL_SEL[12]:1 = FOUTVPLL
46* MUX_MSYS_SEL[16]:0 = SCLKAPLL
47* MUX_DSYS_SEL[20]:0 = SCLKMPLL
48* MUX_PSYS_SEL[24]:0 = SCLKMPLL
49* ONENAND_SEL [28]:0 = HCLK_PSYS
50*
51* MOUT_MSYS=FOUT_APLL=1000MHz
52* MOUT_DSYS=FOUT_MPLL=667MHz
53* MOUT_PSYS=FOUT_MPLL=667MHz
54*/
55CLK_SRC0 = (1<<0)|(1<<4)|(1<<8)|(1<<12);
56/* 5.设置其他模块的时钟源,CLK_SRC1~6
57*在实际嵌入式系统开发中,还需要为其他功能模块配置时钟,比如UART0
58* MOUTUART0=SCLKMPLL
59*/
60CLK_SRC4 = (6<<16);
61/* 6.设置分频系数
62* APLL_RATIO[2:0]: APLL_RATIO = 0x0 freq(ARMCLK) = MOUT_MSYS / (APLL_RATIO + 1) = 1000MHz
63* A2M_RATIO [6:4]: A2M_RATIO= 0x4 freq(A2M) = SCLKAPLL / (A2M_RATIO + 1) = 200MHz
64* HCLK_MSYS_RATIO[10:8]: HCLK_MSYS_RATIO = 0x4 freq(HCLK_MSYS) = ARMCLK / (HCLK_MSYS_RATIO + 1) = 200MHz
65* PCLK_MSYS_RATIO[14:12]:PCLK_MSYS_RATIO = 0x1 freq(PCLK_MSYS) = HCLK_MSYS / (PCLK_MSYS_RATIO + 1) = 100MHz
66* HCLK_DSYS_RATIO[19:16]:HCLK_DSYS_RATIO = 0x3 freq(HCLK_DSYS) = MOUT_DSYS / (HCLK_DSYS_RATIO + 1) = 166.75MHz
67* PCLK_DSYS_RATIO[22:20]:PCLK_DSYS_RATIO = 0x1 freq(PCLK_DSYS) = HCLK_DSYS / (PCLK_DSYS_RATIO + 1) = 83.375MHz
68* HCLK_PSYS_RATIO[27:24]:HCLK_PSYS_RATIO = 0x4 freq(HCLK_PSYS) = MOUT_PSYS / (HCLK_PSYS_RATIO + 1) = 133.44MHz
69* PCLK_PSYS_RATIO[30:28]:PCLK_PSYS_RATIO = 0x1 freq(PCLK_PSYS) = HCLK_PSYS / (PCLK_PSYS_RATIO + 1) = 66.7MHz
70*/
71CLK_DIV0 = (1<<28)|(4<<24)|(1<<20)|(3<<16)|(1<<12)|(4<<8)|(4<<4);
72/* 7.设置其他模块的时钟分频值CLK_DIV1~7
73* UART0_RATIO = 0x9 SCLK_UART0=MOUTUART0/(UART0_RATIO + 1)=667/(9+1)=66.7MHz
74*/
75CLK_DIV4 = (9<<16);
76 }
(2)打印各时钟的频率值
01 /*计算x的y次方*/
02 volatile unsigned int pow(volatile unsigned int x, volatile unsigned char y)
03 {
04if (y == 0)
05x = 1;
06else
07{
08y--;
09while (y--)
10x *= x;
11}
12return x;
13 }
14 void raise(int signum)
15 {
16 }
17 /*打印时钟信息
18*外部晶振FINPLL=24MHz*/
19 void print_clockinfo(void)
20 {
21volatile unsigned short p, m, s, k;
22volatile unsigned int SCLKAPLL, SCLKMPLL, SCLKEPLL, SCLKVPLL, MHz;
23volatile unsigned int MOUT_MSYS, MOUT_DSYS, MOUT_PSYS, MOUT_UART0;
24volatile unsigned char APLL_RATIO, A2M_RATIO, HCLK_MSYS_RATIO, PCLK_MSYS_RATIO, HCLK_DSYS_RATIO,
25PCLK_DSYS_RATIO, HCLK_PSYS_RATIO, PCLK_PSYS_RATIO,UART0_RATIO;
26APLL_RATIO = (CLK_DIV0 >> 0)& 0x7;
27A2M_RATIO= (CLK_DIV0 >> 4) & 0x7;
28HCLK_MSYS_RATIO= (CLK_DIV0 >> 8) & 0x7;
29PCLK_MSYS_RATIO= (CLK_DIV0 >> 12) & 0x7;
30HCLK_DSYS_RATIO= (CLK_DIV0 >> 16) & 0x7;
31PCLK_DSYS_RATIO= (CLK_DIV0 >> 20) & 0x7;
32HCLK_PSYS_RATIO= (CLK_DIV0 >> 24) & 0x7;
33PCLK_PSYS_RATIO= (CLK_DIV0 >> 28) & 0x7;
34UART0_RATIO= (CLK_DIV4 >> 16) & 0xF;
35
36if (CLK_SRC0 & 0x1)
37{
38p = (APLL_CON0 >> 8)& 0x3F;
39m = (APLL_CON0 >> 16) & 0x3FF;
40s = (APLL_CON0 >> 0)& 0x7;
41SCLKAPLL = m * 24 / (p * pow(2, s - 1));/* FOUT_APLL = MDIV X FIN / (PDIV ×2^(SDIV-1)) */
42}
43else
44SCLKAPLL = 24;
45
46if (CLK_SRC0 & (1 << 4))
47{
48p = (MPLL_CON >> 8)& 0x3F;
49m = (MPLL_CON >> 16) & 0x3FF;
50s = (MPLL_CON >> 0)& 0x7;
51SCLKMPLL = m * 24 / (p * pow(2, s));/* FOUT_MPLL = MDIV X FIN / (PDIV ×2^SDIV) */
52}
53else
54SCLKMPLL = 24;
55
56if (CLK_SRC0 & (1 << 8))
57{
58p = (EPLL_CON0 >> 8)& 0x3F;
59m = (EPLL_CON0 >> 16) & 0x1FF;
60s = (EPLL_CON0 >> 0)& 0x7;
61k = EPLL_CON1;
62SCLKEPLL = (m + k / 65536) * 24 / (p * pow(2, s));/* FOUT_EPLL = (MDIV + K / 65536) x FIN / (PDIV x 2^SDIV) */
63}
64else
65SCLKEPLL = 24;
66
67if (CLK_SRC0 & (1 << 12))
68{
69p = (VPLL_CON >> 8)& 0x3F;
70m = (VPLL_CON >> 16) & 0x1FF;
71s = (VPLL_CON >> 0)& 0x7;
72SCLKVPLL = m * 24 / (p * pow(2, s));/* FOUT_VPLL = MDIV X FIN / (PDIV ×2^SDIV) */
73}
74else
75SCLKVPLL = 24;
76
77if (CLK_SRC0 & (1 << 16))
78MOUT_MSYS = SCLKMPLL;
79else
80MOUT_MSYS = SCLKAPLL;
81
82if (CLK_SRC0 & (1 << 20))
83MOUT_DSYS = SCLKAPLL / (A2M_RATIO + 1);
84else
85MOUT_DSYS = SCLKMPLL;
86
87if (CLK_SRC0 & (1 << 24))
88MOUT_PSYS = SCLKAPLL / (A2M_RATIO + 1);
89else
90MOUT_PSYS = SCLKMPLL;
91
92if ( (((CLK_SRC4 & (6 << 16))>>16)&0xF) == 0x6)
93MOUT_UART0 = SCLKMPLL;
94else
95MOUT_UART0 = 66;
96
97MHz = MOUT_MSYS / (APLL_RATIO + 1);
98printf("ARMCLK= %d MHz\r\n", MHz);
99MHz = SCLKAPLL / (A2M_RATIO + 1);
100printf("SCLKA2M= %d MHz\r\n", MHz);
101MHz /= (HCLK_MSYS_RATIO + 1);
102printf("HCLK_MSYS= %d MHz\r\n", MHz);
103MHz /= (PCLK_MSYS_RATIO + 1);
104printf("PCLK_MSYS= %d MHz\r\n", MHz);
105MHz = MOUT_DSYS / (HCLK_DSYS_RATIO + 1);
106printf("HCLK_DSYS= %d MHz\r\n", MHz);
107MHz /= (PCLK_DSYS_RATIO + 1);
108printf("PCLK_DSYS= %d MHz\r\n", MHz);
109MHz = MOUT_PSYS / (HCLK_PSYS_RATIO + 1);
110printf("HCLK_PSYS= %d MHz\r\n", MHz);
111printf("PCLK_PSYS= %d MHz\r\n", MHz /(PCLK_PSYS_RATIO + 1));
112printf("SCLKEPLL= %d MHz\r\n", SCLKEPLL);
113MHz = MOUT_UART0 / (UART0_RATIO + 1);
114printf("SCLK_UART0= %d MHz\r\n", MHz);
115 }
第01~13行用于计算一个数x的y次方根;第14~16行是一个空函数raise,这个函数是为编译器所定义的,在后面Makefile部分再作详细介绍;第17~115行用于打印系统时钟的频率,其中第26~35行从CLK_DIV0和CLK_DIV4寄存器中读取各时钟的分频系数值,第36~96行用于计算各时钟源的频率,第97~115行将计算出来的时钟频率通过串口输出,这里面用到了浮点除法运算。
3.定时器Timer0初始化
01 #defineTCFG0(*(volatile unsigned int *)0xE2500000)
02 #defineTCFG1(*(volatile unsigned int *)0xE2500004)
03 #defineTCON(*(volatile unsigned int *)0xE2500008)
04 #defineTCNTB0(*(volatile unsigned int *)0xE250000C)
05 #defineTCMPB0(*(volatile unsigned int *)0xE2500010)
06/*
07* Timer input clock Frequency = PCLK / {prescaler value+1} / {pider value}
08* {prescaler value} = 1~255
09* {pider value} = 1, 2, 4, 8, 16, TCLK
10*本实验的Timer0的时钟频率=66.7MHz/(65+1)/(16)=63162Hz(即1s计数63162次)
11*设置Timer0 1秒钟触发一次中断:
12*/
13 void timer0_init(void)
14 {
15TCNTB0 = 63162;/* 1秒钟触发一次中断*/
16TCMPB0 = 31581;/* PWM占空比=50% */
17TCFG0 |= 65;/* timer 0 Prescaler value = 65 */
18TCFG1= 0x04;/*选择16分频*/
19TCON|= (1<<1);/*手动更新*/
20TCON= 0x09;/*自动加载,清“手动更新”位,启动定时器0 */
21 }
4.定时器中断初始化
01 #define TINT_CSTAT*((volatile unsigned int *)0xE2500044)
02
03 #defineVIC0IRQSTATUS*((volatile unsigned int *)0xF2000000)
04 #define VIC0INTSELECT*((volatile unsigned int *)0xF200000C)
05 #define VIC0INTENABLE*((volatile unsigned int *)0xF2000010)
06 #define VIC0VECTADDR21*((volatile unsigned int *)0xF2000154)
07 #define VIC0ADDRESS*((volatile unsigned int *)0xF2000F00)
08
09 extern void IRQ_handle(void);
10
11 //使能TIMER0中断
12 void init_irq(void)
13 {
14TINT_CSTAT |= 1;
15 }
16 //清中断
17 void clear_irq(void)
18 {
19TINT_CSTAT |= (1<<5);
20 }
21 //初始化中断控制器
22 void init_int(void)
23 {
24//选择中断类型为IRQ
25VIC0INTSELECT |= ~(1<<21); // TIMER0中断为IRQ
26//清VIC0ADDRESS
27VIC0ADDRESS = 0x0;
28//设置TIMER0中断对应的中断服务程序的入口地址
29VIC0VECTADDR21 = (int)IRQ_handle;
30//使能TIMER0中断
31VIC0INTENABLE |= (1<<21);
32 }
33 //清除需要处理的中断的中断处理函数的地址
34 void clear_vectaddr(void)
35 {
36VIC0ADDRESS = 0x0;
37 }
38 //读中断状态
39 unsigned long get_irqstatus(void)
40 {
41return VIC0IRQSTATUS;
42 }
5.格式化输出printf实现
在实验中需要通过串口输出时钟频率信息,需要用到C库中的printf函数,但不想调用此库,所以我们需要实现printf这个函数,代码如下。
/*打印整数v到终端*/
01 void put_int(volatile unsigned int v)
02 {
03int i;
04volatile unsigned char a[10];
05volatile unsigned char cnt = 0;
06
07if (v == 0)
08{
09putc('0');
10return;
11}
12
13while (v)
14{
15a[cnt++] = v % 10;
16v /= 10;
17}
18
19for (i = cnt - 1; i >= 0; i--)
20putc(a[i] + 0x30); //整数0-9的ASCII分别为0x30-0x39
21 }
22 /*格式化输出到终端*/
24 23 int printf(const char *fmt, ...)
25 {
26va_list ap;
27char c;
28char *s;
29volatile unsigned int d;
30volatile unsigned char lower;
31
32va_start(ap, fmt);
33while (*fmt)
34{
35lower = 0;
36c = *fmt++;
37if (c == '%')
38{
39switch (*fmt++)
40{
41case 'c':/* char */
42c = (char) va_arg(ap, int);
43putc(c);
44break;
45case 's':/* string */
46s = va_arg(ap, char *);
47puts(s);
48break;
49case 'd':/* int */
50case 'u':
51d = va_arg(ap, int);
52put_int(d);
53break;
54}
55}
56else
57putc(c);
58}
59va_end(ap);
60 }
以上printf只支持单字符型、字符串、整型的格式化输出,其他格式的输出需要时再补充。
6.中断服务程序和主程序
(1)中断服务程序
01 // TIMER0中断服务程序(ISR)
02 void irq_handler()
03 {
04volatile unsigned char status = ((get_irqstatus() & (1<<21))>>21)&0x1;// TIMER0's int status
05clear_vectaddr();/*清中断向量寄存器*/
06clear_irq();/*清timer0中断*/
07if (status == 0x1)
08{
09leds_on_off();
10}
11 }
(2)主程序
01 int main(void)
02 {
03int c = 0;
04
05init_leds();/*初始化GPIO引脚*/
06uart0_init();/*初始化UART0 */
07timer0_init();/*初始化Timer0 */
08
09puts("########## Before Init System Clock ##########\r\n");
10print_clockinfo();
11
12clock_init();/*初始化系统时钟*/
13uart0_init();/*初始化UART0 */
14
15puts("########## After Init System Clock ##########\r\n");
16print_clockinfo();
17
18init_irq();/*使能TIMER0中断*/
19init_int();/*初始化中断控制器、使能中断*/20
20
21while (1);
22 }
7.Makefile
01 objs := start.o main.o clock.o uart.o int.o timer.o
02 LDFLAGS=-lgcc -L/opt/tools/crosstool/arm-cortex_a8-linux-gnueabi/lib/gcc/arm-cortex_
a8-linux-gnueabi/4.4.6/
03
04 clock_timer.bin: $(objs)
05arm-linux-ld -Ttext 0xD0020010 -o clock_timer.elf $^ $(LDFLAGS)
06arm-linux-objcopy -O binary -S clock_timer.elf $@
07arm-linux-objdump -D clock_timer.elf > clock_timer.dis
08
09 %.o : %.c
10arm-linux-gcc -c -O2 -fno-builtin $< -o $@
11
12 %.o : %.S
13arm-linux-gcc -c -O2 $< -o $@
14
15 clean:
16rm -f *.o *.elf *.bin *.dis
17
在本章的实验中,用到了浮点除法运算,而前面在制作交叉工具链时,我们选择的浮点类型是软浮点,并不是硬件浮点,所以如果在Makefile中没有将浮点运算相关的库包含进来编译,编译时会报如下错误:
undefined reference to `__aeabi_uip'
要解决这个编译错误,必须将浮点运算相关的库包含进来,对应的库为libgcc.a的静态库,在本书交叉工具链中,此库的路径为“/opt/tools/crosstool/arm-cortex_a8-linux-gnueabi/lib/
gcc/arm-cortex_a8-linux-gnueabi/4.4.6”,所以在链接时要将此库链接到ELF文件中,具体定义如上述Makefile所示。在Makefile中将浮点运算的库链接进来还是不够的,编译器还会去找一个名叫raise的函数,这个函数在Linux内核中是一个系统函数,但在uboot中是没有这个函数的,所以我们定义一个空的raise函数“欺骗”下编译器,这样我们就可以成功编译。最后在补充说明下,这样的编译问题在编译linux内核时是不是也会发生,这里说下是不会的,因为在内核中这些库事先都包含好了。
Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE MicrosoftInternetExplorer4
/* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:宋体; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-fareast-font-family:宋体; mso-fareast-theme-font:minor-fareast; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt;}
将所有源代码传到宿主机上编译生成clock_timer.bin,将bin文件通过SD卡烧写到开发板上运行,可以看到LED1和LED2每隔1秒亮一次,这就是定时器中断的功能。另外通过串口输出系统时钟配置前后各自的频率值,如下所示:
########## Before Init System Clock ##########
ARMCLK= 400 MHz
SCLKA2M= 133 MHz
HCLK_MSYS= 44 MHz
PCLK_MSYS= 22 MHz
HCLK_DSYS= 133 MHz
PCLK_DSYS= 66 MHz
HCLK_PSYS= 133 MHz
PCLK_PSYS= 66 MHz
SCLKEPLL= 40 MHz
SCLK_UART0= 66 MHz
########## After Init System Clock ##########
ARMCLK= 1000 MHz
SCLKA2M= 200 MHz
HCLK_MSYS= 40 MHz
PCLK_MSYS= 20 MHz
HCLK_DSYS= 166 MHz
PCLK_DSYS= 83 MHz
HCLK_PSYS= 133 MHz
PCLK_PSYS= 66 MHz
SCLKEPLL= 96 MHz
SCLK_UART0= 66 MHz
其中配置前的时钟是系统默认的时钟,即iROM中相关的程序设置的时钟频率,配置后的时钟是本书参考S5PV210手册推荐的频率值设置的,有兴趣读者可以配置其他的时钟频率试试,不过不要超出手册上建议的范围即可。
/* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:宋体; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-fareast-font-family:宋体; mso-fareast-theme-font:minor-fareast; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt;}