Cortex-M3 入門指南(三):時鐘總線與復位時鐘控制器
【reset clock control 復位和時鐘控制器】
時鐘信號對於處理器非常重要,比如我們熟悉的 CPU 就是由時鐘信號驅動的,而主頻就是內核的的時鐘信號頻率。Cortex-M3 有著復雜的時鐘樹架構,而且我們需要在初始化階段配置好時鐘參數。
本文將會先介紹時鐘相關的概念,然後介紹使用庫函數便捷設置時鐘總線的方法,在文章最後再深入學習庫函數背後等效的時鐘寄存器原理。
時鐘源
STM32F103
中有 4 種可選時鐘源:
高速外部時鐘
(HSE): 以外部晶振作時鐘源,晶振頻率可取範圍為 4~16 MHz,常用 8MHz 晶振,開發板上的 8MHz 時鐘就是指的這個。高速內部時鐘
(HSI): 由內部 RC 振蕩器產生,頻率為8MHz, 無須外接晶振,但精確性比外部時鐘差。低速外部時鐘
(LSE): 以外部晶振作時鐘源,可以提供時鐘信號給實時時鐘模塊,一般采用 32.768KHz 晶振,較為少用。低速內部時鐘
(LSI): 由內部 RC 振蕩器產生,也主要提供信號給實時時鐘模塊,頻率在 30-60KHz 間浮動,較為少用。
單片機啟動時默認使用高速內部時鐘 (HSI),啟動之後可以通過 RCC
時鐘控制寄存器器改用其他時鐘源。
系統時鐘 (SYSCLK)
系統時鐘 SYSCLK 最大頻率為 72MHz,它是供 STM32 中絕大部分部件工作的時鐘源。系統時鐘可由 PLL(鎖相環)、HSI 或者 HSE 提供輸入,並且通過 AHB(高速總線) 分頻器分頻後輸出送給各模塊使用。
鎖相環 (PLL)
如果打算使單片機運行在最高頻率 (72 MHz),我們還需要倍頻高速時鐘源的時鐘信號。鎖相環能夠將輸出頻率鎖定在輸入頻率的正整數倍。STM32F103
的鎖相環提供了 2 到 16 倍的倍頻系數。假設我們使用高速內部時鐘源 (8 MHz) ,想要使 STM32F103
達到最高主頻 (72 MHz),那麽我們就要要啟用鎖相環,設置為 9 倍倍頻,並將 SYSCLK 時鐘源設置為 PLL。
時鐘總線
STM32F103
中有 4 條時鐘總線:
- AHB 高速總線,時鐘為 HCLK,最大頻率為 72 MHz,時鐘信號提供給存儲器,DMA 及 Cortex 內核,是內核運行的時鐘,也就是主頻,它的大小與 STM32 運算速度,數據存取速度密切相關。
- APB1 低速外設總線,時鐘為 PCLK1,最大頻率為 36 MHz,提供給掛載在APB1總線上的外設, 如
USART2
, - APB2 高速外設總線,時鐘為 PCLK2,最大頻率為 72 MHz,提供給掛載在APB2總線上的外設,如
GPIO
,USART1
。 - FCLK 自由運行時鐘,獨立於內核運行,一般設置為與 HCLK 同頻率,常用於采樣中斷和調試模塊供時。
使用庫函數設置 復位時鐘控制器 (RCC)
復位時鐘控制器是 STM32F103
提供的一組寄存器,負責控制和設置上面提到的時鐘源,鎖相環倍頻,各總線分頻系數以及開關總線上的各類外設。
stm32f1xx_hal
提供了簡潔的接口幫我們設置 RCC 寄存器,足夠滿足日常工作需要。工程上也推薦使用這種方式進行時鐘樹初始化。
下面例子中單片機使用外部 8MHz 晶振,並將所有總線設置為最高頻率:
#![no_main]
#![no_std]
extern crate cortex_m;
extern crate cortex_m_rt as rt;
extern crate panic_halt;
extern crate stm32f103xx_hal as hal;
use hal::prelude::*;
use hal::stm32f103xx;
use rt::entry;
#[entry]
fn main() -> ! {
let dp = stm32f103xx::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
let clocks = rcc
.cfgr
.use_hse(8.mhz()) // 高速外部時鐘源
.sysclk(72.mhz()) // 系統時鐘
.hclk(72.mhz()) // AHB 高速總線
.pclk1(36.mhz()) // APB1 低速外設總線
.pclk2(72.mhz()) // APB2 高速外設總線
.freeze(&mut flash.acr); // 應用時鐘配置
loop {}
}
深入了解 RCC 寄存器
RCC 寄存器的設置比其他一般外設寄存器要更為復雜,因此一般不會手動設置。但是我們可以通過學習它來掌握的庫函數背後的原理。
RCC 時鐘設置一般分為以下幾個步驟:
- 啟用外部晶振 (可選)
- 等待外部晶振穩定
- 設置各總線分頻系數
- 設置 FLASH 等待系數與預讀取
- 啟動鎖相環
- 等待鎖相環鎖定
- 將 SYSCLK 切換到鎖相環信號輸入
PS: 這裏提到的 FLASH 等待系數和預讀取都與 Cortex-M 架構設計有關。Cortex-M 核心采用了三級管道技術,簡單來說就是當一個指令正在處理時,下一個指令已經被解碼的同時第三個指令已經被預讀取進緩存區了,這鐘處理方式能夠大幅提高處理器的運行效率。另外,FLASH 由於原理限制,很難達到與核心相同的高頻率,因此在核心讀取指令的速度超過 FLASH 發送指令速度的時候,核心需要暫時停下來等待。根據手冊,STM32F103
的 FLASH 等待系數應根據核心頻率 (HCLK) 設置:
HCLK | FLASH WAIT STATE
-------------------------------
0 - 24 MHz | 0 wait state
24 - 48 MHz | 1 wait state
48 - 72 MHz | 2 wait state
分頻
事實上,AHB,APB1,APB2 等各總線的頻率都嚴格成正整數倍關系,因為它們的時鐘信號並不是單獨產生的,而是根據特定結構分頻出來的。分頻就是按照預分頻系數降低輸入頻率,預分頻系數都為正整數。STM32F103 中的時鐘樹簡化之後是這樣的:
Example
我們這裏用寄存器代替庫函數初始化時鐘樹,代碼與上節的庫函數完全等效。下面用到的寄存器有 RCC_CR
,RCC_CFGR
,FLASH_ACR
,由於篇幅較長,手冊截圖將放在後面以供參考。
use stm32f103xx;
pub fn rcc_clock_init(rcc: &mut stm32f103xx::RCC, flash: &mut stm32f103xx::FLASH) {
// 啟動外部高速時鐘 (HSE)
rcc.cr.write(|w| w.hseon().enabled());
// 等待外部高速時鐘穩定
while !rcc.cr.read().hserdy().is_ready() {}
// 設置分頻 AHB(HCLK) = SYSCLK, APB1(PCLK1) = SYSCLK / 2, APB2(PCK2) = SYSCLK
rcc.cfgr
.write(|w| w.hpre().no_div().ppre1().div2().ppre2().no_div());
// 設置鎖相環為 9 x HSE
rcc.cfgr
.write(|w| w.pllsrc().external().pllxtpre().no_div().pllmul().mul9());
// 設置 flash : two wait states, 啟用預讀取
flash.acr.write(|w| w.latency().two().prftbe().enabled());
// 啟動鎖相環
rcc.cr.write(|w| w.pllon().enabled());
// 等待鎖相環鎖定
while !rcc.cr.read().pllrdy().is_locked() {}
// 使用鎖相環輸出作為 SYSCLK
rcc.cfgr.write(|w| w.sw().pll());
// 等待 SYSCLK 切換為鎖相環
while !rcc.cfgr.read().sws().is_pll()