前言 时钟是MCU的心跳,默认MCU会以内部的RC震荡器作为默认的时钟源,以默认的频率运行,这并不是MCU最高的主频配置,所以我们需要按照MCU的手册配置最高的主频运行以达到最高性能。磨刀不误砍柴工,这篇就先详细进行时钟模块的配置与测试。
时钟模块基本结构从原理图可以看到外接了12M晶体
我们可以使用该时钟作为时钟源,经PLL倍频得到最终的主频。
我们查看手册中的时钟树描述,得到如下画线的配置路径:
我们可以看到各时钟最大值,分频配置时不要超过该最大值。
各时钟源的频率如下
相关寄存器底层驱动无非就是配置寄存器,所以我们先过一遍寄存器,大概了解下每个寄存器干什么,然后对照手册一个bit一个bit配置。
CGFSAR:安全属性寄存器,默认为1非安全模式。
SCKDIVCR:各模块时钟分频设置
SCKSCR:系统时钟源选择
PLLCCR:PLL配置寄存器
PLLCR:PLL使能控制寄存器
PLL2CCR:PLL2配置寄存器
PLL2CR:PLL2使能控制寄存器
MOSCCR:主时钟振荡器使能控制
SOSCCR:32.768K子时钟振荡器使能控制。
LOCOCR:内部低速振荡器使能控制
HOCOCR:高速内部振荡器使能控制
MOCOCR:中速内部振荡器使能控制
FLLCR1:FLL使能控制
FLLCR2
OSCSF:振荡器稳定标志
OSTDCR:振荡器停止检测控制
OSTDSR:振荡器停止状态
MOSCWTCR:主振荡器稳定等待时间
MOMCR:主时钟模式(有源晶振时钟源还是晶体)
SOMCR:子振荡器模式控制
CKOCR:时钟输出控制
LOCOUTCR,MOCOUTCR,HOCOUTCR
USBCKDIVCR,USBCKCR:USB时钟配置
TRCKCR:TRCKCR时钟配置
配置注意配置CLK相关寄存器需要先设置PRCR.PRC0=1,设置该位使能注意高8位需要同时写入0xA5.
即R_SYSTEM->PRCR |= 0xA501;
然后按照
配置主时钟(外部晶振)->等待外部晶振稳定->配置PLL->等待PLL稳定
->设置各模块分频系数->设置FLASH等待周期->选择PLL作为时钟源->设置时钟输出(我这里未使用) 的步骤进行。
注意选择PLL作为时钟源要放置在最后,否则可能由于FLASH等待周期,分频还未正确设置导致频率超过各模块正常值而导致异常。
另外其他比如32.768KHz的子时钟,内部的高中低速的时钟,USB时钟等按需配置我这里就暂时不配置。
注意一般FLASH的速度没有CPU快,所以需要插入等待周期,本MCU能在ICLK,50MHz时FLASH 0等待周期,性能是不错的,我们ICLK配置为100MHz大于50MHz所以配置等待周期为1(见代码)。
代码注释比较详细,对着代码和手册看即可
SYSTICK测试时钟配置好之后我们就需要测试配置的时钟是否生效
一般可以使用时钟输出引脚测试,但是遗憾的是这个MCU的时钟输出不能选择PLL的输出之后的时钟,只能直接使用各时钟输入,所以不能用它来测试。
我们可以使用SYSTICK来进行测试。
注意SYSTICK可以选择LOCO或者ICLK作为时钟源
如下表和说明只列举了LOCO,应该是手册错误。
如下位置描述是对的
由于我们配置了ICLK为100MHz
所以我们设置Systick记载值为100000-1的话,中断周期就是1000uS,即1mS。
初始化时设置systick_init(100000);
相关接口实现
- int systick_init(uint32_t tick)
- {
- SysTick_Config(tick);
- NVIC_EnableIRQ(SysTick_IRQn);
- return 0;
- }
-
- static uint32_t s_tick_u32 = 0;
- void SysTick_Handler(void)
- {
- //NVIC_ClearPendingIRQ(SysTick_IRQn);
- s_tick_u32++;
- #if 1
- static uint8_t s_led_tog_flag = 0;
- if(s_tick_u32 % 1000 == 0)
- {
- led_set(0, s_led_tog_flag);
- led_set(1, s_led_tog_flag);
- led_set(2, s_led_tog_flag);
- s_led_tog_flag ^= 1;
- }
- #endif
- }
其中SysTick_Config的实现位于srcARMCM33Includecore_cm33.h
- /**
- ingroup CMSIS_Core_FunctionInterface
- defgroup CMSIS_Core_SysTickFunctions SysTick Functions
- brief Functions that configure the System.
- @{
- */
-
- #if defined (__Vendor_SysTickConfig) && (__Vendor_SysTickConfig == 0U)
-
- /**
- brief System Tick Configuration
- details Initializes the System Timer and its interrupt, and starts the System Tick Timer.
- Counter is in free running mode to generate periodic interrupts.
- param [in] ticks Number of ticks between two interrupts.
- return 0 Function succeeded.
- return 1 Function failed.
- note When the variable __Vendor_SysTickConfig is set to 1, then the
- function SysTick_Config is not included. In this case, the file device.h
- must contain a vendor-specific implementation of this function.
- */
- __STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
- {
- if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
- {
- return (1UL); /* Reload value impossible */
- }
-
- SysTick->LOAD = (uint32_t)(ticks - 1UL); /* set reload register */
- NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL); /* set Priority for Systick Interrupt */
- SysTick->VAL = 0UL; /* Load the SysTick Counter Value */
- SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
- SysTick_CTRL_TICKINT_Msk |
- SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
- return (0UL); /* Function successful */
- }
其中SysTick_CTRL_CLKSOURCE_Msk置位就是选择的ICLK,我们也可以不置位使用LOCO(频率是32.768KHz)测试。
注意中断向量的配置
srcARMCM33SourceARMstartup_ARMCM33.S中注释掉
Set_Default_Handler SysTick_Handler (.s中弱定义了一个实现,我们使用外部的实现,所以不用.S里定义的即可)
.S中设置为了.long SysTick_Handler /* -1 SysTick Handler */
SysTick_Handler即我们自己实现的服务函数地址。
srcARMCM33Sourcesystem_ARMCM33.c
SystemInit中设置SCB->VTOR = (uint32_t) &(__VECTOR_TABLE[0]);
extern const VECTOR_TABLE_Type __VECTOR_TABLE[496];
即srcARMCM33SourceARMstartup_ARMCM33.S中
对应如下中断向量,我们需要调用自己的中断服务函数只需要替换对应的函数地址即可,后后面预留了.space (470 * 4)各空间,我们可以按照手册的向量号添加即可。
- __Vectors:
- .long __INITIAL_SP /* Initial Stack Pointer */
- .long Reset_Handler /* Reset Handler */
- .long NMI_Handler /* -14 NMI Handler */
- .long HardFault_Handler /* -13 Hard Fault Handler */
- .long MemManage_Handler /* -12 MPU Fault Handler */
- .long BusFault_Handler /* -11 Bus Fault Handler */
- .long UsageFault_Handler /* -10 Usage Fault Handler */
- .long SecureFault_Handler /* -9 Secure Fault Handler */
- .long 0 /* Reserved */
- .long 0 /* Reserved */
- .long 0 /* Reserved */
- .long SVC_Handler /* -5 SVCall Handler */
- .long DebugMon_Handler /* -4 Debug Monitor Handler */
- .long 0 /* Reserved */
- .long PendSV_Handler /* -2 PendSV Handler */
- .long SysTick_Handler /* -1 SysTick Handler */
-
- /* Interrupts */
- .long Interrupt0_Handler /* 0 Interrupt 0 */
- .long Interrupt1_Handler /* 1 Interrupt 1 */
- .long Interrupt2_Handler /* 2 Interrupt 2 */
- .long Interrupt3_Handler /* 3 Interrupt 3 */
- .long Interrupt4_Handler /* 4 Interrupt 4 */
- .long Interrupt5_Handler /* 5 Interrupt 5 */
- .long Interrupt6_Handler /* 6 Interrupt 6 */
- .long Interrupt7_Handler /* 7 Interrupt 7 */
- .long Interrupt8_Handler /* 8 Interrupt 8 */
- .long Interrupt9_Handler /* 9 Interrupt 9 */
-
- .space (470 * 4) /* Interrupts 10 .. 480 are left out */
- __Vectors_End:
- .equ __Vectors_Size, __Vectors_End - __Vectors
- .size __Vectors, . - __Vectors
我们在100次中断即1S进行一次翻转LED,肉眼可以看到翻转频率对不对,然后使用逻辑分析仪测试周期。看到周期误差是0.068mS/1000mS是非常准确的,因为这里还有程序处理时间,使用的逻辑分析仪采样只有12MHz等有误差等。
可以确认CLK SYSTICK等相关寄存器是否正确配置
参考参考手册《8.Clock Generation Circuit》,其他章节也按需参考。
总结注意CLK寄存器配置需要先解除写保护,寄存器的操作要注意顺序,一般是先配置再等待稳定最后再切换时钟源。
注意一般要配置FLASH等待周期,本MCU在ICLK不超过50MHz时FLASH可以0等待周期,我们大于50MHz需要设置为1。
注意SYSTICKCLK可以选择不同的时钟源,手册中有一些描述写的只能是LOCO是不对的。
我们以上完全通过阅读手册,操作寄存器从零开始配置时钟,这样就会对时钟有一个比较深入的理解,代码也简洁清晰,如果用fsp生成的代码则过于复杂干扰因素太多,知其然不知其所以然,做嵌入式一定任何事都要了然于胸尤其是初学者,所以本文尽可能写的详细仅供参考。