您好,登錄后才能下訂單哦!
匯編實現時鐘設置代碼理解
下面的筆記是我在看《朱老師物聯網大講堂》(www.zhulaoshi.org)之后所做的筆記,只是大概根據自己看了視頻與朱老師上課做的筆記而有的理解記錄下來。
寫了
有代碼的,要把代碼給理解完整。
朱老師的隨堂程序是:clock.s
// 時鐘控制器基地址
#define ELFIN_CLOCK_POWER_BASE0xE0100000
// 時鐘相關的寄存器相對時鐘控制器基地址的偏移值
#define APLL_LOCK_OFFSET0x00
#define MPLL_LOCK_OFFSET0x08
#define APLL_CON0_OFFSET0x100
#define APLL_CON1_OFFSET0x104
#define MPLL_CON_OFFSET0x108
#define CLK_SRC0_OFFSET0x200
#define CLK_SRC1_OFFSET0x204
#define CLK_SRC2_OFFSET0x208
#define CLK_SRC3_OFFSET0x20c
#define CLK_SRC4_OFFSET0x210
#define CLK_SRC5_OFFSET0x214
#define CLK_SRC6_OFFSET0x218
#define CLK_SRC_MASK0_OFFSET 0x280
#define CLK_SRC_MASK1_OFFSET 0x284
#define CLK_DIV0_OFFSET0x300
#define CLK_DIV1_OFFSET0x304
#define CLK_DIV2_OFFSET0x308
#define CLK_DIV3_OFFSET0x30c
#define CLK_DIV4_OFFSET0x310
#define CLK_DIV5_OFFSET0x314
#define CLK_DIV6_OFFSET0x318
#define CLK_DIV7_OFFSET0x31c
#define CLK_DIV0_MASK0x7fffffff
// 這些M、P、S的配置值都是查數據手冊中典型時鐘配置值的推薦配置得來的。
// 這些配置值是三星推薦的,因此工作最穩定。如果是自己隨便瞎拼湊出來的那就要
// 經過嚴格測試,才能保證一定對。
#define APLL_MDIV 0x7d// 125
#define APLL_PDIV 0x3
#define APLL_SDIV 0x1
#define MPLL_MDIV0x29b// 667
#define MPLL_PDIV0xc
#define MPLL_SDIV0x1
#define set_pll(mdiv, pdiv, sdiv)(1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VALset_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VALset_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)
.global clock_init
clock_init:
ldrr0, =ELFIN_CLOCK_POWER_BASE
// 1 設置各種時鐘開關,暫時不使用PLL
ldrr1, =0x0
// 芯片手冊P378 寄存器CLK_SRC:Select clock source 0 (Main)
strr1, [r0, #CLK_SRC0_OFFSET]
// 2 設置鎖定時間,使用默認值即可
// 設置PLL后,時鐘從Fin提升到目標頻率時,需要一定的時間,即鎖定時間
ldrr1,=0x0000FFFF
strr1,[r0, #APLL_LOCK_OFFSET]
str r1, [r0, #MPLL_LOCK_OFFSET]
// 3 設置分頻
// 清bit[0~31]
ldr r1, [r0, #CLK_DIV0_OFFSET]
ldrr2, =CLK_DIV0_MASK
bicr1, r1, r2
ldrr2, =0x14131440
orrr1, r1, r2
strr1, [r0, #CLK_DIV0_OFFSET]
// 4 設置PLL
// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHz
ldrr1, = APLL_VAL
strr1, [r0, #APLL_CON0_OFFSET]
// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHz
ldrr1, = MPLL_VAL
strr1, [r0, #MPLL_CON_OFFSET]
// 5 設置各種時鐘開關,使用PLL
ldrr1, [r0, #CLK_SRC0_OFFSET]
ldrr2, =0x10001111
orrr1, r1, r2
strr1, [r0, #CLK_SRC0_OFFSET]
movpc, lr
(下面就是對這一個代碼的理解)
對clock.s這一個匯編代碼的理解
這一段代碼的第一部分:設置各種時鐘開關,暫時不用PLL
在前面的課程里面有對于時鐘框圖的解釋,像在文檔的3.2的Figure3.3的左上角的部分有畫著這么樣的東西,初始時鐘從左邊過來,然后有兩條路走,一條是通過PLL,另一條是直接連到一個MUX開關,所以我們設置初始時鐘而且不用PLL的時候我們做的就是選MUX開關來達到只設置時鐘不通過(繞過)PLL。這樣子的話就要去設置寄存器控制選通位了。
代碼:
ldrr0, =ELFIN_CLOCK_POWER_BASE
ldrr1, =0x0
strr1, [r0, #CLK_SRC0_OFFSET]// 芯片手冊P378 寄存器CLK_SRC:Select clock source 0 (Main)
解釋說到上面的代碼:
上往下數第一句
這一句是把ELFIN_CLOCK_POWER_BASE這個值賦給r0寄存器,為什么要賦這一個值呢?
可以從這一段匯編函數開始的更前面看起,這一個“ELFIN_CLOCK_POWER_BASE”是一個宏定義就是相當把ELFIN_CLOCK_POWER_BASE與0xE0100000等值起來了(就是用到ELFIN_CLOCK_POWER_BASE的時候就是相當于在用0xE0100000這個值)。
為什么要把這個值在前面給宏定義出來呢?
從數據手冊的3.7 REGISTER DESCRIPTION看到關于這個時鐘的寄存器地址都是從0xE0100000這個地址開始,這個就是基地址,其他的地址都是相對著這一個地址偏移多少的量,通過一個計算,就是用基址地址加上偏移的量就可以找到我們想要設置的寄存器了,
如何通過上述所說的來給目標寄存器寫東西呢?
代碼的第三句有一個CLK_POWER_BASE,這個東西就是偏移量啦,但這是一堆字母沒看到量啊,其實就是想上面的ELFIN_CLOCK_POWER_BASE一樣,在前面已經給宏定義一個值了。這一個值是0x200,因為我們要寫的寄存器是距離0xE0100000有0x200,我們就依據這個東西來找寄存器,并且寫入東西。寫入的值是0x0.就是這一個寄存器全部寫零。 為什么寫入0xE0100200這一個寄存器呢?
這個地址所對應的寄存器的名稱是:CLK_SRC0,首先去看一下這個寄存器是干嘛的,在手冊的378頁有這個寄存的介紹與如何設置(3.7.3.1 Clock Source Control Registers)。為什么要設置這個寄存器,首先就要看我們剛開始的目的是什么,我們想干嘛。我們是想“設置各種時鐘開關,暫時不用PLL”,那么我們怎么去實現呢,那就要看一下,哪一個寄存器能夠滿足我們的要求了。看一下這一寄存器的功能是,數據手冊上面寫的什么Clock Source Control,就是時鐘源控制的寄存器,我想的是這一個寄存器就是控制時鐘源的來源,從圖上面看的是設置晶振與時鐘發生器產生的時鐘是否是要經過PLL,這樣子就可以滿足我們的目的要求了,我們的目的就是不要PLL,晶振與時鐘產生多少的頻率就向右傳多 少頻率,那么我們就是要使它不使用經過PLL的頻率好。
為什么傳入0x0:
從手冊上面看到這一個寄存器的初始值(默認值都是選擇0的)就是都是剛開始默認不用PLL倍頻過后的程序。這個的話與上面的目標相符。這里重點看這幾個:
1、VPLL_SEL
2、EPLL_SEL
3、MPLL_SEL
4、APLL_SEL
這幾個控制位都是賦值零的。就是都選擇FINOUT,這么選,貌似是因為s5pv210這個板子上面只焊了一個時鐘發生器。這個可供其他使用了,這樣子的話都選擇了0,那么就是都不經過PLL的了,這樣的話就是不倍頻了。
代碼最后一段:
這個是按照變址尋址來做的,就是把r1的值寫入到r0+CLK_SRC0_OFFSET這一個地址里面去(因為是統一編址,這個是個寄存器地址)。
到此,設置各種時鐘開關,暫時不用PLL的問題解決了。
第二步:設置鎖定時間,使用默認值即可。
為什么要設置鎖定時間,我的認為,因為鎖相環(PLL)想要初始時鐘(24MHz)在瞬間完成倍頻到1G是不可能的,它需要低頻率在PLL里面回環轉,一次次頻率的升高來達到1G這樣子的話就是是需要時間的,所以等一會兒,好了就可以了,在文檔(3.7.2 PLL CONTROL REGISTERS)上面是說當輸入的頻率頻率改變或者分頻的值改變是得鎖定時間。鎖定時間的長短基于PLL的源時鐘,用PLL_LOCK這一個寄存器去設置,
ldrr1,=0x0000FFFF
strr1,[r0, #APLL_LOCK_OFFSET]
str r1, [r0, #MPLL_LOCK_OFFSET]
為什么寫入0x0000FFFF
這個寄存器只有低十六位可以用,最大值就是0x0000FFFF,值設置越大,鎖定的時間就越就久。默認值是0x00000FFF,設置這一個最大值也沒事,也就是隔久一點而已。時間超過剛好值的話那時候已經是好了。
有兩個寄存器,就是鎖好兩個APLL和MPLL兩個倍頻器。就是這樣。
第三步:設置分頻
設置分頻系統,由它決定給左邊的分多少倍從而得到右邊的頻率,說得清楚一些就是左邊的頻率除以一個分頻器可以接受的值,然后得到的值輸出到右邊。我們要做的就是設置分頻系數,說清楚些就是設置那一個除數。設置的寄存器是CLK_DIV
如何設置呢?
視頻上面用說的方式給清楚地說出了如何去設置,在代碼中那個值是什么意思了。
直接用代碼中的設置值來分析:
ldr r2, =0x14131440
給r2所指代的寄存器寫入0x14131440,從文檔的3.7.4.1 Clock Divider Control Register這一部分看。所寫入的數據的第一個1,是30:28位的,在關于這一個寄存器的設置描述欄中寫的是公式PCLK_PSYS = HCLK_PSYS / (PCLK_PSYS_RATIO + 1),我們寫的1,那就相當于給PCLK_PSYS_RATIO的值賦了一個1,這樣子的話下面的除數就是2.因為1+1等于2,PCLK_PSYS和HCLK_PSYS要從文檔上面3.4 CLOCK GENERATION的那個3.3圖中找,可以找到在右下角的PSYS域中的HCLK_PSYS和PCLK_PSYS,描述欄的公式意思是PCLK_PSYS的時鐘頻率是通過HCLK_PSYS的頻率除以2得到的。
代碼的理解:
ldr r1, [r0, #CLK_DIV0_OFFSET]
ldrr2, =CLK_DIV0_MASK
bicr1, r1, r2
ldrr2, =0x14131440
orrr1, r1, r2
strr1, [r0, #CLK_DIV0_OFFSET]
//忽然發現我理解不了,方法是往哪個寄存器寫東西?為什么往那里寫,為什么寫//0x14131440?(漫長)
第四步:設置PLL
這個地方也是要學會看文檔。(可見有會看文檔的功力也是很重要的)
講的是APLL和MPLL,這兩個PLL我們可以在文檔的361頁可以看到圖。
要對它做什么?
對他設置合適的參數,然后lock,然后等待輸出。
怎么設置參數:
找文檔,現在設置的是APLL,找啊找,在文檔的372,上面有個APLL_CON0,看這一頁的寄存器位的介紹,首先要看一個公式:
FOUT = MDIV X FIN / (PDIV × 2^(SDIV-1))
這個公式是與寄存位的介紹配合起來看的,想知道在公式上面的變量怎么設置看寄存器位的介紹表中的MDIV[25:16]、PDIV[13:8]、SDIV[2:0]這幾位。這幾個很重要。
公式中的MDIV值就是設置寄存器中的25:26位的值,FIN的值是左邊的頻率進PLL的值,比如在文檔上面“3.4 CLOCK GENERATION”部分的那個圖中看左上角的APLL地方的左邊寫著有PLL這就是FIN。FOUT同理可以理解。PDIV就是我們設置的PDIV(所在寄存器的位是13:8)的值,SDIV是就是設置的SDIV所在寄存器的位是(2:0)的值。
例:在文檔中給定的初始值是:MDIV是0xc8(對應的10進制是200),PDIV是0x3(對應的10進制是3),SDIV是0x1(對應的10進制是1),對于APLL的話FIN是24MHz把這些值代入上式得FOUT=24*200/3*2^0=1600,這是文檔上面一個想不到的東西,因為我們要設置的是1GHz,但是按照這一個初值來設置的話就變成了1.6GHz了。在程序中的數值與上述的數值不一樣的是MDIV的值,這個值應該設置成0x7d(對應的10進制是125)。關于這些經典值可以到文檔的357頁的3.3.1RECOMMENDED PLL PMS VALUE FOR APLL可以得到三星給的推薦值。
但是在程序中計算FOUT的值有點讓人眼前一新(自己沒有碰到過的,在朱老師的C高級里面有講位運算):
#define set_pll(mdiv, pdiv, sdiv)(1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VALset_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VALset_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)
下面要講的是MPLL:
MPLL的設置方法與APLL是一樣的,只是公式不一樣而已。
第五步:設置各種時鐘開關,使用PLL
從代碼來看:
ldrr1, [r0, #CLK_SRC0_OFFSET]
ldrr2, =0x10001111
orrr1, r1, r2
strr1, [r0, #CLK_SRC0_OFFSET]
這里要理解的是0x10001111的問題,在CLK_SRC0寄存器中寫入這一個值,為什么要寫入這一個值。
這里又要配合著文檔上面的《3.7.3.1 Clock Source Control Registers》這一部分來查看開了使用了哪一個東西,之后去配合《3.4 CLOCK GENERATION》這一部分的圖后,結合之前學過的xPLL與DIV的使用原理,可以算出在CLK_SRC0之后,頻率經過什么樣的路徑,最后到達使用的部件的頻率是多少。
剛剛才入ARM裸機這水,還不深,其中肯定會有疏忽和錯誤,如果看了這篇筆記發現了錯誤,請指出,謝謝。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。