前言
上一篇博客复习了C语言,这一篇博客主要是学习STM32F407中的总线与时钟,这一部分对计算机组成原理的知识有一定的要求,
这一部分极其枯燥,但是十分重要,望仔细学习
学习资料来自:STM32F407最小系统板开发指南-库函数版本_V1.1.pdf 正点原子,感谢原子哥的开源奉献 正点原子资料下载中心
STM32单片机学习资料均来自 正点原子 ,仅用于学习,如有侵权请联系我删除 本博客内容原创,创作不易,转载请注明
总线
STM32F4 的总线架构比 51 单片机就要强大很多了。 STM32F4 总线架构的知识可以在《STM32F4XX 中文参考手册》 第二章有讲解,这里我们也把这一部分知识抽取出来讲解,是为了大家在学习 STM32F4 之前对系统架构有一个初步的了解。 这里的内容基本也是从中文参考手册中参考过来的,让大家能通过我们手册也了解到,免除了到处找资料的麻烦吧。 如果需要详细深入的了解 STM32 的系统架构,还需要多看看《STM32F4XX 中文参考手册》或者在网上搜索其他相关资料学习。 我们这里所讲的 STM32F4 系统架构主要针对的 STM32F407 系列芯片。
架构图
首先我们看看STM32 的总线架构图:
主系统由 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 通过此总线向存储器加载/存储数据。
对于系统架构的知识,在刚开始学习 STM32 的时候只需要一个大概的了解,大致知道是个什么情况即可。对于寻址之类的知识,这里就不做深入的讲解,中文参考手册都有很详细的讲解。
时钟
STM32F4 时钟系统的知识在《STM32F4 中文参考手册》第六章复位和时钟控制章节有非常详细的讲解,网上关于时钟系统的讲解也基本都是参考的这里,讲不出啥特色,这些知识也不是什么原创,纯粹根据官方提供的中文参考手册和自己的应用心得来总结的,如有不合理之处望大家谅解。 这部分对于计算机组成原理的知识有一定的要求
STM32F4 时钟树概述
众所周知,时钟系统是 CPU 的脉搏,就像人的心跳一样。所以时钟系统的重要性就不言而喻了。 STM32F4 的时钟系统比较复杂,不像简单的 51 单片机一个系统时钟就可以解决一切。 于是有人要问,采用一个系统时钟不是很简单吗?为什么 STM32 要有多个时钟源呢? 因为首先 STM32 本身非常复杂,外设非常的多,但是并不是所有外设都需要系统时钟这么高的频率,比如看门狗以及 RTC 只需要几十 k 的时钟即可。同一个电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱,所以对于较为复杂的 MCU 一般都是采取多时钟源的方法来解决这些问题。
总述
首先让我们来看看 STM32F4 的时钟系统图 在 STM32F4 中,有 5 个最重要的时钟源,为 HSI、HSE、LSI、LSE、PLL 。 其中 PLL 实际是分为两个时钟源,分别为主 PLL 和专用 PLL。 从时钟频率来分可以分为高速时钟源和低速时钟源,在这 5 个中 HSI,HSE 以及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。 从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中 HSE 和LSE 是外部时钟源,其他的是内部时钟源。
功能讲解
- LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗和自动唤醒单元使用。
- LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
- HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为 4MHz~26MHz。我们的开发板接的是 8M 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
- HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作 PLL
输入。 - PLL 为锁相环倍频输出。STM32F4 有两个 PLL:
- 主 PLL(PLL)由 HSE 或者 HSI 提供时钟信号,并具有两个不同的输出时钟。
第一个输出 PLLP 用于生成高速的系统时钟(最高 168MHz) 第二个输出 PLLQ 用于生成 USB OTG FS 的时钟(48MHz),随机数发生器的时钟和 SDIO 时钟。 - 专用 PLL(PLLI2S)用于生成精确时钟,从而在 I2S 接口实现高品质音频性能。
其余细节部分请查看文档
STM32F4 时钟初始化配置
STM32F4 时钟系统初始化是在 system_stm32f4xx.c 中的 SystemInit() 函数中完成的。 对于系统时钟关键寄存器设置主要是在SystemInit 函数中调用 SetSysClock() 函数来设置的。 在设置完相关寄存器后,接下来 SystemInit 函数内部会调用 SetSysClock 函数。
在配置的时候,特别需要注意的地方,就是我们还要同步修改 stm32f4xx.h 中宏定义标识符HSE_VALUE 的值为我们的外部时钟:
#if !defined (HSE_VALUE)
#define HSE_VALUE ((uint32_t)8000000) /*!< Value of the External oscillator in Hz */
#endif /* HSE_VALUE */
这里默认固件库配置的是 25000000 ,我们外部时钟为 8MHz,所以我们根据我们硬件情况修改为 8000000 即可。 那么 SystemInit 函数是怎么被系统调用的呢? SystemInit 是整个设置系统时钟的入口函数。这个函数对于我们使用 ST提供的 STM32F4 固件库的话,会在系统启动之后先执行 main 函数,然后再接着执行 SystemInit 函数实现系统相关时钟的设置。 这个过程设置是在启动文件 startup_stm32f40_41xxx.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
这段代码的作用是在系统复位之后引导进入 main 函数,同时在进入 main 函数之前,首先要调用 SystemInit 系统初始化函数完成系统时钟等相关配置。 最后我们总结一下 SystemInit() 函数中设置的系统时钟大小:
SYSCLK(系统时钟) =168MHz
AHB 总线时钟(HCLK=SYSCLK) =168MHz
APB1 总线时钟(PCLK1=SYSCLK/4) =42MHz
APB2 总线时钟(PCLK2=SYSCLK/2) =84MHz
PLL 主时钟 =168MH
这部分内容十分复杂,对模电和数电的知识储备有要求,内容很多就不再贴出来。
STM32F4 时钟使能和配置
上一部分,说明了系统复位之后调用 SystemInit 函数之后相关时钟的默认配置。 如果在系统初始化之后,我们还需要修改某些时钟源配置,或者我们要使能相关外设的时钟该怎么设置呢?这些设置实际是在 RCC 相关寄存器中配置的。因为 RCC 相关寄存器非常多,有兴趣的同学可以直接打开《STM32F4 中文参考手册》6.3 小节查看所有 RCC 相关寄存器的配置。 所以这里我们不直接讲解寄存器配置,而是通过 STM32F4 标准固件库配置方法给大家讲解。 在 STM32F4 标准固件库里,时钟源的选择以及时钟使能等函数都是在 RCC 相关固件库文件 stm32f4xx_rcc.h 和 stm32f4xx_rcc.c 中声明和定义的。大家打开 stm32f4xx_rcc.h 文件可以看到文件开头有很多宏定义标识符,然后是一系列时钟配置和时钟使能函数申明。这些函数大致可以归结为三类,一类是外设时钟使能函数,一类是时钟源和分频因子配置函数,还有一类是外设复位函数。当然还有几个获取时钟源配置的函数。 下面我们以几种常见的操作来简要介绍一下这些库函数的使用。
时钟使能函数
首先是时钟使能函数。时钟使能相关函数包括外设设置使能和时钟源使能两类。首先我们来看看外设时钟使能相关的函数:
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
void RCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
这里主要有 5 个外设时钟使能函数。5 个函数分别用来使能 5 个总线下面挂载的外设时钟,这些总线分别为:AHB1 总线,AHB2 总线,AHB3 总线,APB1 总线以及 APB2 总线。要使能某个外设,调用对应的总线外设时钟使能函数即可。 这里我们要特别说明一下,STM32F4 的外设在使用之前,必须对时钟进行使能,如果没有使能时钟,那么外设是无法正常工作的。对于哪个外设是挂载在哪个总线之下,虽然我们也可以查手册查询到,但是这里如果大家使用的是库函数的话,实际上是没有必要去查询手册的,这里我们给大家介绍一个小技巧。 如何查看? 比如我们要使能 GPIOA, 我们只需要在 stm32f4xx_rcc.h 头文件里面搜索 GPIOA ,就可以搜索到对应的时钟使能函数的第一个入口参数为 RCC_AHB1Periph_GPIOA ,从这个宏定义标识符一眼就可以看出,GPIOA 是挂载在 AHB1 下面。 同理,对于串口 1 我们可以搜索 USART1 ,找到标识符为 RCC_APB2Periph_USART1 ,那么很容易知道串口 1 是挂载在 APB2 之下。 如何调用? 如果我们要使能 GPIOA ,那么我们可以在头文件 stm32f4xx_rcc.h 里面查看到宏定义标识符 RCC_AHB1Periph_GPIOA ,顾名思义 GPIOA 是挂载在 AHB1 总线之下,所以,我们调用 AHB1 总线下外设时钟使能函数 RCC_AHB1PeriphClockCmd 即可。 -具体调用方式入如下:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//使能 GPIOA 时钟
同理,如果我们要使能串口 1 的时钟,那么我们调用的函数为:
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
具体的调用方法是:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
还有一类时钟使能函数是时钟源使能函数,前面已经讲解过 STM32F4 有 5 大类时钟源。 这里我们列出来几种重要的时钟源使能函数:
void RCC_HSICmd(FunctionalState NewState);
void RCC_LSICmd(FunctionalState NewState);
void RCC_PLLCmd(FunctionalState NewState);
void RCC_PLLI2SCmd(FunctionalState NewState);
void RCC_PLLSAICmd(FunctionalState NewState);
void RCC_RTCCLKCmd(FunctionalState NewState);
这些函数是用来使能相应的时钟源。比如我们要使能 PLL 时钟,那么调用的函数为:
void RCC_PLLCmd(FunctionalState NewState);
具体调用方法如下:
RCC_PLLCmd(ENABLE);
我们要使能相应的时钟源,调用对应的函数即可。
时钟功能函数
接下来我们要讲解的是第二类时钟功能函数:时钟源选择和分频因子配置函数。这些函数是用来选择相应的时钟源以及配置相应的时钟分频系数。 比如我们之前讲解过系统时钟SYSCLK ,我们可以选择 HSI,HSE 以及 PLL 三个中的一个时钟源为系统时钟。那么到底选择哪一个,这是可以配置的。下面我们列举几种时钟源配置函数:
void RCC_LSEConfig(uint8_t RCC_LSE);
void RCC_SYSCLKConfig(uint32_t RCC_SYSCLKSource);
void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
void RCC_PCLK1Config(uint32_t RCC_HCLK);
void RCC_PCLK2Config(uint32_t RCC_HCLK);
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
void RCC_PLLConfig(uint32_t RCC_PLLSource, uint32_t PLLM,
uint32_t PLLN, uint32_t PLLP, uint32_t PLLQ);
比如我们要设置系统时钟源为 HSI,那么我们可以调用系统时钟源配置函数:
void RCC_HCLKConfig(uint32_t RCC_SYSCLK);
具体配置方法如下:
RCC_HCLKConfig(RCC_SYSCLKSource_HSI);//配置时钟源为 HSI
又如我们要设置 APB1 总线时钟为 HCLK 的 2 分频,也就是设置分频因子为 2 分频,那么如果我们要使能 HSI,那么调用的函数为:
void RCC_PCLK1Config(uint32_t RCC_HCLK);
具体配置方法如下:
RCC_PCLK1Config(RCC_HCLK_Div2);
外设复位函数
接下来我们看看第三类外设复位函数。如下:
void RCC_AHB1PeriphResetCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_AHB2PeriphResetCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
void RCC_AHB3PeriphResetCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
void RCC_APB1PeriphResetCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphResetCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
这类函数跟前面讲解的外设时钟函数使用方法基本一致,不同的是一个是用来使能外设时钟,一个是用来复位对应的外设。这里大家在调用函数的时候一定不要混淆。 对于这些时钟操作函数,我们就不一一列举出来,大家可以打开 RCC 对应的文件仔细了解。
总结
总线与时钟部分让人头大,草草的过了一遍知识点,有点囫囵吞枣的意思了,不过我的目标也是先用起来,暂时不深究,等用到了之后再仔细学习相应的部分,未完待续 =w=
|