11-使用 HSE/HSI 配置时钟

1 RCC主要作用—时钟部分


RCC(Reset and Clock Control)是嵌入式系统中用于管理时钟和复位的控制模块

  • 时钟设置
    • 系统时钟(SYSCLK): RCC 负责设置系统时钟的频率,这是系统的主时钟信号,所有其他时钟都基于 SYSCLK。
    • 时钟频率配置: SYSCLK 通常设置为 180 MHz。
  • 总线时钟分频
    • AHB 分频因子: RCC 设置 AHB 总线的分频因子,决定 HCLK 的频率。HCLK 是 AHB 总线的时钟信号,通常与 SYSCLK 相同(例如 HCLK = SYSCLK = 180 MHz)。
    • APB 分频因子: RCC 设置 APB2 和 APB1 总线的分频因子
      • PCLK2: APB2 总线的时钟,通常配置为 PCLK2 = HCLK / 2 = 90 MHz。
      • PCLK1: APB1 总线的时钟,通常配置为 PCLK1 = HCLK / 4 = 45 MHz。
  • 外设时钟管理
    • 外设时钟的分频: RCC 控制各个外设的时钟,包括定时器、ADC、USART 等,允许开发者根据需要对外设时钟进行独立配置和分频。
    • 外设时钟的开启与关闭: RCC 还负责控制每个外设的时钟是否开启,以节省功耗并提高系统效率。
  • 总线时钟的开启控制
    • AHB、APB2、APB1 总线时钟的开启: RCC 负责控制这三条总线的时钟是否开启,确保系统在运行时能够使用这些总线进行数据传输。
  • 标准配置
    • 库函数标准配置: 上述时钟配置(HCLK = SYSCLK = 180 MHz,PCLK1 = 90 MHz,PCLK2 = 45 MHz)通常作为库函数的标准配置,开发者在进行嵌入式系统开发时经常使用这一配置以满足大部分应用需求。

2 RCC 框图剖析—时钟树

2.1 系统时钟

2.1.1 HSE 高速外部时钟信号

HSE(High-Speed External)是嵌入式系统中常用的外部时钟信号,通常由晶振提供。

HSE 是一种重要的外部时钟信号,能够为嵌入式系统提供高速稳定的时钟源。通过选择合适的有源或无源晶振,并利用 PLL 进行倍频,系统可以获得所需的时钟频率。同时,HSE 的故障处理机制确保了系统在外部时钟失效时能够快速切换到内部时钟,保证系统的持续运行和稳定性。

2.1.1.1 HSE 的定义和频率范围
  • HSE 是一种高速的外部时钟信号,能够为微控制器提供高频率的时钟源。
  • HSE 的工作频率通常在 4 MHz 到 26 MHz 之间。
2.1.1.2 晶振类型
  • 有源晶振:当使用有源晶振时,时钟信号从 OSC_IN 引脚输入,而 OSC_OUT 引脚则悬空。 有源晶振内置振荡电路,可以直接提供稳定的时钟信号
  • 无源晶振:使用无源晶振时,时钟信号从 OSC_IN 和 OSC_OUT 引脚进入,并且需要外接谐振电容来形成振荡回路。无源晶振通常更便宜,但需要额外的电路来提供稳定的时钟信号。
2.1.1.3 晶振选择
  • 在实际应用中,通常选择 25 MHz 的无源晶振作为 HSE。
  • HSE 可以直接用作系统时钟(SYSCLK),也可以通过 PLL 进行倍频,以满足更高频率的需求。
2.1.1.4 故障处理机制
  • 如果 HSE 出现故障,系统会自动关闭 HSE 和与之相关联的 PLL;
  • 在 HSE 故障的情况下,系统会切换到内部时钟信号 HSI,其频率为 16 MHz,作为备用的系统时钟;
  • 一旦 HSE 恢复正常,系统可以重新切换回 HSE 作为主时钟源。

2.1.2 锁相环 PLL

PLL(Phase-Locked Loop)是一种用于时钟倍频和分频的电路,主要作用是将输入时钟信号进行倍频处理,并输出到系统的各个功能模块。

2.1.2.1 PLL 的基本概念
  • 分为两种:
    • 主 PLL: 负责系统时钟和部分外设时钟的生成;
    • 专用 PLLI2S: 主要用于生成精确的时钟信号,供 I2S 接口使用。
  •  PLL 可以由 HSE(高速外部时钟)或 HSI(高速内部时钟)提供输入信号
2.1.2.2 主 PLL 的输出
  • PLLCLK: 用于系统时钟,F429 系列中最高可达 180 MHz。
  • USB OTG FS 时钟: 48 MHz,用于 USB 外设。
  • RNG 和 SDIO 时钟: 最高可达 48 MHz。
2.1.2.3 VCO
  • VCO 输入
    • PLL 输入时钟通过分频因子 M(范围 2~63)进行分频,以适应 VCO 的工作频率;
    • 例如,选择 HSE 为 25 MHz,M 设置为 25,则 VCO 输入时钟为 1 MHz。
  • VCO 输出
    • VCO 输出时钟需在 192 MHz 到 432 MHz 之间。通过倍频因子 N 进行倍频处理;
    • 例如,设置 N 为 360,则 VCO 输出为 360 MHz。
2.1.2.4 时钟超频
  • 超频限制:最大输出时钟公式为:PLLCLK_OUTMAX = VCOCLK_OUTMAX / P_MIN = 432 / 2 = 216 MHz,即 F429 可超频至 216 MHz。
  • 如果需要超频,可以调整 VCO 倍频系数 N
2.1.2.5  分频因子的配置
  • 分频因子
    • p:PLLCLK 分频因子,取值为 2、4、6、8。在本例中设置为 2,得出 PLLCLK = 180 MHz。
    • Q:USB OTG FS/RNG/SDIO 时钟分频因子,取值为 4~15。为了满足 USB 的 48 MHz 需求,需要重新配置 VCO 的倍频因子。
  • 重新配置:
    • 初始配置为 N=360,得出 USB 时钟出现小数(Q=360/48=7.5,不合适),通过调整 N=336,得出:
      • VCOCLK = 1 MHz * 336 = 336 MHz
      • PLLCLK = VCOCLK / 2 = 168 MHz
      • USBCLK = VCOCLK / Q = 336 / 7 = 48 MHz
    • 这样,在使用 USB 时,PLLCLK 被降低至 168 MHz,这乃是ST设计的问题。

2.1.3 系统时钟

系统时钟(SYSCLK)是微控制器中用于协调各种操作和功能的重要时钟信号。

2.1.3.1 SYSCLK 的来源
  • HSI(高速内部时钟): 内部生成的时钟信号,频率为 16 MHz。
  • HSE(高速外部时钟): 外部晶振提供的时钟信号,频率通常在 4 MHz 到 26 MHz 之间。
  • PLLCLK(相位锁定环时钟): 由 HSE 或 HSI 经过 PLL 倍频处理后得到的高频时钟,F429 系列中最高可达 180 MHz。
2.1.3.2 时钟配置寄存器
  • 时钟配置寄存器 RCC_CFGR。
  • 该寄存器的 SW 位用于选择当前的系统时钟源。
2.1.3.3 故障处理机制
  • HSE 故障:当系统时钟源为 HSE 经过 PLL 倍频得到的 PLLCLK 时,如果 HSE 发生故障,系统时钟会自动切换到 HSI 作为备用时钟。这种切换确保了系统在外部时钟失效的情况下仍然能够正常运行。
  • HSI 的频率:切换后的系统时钟频率为 16 MHz,直到 HSE 恢复正常。
2.1.3.4 时钟切换流程
  • 正常状态:当 HSE 正常工作时,系统时钟为 SYSCLK = PLLCLK = 180 MHz,满足高性能应用需求。
  • 故障状态:一旦 HSE 检测到故障,系统会自动切换到 HSI,确保系统的持续运行和稳定性。
  • 恢复状态:HSE 恢复正常后,系统可重新切换回 PLLCLK,以恢复到高频操作。

2.1.4 AHB 总线时钟 HCLK

AHB(Advanced High-performance Bus)总线时钟 HCLK 是系统时钟 SYSCLK 经过预分频器处理后的结果,主要用于为片上外设提供时钟信号。

HCLK 设置为与 SYSCLK 相同,即 HCLK = 180 MHz,这种配置可以确保系统运行在最高性能状态,适合对时钟频率要求较高的应用。

2.1.4.1 HCLK 的定义
  • 来源: HCLK 是系统时钟 SYSCLK 经过 AHB 预分频器分频后得到的时钟信号。
  • 作用: HCLK 为片上大部分外设提供时钟信号,是系统内各个模块间通信的重要基础。
2.1.4.2  分频因子
  • 可选分频因子: HCLK 的分频因子可以设置为以下值:[1, 2, 4, 8, 16, 64, 128, 256, 512]
  • 配置方式: 通过时钟配置寄存器 RCC_CFGR 的 HPRE 位进行设置。
2.1.4.3 外设时钟的设置
  • 片上外设: 大多数片上外设的时钟通常是从 HCLK 分频得到的。
  • 外设时钟配置: 对于 AHB 总线上的外设,其具体时钟设置通常在使用该外设时进行调整。在初始配置阶段,只需粗略设置 APB 总线的时钟即可。
  • APB 总线: APB(Advanced Peripheral Bus)是与 AHB 总线相连接的外设总线,通常需要单独配置其时钟。

2.1.5 APB2 总线时钟 PCLK2

APB2(Advanced Peripheral Bus 2)总线时钟 PCLK2 是从 HCLK 经过预分频器得到的时钟信号,主要用于为高速外设提供时钟。

2.1.5.1 PCLK2 的定义
  • 来源: PCLK2 是 HCLK 经过 APB2 预分频器处理后得到的时钟信号。
  • 作用: PCLK2 为连接到 APB2 总线的高速外设提供时钟,包括 GPIO、USART1、SPI1 等。
2.1.5.2  分频因子
  • 可选分频因子: PCLK2 的分频因子可以设置为以下值:[1, 2, 4, 8, 16]
  • 配置方式: 通过时钟配置寄存器 RCC_CFGR 的 PPRE2 位进行设置。
2.1.5.3  外设时钟的设置
  • 片上外设: APB2 总线上的高速外设如 GPIO、USART1 和 SPI1 等,通常会从 PCLK2 获取时钟信号。
  • 外设时钟配置: 具体外设的时钟设置通常在使用该外设时进行调整。在初始配置阶段,可以粗略设置 APB2 的时钟,以便在后续具体应用时进行详细配置。

2.1.6 APB1 总线时钟 PCLK1

APB1(Advanced Peripheral Bus 1)总线时钟 PCLK1 是从 HCLK 经过低速 APB 预分频器得到的时钟信号,主要用于为低速外设提供时钟。

2.1.6.1 PCLK1 的定义
  • 来源: PCLK1 是 HCLK 经过 APB1 预分频器处理后得到的时钟信号。
  • 作用: PCLK1 为连接到 APB1 总线的低速外设提供时钟,包括 USART2、USART3、USART4、USART5、SPI2、SPI3、I2C1 和 I2C2 等。
2.1.6.2 分频因子
  • 可选分频因子: PCLK1 的分频因子可以设置为以下值:[1, 2, 4, 8, 16]
  • 配置方式: 通过时钟配置寄存器 RCC_CFGR 的 PPRE1 位进行设置。
2.1.6.3 外设时钟的设置
  • 片上外设: APB1 总线上的低速外设,如 USART 和 I2C 等,通常会从 PCLK1 获取时钟信号。
  • 外设时钟配置: 具体外设的时钟设置通常在使用该外设时进行调整。在初始配置阶段,可以粗略设置 APB1 的时钟,以便在后续具体应用时进行详细配置。

2.1.7 设置系统时钟库函数

/*
 * 使用 HSE 时,设置系统时钟的步骤
 * 1、开启 HSE ,并等待 HSE 稳定
 * 2、设置 AHB、APB2、APB1 的预分频因子
 * 3、设置 PLL 的时钟来源
 * 4、设置 VCO 输入时钟 分频因子 m
 * 5、设置 VCO 输出时钟 倍频因子 n
 * 6、设置 PLLCLK 时钟分频因子 p
 * 7、设置 OTG FS, SDIO, RNG 时钟分频因子 q
 * 4、开启 PLL,并等待 PLL 稳定
 * 5、把 PLLCK 切换为系统时钟 SYSCLK
 * 6、读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
 */

#define PLL_M 25  // PLL 输入时钟分频因子
#define PLL_N 360 // PLL 输出倍频因子
#define PLL_P 2   // PLL 输出时钟分频因子
#define PLL_Q 7   // USB OTG FS, SDIO, RNG 时钟分频因子
// 如果要超频的话,修改 PLL_N 这个宏即可,取值范围为:192~432。

void SetSysClock(void)
{
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0; // 定义启动计数器和HSE状态变量

    // 使能 HSE (High-Speed External oscillator)
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);

    // 等待 HSE 启动稳定
    do {
        HSEStatus = RCC->CR & RCC_CR_HSERDY; // 检查 HSE 是否已准备好
        StartUpCounter++; // 增加计数器
    } while ((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); // 等待 HSE 稳定或超时

    // 检查 HSE 是否启动成功
    if ((RCC->CR & RCC_CR_HSERDY) != RESET) {
        HSEStatus = (uint32_t)0x01; // HSE 启动成功
    } else {
        HSEStatus = (uint32_t)0x00; // HSE 启动失败
    }

    // HSE 启动成功
    if (HSEStatus == (uint32_t)0x01) {
        // 调压器电压输出级别配置为 1,以便在器件为最大频率工作时
        // 使性能和功耗实现平衡
        RCC->APB1ENR |= RCC_APB1ENR_PWREN; // 使能电源控制时钟
        PWR->CR |= PWR_CR_VOS; // 设置调压器电压输出级别

        // 设置 AHB/APB2/APB1 的分频因子
        // HCLK = SYSCLK / 1
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB 分频因子为 1
        // PCLK2 = HCLK / 2
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2 分频因子为 2
        // PCLK1 = HCLK / 4
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; // APB1 分频因子为 4

        // 配置主 PLL 的时钟来源,设置 M, N, P, Q
        RCC->PLLCFGR = PLL_M | (PLL_N << 6) | // 设置 PLL_M 和 PLL_N
                       (((PLL_P >> 1) - 1) << 16) | // 设置 PLL_P,右移1位并减1
                       (RCC_PLLCFGR_PLLSRC_HSE) | // PLL 时钟源选择 HSE
                       (PLL_Q << 24); // 设置 PLL_Q

        // 使能主 PLL
        RCC->CR |= RCC_CR_PLLON; // 使能 PLL

        // 等待 PLL 稳定
        while ((RCC->CR & RCC_CR_PLLRDY) == 0) {
            // 等待 PLL 稳定
        }

        /*----------------------------------------------------*/
        // 开启 OVER-RIDE 模式,以能达到更改频率
        PWR->CR |= PWR_CR_ODEN; // 使能超频模式
        while ((PWR->CSR & PWR_CSR_ODRDY) == 0) {
            // 等待超频模式准备就绪
        }

        PWR->CR |= PWR_CR_ODSWEN; // 使能超频开关
        while ((PWR->CSR & PWR_CSR_ODSWRDY) == 0) {
            // 等待超频开关准备就绪
        }

        // 配置 FLASH 预取指, 指令缓存, 数据缓存和等待状态
        FLASH->ACR = FLASH_ACR_PRFTEN | // 使能预取指
                     FLASH_ACR_ICEN | // 使能指令缓存
                     FLASH_ACR_DCEN | // 使能数据缓存
                     FLASH_ACR_LATENCY_5WS; // 设置 FLASH 等待状态为 5 个周期
        /*---------------------------------------------------*/

        // 选择主 PLLCLK 作为系统时钟源
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); // 清除系统时钟源选择位
        RCC->CFGR |= RCC_CFGR_SW_PLL; // 设置 PLLCLK 为系统时钟源

        // 读取时钟切换状态位,确保 PLLCLK 选为系统时钟
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL) {
            // 等待 PLLCLK 被选为系统时钟
        }
    } else {
        // HSE 启动出错处理
        // 可以在这里添加错误处理代码,例如重试或进入安全模式
    }
}

2.2 其他时钟

2.2.1 RTC 时钟

RTCCLK 是用于实时时钟(RTC)的时钟源,可以选择不同的时钟输入源。

通常建议使用 LSE 作为 RTC 的时钟源,频率为 32.768 kHz。该频率是 RTC 设计的标准频率,能够提供高精度和低功耗的时钟信号。

晶体谐振器要求: LSE 需要外接的晶体谐振器,配合适当的谐振电容。要确保谐振电容的精度较高,否则可能导致晶体无法正常振荡,从而影响 RTC 的准确性。

RTC 广泛用于需要时间跟踪的应用,如计时器、闹钟、日历等,能够在系统断电时依然保持准确的时间。

2.2.1.1 定义
  • RTCCLK 是为实时时钟(RTC)提供时钟信号的源,确保系统能够准确跟踪时间和日期。
  • 可选时钟源
    • HSE: 外部高速晶体振荡器,频率为 1 MHz,通过可编程预分频器进行分频。
    • LSE: 外部低速晶体振荡器,通常配置为 32.768 kHz。
    • LSI: 内部低速振荡器,频率通常为 40 kHz(可能会有所不同)。
2.2.1.2 寄存器配置
  • RCC 备份域控制寄存器 (RCC_BDCR): 使用 RTCSEL[1:0] 位选择 RTC 时钟源。
  • RCC 时钟配置寄存器 (RCC_CFGR): 使用 RTCPRE[4:0] 位进行 RTC 时钟的预分频配置。
  • 选择的时钟源只能通过复位备份域的方式进行修改,这意味着在运行时无法动态更改。

2.2.2 独立看门狗时钟

独立看门狗(Independent Watchdog)是微控制器中用于监控系统运行状态的一个重要功能,其时钟源对于确保其正常工作至关重要。

  • 独立性: 独立看门狗的工作不依赖于主系统时钟,这使得它在系统故障时仍能正常运行,增加了系统的可靠性。
  • 低功耗: 由于使用 LSI,独立看门狗的功耗相对较低,适合于电池供电或低功耗应用场景。
  • 自动复位功能: 在系统发生异常时,独立看门狗能够自动触发复位,减少人为干预,提高系统的稳定性。
2.2.2.1 定义
  • 独立看门狗时钟用于驱动看门狗定时器,确保系统在出现故障时能够自动复位。看门狗定时器会定期重置,如果未在预定时间内重置,则会产生复位信号,帮助恢复系统的正常状态。
2.2.2.2 时钟源
  • 内部低速时钟 (LSI): 独立看门狗时钟由内部的低速振荡器 LSI 提供。
    • 频率: LSI 的典型频率为 32 kHz。
    • 特点: LSI 是一种低功耗的振荡器,适合在低功耗应用中使用,能够在系统处于待机状态时保持运行。

2.2.3 I2S 时钟

I2S(Inter-IC Sound)是一种用于数字音频数据传输的标准协议,I2S 时钟是该协议中至关重要的部分。

2.2.3.1 定义
  • 功能: I2S 时钟用于同步音频数据的传输,确保音频数据在发送和接收设备之间的正确对齐和时序。
  • 重要性: 正确的时钟配置对于音频信号的质量和稳定性至关重要。
2.2.3.2 时钟源
  • 可选时钟源:
    • 外部时钟引脚 (I2S_CKIN): I2S 时钟可以通过外部时钟输入引脚(I2S_CKIN)输入。这种方式适用于需要使用外部时钟源的场合。
    • PLLI2SCLK: 另一种选择是使用专用的 PLLI2SCLK,这是一种通过相位锁环(PLL)生成的时钟,专门用于 I2S 接口。
  • 配置方式: I2S 时钟源的选择通过 RCC 时钟配置寄存器 (RCC_CFGR) 中的 I2SSCR 位进行配置。
2.2.3.3 推荐配置
  • 使用 PLLI2SCLK: 在使用 I2S 外设驱动 W8978 时,推荐使用 PLLI2SCLK 作为时钟源。这种配置具有以下优点:
    • 节省有源晶振: 使用 PLLI2SCLK 可以避免使用外部有源晶振,从而减少了硬件成本和设计复杂性。
    • 灵活性: PLLI2SCLK 提供的时钟频率可以根据具体应用需求进行调整,确保与音频设备的兼容性。

2.2.4 PHY 以太网时钟

在实现以太网功能时,微控制器(如 F429)需要外接一个物理层收发器(PHY)芯片,以便与网络进行通信。

2.2.4.1 定义
  • 功能: PHY 以太网时钟用于同步以太网数据的传输,确保数据在以太网层和 MAC 层之间的正确对齐。
  • 重要性: 正确的时钟配置是实现稳定和高效以太网通信的关键。
2.2.4.2 PHY 芯片选择
  • 常见 PHY 芯片:
    • DP83848: 支持 MII(媒体独立接口)和 RMII(简化媒体独立接口)两种接口。
    • LAN8720: 仅支持 RMII 接口。
  • 开发板配置: 野火 F429 开发板采用 RMII 接口,选择的 PHY 芯片是 LAN8720。
2.2.4.3  接口选择
  • RMII 接口的优势:
    • 减少 I/O 引脚: RMII 接口相比于 MII 接口,使用的 I/O 引脚减少了一半。这对于 I/O 资源有限的嵌入式系统尤为重要。
    • 相同速度: RMII 接口的传输速度与 MII 接口相同,通常为 100 Mbps,确保了数据传输的效率。
  • 时钟输出要求:
    • RMII 接口: 在使用 RMII 接口时,PHY 芯片只需向 MCU 输出一路时钟。这简化了设计,并减少了对外部时钟源的需求。
    • MII 接口: 如果使用 MII 接口,PHY 芯片需要提供两路时钟给 MCU,增加了设计的复杂性和 I/O 需求。

2.2.5 USB PHY 时钟

在使用微控制器 F429 实现 USB 功能时,由于其内部没有集成 USB PHY,必须外接一个 USB PHY 芯片以支持 USB 高速传输。

2.2.5.1 定义
  • 功能: USB PHY 时钟用于同步 USB 数据的传输,确保数据在 USB 层与微控制器之间的正确对齐和时序。
  • 重要性: 正确的时钟配置对于实现稳定的 USB 通信至关重要。
2.2.5.2  USB PHY 芯片选择
  • USB3300: 这是一个常用的外置 USB PHY 芯片,支持 USB 2.0 高速传输(480 Mbps)。在外接 USB3300 时,PHY 芯片需要向 MCU 提供一个时钟信号,以确保数据传输的稳定性。
2.2.5.3 接口占用与资源复用
  • I/O 资源占用: 外接 USB PHY 芯片会占用相当多的 I/O 引脚。这可能与其他外设(如 SDRAM 和 RGB888 显示接口)发生复用,导致设计复杂性增加。
  • 设计权衡: 由于 USB 高速传输的需求相对较少,野火 F429 挑战者开发板决定不外接 USB PHY 芯片,以避免 I/O 资源的浪费和设计复杂性。

2.2.6 MCO 时钟输出

MCO(Microcontroller Clock Output)是微控制器提供的一种时钟输出功能,能够将内部时钟信号输出到外部设备。

2.2.6.1 定义
  • 功能: MCO 引脚的主要作用是对外提供时钟信号,类似于有源晶振,供外部设备或电路使用。这种功能在需要同步多种设备时尤其重要。
  • 重要性: MCO 使得微控制器能够作为外部时钟源,增强了系统的灵活性和互操作性。
2.2.6.2 MCO 引脚配置
  • 引脚数量: F429 微控制器提供两个 MCO 输出引脚,分别为 PA8 和 PC9。通过引脚复用,这两个引脚可以用于其他功能。
  • 复用配置: 由于 MCO 引脚与其他功能复用,合理配置引脚使用非常重要,以确保系统的正常运行。
2.2.6.3 时钟源选择
  • MCO1 配置:
    • 引脚: PA8
    • 时钟来源: HSI(内部高速时钟)、LSE(低速外部时钟)、HSE(高速外部时钟)、PLLCLK(相位锁环时钟)。
    • 最大输出速率: 100 MHz
  • MCO2 配置:
    • 引脚: PC9
    • 时钟来源: HSE、PLLCLK、SYSCLK(系统时钟)、PLLI2SCLK(I2S 时钟)。
    • 最大输出速率: 100 MHz
时钟输出 IO 引脚 时钟来源 最大输出速率
MCO1 PA8 HSI、LSE、HSE、PLLCLK 100 MHz
MCO2 PC9 HSE、PLLCLK、SYSCLK、PLLI2SCLK 100 MHz

3 系统时钟实验

以下代码来源于野火:
https://gitee.com/Embedfire-stm32f429-tiaozhanzhe/ebf_stm32f429_tiaozhanzhe_std_code

一般情况下,我们都是使用 HSE,然后 HSE 经过 PLL 倍频之后作为系统时钟。F429 系统时钟最高为 180M。

  • 在工程目录下面的USER 文件夹中创建rcc文件夹,在新创建的KEY 文件夹中创建两个文件clock.h 和clock.c
  • clock.h:时钟宏定义
  • clock.c:时钟函数程序
  • 将clock.c 添加进USER 组中

3.1 clock.h

#ifndef __CLKCONFIG_H
#define	__CLKCONFIG_H

#include "stm32f4xx.h"
void HSE_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q);
void HSI_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q);
void MCO1_GPIO_Config(void);
void MCO2_GPIO_Config(void);

#endif

3.2 clock.c

#include "./rcc/clock.h"
#include "stm32f4xx_rcc.h"

/*
 * 使用 HSE 时,设置系统时钟的步骤
 * 1、开启 HSE,并等待 HSE 稳定
 * 2、设置 AHB、APB2、APB1 的预分频因子
 * 3、设置 PLL 的时钟来源
 *    - 设置 VCO 输入时钟分频因子 m
 *    - 设置 VCO 输出时钟倍频因子 n
 *    - 设置 PLLCLK 时钟分频因子 p
 *    - 设置 OTG FS, SDIO, RNG 时钟分频因子 q
 * 4、开启 PLL,并等待 PLL 稳定
 * 5、把 PLLCK 切换为系统时钟 SYSCLK
 * 6、读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
 */

/*
 * m: VCO 输入时钟分频因子,取值 2~63
 * n: VCO 输出时钟倍频因子,取值 192~432
 * p: PLLCLK 时钟分频因子,取值 2,4,6,8
 * q: OTG FS, SDIO, RNG 时钟分频因子,取值 4~15
 * 函数调用举例,使用 HSE 设置时钟
 * SYSCLK = HCLK = 180M, PCLK2 = HCLK / 2 = 90M, PCLK1 = HCLK / 4 = 45M
 * HSE_SetSysClock(25, 360, 2, 7);
 * HSE 作为时钟来源,经过 PLL 倍频作为系统时钟,这是通常的做法
 * 系统时钟超频到 216M
 * HSE_SetSysClock(25, 432, 2, 9);
 */
void HSE_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)
{
    __IO uint32_t HSEStartUpStatus = 0; // 定义 HSE 启动状态变量

    // 使能 HSE,开启外部晶振,野火 F429 使用 HSE = 25M
    RCC_HSEConfig(RCC_HSE_ON);
	
    // 等待 HSE 启动稳定
    HSEStartUpStatus = RCC_WaitForHSEStartUp();

    // 检查 HSE 是否启动成功
    if (HSEStartUpStatus == SUCCESS)
    {
        // 调压器电压输出级别配置为 1,以便在器件为最大频率工作时
        // 使性能和功耗实现平衡
        RCC->APB1ENR |= RCC_APB1ENR_PWREN; // 使能电源控制时钟
        PWR->CR |= PWR_CR_VOS; // 设置调压器电压输出级别
		
        // 设置 AHB/APB2/APB1 的分频因子
        RCC_HCLKConfig(RCC_SYSCLK_Div1);  // HCLK = SYSCLK / 1
        RCC_PCLK2Config(RCC_HCLK_Div2);    // PCLK2 = HCLK / 2
        RCC_PCLK1Config(RCC_HCLK_Div4);    // PCLK1 = HCLK / 4
		
        // 配置主 PLL 的时钟来源,设置 VCO 分频因子 m,倍频因子 n,
        // 以及系统时钟分频因子 p,OTG FS, SDIO, RNG 分频因子 q
        RCC_PLLConfig(RCC_PLLSource_HSE, m, n, p, q);
		
        // 使能主 PLL
        RCC_PLLCmd(ENABLE);
  
        // 等待 PLL 稳定
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }   

        /*-----------------------------------------------------*/
        // 开启 OVER-RIDE 模式,以能达到更高频率
        PWR->CR |= PWR_CR_ODEN; // 使能超频模式
        while((PWR->CSR & PWR_CSR_ODRDY) == 0)
        {
        }
        PWR->CR |= PWR_CR_ODSWEN; // 使能超频开关
        while((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
        {
        }      
        // 配置 FLASH 预取指、指令缓存、数据缓存和等待状态
        FLASH->ACR = FLASH_ACR_PRFTEN 
                    | FLASH_ACR_ICEN 
                    | FLASH_ACR_DCEN 
                    | FLASH_ACR_LATENCY_5WS;
        /*-----------------------------------------------------*/
		
        // 当 PLL 稳定之后,把 PLL 时钟切换为系统时钟 SYSCLK
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

        // 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    {
        // HSE 启动出错处理
        while (1)
        {
            // 在此处可以添加错误处理代码,例如重试或安全模式
        }
    }
}

/*
 * 使用 HSI 时,设置系统时钟的步骤
 * 1、开启 HSI,并等待 HSI 稳定
 * 2、设置 AHB、APB2、APB1 的预分频因子
 * 3、设置 PLL 的时钟来源
 *    设置 VCO 输入时钟 分频因子 m
 *    设置 VCO 输出时钟 倍频因子 n
 *    设置 SYSCLK 时钟分频因子 p
 *    设置 OTG FS, SDIO, RNG 时钟分频因子 q
 * 4、开启 PLL,并等待 PLL 稳定
 * 5、把 PLLCK 切换为系统时钟 SYSCLK
 * 6、读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
 */

/*
 * m: VCO 输入时钟 分频因子,取值 2~63
 * n: VCO 输出时钟 倍频因子,取值 192~432
 * p: PLLCLK 时钟分频因子,取值 2,4,6,8
 * q: OTG FS, SDIO, RNG 时钟分频因子,取值 4~15
 * 函数调用举例,使用 HSI 设置时钟
 * SYSCLK = HCLK = 180M, PCLK2 = HCLK / 2 = 90M, PCLK1 = HCLK / 4 = 45M
 * HSI_SetSysClock(16, 360, 2, 7);
 * HSE 作为时钟来源,经过 PLL 倍频作为系统时钟,这是通常的做法
 
 * 系统时钟超频到 216M
 * HSI_SetSysClock(16, 432, 2, 9);
 */
void HSI_SetSysClock(uint32_t m, uint32_t n, uint32_t p, uint32_t q)	
{
    __IO uint32_t HSIStartUpStatus = 0; // 定义 HSI 启动状态变量
	
    // 把 RCC 外设初始化成复位状态
    RCC_DeInit();
  
    // 使能 HSI,HSI = 16M
    RCC_HSICmd(ENABLE);
	
    // 等待 HSI 就绪
    HSIStartUpStatus = RCC->CR & RCC_CR_HSIRDY;
	
    // 只有 HSI 就绪之后则继续往下执行
    if (HSIStartUpStatus == RCC_CR_HSIRDY)
    {
        // 调压器电压输出级别配置为1,以便在器件为最大频率工作时
        // 使性能和功耗实现平衡
        RCC->APB1ENR |= RCC_APB1ENR_PWREN; // 使能电源控制时钟
        PWR->CR |= PWR_CR_VOS; // 设置调压器电压输出级别
		
        // 设置 AHB/APB2/APB1 的分频因子
        RCC_HCLKConfig(RCC_SYSCLK_Div1);  // HCLK = SYSCLK / 1
        RCC_PCLK2Config(RCC_HCLK_Div2);    // PCLK2 = HCLK / 2
        RCC_PCLK1Config(RCC_HCLK_Div4);    // PCLK1 = HCLK / 4
		
        // 配置主 PLL 的时钟来源,设置 VCO 分频因子 m,倍频因子 n,
        // 以及系统时钟分频因子 p,OTG FS, SDIO, RNG 分频因子 q
        RCC_PLLConfig(RCC_PLLSource_HSI, m, n, p, q);
		
        // 使能 PLL
        RCC_PLLCmd(ENABLE);
  
        // 等待 PLL 稳定
        while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
        {
        }   

        /*-----------------------------------------------------*/
        // 开启 OVER-RIDE 模式,以能达到更高频率
        PWR->CR |= PWR_CR_ODEN; // 使能超频模式
        while ((PWR->CSR & PWR_CSR_ODRDY) == 0)
        {
        }

        PWR->CR |= PWR_CR_ODSWEN; // 使能超频开关
        while ((PWR->CSR & PWR_CSR_ODSWRDY) == 0)
        {
        }      

        // 配置 FLASH 预取指、指令缓存、数据缓存和等待状态
        FLASH->ACR = FLASH_ACR_PRFTEN 
                    | FLASH_ACR_ICEN 
                    | FLASH_ACR_DCEN 
                    | FLASH_ACR_LATENCY_5WS;
        /*-----------------------------------------------------*/
		
        // 当 PLL 稳定之后,把 PLL 时钟切换为系统时钟 SYSCLK
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);

        // 读取时钟切换状态位,确保 PLLCLK 被选为系统时钟
        while (RCC_GetSYSCLKSource() != 0x08)
        {
        }
    }
    else
    {
        // HSI 启动出错处理
        while (1)
        {
            // 在此处可以添加错误处理代码,例如重试或进入安全模式
        }
    }
}

// MCO1 PA8 GPIO 初始化
void MCO1_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  
    // MCO1 GPIO 配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
    GPIO_Init(GPIOA, &GPIO_InitStructure); 
}

// MCO2 PC9 GPIO 初始化
void MCO2_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
  
    // MCO2 GPIO 配置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
    GPIO_Init(GPIOC, &GPIO_InitStructure);
}

3.3 main.c

// 使用 HSE 或者 HSI 配置系统时钟
#include "stm32f4xx.h"
#include "./rcc/bsp_clkconfig.h"
#include "./led/bsp_led.h"

// 函数声明
void Delay(__IO uint32_t nCount); 

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{
    // 程序来到 main 函数之前,启动文件 statup_stm32f429_439xx.s 已经调用
    // SystemInit() 函数把系统时钟初始化成 72MHz
    // SystemInit() 在 system_stm32f4xx.c 中定义
    // 如果用户想修改系统时钟,可自行编写程序进行修改

    /* 注意:由于在 PLL 使能后主 PLL 配置参数便不可更改,而系统上电后会
     * 自动进行初始化,因此在对 HSE 重新初始化之前,需要将 system_stm32f4xx.c
     * 中 line 506:SetSysClock(); 注释掉,否则 HSE 重新初始化之后不生效。
     */

    // MCO GPIO 初始化
    MCO1_GPIO_Config(); // 初始化 MCO1 的 GPIO 配置
    MCO2_GPIO_Config(); // 初始化 MCO2 的 GPIO 配置
  
    // MCO1 输出 PLLCLK
    RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_1); // 配置 MCO1 输出 PLL 时钟

    // MCO2 输出 SYSCLK
    RCC_MCO2Config(RCC_MCO2Source_SYSCLK, RCC_MCO2Div_1); // 配置 MCO2 输出系统时钟

    // 使用 HSE,配置系统时钟为 180M
    // HSE_SetSysClock(25, 360, 2, 7); // 使用 HSE 设置系统时钟为 180MHz

    // 系统时钟超频到 216M,最高是 216M,别往死里整
    HSE_SetSysClock(25, 432, 2, 9); // 使用 HSE 设置系统时钟为 216MHz
    
    // 使用 HSI,配置系统时钟为 180M
    // HSI_SetSysClock(16, 432, 2, 7); // 使用 HSI 设置系统时钟为 180MHz
    
    // LED 端口初始化 
    LED_GPIO_Config(); // 初始化 LED 相关的 GPIO 设置

    // 主循环
    while (1)
    {
        LED1(ON);              // 点亮 LED1
        Delay(0x4FFFFF);      // 延时
        LED1(OFF);             // 熄灭 LED1
        Delay(0x4FFFFF);      // 延时
    }
}

/**
  * @brief  简单的延时函数
  * @param  nCount: 延时时间计数
  * @retval 无
  */
void Delay(__IO uint32_t nCount) // 简单的延时函数
{
    for (; nCount != 0; nCount--); // 循环计数,消耗时间
}

发表评论