Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1380274
  • 博文数量: 860
  • 博客积分: 425
  • 博客等级: 下士
  • 技术积分: 1464
  • 用 户 组: 普通用户
  • 注册时间: 2011-08-20 19:57
个人简介

对技术执着

文章分类

全部博文(860)

文章存档

2019年(16)

2018年(12)

2015年(732)

2013年(85)

2012年(15)

我的朋友

分类: 嵌入式

2015-03-14 14:05:38

 摘自:《嵌入式Linux应用开发完全手册》——韦东山

 一、时钟体系和各类时钟部件

1)s3c2440时钟体系

        s3c2440 的时钟控制逻辑既可以外接晶振,然后通过内部电路产生时钟源,也可以直接外接提供的时钟源,它们通过引脚的设置来选择。时钟控制逻辑给整个芯片提供3种时钟:FCLK用于CPU核心,HCLK用于AHB总线上的设备,比如CPU核、存储控制器、中断控制器、LCD控制器、DMA和USB主机模块等,PCLK用于APB总线上的设备,比如watchdog、IIS、I2C、PWM定时器、MMC接口、ADC、UART、GPIO、RTC和SPI

        AHB(Advanced High performance Bus)总线主要用于高性能模块(CPU、DMA、DSP)之间的连接,APB(Advanced Peripheral Bus)总线主要用于低带宽的周边外设之间的连接,如UART、I2C。

        s3c2440的cpu核心工作电压为1.3V时,主频可以达到400Mhz,为了满足板间布线的要求,s3c2440外接的晶振频率通常很低,本开发板为12Mhz,需要通过时钟控制逻辑的PLL提高系统时钟。

        s3c2440有两个PLL:MPLL和UPLL。UPLL专用于USB设备,MPLL专用于设置FCLK,HCLK,PCLK。

        上电时,PLL没有启动,FCLK等于外部输入时钟,成为Fin,如果要提高系统时钟,需要软件来启动PLL。下面来介绍PLL的设置过程

①上电几毫秒后,晶振(OSC)输出稳定,FCLK=Fin(晶振频率),nRESET信号恢复高电平后,CPU开始执行指令

②可以再程序开头启动MPLL,设置MPLL的几个寄存器后,需要等待一段时间,MPLL输出才稳定,这段时间(Locktime),FCLK停诊,CPU停止工作。lock time长短由寄存器LOCKTIME设定

③Lock Time之后,MPLL输出稳定,CPU工作在心的FCLK下。

        FCLK、HCLK、和PCLK比例是可以改变的,设置它们三者的比例,需要一些设置一些寄存器。

LOCKTIME寄存器:用于设置Lock Time的长度,s3c2440中,位[31:16]用于UPLL,位[15:0]用于MPLL,一般而言用默认的0xFFFFFFFF即可

MPLLCON寄存器:用于设置FCLK与Fin的倍数

位[19:12]为MDIV,位[9:4]为PDIV,位[1:0]为SDIV,FCLK与Fin的关系如下

对于s3c2440:MPLL(FCLK)=(2*m*Fin)/(p*2^s)        m=MDIV+8, p=PDIV+2, s=SDIV

当设置MPLLCON之后,在Lock time之后,MPLL输出稳定,CPU工作在心的FCLK下

CLKDIVN寄存器:用于设置FCLK、HCLK、PCLK三者的比例

        HDIVN为CLKDIVN寄存器的位[2:1],PDIVN为CLKDIVN寄存器的位[0]

HCLK4_HALF、HCLK3_HALF分别为CAMDIVN寄存器的位[9]、位[8]

如果HDIVN非0,CPU的总线模式应该从fast bus mode变为asynchronous bus mode,通过下面指令

mrc p15, 0, r0, c1, c0, 0

orr r0, r0, #R1_nF:OR:R1_iA

mcr p15, 0, r0, c1, c0, 0

其中#R1_nF:OR:R1_iA等于0xC0000000。如果CPU的总线模式仍是fast bus mode,则CPU的工作频率将自动变为HCLK,而不是FCLK.

 

2)PWM定时器

S3C2440共有5个16位的定时器,定时器0、1、2、3有PWM功能,即它们都有一个输出引脚,可以通过定时器来控制引脚周期性的高、低电平变化,定时器4没有输出引脚

        定时器部件的时钟源为PCLK,通过两个8位预分频器降低频率:定时器0、1共用第一个分频器,定时器2、3、4共用第二个预分频器。预分频器的输出将进入第二级分频器。预分频器的输出将进入第二级分频器,它们输出5种频率的时钟;2分频、4分频、8分频、16分频或者外部时钟TCLK0/TCLK1,每个定时器的工作时钟可以从这5种频率中选择。

        这两个预分频都可以通过TCFG0寄存器来设置,每个定时器工作在哪种频率下也可以通过TCFG1寄存器来选择。如下图所示:

 定时器内部控制逻辑的工作流程:

①程序初始,设置TCMPBn、TCNTBn这两个寄存器,它们表示定时器n的比较值、初始计数值

②随之设置TCON寄存器启动定时器n,这时,TCMPBn、TCNTBn的值将被装入其内部寄存器TCMPn、TCNTn中。在定时器n的工作频率下,TCNTn开始减一计数,其值可以通过读取TCNTOn寄存器得知。

③当TCNTn的值等于TCMPn的值时,定时器n的输出管脚TOUTn反转;TCNTn继续减一计数

④当TCNTn的值到达0时,其输出管脚TOUTn再次反转,并触发定时器n的中断

⑤当TCNTn的值到达0时,如果在TCON寄存器中将定时器n设为“自动加载”,则TCMPB0和TCNTB0寄存器的值被自动装入TCMP0和TCNT0寄存器中,下一个计数流程开始。

        定时器n的输出管脚TOUTn初始状态为高电平,以后在TCNTn的值等于TCMPn的值、TCNTn的值等于0时反转。

 

定时器的寄存器使用方法:

①TCFG0寄存器

        位[7:0]、位[15:8]分别用于控制预分频器0、1,它们的值为0-255,经过预分频器出来的时钟频率为:PCLK/{prescaler value+1}

②TCFG1寄存器

        经过预分频器得到的时钟将被2分频、4分频、8分频、16分频,除这4种频率外,定时器0、1还可以工作在外接的TCLK0时钟下,定时器2、3、4还可以工作在外接的TCLK1时钟下。

        定时器工作频率=PCLK/{prescaler value+1}/{divider value}

        {prescaler value}= 0-255

        {divider value}=2,4,8,16

TCFG1寄存器格式如下图

③TCNTBn/TCMPBn寄存器

        这两个寄存器都只用到位[15:0],TCNTBn中保存定时器的初始计数值,TCMPBn中保存比较值。它们的值在启动定时器时,被传到定时器内部寄存器TCNTn、TCMPn中。没有TCMPB4,因为定时器4没有输出引脚

④TCNTOn寄存器

        定时器n被启动后,内部寄存器TCNTn在其工作时钟下不断减一计数,可以通过读取TCNTOn寄存器得知其值

⑤TCON寄存器

        TCON0格式如下,TCON1位[11:8],TCON2位[15:12],TCON3位[19:16],TCON4位[22:20]与TCON0类似

功能 设置
开启/停止 0 0:停止定时器0;1:开启定时器0
手动更新 1

0:无用

1:将TCNTBn/TCMPBn寄存器的值装入内部寄存器TCNTn、TCMPn中

输出反转 2 0:TOUT0不反转;1:TOUT0反转
自动加载 3

0:不自动加载

1:在定时器0计数达到0时,TCNTBn/TCMPBn寄存器的值自动装入内部

寄存器TCNTn、TCMPn中

 

3)WATCHDOG定时器

        WATCHDOG 定时器可以像一般的16位定时器一样用于产生周期性的中断,也可以用于发出复位信号以重启失常的系统。它与PWM定时器结构类似。它的8位预分频器将 PCLK分频后,被再次分频得到4中频率,16分频、32分频、64分频、128分频,WATCHDOG定时器可以选择工作于哪种频率之下。WTCNT寄存器按照其工作频率减一计数,当达到0时,可以产生中断信号,可以输出复位信号。在第一次使用WATCHDOG定时器时,需要往WTCNT寄存器写入初始计数值,以后计数值到达0时自动从WATDAT寄存器中装入,重新开始下一个计数周期。

        使用WATCHDOG定时器时,正常程序中,必须不断从新设置WTCNT寄存器使得它不为0,否则系统会不断重启,这成为喂狗,当程序崩溃不能喂狗时,系统将被重启,为了克服各种干扰,避免各类系统错误时系统彻底死机,经常使用WATCHDOG功能

WATCHDOG定时器的寄存器如下:

①WTCON寄存器

        用于设置分频系数、选择工作频率,决定是否使能中断,是否启动WATCHDOG功能,如下图所示

WATDOG定时器工作频率=PCLK/{prescaler value+1}/{divider value}

 

        {prescaler value}= 0-255

        {divider value}=16,32,64,128

②WTDAT寄存器

        WTDAT寄存器用来决定WATCHDOG定时器的超时周期,定时器启动后,当计数达到0时,WTDAT寄存器的值自动传入WTCNT寄存器,不过,第一次启动WATDOG定时器时,WTDAT寄存器的值会自动传入WTCNT寄存器

③WTCNT寄存器

        启动WATDOG前,必须往这个寄存器写入初始计数值。启动定时器后,它减一计数,当计数为0时,如果中断使能的话发出中断,如果WATCHDOG功能被使能的话发出复位信号,装载WTDAT寄存器的值重新计数。

 

二、MPLL和定时器操作实例

 这个实例首先启动MPLL提高系统时钟,初始化存储控制器使SDRAM工作在新的HCLK下,然后将定时器0设为0.5s产生一次中断,在中断程序里改变LED的状态。

首先是Head.S与上一章类似,不做解释

 

@******************************************************************************

@ File:head.S

@ 功能:初始化,设置中断模式、系统模式的栈,设置好中断处理函数

@******************************************************************************       

   

.extern     main

.text 

.global _start 

_start:

@******************************************************************************       

@ 中断向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用

@******************************************************************************       

    b   Reset


@ 0x04: 未定义指令中止模式的向量地址

HandleUndef:

    b   HandleUndef 

 

@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式

HandleSWI:

    b   HandleSWI


@ 0x0c: 指令预取终止导致的异常的向量地址

HandlePrefetchAbort:

    b   HandlePrefetchAbort


@ 0x10: 数据访问终止导致的异常的向量地址

HandleDataAbort:

    b   HandleDataAbort


@ 0x14: 保留

HandleNotUsed:

    b   HandleNotUsed


@ 0x18: 中断模式的向量地址

    b   HandleIRQ


@ 0x1c: 快中断模式的向量地址

HandleFIQ:

    b   HandleFIQ


Reset:                  

    ldr sp, =4096           @ 设置栈指针,以下都是C函数,调用前需要设好栈

    bl  disable_watch_dog   @ 关闭WATCHDOG,否则CPU会不断重启

    bl  clock_init          @ 设置MPLL,改变FCLK、HCLK、PCLK

    bl  memsetup            @ 设置存储控制器以使用SDRAM

    bl  copy_steppingstone_to_sdram     @ 复制代码到SDRAM中

    ldr pc, =on_sdram                   @ 跳到SDRAM中继续执行

on_sdram:

    msr cpsr_c, #0xd2       @ 进入中断模式

    ldr sp, =4096           @ 设置中断模式栈指针


    msr cpsr_c, #0xdf       @ 进入系统模式

    ldr sp, =0x34000000     @ 设置系统模式栈指针,


    bl  init_led            @ 初始化LED的GPIO管脚

    bl  timer0_init         @ 初始化定时器0   

    bl  init_irq            @ 调用中断初始化函数,在init.c中

    msr cpsr_c, #0x5f       @ 设置I-bit=0,开IRQ中断

    

    ldr lr, =halt_loop      @ 设置返回地址

    ldr pc, =main           @ 调用main函数

halt_loop:

    b   halt_loop


HandleIRQ:

    sub lr, lr, #4                  @ 计算返回地址

    stmdb   sp!,    { r0-r12,lr }   @ 保存使用到的寄存器

                                    @ 注意,此时的sp是中断模式的sp

                                    @ 初始值是上面设置的4096

    

    ldr lr, =int_return             @ 设置调用ISR即EINT_Handle函数后的返回地址  

    ldr pc, =Timer0_Handle          @ 调用中断服务函数,在interrupt.c中

int_return:

    ldmia   sp!,    { r0-r12,pc }^  @ 中断返回, ^表示将spsr的值复制到cpsr

    

然后是init.c用于初始化

 

/*

 * init.c: 进行一些初始化

 */ 


#include "s3c24xx.h"

 

void disable_watch_dog(void);

void clock_init(void);

void memsetup(void);

void copy_steppingstone_to_sdram(void);

void init_led(void);

void timer0_init(void);

void init_irq(void);


/*

 * 关闭WATCHDOG,否则CPU会不断重启

 */

void disable_watch_dog(void)

{

    WTCON = 0;  // 关闭WATCHDOG很简单,往这个寄存器写0即可

}

 

#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))

/*

 * 对于MPLLCON寄存器,[19:12]为MDIV,[9:4]为PDIV,[1:0]为SDIV

 * 有如下计算公式:

 *  S3C2410: MPLL(FCLK) = (m * Fin)/(p * 2^s)

 *  S3C2410: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s)

 *  其中: m = MDIV + 8, p = PDIV + 2, s = SDIV

 * 对于本开发板,Fin = 12MHz

 * 设置CLKDIVN,令分频比为:FCLK:HCLK:PCLK=1:2:4,

 * FCLK=200MHz,HCLK=100MHz,PCLK=50MHz

 */

void clock_init(void)

{

    // LOCKTIME = 0x00ffffff;   // 使用默认值即可

    CLKDIVN  = 0x03;            // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1


    /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */

__asm__(

    "mrc    p15, 0, r1, c1, c0, 0\n"        /* 读出控制寄存器 */ 

    "orr    r1, r1, #0xc0000000\n"          /* 设置为“asynchronous bus mode” */

    "mcr    p15, 0, r1, c1, c0, 0\n"        /* 写入控制寄存器 */

    );

        MPLLCON = S3C2440_MPLL_200MHZ;  /* 现在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */


/*

 * 设置存储控制器以使用SDRAM

 */

void memsetup(void)

{

    volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;


    /* 这个函数之所以这样赋值,而不是像前面的实验(比如mmu实验)那样将配置值

     * 写在数组中,是因为要生成”位置无关的代码”,使得这个函数可以在被复制到

     * SDRAM之前就可以在steppingstone中运行

     */

    /* 存储控制器13个寄存器的值 */

    p[0] = 0x22011110;     //BWSCON

    p[1] = 0x00000700;     //BANKCON0

    p[2] = 0x00000700;     //BANKCON1

    p[3] = 0x00000700;     //BANKCON2

    p[4] = 0x00000700;     //BANKCON3  

    p[5] = 0x00000700;     //BANKCON4

    p[6] = 0x00000700;     //BANKCON5

    p[7] = 0x00018005;     //BANKCON6

    p[8] = 0x00018005;     //BANKCON7

    

    /* REFRESH,

     * HCLK=12MHz:  0x008C07A3,

     * HCLK=100MHz: 0x008C04F4

     */ 

    p[9]  = 0x008C04F4;        //2^11+1-100*7.8125=1267.75=0x4F4

    p[10] = 0x000000B1;     //BANKSIZE

    p[11] = 0x00000030;     //MRSRB6

    p[12] = 0x00000030;     //MRSRB7

}


void copy_steppingstone_to_sdram(void)

{

    unsigned int *pdwSrc  = (unsigned int *)0;

    unsigned int *pdwDest = (unsigned int *)0x30000000;

    

    while (pdwSrc < (unsigned int *)4096)

    {

        *pdwDest = *pdwSrc;

        pdwDest++;

        pdwSrc++;

    }

}


/*

 * LED1-4对应GPB5、GPB6、GPB7、GPB8

 */

#define GPB5_out        (1<<(5*2))      // LED1

#define GPB6_out        (1<<(6*2))      // LED2

#define GPB7_out        (1<<(7*2))      // LED3

#define GPB8_out        (1<<(8*2))      // LED4

 

 

void init_led(void)

{

    GPBCON = GPB5_out | GPB6_out | GPB7_out | GPB8_out ;

}


/*

 * Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}

 * {prescaler value} = 0~255

 * {divider value} = 2, 4, 8, 16

 * 本实验的Timer0的时钟频率=100MHz/(99+1)/(16)=62500Hz

 * 设置Timer0 0.5秒钟触发一次中断:

 */

void timer0_init(void)

{

    TCFG0  = 99;        // 预分频器0 = 99        

    TCFG1  = 0x03;      // 选择16分频

    TCNTB0 = 31250;     // 0.5秒钟触发一次中断

    TCON   |= (1<<1);   // 手动更新

    TCON   = 0x09;      // 自动加载,清“手动更新”位,启动定时器0

}


/*

 * 定时器0中断使能

 */ 

void init_irq(void)

{        

    // 定时器0中断使能

    INTMSK   &= (~(1<<10));

}

 

 

interrupt.c中断服务程序
 
#include "s3c24xx.h"

void Timer0_Handle(void)
{
    /*
     * 每次中断令4个LED改变状态
     */
    if(INTOFFSET == 10)
    {
        GPBDAT = ~(GPBDAT & (0xf << 5));
    }
    //清中断
    SRCPND = 1 << INTOFFSET;
    INTPND = INTPND;     
}
 
main.c不做任何事情
int main(void)
{
    while(1);
    return 0;
}
 
time.lds设置段
SECTIONS {
    . = 0x30000000;
    .text          :   { *(.text) }
    .rodata ALIGN(4) : {*(.rodata)} 
    .data ALIGN(4) : { *(.data) }
    .bss ALIGN(4)  : { *(.bss)  *(COMMON) }
}
 
 
最后是Makefile
objs := head.o init.o interrupt.o main.o

timer.bin: $(objs)
arm-linux-ld -Ttimer.lds -o timer_elf $^
arm-linux-objcopy -O binary -S timer_elf $@
arm-linux-objdump -D -m arm timer_elf > timer.dis
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<

%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<

clean:
rm -f timer.bin timer_elf timer.dis *.o
阅读(310) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~