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设置为输入引脚。

扫描二维码关注公众号,回复: 9846240 查看本文章

上图可以看出,引脚有上拉电阻,即按键不按下时,引脚呈高电平,按下后为低电平。

而对应的引脚分别为

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时钟下可以明显地观察到灯闪烁的速度加快。

坚持!

发布了19 篇原创文章 · 获赞 7 · 访问量 6926

猜你喜欢

转载自blog.csdn.net/G_METHOD/article/details/104271762