如果说 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 的时钟信号。之后我们需要相应外设的时候再对外设的时钟信号进行调整。
简单计算可以知道,
STM32F4 的锁相环配置
F4 包含 2 个 PLL, 分别为主 PLL 和 I2SPLL. 我们现在关心的是前者。
每个 PLL 中需要配置的变量有 /M
, *N
, /P
和 /Q
. 根据时钟图,我们知道目标配置应该满足
但是 M
, N
, P
, Q
的值并不能随意配置。它们的取值范围分别为:
PLL 变量 | 取值范围 |
---|---|
M | 2 - 63 |
N | 50 - 432 |
P | 2, 4, 6, 8 |
Q | 2 - 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 个指令周期
}
正在加载评论……