基于ARM单片机的软件时钟
ARM单片机的功能要大大优于51单片机,而其价格已经很低了,生产厂家不论是出于广告宣传的需要,还是出于利润的角度考虑(嵌入ARM单片机将会卖更高的价格),他们更希望在自己的产品中嵌入ARM单片机。
本人使用的是周立功公司推出的Easy ARM1138开发板,它除了具有32位ARM Cortex-M3内核的LM3S1138单片机之外,还内嵌有USB接口的仿真调试器,开发调试非常方便,不愧为广大师生和工程师的首选。如下图所示。
1. 硬件设计
利用LM3S1138内部的定时器,0.5秒产生一次中断,再用一个时间计数器进行计时;然后再利用1602LCD进行显示;按键sw1为时间“选择”键,按键sw2为时间“+1”键,它们用于对于时间的调整;从而实现完整的软件时钟。电位器用于调整LCD的对比度。完整的电原理图如下所示。
目标板是利用原有的一块印制板,在其上焊接有两只按键、两只电阻、一只电位器和连接1602LCD的插座(参见后面的实物照片),目标板与1138开发板之间通过15根杜邦线相连。
⒉ 软件设计
程序一开始要进行系统初始化、定时器设置初值、开中断、LCD初始化、调用显示子函数。然后进入大循环;首先判断是否是“选择”键按下,若是则进行消抖处理,再判断原为时间的哪一位在闪烁,并使得下一位闪烁,同时使闪烁计时为零; 然后判断是否是“+1”键按下,若是则进行消抖处理,再判断原为时间的哪一位在闪烁,并使得该位+1,同时使闪烁计时为零;然后循环判断是否是“选择”键按下……。
定时器T0每0.5秒中断一次,在定时器中断函数中,首先中断计数器+1,若中断次数为奇数,则根据闪烁字控制某位不显示;若中断次数为偶数(一秒),则时间计数器+1,当时间计数器的值超过23点59分59秒时时间计数器=0,再调显示子函数,并使闪烁计时器+1,然后判断闪烁字是否不为零,若是则判断闪烁是否已达30秒?若是则停止闪烁,并中断返回。详细的流程如下图所示。
基于ARM单片机LM3S1138软件时钟的详细程序如下所示(基于《Stellaris外设驱动库》)。
/*****************************************************
T0+LCD1602程序 ,一行显示Easy ARM1138,另一行显示时间
PA口--数据,PF0--RS,PF1--RW,PF2--E,PF6-sw1,PF7-sw2
******************************************************/
// 包含必要的头文件
#include <hw_types.h>
#include <hw_memmap.h>
#include <hw_ints.h>
#include <hw_sysctl.h>
#include <hw_gpio.h>
#include <hw_timer.h>
#include <interrupt.h>
#include <sysctl.h>
#include <gpio.h>
#include <timer.h>
// 将较长的标识符定义成较短的形式
#define SysCtlPeriEnable SysCtlPeripheralEnable
#define SysCtlPeriDisable SysCtlPeripheralDisable
#define GPIOPinTypeIn GPIOPinTypeGPIOInput
#define GPIOPinTypeOut GPIOPinTypeGPIOOutput
#define RS GPIO_PORTF_BASE,GPIO_PIN_0 //LCD寄存器选择
#define RW GPIO_PORTF_BASE,GPIO_PIN_1 //LCD读写控制
#define E GPIO_PORTF_BASE,GPIO_PIN_2 //LCD使能
#define AIO GPIO_PORTA_BASE,0xff //LCD数据口
#define sw1 GPIO_PORTF_BASE,GPIO_PIN_6 //选择键
#define sw2 GPIO_PORTF_BASE,GPIO_PIN_7 //+1键
#define uint unsigned int
#define uchar unsigned char
uint sjjs=3600*8+60*15; //时间初值为8:15
uint zdjs=0; //中断计数
uchar SJ=0; //闪烁计时
uchar sz=0; //闪烁字 (小时十位=08,个位=04,分钟十位=02,个位闪烁=01)
// 定义全局的系统时钟变量
unsigned long TheSysClock = 12000000UL;
// 延时
void Delay(unsigned long ulVal)
{
while ( --ulVal != 0 );
}
//LCD初始化
void LCD_Init(void)
{
int i;
GPIOPinWrite(AIO,0x30); //GPIOA口出0x30
GPIOPinWrite(RS,0) ; //RS0
GPIOPinWrite(RW,0) ; //RW0
for(i=0;i<3;i++)
{
GPIOPinWrite(E,0x01<<2) ; //E1
GPIOPinWrite(E,0) ; //E=0
Delay(500* (TheSysClock / 4000000)); // 延时约500us
}
}
//LCD命令写
void Write_Com(uchar a)
{
GPIOPinTypeOut(AIO); //GPIOA为输出
GPIOPinWrite(RS,0) ; //RS=0
GPIOPinWrite(RW,0x01<<1) ; //RW=1
GPIOPinWrite(E,0x01<<2) ; //E=1
GPIOPinTypeIn(AIO); //GPIOA为输入
while (0x80==(GPIOPinRead(AIO)&0x80)) //测试忙闲
;
GPIOPinWrite(E,0) ; //E=0
GPIOPinWrite(RW,0) ; //RW=0
GPIOPinTypeOut(AIO); //GPIOA为输出
GPIOPinWrite(AIO,a); //GPIOA口出a
GPIOPinWrite(E,0x01<<2) ; //E=1
GPIOPinWrite(E,0) ; //E=0
}
//LCD数据写
void Write_Data(uchar a)
{
GPIOPinTypeOut(AIO); //GPIOA为输出
GPIOPinWrite(RS,0) ; //RS=0
GPIOPinWrite(RW,0x01<<1) ; //RW=1
GPIOPinWrite(E,0x01<<2) ; //E=1
GPIOPinTypeIn(AIO); //GPIOA为输入
while (0x80==(GPIOPinRead(AIO)&0x80)) //测试忙闲
;
GPIOPinWrite(E,0) ; //E=0
GPIOPinWrite(RS,1) ; //RS=1
GPIOPinWrite(RW,0) ; //RW=0
GPIOPinTypeOut(AIO); //GPIO A为输出
GPIOPinWrite(AIO,a); //GPIO A口出a
GPIOPinWrite(E,0x01<<2) ; //E=1
GPIOPinWrite(E,0) ; //E=0
}
// 系统初始化
void SystemInit(void)
{
SysCtlLDOSet(SYSCTL_LDO_2_50V); // 设置LDO输出电压
SysCtlClockSet(SYSCTL_USE_OSC | // 系统时钟设置,采用主振荡器
SYSCTL_OSC_MAIN |
SYSCTL_XTAL_6MHZ |
SYSCTL_SYSDIV_1);
TheSysClock = SysCtlClockGet(); // 获取系统时钟,单位:Hz
}
// 计算并显示时间
void Timer_Disp( void )
{
unsigned int a,b,f;
// 计算并显示小时
a=(sjjs/3600);
b=a%10;
a=a/10;
Write_Com(0xc4); //小时十位
Write_Data(0X30+a);
Write_Data(0X30+b);
Write_Data(0x3a);
// 计算并显示分钟
f=sjjs%3600;
a=f/60;
b=a%10;
a=a/10;
Write_Data(0X30+a);
Write_Data(0X30+b);
Write_Data(0x3a);
// 计算并显示秒
f=sjjs%60;
b=f%10;
a=f/10;
Write_Data(0X30+a);
Write_Data(0X30+b);
}
// 主函数(程序入口)
int main(void)
{
SystemInit(); // 系统初始化
uint c;
SysCtlPeriEnable(SYSCTL_PERIPH_TIMER0); //使能定时器模块
TimerConfigure(TIMER0_BASE , TIMER_CFG_32_BIT_PER); //配置定时器为32位周期定时器
TimerLoadSet(TIMER0_BASE , TIMER_A , 3000000UL); //设置定时器初值(0.5S)
TimerIntEnable(TIMER0_BASE , TIMER_TIMA_TIMEOUT); //使能定时器超时中断
IntEnable(INT_TIMER0A); //使能定时器中断
IntMasterEnable(); //使能处理器中断
TimerEnable(TIMER0_BASE , TIMER_A); //使能定时器计数
SysCtlPeriEnable(SYSCTL_PERIPH_GPIOA); //使能GPIOA口外设
GPIOPinTypeOut(GPIO_PORTA_BASE ,0xFF); //GPIOA为输出
SysCtlPeriEnable(SYSCTL_PERIPH_GPIOF); //使能GPIOF口外设
GPIOPinTypeOut(GPIO_PORTF_BASE ,0x07); //PF0,1,2为输出
GPIOPinTypeIn(GPIO_PORTF_BASE ,0xC0); //PF6,7为输入
LCD_Init(); //LCD初始化
Write_Com(0x38); //设置工作方式
Write_Com(0x01); //清除显示
Write_Com(0x06); //设置输入方式
Write_Com(0x0c); //设置显示方式
Write_Com(0x82);
Write_Data(0x45); //显示Easy ARM1138
Write_Data(0x61);
Write_Data(0x73);
Write_Data(0x79);
Write_Data(0x20);
Write_Data(0x41);
Write_Data(0x52);
Write_Data(0x4d);
Write_Data(0x31);
Write_Data(0x31);
Write_Data(0x33);
Write_Data(0x38);
Timer_Disp(); //显示时间
while(1)
{
if(GPIOPinRead(sw1)==0)
{
Delay(50* (TheSysClock / 4000)); //消抖,延时约50ms
if(GPIOPinRead(sw1)==0)
{ //选择键按下
for(;;)
{
if(sz==0x08)
{ //原为小时十位闪烁
sz=0x04; //改为小时个位闪烁
SJ=0;
for(;;)
if(GPIOPinRead(sw1)) //选择键松开
goto loop;
}
if(sz==0x04)
{ //原为小时个位闪烁
sz=0x02; //改为分钟十位闪烁
SJ=0;
for(;;)
if(GPIOPinRead(sw1)) //选择键松开
goto loop;
}
if(sz==0x02)
{ //原为分钟十位闪烁
sz=0x01; //改为分钟个位闪烁
SJ=0;
for(;;)
if(GPIOPinRead(sw1)) //选择键松开
goto loop;
}
{ //原无闪烁或为分钟个位闪烁
sz=0x08; //改为小时十位闪烁
SJ=0;
for(;;)
if(GPIOPinRead(sw1)) //选择键松开
goto loop;
}
}
}
}
loop:if(GPIOPinRead(sw2)==0)
{ //+1键按下
Delay(50* (TheSysClock / 4000)); //消抖,延时约50ms
if(GPIOPinRead(sw2)==0)
{
for(;;)
{
if(sz==0x08)
{ //小时十位闪烁
if(sjjs/36000<2)
sjjs+=36000 ; //小时十位+1
else
sjjs%=36000;
SJ=0;
for(;;)
if(GPIOPinRead(sw2)) //+1键松开
goto loop;
}
if(sz==0x04)
{ //小时个位闪烁
c=sjjs%36000;
if(c/3600<9)
sjjs+=3600 ; //小时个位+1
else
sjjs-=3600*9;
SJ=0;
for(;;)
if(GPIOPinRead(sw2)) //+1键松开
goto loop;
}
if(sz==0x02)
{ //分钟十位闪烁
c=sjjs%36000;
c%=3600;
if(c/600<5)
sjjs=sjjs+600; //分钟十位+1
else
sjjs-=600*5;
SJ=0;
for(;;)
if(GPIOPinRead(sw2)) //+1键松开
goto loop;
}
if(sz==0x01)
{ //分钟个位闪烁
c=sjjs%36000;
c%=3600;
c=c%600;
if(c/60<9)
sjjs+=60 ; //分钟个位+1
else
sjjs-=60*9;
SJ=0;
for(;;)
if(GPIOPinRead(sw2)) //+1键松开
goto loop;
}
}
}
}
}
}
// 定时器的中断服务函数
void Timer0A_ISR(void)
{
unsigned long ulStatus;
ulStatus = TimerIntStatus(TIMER0_BASE , true); //读取中断状态
TimerIntClear(TIMER0_BASE , ulStatus); //清除中断状态,重要!
if ( ulStatus & TIMER_TIMA_TIMEOUT ) //如果是定时器超时中断
{
zdjs+=1;
if(zdjs&0x01)
{ //0.5秒
if(sz)
{
switch(sz)
{
case 1:Write_Com(0xc8);Write_Data(0x20);break; //分钟个位灭
case 2:Write_Com(0xc7);Write_Data(0x20);break; //分钟十位灭
case 4:Write_Com(0xc5);Write_Data(0x20);break; //小时个位灭
case 8:Write_Com(0xc4);Write_Data(0x20);break; //小时十位灭
}
}
}
else
{ //一秒
sjjs+=1;
if(sjjs>23*3600+59*60+59)
sjjs=0;
Timer_Disp(); // 显示时间
SJ+=1;
if(sz)
{
if(SJ>29)
{
sz=0; //停止闪烁
}
}
}
}
}
本人使用的是IAR EWARM(IAR Embedded Workbench for ARM)集成开发环境,利用周立功公司提供的工程模板,输入以上的源程序,点击菜单“Project”→“Mark”进行编译,或按F7键,根据提示修改错误,再进行编译直至无错误为止 。
3 软件调试
编译无误后即可点击菜单“Project”→“Dbuge”下载源程序的机器码至开发板(或按Ctrl+D),同时在桌面上出现了几个调试用的快捷按钮——运行(go)、运行到光标处(Run to Cursor)、步出(Step Out)、步入(Step Into)、步越(Step Over)、停止(Break)、复位(Reset)等。通过选择上述不同的快捷按钮,以及观察变量的值(点击菜单“View”→“Watch”在Expression中输入变量名,即可在Value中看到该变量的值;或将光标悬停在某变量上,即可看到该变量的值),来调试程序,直至一切无误后即可全速运行了(点击快捷按钮go)。实物图片如下所示。
文章评论(0条评论)
登录后参与讨论