瞭解S3C2410/S3C2440的時鐘體繫結構 掌握通過設置MPLL改變系統時鐘的方法 掌握在不同的頻率下設置存儲控制器的方法 掌握PWM定時器的用法 瞭解WATCHDOG定時器的用法 ...
本章目標 瞭解S3C2410/S3C2440的時鐘體繫結構 掌握通過設置MPLL改變系統時鐘的方法 掌握在不同的頻率下設置存儲控制器的方法 掌握PWM定時器的用法 瞭解WATCHDOG定時器的用法 10.1 時鐘體系及各類時鐘部件 10.1.1 S3C2410/S3C2440時鐘系統 S3C2410/S3C2440的時鐘控制邏輯既可以外接晶振,然後通過內部電路產生時鐘源;也 可以直接使用外部提供的時鐘源,它們通過引腳的設置來選擇。時鐘控制邏輯給整個晶元提 供3種時鐘:FCLK用於CPU核;HCLK用於AHB匯流排上的設備,比如CPU核、存儲控制器、 中斷控制器、LCD控制器、DMA和USB主機模塊等;PCLK用於APB匯流排上的設備,比如 WATCHDOG、IIS、I2C、PWM定時器、MMC介面、ADC、UART、GPIO、RTC和SPI。 為了降低電磁干擾、降低板間佈線的要求,S3C2410/S3C2440外接的晶振頻率通常很低, 本開發板上為12MHz,需要通過時鐘控制邏輯的PLL提高系統時鐘。 S3C2410/S3C2440有兩個PLL:MPLL和UPLL。UPLL專用於USB設備,MPLL用於設置 FCLK、HCLK、PCLK。它們的設置相似,本書以MPLL為例。 上電時,PLL沒被啟動,FCLK即等於外部輸入時鐘,稱為Fin。 若要提高系統時鐘,需要開啟PLL。 PLL設置過程如下所示,請參考圖10.1,跟隨FCLK的圖像瞭解啟動過程。 (1)上電幾毫秒後,晶振輸出穩定,FCLK = Fin(晶振頻率),nRESET信號恢復高電平後, CPU開始執行命令。 (2)可以在程式開頭啟動MPLL,設置MPLL的幾個寄存器後,需要等待一段時間(Lock Time), MPLL輸出才穩定。在這段時間(Lock Time)內,FCLK停振,CPU停止工作。Lock Time的長短由 寄存器LOCKTIME設定。 (3)Lock Time之後,MPLL輸出正常,CPU工作在新的FCLK之下。 FCLK、HCLK和PCLK的比例是可以改變的,設置它們三者的比例,啟動MPLL只需要設置3個 寄存器(對於S3C2440的一些時鐘比例,還需要額外設置一個寄存器)。 【1】LOCKTIME寄存器(LOCK TIME COUNT):用於設置“Lock Time”的長度。
前面說過,MPLL啟動後需要等待一段時間,使得其輸出穩定。S3C2410中,位[23:12]用於UPLL, 位[11:0]用於MPLL。S3C2440中,位[31:16]用於UPLL,位[15:0]用於MPLL。一般而言,使用它的 預設值即可,S3C2410中預設值為0x00FF FFFF,S3C2440中預設值為0xFFFF FFFF。 【2】MPLLCON寄存器(Main PLL Control):用於設置FCLK與Fin的倍數。 位[19:12]的值稱為MDIV,位[9:4]的值稱為PDIV,位[1:0]的值稱為SDIV。FCLK與Fin的關係 有如下計算公式: ① 對於S3C2410: MPLL(FCLK) = ( m * Fin) / (p * 2^s) ② 對於S3C2440: MPLL(FCLK) = (2 * m * Fin) / (p * 2^s) 其中:m = MDIV + 8,p = PDIV + 2,s = SDIV。 當設置MPLLCON之後——相當於圖10.1中的“首先使用軟體設置PLL”,Lock Time就被自動插入。 Lock Time之後,MPLL輸出穩定,CPU工作在新的FCLK下。 【3】CLKDIVN寄存器(CLOCK DIVIDER CONTROL):用於設置FCLK、HCLK、PCLK三者的比例。 對於S3C2410、S3C2440,這個寄存器表現稍有不同,請參考表10.1和圖10.2. 對於S3C2440的一些時鐘比例,還需要額外的設置一個寄存器CAMDIVN。圖10.2中, HDIVN為CLKDIVN寄存器為位[2:1],PDIVN為位[0];HCLK4_HALF、HCLK3_HALF分 別為CAMDIVN寄存器的位[9]、[8]。各種時鐘對比對應的寄存器設置如圖10.2所示。 對於S3C2410,HDIVN是CLKDIVN寄存器的位[1]; 對於S3C2440,HDIVN是CLKDIVN寄存器的位[2:1],如果HDIVN非0,CPU的匯流排模式 應該從“fast bus mode”變為“asynchronous bus mode”,這可以通過如下指令來完成:
# MMU_SetAsyncBusMode mrc p15, 0, r0, c1, c0, 0 orr r0, r0, #R1_nF:OR:RL_iA mcr p15, 0, r0, c1, c0, 0其中的“R1_nF:OR:R1_iA”等於0xC000 0000。如果HDIVN非0時,而CPU的匯流排模式仍是 “fast bus mode”,則CPU的工作頻率將自動變為HCLK,而不再是FCLK。 10.1.2 PWM定時器 S3C2410/S3C2440的定時器部件完全一樣,共有5個16位定時器。其中定時器0、1、2、3 有PWM功能;定時器4沒有輸出引腳。 定時器部件的時鐘源為PCLK,首先通過兩個8位的預分頻器降低頻率:定時器0、1共用第 一個預分頻器,定時器2、3、4共用第二個預分頻器。預分頻器的輸出將進入第二部分分頻器, 它們輸出5種頻率的時鐘:2分頻、4分頻、8分頻、16分頻或者外部時鐘TCLK0/TCLK1。每個 定時器的工作時鐘可以從這5種頻率中選擇。 這兩個預分頻都可以通過TCFG0寄存器來設置,每個定時器工作在哪種頻率下也可以通過 TCFG1寄存器來選擇。如圖10.3所示,形象地說明定時器的結構。 圖10.3 定時器結構圖 上面只是確定了定時器的工作頻率,至於定時器如何工作還得瞭解其內部結構,如圖 10.4所示。 定時器內部控制邏輯的工作流程如下: (1)程式初始,設定TCMPBn、TCNTBn這兩個寄存器,它們表示定時器n的比較值、 初始計數值。
(2)隨之設置TCON寄存器啟動定時器n,這時,TCMPBn、TCNTBn的值將被裝入其 內部寄存器TCMPn、TCNTn中。在定時器n的工作頻率下,TCNTn開始減1計數,其值可 以通過讀取TCNTOn寄存器得知。 (3)當TCNTn的值等於TCMPn的值時,定時器n的輸出管腳TOUTn反轉;TCNTn繼續 減1計數。 (4)當TCNTn的值達到0時,其輸出管腳TOUTn再次反轉,並觸發定時器n的中斷(如果 中斷使能了的話)。 (5)當TCNTn的值達到0時,如果TCON寄存器中將定時器n設為“自動載入”,則TCMPB0 和TCNTB0寄存器的值將被自動裝入TCMP0和TCNT0寄存器中,下一個計數流程開始。 定時器n的輸出管腳TOUTn初始狀態為高電平,以後在TCNTn的值等於TCMPn的值、 TCNTn的值時反轉。也可以通過TCON寄存器設置其初始電平,這樣TOUTn的輸出就完全 反相了。通過設置TCMPBn、TCNTBn的值可以設置管腳TOUTn輸出信號的占空比,這就 是所謂的PWM,所以這些定時器又被稱為PWM定時器。 下麵講解定時器時寄存器的使用方法。 (1)TCFG0寄存器(TIMER CONFIGURATION) 位[7:0]、位[15:8]分別被用於控制預分頻器0、1,它們的值為0~255。經過預分頻器出來 的時鐘頻率為:PCLK/{prescaler value + 1}。 (2)TCFG1寄存器 經過預分頻器得到的時鐘將被2、4、8、16分頻,除了這4種頻率外,額外的,定時器0、1 還可以工作在外接的TCLK0時鐘下,定時器2、3、4還可以工作在外接的TCLK1時鐘下。 通過TCFG1寄存器來設置這5個定時器,分別工作於這5個頻率中哪一個下,如表10.2所示。 這樣,定時器n的工作頻率或者外接的TCLK0或TCLK1可以通過這個公式計算: 定時器工作頻率 = PCLK / {prescaler value +1} / {divider value} {prescaler value} = 0~255 {divider value} = 2、4、8、16 (3)TCNTBn/TCMPBn寄存器(COUNT BUFFER REGISTER & COMPARE BUFFER REGISTER)。
n為0~4,這兩個寄存器都只用到位[15:0],TCNTBn中保存定時器的初始值,TCMPBn 中保存比較值。它們的值在啟動定時器時,被傳到定時器內部寄存器TCNTn、TCMPn中。 沒有TCMPB4,因為定時器4沒有輸出管腳。 (4)TCNTOn寄存器(COUNT OBSERVATION) n為0~4,定時器n被啟動後,內部寄存器TCNTn在其工作時鐘下不斷減1計數,可以通過 讀取TCNTOn寄存器得知其值。 (5)TCON寄存器(TIMER CONTROL) 它有以下4個作用: ① 第一次啟動定時器時,手動將TCNTBn/TCMPBn寄存器中的數據裝入內部寄存器 TCNTn、TCMPn中。 ② 啟動、停止定時器。 ③決定在定時器計數到達0時,是否自動將TCNTBn/TCMPBn寄存器的值裝入內部 寄存器TCNTn、TCMPn中。 ④ 決定定時器的管腳TOUTn的輸出電平是否反轉、 TCON寄存器位[3:0]、位[11:8]、位[15:12]、位[19:16]、位[22:20]分別用於定時器0~4。 除了定時器因為沒有輸出引腳在沒有“輸出反轉”位外,其他位的功能相似。表10.3以定時器 0為例說明這些位的作用。 在第一次使用定時器時,需要設置“手動更新”位為1,以使TCNTBn/TCMPBn寄存器的 值裝入內部寄存器TCNTn、TCMPn中。下一次如果還要設置這一位,需要先將它清0。 定時器還有其他功能,比如DMA、Dead zone等,需要瞭解的讀者清參考數據手冊。 寄存器中涉及它們的部分這裡就省略了。 10.1.3 WATCHDOG定時器 WATCHDOG定時器可以像一般16位定時器一樣用於產生周期性中斷,也可以用於發 出複位信號以重啟失常的系統。它與PWM定時器的結構類似,如圖10.5所示。 同樣,WATCHDOG定時器的8位分頻器將PCLK分頻後,被再次分頻得到4種頻率: 16、32、64、128分頻,WATCHDOG定時器可以選擇工作在哪種頻率之下。WTCNT 寄存器按照其工作頻率減1計數,當達到0時,可以產生中斷信號,可以輸出複位信號。 在第一次使用WATCHDOG定時器時,需要向WTCNT寄存器中寫入初始計數值,以後 在計數值達到0時,自動從WATDAT寄存器中寄存器中裝入,重新開始下一個計數周期。 使用WATCHDOG定時器的“WATCHDOG功能”時,在正常的程式中,必須不斷重新 設置WTCNT寄存器使得它不為0,這樣可以保證系統不被重啟,這稱為喂狗。 WATCHDOG定時器所涉及的寄存器如下: (1)WTCON寄存器(WATCHDOG TIMER CONTROL) 用於設置預分頻繫數,選擇工作頻率,決定是否使用中斷、是否啟用WATDOG功能(即 是否輸出複位信號),各位的作用如表10.4所示。 與PWM定時器相似,WATDOG定時器的工作頻率通過這個公式計算: WATDOG定時器工作頻率 = PCLK / {prescaler value + 1} / {divider value} {prescaler value} = 0~255;{divider value} = 16、32、64、128 (2)WTDAT寄存器(WATCHDOG TIMER DATA)。 用於決定WATCHDOG定時器的超時周期,在定時器啟動後,當計數達到0時,WTDAT 寄存器的值會自動傳入WTCNT寄存器。不過,第一次啟動WATDOG定時器時,WTDAT 寄存器的值會自動傳入WTCNT寄存器。 (3)WTCNT寄存器(WATCHDOG TIMER COUNT)。 在啟動WATDOG定時器前,必須往這個寄存器寫入初始值。啟動定時器後,它減1計數, 當計數值到達0時: 如果中斷被使能的話發出中斷; 如果WATCHDOG功能被使能的話,發出複位信號,裝載WTDAT寄存器的值並重新計數。 10.2 MPLL和定時器操作實例 10.2.1 程式設計 本實例講解MPLL、定時器的使用。首先啟動MPLL提高系統時鐘,初始化存儲控制器使 SDRAM工作在新的HCLK下,然後將定時器0設為0.5s產生一次中斷,在中斷程式中改變 LED的狀態。 10.2.2 代碼詳解 源碼在/work/hardware/timer目錄下。 本實驗的重點在4點: ① 設置/啟動 MPLL; ② 根據HCLK設置存儲控制器; ③ 初始化定時器0; ④ 初始化定時器中斷。 相關函數在init.c中。 1.設置/啟動 MPLL clock_init函數用於設置MPLL,本開發板的輸入時鐘頻率Fin為12MHz,將FCLK、HCLK、 PCLK分別設為200MHz、100MHz和50MHz,代碼如下:
1 行號 2 23行 #define s3c2410_MPLL_200MHz ((0x5c << 12) | (0x04 << 4) | (0x00)) /*MDIV = 0x5c, PDIV = 0x04, SDIV = 0*/ 3 24行 #define s3c2440_MPLL_200MHz ((0x5c << 12) | (0x01 << 4) | (0x02)) 4 25行 /* 5 26行 *對於MPLLCON寄存器,[19:12]為MDIV、[1:0]為SDIV 6 27行 *有如下公式: 7 28行 * s3c2410:MPLL(FCLK) = (m * Fin)/(p * 2^s) 8 29行 * s3c2440:MPLL(FCLK) = (2*m*Fin)/(p * 2^s) 9 30行 * 其中:m = MDIV + 8,p = PDIV +2, s = SDIV 10 31行 *對於本開發板,Fin = 12MHz 11 32行 *設置CLKDIVN,令分頻比為:FCLK:HCLK:PCLK = 1:2:4 12 33行 *FCLK = 200MHz,HCLK = 100MHz,PCLK = 50MHz 13 34行 */ 14 35行 void clock_init(void) 15 36行 { 16 37行 //LOCKTIME = 0x00ff ffff //使用預設值即可 17 38行 CLKDIVN = 0x03; //FCLK:HCLK:PCLK = 1:2:4,HDIVN = 1, PDIVN = 1 18 39行 19 40行 /*如果HDIVN非0,CPU的匯流排模式應該從“fast bus mode”變為“asynchronous bus mode”*/ 20 41行 __asm__( 21 42行 "mrc p15, 0, r1, c1, c0, 0\n" //讀出控制寄存器 22 43行 "orr r1, r1, #0xc0000000\n" //設置為“asynchronous bus mode” 23 44行 "mcr p15, 0, r1, c1, c0, 0\n" //寫入控制寄存器 24 45行 ) 25 46行 26 47行 /*判斷是s3c2410還是s3c2440*/ 27 48行 if((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002)) 28 49行 { 29 50行 MPLLCON = S3C2410_MPLL_200MHz; /*現在,FCLK = 200MHz,HCLK = 100MHz,PCLK = 50MHz*/ 30 51行 } 31 52行 else 32 53行 { 33 54行 MPLLCON = S3C2440_MPLL_200MHz; 34 55行 } 35 56行 } 36 57行init.c->clock_init.c() 如果處理器是S3C2410,使用第50行設置MPLL寄存器,令MDIV = 0x5c,PDIV = 0x04, SDIV = 0,所以:
MPLL(FCLK) = (m * Fin)/(p * 2^s) = (0x5c + 8) * 12MHz/((0x04 + 2)*2^0) = 200MHz HCLK = FCLK/2 = 100MHz PCLK = FCLK/4 = 50MHz如果處理器是S3C2440,使用第54行設置MPLL寄存器,使用第29行的公式可以計算 出MPLL = 200MHz,所以FCLK、HCLK、PCLK分別為200MHz、100MHz和50MHz。 2.設置存儲控制器 memsetup函數被用來設置存儲控制器,代碼如下:
1 行號 2 58行/* 3 59行*設置存儲控制器以使用SDRAM 4 60行*/ 5 61行void memsetup(void) 6 62行{ 7 63行 volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE; 8 64行 9 65行 /*這個函數之所以這樣賦值,而不是像前面的實驗(比如mmu實驗)那樣將配置值 10 66行 *寫在數組中,是因為要生成位置無關代碼,使得這個函數可以被覆制到 11 67行 *SDRAM之前就可以在Steppingstone中運行 12 68行 */ 13 69行 /*存儲控制器13個寄存器的值*/ 14 70行 p[0] = 0x22011110; //BWSCON 15 71行 P[1] = 0x00000700; //BANKCON0 16 72行 p[2] = 0x00000700; //BANKCON1 17 73行 p[3] = 0x00000700; //BANKCON2 18 74行 p[4] = 0x00000700; //BANKCON3 19 75行 p[5] = 0x00000700; //BANKCON4 20 76行 p[6] = 0x00000700; //BANKCON5 21 77行 p[7] = 0x00018005; //BANKCON6 22 78行 p[8] = 0x00018005; //BANKCON7 23 79行 24 80行 /*REFRESH, 25 81行 *HCLK = 12MHz :0x008c 07a3 26 82行 *HCLK = 100MHz:0x008c 04f4 27 83行 */ 28 84行 p[9] = 0x008c04f4; 29 85行 p[10] = 0x000000b1; //BANKSIZE 30 86行 p[11] = 0x00000030; //MRSRB6 31 87行 p[12] = 0x00000030; //MRSRB7 32 88行} 33 89行init.c->memsetup() 除REFRESH寄存器外,其他寄存器的值與第6章的實驗程式一樣。現在HCLK等於 100MHz,REFRESH寄存器的值需要重新計算。參考第6章的公式可以計算: R_CNT = 2^11 + 1 - 100MHz * 7.8125uS = 0x04F4, 所以,REFRESH = 0x008c0000 + R_CNT = 0x008c04f4。 在連接腳本timer.lds中,全部代碼的起始運行地址都被設為0x3000 0000,但是在執行 memsetup函數時,代碼還在內部SRAM(steppingston)中,為了能在此處運行這個函數, 它應該是位置無關的。 3.初始化定時器0 timer0_init函數用於初始化定時器0,根據相關寄存器的格式並參考代碼中的註釋就可 以理解這個函數,代碼如下:
1 行號 2 124行/* 3 125行*Timer input clock Frequency = PCLK / (prescaler value + 1) / (divider value) 4 126行*(prescaler value) = 0~255 5 127行*(divider value) = 2、4、8、16 6 128行*本實驗的Timer0的時鐘頻率 = 100MHz/(99 + 1)/(16) = 62500Hz 7 129行*設置Timer0 0.5s觸發一次中斷 8 130行*/ 9 131行void timer0_init(void) 10 132行{ 11 133行 TCFG0 = 99; //預分頻器 0 = 99 12 134行 TCFG1 = 0x03; //選擇16分頻 13 135行 TCNTB0 = 31250; //0.5s觸發一次中斷 14 136行 TCON |= (1 << 1); //手動更新 15 137行 TCON = 0x09; //自動載入,清除“手動更新”位,啟動定時器0 16 138行} 17 139行init.c->timer0_init() 4.定時器中斷 head.S中調用timer0_init函數之後,定時器開始工作;調用init_irq函數使能定時器0 中斷、設置CPSR寄存器開啟IRQ中斷後,每當定時器0計數達到0時,就會觸發中斷。 init_irq函數很簡單,在init.c中,代碼如下:
行號 140行/* 141行*定時器0中斷使能 142行*/ 143行void init_irq(void) 144行{ 145行 //定時器0中斷使能 146行 INTMSK &= (~(1 << 10)); 147行}init.c->init_irq() 發生定時器中斷時,CPU將調用其中斷服務程式Timer0_Handler,它在interrupt.c中:
1 行號 2 03行void Timer0_Handler(void) 3 04行{ 4 05行 /* 5 06行 *每次中斷令3個LED改變狀態 6 07行 */ 7 08行 if(INTOFFSET == 10) 8 09行 { 9 10行 GPFDAT = ~(GPFDAT & (0x7 << 4)); 10 11行 } 11 12行 //清除中斷 12 13行 SRCPND = 1 << INTOFFSET; 13 14行 INTPND = INTPND; 14 15行}interrupt.c 定時器0的中斷使用SRCPND、INTPND寄存器中的位10來表示。中斷服務程式 Timer0_Handler先判斷是否定時器0的中斷,若是則反轉3個LED的狀態。 10.2.3 實例測試 在timer目錄下執行make命令生成timer.bin可執行程式,燒入NAND Flash中執行, 即可看到3個LED每1s閃爍一次。 將head.S中對clock_init函數的調用去掉,不啟動MPLL;並隨之將init.c中的 memsetup函數的REFRESH寄存器改為12MHz對應的0x008c 07a3.重新編譯、燒寫, 可以看到差不多8sLED閃爍一次。 附:代碼: 鏈接: https://pan.baidu.com/s/1kV24a9L 密碼: tfab