PIC振荡器配置与时钟切换

单片机编程就是C语言+振荡器配置+寄存器设置。
以前对PIC振荡器的配置都是拿来主义,把别人的代码拿过来用就行了。这两天特意研究下振荡器的配置与时钟切换。在mplab IDE和C30编译器下,针对PIC24FJxx系列单片机完成的测试。
配置振荡器最主要的目的就是为了设置机械时钟Fosc,此时钟给CPU和外设提供时钟源。但为了降低功耗又不中断外设正常通信,此系列PIC保证CPU与外设的时钟同步情况下,增加了打盹模式,用于降低CPU运行时钟速度,以达到节能效果。
个人是这么理解的,CPU时钟就是代码运行时钟,决定代码运行速度;外设时钟就是中断、定时器、输入捕捉、输出比较、UART、SPI等外设的时钟源。

在时钟框图中可看出,由四个振荡器提供时钟源,包括两个外部振荡器(主振荡器、辅助振荡器)和两个内部振荡器(FRC振荡器——快速RC、LPRC振荡器——低功耗RC)。

时钟模式共11种:

1. XT——1M到4M的石英晶体振荡器(主振荡器)

2. HS——超过4M的石英晶体振荡器(主振荡器)

3. EC——低于1M的陶瓷振荡器(主振荡器)

4. XTPLL——带PLL模块的主振荡器(主振荡器)

5. HSPLL——带PLL模块的主振荡器(主振荡器)

6. ECPLL——带PLL模块的主振荡器(主振荡器)

7. FRCPLL——带后分频器和PLL模块的快速RC振荡器(内部振荡器)

8. FRCDIV——带后分频器的快速RC振荡器(内部振荡器)

9. FRC——快速RC振荡器(内部振荡器)

10. LPRC——低功耗RC振荡器(内部振荡器)

11. SOSC——辅助振荡器(辅助振荡器)

注:①PLL模块是一个锁相环(Phase Lock Loop)倍频器,可提高4倍频率,②这里共有11时钟模式,在配置时,有一个保留模式,但无HSPLL模式

一. 振荡器配置
一般为了减少外围电路,采用内部振荡器,其时钟频率最高可达32M。如果需要严格的时钟频率,而内部振荡器又无法匹配上,那才考虑外部振荡器。所以一般按以下步骤来配置(后面对应寄存器名):
1. 是否使用主振荡器——配置位CW2的POSCMD1:POSCMD0
2. 选择初始振荡器,即时钟模式——配置位CW2的FNOSC2:FNOSC0
3. 配置OSCO引脚功能,在EC和非主振荡器时钟模式下,不占用此引脚,可配置成Fosc/2时钟输出CLKO,或普通I/O口RA3——配置位CW2的OSCIOFCN
4. 【不使用SOSC模式可忽略此步】配置SOSC辅助振荡器使能位——OSCCON的SOSCEN
5. 【不使用FRCPLL、FRCDIV模式可忽略此步】配置FRC后分频比——CLKDIV的RCIDV2:RCDIV0
6. 【不使用打盹模式可忽略此步】配置CPU与外设的时钟比——CLKDIV的DOZE2:DOZE0
7. 【不使用打盹模式可忽略此步】配置中断是否影响打盹使能位(DOZEN)——CLKDIV的RIO
8. 【不使用打盹模式可忽略此步】使能打盹模式——CLKDIV的DOZEN
9. 【不调节FRC振荡器频率,应忽略此步】校准FRC振荡器频率——OSCTUN的TUN5:TUN0
(四个寄存器位组成参考文章末尾)

在不使用时钟切换的模式下,以上9步就可解决振荡器的配置问题。上面涉及到4个寄存器CW2、OSCCON、CLKDIV和OSCTUN,要配置他们可不是直接使用“_SOSCEN = 1;”或“OSCCONbits.SOSCEN = 1;”那样简单。CLKDIV和OSCTUN两个寄存器可按前面的方式进行配置,而CW2与OSCCON需要通过其他方式进行配置:
1)CW2——两种方法
可通过IDE自带配置位界面(Configure->Configuration Bits...)直接选择
或在#include包含文件位置后,使用代码_CONFIG2(value),value为配置数值,如:
_CONFIG2(POSCMOD_NONE & FNOSC_FRCPLL & OSCIOFNC_ON & FCKSM_CSECME)
2)OSCCON——也有两种方法
首先说明下,OSCCON是个核心寄存器,不是可以随便编辑的,用了两把锁把它的高低字节分别锁起来了。所以要编辑它,必须先解锁。高字节OSCCON<15:8>写序列为:连续将78h和9Ah写入高字节进行解锁,立即写入需要的数值。低字节OSCCON<7:0>写序列为:连续将46h和57h写入低字节进行解锁,立即写入需要的数值。
因此,要先区分所编辑位属于OSCCON高字节还是低字节,再按要求进行解锁和写入。
第一种方法,使用内置函数,__builtin_write_OSCCONH(value)来配置OSCCON高字节,和__builtin_write_OSCCONL(value)来配置OSCCON低字节。使用内置函数,不需要考虑解锁,编译成汇编代码已经包含了解锁序列,见下图(参考C30编译器的帮助文件hlpMPLABC30.chm)

第二种方法,直接使用汇编语言,嵌套在C语言中。发现C30不支持#asm和#endasm的多行汇编,就使用单行嵌入“asm(instruction);”,望知道的大侠告诉一声(谢谢^_^)。

    asm("mov #OSCCONH, w1");
    asm("mov #0x78, w2");
    asm("mov #0x9A, w3");
    asm("mov #0x00, w4"); //0x00 is the value will write to OSCCONH
    asm("mov.b w2, [w1]");
    asm("mov.b w3, [w1]");
    asm("mov.b w4, [w1]");

    asm("mov #OSCCONL, w1");
    asm("mov #0x46, w2");
    asm("mov #0x57, w3");
    asm("mov #0x01, w4"); //0x01 is the value will write to OSCCONL
    asm("mov.b w2, [w1]");
    asm("mov.b w3, [w1]");
    asm("mov.b w4, [w1]");
相信用C语言写程序的人都会选内置函数。
以下是个完整的代码,使用FRCPLL时钟模式,Fosc=32M,用定时器T1开关LED灯,实现每3s切换一次状态:
#include <p24FJ64GA002.h>

_CONFIG1(JTAGEN_OFF & GCP_OFF & FWDTEN_OFF)
_CONFIG2(POSCMOD_NONE & FNOSC_FRCPLL & OSCIOFNC_ON) // clock mode:FRCPLL, OSCO/RA3 functions as port I/O

typedef char BYTE;
typedef unsigned int WORD;
typedef unsigned long int DWORD;

#define LED_TRIS        _TRISA3
#define LED_ON()        _LATA3 = 1
#define LED_OFF()       _LATA3 = 0
#define LED_TRIGGER()   _LATA3 = ~_LATA3

#define TIME_TEN_MICROSECOND    300

WORD ledTriggerCount = 0;

void IC_Initialize(void)
{
    /*Oscillator configuration :32M FRCPLL*/
    __builtin_write_OSCCONL(0x00);
    CLKDIVbits.RCDIV = 0b000; //FRC postscaler divide by 1, is 8M

    /*enable DOZE mode*/
    //CLKDIVbits.DOZE = 0b001;
    //CLKDIVbits.DOZEN = 1; //enable DOZE bit

    /*initialize T1*/
    T1CONbits.TCS = 0; // internal clock
    T1CONbits.TGATE = 0; //disable Gated time accumulation
    T1CONbits.TCKPS = 0b01; //prescale =1:8, T1 = 2*8/fosc = 0.5us
    T1CONbits.TON =0;
    TMR1 = 0;
    PR1 = 20000; // time on a cycle is 10us
    _T1MD = 0; //default value, enable clock source to T1
    _T1IP = 7; //highest priority
    _T1IF = 0;
    _T1IE = 1; //enable T1 interrupt
}

int main(void)
{
    IC_Initialize();
    LED_TRIS =0;
    LED_ON();
    T1CONbits.TON =1; //T1 start
    while(1);

    return 0;
}

void __attribute__ ((__interrupt__, no_auto_psv)) _T1Interrupt(void)
{
    if(++ledTriggerCount == TIME_TEN_MICROSECOND){
        ledTriggerCount = 0;
        LED_TRIGGER();
    }
    _T1IF = 0;
}

二. 时钟切换

时钟切换按正常逻辑来理解,应该是告诉我一个新时钟模式,然后我切换过去就好了。对,就是这么简单。具体地寄存器操作步骤,看下面:

1. 开启时钟切换功能,FCKSM1位必须清零——CW2的FCKSM1:FCKSM0

2. 配置新时钟模式——OSCCON的NOSC2:NOSC0

3. 开始切换——OSCCON的OSWEN

三步完成时钟切换,但有四点要注意:

1)主振荡器下的三个子模式(XT、HS和EC)是由配置位决定,他们之间无法切换的。这好理解,你用一台发电机给工厂发电,你要切换发电机,在不断电的情况下不好办吧,得先断电后再切换。这里要切换就要重新烧录程序并设置配置位

2)使能PLL的主振荡器与FRCPLL之间也不能直接切换,但可通过先中转到FRC下再切换

3)涉及到引脚或分频类的,要注意设置好,参考datasheet,这里不再赘述

4)OSCCON的COSC2:COSC0可读出当前时钟模式,在切换前可先判断当前时钟模式

下面实例代码,在FRCPLL(Fosc=32M)和FRC(Fosc=8M)模式之间循环切换,通过LED呈现状态结果。在FRCPLL模式下,LED亮2s,灭2s,然后切换到FRC模式下,亮8s,灭8s,再切换到FRCPLL模式下,如此循环:

#include <p24FJ64GA002.h>

_CONFIG1(JTAGEN_OFF & GCP_OFF & FWDTEN_OFF)
_CONFIG2(POSCMOD_NONE & FNOSC_FRCPLL & OSCIOFNC_ON & FCKSM_CSECME) // OSCO/RA3 functions as port I/O, enable clock switch

typedef char BYTE;
typedef unsigned int WORD;
typedef unsigned long int DWORD;

#define LED_TRIS        _TRISA3
#define LED_ON()        _LATA3 = 1
#define LED_OFF()       _LATA3 = 0
#define LED_TRIGGER()   _LATA3 = ~_LATA3

#define TIME_TEN_MICROSECOND    200

WORD ledTriggerCount = 0;
BYTE  oscillatorSwitchCount = 0;

void IC_ClockSwitch(BYTE newOSC)
{
    _T1IE = 0; //disable all interrupts before switching
    __builtin_write_OSCCONH(newOSC); //set new oscillator mode
    __builtin_write_OSCCONL(0x01);  //enable oscillator switch

    while(OSCCONbits.OSWEN); //waiting for a successful clock transition
    _T1IE = 1; //enable interrupt after switched
}

void IC_Initialize(void)
{
    //Oscillator configuration :32M FRCPLL
    CLKDIVbits.RCDIV = 0b000; //FRC postscaler divide by 1, input 4*PLL is 8M
    CLKDIVbits.DOZE = 0b001; //1:2, CPU clock:16M
    CLKDIVbits.DOZEN = 1; //enable DOZE bit

    //initialize T1
    T1CONbits.TCS = 0; // internal clock
    T1CONbits.TGATE = 0; //disable Gated time accumulation
    T1CONbits.TCKPS = 0b01; //prescale =1:8, T1 = 2*8/fosc = 0.5us
    T1CONbits.TON =0;
    TMR1 = 0;
    PR1 = 20000; // time on a cycle is 10us
    _T1MD = 0; //default value, enable clock source to T1
    _T1IP = 7; //highest priority
    _T1IF = 0;
    _T1IE = 1; //enable T1 interrupt

}

int main(void)
{
    IC_Initialize();

    LED_TRIS =0;
    LED_ON();
    T1CONbits.TON =1; //T1 start
    while(1);

    return 0;
}

void __attribute__ ((__interrupt__, no_auto_psv)) _T1Interrupt(void)
{
    if(++ledTriggerCount == TIME_TEN_MICROSECOND){
        ledTriggerCount = 0;
        LED_TRIGGER();
        if(++oscillatorSwitchCount == 2){
            oscillatorSwitchCount = 0;
            IC_ClockSwitch(~OSCCONbits.COSC&0x01);//switch clock mode between FRCPLL and FRC
        }
    }
    _T1IF = 0;
}
里面的代码“IC_ClockSwitch(~OSCCONbits.COSC&0x01);”与下面代码功能一样:
if(OSCCONbits.COSC == 0b001)
    IC_ClockSwitch(0x00); //FRCPLL -> FRC
else
    IC_ClockSwitch(0x01); //FRC -> FRCPLL
你妹的,写一篇这样的博文,竟然花了我那么多时间。会用,但不知道用语言组织表达出来(⊙﹏⊙)b

时间: 2024-08-10 21:13:07

PIC振荡器配置与时钟切换的相关文章

【DATAGUARD】物理dg配置客户端无缝切换--Fast-Start Failover的配置

[DATAGUARD]物理dg配置客户端无缝切换--Fast-Start Failover的配置 一.2.2  实验环境介绍 项目 主库 dg库 db 类型 单实例 单实例 db version 11.2.0.3 11.2.0.3 db 存储 FS type FS type ORACLE_SID oradg11g oradgphy db_name oradg11g oradg11g 主机IP地址: 192.168.59.130 192.168.59.130 OS版本及kernel版本 RHEL6

【DATAGUARD】物理dg配置客户端无缝切换 (八.4)--ora-16652 和 ora-16603错误

[DATAGUARD]物理dg配置客户端无缝切换 (八.4)--ora-16652 和 ora-16603错误 一.1  BLOG文档结构图       一.2  前言部分   一.2.1  导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① Data Guard Broker 的配置 ② Fast-Start Failover 的配置 ③ Oracle DataGuard 之客户端TAF 配置 ④ 使用DGMGRL 来管理数据库

【DATAGUARD】物理dg配置客户端无缝切换 (八.2)--Fast-Start Failover 的配置

[DATAGUARD]物理dg配置客户端无缝切换 (八.2)--Fast-Start Failover 的配置 一.1  BLOG文档结构图       一.2  前言部分   一.2.1  导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① Data Guard Broker 的配置 ② Fast-Start Failover 的配置 ③ Oracle DataGuard 之客户端TAF 配置 ④ 使用DGMGRL 来管理数据库

【DATAGUARD】物理dg配置客户端无缝切换 (八.1)--Data Guard Broker 的配置

[DATAGUARD]物理dg配置客户端无缝切换 (八.1)--Data Guard Broker 的配置 一.1  BLOG文档结构图       一.2  前言部分   一.2.1  导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① Data Guard Broker 的配置 ② Fast-Start Failover 的配置 ③ Oracle DataGuard 之客户端TAF 配置 ④ 使用DGMGRL 来管理数据库 ⑤

亚稳态与多时钟切换

前面的博文聊到了触发器的建立时间和保持时间:http://www.cnblogs.com/IClearner/p/6443539.html  那么今天我们来聊聊与触发器有关的亚稳态已经多时钟系统中的时钟切换.与亚稳态有关的问题比如跨时钟域的问题很快就会补充.今天的主要内容如下所示: ·亚稳态的产生与传输 ·亚稳态的恢复时间与平均无故障时间 ·减小亚稳态的建议 ·多时钟切换电路 1.亚稳态的产生与传输 我们知道,交叉耦合反相器.SR锁存器.D锁存器和D触发器等存储元件有两个稳定的状态,即0和1,也

【DATAGUARD】物理dg配置客户端无缝切换--Data Guard Broker 的配置(1)

[DATAGUARD]物理dg配置客户端无缝切换 (八.1)--Data Guard Broker 的配置 一.2.2  实验环境介绍 项目 主库 dg库 db 类型 单实例 单实例 db version 11.2.0.3 11.2.0.3 db 存储 FS type FS type ORACLE_SID oradg11g oradgphy db_name oradg11g oradg11g 主机IP地址: 192.168.59.130 192.168.59.130 OS版本及kernel版本

安装和配置CentOS时钟同步服务

Type the following command to install ntp: # yum install -y ntp Turn on service: # chkconfig ntpd on Synchronize the system clock with 0.pool.ntp.org server: # ntpdate pool.ntp.org Start the NTP: # /etc/init.d/ntpd start 安装和配置CentOS时钟同步服务

单片机成长之路(51基础篇) - 023 N76e003 系统时钟切换到外部时钟

N76e003切换到外部时钟的资料很少(因为N76e003的片子是不支持无源晶振的,有源晶振的成本又很高,所以网上很少有对N76e003的介绍).有图有真相: 代码如下: main.c 1 #include <N76E003.H> 2 #include <SFR_Macro.h> 3 #include <Function_Define.h> 4 5 bit BIT_TMP; // 调用 SFR_Macro.h 使用的 6 7 void main(void){ 8 //

redis主从配置及主从切换

环境描述:主redis:192.168.10.1 6379从redis:192.168.10.2 6380一.主从配置1.将主从redis配置文件redis.conf中的aemonize no 改为 yes2.修改从redis配置文件redis.conf中的port 6379 改为 6380,添加slaveof 192.168.10.1 6379 3.启动主从服务 主redis: [[email protected] redis-2.8.3]# src/redis-server /soft/re