Operating Systems for Practical Programmers [3] – Clock In

/ 1评 / 0

如果说 x86 的操作系统开发简单在哪,我想是不需要手动初始化时钟——4.77MHz 就是主流,倍频之类的事情由 BIOS 来控制。而在 STM32 里面,没有 BIOS 这个说法,你编写的就是 BIOS.

上一节我们为 C 语言的使用铺平了道路,现在我们就可以用 C 来写这些初始化函数了。当然,如果你使用 CubeMX, 那么 system_stm32f4xx.c 这个文件也会被一并生成。但是,我们既然都很硬核地写了 startup.s 了,而且还用了不同于 ST 的命名风格,那么这个初始化文件也得自己写了。

So let's get it started.


标准外设库

首先,为了不至于重造太多轮子,我们还是会使用标准外设库的。手动操作寄存器的感觉太糟糕了,还是少做为好。

时钟源

根据硬件设计的不同,你可以为芯片提供不同的信号,并配置不同的倍频线路和分频线路驱动核心和外设。我们以一个典型的搭载 8MHz 晶振的开发板入手尝试设计。

从《数据手册》中可以知道,CPU 主频最大为 168MHz. 我们要实现的操作系统显然是希望能够利用全部 CPU 性能的,所以我们的目标时钟频率 为 168MHz.

从《参考手册》的图 16 可以获得 STM32F4 的时钟图。这个时钟图非常的庞大,但是我们需要关心的目前只有驱动 CPU 的时钟信号。之后我们需要相应外设的时候再对外设的时钟信号进行调整。

简单计算可以知道,\( 168 \div 8 = 21 \). 但是通过简单的倍频是不可能出现 21 倍频的,因为 21 不是 2 的倍数,所以我们将使用锁相环来实现 21 倍频。

STM32F4 的锁相环配置

F4 包含 2 个 PLL, 分别为主 PLL 和 I2SPLL. 我们现在关心的是前者。

每个 PLL 中需要配置的变量有 /M, *N, /P/Q. 根据时钟图,我们知道目标配置应该满足 \( 8 \div M \times N \div P = 168 \) 且 \( 8 \div M \times N \div Q = 48 \). 求 168 和 48 的最小公倍数为 336 = 42 * 8.

但是 M, N, P, Q 的值并不能随意配置。它们的取值范围分别为:

PLL 变量取值范围
M2 - 63
N50 - 432
P2, 4, 6, 8
Q2 - 15

N = 84, M = 2, P = 2, Q = 7 即可满足要求。

其他需要注意时钟的位置还有外设总线,APB2 的频率不高于 84MHz, APB1 的频率不高于 42MHz. 这两个频率都可以通过简单分频得到。

配置代码

#include "stm32f4xx.h" // 包含基本的外设定义

const uint32_t N = 84, M = 2, P = 2, Q = 7;
void SetupClock();
void SetupCoprocessor();
void SetupFlash();

void SystemInit() { // 在 startup.s 中调用的设置函数
  // RCC_CR 寄存器的配置见《参考手册》7.3.1 节
  RCC->CR |= 0x00000001u; // HSION = 1, 启用内部高频率晶振
  // RCC_CFGR 寄存器的配置见《参考手册》7.3.3 节
  RCC->CFGR = 0; // 清零时钟输出与分频配置
  RCC->CR &= 0xFEF6FFFFu; // HSEON = 0, CSSON = 0, PLLON = 0, 关闭外部晶振、时钟监控和 PLL 以便配置
  // RCC_PLLCFGR 寄存器的配置见《参考手册》7.3.2 节
  RCC->PLLCFGR = 0x24003010u; // 初始化 PLL 配置寄存器
  RCC->CR &= 0xFFFBFFFFu; // HSEBYP = 0, 不绕过外部晶振
  RCC->CIR = 0; // 关闭所有时钟中断
  // SCB 寄存器见《编程手册》4.4 节
  SCB->VTOR = 0; // 中断向量表在 0x0000_0000, 代码段

  volatile uint32_t status = 0;
  RCC->CR |= 0x00010000u; // HSEON = 1, 启用外部晶振
  do {
    status = RCC->CR & 0x00020000;
  } while (status == 0); // 等待外部时钟启动
  // 7.3.13
  RCC->APB1ENR |= 0x10000000u; // 启动电源外设时钟
  // 5.4.1
  PWR->CR |= 0x00004000u; // 使用电源模式 1
  RCC->CFGR &= 0xffffff0fu; // AHB 不分频
  RCC->CFGR |= 0x00009400u; // APB2 使用 2 分频、APB1 使用 4 分频
  RCC->PLLCFGR = (Q << 24u) | (1u << 22u) | (P << 16u) | (N << 6u) | M; // PLL 配置
  RCC->CR |= 0x01000000; // PLLON = 1, 启动 PLL
  do {
    status = RCC->CR & 0x02000000u;
  } while (status == 0); // 等待 PLL 启动
  RCC->CFGR |= 0x00000002u; // 选择 PLL 作为系统时钟源
  do {
    status = RCC->CFGR & 0x00000008u;
  } while (status == 0); // 等待系统时钟源切换

  // 《编程手册》4.6.1
  SCB->CPACR = 0x00f00000; // 启用协处理器

  // 3.9.1
  FLASH->ACR = 0x00000705u; // DCEN = 1, ICEN = 1, PRFEN = 1, LATENCY[2:0] = 5, 使用数据缓存、指令缓存、预取、延迟 5 个指令周期
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Your comments will be submitted to a human moderator and will only be shown publicly after approval. The moderator reserves the full right to not approve any comment without reason. Please be civil.