JZ2440裸板開發練習#3 - LED程序完善(看門狗、按鍵、時鐘)

本次練習將完善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時鐘下可以明顯地觀察到燈閃爍的速度加快。

堅持!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章