关于内外时钟切换及时钟超频测试

前言  
近日,有群友困于stm32的时钟系统。这里就详细介绍一下关于内外时钟切换及时钟超频测试,希望对大家能有所帮助。
诚然,当使用固件库时,把外部晶振摘掉,系统确实会自动切换到内部时钟,但是只会以8m的默认值运行,显然这是十分不可行的,8m的速度直接让我们的stm32病入膏肓,今天的任务就是让stm32失去外挂(晶振)时,依旧可以激情澎湃。
时钟详解这里不过多介绍,自己也没有别人介绍的好,本文旨在解决现实问题。
此处插播广告:群友问过这种问题,外部接8m晶振和16m晶振有啥区别?
以我微薄的经验来看,这两个在用的时候差别不大,如果使用st的固件库(以stm32f103为例),使用8m的晶振会更方便,不用改任何代码,时钟就是72m的全速运行状态。如果用16m晶振,则需要修改代码:
在stm32f10x.h中修改宏定义hse_value   ((uint32_t)8000000)为hse_value   ((uint32_t)16000000)。
之后进入system_stm32f10x.c,将rcc->cfgr |= (uint32_t)(rcc_cfgr_pllsrc_hse | rcc_cfgr_pllmull9);改为rcc->cfgr |= (uint32_t)(rcc_cfgr_pllsrc_hsi_div2| rcc_cfgr_pllmull9);此处是将输入时钟二分频为8m,再进行9倍频到72m,和使用了8m没区别。
如果不进行该二分频操作,时钟还是有的,但是会以16m为基准进行9倍频到144m,此时单片机以超频模式运行,也是可以运行的。但是,时钟的精准性不能得到保证。
系统的时钟可以通过添加代码在debug模式下显示:
rcc_clockstypedef clockinfo;rcc_getclocksfreq(&clockinfo);
通过debug模式下观察clockinfo的值,便可知道此时系统时钟速度:
这里提一下,在使用外部晶振的情况下,st即使是超频,依旧发挥稳定,不得不夸一下st的质量。
此时我将我的开发板以8m的基准倍频16倍,得到128m的主频,使用定时器定时10us,示波器测试无误差。串口通信无误。
以72m的主频跑128依旧稳定,赞一个,因为我的外部晶振只有8m最大只能倍频到128,如果使用外部16m,不知继续倍频可以到多少。不过性能还是很好的。
预留测试gd32的效果:
写本文时,将gd的gd32e230翻出来进行了同样的测试,因为gd的倍频器倍数较高,我已经倍频到144m(标准72m),测试定时器依旧稳定。
广告很长,请忍一下:
上半场结束,下半场继续:
此处歪解一下时钟的问题,之前有群友很疑惑单片机的低功耗和时钟的关系,疑惑高速的时钟会不会增加mcu的功耗,为啥低功耗要降低时钟速度。这里讲解一下:
可以用用单位时间内执行的指令来看,高速时钟在单位时间内使系统跑了更多的指令,而低速时钟单位时间内跑的少,而单片机是直线结构,内核是不会休息的,功耗就看执行的指令多少。而单片机的低功耗就是降低时钟,让单片机跑慢点。就像人一样,低功耗相当于你不跑了,原地休息,但是你的心跳不会停止,你还是得消耗能量,即使再少还得消耗。
就像人一样,时钟就相当于心跳,只要还活着就得消耗能量,你要想跑得快,心脏就得跳得快,跳得越快能量消耗越高,即使你去睡觉,心跳只要不停止,你还得消耗能量,如果心跳没了,整个人就没了,mcu也就宕机了。所以,在处理低功耗时最先解决的就是时钟频率,只有降低了时钟的频率,才能真正降低功耗。关于单片机进入低功耗和唤醒,以及降低整体运行功耗我看能不能在下文讲解,近期刚好做了一个低功耗的项目,这里留悬念吧。
广告结束,正文开始,不好意思,有点喧宾夺主了哈!
回到主题,为了解决时钟切换的问题,才有了这个帖子,上文全属歪楼,为最近开发时的经验总结。
我们在使用stm32103的固件库时,时钟配置在system_stm32f10x.c中,但是只是对外部晶振做了初始化,而对于内部时钟并没有添加代码,如果你的mcu没有外部晶振,当系统运行时是先启动内部时钟,然后会检测外部晶振,如果没有检测到晶振,系统便以内部的8m继续运行,这是不合理的。
这里可以看到,如果外部启动失败,会进入这个else,但是这个else中并未添加任何代码,所以只会用8m的内钟执行,我们要做的就是在else中添加外部启动失败的代码:
/* 开启hsi 即内部晶振时钟 */        rcc->cr |= (uint32_t)0x00000001;         /*选择hsi为pll的时钟源hsi必须2分频给pll*/        rcc->cfgr |= (uint32_t)rcc_cfgr_pllsrc_hsi_div2;                                  /*pllclk=8/2*13=52mhz   设置倍频得到时钟源pll的频率*/        rcc->cfgr |= (uint32_t)rcc_cfgr_pllmull12;        /* pll不分频输出  */        rcc->cfgr |= (uint32_t)rcc_cfgr_hpre_div1;                 /* 使能 pll时钟 */        rcc->cr |= rcc_cr_pllon;        /* 等待pll时钟就绪*/        while((rcc->cr & rcc_cr_pllrdy) == 0)        {        }        /* 选择pll为系统时钟的时钟源 */        rcc->cfgr &= (uint32_t)((uint32_t)~(rcc_cfgr_sw));        rcc->cfgr |= (uint32_t)rcc_cfgr_sw_pll;            /* 等到pll成为系统时钟的时钟源*/        while ((rcc->cfgr & (uint32_t)rcc_cfgr_sws) != (uint32_t)0x08)        {        }  
该代码填充后如果检测到有外部时钟,便以外部时钟为基准进行时钟的倍频处理,达到用户想要的时钟频率,如果你的mcu没有外部时钟,则会执行else内部的代码,将时钟源切换到内部时钟并进行倍频,如此便达到了自动检测时钟的目的。
问题:这是我根据stm32f031的时钟切换代码演变来的,但是这个只能用于主频小于或等于48m时使用,如果倍频因子超过12,也就是主频超过48m是,就会出现硬件错误,直接卡死。当需要更高的主频时就需要如下配置。
在else里面最开头添加:
/* enable prefetch buffer */    flash->acr |= flash_acr_prftbe;    /* flash 2 wait state */    flash->acr &= (uint32_t)((uint32_t)~flash_acr_latency);    flash->acr |= (uint32_t)flash_acr_latency_2;
问:如果我的mcu有晶振,但是我不想用外部,就想用内部,如何处理呢?
答:打一顿就好了,有外部不用干啥用内部呢?
上述纯属恶搞自己,被坑过……
因为内部时钟不准!!!测试内部时钟在使用定时器时会有偏差,本人在此吃过亏。此问题在stm32f031和gd32e230中均有体现。但是usart和spi通信是正常的,即使我用的2.5m波特率的usart和8m的spi。
解决办法,上述代码不用动,添加如下代码。
通过注释原文rcc->cr |= ((uint32_t)rcc_cr_hseon);并添加rcc->cr &= ~((uint32_t)rcc_cr_hseon);可默认之以内部时钟方式启动。  注意:在主函数加上systeminit();函数哦!!!  最终代码如下:  
static void setsysclockto72(void){  __io uint32_t startupcounter = 0, hsestatus = 0;    /* sysclk, hclk, pclk2 and pclk1 configuration ---------------------------*/      /* enable hse */    //  rcc->cr |= ((uint32_t)rcc_cr_hseon);        /*取消改行注释并注释上文,可默认启动内部时钟*/        rcc->cr &= ~((uint32_t)rcc_cr_hseon);  /* wait till hse is ready and if time out is reached exit */  do  {    hsestatus = rcc->cr & rcc_cr_hserdy;    startupcounter++;    } while((hsestatus == 0) && (startupcounter != hse_startup_timeout));  if ((rcc->cr & rcc_cr_hserdy) != reset)  {    hsestatus = (uint32_t)0x01;  }  else  {    hsestatus = (uint32_t)0x00;  }    if (hsestatus == (uint32_t)0x01)  {    /* enable prefetch buffer */    flash->acr |= flash_acr_prftbe;    /* flash 2 wait state */    flash->acr &= (uint32_t)((uint32_t)~flash_acr_latency);    flash->acr |= (uint32_t)flash_acr_latency_2;        /* hclk = sysclk */    rcc->cfgr |= (uint32_t)rcc_cfgr_hpre_div1;          /* pclk2 = hclk */    rcc->cfgr |= (uint32_t)rcc_cfgr_ppre2_div1;        /* pclk1 = hclk */    rcc->cfgr |= (uint32_t)rcc_cfgr_ppre1_div2;#ifdef stm32f10x_cl    /* configure plls ------------------------------------------------------*/    /* pll2 configuration: pll2clk = (hse / 5) * 8 = 40 mhz */    /* prediv1 configuration: prediv1clk = pll2 / 5 = 8 mhz */            rcc->cfgr2 &= (uint32_t)~(rcc_cfgr2_prediv2 | rcc_cfgr2_pll2mul |                              rcc_cfgr2_prediv1 | rcc_cfgr2_prediv1src);    rcc->cfgr2 |= (uint32_t)(rcc_cfgr2_prediv2_div5 | rcc_cfgr2_pll2mul8 |                             rcc_cfgr2_prediv1src_pll2 | rcc_cfgr2_prediv1_div5);      /* enable pll2 */    rcc->cr |= rcc_cr_pll2on;    /* wait till pll2 is ready */    while((rcc->cr & rcc_cr_pll2rdy) == 0)    {    }           /* pll configuration: pllclk = prediv1 * 9 = 72 mhz */     rcc->cfgr &= (uint32_t)~(rcc_cfgr_pllxtpre | rcc_cfgr_pllsrc | rcc_cfgr_pllmull);    rcc->cfgr |= (uint32_t)(rcc_cfgr_pllxtpre_prediv1 | rcc_cfgr_pllsrc_prediv1 |                             rcc_cfgr_pllmull9); #else        /*  pll configuration: pllclk = hse * 9 = 72 mhz */    rcc->cfgr &= (uint32_t)((uint32_t)~(rcc_cfgr_pllsrc | rcc_cfgr_pllxtpre |                                        rcc_cfgr_pllmull));    rcc->cfgr |= (uint32_t)(rcc_cfgr_pllsrc_hse | rcc_cfgr_pllmull16);#endif /* stm32f10x_cl */    /* enable pll */    rcc->cr |= rcc_cr_pllon;    /* wait till pll is ready */    while((rcc->cr & rcc_cr_pllrdy) == 0)    {    }        /* select pll as system clock source */    rcc->cfgr &= (uint32_t)((uint32_t)~(rcc_cfgr_sw));    rcc->cfgr |= (uint32_t)rcc_cfgr_sw_pll;        /* wait till pll is used as system clock source */    while ((rcc->cfgr & (uint32_t)rcc_cfgr_sws) != (uint32_t)0x08)    {    }  }  else  {     /* enable prefetch buffer */    flash->acr |= flash_acr_prftbe;    /* flash 2 wait state */    flash->acr &= (uint32_t)((uint32_t)~flash_acr_latency);    flash->acr |= (uint32_t)flash_acr_latency_2;                     /* 开启hsi 即内部晶振时钟 */        rcc->cr |= (uint32_t)0x00000001;         /*选择hsi为pll的时钟源hsi必须2分频给pll*/        rcc->cfgr |= (uint32_t)rcc_cfgr_pllsrc_hsi_div2;                                  /*pllclk=8/2*13=52mhz   设置倍频得到时钟源pll的频率*/        rcc->cfgr |= (uint32_t)rcc_cfgr_pllmull16;        /* pll不分频输出  */        rcc->cfgr |= (uint32_t)rcc_cfgr_hpre_div1;                 /* 使能 pll时钟 */        rcc->cr |= rcc_cr_pllon;        /* 等待pll时钟就绪*/        while((rcc->cr & rcc_cr_pllrdy) == 0)        {        }        /* 选择pll为系统时钟的时钟源 */        rcc->cfgr &= (uint32_t)((uint32_t)~(rcc_cfgr_sw));        rcc->cfgr |= (uint32_t)rcc_cfgr_sw_pll;            /* 等到pll成为系统时钟的时钟源*/        while ((rcc->cfgr & (uint32_t)rcc_cfgr_sws) != (uint32_t)0x08)        {        }  }}  
在stm32f030或者stm32f031中,同样可以做类似操作:
static void setsysclock(void){  __io uint32_t startupcounter = 0, hsestatus = 0;    /* sysclk, hclk, pclk configuration ----------------------------------------*/  /* enable hse */   rcc->cr |= ((uint32_t)rcc_cr_hseon);        //修改为内部晶振        //        rcc->cr &= ~((uint32_t)rcc_cr_hseon);   /* wait till hse is ready and if time out is reached exit */  do  {    hsestatus = rcc->cr & rcc_cr_hserdy;    startupcounter++;    } while((hsestatus == 0) && (startupcounter != hse_startup_timeout));  if ((rcc->cr & rcc_cr_hserdy) != reset)  {    hsestatus = (uint32_t)0x01;  }  else  {    hsestatus = (uint32_t)0x00;  }    if (hsestatus == (uint32_t)0x01)  {    /* enable prefetch buffer and set flash latency */    flash->acr = flash_acr_prftbe | flash_acr_latency;     /* hclk = sysclk */    rcc->cfgr |= (uint32_t)rcc_cfgr_hpre_div1;          /* pclk = hclk */    rcc->cfgr |= (uint32_t)rcc_cfgr_ppre_div1;    /* pll configuration = hse * 6 = 48 mhz */    rcc->cfgr &= (uint32_t)((uint32_t)~(rcc_cfgr_pllsrc | rcc_cfgr_pllxtpre | rcc_cfgr_pllmull));    rcc->cfgr |= (uint32_t)(rcc_cfgr_pllsrc_prediv1 | rcc_cfgr_pllxtpre_prediv1 | rcc_cfgr_pllmull7);                /* enable pll */    rcc->cr |= rcc_cr_pllon;    /* wait till pll is ready */    while((rcc->cr & rcc_cr_pllrdy) == 0)    {    }    /* select pll as system clock source */    rcc->cfgr &= (uint32_t)((uint32_t)~(rcc_cfgr_sw));    rcc->cfgr |= (uint32_t)rcc_cfgr_sw_pll;        /* wait till pll is used as system clock source */    while ((rcc->cfgr & (uint32_t)rcc_cfgr_sws) != (uint32_t)rcc_cfgr_sws_pll)    {    }  }  else  { /* if hse fails to start-up, the application will have wrong clock          configuration. user can add here some code to deal with this error */                   // hsi 内部时钟做为pll时钟源并配置pll 56m做为系统时钟    /* enable prefetch buffer and set flash latency */    flash->acr = flash_acr_prftbe | flash_acr_latency;    /* hclk = sysclk */    rcc->cfgr |= (uint32_t)rcc_cfgr_hpre_div1;    /* pclk = hclk */    rcc->cfgr |= (uint32_t)rcc_cfgr_ppre_div1;    // pll configuration = (hsi/2) * 12 = 48 mhz    rcc_pllconfig(rcc_pllsource_hsi_div2, rcc_pllmul_14); // 8m/2 * 14 = 56m    /* enable pll */    rcc->cr |= rcc_cr_pllon;    /* wait till pll is ready */    while ((rcc->cr & rcc_cr_pllrdy) == 0)    {    }    /* select pll as system clock source */    rcc_sysclkconfig(rcc_sysclksource_pllclk); // pll 做系统时钟    /* wait till pll is used as system clock source */    while ((rcc->cfgr & (uint32_t)rcc_cfgr_sws) != (uint32_t)rcc_cfgr_sws_pll)    {    }  }  }  
在stm32f103中,使用内部晶振,最大时钟频率也只能到64m,受倍频因子的影响嘛,最大只能倍频16倍。但在stm32f031中,标准使用内部时钟主频只有48m,但是我们仍然可以继续倍频,用内部时钟进行超频达到64m。在我们的产品中就用过内部超频到56m,usart和spi长时间无问题。
而gd32e230因为其高达32的倍频因子,内部时钟可以倍频到128m。
但是,这种几分钟内没有明显发热现象,不敢做长时间测试,现在mcu有点小贵。干费一个就心疼。
总之,无论st还是国产,其主频更适合在规定的范围内运行,但是跑极限在短时间内也没有很大的问题。这些数据仅供参考。
至此单片机时钟讲解就结束了,没有多少理论性的东西,主要是解决一些时钟使用时的问题,自己也总是忘,留帖一篇作为自省。
本文中所有代码都经过本人测试,运行无任何问题,但是对于问题的阐述或者一些见解可能有错误,欢迎大佬们批评指正,一定接受各种批评,努力完善!


电机运动控制算法之S速度曲线规划算法
快讯!唯样商城与开步睿思达成战略合作
如何使用模拟发现调试数字逻辑
大算力芯片何时迎来终局战?
基于区块链技术运行的dApp应用跟App有什么区别
关于内外时钟切换及时钟超频测试
重大升级!Proxmox Virtual Environment 8.0 发布
关于看门狗时间选定问题
三坐标测量仪的操作流程及注意事项
ZUK Edge发布会在哪看?12.20联想ZUK Edge新品发布会
【比特熊充电栈】Azure OpenAI 守护大模型数据与安全!
志高电磁炉检修方法
电动机如何一键实现启动和停止电路
TM-30是否具有明确的颜色质量指标
华为推出麦芒7,拥有大运存、长续航、智慧拍摄、GPU Turbo等产品优势
云原生的目的是构建和运行可弹性扩展的应用
多媒体融合通信系统在铁路系统编组站管理中的应用
荣誉丨国辰机器人荣获恰佩克品牌奖
怎样用FU-50制作单瑞甲类功放
WiFi联盟宣布暂时撤销华为会员资格,WAPI能否取代