上一篇博客复习了C语言,这一篇博客主要是学习STM32F407中的总线与时钟,这一部分对计算机组成原理的知识有一定的要求,
这一部分极其枯燥,但是十分重要,望仔细学习
学习资料来自:STM32F407最小系统板开发指南-库函数版本_V1.1.pdf
正点原子,感谢原子哥的开源奉献
正点原子资料下载中心
STM32单片机学习资料均来自 正点原子 ,仅用于学习,如有侵权请联系我删除
本博客内容原创,创作不易,转载请注明
本文链接
个人博客:https://ronglin.fun/?p=115
PDF链接:见博客网站
CSDN: https://blog.csdn.net/RongLin02/article/details/121308080
STM32F4 的总线架构比 51 单片机就要强大很多了。
STM32F4 总线架构的知识可以在《STM32F4XX 中文参考手册》
第二章有讲解,这里我们也把这一部分知识抽取出来讲解,是为了大家在学习 STM32F4 之前对系统架构有一个初步的了解。
这里的内容基本也是从中文参考手册中参考过来的,让大家能通过我们手册也了解到,免除了到处找资料的麻烦吧。
如果需要详细深入的了解 STM32 的系统架构,还需要多看看《STM32F4XX 中文参考手册》或者在网上搜索其他相关资料学习。
我们这里所讲的 STM32F4 系统架构主要针对的 STM32F407 系列芯片。
主系统由 32 位多层 AHB
总线矩阵构成。
总线矩阵用于主控总线之间的访问仲裁管理。仲裁采取循环调度算法。总线矩阵可实现以下部分互联:
八条主控总线是:
下面我们具体讲解一下图中几个总线的知识。
对于系统架构的知识,在刚开始学习 STM32 的时候只需要一个大概的了解,大致知道是个什么情况即可。对于寻址之类的知识,这里就不做深入的讲解,中文参考手册都有很详细的讲解。
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 是外部时钟源,其他的是内部时钟源。
其余细节部分请查看文档
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
这部分内容十分复杂,对模电和数电的知识储备有要求,内容很多就不再贴出来。
上一部分,说明了系统复位之后调用 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=