一、查看STM32各个时钟的频率
#include "stm32f10x_rcc.h"提供了查看时钟频率的函数:void RCC_GetClocksFreq(RCC_ClocksTypeDef* RCC_Clocks);
把RCC_Clocks添加到watch窗口用jlink观察即可,或者用串口把数据打印出来也行。
可以看到,所有的时钟都列出来了,其中系统时钟为SYSCLK,其值为0x044AA200,也即72000000=72M
注意:这个函数能否正确返回MCU真正的频率,还得看看这个函数的帮助:
最后一条很关键:如果你使用的外部晶振频率是带小数的,这个函数的返回值可能会出错。
正确设置HSE_VALUE、HSI_VALUE的值也很关键,这个函数内部会自动判断MCU正在使用HSI、HSE、还是PLL,但是HSI/HSE的频率还是得人为的告诉程序具体频率值是多少,这两个值是个宏。
二、修改STM32的时钟频率
设置系统时钟频率,一般是在SystemInit函数中,这个函数在boot文件中被调用,然后才进入main函数:
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state ------------*/
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset CFGR register */
RCC->CFGR = 0x00000000;
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x24003010;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Disable all interrupts */
RCC->CIR = 0x00000000;
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the System clock source, PLL Multiplier and Divider factors,
AHB/APBx prescalers and Flash settings ----------------------------------*/
SetSysClock();
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
SystemInit函数的操作操作流程如下:首先使能内部8M高速时钟HSI作为总时钟源,然后进入SetSysClock()函数设置PLL_CLK/APB_CLK/AHB_CLK等,SetSysClock()函数中会首先启动外部高速时钟HSE,如果能正常起振,则把总时钟源切换到外部晶振HSE,并使能PLL,PLL输出作为SYSCLK。
按照官方库函数的话,只要正确设置了外部时钟的频率,系统就能正常工作,外部晶振的频率宏为HSE_VALUE,默认为25M,一般我们用的是8M,需要改一下。
三、查看程序的精确的运行时间
正确填写外部晶振的频率(我实测过,填错了也不要紧,不知道这个Xtal是干嘛的,为了保险起见,还是把它填对吧)
然后在KEIL中设置MCU的实际的SYSCLK频率,上文中我们已经查到了SYSCLK的频率,然后把这个值填到->魔术棒->Debug->settings->Trace->Core框里面,如下图所示。(测量不准,原因就出在这里,这个必须要设置正确)
进入debug模式:
每进入下一个断点,sec都会更新,
截图中的这个示例程序是用TIM定时器写的一个定时20ms函数,keil测量实际的时间为19.85602ms,差别不大。
当然,利用上述方法查看时间差,需要自己用计算器减出来,不是很方便,实际上,keil也提供了仿真计时清0的功能,在keil主界面的右下角,你可以在任意断点时,reset这个仿真计时器是它归0。(实测发现,这个功能有时候不大好使,时行时不行)
四、精确的delay函数
有了这个仿真计时功能,我们就能写出死等的delay函数。执行以下程序
我们发现这个while死等就是3条汇编指令:MOVS SUB BNE,当Tscl<>0时,BNE就会跳转回0x080009E0地址处,也即MOVS指令处,就这3条指令。这3条指令执行10000次,通过断点仿真计时来查看,总共耗时0.00023812s=238.12纳秒。
由于ARM从flash取指需要耗时,ARM又采用了三级、五级、七级等流水线,导致我们无法直接根据指令数求出延时,所以还得靠仿真测量,如果哪有朋友有好的方法,请留言指教。
于是很容易推导出:t = 41999; while(t--);会耗时1ms。(SYSCLK=168M)
后记:使用这种delay过程中发现,即使SYSCLK频率不变、全程不开中断,同一段while死等耗时也不相同,刚上电时跑的慢,后来就快了,最终决定舍弃这种delay方法,还是用硬件定时器中断来计时吧