本文摘自:
https://blog.csdn.net/xiashiwendao/article/details/122292404
概述
點亮LED表面看起來貌似很簡單,但是如何想要搞清楚其背后牽涉的每一行代碼的具體含義,還是需要花費一些功夫的,而且,只有把LED的背后只是搞清楚了,才算嵌入式開發的基礎入門,
今天我們就來研究一下LED的重頭戲,RCC_Init;什么是RCC?上手冊:

RCC
RCC,Reset and Clock Control,重置以及時鐘控制;STM32手冊使用了兩個章節來對其進行描述,可見它的重要性;對于RCC的初始化也是比較復雜,里面包含了STM32對于時鐘的相關機制,
代碼總覽
void RCC_init(uint16_t PLL)
{
uint32_t temp=0;
*((uint32_t *)RCC_CR) |= 0x00010000;
while(!( *((uint32_t *)RCC_CR) >>17));
*((uint32_t *)RCC_CFGR) = 0X00000400;
PLL -= 2;
*((uint32_t *)RCC_CFGR) |= PLL<<18;
*((uint32_t *)RCC_CFGR) |= 1<<16;
*((uint32_t *)FLASH_ACR)|=0x2;
*((uint32_t *)RCC_CR) |= 0x01000000;
while(!(*((uint32_t *)RCC_CR) >> 25));
*((uint32_t *)RCC_CFGR) |= 0x00000002;
while(temp != 0x02)
{
temp = *((uint32_t *)RCC_CFGR) >> 2;
temp &= 0x03;
}
}
使能HSE
第一行有效代碼,是熟悉的味道,前一節我們說過,或運算一般用于設定指定位(而且還不影響其他位)
*((uint32_t *)RCC_CR) |= 0x00010000;
看到了RCC_CR,首先就是翻手冊,RCC的章節的RCC_CR章節:

然后查看暫存器內部32位的定義:

0x00010000轉換為32bit的二進制:0000 0000 0000 0001 0000 0000 0000 0000;這里有一個小技巧,就是學會分割來看,手冊里面的暫存器的定義一般定義是分為上下兩行的;從16進制來看,從左往右,前面4為是負責上面RCC_CFGR的高位16bit,后面4位是對應(二進制)低位16bit,即下面的16bit;對此次而言,低4位都是0,可以不需要care,直接關注高4位即可,其中高4為只有最后一位是有效設定,1對應的四位二進制是0001,即16位設定為1,查看暫存器定義的16位對應的是HSEON位,即使能HSEON,
HSE和時鐘
什么是HSEON?需要拆開來看:HSE ON,HSE是High Speed External,外部高速時鐘,所以HSEON就是使能外部高速時鐘;
為什么要配置HSE呢?因為在硬體系統中,各個部件的作業、協調,都是基于系統時鐘的,比如我們通常講的CPU的速度快慢,就是指CPU的時鐘頻率,即每秒鐘能夠作業多少個時鐘;

小貼士:
在win10系統中可以看到有兩個頻率,如下圖,分別是1.9GHz以及2.11GHz;其中第一個是Intel提供的標準頻率,其實是CPU名字的一部分,第二個頻率是win10系統自己計算出來的,很多時候和Intel提供的頻率值并不相等;不過兩者相差也不會太大,
系統時鐘有三個來源,分別是HSE(High Spped External),HSI(High Speed Internal)以及PLLCLK,引入了PLL就是因為很多時候,需要基于系統時鐘進行分頻倍頻,比如有的設備(RAM,DMA)需要比系統時鐘快,于是需要通過PLL來進行加倍頻率,還有的低俗的設備需要低于系統頻率作業,也是通過PLL來進行減速,這樣只需要一個系統時鐘就可以同時滿足高速和低俗設備,
所以PLL的時鐘源還是HSE,HSI,不過經過PLL倍頻調節輸出的時鐘稱之為PLLCLK;
再回到我們的代碼里面,這里配置時鐘源就是HSEON(如果需要使用PLL則還需要將PLLx位配置為1);
確認HSE使能生效
完成了時鐘源的配置,下面一步是等待HSEON的配置的生效:
while(!( *((uint32_t *)RCC_CR) >>17));
即第等待第18位(編號17)HSERDY的值變成1,當且僅當配置HSEON生效之后,該位置才會由硬體設定為1,注意在暫存器定義里面HSERDY配置為“r”,這個代表軟體層面是無法改變這個bit的值,只能夠讀取:

然后再查看暫存器定義里面對于這一位的解釋,0就是HSEON位設定并未生效,1就是設定已經生效:

等待的程序是使用位運算里面右移“>>"實作的,右移的運算規則里面是低位直接丟棄,例如11111右移4bit,最后結果就是1;這行代碼的邏輯意義就是RCC_CR右邊17位直接丟棄,即016為拋棄,只是保留了3118位(注意數字方向,是從左向右逐次減小的,和暫存器定義一致),共計15bit;
又因為上一步驟中通過和0x00010000進行與運算將除了HSEON位之外的位置全部置為“0”了,所以之類RCC_CR右移17位之后,包括HSERDY在內的位全部都是0,只有當HSEON生效之后,HSERDY才是1,即*((uint32_t *)RCC_CR) >>17 = 1,于是此時退出while回圈,
配置其他時鐘
CR位配置解決了,下面我們看一下CFGR的配置:
*((uint32_t *)RCC_CFGR) = 0X00000400;
看到這里,我覺得我們可以總結一下寫暫存器的基本套路:
-
查看手冊相應章節,并了解縮寫意義,比如CFGR,全稱Clock Configuration Register,時鐘配置暫存器:

-
查看暫存器定義:

-
查看具體的某一位的定義,比如我們這里設定為0X00000400,4是在后四位,所以重點關注低16bit,即從15 ~ 0bit:0000 0100 0000 0000,發現正好配置的PPRE1,值為100,手冊介紹如下:

用來配置APB1的從HCLK中獲得時鐘的分頻系數,二進制100對應的分頻系數是2;HCLK是AHB總線的時鐘;然后AHB經過AHB-APB橋接將時鐘分配到APB1和APB2總線,APB1的總線對應PCLK1,APB2總線對應的PCLK2;這里配置的就是APB1時鐘頻率的分頻值,為HCLK/2;
這里有一個坑,雖然顯式的為PCLK1賦值了,但是其實隱式的同時將PCLK2(對應APB2),AHB都賦值了;只不過配置的是0;
比如PPRE2配置為000,對應的就是不分頻,或者說分頻數為1:

還有HPRE位,配置也是000,對應的不分頻,或者說頻數為1:

所以CFGR的配置重要的指定了AHB總線以及APB1和APB2總線的分頻/倍頻數;
設定PLL倍頻數
繼續看后面的代碼,PLL是引數,即倍頻數,PLL上面已經介紹了,專門用于接入時鐘源然后后對其進行分頻倍頻再分配給各個總線(下面掛載的設備):
PLL -= 2;
*((uint32_t *)RCC_CFGR) |= PLL<<18;
那么這里為什么要-2呢?我們先存疑,理解了下一行代碼謎底就自然打開了;
PLL值左移18位,左移是位運算,左移+或運算 = 賦值操作,即將PLL值賦給18位起始的后面四位,通過查看暫存器定義可以看到CFGR的18位起始到后面四位是PLLMUL,再查看手冊對于PLLMUL的解釋:PLL的倍頻;不過看一下賦值情況0010(十進制2)對應是4倍頻,0011(十進制3)對應的是5倍頻,以此類推,就是暫存器的只是和真實的倍頻差2,看到這里你就明白了為什么在PLL -=2了:

緊隨其后的,可以知道是要給CFGR的第16bit賦值為1:
*((uint32_t *)RCC_CFGR) |= 1<<16;
查看手冊,是描述PLL時鐘源,1對應是PLL的時鐘原始PREDIV1:
時鐘樹
關于PLLSRC以及PREDIV1他們之間關系在時鐘樹(Clock Tree)上面有比較明確的關系說明,從下圖可以看到作為時鐘源最開始是HSE,然后通過時鐘被分頻后成為了PREDIV1,然后再除以2,獲得了PLLSRC:

所以如果PLLMUL的值設定為1,即PLL的時鐘源采用內部高速時鐘(HSI),最終輸出時鐘信號在HSI/2即可;
如果值設定為1即采用外部高速時鐘(HSE),在經歷了分頻后輸出為PREDIV1(時鐘)信號,還需要再分頻一下,分頻系數為2;
FLASH ARC配置
再看下面的代碼就沒有那么怕了,直接明白,目的就是要給FLASH的ACR暫存器賦值:
*((uint32_t *)FLASH_ACR)|=0x2;
不過這次要找FLash的ACR似乎并不那么順利,很難直接從目錄中查找到,需要全域搜一下ACR,不過還好,沒有讓我們搜索太多時間,在3.3.3章節中找到了:

暫存器的定義如下:

關于16進制轉2進制補0
這里其實有一個補0問題,就是對于32bit,0x2究竟是0x0000 0002,還是0x2000 0000呢?回歸本源,這兩種表達形式哪一個是2呢?毫無疑問,是第一個;
所以這里的0x2,切換到32bit二進制表示就是:0000 0000 0000 0000 0000 0000 0000 0010;可以看到其實有效賦值是LATENCY,位的值是010,查看暫存器定義:

代表了比例,系統時鐘比FLASH存取周期的值,010對應的2,即系統時間是Flash的兩倍,所以同步的時候,需要做兩個周期的延遲用以同步CPU和Flash之間時鐘;
為什么需要做此配置呢?首先程式都是存放在FLASH里面,CPU需要從FLASH里面取出執行指令,所以通信之前需要首先同步時鐘;那么就需要知道Flash的時鐘和系統時鐘(CPU時鐘)之間的差別;又因為我們配置時鐘是72MHz,這里010的時鐘范圍包含了72MHz;
使能PLL
下面是對于RCC_CR暫存器最后的配置:
*((uint32_t *)RCC_CR) |= 0x01000000;
while(!(*((uint32_t *)RCC_CR) >> 25));
還是套路:拆解0x0100 0000,只有高16位有有效值,低16位全零;高16位轉換為2進制之后值為:0000 0001 0000 0000,所以我們看到對應操作的是PLLON位,PLLON位說明如下:

寫入1則代表打開PLL,上面我們做了關于PLL的倍頻的配置,PLL時鐘源的配置,都是需要在PLL使能之后才會生效,在嵌入式開發中,所有的配置都是基于設備/ 組件使能的前提下才會配置生效;
至于while陳述句和上面配置HSE使能的陳述句的等待READY的功效是一致的;可以通過查看暫存器定義,25位是PLLRDY,while回圈就是等待這一位置為1,是否需要擔心其他位有1從而影響回圈判斷?大可不必,因為31~26都是Reserved,必然為0,唯一的有效位就是PLLRDY位,
可以看到在RCC初始化的代碼中,每次配置一個使能都是需要通過while回圈來確認配置成功了;
配置PLLCLK為系統時鐘
最后一部分代碼,讓RCC_CFGR與0x00000002做或運算:
*((uint32_t *)RCC_CFGR) |= 0x00000002;
while(temp != 0x02)
{
temp = *((uint32_t *)RCC_CFGR) >> 2;
temp &= 0x03;
}
有效為發生在低16位,0000 0000 0000 0010,有效配置位是最后兩位,即SW位(Switch,切換系統時鐘之意),解釋如下,可以看到10代表使用PLL的輸出(PLLCLK)作為系統時鐘:

之后的while陳述句則是輪訓查看使能配置是否生效;注意這里的使能生效并沒有采用上面單句while回圈的方式,而是采用賦值方式來進行的,就是因為會有其他位配置會影響判斷;
首先看一下SWS位說明:

賦值判斷的方式也是非常巧妙,首先是RCC_CFGR右移(>>)2位,擠掉了SW位,現在SWS(Switch Status)位在最后面,然后將其和0x03進行與運算,與運算的目的就是:0位清零,1位保留原值(或運算目的:0位維持原值不變,1位用于置1),
所以,和temp進行與運算0x03的其實是30bit數(最后兩位已經通過右移2位擠掉了):0000 0000 0000 0000 0000 0000 0000 11,所以就是要取用SWS的位的值,當回傳值為“10”的時候,即0x02,代表PLL已經被設定為系統時鐘,說明RCC_CFGR的配置已經生效,
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/402394.html
標籤:嵌入式
