——基于STM32F107VCT6的理解
内部高速时钟是芯片自带的时钟,芯片自带时钟有两个HIS和LSI(内部低速时钟:40kHZ),内部时钟是RC振荡器产生的,不够稳定。一般不长时间作为系统时钟使用,一般做备用,或在切换时使用。
STM32提供两组外部时钟接口,HSE和LSE(外部低速时钟)。外部时钟的大小由外部所接晶振确定(本文HSE=25MHZ,LSE=32.768kHZ),所以较为准确。
从图中可知SYSCLK(系统时钟)的来源可以是SHE、SHI和PLLCLK(分频器1),这里通过SW(两位二进制数00、01、10、11)来选择,具体可参考《STM32F10xxx参考手册》第88页,CSS的作用是用来检测HSE是否正常的。HSE和SHI都是固定不变的(硬件不变的情况下),所以想要设置不同的时钟频率出来一般选择PLLCLK作为系统时钟。PLLCKL的时钟源有3中不同的情况:(1)SHI经过2分频,经过PLLSCR选择(一位二进制数:0选择图中上面的路线,1选择下面路线);(2)SHE经过PREDIV1SCR选择(0选择上面路线,1选择下面路线),在经过PREDIV1(分频器1),再经过PLLSCR选择;(3)SHE经过PREDIV2分频,进入PLL2MUL倍频,经过PREDIV1SCR选择(0选择上面路线,1选择下面路线),在经过PREDIV1(分频器1),再经过PLLSCR选择。三种情况如下:
对于情况(1)和(2)产生的的时钟频率相对较少,有多种的情况都不能产生,而情况(3)则有多种选择,不能产生的频率,也可以产生一个误差小于0.1MZH与目标频率相近的评率。本文采用(3)这条路线。
(1)、切换内部时钟:SHI使能(SHION写1)、等待SHI稳定(SHIDRY被硬件置1)、系统时钟切换为内部时钟(SW写00)、等待切换成功(SWS被硬件置00);
说明:若某条线路被作为了SYSCLK,则改线路的所有的配置都不能被更改,只能暂时切换到其他路线。SHI只有被使能了才能切换成功。
(2)、配置相关分频、倍频系数。关闭PLL2,PLL使能(PLLON、PLL2ON写0)选择PREDIV2、PREDIV1的分频系数,选着PLLMUL2、PLLMUL的倍频系数,PREDIV1SCR、PLLSCR的时钟源;打开SHE(SHEON写1),等待SHE稳定(SHERDY被硬件写1),打开PLL2,PLL使能(PLLON、PLL2ON写1),等待准备就绪(PLLRDY、PLL2RDY被硬件写1);
(3)、SYSCLK切换成PLLCLK。SYSCLK由SHI切换成PLLCLK(SW写10),等待切换成功(SWS被置为10)
方案一:通过运算得到与目标频率最相近的分频、倍频方案,并进行设置。
优点:只要改变带入参数的类型,可计算产生小数的频率;
缺点:每次都要进行计算,运算量大,要时间计算;
//1、寄存器的声明定义(只声明用到的)
#define RCC_CR *(uint32_t *)0x40021000
#define RCC_CFGR *(uint32_t *)0x40021004
#define RCC_CFGR2 *(uint32_t *)0x4002102c
/**********************************
//重设系统时钟函数
//可设置范围:4-72MHZ
//带入参数: 需要设置的系统时钟频率
//带入参数类型:int
//返回值:实际设置的成的系统时钟频率
//注意事项:
//1、低于4MHZ的不能设置,程序在
// while((RCC_CR & 0x0a000000) != 0x0a000000 );死循环
//2、高于72MHZ系统时钟设置为72MHZ
***********************************/
double ResetSysClK(int Sysclk_Value)
{
unsigned char PLLMUL,PLLMUL2,M1,M2,D1,D2;
unsigned int DIV1,DIV2;
double clk_set,clk_temp,abs_clk_temp,abs_clk_min = 72.0;
float MUL[7] = {4,5,6,7,8,9,6.5};
float MUL2[9] = {8,9,10,11,12,13,14,16,20};
unsigned int MUL_Code[7] = {2,3,4,5,6,7,13};
unsigned int MUL2_code[9] = {6,7,8,9,10,11,12,14,15};
for(M1 = 0;M1 < 7;M1++) //PLLMUL[8]
for(M2 = 0;M2 < 9;M2++)//PLL2MUL
for(D1 = 1; D1 < 17;D1++)//PREDIV1[16]
for(D2 = 1;D2 < 17 ;D2++)//PREDIV1[16]
{
//计算当前组合的时钟频率
clk_temp = (25.0*MUL[M1]*MUL2[M2]) / ((double)D1*(double)D2);
//时钟频率在1-72MZH的组合计进入判断
if( clk_temp<=72.0 && clk_temp >=1.0 )
{
//计算当前组合与想要得到的时钟频率差
abs_clk_temp = Sysclk_Value - clk_temp;
//若当前组合产生的时钟频率就是想要的时钟频率
//则不用继续遍历后面的组合,跳出所有循环
if(abs_clk_temp == 0.0)
{
clk_set = clk_temp;
PLLMUL = M1;
PLLMUL2 = M2;
DIV1 = D1;
DIV2 = D2;
M1 = M2 = 10;
D1 = 20;
break;//只能跳出最小的循环
}
if(abs_clk_temp < 0.0)
abs_clk_temp = (-abs_clk_temp);
//遍历所有组合找出时钟频率组合与目标时钟频率相差最小的组合
if(abs_clk_temp < abs_clk_min)
{
//保留当前更合适的组合
abs_clk_min = abs_clk_temp;
clk_set = clk_temp;
PLLMUL = M1;
PLLMUL2 = M2;
DIV1 = D1;
DIV2 = D2;
}
}
}
//HSI时钟使能
RCC_CR = ((RCC_CR | 0x00000001));
//延时直到HSI准备就绪
while((RCC_CR & 0x00000002) == 0);
//切换内部时钟HSI
RCC_CFGR = (RCC_CFGR & 0xfffffffc);
while((RCC_CFGR & 0x0000000c) != 0);
//HSE使能(打开)
RCC_CR = (RCC_CR & 0xfffeffff)|0x00010000;
while((RCC_CR & 0x00020000) != 0x00020000);
//关闭PLLON和PLL2ON
RCC_CR = (RCC_CR & 0xf0ffffff);
//设置MUL的倍率、PLLSRC(选择PREDIV1输出作为PLL输入时钟)
RCC_CFGR = (RCC_CFGR&0xffc1ffff)|0x00010000|(MUL_Code[PLLMUL]<<18);
//设置MUL2、DIV2、DIV1、PREDIV1SRC(PLL2作为PREDIV1的时钟源)
RCC_CFGR2 = (RCC_CFGR2 & 0xfffef000)|0x00010000|( MUL2_code[PLLMUL2]<<8)|( (DIV2-1)<<4 )|(DIV1-1);
//PLL、PLL2使能
RCC_CR |= 0x05000000;
/* Wait till PLL2 and PLL is ready */
while((RCC_CR & 0x0a000000) != 0x0a000000 );
/* Select PLL as system clock source */
RCC_CFGR |= 0x00000002;
/* Wait till PLL is used as system clock source */
while ((RCC_CFGR & 0x00000008) != (uint32_t)0x08);
return clk_set;
}
方案二:预先将能4-72MHZ的整数频率的最佳方案计算出来,放入数组。
优点:可以直接通过查表得到分频倍频信息,不用计算运行时间短;
缺点:只能输入只能是4-72的整数。代码量较多。
#define *RCC_CR (uint32_t *)0x40021000
#define *RCC_CFGR (uint32_t *)0x40021004
#define *RCC_CFGR2 (uint32_t *)0x4002102c
//对应位的编码数组
//其二进制码经过位移合并到相应位就可以了
//顺序:MUL_Code、MUL2_Code、DIV1_Code、DIV2_Code
int data[69][4] = {
{2, 7, 14, 14,},
{2, 6, 9, 15,},
{2, 7, 9, 14,},
{5, 7, 14, 14,},
{2, 6, 9, 9,},
{2, 7, 9, 9,},
{2, 6, 4, 15,},
{2, 9, 9, 9,},
{2, 7, 4, 14,},
{2, 11, 9, 9,},
{2, 12, 9, 9,},
{2, 7, 3, 14,},
{2, 6, 4, 9,},
{5, 12, 8, 15,},
{2, 7, 4, 9,},
{7, 11, 10, 13,},
{2, 6, 3, 9,},
{4, 12, 9, 9,},
{2, 9, 4, 9,},
{7, 7, 7, 10,},
{2, 10, 4, 9,},
{2, 6, 1, 15,},
{2, 11, 4, 9,},
{4, 7, 4, 9,},
{2, 12, 4, 9,},
{3, 11, 3, 13,},
{2, 7, 1, 14,},
{2, 11, 2, 13,},
{2, 6, 4, 4,},
{4, 9, 4, 9,},
{5, 12, 5, 11,},
{2, 12, 3, 9,},
{2, 7, 4, 4,},
{5, 9, 3, 12,},
{7, 11, 6, 10,},
{4, 11, 4, 9,},
{2, 6, 1, 9,},
{2, 14, 2, 12,},
{4, 12, 4, 9,},
{3, 9, 1, 15,},
{2, 9, 4, 4,},
{2, 7, 1, 9,},
{7, 7, 3, 10,},
{13, 11, 2, 14,},
{2, 10, 4, 4,},
{5, 12, 4, 9,},
{2, 6, 0, 15,},
{3, 15, 6, 6,},
{2, 11, 4, 4,},
{3, 12, 2, 10,},
{4, 7, 4, 4,},
{2, 9, 1, 9,},
{2, 12, 4, 4,},
{5, 11, 3, 9,},
{3, 11, 1, 13,},
{4, 9, 1, 13,},
{2, 7, 0, 14,},
{4, 11, 1, 15,},
{2, 11, 2, 6,},
{5, 7, 4, 4,},
{2, 14, 4, 4,},
{2, 11, 1, 9,},
{4, 9, 4, 4,},
{3, 12, 1, 12,},
{5, 12, 2, 11,},
{5, 11, 2, 10,},
{2, 12, 1, 9,},
{5, 11, 1, 15,},
{4, 10, 4, 4,},
};
//实际设置成功的频率数组
//与上面一一对应
float set_clk[69]={
4.000000, 5.000000, 6.000000, 7.000000,
8.000000, 9.000000, 10.000000, 11.000000,
12.000000, 13.000000, 14.000000, 15.000000,
16.000000, 17.013889, 18.000000, 18.993506,
20.000000, 21.000000, 22.000000, 23.011364,
24.000000, 25.000000, 26.000000, 27.000000,
28.000000, 29.017857, 30.000000, 30.952381,
32.000000, 33.000000, 34.027778, 35.000000,
36.000000, 37.019231, 37.987013, 39.000000,
40.000000, 41.025641, 42.000000, 42.968750,
44.000000, 45.000000, 46.022727, 46.944444,
48.000000, 49.000000, 50.000000, 51.020408,
52.000000, 53.030303, 54.000000, 55.000000,
56.000000, 56.875000, 58.035714, 58.928571,
60.000000, 60.937500, 61.904762, 63.000000,
64.000000, 65.000000, 66.000000, 67.307692,
68.055556, 68.939394, 70.000000, 71.093750,
72.000000 };
/*重设系统时钟函数*/
float ResetSysClK1(int Sysclk_Value)
{
//HSI时钟使能
*RCC_CR = ((*RCC_CR | 0x00000001));
//延时直到HSI准备就绪
while((*RCC_CR & 0x00000002) == 0);
//切换内部时钟HSI
* RCC_CFGR = (*RCC_CFGR & 0xfffffffc);
while((*RCC_CFGR & 0x0000000c) != 0);
//HSE使能(打开)
* RCC_CR = (*RCC_CR & 0xfffeffff)|0x00010000;
while((*RCC_CR & 0x00020000) != 0x00020000);
//关闭PLLON和PLL2ON
*RCC_CR = (*RCC_CR & 0xf0ffffff);
//设置MUL的倍率、PLLSRC(选择PREDIV1输出作为PLL输入时钟)
*RCC_CFGR = (*RCC_CFGR&0xffc1ffff)|0x00010000|(data[Sysclk_Value-4][0]<<18);
//设置MUL2、DIV2、DIV1、PREDIV1SRC(PLL2作为PREDIV1的时钟源)
*RCC_CFGR2 = (*RCC_CFGR2 & 0xfffef000)|0x00010000|( data[Sysclk_Value-4][1]<<8)|( data[Sysclk_Value-4][3]<<4 )|data[Sysclk_Value-4][2];
//PLL、PLL2使能
*RCC_CR |= 0x05000000;
/* Wait till PLL2 and PLL is ready */
while((*RCC_CR & 0x0a000000) != 0x0a000000 );
/* Select PLL as system clock source */
*RCC_CFGR |= 0x00000002;
/* Wait till PLL is used as system clock source */
while ((*RCC_CFGR & 0x00000008) != (uint32_t)0x08);
return set_clk[Sysclk_Value-4];
}