文章目录
一、STM32F4 总线架构
STM32F29
的总线架构图如下所示:
主系统由 32 位多层 AHB
总线矩阵构成。总线矩阵用于主控总线之间的访问仲裁管理。仲裁采取循环调度算法。总线矩阵可实现以下部分互联:
-
八条主控总线是:
- Cortex-M4 内核 I 总线, D 总线和 S 总线;
- DMA1 存储器总线, DMA2 存储器总线;
- DMA2 外设总线;
- 以太网 DMA 总线;
- USB OTG HS DMA 总线;
-
七条被控总线:
- 内部 FLASH ICode 总线;
- 内部 FLASH DCode 总线;
- 主要内部 SRAM1(112KB)
- 辅助内部 SRAM2(16KB);
- 辅助内部 SRAM3(64KB) (仅适用 STM32F42xx 和 STM32F43xx 系列器件);
- AHB1 外设 和 AHB2 外设;
- FSMC
下面我们具体讲解一下图中几个总线的知识。
I 总线(S0)
:此总线用于将Cortex-M4
内核的指令总线连接到总线矩阵。内核通过此总线获取指令。此总线访问的对象是包括代码的存储器。D 总线(S1)
:此总线用于将Cortex-M4
数据总线和64KB CCM
数据RAM
连接到总线矩阵。内核通过此总线进行立即数加载和调试访问。S 总线(S2)
:此总线用于将Cortex-M4
内核的系统总线连接到总线矩阵。此总线用于访问位于外设或SRAM
中的数据。DMA 存储器总线(S3,S4)
:此总线用于将DMA
存储器总线主接口连接到总线矩阵。DMA
通过此总线来执行存储器数据的传入和传出。DMA
外设总线:此总线用于将DMA
外设主总线接口连接到总线矩阵。DMA
通过此总线访问AHB
外设或执行存储器之间的数据传输。- 以太网
DMA
总线:此总线用于将以太网DMA
主接口连接到总线矩阵。以太网DMA
通过此总线向存储器存取数据。 USB OTG HS DMA 总线(S7)
:此总线用于将USB OTG HS DMA
主接口连接到总线矩阵。USB OTG HS DMA
通过此总线向存储器加载/存储数据。
二、STM32F4时钟系统
1.STM32F429 时钟树概述
STM32F429
的时钟系统比较复杂,不像简单的51
单片机一个系统时钟就可以解决一切。于是有人要问,采用一个系统时钟不是很简单吗?为什么 STM32
要有多个时钟源呢? 因为首先 STM32
本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及 RTC
只需要几十 k
的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU
一般都是采取多时钟源的方法来解决这些问题。
首先,看看 STM32F429
的时钟系统图。
在 STM32F429
中,有 5 个最重要的时钟源,为 HSI
、HSE
、LSI
、LSE
、PLL
。其中 PLL
实际是分为三个时钟源,分别为主 PLL
和 I2S
部分专用 PLLI2S
和 SAI
部分专用 PLLSAI
。从时钟频率来分可以分为高速时钟源和低速时钟源,在这 5 个中 HSI
,HSE
以及 PLL
是高速时钟,LSI
和 LSE
是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HSE
和 LSE
是外部时钟源,其他的是内部时钟源。
按图中红圈标示的顺序,STM32F429
的这 5 个时钟源介绍如下:
LSI
是低速内部时钟,RC
振荡器,频率为32kHz
左右。供独立看门狗和自动唤醒单元使用。LSE
是低速外部时钟,接频率为32.768kHz
的石英晶体。这个主要是RTC
的时钟源。HSE
是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~26MHz
。
STM32F4
开发板接的是25M
的晶振。HSE
也可以直接做为系统时钟或者PLL
输入。HSI
是高速内部时钟,RC
振荡器,频率为16MHz
。可以直接作为系统时钟或者用作PLL
输入。PLL
为锁相环倍频输出。STM32F4
有三个PLL
:- 主
PLL(PLL)
由HSE
或者HSI
提供时钟信号,并具有两个不同的输出时钟。
第一个输出PLLP
用于生成高速的系统时钟(最高 180MHz)
第二个输出PLLQ
为 48M 时钟,用于USB OTG FS
时钟,随机数发生器的时钟和SDIO
时钟。 - 第一个专用
PLL
(PLLI2S
)用于生成精确时钟,在I2S
和SAI1
上实现高品质音频性能。其中,N
是用于PLLI2S vco
的倍频系数,其取值范围是:192~432
;R
是I2S
时钟的分频系数,其取值范围是:2~7
;Q
是SAI
时钟分频系数,其取值范围是:2~15
;P
没用到。 - 第二个专用
PLL
(PLLSAI
)同样用于生成精确时钟,用于SAI1
输入时钟,同时还为LCD_TFT
接口提供精确时钟。其中,N
是用于PLLSAI vco
的倍频系数,其取值范围是:192~432
;Q
是SAI
时钟分频系数,其取值范围是:2~15
;R
是LTDC
时钟的分频系数,其取值范围是:2~7
;P
没用到。
- 主
这里我们着重看看主 PLL
时钟第一个高速时钟输出 PLLP
的计算方法。STM32F429
主PLL
时钟图如下:
主 PLL
时钟的时钟源要先经过一个分频系数为 M
的分频器,然后经过倍频系数为 N
的倍频器出来之后还需要经过一个分频系数为 P
(第一个输出 PLLP
)或者 Q
(第二个输出 PLLQ
)的分频器分频之后,最后才生成最终的主 PLL
时钟。
例如我们的外部晶振选择 25MHz
,同时我们设置相应的分频器 M=25
,倍频器倍频系数 N=360
,分频器分频系数 P=2
,那么主 PLL
生成的第一个输出高速时钟 PLLP
为:
P L L = 25 M H z ∗ N / ( M ∗ P ) = 25 M H z ∗ 360 / ( 25 ∗ 2 ) = 180 M H z PLL=25MHz * N/ (M*P)=25MHz* 360 /(25*2) = 180MHz PLL=25MHz∗N/(M∗P)=25MHz∗360/(25∗2)=180MHz
如果我们选择HSE
为PLL
时钟源,同时SYSCLK
时钟源为PLL
,那么SYSCLK
时钟为 180MHz
。对于我们后面的实验都是采用这样的配置。
总结:
-
系统时钟
SYSCLK
可来源于三个时钟源:HSI
振荡器时钟HSE
振荡器时钟PLL
时钟
-
STM32F4
时钟信号输出MCO1(PA8)
和MCO2(PC9)
。MCO1
:用户可以配置预分频器(1~5)向MCO1
引脚PA8
输出4个不同的时钟源:
HIS
LSE
HSE
PLLMCO2
:用户可以配置预分频器(1~5)向MCO2
引脚PC9
输出4个不同的时钟源:
HSE
PLL
SYSCLK
PLLI2S
-
注意:任何一个外设在使用前,必须首先使能其相应的时钟。
2.STM32F429 时钟初始化配置
不管是在STM32F4xx
还是在STM32F7xx
的启动文件startup_stm32f429xx.s
中,都有下面一段代码:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
这一段代码的作用是:引导芯片启动之后,先执行 SystemInit
函数,然后再执行main
函数。
在系统启动之后,程序会先执行 HAL
库定义的 SystemInit
函数,进行系统一些初始化配置。那么我们先来看看 SystemInit
函数(在system_stm32f4xx.c
中定义):
void SystemInit(void)
{
/* FPU 设置------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* 复位 RCC 时钟配置为默认配置-----------*/
RCC->CR |= (uint32_t)0x00000001;//打开 HSION 位
RCC->CFGR = 0x00000000;//复位 CFGR 寄存器
RCC->CR &= (uint32_t)0xFEF6FFFF;//复位 HSEON, CSSON and PLLON 位
RCC->PLLCFGR = 0x24003010; //复位寄存器 PLLCFGR
RCC->CR &= (uint32_t)0xFFFBFFFF;//复位 HSEBYP 位
RCC->CIR = 0x00000000;//关闭所有中断
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* 配置中断向量表地址=基地址+偏移地址 ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
#endif
}
从上面代码可以看出,SystemInit
主要做了如下四个方面工作:
FPU
设置- 复位
RCC
时钟配置为默认复位值(默认开始了HIS
) - 外部存储器配置
- 配置中断向量表地址配置
HAL
库的 SystemInit
函数并没有像标准库的 SystemInit
函数一样进行时钟的初始化配置。HAL
库的 SystemInit
函数除了打开 HSI
之外,没有任何时钟相关配置,所以,使用 HAL
库我们必须编写自己的时钟配置函数。首先,我们打开工程模板看看我们在工程 SYSTEM
分组下面定义的 sys.c
文件中的时钟初始化函数 Stm32_Clock_Init
的内容:
//时钟系统配置函数
//Fvco=Fs*(plln/pllm);
//SYSCLK=Fvco/pllp=Fs*(plln/(pllm*pllp));
//Fusb=Fvco/pllq=Fs*(plln/(pllm*pllq));
//Fvco:VCO频率
//SYSCLK:系统时钟频率
//Fusb:USB,SDIO,RNG等的时钟频率
//Fs:PLL输入时钟频率,可以是HSI,HSE等.
//plln:主PLL倍频系数(PLL倍频),取值范围:64~432.
//pllm:主PLL和音频PLL分频系数(PLL之前的分频),取值范围:2~63.
//pllp:系统时钟的主PLL分频系数(PLL之后的分频),取值范围:2,4,6,8.(仅限这4个值!)
//pllq:USB/SDIO/随机数产生器等的主PLL分频系数(PLL之后的分频),取值范围:2~15.
//外部晶振为25M的时候,推荐值:plln=360,pllm=25,pllp=2,pllq=8.
//得到:Fvco=25*(360/25)=360Mhz
// SYSCLK=360/2=180Mhz
// Fusb=360/8=45Mhz
//返回值:0,成功;1,失败
void Stm32_Clock_Init(u32 plln,u32 pllm,u32 pllp,u32 pllq)
{
HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef RCC_OscInitStructure;
RCC_ClkInitTypeDef RCC_ClkInitStructure;
__HAL_RCC_PWR_CLK_ENABLE();