大家在学习STM32是,肯定被复杂的时钟搞得晕头转向。只不过在学习了很多内容之后就会忽略这个问题,直到自己需要创建工程,从12M的外部晶振换成8M外部晶振时,总会对程序的异常运行搞得炸开了头,例如串口通信的处理。大家在反复确认过程序的基础配置没有出错之后,有的人只能赞叹科技的玄学,然后把别人的工程拷过来,自己添进去自己的内容。
今天呢,我们就一劳永逸的解决这个问题,从系统初始来解决这个问题,并且介绍一个Systick定时器的实用方法。
STM32的keil工程里,经常会出现像上面eg类似的一个.s文件,这就是一个启动文件,启动文件里有好多东西,其中我们这次感兴趣的内容是这一点
; Reset handler
Reset_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()的决定性代码是
*********************以上截屏来自于system_stm32f10x.c*****************
*************截屏来自于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()函数,用直接操作寄存器的方法实现功能配置,这里也就不多说了,反正具体的步骤都是相同的。
**********初始化滴答定时器**********
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中
*******************截图来自于<Cortex-M3权威指南中文版>*******************
这段代码是,先获取系统RCC时钟的信息,然后选取系统频率的8分频,9M作为Systick的时钟,定时为1ms中断一次。
Systick定时器VAL也就是当前值为0,就ROAD里重装值,同时产生中断。
计数值=cnts=72000000/1000/8=9000;
时间t = cnts/时钟频率 = 9000/9000000=0.001s = 1ms
就是这样计算的,大家也可以梳理一下思路。
当然是用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; //等待系统稳定,开启传输
}
}
如果有很多任务的时候,这样写可读性会很好,而且在控制任务的执行频率上可以很容易的调整。
原创作品,未经权利人授权禁止转载。详情见转载须知。 举报文章
我要举报该内容理由
×