大家在学习STM32是,肯定被复杂的时钟搞得晕头转向。只不过在学习了很多内容之后就会忽略这个问题,直到自己需要创建工程,从12M的外部晶振换成8M外部晶振时,总会对程序的异常运行搞得炸开了头,例如串口通信的处理。大家在反复确认过程序的基础配置没有出错之后,有的人只能赞叹科技的玄学,然后把别人的工程拷过来,自己添进去自己的内容。
今天呢,我们就一劳永逸的解决这个问题,从系统初始来解决这个问题,并且介绍一个Systick定时器的实用方法。
0.STM32启动文件 eg:stratup_stmf10x_md.s
STM32的keil工程里,经常会出现像上面eg类似的一个.s文件,这就是一个启动文件,启动文件里有好多东西,其中我们这次感兴趣的内容是这一点
; Reset handlerReset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT __main IMPORT SystemInit LDR R0, = SystemInit BLX R0 LDR R0, =__main BX R0 ENDP
这个就是要接下来说的内容,主要意思是,先执行SysemInit函数,再执行main函数。可能大家对这个有印象,如果有兴趣的话,可以了解一下Thumb汇编指令集,这个我也是只了解一点点,具体我也不太清楚,大家可以有兴趣一起学习。
现在看一看SystemInit函数,
void SystemInit (void){RCC->CR |= (uint32_t)0x00000001;RCC->CFGR &= (uint32_t)0xF8FF0000;RCC->CR &= (uint32_t)0xFEF6FFFF;RCC->CR &= (uint32_t)0xFFFBFFFF;RCC->CFGR &= (uint32_t)0xFF80FFFF;RCC->CIR = 0x009F0000;SetSysClock();}
大家按照这个代码继续读下去,会发现依次进行
SystemTnit();
SrtSysClock();
SetSysClockTo72();
其中进去SetSysClockTo72()函数的原因是,这里定义了SYSCLK_FREQ_72MHz,用来记录系统时钟的主频,72000000
其中SetSysClockTo72()的决定性代码是
这段代码实在是太长了,为了便于观察,我留下需要的部分,上面有关的寄存器是RCC_CR和RCC_CFGR寄存器下面重点解释这两个寄存器。
*********************以上截屏来自于system_stm32f10x.c*****************
1.STM32系列的RCC寄存器
*************截屏来自于STM32中文参考手册****************
SystemInit()函数里的
#ifndef STM32F10X_CL RCC->CFGR &= (uint32_t)0xF8FF0000;可以看到清空了CFGR里的数值关键数值,ADC APB1 APB2 AHB这些时钟总线分频系数部分全部选择不分频,HSI作为系统时钟
RCC->CR &= (uint32_t)0xFEF6FFFF;可以看到0-15全部置为1,第16位为0,第16位在图片中查表得知,是HSE外部高速时钟的使能位
这样可以理解,SystemInit()函数的工作。
实际上,我们经常选择HSE外部时钟作为时钟来源,我们更信任晶振提供周期频率,但是就算是外部时钟,主流的8M.12M时钟也显得太慢了,所以我们会配置PLL倍频,而这就引出了下一个函数SetSysClockTo72(),为什么会选择72M,为什么不能更高或者更低?
我查阅的资料告诉我,频率更高对于STM32F103系列的板子会不稳定,太低又不满足需求。(所以,例如STM32F4,STM32F7系列有更高的主频,甚至有的支持超频,当然我没有试过)
问:72M,我们选择的外部晶振晶振HSE不过是8M,12M怎么达到72M呢?
答:用PLL倍频输出,8M晶振9倍,12M晶振6倍。
问:怎么实现呢?
答:
1054 RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |1055 RCC_CFGR_PLLMULL));
1056 RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
就是这一段,RCC_CFGR_PLLMULL9,9倍频,8M晶振被频出72M,作为系统时钟,提供SYSCLK
RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
这是提供AHB总线时钟,APB1总线时钟,APB2总线时钟
正点原子的: 8M晶振
Onenet麒麟板的 12M晶振
********时钟到此便配置好了,如果这里配置好,就不会出现类似于串口传输乱码的问题了**********
当然喜欢直接配置寄存器的同学,在添加启动文件(.s)时会自己注释掉SystemInit()函数,用直接操作寄存器的方法实现功能配置,这里也就不多说了,反正具体的步骤都是相同的。
2.Systick 滴答定时器
**********初始化滴答定时器**********
void SysTick_Configuration(void){ RCC_ClocksTypeDef rcc_clocks; uint32_t cnts; RCC_GetClocksFreq(&rcc_clocks); SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); cnts = (uint32_t)rcc_clocks.HCLK_Frequency / TICK_PER_SECOND; cnts = cnts/8; SysTick->LOAD = cnts - 1; NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); SysTick->VAL = 0; SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; }Systick作为Cortex-M3内核的内容,定义在core_cm3.h中
*******************截图来自于
这段代码是,先获取系统RCC时钟的信息,然后选取系统频率的8分频,9M作为Systick的时钟,定时为1ms中断一次。
Systick定时器VAL也就是当前值为0,就ROAD里重装值,同时产生中断。
计数值=cnts=72000000/1000/8=9000;
时间t = cnts/时钟频率 = 9000/9000000=0.001s = 1ms
就是这样计算的,大家也可以梳理一下思路。
3.轮询式的程序调度实现
当然是用Systick定时器产生的1ms中断来生产任务调度函数了。不同于FreeRTOS RT-Thread这样的高级的抢占式任务操作系统,我们只是简单的使用它作为轮询式的简易系统。
eg:
//程序运行时间统计typedef struct{ u8 count_1ms; u8 count_2ms; u8 count_5ms; u8 count_50ms; u8 count_100ms; u8 count_500ms; //u8类型最大计数256,所以大家使用时候,不要出现超出计数范围的情况 u8 count_1s;}timer_user;extern timer_user TIMER;void SysTick_Handler(void){ Text_timer();}
void Text_time(){ TIMER.count_1ms++; TIMER.count_2ms++; TIMER.count_5ms++; TIMER.count_100ms++; LED_Status_Display(); if(TIMER.count_2ms == 2) { TIMER.count_2ms = 0; } if(TIMER.count_5ms == 5) { TIMER.count_5ms = 0; } if(TIMER.count_50ms == 50) { TIMER.count_50ms = 0; } if(TIMER.count_100ms == 100) { TIMER.count_100ms =0; TIMER.count_500ms++; LED_Status_Show(); } if(TIMER.count_500ms == 5) { TIMER.count_500ms = 0; TIMER.count_1s++; DT_Send_RESADC_status(); } if(TIMER.count_1s == 2) { TIMER.count_1s = 0; wait_for_translate = 1; //等待系统稳定,开启传输 }}如果有很多任务的时候,这样写可读性会很好,而且在控制任务的执行频率上可以很容易的调整。