本次练习将完善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时钟下可以明显地观察到灯闪烁的速度加快。
坚持!