Stm32时钟分析
该分析材料大部分来自opendev论坛,我所做的只不过是加上一些自己的分析和整理,由于个人能力有限,纰漏之处在所难免,欢迎指正。
一、硬件上的连接问题
如果使用内部RC振荡器而不使用外部晶振,请按照如下方法处理:
1)对于100脚或144脚的产品,OSC_IN应接地,OSC_OUT应悬空。
2)对于少于100脚的产品,有2种接法:
i)OSC_IN和OSC_OUT分别通过10K电阻接地。此方法可提高EMC性能。
ii)分别重映射OSC_IN和OSC_OUT至PD0和PD1,再配置PD0和PD1为推挽输出并输出'0'。此方法可以减小功耗并(相对上面i)节省2个外部电阻。
对上图的分析如下:
重要的时钟:
PLLCLK,SYSCLK,HCKL,PCLK1,PCLK2
之间的关系要弄清楚
;
1、
HSI:高速内部时钟信号
stm32单片机内带的时钟
(8M频率
) 精度较差
2、
HSE:高速外部时钟信号
精度高来源
(1)HSE外部晶体
/陶瓷谐振器
(晶振
)
(2)HSE用户外部时钟
3、
LSE:低速外部晶体
32.768kHz主要提供一个精确的时钟源一般作为
RTC时钟使用
在
STM32中,有五个时钟源,为
HSI、
HSE、
LSI、
LSE、
PLL。
①、
HSI是高速内部时钟,
RC振荡器,频率为
8MHz。
②、
HSE是高速外部时钟,可接石英
/陶瓷谐振器,或者接外部时钟源,频率范围为
4MHz~16MHz。
③、
LSI是低速内部时钟,
RC振荡器,频率为
40kHz。
④、
LSE是低速外部时钟,接频率为
32.768kHz的石英晶体。
⑤、
PLL为锁相环倍频输出,其时钟输入源可选择为
HSI/2、
HSE或者
HSE/2。倍频可选择为
2~16倍,但是其输出频率最大不得超过
72MHz。
其中
40kHz的
LSI供独立看门狗
IWDG使用,另外它还可以被选择为实时时钟
RTC的时钟源。另外,实时时钟
RTC的时钟源还可以选择
LSE,或者是
HSE的
128分频。
RTC的时钟源通过
RTCSEL[1:0]来选择。
STM32中有一个全速功能的
USB模块,其串行接口引擎需要一个频率为
48MHz的时钟源。该时钟源只能从
PLL输出端获取,可以选择为
1.5分频或者
1分频,也就是,当需要使用
USB模块时,
PLL必须使能,并且时钟频率配置为
48MHz或
72MHz。
另外,
STM32还可以选择一个时钟信号输出到
MCO脚
(PA8)上,可以选择为
PLL输出的
2分频、
HSI、
HSE、或者系统时钟。
系统时钟
SYSCLK,它是供
STM32中绝大部分部件工作的时钟源。系统时钟可选择为
PLL输出、
HSI或者
HSE。系统时钟最大频率为
72MHz,它通过
AHB分频器分频后送给各模块使用,
AHB分频器可选择
1、
2、
4、
8、
16、
64、
128、
256、
512分频。其中
AHB分频器输出的时钟送给
5大模块使用:
①、
送给AHB总线、内核、内存和DMA使用的HCLK时钟。
②、
通过8分频后送给Cortex的系统定时器时钟。
③、直接送给
Cortex的空闲运行时钟
FCLK。
④、送给
APB1分频器。
APB1分频器可选择
1、
2、
4、
8、
16分频,其输出一路供
APB1外设使用
(PCLK1,最大频率
36MHz),另一路送给定时器
(Timer)2、
3、
4倍频器使用。该倍频器可选择
1或者
2倍频,时钟输出供定时器
2、
3、
4使用。
⑤、送给
APB2分频器。
APB2分频器可选择
1、
2、
4、
8、
16分频,其输出一路供
APB2外设使用
(PCLK2,最大频率
72MHz),另一路送给定时器
(Timer)1倍频器使用。该倍频器可选择
1或者
2倍频,时钟输出供定时器
1使用。另外,
APB2分频器还有一路输出供
ADC分频器使用,分频后送给
ADC模块使用。
ADC分频器可选择为
2、
4、
6、
8分频。
在以上的时钟输出中,有很多是带使能控制的,例如
AHB总线时钟、内核时钟、各种
APB1外设、
APB2外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
需要注意的是定时器的倍频器,当
APB的分频为
1时,它的倍频值为
1,否则它的倍频值就为
2。
连接在
APB1(低速外设
)上的设备有:电源接口、备份接口、
CAN、
USB、
I2C1、
I2C2、
UART2、
UART3、
SPI2、窗口看门狗、
Timer2、
Timer3、
Timer4。注意
USB模块虽然需要一个单独的
48MHz时钟信号,但它应该不是供
USB模块工作的时钟,而只是提供给串行接口引擎
(SIE)使用的时钟。
USB模块工作的时钟应该是由
APB1提供的。
连接在
APB2(高速外设
)上的设备有:
UART1、
SPI1、
Timer1、
ADC1、
ADC2、所有普通
IO口
(PA~PE)、第二功能
IO口。
涉及的寄存器:
RCC 寄存器结构,
RCC_TypeDeff,在文件
“stm32f10x_map.h”中定义如下:
typedef struct
{
vu32 CR; //HSI,HSE,CSS,PLL等的使能
vu32 CFGR; //PLL等的时钟源选择以及分频系数设定
vu32 CIR; // 清除
/使能时钟就绪中断
vu32 APB2RSTR; //APB2线上外设复位寄存器
vu32 APB1RSTR; //APB1线上外设复位寄存器
vu32 AHBENR; //DMA,
SDIO等时钟使能
vu32 APB2ENR; //APB2线上外设时钟使能
vu32 APB1ENR; //APB1线上外设时钟使能
vu32 BDCR; //备份域控制寄存器
vu32 CSR;
} RCC_TypeDef;
这些寄存器的具体定义和使用方式参见芯片手册,因为C语言的开发可以不和他们直接打交道,当然如果能够加以理解和记忆,无疑是百利而无一害。
如果外接晶振为8Mhz,最高工作频率为72Mhz,显然需要用PLL倍频9倍,这些设置都需要在初始化阶段完成。为了方便说明,以例程的RCC设置函数,并用中文注释的形式加以说明:
static void RCC_Config(void)
{
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp();
if (HSEStartUpStatus == SUCCESS)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//上面这句例程中缺失了,但却很关键
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
{}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while (RCC_GetSYSCLKSource() != 0x08)
{}
}
//使能外围接口总线时钟,注意各外设的隶属情况,不同芯片的分配不同,到时候查手册就可以
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE |
RCC_APB2Periph_GPIOF | RCC_APB2Periph_GPIOG |
RCC_APB2Periph_AFIO, ENABLE);
}
由上述程序可以看出系统时钟的设定是比较复杂的,外设越多,需要考虑的因素就越多。同时这种设定也是有规律可循的,设定参数也是有顺序规范的,这是应用中应当注意的,例如PLL的设定需要在使能之前,一旦PLL使能后参数不可更改。
经过此番设置后,对于外置8Mhz晶振的情况下,系统时钟为72Mhz,高速总线和低速总线2都为72Mhz,低速总线1为36Mhz,ADC时钟为12Mhz,USB时钟经过1.5分频设置就可以实现48Mhz的数据传输。
一般性的时钟设置需要先考虑系统时钟的来源,是内部RC还是外部晶振还是外部的振荡器,是否需要PLL。然后考虑内部总线和外部总线,最后考虑外设的时钟信号。遵从先倍频作为CPU时钟,然后在由内向外分频,下级迁就上级的原则。
时钟控制寄存器(RCC_CR)
31~26
25
24
23~20
19
18
17
16
保留
PLLRDY
PLLON
保留
CSSON
HSEBYP
HSERDY
HSEON
eg:RCC->CR|=0x00010000;
//外部高速时钟使能HSEON
RCC->CR|=0x01000000;
//使能PLLON
RCC->CR>>25;
//等待PLL锁定
时钟配置寄存器(RCC_CFGR)
31:27
26:24
23
22
21:18
17
16
保留
MCO[2:0]
保留
USBPRE
PLLMUL[3:0]
PLLXTPRE
PLLSRC
15:14
13:11
10:8
7:4
3:2
1:0
ADCPRE[1:0]
PPRE2[2:0]
PPRE1[2:0]
HPRE[3:0]
SWS[1:0]
SW[1:0]
位26:24
MCO: 微控制器时钟输出 (Microcontroller clock output)
由软件置’1’或清零。
0xx:没有时钟输出;
100:系统时钟(SYSCLK)输出;
101:内部RC振荡器时钟(HSI)输出;
110:外部振荡器时钟(HSE)输出;
111:PLL时钟2分频后输出。
位22
USBPRE:USB预分频 (USB prescaler)
由软件置’1’或清’0’来产生48MHz的USB时钟。在RCC_APB1ENR寄存器中使能USB时钟之前,必须保证该位已经有效。如果USB时钟被使能,该位不能被清零。
0:PLL时钟1.5倍分频作为USB时钟
1:PLL时钟直接作为USB时钟
位21:18
PLLMUL:PLL倍频系数 (PLL multiplication factor)
由软件设置来确定PLL倍频系数。只有在PLL关闭的情况下才可被写入。
注意:PLL的输出频率不能超过72MHz
0000:PLL 2倍频输出 1000:PLL 10倍频输出
0001:PLL 3倍频输出 1001:PLL 11倍频输出
0010:PLL 4倍频输出 1010:PLL 12倍频输出
0011:PLL 5倍频输出 1011:PLL 13倍频输出
0100:PLL 6倍频输出 1100:PLL 14倍频输出
0101:PLL 7倍频输出 1101:PLL 15倍频输出
0110:PLL 8倍频输出 1110:PLL 16倍频输出
0111:PLL 9倍频输出 1111:PLL 16倍频输出
位17
PLLXTPRE:HSE分频器作为PLL输入 (HSE divider for PLL entry)
由软件置’1’或清’0’来分频HSE后作为PLL输入时钟。只能在关闭PLL时才能写入此位。
0:HSE不分频
1:HSE 2分频
位16
PLLSRC:PLL输入时钟源 (PLL entry clock source)
由软件置’1’或清’0’来选择PLL输入时钟源。只能在关闭PLL时才能写入此位。
0:HSI振荡器时钟经2分频后作为PLL输入时钟
1:HSE时钟作为PLL输入时钟。
位15:14
ADCPRE[1:0]:ADC预分频 (ADC prescaler)
由软件置’1’或清’0’来确定ADC时钟频率
00:PCLK2 2分频后作为ADC时钟
01:PCLK2 4分频后作为ADC时钟
10:PCLK2 6分频后作为ADC时钟
11:PCLK2 8分频后作为ADC时钟
位13:11
PPRE2[2:0]:高速APB预分频(APB2) (APB high-speed prescaler (APB2))
由软件置’1’或清’0’来控制高速APB2时钟(PCLK2)的预分频系数。
0xx:HCLK不分频
100:HCLK 2分频
101:HCLK 4分频
110:HCLK 8分频
111:HCLK 16分频
位10:8
PPRE1[2:0]:低速APB预分频(APB1) (APB low-speed prescaler (APB1))
由软件置’1’或清’0’来控制低速APB1时钟(PCLK1)的预分频系数。
警告:软件必须保证APB1时钟频率不超过36MHz。
0xx:HCLK不分频
100:HCLK 2分频
101:HCLK 4分频
110:HCLK 8分频
111:HCLK 16分频
位7:4
HPRE[3:0]: AHB预分频 (AHB Prescaler)
由软件置’1’或清’0’来控制AHB时钟的预分频系数。
0xxx:SYSCLK不分频
1000:SYSCLK 2分频 1100:SYSCLK 64分频
1001:SYSCLK 4分频 1101:SYSCLK 128分频
1010:SYSCLK 8分频 1110:SYSCLK 256分频
1011:SYSCLK 16分频 1111:SYSCLK 512分频
位3:2
SWS[1:0]:系统时钟切换状态 (System clock switch status)
由硬件置’1’或清’0’来指示哪一个时钟源被作为系统时钟。
00:HSI作为系统时钟;
01:HSE作为系统时钟;
10:PLL输出作为系统时钟;
11:不可用。
位1:0
SW[1:0]:系统时钟切换 (System clock switch)
由软件置’1’或清’0’来选择系统时钟源。
00:HSI作为系统时钟;
01:HSE作为系统时钟;
10:PLL输出作为系统时钟;
11:不可用
eg: RCC->CFGR=0x00000400;
//APB1=DIV2;APB2=DIV1(不分频);AHB=DIV1(不分频);
根据STM32库函数设置时钟流程:
RCC_DeInit();
//设置RCC寄存器重新设置为默认值
RCC_HSEConfig(RCC_HSE_ON);
//打开外部高速时钟晶振
HSEStartUpStatus = RCC_WaitForHSEStartUp();
//等待外部高速时钟晶振工作
if(HSEStartUpStatus == SUCCESS)
//外部就绪
{
//Add here PLL ans system clock config
RCC_HCLKConfig(RCC_SYSCLK_Div1); //设置AHB时钟不分频
RCC_PCLK2Config(RCC_HCLK_Div1); //设置APB2时钟不分频
RCC_PCLK1Config(RCC_HCLK_Div2); //设置APB1时钟二分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC时钟六分频
//设置PLL时钟将8M时钟9倍频到72M
RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
RCC_PLLCmd(ENABLE); //使能PLL
FlagStatus Status;
Status = RCC_GetFlagStatus(RCC_FLAG_PLLRDY);
if(Status == RESET)
{
……
}
RCC_SYSCLKConfig(RCC-SYSCLKSource_PLLCLK);
//将PLL输出设置为系统时钟
while(RCC_GetSYSCLKSource()!=0x08) //测试PLL是否被用作系统时钟等待校验完成
{}
}
else
{
//Add here some code to deal with this error
}
//使能外围接口总线时钟
RCC_APB2PeriphClockCmd() / RCC_APB1PeriphClockCmd()
具体配置过程:
第一步:
复位并配置向量表。
函数MYRCC_DeInit();
下面对该函数进行分析:
(1)
设置外设复位寄存器:RCC->APB1RSTR = 0x00000000
该寄存器中包含dac,电源复位,定时器等外设复位设置,某位为1表示对相应外设复位。开机启动时将该寄存器数据清空。
(2)
设置外设复位寄存器:RCC->APB2RSTR = 0x00000000
同第一步外设复位寄存器的设置。
解答:
RCC->APB1RSTR = 0x00000000;//复位结束
RCC->APB2RSTR = 0x00000000;
这里的“复位结束”具体是什么意思??我把它注释掉后发现也是可以运行的
1是复位.0当然是不复位了
不复位那就是复位结束了.
(3)
睡眠模式闪存和sram时钟使能,其他关闭。用于使用
sram。
Sram相当于
pc的内存。
STm32有三种启动模式:
1,ISP模式.这种模式就是STM32复位后就执行固化在内部的BOOTLOADER程序(固化的,我们无法读写.),然后等待串口数据,从而实现串口bootloader功能.
这种模式不会从用户存储区启动(除非用串口控制其从0X08000000启动),所以在更新了代码之后,需要设置为其他模式(FLASH模式).
2,FLASH启动模式.这种模式直接从0X08000000启动,也就是我们自己编写的代码的启动方式了.正常情况都应该用这种.
3,SRAM启动模式.这种模式我没有用过,是从0X20000000启动的,也就是说在sram模式开始之前,你要确保SRAM里面已经有代码了,否则就是死机.
RCC->AHBENR = 0x00000014
(4)
设置外设时钟使能寄存器:
RCC->APB1ENR = 0x00000000;
RCC->APB2ENR = 0x00000000; 将所有外设全部关闭
(5)
使能内部高速HSION。
RCC->CR |=0x00000001;
stm32的时钟启动过程。
启动过程是:
1,首先使用内部时钟(这也是为什么你不接晶振也可以下载代码了)。
2,尝试开启外部时钟
.
3,如果开启成功,则使用外部时钟,否则使用内部。
4,做其他事情。
当然以上代码都需要你自己写代码实现,当然内部时钟是默认的时钟,你不开启也可以
.
(6) 复位SW,HPRE,PPRE1,PPRE2,ADCPRE,MCO
RCC->CFGR &= 0xF8FF0000;
这步有什么意思呢,我的理解是。Cfgr寄存器主要用于对时钟分频的控制,见下图:
通过该步的配置:
首先配置MCO无输出,MCO是什么呢?是指可以将stm32的内部时钟通过IO口引脚输出出去,如上图就可以看到,对cfgr的配置,可以有四种mco输出,分别是将pllclk两分频后输出,hsi(片内时钟)输出等。
其次:配置ADCPRE就是上图中AHB分频器线面的ADC
再次:配置ppre2也就是高速外部时钟APB2,这里设成不分频。高速外部时钟主要驱动一些高速外设,这个在APB2ENR时钟控制寄存器中有介绍
再次:配置PPRE1配置低速外部时钟分频APB1这里也全部设成不分频。
再次:配置HPRE。这几个位主要用来配置AHB这个寄存器的分频系数这里也设置成不分频。也就是说上图SYSCLK经AHB没有分频。
最后:配置SW,以及SWS。表示启用HIS作为系统时钟。
到这一步,经过分析得知,RCC->CFGR &= 0xF8FF0000;主要是用来配置ahb等各个分频器的设置,以及将片内时钟作为系统内部时钟。
(6)
关闭HSEON,CSSON,PLLON
RCC->CR &= 0xFEF6FFFF;
通过分析CR寄存器可以看出,该寄存器主要涉及三个时钟PLL,CSS,HSE。
(7)
复位HSEBYP.
RCC->CR &= 0xFFFBFFFF;这一步有什么作用呢?查询数据手册
57页可知,外部时钟源
HSE有两种模式,
HSEBYP设置为
0时,是选择外部晶体作为外部时钟源这种时钟更加精准,当然也是和外部电路有关的。当然因为第(
6)步已经设置了
HSEON关闭了,所以这一步才可自由设置
HSEBYP。
(8)
复位PLLSRC,PLLXTPRE,PLLMUL and USBPRE
RCC->CFGR &= 0xFF80FFFF;
注意:在这一部中可能会有这样的疑问:
RCC->CFGR &= 0xFF80FFFF;
PLLSRC=0 HSI振荡器时钟经2分频后作为PLL输入时钟
PLLXTPRE=0,HSE分频器作为PLL输入,HSE不分频
这样不冲突吗?
答案是:以最后配置为准,就是最后一次配置会改变前一次的配置,所以说以最后一次配置为准。
也就是说后文还有其他代码对其进行定义。那干嘛还要怎么重复配置呢?
有时候是有用的。比如你想让stm32超频一会,然后又恢复正常运行,这就有用了。
(9)
关闭所有中断
RCC->CIR = 0x00000000;
(10)
配置向量表
#ifndef VECT_TAB_RAM
MY_NVIC_SetVectorTable(NVIC_VectTab_RAM,0x0);
#else
MY_NVIC_SetVectorTable(NVIC_VextTab_FLASH,0x0);
#endif
下面对该函数分析:
//函数功能:设置向量表偏移地址
//NVIC_VectTab:基址
//Offset:偏移量
void MY_NVIC_SetVectorTable(u32 NVIC_VectTab, u32 Offset)
{
//检查参数合法性
assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));
assert_param(IS_NVIC_OFFSET(Offset));
SCB->VTOR = NVIC_VectTab|(Offset & (u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器
//用于标识向量表是在CODE区还是在RAM区
}
前面两行是用来检查参数合法性,这里不作分析。重点看第三行
配置这个向量表有什么用?相见cortexm3权威指南113页向量表的解释
这里
#define NVIC_VectTab_RAM ((u32)0x20000000)
#define NVIC_VectTab_FLASH ((u32)0x08000000)
Offset的值为0x0,为偏移地址,地址必须能被64 * 4 = 256整除,具体请看权威手册113页
SCB->VTOR = NVIC_VectTab|(Offset & (u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器的疑问如下:
SCB->VTOR = NVIC_VectTab|(Offset & (u32)0x1FFFFF80);//设置NVIC的向量表偏移寄存器。
既然是设置NVIC的向量表偏移量,为什么还要和NVIC_VectTab相或呢。只设置OFFSET不就可以了吗,另外VTOR设置只有BIT【28:7】有作用啊,相或以后也放不下这么多位吧?
这个是基址。
那个7~28的,你能定义一个28位的数据出来嘛?
VTOR设置只有BIT【28:7】,你把(u32)0x1FFFFF80二进制看看是不是【28:7】。
然后再看下面一段话:
在
<<权威指南
>>第一百零四页
,有这么一段话
:
NVIC 中有一个寄存器,称为“向量表偏移量寄存器”(在地址0xE000_ED08处),通过修改它的值就能定位向量表。但必须注意的是:向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是2的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有32个中断,则共有32+16(系统异常)=48个向量,向上增大到2的整次幂后值为64,因此地址地址必须能被64*4=256整除,从而合法的起始地址可以是:0x0,
0x100, 0x200等。
向量表偏移量寄存器,也就是SCB->VTOR.它的第29位,用来标识向量表是在CODE区还是RAM区,从而0X1,就是最高3位不去动,这好理解. 但是低位,根据上面这段话的理解,STM32自己有60个中断,加上CM3的16个,总共有76个中断,扩大到2的整次幂,那就是128,然后再乘以4,得到512,也就是0X200.根据这样计算,合法的偏移地址应该是0X0,0X200,0X400,0X600...因此,在此处应该&0X1FFF
FE00.才对.
以上是我的理解.实际上确是&0X1FFF FF80;这点,我也有疑问.
答案:cortex-m3权威指南上介绍
bit 28-7为向量表的起始地址。所以低
7位没有用到,所以
&0X80,为的就是将低七位清零。但这里写
&0X1FFF
FE00,也能达到清零的目的。至于地址必须是512的整数只要offset这个参数注意就可以了。
下面我们回到例说stm32这本书61页的Stm32_Clock_Init()函数:
经过上面配置完毕后,下面开始配置外部时钟。
Ministm32开发板目前的实都是采用高速外部时钟作为时钟源,在经过
MYRCC_Deinit()先将外部时钟源关闭,然后在
cfgr重新配置之后,下面就准备开启高速外部时钟。
(11) RCC->CR |= 0x00010000;外部高速时钟使能HSEON,前面说过以最后一次设置为准,所以自打这一步开始HSE作为了外部时钟。
(12) 等待外部时钟是否就绪
While(!(RCC->CR>>17)); (其实这一步的作用和
while(RCC->CR&(u32)(1<<17));是一样的,因为在
MYRCC_Deinit()中的
18位至
31位全为
0了,当然在论坛中
http://www.openedv.com/posts/list/1943.htm第23楼也承认
While(!(RCC->CR>>17)这样写有点轻率,
23楼这样写道
对此,原子哥也说了写成(RCC-CR>>17)&0X01比较合适,但我感觉RCC-CR>>17是不准确的,比方说如果第十八位是1,那么右移17位后不管时钟是否就绪,表达式“RCC-CR>>17”的结果始终为真,这样while(!(RCC-CR>>17))不就没有意义了吗?所以写成(RCC-CR>>17)&0X01才是最准确的
)
(13) 配置APB1/2=DIV2和AHB = DIV1
RCC->CFGR = 0x00000400;
(14) 设置PLL分频
PLL -=2;
RCC->CFGR = PLL <<18;
设置PLL 9倍频
这里还涉及到了一个问题,如下
其实,这里今天林妹妹问了一个比较专业的问题,那就是PLL是一个u8的数据类型,为什么在这里可以右移18位呢?不是早超出了么?其实,我们看看汇编代码就明白了,汇编代码如下:
219: RCC->CFGR|=PLL<<18; //设置PLL值
2~16 0x08000618 4608 MOV r0,r1 0x0800061A 6840 LDR r0,[r0,#0x04] 0x0800061C EA404084 ORR r0,r0,r4,LSL #18 0x08000620 6048 STR r0,[r1,#0x04]可以看到,这个移位操作,是在R0和R1里面进行的,r0,r1均是32位的寄存器,所以,这里的移位操作并不会产生错误(结果是赋值给32位的寄存器:RCC->CFGR).
(15) FLASH->ACR |= 0x32 //flash 2个延时周期。FLASH->ACR|=0x32是为了使频率匹配,
//具体见《
STM32闪存编程》
(16) 打开PLLON
RCC->CR|=0x01000000;
(17) 等待PLL锁定
while(!((RCC->CR>>25)&0x01));
(18) PLL作为系统时钟
RCC->CFGR |= 0x00000002;
(19) 等待PLL作为系统时钟设置成功
Unsigned char Temp = 0;
While(Temp!=0x02)
{
Temp = RCC->CFGR>>2;
Temp &= 0x03;
}
其实这段代码就是判断SWS,等待系统时钟成功转为PLL时钟。
结合上面的分析已经明了STM32时钟一个始终配置过程,主要流程图如下:
其实个人感觉不用想mini32中自带例程配置有一些没有必要,所以自己改动了一些,发现在跑马灯程序中也能运行,目前只在跑马灯程序中试验过:
第一步:
RCC->APB1RSTR = 0x00000000;//复位结束
RCC->APB2RSTR = 0x00000000;
第二步:
RCC->AHBENR = 0x00000014; //睡眠模式闪存和
SRAM时钟使能
.其他关闭
.
第三步:关闭所有外设时钟
RCC->APB2ENR = 0x00000000; //外设时钟关闭
.
RCC->APB1ENR = 0x00000000;
为什么要这步因为在配置cfgr以及cr等寄存器时,一些外设时钟要关闭。
第四步:
RCC->CR &= 0xFEF2FFFF; //该补的主要作用是开启内部
HSION,且关闭
HSE,
CSS,
PLLON
第五步:设置分频寄存器,配置分频,使能PLLSRC ON
RCC->CFGR=0X00000400; //APB1/2=DIV2;APB2=DIV1;AHB=DIV1;查询中文手册可知,
apb1最大为
36MHZ所以这里要对其分频,因为经过这番设置
PLLMUL输出后为
72MHZ所以为,这里要让
APB1/2=DIV2是
36MHZ。
PLL-=2;//抵消
2个单位
RCC->CFGR|=PLL<<18; //设置
PLL值
2~16 设置
PLL为
9倍频
RCC->CFGR|=1<<16; //PLLSRC ON设置
HSE为输入时钟,因为第
cfgr的
17位也为
0,所以
HSE输入到
PLLSRC的就是
8M
此时hse为8MHZ显然经过上面的9倍频,经分析可知输出到AHB的SYSCLK为72MHZ。因为前面设置AHB不分频,所以AHB输出也是72MHZ。apb1因为前面分频了所以输出后为36MHZ。apb2为72MHZ
第七步:
FLASH->ACR|=0x32; //FLASH 2个延时周期
第八步:
RCC->CIR = 0x00000000; //关闭所有中断
第九步:
//配置向量表
#ifdef VECT_TAB_RAM
MY_NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else
MY_NVIC_SetV