首页 > 其他分享 >《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章 系统时钟配置实验​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章 系统时钟配置实验​

时间:2022-12-16 19:00:45浏览次数:44  
标签:MPSoc PLL HAL FPGA HSE DFZU2EG 寄存器 时钟 RCC

第十八章 系统时钟配置实验​


如果让你用一句话来形容时钟,你会怎么说?​

A同学说:“时钟是单片机的心脏”;​

B同学说:“军训方队训练时的121”;​

C同学说:“时钟是单片机的能量”;​

D同学说:“早上的闹铃”;​

......​

可见,时钟是多么的重要。​

本章节我们来了解STM32MP1的时钟系统,并分析HAL库中和时钟相关的API函数,然后通过STM32CubeMX插件生成时钟初始化代码,通过分析工程代码去学习STM32MP1的系统时钟初始化步骤。​

本章将分为如下几个小节:​

18.1、认识时钟树;​

18.2、RCC寄存器介绍;​

18.3、时钟相关API函数;​

18.4、配置系统时钟实验;​

19.5、编译和测试;​



18.1 认识时钟树

时钟是单片机运行的基础,相当于心跳,没有时钟,单片机就无法工作。为什么这么说呢?我们知道,寄存器是CPU内部的元件,由锁存器或触发器构成,触发器的置1、清0和置数的功能是靠其外部提供的时钟脉冲来产生跳变的,如果没有这个脉冲信号,电路就没有数字跳变,也就没有时序,单片机也就没有工作。时钟就像是人的心脏,这个恰当的比喻把时钟的重要性概括的淋漓尽致。​

听说过时钟,但时钟树又是啥?前面我们说单片机的运行需要时钟脉冲来驱动,这个脉冲由源始晶体振荡器提供时钟输入,经过各级电路的处理,最终传输给各个设备,设备获得一定时钟频率以后开始工作。时钟的这种传输过程就好比一棵大树的养分供给给其它分支,树上的主干和树枝就好比时钟的总线,树叶就像是外设,时钟就是通过总线传输给设备的。​

18.1.1 时钟源

51单片机的一个系统时钟就可以解决一切,但STM32不一样,STM32有5个输入时钟源(Input Clock)可以用,分为2个外部时钟和3个内部时钟。​

下面我们看看这几种时钟源。​

(1)2个外部时钟源:

  • 高速外部振荡器​ 可通过外接有源或者无源晶振驱动,支持 4 MHz 到 48 MHz 频率范围内的晶振。如果要使用外部晶振,在STM32CubeIDE上配置的时候,请根据板子上外部焊接的晶振频率来配置,正点原子STM32MP157开发板的核心板上外接了24MHz有源晶振。​
  • 低速外部振荡器​ 可通过外接晶振驱动,固件库中默认配置32.768 kHz。正点原子STM32MP157开发板的核心板上外接了32.768KHz无源晶振,主要用于驱动RTC 实时时钟和唤醒功能。​
    (2)3个内部时钟源:
  • 高速内部振荡器​ 频率约为8、16、32、64 MHz,在固件库中默认配置为64 MHz。HSI比HSE具有更快的启动时间,大约几微秒。​
  • 低速内部振荡器​ 频率约为32 kHz,实际值可能会因为电压和温度而变化。主要用于看门狗。​
  • 低功耗内部振荡器频率约是为4MHz,主要用于低功耗。
    以上2个外部时钟源都是由芯片外部晶振产生时钟频率,其精度较高,而内部的时钟源可能因为受到温度的变化,频率发生变化,也就是存在“温飘”的情况,即使经过校准以后,精度也比外部晶振低,对于日常普通的短时间计时,使用内部时钟相差不大,对于较长时间计时就不建议使用内部时钟了。使用内部时钟可以降低成本,例如在一些白色家电中,一些家电产品为了降低成本,把电路板上外接的晶振去掉,程序改用内部时钟来工作。
    大家可能会问,为什么STM32 要有多个时钟源呢?
    我们知道,在同一个电路中,外设开的越多以及频率越高,功耗就越大,同时抗电磁干扰能力也会越弱。在实际的使用中我们并不是所有外设都会用到,有时候只是用到其中的某几个功能,如果打开用不到的外设,功耗会变大,而对于用到的外设,有的外设可能用不到系统时钟那么高的频率,例如看门狗或者RTC只需要几十KHz的时钟就可以工作了。ST在设计芯片的时候也是考虑到了这一点,对复杂的MCU采用多时钟源的方法来解决,并用时钟树来管理,将所有的外设时钟都关掉,用到什么外设,就使能对应的外设时钟即可。下面,我们来看看STM32MP1的时钟树。

18.1.2 时钟树

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟


图18.1.2. 1时钟树框图​

注:​

①图中的 《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_02表示动态时钟选择器,图中的D符号《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_03表示动态选择开关,即时钟选择器允许即时重新配置选择输入的时钟源,两个输入之间的过渡无干扰。仅当目标时钟源准备就绪(启动延迟或PLL锁定后时钟稳定)时,才从一个时钟源切换到另一时钟源。如果选择了尚未准备好的时钟源,则在时钟源准备就绪时进行切换。​

②图中的部分时钟命名:​

hse_ck​

HSE时钟​

lse_ck​

LSE时钟​

hsi_ck​

HSI时钟​

csi_ck​

CSI时钟​

lsi_ck​

LSI时钟​

axiss_ck​

AXI子系统时钟​

mcuss_ck​

MCU子系统时钟​

mpuss_ck​

MPU子系统时钟​

表18.1.2. 1时钟命名​

1. 外部时钟输入

图中处是外部时钟输入引脚,当使用HSE时,HSE在OSC_IN引脚接收外部时钟源,当使用LSE时,LSE在OSC32_IN引脚接收外部时钟源。​

外部时钟

Pin name

外部时钟

Pin name

OSC_IN​

PH0-OSC_IN​

OSC32_IN​

PC14-OSC32_IN​

OSC_OUT​

PH1-OSC_OUT​

OSC32_OUT​

PC15-OSC32_OUT​

表18.1.2. 2外部时钟输入引脚​

2. MCO时钟输出

处是MCO微控制器时钟输出(microcontroller clock output)部分。

  • MCO1和MCO2是时钟输出,通过芯片引脚可以给外部的芯片提供时钟,可以节省晶振,节约成本。MCO1和MCO2时钟输出通过配置RCC_MCO1CFGR寄存器来实现,具体配置可以参考手册详细说明。

时钟输出

Pin name

MCO1​

PA13、PI11、PA8​

MCO2​

PA14、PG2、PC12​


表18.1.2. 3 MCO时钟输出引脚​

  • MCO1SEL和MCO2SEL是时钟源选择器,分别选择MCO1和MCO2的时钟来源;​
  • MCO1DIV和MCO2DIV是时钟分频器(也叫预分频器),取值范围是1~16;​
  • MCO1时钟源来自hse_ck、lse_ck、his_ck、lsi_ck和csi_ck,用于输出HSI,HSE,CSI,LSI或LSE时钟。​
  • MCO2时钟源来自mpuss_ck、axiss_ck、mcuss_ck、pll4_p_ck、hse_ck和hsi_ck,用于输出MPUSS、AXISS、MCUSS、PLL4(PLL4_P)、HSE或HSI时钟。

3. 锁相环PLL

处是和锁相环有关部分

PLL(Phase Locked Loop)锁相环路,其作用是使外部的输入信号与内部的振荡信号的相位同步,在STM32中主要用于分频和倍频。STM32MP1有4个锁相环,分别为PLL1、PLL2、PLL3和PLL4:​

  • PLL1,用于向MPU子系统提供时钟;​
  • PLL2,用于向AXI子系统、DDR和GPU提供时钟;​
  • PLL3,用于向MCU子系统提供时钟并为外设生成内核时钟;​
  • PLL4,用于生成外设的内核时钟。​

是PLL时钟源选择器

锁相环输入时钟源有三个:his_ck、hse_ck 和csi_ck,分别对应HSI、HSE和CSI的时钟。我们可以配置某个锁相环选择对应的时钟源。​

是PLL时钟源分频器

DIVM1、DIVM2和DIVM3和DIVM4分别是PLL1、PLL2、PLL3和PLL4的分频器,取值范围都是1~64,表示将输入时钟源进行分频。​

是锁相环

其中PLL1是一个输入一个输出,PLL2、PLL3和PLL4是一个输入三个输出。​

我们以PLL3为例子分析一下锁相环的内部结构:​

  • VCO表示输出频率;​
  • DIVN表示VCO的倍数;​
  • DIVP、DIVQ和DIVR表示预分频器;​
  • SSCG扩频时钟发生器,可以减少EMI峰值的数量,一般不用设置;​
  • FRACV是分数倍频系数,它和DIVN一起组成PLL的倍频系数,当FRACV等于0的时候表示在整数模式下,大于0的时候表示在小数(或者说分数)模式下。​

PLL输出的频率有两种计算方法,我们以PLL3输出pll3_p_ck为例,其它锁相环的输出计算方法类似,时钟源默认使用HSI,即64MHz。​

在整数模式下使用PLL:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_04


在小数模式下使用PLL:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_05


注:​

手册中公式的(DIVN + 1)其实就相当于我们上面公式中的DIVN,因为STM32CubeMX插件中是这样计算的,我们的公式中就跟STM32CubeMX插件的计算方式一致。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_06


图18.1.2. 2参考手册上的公式​

例如,在整数模式下(FRACV为0),当设置分频器DIVM3为4,DIVN为26,预分频器DIVP为2时,计算出pll3_p_ck为208MHz。如果再设置FRACV的值为10,根据上述计算出pll3_p_ck约为208.009766MHz。在STM32CubeMX上,我们手动配置这几个参数,结果和我们计算的结果一致,如下图所示:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_07


图18.1.2. 3整数模式​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_08


图18.1.2. 4分数模式​

4. 子系统时钟

处是子系统时钟,RCC处理三个子系统时钟:mpuss_ck、axiss_ck和mcuss_ck。其中mpuss_ck给MPU提供时钟,axiss_ck给AXI互联矩阵提供时钟,mcuss_ck给MCU提供时钟。上电复位后,所有的PLL和CSI以及HSE关闭,默认将HSI选择为整个系统的主时钟,因此,子系统时钟默认来自HSI。当系统运行时,用户可以选择子系统的时钟源。​

我们来查看子系统部分的详细框图:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_09


图18.1.2. 5子系统框图​

mpuss_ck的频率最大可以是650MHz或者800MHz,这个根据所用的器件型号决定,STM32MP157A的MOU主频为650MHz,STM32MP157D的MPU主频为800MHz。 axiss_ck时钟最大是266 MHz,其经过AXIDIV分频器分频后分流为几路,给总线提供时钟。mcuss_ck时钟最大为209 MHz,经过MCUDIV分频器分频以后也是分成了几路。​

pclk4和pclk5最大为133 MHz,pclk1、pclk2、pclk3最大为104.5 MHz。​

子系统时钟汇总如下表格所示:​

子系统

时钟来源

分频器

总线时钟

用途

最大值

mpuss_ck​

HSI​

HSE​

pll1_p_ck​

-pll1_p_ck/MPUDIV​



MPU(CA7_SS)时钟​

650MHz​

或​

800MHz​

axiss_ck​

HSI​HSE​

pll2_p_ck​

AXIDIV​

aclk​

AXI桥接器和外设时钟​

266MHz​

hclk5​

AHB5桥接器和外设时钟​

hclk6​

AHB6桥接器和外设时钟​

pclk4​

APB4桥接器和外设时钟​

133MHz​

pclk5​

APB5桥接器和外设时钟​

mcuss_ck​

HSI​

HSE​

CSI​

pll3_p_ck​

MCUDIV​

mcu_ck​

MCU时钟​

209MHz​

fclk_ck​

mcu_systick_ck​

Systick的时钟​

hclk4​

AHB4桥接器和外设时钟​

mlhclk_ck, hclk[3:1]_ck​

AHB1、AHB2、AHB3桥接器和外设时钟​

pclk1​

APB1桥接器和外设时钟​

104.5MHz​

pclk2​

APB2桥接器和外设时钟​

pclk3​

APB3桥接器和外设时钟​

表18.1.2. 4子系统时钟信息表​

5. CPU、AXI总线矩阵和外设时钟

在STM32CubeMX上,我们可以看更具体的时钟树框图,如下图:​

  • A处是时钟源,可选外部时钟或者内部时钟,要用外部时钟的话,必选先使能外部时钟;​
  • B处是锁相环PLL;​
  • C处是内部时钟输出MCO1和MCO2;​
  • 黄色区域是子系统时钟;​
  • 紫色区域是专门给CA7外设提供时钟的总线时钟,分别是AXI、AHB5、AHB6、APB4、APB5,;​
  • 蓝色区域是给MPU和MCU提供时钟,其中,MPU时钟最大是650或800MHz,MCU时钟最大是209MHz,MCU有一个内核外设Systick,频率最大也是209MHz;​
  • 绿色区域中AHB1-4、APB1-3 是提供给 CM4 外设使用的;​
  • 红色区域是内核外设时钟具体配置部分,默认没有使能,是灰色,只要在 STM32CubeMX 使能了对应的外设,对应外设的时钟就会由灰色变成蓝色。​

注:​

RCC为整个电路提供时钟,为了避免造成误解,和参考手册一致,使用以下术语:​

•外设时钟:

外设时钟是RCC提供给外设的时钟,可以为外围设备提供两种时钟:​

–总线接口时钟​

–内核时钟​

这里的外设有分普通的外设和内核外设。外设将从RCC接收一个或多个总线接口时钟,该时钟通常是AHB,APB或AXI总线接口时钟,具体取决于外围设备连接的总线。 ​

某些外设仅需要总线接口时钟(例如GPIOx、ETZPC、EXTI 、IPCC 、WWDG1、DMAx ...)。
一些外设可能还需要专用时钟来处理接口功能,该时钟称为内核时钟。例如,诸如SAI、ETH、FMC、GPU等这些的外设需要生成特定且准确的主时钟频率,这需要专用的内核时钟频率。​

•CPU时钟

CPU时钟是提供给CPU的时钟mpuss_ck。MCU时钟是mpuss_ck。​

•总线矩阵时钟

总线矩阵时钟是提供给不同桥(APB,AHB或AXI)的时钟。​

图中,

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_10

表示APB总线的分频器,里边的数值表示分频因子。《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_11表示是时钟源选择器。​

STM32CubeMX上的时钟树比较形象,很好理解,图中的一些符号可能和我们参考手册上看到的不一样,不过根据字面上的意思可以知道大概对应了哪些设备。我们可以通过STM32CubeMX来配置时钟,然后自动生成时钟的初始化代码。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_12


图18.1.2. 6时钟树​

对于内核外设时钟,我们以SPI为例子来说明一下:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_13


图18.1.2. 7时钟框图​

PKCS (Peripheral Kernel Clock Selection)是外设内核时钟选择单元,用于选择某个外设的内核时钟来源。PCKE(Peripheral ClocKs Enable)是外设时钟使能单元,用于是否启用时钟输出到外设中。​

如上图,SPI6的内核时钟源来自于APB5总线(pclk5)、PLL(PLL3和PLL4)、HSI、CSI和HSE中的某一个,具体是哪一个由PKCS决定。其中,HSI和CSI用于低功耗中,PLL4在需要时具有更高的灵活性,例如,它允许通过PLL3更改MCU总线的频率,而不会影响某些串行接口的速度。​

SPI6的PCKE有两个时钟部分,一个是来自于APB5总线接口的spi_pclk,一个是来自于内核时钟spi_ker_ck。PCKE决定是否启用SPI6的时钟,其它内核外设也是通过PCKE来开启的。由此可见,各个外设时钟基本都是可控的,不用的外设,就关闭对应的时钟,用到的外设,就根据分频器来配置,选择一个适合频率的时钟源,然后再开启外设时钟,这样的设计大大降低了功耗。​

根据STM32CubeMX插件上的时钟树,ST给出了一张总线框图,如下图,从图中可以看出哪些外设挂在哪根总线上,以及总线的最大时钟频率是多少等信息。​

下图中,AXI互联矩阵主要用于Cortex-A7 CPU子系统和高带宽的设备(USBH、ETH、SDMMC1/2、MDMA、GPU和 LTDC等)。MLAHB高性能总线主要用于Cortex-M4子系统和相关的设备(GPIO、ADC、SRAM、OTG、DMA1/2和SDMMC3等)。APB低速总线主要用于低带宽的周边外设(如UART、USB、SPI、I2C等)之间的连接,也称之为外围总线。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_14


图18.1.2. 8 STM32MP157A/D系统总架构图​

6. 时钟安全系统(CSS)

如果启用外部时钟HSE和LSE的话,硬件将启用HSE或者LSE上的CSS。我们在上面的框图以及STM32CubeMX上的时钟树均可以看到CSS模块。如下图,右边的是框图中的部分截图,右边的是STM32CubeMX上具体的截图。图中配置HSE为PLL3的时钟来源,在并将pll3_p_ck(PLL3P)作为MCU子系统的时钟源,可以看到Enable CSS模块自动变为深蓝色,表示使能了CSS。而没有使用的LSE,对应的Enable CSS模块颜色为灰色。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_15


图18.1.2. 9外部时钟CSS​

RCC具有时钟安全系统(CSS),允许应用程序检测外部时钟(HSE、LSE)是否由于意外原因而停止翻转。​

如果使用的是HSE,在检测到故障的情况下,CSS将生成系统重置,并保护BKPSRAM的敏感数据。时钟安全系统可以由应用软件通过HSECSSON位激活,(但无法通过软件清除HSECSSON位),即使将HSEON设置为0,也可以激活HSECSSON位。 当启用并准备好HSE并且HSECSSON设置为“ 1”时,硬件将启用HSE上的CSS。当禁用HSE时, HSE上的CSS会被禁用。​

如果使用的是LSE,如果检测到故障,CSS将生成故障事件,禁用提供给RTC的时钟并保护BKPSRAM的敏感数据。也可以通过应用程序将RCC_BDCR寄存器的LSECSSON位置1来激活LSE上的时钟安全系统(CSS),但必须按照以下顺序在LSE上激活CSS:​

①将LSEON设置为'1',并等待LSERDY ='1';​

②通过RTCSRC字段选择LSE时钟;​

③设置LSECSSON 到“ 1”。​

关于CSS,我们这里不做深入研究,感兴趣的可以查阅参考手册以及ST官网相关介绍。​

18.2 RCC寄存器介绍

下面我们来看看RCC相关的寄存器。RCC寄存器分为RCC TrustZone相关寄存器,RCC振荡器时钟相关寄存器,HSI、CSI、MCO1和MCO2、AXI、AHB、APB、PLL、MPU和MCU相关寄存器,还有外设的内核时钟选择寄存器。这么多寄存器,我们要配置哪个寄存器就查看对应寄存器即可,这里就不一一介绍所有的寄存器了。而且HAL库已经将这些寄存器封装好了,调用HAL库即可控制这些寄存器。​

18.2.1 寄存器分类介绍

1. RCC振荡器时钟相关寄存器

RCC振荡器时钟寄存器有RCC_OCENSETR、RCC_OCENCLRR、RCC_OCRDYR。​

(1)RCC振荡器时钟使能设置寄存器(RCC_OCENSETR

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_16


图18.2.1.1 RCC_OCENSETR寄存器​

RCC_OCENSETR时钟用于使能(HSE、HSI、CSI)时钟,向该寄存器写入0无效,将对应的位置1则开启对应的时钟。如果TZEN = MCKPROT ='1',则只能在安全模式下修改此寄存器。在时钟恢复序列期间,不允许对该寄存器进行写访问。​

该寄存器配置也比较简单,以HSE为例,如要开启HSE,则先将第8位HSEON置1来开启HSE时钟,再根据是否使用旁路模式来配置第10位HSEBYP,如果使用安全模式,则配置HSECSSON。​

(2)RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_17


图18.2.1.2 RCC_OCENCLRR寄存器​

该寄存器用于清除寄存器对应的位,对位写0无效操作,写1表示清除设置的位,也就是关闭对应的时钟。如果TZEN = 1,则只能在安全模式下修改此寄存器。 在时钟恢复序列期间,不允许对该寄存器进行写访问。​

例如,要关闭HSE,则将HSEON位写1即可。其它寄存器操作也类似。 ​

(3)RCC振荡器时钟就绪寄存器(RCC_OCRDYR

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_18


图18.2.1.3 RCC_OCRDYR寄存器​

该寄存器是一个只读访问寄存器,它包含振荡器的状态标志,写该寄存器的话是没有效果的。如果读取某位为0的话,表示该时钟未准备就绪,如果读取某位为1的话,表示该时钟已经准备就绪。例如,如果读取HSERDY为0,表示HSE时钟未准备就绪,如果为1,表示HSE时钟已经准备就绪。​

2. MCO1和MCO2配置寄存器

MCO1和MO2用于配置MCO1和MCO2输出,两个寄存器的配置类似,我们以MCO1为例:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_19


图18.2.1.4 RCC_MCO1CFGR寄存器​

MCO1SEL[2:0]:为MCO1时钟输出选择位,由软件设置和清除。时钟源选择可能会在MCO1上产生毛刺,所以建议仅在复位后配置这些位,然后再使能外部振荡器和PLL。将该位:​

0x0:选择HSI时钟(hsi_ck)作为输出;​

0x1:选择HSE时钟(hse_ck)作为输出;​

0x2:选择CSI时钟(csi_ck)作为输出;​

0x3:选择LSI时钟(lsi_ck)作为输出;​

0x4:选择LSE时钟(lse_ck)作为输出;​

MCO1DIV[3:0]位用于配置MCO1的预分频器,对该位配置:​

0x0:旁路模式;​

0x1:分频值为2;​

0x2:分频值为3;​

...​

0xF:分频值为16.​

MCO1ON位用于控制MCO1输出,将该位写0则关闭MCO1输出,写1则开启MCO1输出。​

3. MPU和MCU时钟源选择寄存器

STM32MP157有两个A7内核和一个M4内核,RCC_MPCKSELR和RCC_MSSCKSELR分别用于选择MPU和MCU的时钟源。我们以MCU为例:​

(1)RCC MCU子系统时钟选择寄存器(RCC_MSSCKSELR

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_20


图18.2.1.5 RCC_MSSCKSELR寄存器​

MCUSSRC[1:0]位用于选择MCU子系统的时钟(MCUSS)来源,通过配置该位可以选择MCUSS可以来自:​

0x0: 配置MCUSS时钟源来自HSI时钟(hsi_ck);​

0x1: 配置MCUSS时钟源来自HSE (hse_ck);​

0x2:配置MCUSS时钟源来自CSI (csi_ck);​

0x3:配置MCUSS时钟来自PLL3 (pll3_p_ck)​

MCUSSRCRDY属于只读位,读取该位为1,表示MCUSS已经准备好,可以切换到MCUSSRC配置的时钟;读取该位为0表示MCUSS未准备好。​


通过软件置1和清除,以配置MCO1的预分频器。 修改该预分频器可能会在MCO1上产生毛刺。 强烈建议仅在复位后才更改该预分频器,然后再使能外部振荡器和PLL。​

有关详细信息,请参见第10.4.4节:时钟输出生成(MCO1和MCO2)。​

(2)RCC MCU时钟分频器寄存器(RCC_MCUDIVR

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_21


图18.2.1.6 RCC_MCUDIVR寄存器​

MCUDIV[3:0]用于配置MCU时钟的分频值,配置该位:​

0x0:MCU子系统时钟(也可以写为MCUSS或者mcuss_ck)不分频;​

0x1:2分频;​

0x2:4分频;​

0x3:8分频;​

...​

0x8:256分频。​

其它配置:512分频​

MCUDIVRDY属于只读位,用于指示是否考虑了新的分频系数,该位:​

:尚未考虑新的除法系数;​

:考虑新的除法系数。(重置后的默认值)​

4. HSI/CSI时钟配置寄存器

HSI和CSI时钟配置寄存器由RCC_HSICFGR和RCC_CSICFGR来控制,用于配置HSI和CSI的时钟源选择和分频器分频值等。​

(1)RCC HSI配置寄存器(RCC_HSICFGR

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_22


图18.2.1.7 RCC_HSICFGR寄存器​

HSIDIV[1:0]用于配置HSI的时钟分频值:​

0x0:配置HSI为1分频,hsi_ck (hsi_ker_ck) = 64 MHz;​

0x1:配置HSI为2分频,hsi_ck (hsi_ker_ck) = 32 MHz;​

0x2:配置HSI为4分频,hsi_ck (hsi_ker_ck) = 16 MHz;​

0x3:配置HSI为8分频,hsi_ck (hsi_ker_ck) = 8 MHz;​

HSICAL[11:0]和HSITRIM[6:0]:用于配置时钟校准调整值,这两位我们可以不用管。​

5. AXI/PLL1/PLL2/PLL3/PLL4时钟选择

AXI/PLL1/PLL2/PLL3/PLL4都有对应的时钟选择寄存器,用于选择其时钟源。​

AXI子系统的时钟可以选择HSI、HSE、PLL2;​

PLL1和PLL2时钟可以来自HSI、HSE;​

PLL3时钟可以来自HSI、HSE、CSI;​

PLL4时钟可以来自HSI、HSE、CSI和I2S_CKIN。​

以上相关时钟源选择配置寄存器的配置也比较简单,我们就不一一举例子了,如需要,大家按照要求来配置即可。​

6. APB、AXI分频寄存器

APB1~APB5以及AXI都有时钟分频器,可以设置时钟分频值。AXI时钟分频器可以配置分频值为1、2、3和4。APB1~APB5分频器的分频值可以配置为2、4、8和16。相关寄存器的配置也比较简单,我们这里就不一一介绍了。​

7. PLL控制和配置寄存器

PLL1~PLL4有专门的控制寄存器和配置寄存器,我们这里以PLL1为例进行介绍。​

(1)RCC PLL1控制寄存器(RCC_PLL1CR

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_23


图18.2.1.8 RCC_PLL1CR寄存器​

PLLON位用于使能PLL1,将该位置1表示使能PLL1,复位表示将PLL1关闭;​

PLL1RDY表示PLL1时钟就绪标志位,属于只读位。该位为1表示PLL1处于锁的状态,为0表示PLL1处于未锁的状态;​

DIVPEN表示PLL1 DIVP分频器输出使能位,通过软件置1和复位,以使能PLL1的pll1_p_ck输出。该位为0,表示pll1_p_ck输出被禁用,为1,表示pll1_p_ck输出启用。为了节省功耗,当不需要pll1_p_ck时,必须将DIVPEN和DIVP设置为'0'。​

DIVQEN是PLL1 DIVQ分频器输出使能位,该位为0,表示pll1_q_ck输出被禁用;该位为1,表示设置pll1_q_ck输出已启用。​

DIVREN表示PLL1 DIVR分频器输出使能位,该位为0,表示pll1_r_ck输出被禁用;该位为1,表示设置pll1_r_ck输出启用。​

(2)PLL1配置寄存器(RCC_PLL1CFGR1和RCC_PLL1CFGR2

PLL1配置寄存器有2个,其它PLL配置寄存器也是有2个。PLL1配置寄存器1(RCC_PLL1CFGR1)如下:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_24


图18.2.1.9 RCC_PLL1CFGR1寄存器​

DIVN[8:0]位用于配置PLL1 VCO的倍数,将该位配置为:​

0x18:分频比为25;​

0x19:分频比为;​

...​

0x31: 分频比为50 (default after reset);​

...​

0x63: 分频比为100。​

PLL1配置寄存器1(RCC_PLL1CFGR2)如下:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_25


图18.2.1.10 RCC_PLL1CFGR2寄存器​

DIVP[6:0]位、DIVQ[6:0]位和DIVR[6:0]位分别用于配置PLL1 DIVP、PLL1 DIVQ和PLL1 DIVR的分频系数,要配置为什么样的分频值,根据手册来配置即可。​

8. PLL小数寄存器

我们前面也说过,锁相环PLL的 FRACV是分数倍频系数,它和DIVN一起组成PLL的倍频系数,当FRACV等于0的时候表示在整数模式下,大于0的时候表示在小数(或者说分数)模式下。每个PLL都有其配置小数寄存器。我们以PLL1为例子。RCC PLL1小数寄存器(RCC_PLL1FRACR)如下:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_26


图18.2.1.11 RCC_PLL1FRACR寄存器​

FRACLE是PLL1分数使能位,该位必须先设置为0然后设置为1,这样,通过从0到“1”的转换将FRACV的内容传输到调制器中。​

FRACV[12:0]是PLL1的乘法因子的小数部分。FRACV可介于0和213-1之间。​

18.2.2 外设时钟使能和关闭

我们要注意,STM32MP1外设的时钟在上电复位以后默认处于关闭状态,即这些外设都不可用,这么做的目的就是为了降低功耗,如果我们要使用某个外设,必须要先打开外设的时钟才可以对外设进行操作。​

在RCC外设相关寄存器中,我们会看到有两种寄存器,一个是以“SETR”结尾的寄存器,一个是以“CLRR”结尾的寄存器,前者是使能寄存器,后者是关闭寄存器。​

例如,APB1总线挂载的设备有窗口看门狗(WWDG)、I2C1~ I2C3、USART2、UART8和TIM2~TIM7等等外设,如果要使用WWDG,就必须先开启WWDG的时钟。对于MCU,APB1外设的时钟由RCC_MC_APB1ENSETR寄存器来开启,其第28位是WWDG时钟使能位,对该位:​

  • 写'0'表示无效操作,读取该位,读到'0'则表示该外设时钟已经被关闭;​
  • 写'1'表示开启外设时钟,读取该位,读取到'1'意味着外设时钟已经开启了。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_27


图18.2.2.1 MC_APB1ENSETR寄存器​

另外一个寄存器RCC_MP_APB1ENCLRR用于关闭APB1外设的时钟,我们看看此寄存器:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_28


图18.2.2.2 MP_APB1ENCLRR寄存器​

可以发现,并没有看到控制WWDG的时钟位,第28位是保留的,而其它外设,例如I2C1~ I2C3、USART2、UART8等对应的位有看到,对这些位:​

  • 写'0'表示无效操作,读取到'0'表示已经关闭外设的时钟了;​
  • 写'1'表示关闭外设时钟,读取到'1'表示已经开启外设时钟了;​

为什么窗口看门狗(WWDG)在RCC_MC_APB1ENCLRR寄存器中没有对应的时钟操作位呢?因为WWDG这个外设比较特殊,只要开启窗口看门狗以后就不能将其关闭了,必须等到系统重置以后才可以禁用窗口看门狗,而其它外设就不一样了,其它外设可以通过对时钟操作位写1来将其时钟关闭。​

在HAL库的stm32mp1xx_hal_rcc.h头文件中有所有外设时钟开启或者关闭宏定义:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_29


图18.2.2.3 外设时钟使能/禁用宏定义​

例如,WWDG部分:​

1 #define __HAL_RCC_WWDG1_CLK_ENABLE() (RCC->MC_APB1ENSETR = \              RCC_MC_APB1ENSETR_WWDG1EN)​
2 #define __HAL_RCC_WWDG1_CLK_DISABLE() (RCC->MC_APB1ENCLRR = \ RCC_MC_APB1ENCLRR_WWDG1EN)​
RCC_MC_APB1ENSETR_WWDG1EN和RCC_MC_APB1ENCLRR_WWDG1EN在stm32mp157axx_cm4.h头文件中有定义:​
#define RCC_MC_APB1ENSETR_WWDG1EN B(28)​
#define RCC_MC_APB1ENCLRR_WWDG1EN B(28)

B(28)则表示将第28位置1,所以(RCC->MC_APB1ENSETR = RCC_MC_APB1ENSETR_WWDG1EN)就表示将RCC_MC_APB1ENSETR寄存器的第28位写1,也就是开启WWDG这个外设的时钟。在代码中,要开启WWDG的时钟的话,直接调用__HAL_RCC_WWDG1_CLK_ENABLE()这个宏就可以实现,其它的所有外设的时钟开启都可以类推。​

同理,(RCC->MC_APB1ENCLRR = RCC_MC_APB1ENCLRR_WWDG1EN)表示关闭WWDG的时钟,不过因为WWDG特殊,系统运行的时候是不能将WWDG时钟关闭的,所以寄存器RCC_MC_APB1ENCLRR并没有针对WWDG的操作位,所以调用__HAL_RCC_WWDG1_CLK_DISABLE()这个宏是没什么实际意义的,不过对于其它一般的外设,例如I2C3、USART2、UART8等,调用对应的宏就可以将外设时钟关闭了。​

又例如GPIOA~GPIOK是挂在AHB4总线上的,在HAL库的stm32mp1xx_hal_rcc.h头文件中同样有GPIO时钟开启和关闭宏定义,操作方法也和上面的外设类似。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_30


图18.2.2.4 外设时钟使能/禁用宏定义​

事实上,用STM32CubeMX插件生成的初始化代码中,也就是通过调用这些宏来开启外设时钟的。​

18.3 时钟相关API函数

头文件:stm32mp1xx_hal_rcc.h。​

stm32mp1xx_hal_rcc.c是RCC相关的HAL模块驱动程序文件,主要管理复位、初始化、取消初始化和外设时钟控制的功能。​

复位后,设备从内部高速时钟(HSI 64MHz)运行,并且所有外设均关闭,但内部SRAM1,SRAM2,SRAM3和PWR除外。高速(AHB)和低速(APB)总线上没有预分频,这些总线上映射的所有外设均以HSI速度运行。​

设备复位后,如果用户需要更高的频率/性能,需要进行如下操作:​

①选择用于驱动MPU、AXI和MCU子系统的时钟源;​

②配置AHB和APB总线分频器;​

③对于那些不是从总线获得时钟的内核外设需要配置内核时钟源;​

④启用要使用的外设的时钟。​

不管使用哪个时钟源,都是通过软件对相关寄存器的操作来实现的,我们直接通过HAL库的API函数来实现控制寄存器操作,下面,我们来分析一下stm32mp1xx_hal_rcc.c文件中的API函数。​

18.3.1 函数HAL_RCC_DeInit

  1. 函数功能:主要就是将RCC时钟配置为默认重置状态,主要做了以下操作:​ ①将HSION位置1,将HSI用作系统时钟源;​
    ②将MCO1、MCO2HSE、PLL1、PLL2、PLL3和PLL4关闭;​
    ③AHB,APB总线预分频器设置为1;​
    ④禁止所有中断(从CSTOP中断允许唤醒除外)。​
  2. 函数返回值:​枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)​
  3. 注意:此功能不会修改外设、LSI、 LSE 和 RTC时钟,不会更改HSECSS(HSE时钟安全系统HSE Clock security system)和LSECSS(LSE Clock security system)。函数HAL_RCC_DeInit的部分代码如下,因为代码很多,我们省略掉了部分代码。
1 HAL_StatusTypeDef HAL_RCC_DeInit(void)
2{
3uint32_t tickstart;
4
5/* 将HSION位置1,使能HSI振荡器 */
6 SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSION);
7
8/* 获取全局变量uwTick当前计算值 */
9 tickstart = HAL_GetTick();
10
11/* 等待HSI准备就绪 */
12while((RCC->OCRDYR & RCC_OCRDYR_HSIRDY)==0U)
13{
14if((HAL_GetTick()- tickstart)> HSI_TIMEOUT_VALUE)
15{
16return HAL_TIMEOUT;
17}
18}
19
20/*
21省略掉代码......
22 关闭MCO1、MCO2HSE、PLL1、PLL2、PLL3和PLL4
23 */
24
25/* 等待HSIDIV分频器就绪*/
26while((RCC->OCRDYR & RCC_OCRDYR_HSIDIVRDY)==0U)
27{
28if((HAL_GetTick()- tickstart)> HSI_TIMEOUT_VALUE)
29{
30return HAL_TIMEOUT;
31}
32}
33/******省略掉代码******/
34/* 更新SystemCoreClock全局变量 */
35 SystemCoreClock = HSI_VALUE;
36
37/* 调整Systick中断时间 */
38if(HAL_InitTick(uwTickPrio)!= HAL_OK)
39{
40return HAL_ERROR;
41}
42
43return HAL_OK;
44}

  1. 我们简单分析这部分代码:
    第6行,通过SET_BIT置位操作,将RCC_OCENSETR寄存器的HSEON位置1,表示使能HSI振荡器。
    第9到,先获取全局变量uwTick当前计算值。
    第12行,等待HSI是否已经就绪。
    注:RCC_OCRDYR是振荡器时钟就绪寄存器
    ①第0位HSIRDY是HSI时钟就绪标志,由硬件置1,以指示HSI振荡器稳定,如果此位为0,表示HSI时钟未准备好,如果此位为1,表示HSI时钟已准备好。
    ②第2位HSIDIVRDY是分频器就绪标志位,由硬件置位和复位。如果此位为0,表示新的分频比尚未传播到hsi_ck(hsi_ker_ck)(复位后默认),如果此位为1,表示hsi_ck(hsi_ker_ck)时钟频率反映了新的HSIDIV值,即分频器已经准备好。
    ③第4位CSIRDY是CSI时钟就绪标志,由硬件置1,以指示CSI振荡器稳定。如果此位为0,表示CSI时钟未准备好(复位后的默认值),如果此位为1,表示CSI时钟已准备好。
    ④第8位HSERDY表示HSE时钟就绪标志由硬件置1,指示HSE振荡器稳定。如果此位为0,表示HSE时钟未准备好(复位后的默认值),如果此位为1,HSE时钟已准备好。

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_31

图18.3.1. 1寄存器​

代码中,RCC_OCRDYR_HSIRDY值为0x00000001。如果(RCC->OCRDYR & RCC_OCRDYR_HSIRDY) ==,表示HSI振荡器未稳定,程序会卡在while循环中。如果两次取得的全局变量uwTick的差值大于HSI_TIMEOUT_VALUE(100ms),表示HSI超时,程序返回HAL_TIMEOUT标志并退出while循环。​

第26到第32行,表示等待HSIDIV分频器就绪。​

第35行,更新SystemCoreClock全局变量的值为HSI_VALUE。SystemCoreClock变量用于存储系统时钟频率,可以用来设置SysTick定时器或配置其他参数,每次时钟变化时,都应该更新它,这样也是为了保证SystemCoreClock的准确性。我们在分析system_stm32mp1xx.c文件时有分析过。​

第38到第41行,调用HAL_InitTick函数更新Systick中断时间(1ms),因为很多地方要用systick作为时基源,所以时钟源变化后,要将其更新。如果HAL_InitTick函数没有运行成功,程序将返回HAL_ERROR。HAL_InitTick函数我们在stm32mp1xx_hal.c文件中有分析过。​

18.3.2函数HAL_RCC_OscConfig

  1. 函数功能:主要就是配置 HSE、HSI、LSI、LSE 和 PLL(PLL1、PLL2、PLL3和PLL4)。​
  2. 函数返回值:枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)
  • 注意:当PLL用作系统时钟时,PLL不会被禁用。函数部分代码如下:
1 __weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef\ *RCC_OscInitStruct)
2{
3uint32_t tickstart;
4 HAL_StatusTypeDef result = HAL_OK;
5
6/* 检查是否是空指针 */
7if(RCC_OscInitStruct ==NULL)
8{
9return HAL_ERROR;
10}
11
12/* 使用断言检查参数 */
13assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));
14
15/******省略HSE、HSI、CSI、LSI、LSE、PLL配置代码 ******/
16return HAL_OK;
17}
18
  • weak表示函数是弱定义,用户可以在其它地方重新定义一个同名的函数。函数参数是RCC_OscInitTypeDef 类型结构体变量,主要对RCC内部/外部振荡器(HSE,HSI,CSI,LSE和LSI)配置的结构定义,RCC_OscInitStruct是指向RCC_OscInitTypeDef结构的指针,RCC_OscInitTypeDef 类型结构体声明如下,通过指针可以操作结构体中的成员变量。例如,如果要选择HSE为振荡器:①设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE;②然后设置HSEState的值为RCC_HSE_ON开启HSE;③如果用到PLL,则配置对应PLL的参数。对于其它的时钟源(HSI、LSI、LSE和CSI)配置方法类似。
stm32mp1xx_hal_rcc.h文件代码
typedefstruct
{
uint32_t OscillatorType;/* 要配置的振荡器 */
uint32_t HSEState;/* HSE的新状态 */
uint32_t LSEState;/* LSE的新状态 */
uint32_t HSIState;/* HSI的新状态 */
uint32_t HSICalibrationValue;/* 校准调整值 */
uint32_t HSIDivValue;/* HSI的分频系数 */
uint32_t LSIState;/* LSI的新状态 */
uint32_t CSIState;/* CSI的新状态 */
uint32_t CSICalibrationValue;/* 校准调整值 */
RCC_PLLInitTypeDef PLL;/* PLL1结构参数: */
RCC_PLLInitTypeDef PLL2; /* PLL2结构参数 */
RCC_PLLInitTypeDef PLL3;/* PLL3结构参数 */
RCC_PLLInitTypeDef PLL4;/* PLL4结构参数 */
} RCC_OscInitTypeDef;
  • 我们查看PLL的RCC_PLLInitTypeDef结构参数有哪些,如下,此结构体定义了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量,通过给这些结构体成员赋值即可配置PLL的时钟。
stm32mp1xx_hal_rcc.h文件代码
typedefstruct
{
uint32_t PLLState;/* PLL的新状态 */
uint32_t PLLSource;/* PLL输入时钟源 */
uint32_t PLLM;/* PLL VCO输入时钟的分频系数DIVM */
uint32_t PLLN;/* PLL VCO输出时钟的倍数DIVN */
uint32_t PLLP;/* 分频因子DIVP */
uint32_t PLLQ;/* 分频因子DIVQ */
uint32_t PLLR;/* 分频因子DIVR */
uint32_t PLLRGE;/* PLL3和PLL4的PLL输入频率范围 */
uint32_t PLLFRACV;/* PLL1 VCO乘数的小数部分FRACV */
uint32_t PLLMODE;/* 使用PLL模式 */
uint32_t MOD_PER;/* 调制周期调整 */
uint32_t RPDFN_DIS;/* 抖动的RPDF噪音控制 */
uint32_t TPDFN_DIS;/* 抖动的TPDF噪声控制 */
uint32_t SSCG_MODE;/* 扩频时钟发生器模式 */
uint32_t INC_STEP;/* 调制深度调整*/
} RCC_PLLInitTypeDef;

  • 在HSE、HSI、CSI、LSI、LSE、PLL配置代码部分,我们主要分析HSE、HSI和PLL配置部分。

1. HSE配置部分

1 if (((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) \​
== RCC_OSCILLATORTYPE_HSE)​
2 {​
3 /* 使用断言检查检查参数 */​
4 assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));​
5 /* 当HSE在系统中的某处使用时,将不会被禁用 */​
6 if (IS_HSE_IN_USE())​
7 {​
8 if ((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && \ (RCC_OscInitStruct->HSEState != RCC_HSE_ON))​
9 {​
10 return HAL_ERROR;​
11 }​
12 }​
13 else​
14 {​
15 /* 配置HSE振荡器 */​
16 result = HAL_RCC_HSEConfig(RCC_OscInitStruct->HSEState);​
17 if (result != HAL_OK)​
18 {​
19 return result;​
20 }​
21 }​
22 }

第1行,通过RCC_OscInitStruct指针判断振荡器是否选中了HSE,如果选中了HSE,将进行后续的配置操作。​

第4行,使用断言检查HSE参数的状态设置是否正确。​

第6到第12行,判断HSE是否有在使用中,如果有在使用,则不会被禁用。 (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)用于判断​​RCC_OCRDYR​​振荡器时钟就绪寄存器中的HSERDY位是否为0,为0的话表示HSE时钟未准备好(复位后的默认值也是0)。​

(RCC_OscInitStruct->HSEState != RCC_HSE_ON)表示HSE的状态没有被开启。​

如果HSERDY位为0,且HSE的状态没有开启,那么HSE没有被使用,程序返回HAL_ERROR。是否已经开启HSE,这取决于用户,所以程序要通过判断HSERDY位以及用户的设置状态来判断到底HSE用还是没用。​

第16行,调用HAL_RCC_HSEConfig函数完成HSE振荡器的初始化。​

程序中主要实现操作3个寄存器,一个是上面提到的​​RCC_OCRDYR​​,另外两个是RCC_OCENCLRR核RCC_OCENSETR寄存器。​

注:​

RCC_OCENCLRR寄存器用于控制振荡器,向该寄存器写入0无效,写入1将清除相应的位。​

第0位,HSION:写1表示清除HSION位,禁用HSI。​

第4位,CSION:写1表示清除CSION位,禁用CSI。​

第7位,DIGBYP:当连接到OSC_IN的外部时​钟为低摆幅信号时,软件将其清零。写1表示清除DIGBYP位(模拟旁路),禁用​

第8位,HSEON:写1表示清除HSEON位,禁用HSE。​

RCC_OCENSETR寄存器的功能和RCC_OCENCLRR寄存器的功能相反,RCC_OCENSETR寄存器表示开启对应的功能。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_32


图18.3.2. 1寄存器​

代码中附上了详细的注释,通过注释可以知道程序的实现过程:

①在配置HSE前,先关闭HSE,如果已经使用了HSE,应先切换成别的时钟源,然后再关闭HSE,最后才可以配置HSE。​

②程序中通过将RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)的HSEON位写入“ 1”来禁用HSE。​

③通过验证RCC_OCRDYR的HSERDY位是否设置为'0',检查HSE是否被禁用。​

④通过将RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)的HSEBYP位写入'1',禁用HSE旁路模式。​

⑤通过将RCC振荡器时钟使能设置寄存器(RCC_OCENSETR)的HSEON位写入“ 1”来再次使能HSE​

⑥检查RCC_OCRDYR的HSERDY位是否设置为“ 1”,然后准备使用HSE。​

1 /**​
2根据指定的参数初始化RCC HSE振荡器。​
3 在更改HSE配置之前,请注意不要将HSE振荡器用作时钟,也就是要先关闭HSE,​
4 * 如果使用了HSE,则必须选择另一个源时钟,然后更改HSE状态(例如:禁用它)。 ​
5 *@note 进入STOP和STANDBY模式时,HSE由硬件停止。​
6 *@note 此功能会重置CSSON位,因此,如果先前启用了时钟安全系统(CSS),​
7 则必须在调用此功能后再次启用CCS。​
8 *@param 状态包含RCC HSE振荡器的配置。​
9 * 此参数可以是下列值之一:​
10 * @arg RCC_HSE_OFF:​
11 * 关闭HSE振荡器​
12 * @arg RCC_HSE_ON: ​
13 * 打开HSE振荡器​
14 * @arg RCC_HSE_BYPASS:​
15 * 使用提供给OSC_IN的低摆幅模拟信号(旁路时钟),​
16 HSE振荡器被外部时钟旁路​
17 * @arg RCC_HSE_BYPASS_DIG:​
18 * 使用提供给OSC_IN的全摆幅数字信号(数字旁路时钟),​
19 HSE振荡器被外部时钟旁路​
20 * @retval HAL status​
21 */​
22 HAL_StatusTypeDef HAL_RCC_HSEConfig(uint32_t State)​
23 {​
24 uint32_t tickstart;​
25 ​
26 /* 检查参数 */​
27 assert_param(IS_RCC_HSE(State));​
28 ​
29 /* 在配置HSE之前禁用HSEON */​
30 WRITE_REG(RCC->OCENCLRR, RCC_OCENCLRR_HSEON);​
31 ​
32 /* 获取全局变量uwTick当前计算值 */​
33 tickstart = HAL_GetTick();​
34 ​
35 /* 等待直到禁用HSE */​
36 while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)​
37 {​
38 if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)​
39 {​
40 return HAL_TIMEOUT;​
41 }​
42 }​
43 ​
44 /* 清除剩余的位 */​
45 WRITE_REG(RCC->OCENCLRR, (RCC_OCENCLRR_DIGBYP | \ RCC_OCENSETR_HSEBYP));​
46 ​
47 /* 如果需要,启用HSE */​
48 if (State != RCC_HSE_OFF)​
49 {​
50 /* 如果设置的是旁路时钟,则启用旁路模式 */​
51 if (State == RCC_HSE_BYPASS)​
52 {​
53 SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEBYP);​
54 }​
55 /* 如果设置的是数字旁路时钟,则启用数字旁路模式 */​
56 else if (State == RCC_HSE_BYPASS_DIG)​
57 {​
58 SET_BIT(RCC->OCENSETR, RCC_OCENCLRR_DIGBYP);​
59 SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEBYP);​
60 }​
61 ​
62 /* 启用HSE */​
63 SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEON);​
64 ​
65 /* 获取全局变量uwTick当前计算值 */​
66 tickstart = HAL_GetTick();​
67 ​
68 /* 等待HSE准备就绪 */​
69 while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)​
70 {​
71 if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)​
72 {​
73 return HAL_TIMEOUT;​
74 }​
75 }​
76 }​
77 ​
78 return HAL_OK;​
79 }

2. HSI配置部分

关于HSI的配置部分,有以下两点:​

①RCC_OCRDYR寄存器的HSIRDY标志指示HSI是否稳定。 启动时,在硬件将HSIRDY位置1之前,不会释放HSI输出时钟。应用程序还可以通过位于RCC振荡器时钟使能设置寄存器(RCC_OCENSETR)和RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)中的HSION位来控制HSI激活。​

②HSI的预分频器由位于HSI配置寄存器RCC_HSICFGR中的HSIDIV位控制。RCC振荡器时钟就绪寄存器RCC_OCRDYR可以使用标志HSIDIVRDY位来检查硬件何时考虑新的分频比。HSIDIV值可以即时更改,并且HSI将考虑新的分频比,但是,必须考虑:​

  • 如果HSI当前用作PLL的参考时钟,则不允许更改HSIDIV。​
  • 如果当前将HSI时钟用作某些外围设备的内核时钟,则应用程序必须确保HSI频率变化不会干扰外围设备。​

配置部分的代码框架如下,代码比较多,这里省略了代码。​

1 if (((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSI) \​
== RCC_OSCILLATORTYPE_HSI)​
2 {​
3 /* 使用断言检查检查参数是否符合范围*/​
4 /****** 省略部分代码 ******/​
5 /* ​
6当HSI被使用时(在AXI、MPU、MCU和PLL1~pLL4使用),它不会被禁用,允许校准。​
7、如果HSI当前用作PLL的参考时钟,则不允许更改HSIDIV的置。​
8、如果HSI没有被当做PLL的参考时钟,则更新HSIDIV值,​
9同时,更新SystemCoreClock全局变量。​
10 */​
11 if (IS_HSI_IN_USE())​
12 {​
13 /****** 省略部分代码 ******/​
14 }​
15 /* ​
16如果HSI没有在AXI、MPU、MCU和PLL1~pLL4使用:​
17、确定HSI没有被使用,如果检查到HSIState为开启状态(一般是用户开启),先启​
18用HSI,等待HSI准备就绪后,更新HSIDIV值,然后整内部高速振荡器(HSI)校准值。​
19 * ​
20、如果如果检查到HSIState为关闭状态(一般是用户关闭),则禁用HSI。​
21 */​
22 else​
23 {​
24 /****** 省略部分代码 ******/​
25 }​
26 }

3. PLL配置部分

在启用PLL之前,必须先完成以下PLL配置:​

  • 选择PLL时钟输入(HSI或CSI或HSE)​
  • PLL时钟频率输入范围(配置DIVM参数)​
  • 一旦启用了PLL,DIVMx,DIVNx,DIVPx,DIVQx和DIVRx这些参数就无法更改如果用户想要更改PLL参数,则他必须禁用相关的PLL(PLLxON = 0),并等待PLLxRDY标志为0才可以(这里的x等于1~4)。​

配置PLL部分主要是通过调用RCC_PLL1_Config、RCC_PLL2_Config、RCC_PLL3_Config、RCC_PLL4_Config函数来实现,函数的参数RCC_OscInitStruct用于指定配置哪一个PLL。通过对PLL的RCC_PLLInitTypeDef结构体成员赋值可以配置PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数。​

1 /* 配置PLL1 */​
2 result = RCC_PLL1_Config(&(RCC_OscInitStruct->PLL));​
3 if (result != HAL_OK)​
4 {​
5 return result;​
6 }​
7 ​
8 /* 配置PLL2 */​
9 result = RCCEx_PLL2_Config(&(RCC_OscInitStruct->PLL2));​
10 if (result != HAL_OK)​
11 {​
12 return result;​
13 }​
14​
15 /* 配置PLL3 */​
16 result = RCCEx_PLL3_Config(&(RCC_OscInitStruct->PLL3));​
17 if (result != HAL_OK)​
18 {​
19 return result;​
20 }​
21​
22 /* 配置PLL4 */​
23 result = RCCEx_PLL4_Config(&(RCC_OscInitStruct->PLL4));​
24 if (result != HAL_OK)​
25 {​
26 return result;​
27 }

18.3.3 HAL_RCC_ClockConfig

  1. 函数功能:根据RCC_ClkInitStruct中指定的参数初始化MPU,AXI,AHB和APB总线时钟。​
  2. 函数返回值:枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)
  • 注意:​

①系统从上电复位、停机或者待机唤醒以后,或者在HSE发生故障的情况下,则直接或间接将HSI直接或间接用作系统时钟。​

②仅当目标时钟源准备就绪(启动延迟或PLL锁定后时钟稳定)时,才会从一个时钟源切换到另一时钟源。如果选择了尚未准备好的时钟源,则当时钟源准备就绪时将进行切换。​

③根据设备电压范围,软件必须正确设置HPRE [3:0]位以确保HCLK不超过最大允许频率。​

1 HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef \  *RCC_ClkInitStruct)​
2 {​
3 ​
4 HAL_StatusTypeDef status = HAL_OK;​
5 uint32_t tickstart;​
6 ​
7 /* 检查是否是检查空指针 */​
8 if (RCC_ClkInitStruct == NULL)​
9 {​
10 return HAL_ERROR;​
11 }​
12​
13 assert_param(IS_RCC_CLOCKTYPETYPE(RCC_ClkInitStruct->ClockType));​
14 /******省略 MPU、AXISS模块配置代码******/​
15 /******省略 APB1~APB5总线的分频器APB1DIV~APBDIV5的代码******/​
16 ​
17 return HAL_OK;​
18 }

参数RCC_ClkInitStruct是指向RCC_ClkInitTypeDef结构的指针,我们查看此结构体。如下,此结构体主要用于设置时钟源以及MPU、AXI子系统、MCU子系统和APB1~APB5的分频系数。​

stm32mp1xx_hal_rcc.h文件代码​

typedef struct​
{​
uint32_t ClockType; /* 时钟源选择 */ ​
RCC_MPUInitTypeDef MPUInit; /* MPU结构参数(时钟源和分频数) */ ​
RCC_AXISSInitTypeDef AXISSInit; /* AXI结构参数(时钟源和分频数)*/ ​
RCC_MCUInitTypeDef MCUInit; /* APB4分频数 */ ​
uint32_t APB4_Div; /* APB4分频数 */ ​
uint32_t APB5_Div; /* APB5分频数 */ ​
uint32_t APB1_Div; /* APB1分频数 */ ​
uint32_t APB2_Div; /* APB2分频数 */ ​
uint32_t APB3_Div; /* APB3分频数 */ ​
} RCC_ClkInitTypeDef;

18.3.4 HAL_RCC_GetSystemCoreClockFreq

  1. 函数功能:根据所选的时钟源以及预定义的常量来返回系统核心频率。​
  2. 函数返回值:枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)
  • 注意:​

①我们说的系统时钟频率,其实就是MCU或者MPU的时钟频率。​

②每次MCU/MPU更改时,必须调用此函数以更新正确的值,否则,基于此功能的任何配置都将不正确。我们前面说的SystemCoreClock全局变量就是通过此函数来获取最新的系统时钟频率的:​

SystemCoreClock = HAL_RCC_GetSystemCoreClockFreq();

我们后面的实验会通过SystemCoreClock来验证配置的系统时钟频率是否正确。HAL_RCC_GetSystemCoreClockFreq函数如下,函数中,通过宏定义CORE_CA7来选择MPU或者MCU的时钟,其中HAL_RCC_GetMPUSSFreq函数返回MPU的时钟,HAL_RCC_GetMCUFreq函数返回MCU的时钟。

1 uint32_t HAL_RCC_GetSystemCoreClockFreq(void)​
2 {​
3 #ifdef CORE_CA7​
4 return HAL_RCC_GetMPUSSFreq();​
5 #else /* CORE_CM4 */​
6 return HAL_RCC_GetMCUFreq();​
7 #endif​
8 }

18.3.5 HAL_RCCEx_PeriphCLKConfig

  1. 函数功能:根据RCC_PeriphCLKInitTypeDef中的指定参数初始化RCC外设时钟。​
  2. 函数返回值:​

枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)​

可以通过此函数可以指定配置哪个外设以及外设的时钟源是哪个,后面的外设实验中,我们会接触此函数。该函数声明如下:​

HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef​
*PeriphClkInit)

PeriphClkInit是指向包含字段PeriphClockSelection的RCC_PeriphCLKInitTypeDef结构的指针,该字段可以指定是哪个外设。例如,RCC_PERIPHCLK_UART24指定USART2和UART4,RCC_PERIPHCLK_I2C12指定I2C1和I2C2。​

typedef struct​
{​
uint64_t PeriphClockSelection; /* 外设时钟源选择 */​
RCC_PLLInitTypeDef PLL2; /* PLL2结构参数 */ ​
RCC_PLLInitTypeDef PLL3; /* PLL3结构参数 */​
RCC_PLLInitTypeDef PLL4; /* PLL4结构参数 */​
/* 指定I2C1/2时钟源,该参数可以是RCC_PERIPHCLK_I2C12 */​
uint32_t I2c12ClockSelection; ​
/********* 省略部分代码 *********/​
/* 指定UART2/4时钟源,该参数可以是RCCEx_UART24_Clock_Source */​
uint32_t Uart24ClockSelection; ​
/* 指定UART3/5时钟源,该参数可以是RCCEx_UART35_Clock_Source */ ​
uint32_t Uart35ClockSelection; ​
/* 指定UART6时钟源,该参数可以是RCCEx_USART6_Clock_Source */ ​
uint32_t Usart6ClockSelection; ​
/* 指定UART7/8时钟源,该参数可以是RCCEx_UART78_Clock_Source */ ​
uint32_t Uart78ClockSelection; ​
/* 指定RNG1时钟源,该参数可以是RCCEx_RNG1_Clock_Source */ ​
uint32_t Rng1ClockSelection; ​
/* 指定RTC时钟源,该参数可以是RCC_RTC_Clock_Source */ ​
uint32_t RTCClockSelection; ​
/********* 省略部分代码 *********/​
/* 指定TIM2时钟源,该参数可以是RCCEx_TIMG2_Prescaler_Selection */ ​
uint32_t TIMG2PresSelection; ​
} RCC_PeriphCLKInitTypeDef;

RCC_PLLInitTypeDef结构体我们前面已经分析过,其声明了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量。​

18.3.6 其它函数

函数名

函数说明

HAL_RCC_HSEConfig​

初始化RCC HSE振荡器时钟函数​

RCC_AXISSConfig​

初始化AXISS时钟函数​

RCC_MCUConfig​

初始化MCU时钟函数​

HAL_RCC_MCOConfig​

选择要在MCO1引脚或MCO2引脚上输出的时钟源​

HAL_RCC_GetOscConfig​

根据内部RCC配置寄存器配置RCC_OscInitStruct​

HAL_RCC_GetClockConfig​

根据内部RCC配置寄存器配置RCC_ClkInitStruct​

HAL_RCC_GetPLL1ClockFreq​

获取PLL1时钟频率:PLL1_P_Frequency、PLL1_R_Frequency和PLL1_Q_Frequency​

HAL_RCC_GetPCLK1Freq​

返回PCLK1的频率。同样地,还有HAL_RCC_GetACLKFreq和HAL_RCC_GetHCLK1Freq函数,根据函数名字可以推测函数的作用。​

HAL_RCC_GetMPUSSFreq​

返回MPUSS频率,同样的,还有HAL_RCC_GetAXISSFreq和HAL_RCC_GetMCUSSFreq函数,根据函数名字可以推测函数的作用。​

HAL_RCC_IRQHandler​

处理RCC全局中断函数​

HAL_RCC_Callback​

RCC全局中断回调函数​

HAL_RCC_WAKEUP_IRQHandler​

处理RCC唤醒中断函数​

HAL_RCC_WAKEUP_Callback​

RCC唤醒中断回调函数​

表18.3.5. 1其它API函数​

18.4 配置系统时钟实验

本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验7 系统时钟配置实验。​

18.4.1 硬件设计

1. 例程功能

STM32MP157默认的情况下使用的是内部64M的HSI作为时钟源,所以不需要外部晶振也可以下载和运行代码的。根据前面的分析以及ST官方给的总线框图,MCU时钟最大是209MHz,MCU有一个内核外设Systick,频率最大也是209MHz。下面我们来实现STM32MP157的MCU在209MHZ的频率下工作,这是官方推荐使用的最高的稳定时钟频率。​

2. 硬件资源

LED0

HSE

PI0​

24MHz​

表18.4.1.1硬件资源​

3. 原理图

正点原子的STM32MP157开发板的外部高速晶振的频率是24MHz,我们就是在这个晶振频率的基础上,通过各种倍频和分频得到209MHZ的系统工作频率。​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_初始化_33


图18.4.1 晶振原理图截图​

18.4.2程序设计

1. 关联文件

RCC相关的驱动在stm32mp1xx_hal_rcc.c和stm32mp1xx_hal_rcc_ex.c文件及其头文件中,实验工程中我们一定要关联这两个文件。​

2. 配置HSE相关宏定义

此部分主要是在stm32mp1xx_hal_conf.h文件中完成的,在 第九章 有分析过此文件。该文件有ST配置好了,关于HSE,我们主要了解以下配置:​

(1)选择HSE相关模块

在stm32mp1xx_hal_conf.h文件中使能HAL驱动程序中要使用的模块的列表,实验中用到了RCC:​

stm32mp1xx_hal_conf.h文件代码​

#define HAL_MODULE_ENABLED​
/*#define HAL_HASH_MODULE_ENABLED */​
#define HAL_HSEM_MODULE_ENABLED​
/*#define HAL_WWDG_MODULE_ENABLED */​
#define HAL_GPIO_MODULE_ENABLED​
#define HAL_EXTI_MODULE_ENABLED​
#define HAL_DMA_MODULE_ENABLED​
#define HAL_MDMA_MODULE_ENABLED​
#define HAL_RCC_MODULE_ENABLED​
#define HAL_PWR_MODULE_ENABLED​
#define HAL_CORTEX_MODULE_ENABLED

(2)包含HSE相关模块的头文件

stm32mp1xx_hal_conf.h文件代码​

#ifdef HAL_RCC_MODULE_ENABLED​
#include "stm32mp1xx_hal_rcc.h"​
#endif /* HAL_RCC_MODULE_ENABLED */​

#ifdef HAL_EXTI_MODULE_ENABLED​
#include "stm32mp1xx_hal_exti.h"​
#endif /* HAL_EXTI_MODULE_ENABLED */​

#ifdef HAL_GPIO_MODULE_ENABLED​
#include "stm32mp1xx_hal_gpio.h"​
#endif /* HAL_GPIO_MODULE_ENABLED */​

#ifdef HAL_HSEM_MODULE_ENABLED​
#include "stm32mp1xx_hal_hsem.h"​
#endif /* HAL_HSEM_MODULE_ENABLED */​
/****** 省略部分代码 *******/

(3)配置HSE_VALUE

宏定义HSE_VALUE匹配我们实际硬件的高速晶振频率(这里是24MHz),代码如下:​

stm32mp1xx_hal_conf.h文件代码​

#if !defined (HSE_VALUE)​
#define HSE_VALUE (24000000U) ​
#endif

18.4.3 SystemCoreClockUpdate函数

SystemCoreClockUpdate函数函数我们在前面 第八章​​ 8.3.4小节​​有介绍。MCU的时钟源有:HSI(默认值64 MHz)、HSE(默认值为24 MHz)、CSI(默认值为4 MHz)和PLL3_P ,系统复位以后,系统时钟默认采用64MHz的HSI。不管我们有没有重新配置MCU子系统的时钟,SystemCoreClockUpdate函数根据时钟寄存器的值来更新SystemCoreClock全局变量,该变量可以用于设置SysTick定时器或配置其他时钟参数。​

18.4.4 编写时钟设置函数

1. 时钟配置代码实现

在工程的sys.c文件中我们编写时钟设置函数sys_stm32_clock_init,该函数通过配置PLL3相关参数实现用户时钟系统配置,函数中我们也添加了MPU时钟设置的相关代码,代码中已经附上了详细的注释,如下:​

#include "./SYSTEM/sys/sys.h"​
/**​
* @brief M4主频时钟设置函数,也就是设置PLL3​
* @param plln: PLL3倍频系数(PLL倍频), 取值范围:​
(设置RCC_PLL3CFGR1的DIVN[8:0]位).​
* @param pllm: PLL3预分频系数(进PLL之前的分频), 取值范围:​
(设置RCC_PLL3CFGR1的DIVM3[5:0]位).​
* @param pllp: PLL3的p分频系数(PLL之后的分频), 分频后作为系统时钟, ​
取值范围: 1~128.(且必须是2的倍数)(RCC_PLL3CFGR2的DIVP[6:0]位)​
* @param pllq: PLL3的q分频系数(PLL之后的分频), 取值范围: ​
(设置RCC_PLL3CFGR2的DIVR[6:0]位).​
* @param pllfracv:PLL3的FRACV位,即乘法因子的小数部分,FRACV可介于​
和213-1之间。实际上是配置RCC_PLL3FRACR的 FRACV[12:0]位。 ​
* @note​
* Fvco: VCO频率​
* Fsys: 系统时钟频率, 也是PLL1的p分频输出时钟频率​
* Fq: PLL1的q分频输出时钟频率​
* Fs: PLL输入时钟频率, 可以是HSI, CSI, HSE等.​
* Fvco = (Fs / pllm) * (plln + (pllfracv / 8192))​
* Fsys = Fvco / pllp;​
* Fq = Fvco / pllq;​
* ​
外部晶振为24M时, 推荐值: plln = 34, pllm = 2, pllp = 2, pllq = 17, pllfracv = 6826.​
* 得到:Fvco = (24 / 2) * (34 + (6826 / 8192)) = 417.999MHz ≈ 418MHz​
* Fsys = Fvco / pllp = 418 / 2 = 209MHz​
* Fq = Fvco / pllq = 418 / 17 = 24.588MHz​
*​
* MP157默认需要配置的频率如下:​
* CPU频率(mcu_ck) = MLHCLK = PLL3P / 1 = 209Mhz​
* hclk = MLHCLK = 209Mhz​
* AHB1/2/3/4 = hclk = 209Mhz​
* APB1/2/3 = MLHCLK / 2 = 104.5Mhz​
* @retval错误代码: 0, 成功; 1, 错误;​
*/​
uint8_t sys_stm32_clock_init(uint32_t plln, uint32_t pllm, uint32_t pllp, uint32_t pllq, uint32_t pllfracv)​
{​
RCC_OscInitTypeDef rcc_osc_init_handle;​
RCC_ClkInitTypeDef rcc_clk_init_handle;​

HAL_PWR_EnableBkUpAccess(); /* 使能对RTC和备份寄存器的访问 */​
__HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_MEDIUMHIGH); /* 允许修改LSE */ ​
/**​
使能HSE,将HSE(24MHz有源晶振)作为PLL时钟源​
*/​
rcc_osc_init_handle.OscillatorType = RCC_OSCILLATORTYPE_HSE | \​
RCC_OSCILLATORTYPE_LSE|RCC_OSCILLATORTYPE_LSE | RCC_OSCILLATORTYPE_LSI| \​
RCC_OSCILLATORTYPE_CSI;​
rcc_osc_init_handle.HSEState = RCC_HSE_ON; /* 打开HSE */​
rcc_osc_init_handle.LSEState = RCC_LSE_ON; /* 打开LSE */​
rcc_osc_init_handle.HSIState = RCC_HSI_ON; /* 打开HSI */​
rcc_osc_init_handle.CSIState = RCC_CSI_ON; /* 打开CSI */​
rcc_osc_init_handle.LSIState = RCC_LSI_ON; /* 打开HSI */​
rcc_osc_init_handle.HSICalibrationValue = 16; /* 校准HSI值 */​
rcc_osc_init_handle.HSIDivValue = RCC_HSI_DIV1;/* 设置HSI分频值为1 */​
/**​
* 配置PLL1,PLL1主要为MPU系统时钟,也就是A7的时钟​
* A7系统时钟计算公式为:​
* Fref1_ck = 24M / PLLM = 24M / 3 = 8MHz​
* PLL1_vco = Fref1_ck * 2 *((DIVN + 1) + (FRACV / 8192))​
* PLL1_vco = 8MHz * 2 * (100 + (0/ 8192)) ​
* = 1600MHz​
*/​
rcc_osc_init_handle.PLL.PLLState = RCC_PLL_ON;/* 开启PLL1 */​
/* 选择HSE为PLL1/2时钟源 */​
rcc_osc_init_handle.PLL.PLLSource = RCC_PLL12SOURCE_HSE;​
rcc_osc_init_handle.PLL.PLLM = 3;/* 配置PLL1预分频系数 */​
rcc_osc_init_handle.PLL.PLLN = 100;/* 注意这里写的是加1以后的PLLN(DIVN)值 */​
rcc_osc_init_handle.PLL.PLLP = 1;/* PLL1P路时钟=PLL1_vco / 2 / PLLP = 800MHz */​
rcc_osc_init_handle.PLL.PLLQ = 1;/* PLL1Q路时钟=PLL1_vco / 2 / PLLQ = 800MHz */​
rcc_osc_init_handle.PLL.PLLR = 1;/* PLL1R路时钟=PLL1_vco / 2 / PLLR = 800MHz */​
rcc_osc_init_handle.PLL.PLLFRACV = 0;/* FRACV=0 */​
rcc_osc_init_handle.PLL.PLLMODE = RCC_PLL_FRACTIONAL;/* 分数模式 */​
/* 不使用RPDF噪声控制 */​
rcc_osc_init_handle.PLL.RPDFN_DIS = RCC_RPDFN_DIS_DISABLED;​
/* 不使用TPDF噪声控制 */​
rcc_osc_init_handle.PLL.TPDFN_DIS = RCC_TPDFN_DIS_DISABLED;​
/**​
配置PLL2,PLL2主要为AXI、AHB5、AHB6、APB4、APB5、GPU等时钟​
也分为三路出去:PLL2P、PLL2Q和PLL2R。这三路均从PLL2N配置出来​
* Fref2_ck = 24M / PLL1M = 24M / 3 = 8MHz​
* PLL2_vco = Fref2_ck * 2 *((DIVN + 1) + (FRACV / 8192))​
* = 8MHz * 2 * (66 + (5120 / 8192))​
* = 1066MHz​
*/​
rcc_osc_init_handle.PLL2.PLLState = RCC_PLL_ON;/* 打开PLL2 */​
/* PLL2时钟源为HSE=24M */​
rcc_osc_init_handle.PLL2.PLLSource = RCC_PLL12SOURCE_HSE;​
rcc_osc_init_handle.PLL2.PLLM = 3;/* 配置PLL2预分频系数 */​
rcc_osc_init_handle.PLL2.PLLN = 66;/* 注意这里写的是加1以后的PLLN(DIVN)值 */​
rcc_osc_init_handle.PLL2.PLLP = 2;/* PLL2P路时钟=PLL2_vco/2 /PLLP = 266.5MHz */​
rcc_osc_init_handle.PLL2.PLLQ = 1;/* PLL2Q路时钟=PLL2_vco /2 / PLLQ = 533MHz */​
rcc_osc_init_handle.PLL2.PLLR = 1;/* PLL2R路时钟=PLL2_vco/ 2 / PLLR = 533MHz */​
rcc_osc_init_handle.PLL2.PLLFRACV = 5120;/* FRACV=5120 */​
rcc_osc_init_handle.PLL2.PLLMODE = RCC_PLL_FRACTIONAL;/* 分数模式 */​
rcc_osc_init_handle.PLL2.RPDFN_DIS = RCC_RPDFN_DIS_DISABLED;​
rcc_osc_init_handle.PLL2.TPDFN_DIS = RCC_TPDFN_DIS_DISABLED; ​
/**​
配置PLL3,PLL3为MCU、MCU Systick、AHB1~AHB4、APB1~APB3外设和时钟、DFSDM时钟​
也分为三路出去:PLL3P、PLL3Q和PLL3R。这三路均从PLL3N配置出来​
* Fref3_ck = 24M / PLL3M = 24M / 2 = 12MHz​
* PLL3_vco = Fref3_ck * ((DIVN + 1) + (FRACV / 8192))​
* = 12MHz * (34 + (6826 / 8192))​
* = 417.999MHz​
* ≈ 418MHz​
*​
的pll3_p_ck输出频率为:​
* pll3_p_ck=(hse_ck*(DIVN3+FRACV/2^13 ))/(DIVM3*DIVP3)​
* = (24MHz*(plln+pllfracv/2^13 ))/(pllm*pllp)​
*/​
rcc_osc_init_handle.PLL3.PLLState = RCC_PLL_ON;/* 打开PLL3 */​
/* PLL3时钟源为HSE=24MHz */​
rcc_osc_init_handle.PLL3.PLLSource = RCC_PLL3SOURCE_HSE;​
rcc_osc_init_handle.PLL3.PLLM = pllm;/* 配置PLL3预分频系数 */​
rcc_osc_init_handle.PLL3.PLLN = plln;/* 注意这里写的是加1以后的PLLN(DIVN)值 */​
rcc_osc_init_handle.PLL3.PLLP = pllp;/* PLL3P=PLL3_vco/PLLP=208.9995MHz */​
rcc_osc_init_handle.PLL3.PLLQ = pllq;/* PLL3Q时钟=PLL3_vco/PLLQ=24.5881MHz */​
rcc_osc_init_handle.PLL3.PLLR = 37; /* PLL3R路时钟=PLL3_vco/PLLR =11.2972MHz */​
rcc_osc_init_handle.PLL3.PLLRGE = RCC_PLL3IFRANGE_1; /* ref3_ck范围为8~16Mhz */​
rcc_osc_init_handle.PLL3.PLLFRACV = pllfracv;/* FRACV=pllfracv */​
rcc_osc_init_handle.PLL3.PLLMODE = RCC_PLL_FRACTIONAL;/* 分数模式 */​
rcc_osc_init_handle.PLL3.RPDFN_DIS = RCC_RPDFN_DIS_DISABLED;​
rcc_osc_init_handle.PLL3.TPDFN_DIS = RCC_TPDFN_DIS_DISABLED;​
/**​
配置PLL4,某些外设时钟源,比如LTDC、DSI等​
也分为三路出去:PLL4P、PLL4Q和PLL4R。这三路均从PLL4N配置出来​
* Fref4_ck = 24M / PLL4M = 24M / 4 = 6MHz​
* PLL4_vco = Fref4_ck * ((DIVN + 1) + (FRACV / 8192))​
* = 6MHz * (99 + (0 / 8192))​
* = 594MHz​
*/​
rcc_osc_init_handle.PLL4.PLLState = RCC_PLL_ON; /* 打开PLL4 */​
/* PLL4时钟源为HSE=24MHz */​
rcc_osc_init_handle.PLL4.PLLSource = RCC_PLL4SOURCE_HSE; ​
rcc_osc_init_handle.PLL4.PLLM = 4; /* 配置PLL4预分频系数 */​
rcc_osc_init_handle.PLL4.PLLN = 99; /* 注意这里写的是加1以后的PLLN(DIVN)值 */​
rcc_osc_init_handle.PLL4.PLLP = 6;/* PLL4P路时钟=PLL4_vco / PLLP = 99MHz */​
rcc_osc_init_handle.PLL4.PLLQ = 8;/* PLL4Q路时钟=PLL4_vco / PLLQ = 74.25MHz */​
rcc_osc_init_handle.PLL4.PLLR = 8;/* PLL4R路时钟=PLL4_vco / PLLR = 74.25MHz */​
rcc_osc_init_handle.PLL4.PLLRGE = RCC_PLL4IFRANGE_0;​
rcc_osc_init_handle.PLL4.PLLFRACV = 0; /* FRACV=5120 */​
rcc_osc_init_handle.PLL4.PLLMODE = RCC_PLL_INTEGER;/* 整数模式 */​
rcc_osc_init_handle.PLL4.RPDFN_DIS = RCC_RPDFN_DIS_DISABLED;​
rcc_osc_init_handle.PLL4.TPDFN_DIS = RCC_TPDFN_DIS_DISABLED;​
/**​
调用的HAL_RCC_OscConfig函数用于判断 HSE、HSI、LSI、LSE 和​
(PLL1、PLL2、PLL3和PLL4)是否配置完成,配置完成则返回HAL_OK。​
如果没有配置完成,发生错误的话就会进入Error_Handler函数(空循环)。​
*/​
if (HAL_RCC_OscConfig(&rcc_osc_init_handle) != HAL_OK)​
{​
return 1;​
}​
/**​
配置RCC时钟,也就是HCLK、ACLK、PCLK1~5以及MPU。​
也分为三路出去:PLL4P、PLL4Q和PLL4R。这三路均从PLL4N配置出来​
* Fref4_ck = 24M / PLL4M = 24M / 4 = 6MHz​
* PLL4_vco = Fref4_ck * ((DIVN + 1) + (FRACV / 8192))​
* = 6MHz * (99 + (0 / 8192))​
* = 594MHz​
*/​
rcc_clk_init_handle.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_ACLK​
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2​
|RCC_CLOCKTYPE_PCLK3|RCC_CLOCKTYPE_PCLK4​
|RCC_CLOCKTYPE_PCLK5|RCC_CLOCKTYPE_MPU;​
/* MPU时钟源为PLL1P = 800MHz */​
rcc_clk_init_handle.MPUInit.MPU_Clock = RCC_MPUSOURCE_PLL1;​
/* MPUDIV 2分频 = 800 / 2 = 400MHz */ ​
rcc_clk_init_handle.MPUInit.MPU_Div = RCC_MPU_DIV2; ​
/* AXI时钟为PLL2P = 266.5MHz */ ​
rcc_clk_init_handle.AXISSInit.AXI_Clock = RCC_AXISSOURCE_PLL2; ​
rcc_clk_init_handle.AXISSInit.AXI_Div = RCC_AXI_DIV1;/* AXI时钟1分频 */​
/* MCU时钟源选择PLL3P=209MHz */ ​
rcc_clk_init_handle.MCUInit.MCU_Clock = RCC_MCUSSOURCE_PLL3; ​
rcc_clk_init_handle.MCUInit.MCU_Div = RCC_MCU_DIV1;/* MCU时钟1分频 */​
/* APB4为AXI的2分频=266.5/2=133.25MHz */​
rcc_clk_init_handle.APB4_Div = RCC_APB4_DIV2;​
/* APB5为AXI的4分频=266.5/4=66.625MHz */​
rcc_clk_init_handle.APB5_Div = RCC_APB5_DIV4;​
/* APB1为MLHCLK的2分频=209/2=104.5MHz */​
rcc_clk_init_handle.APB1_Div = RCC_APB1_DIV2;​
/* APB2为MLHCLK的2分频=209/2=104.5MHz */​
rcc_clk_init_handle.APB2_Div = RCC_APB2_DIV2;​
/* APB3为MLHCLK的2分频=209/2=104.5MHz */​
rcc_clk_init_handle.APB3_Div = RCC_APB3_DIV2; ​
/**​
调用HAL_RCC_ClockConfig函数,根据rcc_clk_init_handle中​
指定的参数初始化MPU,AXI,AHB和APB总线时钟,如果初始化​
不成功,则进入Error_Handler空循环函数。​
*/​
if (HAL_RCC_ClockConfig(&rcc_clk_init_handle) != HAL_OK)​
{​
return 2;​
}​
return 0;​
}

以上代码中已经附上了详细的注释,可以很容易看懂代码。sys_stm32_clock_init就是用户的时钟系统配置函数,代码中使用HSE作为PLL3的时钟源,除了配置PLL的相关参数值之外,还配置了AHB1~AHB、APB1~APB5、AXI的时钟。​

其中,MCU的时钟由PLL3获得,MPU时钟由PLL1获得。最终配置MPU时钟为800MHz,而MCU时钟由参数plln、pllm、pllp、pllfracv的值来确定,我们调用sys_stm32_clock_init函数的时候,通过设置参数的值可以配置MCU子系统的时钟。​

2. 代码分析

(1)RCC_OscInitTypeDef类型结构体

RCC_OscInitTypeDef类型结构体在stm32mp1xx_hal_rcc.h文件中有定义,我们前面也分析过这两个结构体,为了方便大家查看,这里列出结构体的声明。RCC_OscInitTypeDef主要对RCC内部/外部振荡器(HSE,HSI,CSI,LSE和LSI)配置的结构定义,主要是定义要配置的振荡器、时钟源的状态、校准值和PLL的参数。通过给结构体成员赋值即可开或者关对应的时钟,并可以配置PLL的参数。​

stm32mp1xx_hal_rcc.h文件代码​

typedef struct​
{​
uint32_t OscillatorType; /* 要配置的振荡器 */ ​
uint32_t HSEState; /* HSE的新状态 */ ​
uint32_t LSEState; /* LSE的新状态 */ ​
uint32_t HSIState; /* HSI的新状态 */ ​
uint32_t HSICalibrationValue; /* 校准调整值 */ ​
uint32_t HSIDivValue; /* HSI的分频系数 */ ​
uint32_t LSIState; /* LSI的新状态 */ ​
uint32_t CSIState; /* CSI的新状态 */ ​
uint32_t CSICalibrationValue; /* 校准调整值 */ ​
RCC_PLLInitTypeDef PLL; /* PLL1结构参数 */ ​
RCC_PLLInitTypeDef PLL2; /* PLL2结构参数 */ ​
RCC_PLLInitTypeDef PLL3; /* PLL3结构参数 */ ​
RCC_PLLInitTypeDef PLL4; /* PLL4结构参数 */ ​
} RCC_OscInitTypeDef;

我们查看PLL的结构参数有哪些,如下,此结构体定义了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量,通过给这些结构体成员赋值即可配置PLL的时钟。​

stm32mp1xx_hal_rcc.h文件代码​

typedef struct​
{​
uint32_t PLLState; /* PLL的新状态 */ ​
uint32_t PLLSource; /* PLL输入时钟源 */ ​
uint32_t PLLM; /* PLL VCO输入时钟的分频系数DIVM */ ​
uint32_t PLLN; /* PLL VCO输出时钟的倍数DIVN */ ​
uint32_t PLLP; /* 分频因子DIVP */ ​
uint32_t PLLQ; /* 分频因子DIVQ */ ​
uint32_t PLLR; /* 分频因子DIVR */ ​
uint32_t PLLRGE; /* PLL3和PLL4的PLL输入频率范围 */ ​
uint32_t PLLFRACV; /* PLL VCO乘数的小数部分FRACV */ ​
uint32_t PLLMODE; /* 使用PLL模式 */ ​
uint32_t MOD_PER; /* 调制周期调整 */ ​
uint32_t RPDFN_DIS; /* 抖动的RPDF噪音控制 */ ​
uint32_t TPDFN_DIS; /* 抖动的TPDF噪声控制 */ ​
uint32_t SSCG_MODE; /* 扩频时钟发生器模式 */ ​
uint32_t INC_STEP; /* 调制深度调整*/ ​
} RCC_PLLInitTypeDef;

(2)RCC_ClkInitTypeDef类型结构体

RCC_ClkInitTypeDef结构体定义如下,主要是定义AXI和MPU的时钟源以及分频系数,还有APB1~APB5的分频系数,通过给结构体成员赋值即可实现对MPU、AXI和APB进行分频。​

stm32mp1xx_hal_rcc.h文件代码​

typedef struct​
{​
uint32_t ClockType; /* 时钟源选择 */ ​
RCC_MPUInitTypeDef MPUInit; /* MPU结构参数(时钟源和分频数) */ ​
RCC_AXISSInitTypeDef AXISSInit; /* AXI结构参数(时钟源和分频数)*/ ​
RCC_MCUInitTypeDef MCUInit; /* APB4分频数 */ ​
uint32_t APB4_Div; /* APB4分频数 */ ​
uint32_t APB5_Div; /* APB5分频数 */ ​
uint32_t APB1_Div; /* APB1分频数 */ ​
uint32_t APB2_Div; /* APB2分频数 */ ​
uint32_t APB3_Div; /* APB3分频数 */ ​
} RCC_ClkInitTypeDef;

分析完了结构体,整个系统时钟初始化就大概明白了,需要哪个时钟源,要用那个PLL以及PLL的各个参数,要对那根总线进行分频以及分频系数是多少等等,我们都可以通过给结构体成员赋值来实现。​

(3)实验配置步骤

根据以上代码,我们分析一下时钟设置函数的配置流程:​

1、使能RTC和备份域的访问(此步骤非必须)​

如果要使用LSE,则应执行以下代码(如果不使用LSE,则可以忽略这步骤):​

HAL_PWR_EnableBkUpAccess(); /* 使能对RTC和备份寄存器的访问 */​
__HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_MEDIUMHIGH); /* 允许修改LSE */

在复位状态下,RCC_BDCR,PWR_CR2,RTC和备份寄存器受到保护,如果将HSE进行分频后作为RTC时钟,则应保持启用“备份域访问”。同时,由于LSE在备份域中,并且重置后拒绝对该域的写访问,因此必须在配置LSE之前使用HAL_PWR_EnableBkUpAccess函数启用写访问权限(重置后必须执行一次)。​

2、开启HSE、LSE、HSI、CSI和PLL​

如果要使用HSE,则必须先开启HSE,同理,使用其它时钟源也是一样的,先开启。​

3、配置PLL参数​

PLL有PLL1~PLL4,使用哪一个就配置哪一个参数,以PLL3为例子,需要配置预分频系数DIVM、倍频系数和DIVN和分频系数DIVP、DIVQ、DIVR。​

4、配置MCU和MPU的时钟源以及分频系数​

5、配置AHB和APB的分频系数​

3. main.c文件调用

main.c文件代码如下:​

#include "./SYSTEM/sys/sys.h"​
#include "./SYSTEM/delay/delay.h"​
#include "./BSP/LED/led.h"​
#include "./BSP/BEEP/beep.h"​
#include "./BSP/KEY/key.h"​

/**​
* @brief主函数​
* @param无​
* @retval无​
*/​
int main(void)​
{​
HAL_Init(); /* 初始化HAL库 */​
/* 初始化M4内核时钟 */​
if(IS_ENGINEERING_BOOT_MODE())​
{​
sys_stm32_clock_init(34, 2, 2, 17, 6826);​
}​
led_init(); /* 初始化LED */​
while(1) ​
{​
LED0(0); /* LED0亮 */​
delay(100); /* 延时一段时间 */​
LED0(1); /* LED0灭 */​
delay(100); /* 延时一段时间 */​
//HAL_Delay(10); /* 也可以使用HAL库的HAL_Delay实现延时 */​
}​
}

以上代码中,先执行HAL_Init函数完成HAL库的初始化,此时MCU的时钟为64MHz。执行sys_stm32_clock_init函数后,MCU的时钟就不再是64MHz了,根据参数范围定义,MCU子系统时钟最大可以配置为209MHz,推荐配置参数plln、pllm、pllp、pllfracv的值分别为:34、2、2和6826,代入公式:​

pll3_p_ck=
=209MHz

所以此时MCU的时钟为209MHz,此外,AHB1~AHB4的时钟频率为209MHz,APB1~APB3的时钟为104.5MHz,APB5为66.625MHz,APB4为133.25MHz,AXI、AHB5和AHB6为266.5MHz。LED0使用的是PI0引脚,而GPIOI挂在AHB4上,所以GPIO的时钟频率为209MHz。​

18.5 编译和测试

保存修改,编译无报错后进入仿真模式,运行后可以看到LED0在闪烁,说明时钟配置是OK的。我们可以通过查看全局变量SystemCoreClock看看此时的MCU时钟是不是209MHz,步骤如下:​

进入仿真后,运行程序,然后点击菜单栏的Watch WindowsàWatch1添加观察窗口:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_寄存器_34


图18.5.1 添加观察变量​

在观察窗口中添加要查看的变量SystemCoreClock,其值为0xC7516C0,计算得出209000128,约等于209M,所以我们的配置是正确的:​

《DFZU2EG_4EV MPSoc之FPGA开发指南》第十八章  系统时钟配置实验​_系统时钟_35


图18.5.2 观察SystemCoreClock变量​

后续的实验中,我们就直接调用sys_stm32_clock_init函数实现MCU时钟的配置以及总线时钟的配置,就不默认采用HSI作为系统时钟了。​


标签:MPSoc,PLL,HAL,FPGA,HSE,DFZU2EG,寄存器,时钟,RCC
From: https://blog.51cto.com/u_15046463/5948175

相关文章

  • 基于FPGA的SD卡的数据读写实现(SD NAND FLASH)
    文章目录1、存储芯片分类 2、NORFlash与 NANDFlash的区别3、什么是SD卡?4、什么是SDNAND?5、SDNAND的控制时序6、FPGA实现SDNAND读写6.1、设计思路6.2、仿真结果6.3、......
  • 《DFZU2EG_4EV MPSoc之FPGA开发指南》第十七章 按键输入实验
    第十七章按键输入实验上一章,我们介绍了STM32MP157的IO口作为输出的使用。本章,我们向大家介绍IO口作为输入使用的操作方法,我们将利用板载的3个按键来控制LED灯亮和灭以及......
  • 《DFZU2EG_4EV MPSoc之FPGA开发指南》第十六章 蜂鸣器实验
    第十六章蜂鸣器实验上一章,我们介绍了STM32MP157的IO口作为输出的使用。本章,我们将通过另外一个例子继续巩固IO口作为输出使用的操作方法,不同的是本章讲的不是用IO口直接......
  • 【学习FPGA必看】一个FPGA小白的自述
       知道明德扬科教是一个偶然,但后来耐心看完明德扬知识大串讲系列视频却是一个必然。因为做得用心,所以看得放心。本人是一个纯FPGA小白,就连FPGA这个名词我都是最近才知......
  • 基于DSP+ZYNQ平台Zynq7035/45 FPGA高速串行接口的千兆以太网UDP例程设计和使用说明
         Xines基于XilinxXC7Z035/45-2FFG676I自研平台XQ6657Z35-EVM的Zynq7035/45PL端高速串行接口,使用千兆以太网通讯方式来测试验证底板上的光口通信,实现以下以......
  • FPGA新起点V1开发板(四)——Modelsim安装
    文章目录​​一、Modelsim的介绍​​​​二、安装​​​​三、破戒​​一、Modelsim的介绍Mentor公司的ModelSim是业界最优秀的HDL语言仿真软件,它能提供友好的仿真环境,是业......
  • 基于FPGA的百兆以太网ARP测试实验
    由于各种原因,如旧式电脑的网卡不支持千兆网等,导致PHY在自协商时速率达不到1000Mbps。而目前主流的FPGA的以太网实验均基于千兆网络编写测试代码,因此根据千兆网络降速编写百......
  • FPGA串口发送模块
    1、串口原理通用异步收发传输器(UniversalAsynchronousReceiver/Transmitter,UART)是一种异步收发传输器,其在数据发送时将并行数据转换成串行数据来传输,在数据接收时将接......
  • 使用串口将数据回传至FPGA
    在使用串口传输助手读取txt文件中的数据传送至FPGA芯片时发现传输助手在发送文件中数据时是以字符串的形式发送。    例如,发送数据124时,串口助手是将其当做1、2、3......
  • 《DFZU2EG_4EV MPSoc之FPGA开发指南》第十五章 RGB LCD彩条显示实验​
    RGBLCD彩条显示实验​TFT-LCD是一种液晶显示屏,它采用薄膜晶体管(TFT)技术提升图像质量,如提高图像亮度和对比度等。相比于传统的CRT显示器,TFT-LCD有着轻薄、功耗低、无辐射、......