前面介绍过STC32G12K128的时钟系统使用的几种时钟源。接下来,我使用不同的时钟源测试时钟的精度,思路是,使用不同的时钟源,利用定时器计数,在GPIO口输出脉冲信号,然后测量脉冲信号的周期,看看差异以及与预想的是否一致来做比较。对于使用外部晶振的情况,因为受控于外部晶振的精度,所以不作为本次测试的内容。
下面是测试的三种分支。
测试代码:
/************* 功能说明 **************
使用Keil C251编译器,Memory Model推荐设置XSmall模式,默认定义变量在edata,单时钟存取访问速度快。
edata建议保留1K给堆栈使用,空间不够时可将大数组、不常用变量加xdata关键字定义到xdata空间。
切换时钟源,分别是默认IRC主频,主频24分频,PLL 96M 16分频,内部32K IRC。
下载时, 选择时钟 24MHz (用户可自行修改频率).
******************************************/ #include "STC32G.h" #include "stdio.h" #include "intrins.h" typedef unsigned char u8; typedef unsigned int u16; typedef unsigned long u32; #define MAIN_Fosc 24000000UL #define Timer4_Reload (MAIN_Fosc / 5000) //Timer 4 中断频率, 5000次/秒 /****************************** 用户定义宏 ***********************************/ /*****************************************************************************/ /************* 本地常量声明 **************/ #define CKMS 0x80 #define HSIOCK 0x40 #define MCK2SEL_MSK 0x0c #define MCK2SEL_SEL1 0x00 #define MCK2SEL_PLL 0x04 #define MCK2SEL_PLLD2 0x08 #define MCK2SEL_IRC48 0x0c #define MCKSEL_MSK 0x03 #define MCKSEL_HIRC 0x00 #define MCKSEL_XOSC 0x01 #define MCKSEL_X32K 0x02 #define MCKSEL_IRC32K 0x03 #define ENCKM 0x80 #define PCKI_MSK 0x60 #define PCKI_D1 0x00 #define PCKI_D2 0x20 #define PCKI_D4 0x40 #define PCKI_D8 0x60 void Timer4_init(void); /************* 本地变量声明 **************/ u8 mode = 0; /************* 本地函数声明 **************/ void delay_ms(u8 ms); void MCLK_Sel(void); /********************* 主函数 *************************/ void main(void) { WTST = 0; //设置程序指令延时参数,赋值为0可将CPU执行指令的速度设置为最快 EAXFR = 1; //扩展寄存器(XFR)访问使能 CKCON = 0; //提高访问XRAM速度 P0M1 = 0x00; P0M0 = 0x00; //设置为准双向口 P1M1 = 0x00; P1M0 = 0x00; //设置为准双向口 P2M1 = 0x00; P2M0 = 0x00; //设置为准双向口 P3M1 = 0x00; P3M0 = 0x00; //设置为准双向口 P4M1 = 0x00; P4M0 = 0x00; //设置为准双向口 P5M1 = 0x00; P5M0 = 0x00; //设置为准双向口 P6M1 = 0xff; P6M0 = 0xff; //设置为漏极开路(实验箱加了上拉电阻到3.3V) P7M1 = 0x00; P7M0 = 0x00; //设置为准双向口 EA = 1; //打开总中断 Timer4_init(); //MCLK_Sel(); while(1) { if (P20 == 0) { delay_ms(20); if (P20 == 0) { P65=0; while(P20 == 0); P65=1; mode = (mode+1)%4; MCLK_Sel(); if (mode==0) { P60=1; P61=1; } else if (mode==1) { P60=0; P61=1; } else if (mode==2) { P60=1; P61=0; } else if (mode==3) { P60=0; P61=0; } } } } } //======================================================================== // 函数: void MCLK_Sel(void) // 描述: 系统时钟设置函数。 // 参数: none. // 返回: none. // 版本: VER1.0 // 日期: 2020-7-29 // 备注: //======================================================================== void MCLK_Sel(void) { if(mode == 0) { HIRCCR = 0x80; //启动内部 IRC while (!(HIRCCR & 1)); //等待时钟稳定 CLKDIV = 0; CLKSEL = 0x00; //选择内部 IRC ( 默认 ) } else if(mode == 1) { HIRCCR = 0x80; //启动内部 IRC while (!(HIRCCR & 1)); //等待时钟稳定 CLKDIV = 24; //MCLK/24 CLKSEL = 0x00; //选择内部 IRC ( 默认 ) } else if(mode == 2) { CLKSEL &= ~CKMS; //选择PLL的96M作为PLL的输出时钟 USBCLK |= PCKI_D2; //输入时钟2分频(选择PLL输入时钟分频,保证输入时钟为12M) //启动PLL USBCLK |= ENCKM; //使能PLL倍频 delay_ms(1); //等待PLL锁频 CLKDIV = 16; //主时钟选择高速频率前,必须先设置分频系数,否则程序会当掉 CLKSEL &= ~MCKSEL_MSK; CLKSEL &= ~MCK2SEL_MSK; CLKSEL |= MCKSEL_HIRC; //选择内部高速IRC作为主时钟 CLKSEL |= MCK2SEL_PLLD2; //选择PLL输出时钟2分频后的时钟作为主时钟 } else if(mode == 3) { IRC32KCR = 0x80; //启动内部 32K IRC while (!(IRC32KCR & 1)); //等待时钟稳定 CLKDIV = 0x00; //时钟不分频 CLKSEL = 0x03; //选择内部 32K } } //======================================================================== // 函数: void Timer4_init(void) // 描述: timer4初始化函数. // 参数: none. // 返回: none. // 版本: V1.0, 2015-1-12 //======================================================================== void Timer4_init(void) { T4R = 0; //停止计数 #if (Timer4_Reload < 64) // 如果用户设置值不合适, 则不启动定时器 #error "Timer4设置的中断过快!" #elif ((Timer4_Reload/12) < 65536UL) // 如果用户设置值不合适, 则不启动定时器 ET4 = 1; //允许中断 T4_CT = 0; //定时 T4CLKO = 0; //不输出时钟 #if (Timer4_Reload < 65536UL) T4x12 = 1; //1T mode T4H = (u8)((65536UL - Timer4_Reload) / 256); T4L = (u8)((65536UL - Timer4_Reload) % 256); #else T4x12 = 0; //12T mode T4H = (u8)((65536UL - Timer4_Reload/12) / 256); T4L = (u8)((65536UL - Timer4_Reload/12) % 256); #endif T4R = 1; //开始运行 #else #error "Timer4设置的中断过慢!" #endif } //======================================================================== // 函数: void timer4_int (void) interrupt TIMER4_VECTOR // 描述: timer4中断函数. // 参数: none. // 返回: none. // 版本: V1.0, 2015-1-12 //======================================================================== void timer4_int(void) interrupt 20 { P67 = ~P67; P27 = P67; }
代码中使用了STC原厂提供的改变时钟源的代码。
1、使用内部高精度 IRC,不分频
测出的定时周期为198.456us,理论值应该是200us,误差为0.772%
2、使用内部高精度 IRC,24分频
测出的定时周期为4.783ms,理论值应该是4.8ms,误差约0.354%。
3、使用PLL时钟,16分频
测出的定时周期为1.585ms,理论值应该是1.67ms,误差为5.090%。
4、使用内部 32KHz 的 IRC,不分频
测出的定时周期为135.139ms,理论值应该是152.58ms,误差约为11.431%
从以上的测试结果上看,使用内部有RC构成的32.768KHz的时钟源,误差是最大的。其次是内部PLL输出,也挺大,达到5%。总体来说内部高精度IRC时钟还是比较靠谱的,误差很小,不到1%。
通过这次测试,对于单片机的时钟源的选择上的相关知识有了实际认识。涉及数据计算的部分,理解可能还有不到位的地方,期待大佬指正。
测试工程包:STC32时钟源.zip