本次練習將完善LED程序
1.模塊化
首先將程序進行模塊化劃分,將寄存器相關的信息放在 s3c2440.h 中,後續使用到其他功能寄存器將會持續添加,從而逐漸完善該頭文件。同時現有模塊爲LED模塊,將其獨立設置爲一個文件,而主文件來調用這些模塊提供的機制,從而實現期望的功能。(啓動文件沒有差別,故不列出。)
s3c2440.h
---------------------------------------
#ifndef __S3C2440_H
#define __S3C2440_H
#include <stdint.h>
/* GPIO */
#define GPFCON (*((volatile uint32_t*)0x56000050))
#define GPFDAT (*((volatile uint32_t*)0x56000054))
#endif
led.h
----------------------------------
#ifndef __LED_H
#define __LED_H
#include <stdint.h>
#define kLed1 4
#define kLed2 5
#define kLed3 6
void LedInitAll(void);
void SingleLedInit(const uint8_t ledn);
void SingleLedON(const uint8_t ledn);
void SingleLedOFF(const uint8_t ledn);
#endif
led.c
--------------------------------------
#include "led.h"
#include "s3c2440.h"
void LedInitAll(void)
{
SingleLedInit(kLed1);
SingleLedInit(kLed2);
SingleLedInit(kLed3);
SingleLedOFF(kLed1);
SingleLedOFF(kLed2);
SingleLedOFF(kLed3);
}
/* init the led gpio as a output pin */
void SingleLedInit(const uint8_t ledn)
{
GPFCON &= ~(3 << ledn * 2);
GPFCON |= (1 << ledn * 2);
}
void SingleLedON(const uint8_t ledn)
{
GPFDAT &=~(1 << ledn);
}
void SingleLedOFF(const uint8_t ledn)
{
GPFDAT |= 1 << ledn;
}
main.c
------------------------------------
#include <stdint.h>
#include "s3c2440.h"
#include "led.h"
void Delay(uint32_t time)
{
while(time--);
}
int main()
{
uint8_t led_now=kLed1;
LedInitAll();
while(1)
{
SingleLedOFF(led_now++);
if(led_now > kLed3) { led_now =kLed1; }
SingleLedON(led_now);
Delay(100000);
}
return 0;
}
以上完成了流水燈功能,效果爲三個LED依次亮起熄滅。(此處遇到的一個問題是沒有定義了全局變量或者靜態變量時,反彙編文件會出現dss段和data或者rodata段 out of bounds 的錯誤提示,因爲沒有鏈接腳本或者相應的指引編譯,纔會出現該問題,因此此處先用宏定義代替全局變量完成功能)。
2.看門狗
使用上述流水燈程序運行時發現,經過一段時間程序會自動重啓運行,這是因爲s3c2440存在看門狗機制,且默認打開,需要按時“餵狗”,才能避免程序重啓,這是硬件方式防止程序死鎖,這裏我們先將其關閉即可。
由芯片手冊可以得到,將看門狗配置寄存器置0,即可達到關閉看門狗目的。將其以C語言方式封裝,在main函數中調用,方便後續的使用和拓展。
s3c2440.h
------------------------------
#ifndef __S3C2440_H
#define __S3C2440_H
#include <stdint.h>
/* WATCH DOG */
#define WTCON (*((volatile uint32_t*)0x53000000))
/* GPIO */
#define GPFCON (*((volatile uint32_t*)0x56000050))
#define GPFDAT (*((volatile uint32_t*)0x56000054))
void WatchDogDisable(void);
#endif
s3c2440.c
-------------------------
#include "s3c2440.h"
void WatchDogDisable(void)
{
WTCON = 0x0;
}
3.按鍵
與LED的分析相同,先在電路原理圖上找到按鍵的引腳,再在芯片手冊上查找相關GPIO寄存器的設置,差異點爲按鍵需要將GPIO設置爲輸入引腳。
上圖可以看出,引腳有上拉電阻,即按鍵不按下時,引腳呈高電平,按下後爲低電平。
而對應的引腳分別爲
EINT0 | EINT2 | EINT11 | EINT19 |
GPF0 | GPF2 | GPG3 | GPG11 |
直接按照需要模塊化封裝按鍵,如下:
key.h
--------------------------------
#ifndef __KEY_H
#define __KEY_H
#include <stdint.h>
#define KEY_1 0
#define KEY_2 2
#define KEY_3 3
#define KEY_4 11
void AllKeyInit(void);
void SingleKeyInit(uint8_t keyn);
uint8_t Key1CheckDown(void);
uint8_t Key2CheckDown(void);
uint8_t Key3CheckDown(void);
uint8_t Key4CheckDown(void);
#endif
key.c
----------------------------------------
#include "key.h"
#include "s3c2440.h"
void AllKeyInit(void)
{
SingleKeyInit(KEY_1);
SingleKeyInit(KEY_2);
SingleKeyInit(KEY_3);
SingleKeyInit(KEY_4);
}
//config key as a input io
void SingleKeyInit(uint8_t keyn)
{
switch(keyn)
{
case KEY_1:
case KEY_2:
GPFCON &= ~(3 << keyn*2);
break;
case KEY_3:
case KEY_4:
GPGCON &= ~(3 << keyn*2);
break;
default:
break;
}
}
uint8_t Key1CheckDown(void)
{
return !(GPFDAT & (1 << KEY_1));
}
uint8_t Key2CheckDown(void)
{
return !(GPFDAT & (1 << KEY_2));
}
uint8_t Key3CheckDown(void)
{
return !(GPGDAT & (1 << KEY_3));
}
uint8_t Key4CheckDown(void)
{
return !(GPGDAT & (1 << KEY_4));
}
相應地更新了s3c2440.h的寄存器內容
#ifndef __S3C2440_H
#define __S3C2440_H
#include <stdint.h>
/* WATCH DOG */
#define WTCON (*((volatile uint32_t*)0x53000000))
/* GPIO */
#define GPGCON (*((volatile uint32_t*)0x56000060))
#define GPGDAT (*((volatile uint32_t*)0x56000064))
#define GPGUP (*((volatile uint32_t*)0x56000068))
#define GPFCON (*((volatile uint32_t*)0x56000050))
#define GPFDAT (*((volatile uint32_t*)0x56000054))
#define GPFUP (*((volatile uint32_t*)0x56000058))
void WatchDogDisable(void);
void Delay(uint32_t time);
#endif
同時結合led模塊,在main.c中添加邏輯代碼,效果爲按下按鍵,對應的led熄滅。
#include <stdint.h>
#include "s3c2440.h"
#include "led.h"
#include "key.h"
int main()
{
WatchDogDisable();
LedInitAll();
AllKeyInit();
while(1)
{
if(Key1CheckDown()){ SingleLedON(kLed1); }
else { SingleLedOFF(kLed1); }
if(Key2CheckDown()){ SingleLedON(kLed2); }
else { SingleLedOFF(kLed2); }
if(Key3CheckDown()){ SingleLedON(kLed3); }
else { SingleLedOFF(kLed3); }
}
}
這裏用極爲粗糙的方式實現了該功能,因爲是過渡程序所以妥協了。
4.時鐘
首先需要看下S3C2440的功能塊圖,從上圖可以看到,S3C2440主要由核心處理器ARM920T、AHB總線即掛接在總線上的設備、APB總線及掛接在其上的外設組成。同時參考時鐘圖,可以看到AHB總線設備的時鐘由HCLK提供,APB總線設備的時鐘由PCLK提供,而FCLK提供給核心處理器ARM920T作爲主時鐘。
往前看,發現HCLK和PCLK由FCLK分頻所得,而FCLK則由外部輸入的時鐘經過PLL鎖相環倍頻得到。輸入的時鐘可以是晶振或者是外部時鐘。閱讀芯片手冊,可以找到相關信息如下:
輸入的時鐘由OM來選擇輸入源,這裏板子上直接將OM下拉爲00,即使用晶振作爲輸入時鐘源,主時鐘和USB時鐘都採用該時鐘源,板載晶振爲12MHz。
下面的notes說明了需要我們設置MPLLCON寄存器後,MPLL的輸出纔會作爲系統時鐘運行,因此前面的代碼我們未設置的情況下,系統時鐘爲12MHz,非常慢,因此我們需要設置該寄存器提高系統運行速度。
板子上電後,需要nRESET信號對系統進行復位,系統纔會開始運行,而上電1 到 nRESET信號2存在一段時間,這是因爲需要等待電源穩定,這個時間由一個蓄電電容或者一個延時芯片保證。2之後系統復位,開始運行,此時如果PLL被軟件修改,即我們對其設置,則會引起一個clockdisable,將時鐘停止,此時系統停止運行,當系統穩定後,lock Time過去之後,系統將以我們設置的時鐘開始運行,即圖中4之後的FCLK is new frequency。這一步需要設置的是MPLL的PMS,決定MPLL倍頻數。
第二步需要設置FCLK到HCLK和PCLK的分頻係數。由上看到不同分頻係數的最終分頻數,程序需要根據該表設置,其餘的注意項看原文即可。需要特別注意的是HCLK和PCLK有上限,需要在確定FCLK後仔細確定分頻係數。同時,當HDIVN不爲0時,需要用如下圖彙編方式將CPU模式設置爲異步模式,否則CPU將會使用HCLK作爲系統時鐘運行。
MRC和MCR爲對協處理器處理的指令,orr中,R1_nF:OR:R1_iA參考“ARM920T(Rev 1)Technical Reference Manual”可知,需要控制一個控制寄存器的高兩位,從而使得ARM920T處於異步模式。在 https://blog.csdn.net/shaodongju/article/details/51584576?locationNum=14&fps=1 中有詳細說明,同樣可以直接查閱參考手冊得到上述結論,得到該段彙編代碼應該爲
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000
mcr p15,0,r0,c1,c0,0
MPLL操作的寄存器爲
這裏我們只需要參照手冊給的例值即可(自己確認較難且不準確),
分頻係數也同樣
綜上,設置MPLL使得12MHz輸入倍頻爲400MHz系統時鐘,分頻爲FCLK:HCLK:PCLK=1:4:8,即HCLK輸出100MHz,PCLK輸出50MHz。程序如下:
s3c2440.h
---------------------------------------------
#ifndef __S3C2440_H
#define __S3C2440_H
#include <stdint.h>
/* CLOCK */
#define MPLLCON (*((volatile uint32_t*)0x4C000004))
#define UPLLCON (*((volatile uint32_t*)0x4C000008))
#define CLKDIVN (*((volatile uint32_t*)0x4C000014))
/* WATCH DOG */
#define WTCON (*((volatile uint32_t*)0x53000000))
/* GPIO */
#define GPGCON (*((volatile uint32_t*)0x56000060))
#define GPGDAT (*((volatile uint32_t*)0x56000064))
#define GPGUP (*((volatile uint32_t*)0x56000068))
#define GPFCON (*((volatile uint32_t*)0x56000050))
#define GPFDAT (*((volatile uint32_t*)0x56000054))
#define GPFUP (*((volatile uint32_t*)0x56000058))
void HardwareInitAll(void);
void Delay(uint32_t time);
#endif
s3c2440.c
---------------------------------------------
#include "s3c2440.h"
void Delay(uint32_t time)
{
while(time--);
}
static void WatchDogDisable(void)
{
WTCON = 0x0;
}
static void MPLLConfig(void)
{
MPLLCON = ( 0x5c << 12) | ( 1 << 4 ) | ( 1 << 0 );
}
static void ClockDevideConfig(void)
{
CLKDIVN = (2 << 1) | ( 1<< 0);
}
static void ChangeModeToAsynchronous(void)
{
asm(
"mrc p15,0,r0,c1,c0,0 \n\t"
"orr r0,r0,#0xc0000000 \n\t"
"mcr p15,0,r0,c1,c0,0 \n\t"
);
}
void HardwareInitAll(void)
{
WatchDogDisable();
ChangeModeToAsynchronous();
MPLLConfig();
ClockDevideConfig();
}
main.c
------------------------------------------------
#include <stdint.h>
#include "s3c2440.h"
#include "led.h"
int main()
{
HardwareInitAll();
uint8_t led_now=kLed1;
LedInitAll();
while(1)
{
SingleLedOFF(led_now++);
if(led_now > kLed3) { led_now =kLed1; }
SingleLedON(led_now);
Delay(100000);
}
return 0;
}
以流水燈爲例,修改時鐘之後,400MHz時鐘下可以明顯地觀察到燈閃爍的速度加快。
堅持!