单片机C语言的编程规范

编程语言 专栏收录该内容
27 篇文章 0 订阅

写单片机程序也是程序,也要遵循写软件的一些基本原则,不是为了完成功能那么简单。我看过的所有的C语言单片机书籍基本都不注重模块化思想,完全是拿着C当汇编用,简直是在糟蹋C语言!

如下问题,几乎所有的单片机书籍中都大量存在(更别说网上的和现实中的代码了,书上都写的那么差劲,学的人能好到哪里去):
1、变量到处定义,根本不管变量的生命周期是否合适(请回答:全局变量、局部变量、静态变量、volatile变量有什么区别联系?)
2、变量名称极不规范,根本从名字上看不出来这个变量类型是什么,到底想干什么。
3、函数定义几乎不用参数,全都是void
4、语句写的一点都不直观,根本就是在用汇编。比如:想取一个字长的高字节和低字节,应该定义一个宏或是函数来做,如#define HIBYTE(w) ((BYTE)((DWORD)(w) >> 8)),以后直接用HIBYTE()多直观,难道非得用(BYTE)((DWORD)(w) >> 8)代表你的移位操作的水平很高吗?
5、最重要的一点,没有建立模块化的编程思想。一个程序往往要很多部分协同工作,需要把不同的功能分离出来单独创建一个.h和.c的文件,然后在头文件中把可以访问的函数暴露出来。
6、不思考曾经做过的程序是否还有改进的余地,写程序如果只是为了写而写,一辈子也长进不了多少

为了证明我以上的观点,特此发一下我对c51定时器的封装,此定时器可以同时设定多个定时任务,定时精度由晶振精度决定。我的项目中一般用的是12MHZ的晶振,最小定时在20ms基本可以接受,再小的不能保证。


/
头文件
//
#ifndef _TIMER_CONFIG_H_
#define _TIMER_CONFIG_H_
#include "const.h"
#include "oscfrequencydef.h"

#ifndef OSC_FREQUENCY
#error undefined OSC_FREQUENCY
#endif

//#warning must be used in AT89C52 or later version because of "idata"
#warning **********************************************************************************
#warning !! make sure MAX_TIMER_EVENT_NUM and TIMER0_BASE_INTERVAL has appropriate value!! 
#warning **********************************************************************************

/****************************************************************************
定时中断每TIMER0_BASE_INTERVAL毫秒产生一次,用户定义的中断时间必须是它的整数倍
****************************************************************************/
#define MAX_TIMER_EVENT_NUM 5 //可设置不同定时事件的最大个数(至少为2)
#define TIMER0_BASE_INTERVAL 20 //单位:毫秒




typedef void (*TIMERPROC)(BYTE nID);

void InitTimer0();
BOOL SetTimerCallback(TIMERPROC lpTimerFunc); //必须在SetTimer0之前调用
BOOL SetTimer0(BYTE nID, WORD wInterval); //通过nID(nID>0)来区分 
//BOOL KillTimer0(BYTE nID);





/
//以下为内部使用

typedef struct tagTIMERINFO
{
BYTE nID; //定时器ID
WORD wInterval; //此定时器的设定间隔时间
WORD wElapse; //剩余的时间

}TIMERINFO;
static BOOL AddTail(const TIMERINFO* pTimerInfo);
static BOOL Remove(BYTE nID);
static BYTE FindID(BYTE nID);

#endif


其中用到的的const.h定义如下:


#ifndef _CONST_H_
#define _CONST_H_
#include <intrins.h>

#define TRUE 1
#define FALSE 0

typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;
typedef float FLOAT;  
typedef char CHAR;
typedef unsigned char UCHAR;
typedef int INT;
typedef unsigned int UINT;
typedef unsigned long ULONG;
typedef UINT WPARAM;
typedef ULONG LPARAM;
typedef ULONG LRESULT;
typedef void VOID;
typedef const CONST;
typedef void *PVOID;
typedef bit BOOL; 




#define MAKEWORD(lo, hi) ((WORD)(((BYTE)(lo)) | ((WORD)((BYTE)(hi))) << 8))
#define MAKEDWORD(lo, hi) ((DWORD)(((WORD)(lo)) | ((DWORD)((WORD)(hi))) << 16))
#define LOWORD(l) ((WORD)(l))
#define HIWORD(l) ((WORD)(((DWORD)(l) >> 16) & 0xFFFF))
#define LOBYTE(w) ((BYTE)(w))
#define HIBYTE(w) ((BYTE)(((WORD)(w) >> 8) & 0xFF))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))


#define SET_STATE_FLAG(state, mask) ((state) |= (mask))
#define RESET_STATE_FLAG(state, mask) ((state) &= ~(mask))
#define TEST_STATE_FLAG(state, mask) ((state) & (mask))



#define TEST_BIT(b, offset) (1 & ((b) >> (offset)))
#define SET_BIT(b, offset) ((b) |= (1 << (offset)))
#define RESET_BIT(b, offset) ((b) &= (~(1 << (offset))))



//将BCD码变为十进制,如将0x23变为23
//注意:高四位和低四位均不能大于9
#define BCD_TO_DECIMAL(bcd) ((BYTE)((((BYTE)(bcd)) >> 4) * 10 + (((BYTE)(bcd)) & 0x0f)))
#define DECIMAL_TO_BCD(decimal) ((BYTE)(((((BYTE)(decimal)) / 10) << 4) | ((BYTE)(decimal)) % 10))

#define NOP() _nop_()
#define BYTE_ROTATE_LEFT(b, n) _crol_(b, n)
#define BYTE_ROTATE_RIGHT(b, n) _cror_(b, n)
#define WORD_ROTATE_LEFT(w, n) _irol_(w, n)
#define WORD_ROTATE_RIGHT(w, n) _iror_(w, n) 
#define DWORD_ROTATE_LEFT(dw, n) _lrol_(dw, n)
#define DWORD_ROTATE_RIGHT(dw, n) _lror_(dw, n)

#define ENABLE_ALL_INTERRUPTS() (EA = 1)
#define DISABLE_ALL_INTERRUPTS() (EA = 0)


#endif
下面是定时器的.c文件的具体实现:

实现中用到了一点数据结构中“队列”的概念

#include "timerconfig.h"
#include "chiptypedef.h"
#include <limits.h>
#include <string.h>



code const WORD TIMER0_INIT_VALUE = UINT_MAX - ((WORD)((float)OSC_FREQUENCY * 1.0f / 12 * 1000)) * TIMER0_BASE_INTERVAL;


idata TIMERINFO TimerInfoArray[MAX_TIMER_EVENT_NUM] = {0};
TIMERPROC g_pfnTimerFunc = NULL;
BYTE g_nTimerInfoNum = 0; //当前队列的元素个数


void InitTimer0()
{
TMOD |= T0_M0_; //定时器0,工作方式1
TH0 = HIBYTE(TIMER0_INIT_VALUE);
TL0 = LOBYTE(TIMER0_INIT_VALUE);
TR0 = 0; //停止定时器0
ET0 = 0; //关定时器0中断
EA = 1;
}

BOOL SetTimerCallback(TIMERPROC lpTimerFunc)
{
if(lpTimerFunc == NULL)
return FALSE;


g_pfnTimerFunc = lpTimerFunc;

return TRUE;

}

BOOL SetTimer0(BYTE nID, WORD wInterval)
{
TIMERINFO ti;
if(g_pfnTimerFunc == NULL || nID == 0 || wInterval == 0)
return FALSE;

if(wInterval % TIMER0_BASE_INTERVAL != 0) //定时间隔必须是TIMER0_BASE_INTERVAL的整数倍
return FALSE;

if(FindID(nID) != MAX_TIMER_EVENT_NUM) //若已经有相同的ID存在
return FALSE;




ti.nID = nID;
ti.wInterval = wInterval;
ti.wElapse = wInterval;

if(!AddTail(&ti))
return FALSE;

TR0 = 1; //启动定时器0
ET0 = 1; //开定时器0中断

return TRUE;

}
  /*
BOOL KillTimer0(BYTE nID)
{

if(!Remove(nID) || nID == 0)
return FALSE;

if(g_nTimerInfoNum == 0) //若最后一个定时事件已经停止,则关定时器中断
ET0 = 0;

return TRUE;
} */

static BYTE FindID(BYTE nID)
{
BYTE i = 0;
for(i = 0; i < MAX_TIMER_EVENT_NUM; i++)
{
if(TimerInfoArray[i].nID == nID)
return i;
}

return MAX_TIMER_EVENT_NUM;
}

static BOOL AddTail(const TIMERINFO* pTimerInfo)
{
if(g_nTimerInfoNum == MAX_TIMER_EVENT_NUM || pTimerInfo == NULL)
return FALSE;


memcpy(&TimerInfoArray[g_nTimerInfoNum], pTimerInfo, sizeof(TIMERINFO));
g_nTimerInfoNum++;

return TRUE;
}

/*
static BOOL Remove(BYTE nID)
{
BYTE nIndex = FindID(nID);
BYTE nRest = g_nTimerInfoNum - nIndex - 1;

if(nIndex == MAX_TIMER_EVENT_NUM || nID == 0)
return FALSE;

if(nRest == 0) //已经是队列尾元素
{
memset(&TimerInfoArray[nIndex], 0, sizeof(TIMERINFO));
}
else
{
//删除后,前移
memcpy(&TimerInfoArray[nIndex], &TimerInfoArray[nIndex + 1], sizeof(TIMERINFO) * nRest);
memset(&TimerInfoArray[nIndex + nRest], 0, sizeof(TIMERINFO) * (MAX_TIMER_EVENT_NUM - (nIndex + nRest) - 1));
}

g_nTimerInfoNum--;

return TRUE;

} */

void Timer0ISR() interrupt TF0_VECTOR
{
BYTE i = 0;
TF0 = 0;

TH0 = HIBYTE(TIMER0_INIT_VALUE);
TL0 = LOBYTE(TIMER0_INIT_VALUE);

for(i = 0; i < g_nTimerInfoNum; i++)
{
TimerInfoArray[i].wElapse -= TIMER0_BASE_INTERVAL;
if(TimerInfoArray[i].wElapse == 0)
{
(*g_pfnTimerFunc)(TimerInfoArray[i].nID);
TimerInfoArray[i].wElapse = TimerInfoArray[i].wInterval;
}

}
}
稍微说一下这个定时器的使用:

1、设定晶振频率标识符OSC_FREQUENCY为所需,比如12MHz就设置为12
2、更改预定义标识符的值MAX_TIMER_EVENT_NUM和TIMER0_BASE_INTERVAL 。注意MAX_TIMER_EVENT_NUM的值至少为2,TIMER0_BASE_INTERVAL 的值不能超过当前晶振频率下定时器0的最大溢出时间。如:12MHz下,定时器0的溢出时间为65.535ms,即TIMER0_BASE_INTERVAL 的值不能超过65的整数
3、初始化,调用InitTimer0()
4、设定回调函数SetTimerCallback(TimerProc),TimerProc的原型为typedef void (*TIMERPROC)(BYTE nID),即参数是unsigned char,返回值为void的函数。
5、设定定时事件SetTimer0(),注意定时间隔必须是TIMER0_BASE_INTERVAL的整数倍
6、具体实现回调函数void TimerProc(BYTE nID)
这样每当一个定时事件触发后便会自动调用TimerProc函数,程序员具体的任务只需要在函数中实现定时器到时后需要处理的事情,通过判断nID来表明是哪个定时事件触发的当前定时事件

把定时器做成这样有什么好处呢?

1、体现了模块化的思想,达到了代码的复用目的,因为定时器几乎是每个单片机项目都需要用到的资源
2、屏蔽了定时器使用者需要了解定时器内部设定的细节,达到了一定的抽象,因为调用者只需要简单地设置几个预定义的标示符即可使用了,不需要了解定时器初始值的计算、定时器中断函数中初始值还需重新装载等很多琐碎容易出错的问题
3、可以设定多个定时任务,因为往往定时器的使用并非为了解决一个任务而设定的。如果用最原始的实现方法来完成多个定时任务,那么就需要很多标志位变量来区别不同的定时事件,大量的全局性的标志位变量势必会影响程序的结构,使各函数之间的耦合无形中增大了

但也有如下的不足:
1、为了完成各定时事件的调度,需要额外占用单片机的ram和rom资源,所以这个定时器不太适用仅有128字节的c51芯片,适合256字节以上的系列
2、各个定时事件是依次调用的,这样会造成实时性和定时精度不佳,实测基本最小时间间隔基本10~20ms,当然这晶振频率和定时事件中处理的任务量有关系了。如果需要更高的定时精度那只能:一、提高晶振频率,二、老老实实用最原始的定时器来实现,三、再不行就只能用汇编了


小程序可是有大学问的。举个例子吧,断码管用过吧?它的解法有两种:共阴极和共阳极。断码管有abcdefg七个端子接入,一般我们会按顺序abcdef对应单片机某一端口的从低位到高位接。但你想过没有,若是正好把高低位顺序完全接反了怎么办(不要说不可能,我可碰到过)?再加上又可以共阴极和共阳极两种选择。是否可以把这四种情况都统一到一起形成一个.h头文件供日后随意使用呢?
这其实应该是程序员的直觉,一种天生的惰性,把经常用到的东西一次性做好,供日后使用。
我是这么实现的,参见以下代码:[code=C/C++][/code]
#ifndef _LED_NUM_H_
#define _LED_NUM_H_
#include "const.h"

typedef enum tagLEDNUM
{
LED_0,
LED_1,
LED_2,
LED_3,
LED_4,
LED_5,
LED_6,
LED_7,
LED_8,
LED_9,
/* LED_A,
LED_B,
LED_C
-----《c专家编程》和《c陷阱与缺陷》,
  • 0
    点赞
  • 1
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

关于8255单片机程序设计 ;变量声明区 SECOND EQU 30H ;秒 MINUTE EQU 31H ;分 HOUR EQU 32H ;时 DAY EQU 33H ;日 MONTH EQU 34H ;月 YEAR_L EQU 35H ;年低位 YEAR_H EQU 36H ;年高位 DISPBUF EQU 37H ;显示缓冲区(6字节) DISPBIT EQU 3DH ;位选偏移量 FLAG EQU 3EH ;标记(0表示在主界面,1-6分别代表显示时、分、秒、年、月、日,0为一般显示(当前位置为主界面),7为显示日期,8为显示闹铃开关,9、10显示闹铃时和分,11表示不做缓冲处理) KEY EQU 3FH ;按键值 CLOSE_BIT EQU 40H ;显示屏蔽(和位选相与后送P2) A1_MINUTE EQU 41H ;闹铃1 分钟 A1_HOUR EQU 42H ;闹铃1 小时 A1_SWITCH EQU 43H ;闹铃1 开关 A2_MINUTE EQU 44H ;闹铃2 分钟 A2_HOUR EQU 45H ;闹铃2 小时 A2_SWITCH EQU 46H ;闹铃2 开关 A3_MINUTE EQU 47H ;闹铃3 分钟 A3_HOUR EQU 48H ;闹铃3 小时 A3_SWITCH EQU 49H ;闹铃3 开关 FLAG_KEEP EQU 4AH ;闹钟持续鸣叫标志(1开启,0关闭) FLAG_ALARM EQU 4BH ;闹钟响标志(为1蜂鸣器响,为0蜂鸣器不响) FLAG_NUM EQU 4CH ;当前闹钟标志(1-3个闹钟) FLAG_MOVING EQU 4DH ;流水显示初始化标志(0未初始化 1已经初始化) COUNT EQU 4EH ;T0中断次数(T0内部使用) COUNT_1S EQU 4FH ;计数1S(满N秒后执行程序) FLAG_1S EQU 50H ;满1秒取反标志(1秒执行程序1,另一秒执行程序2) FLAG_ADD EQU 51H ;时间设置标记(1代表FLAG对应时段加1) FLAG_CLOSE EQU 52H ;闪烁显示标记(为0不闪烁) DATE_STYLE EQU 53H ;日期显示模式标志(为1跳变切换显示,为2流水显示,为3下滑切换显示) CLOSE_BIT_TEMP EQU 54H ;屏蔽位暂存(流水显示模式) CLOSE_BIT_CODE EQU 55H ;段选屏蔽位(和段选相与后送P0) KEY_TEMP EQU 56H ;临时按键值(串口发送内容) COUNT_50MS EQU 57H ;50毫秒计数 TEMP_R0 EQU 58H ;保护R0 TEMP_R1 EQU 59H ;保护R1 TEMP_R2 EQU 5AH ;保护R2 FLAG_DOWN_S EQU 5BH ;下移显示阶段标志(每0.2s加1;0无显示,1、2移入显示,3-17正常,18、19移出显示) MOVING_DISPBUF EQU 5CH ;临时缓冲区(流水显示模式) ;---------------------------------------------------------------------------------------------- ;主程序 ORG 00H SJMP START ORG 0BH LJMP INT_T0 ORG 13H AJMP INT_1 ORG 30H START: ;初始化 MOV SECOND,#00 MOV MINUTE,#42 MOV HOUR,#02 MOV DAY,#09 MOV MONTH,#12 MOV YEAR_L,#09 MOV YEAR_H,#20 MOV A1_SWITCH,#00H MOV A1_MINUTE,#22 MOV A1_HOUR,#12 MOV A2_SWITCH,#00H MOV A2_MINUTE,#00H MOV A2_HOUR,#00H MOV A3_SWITCH,#00H MOV A3_MINUTE,#00H MOV A3_HOUR,#00H MOV DISPBIT,#00H MOV FLAG_KEEP,00H MOV P3,#0FH ;键盘进入监控输入状态 MOV R7,#00H ;整点报时鸣铃次数清0 SETB P2.0 ;关蜂鸣器 MOV FLAG_CLOSE,#00H MOV FLAG,#0BH MOV FLAG_ADD,#00H MOV FLAG_1S,#01H MOV COUNT,#00H MOV KEY,#00H SETB PT0 ;计数器中断优先级高 CLR PX1 ;外部中断1优先级低 MOV TMOD,#21H ;T0为定时器方式1,T1为方式2 SETB TR0 ;T0开始计数 MOV TL1,#0F3H ;T1初始化 MOV TH1,#0F3H ;T1重置初值 SETB TR1 ;T1开始计数 MOV SCON,#50H ;串口工作方式1,允许接收 MOV PCON,#80H ;SMOD=1 MOV IE,#10000110B ;开总中断,开T0,外部中断1 CLR IT1 ;外部中断1触发方式为电平触发 ;--------------------------------------------------------------------------------------------- ;基本功能模块,按1显示012345,按2显示ABCDEF,按4进入电子时钟界面 MOV CLOSE_BIT_CODE,#00H MOV FLAG_1S,#01H MAIN0: ACALL DISPLAY MOV A,KEY ;取键值 CJNE A,#01H,MAIN0_J0 ;判断键值 MOV KEY,#00H ;主菜单下KEY=1,显示012345 SHOW_012345_START: ;显示初始化 MOV CLOSE_BIT_CODE,#0FFH ;无段选屏蔽 MOV CLOSE_BIT,#81H ;屏蔽所有位选 ACALL BUF_012345 ;显示缓冲区缓存012345 SHOW_012345: MOV A,FLAG_1S ;取1S取反标志 CJNE A,#01H,SHOW_012345_J0 CPL A ;1S已经到(FLAG_1S=1) INC A MOV FLAG_1S,A ;1S取反标志取反,等待下一秒 SETB C MOV A,CLOSE_BIT RLC A MOV CLOSE_BIT,A ;已到1S,依次开位选 SHOW_012345_J0: ;1S未到(FLAG_1S=0),继续显示 ACALL DISPLAY MOV A,KEY ;取键值 CJNE A,#02H,SHOW_012345_J1 MOV KEY,#00H SJMP SHOW_ABCDEF_START ;子功能1下KEY=2,转至显示ABCDEF SHOW_012345_J1: CJNE A,#04H,SHOW_012345 ;子功能1下KEY不为2和4,循环 MOV KEY,#00H SJMP MAIN1_START ;子功能1下KEY=4,转入时钟模块 MAIN0_J0: CJNE A,#02H,MAIN0_J1 MOV KEY,#00H ;主菜单下KEY=2,显示ABCDEF SHOW_ABCDEF_START: ;显示初始化 MOV CLOSE_BIT_CODE,#00H ;屏蔽所有段选 MOV CLOSE_BIT,#0FFH ;无位选屏蔽 ACALL BUF_ABCDEF ;显示缓冲区缓存ABCDEF SHOW_ABCDEF: MOV A,FLAG_1S ;取1S取反标志 CJNE A,#01H,SHOW_ABCDEF_J0 CPL A ;1S已经到(FLAG_1S=1) INC A MOV FLAG_1S,A ;1S取反标志取反,等待下一秒 SETB C MOV A,CLOSE_BIT_CODE RLC A MOV CLOSE_BIT_CODE,A ;已到1S,依次开段选 SHOW_ABCDEF_J0: ;1S未到(FLAG_1S=0),继续显示 ACALL DISPLAY MOV A,KEY ;取键值 CJNE A,#01H,SHOW_ABCDEF_J1 MOV KEY,#00H SJMP SHOW_012345_START ;子功能2下KEY=1,转至显示012345 SHOW_ABCDEF_J1: CJNE A,#04H,SHOW_ABCDEF ;子功能2下KEY不为1和4,循环 MOV KEY,#00H SJMP MAIN1_START ;子功能2下KEY=4,转入时钟模块 MAIN0_J1: CJNE A,#04H,MAIN0 MOV KEY,#00H ;主菜单下KEY=4,转入时钟模块 SJMP MAIN1_START ;---------------------------------------------------------------------------------------------- ;缓存012345 BUF_012345: MOV A,#DISPBUF ADD A,#5 MOV R0,A MOV R1,#6 MOV A,#0 BUF_012345_LOOP: MOV @R0,A DEC R0 INC A DJNZ R1,BUF_012345_LOOP RET ;---------------------------------------------------------------------------------------------- ;缓存ABCDEF BUF_ABCDEF: MOV A,#DISPBUF ADD A,#5 MOV R0,A MOV R1,#6 MOV A,#0AH BUF_ABCDEF_LOOP: MOV @R0,A DEC R0 INC A DJNZ R1,BUF_ABCDEF_LOOP RET ;---------------------------------------------------------------------------------------------- ;功能模块2,电子时钟 MAIN1_START: MOV FLAG,#00H MOV CLOSE_BIT_CODE,#0FFH MOV CLOSE_BIT,#0FFH MOV FLAG_CLOSE,#00H MOV FLAG_DOWN_S,#0FFH MAIN1: LCALL ALARM LCALL ALARM_TIME LCALL BUF_TIME LCALL DISPLAY MOV A,KEY ;读键值 CJNE A,#01H,MAIN1_NOT_KEY1 ;KEY=1,转入功能1,否则继续判断 MOV KEY,#00H SJMP FUN1_START MAIN1_NOT_KEY1: MOV A,KEY CJNE A,#02H,MAIN1_NOT_KEY2 ;KEY=2,转入功能2,否则继续判断 MOV KEY,#00H SJMP FUN2_START MAIN1_NOT_KEY2: MOV A,KEY CJNE A,#03H,MAIN1_NOT_KEY3 ;KEY=3,转入功能3,否则继续判断 MOV KEY,#00H AJMP FUN3_START MAIN1_NOT_KEY3: MOV A,KEY CJNE A,#04H,MAIN1_NOT_KEY4 ;KEY=4,转入功能4,否则停留在当前界面 MOV KEY,#00H AJMP FUN4_START MAIN1_NOT_KEY4: SJMP MAIN1 ;---------------------------------------------------------------------------------------------- ;功能1界面:显示日期(YYMMDD),按1键退出 FUN1_START: MOV FLAG,#07H ;FLAG=7,显示YYMMDD FUN1: LCALL BUF_TIME ;缓冲处理 LCALL DISPLAY ;执行显示 MOV A,KEY ;读键值 CJNE A,#01H,FUN1_EXIT ;KEY=1,返回主菜单,否则停留在当前界面 MOV KEY,#00H MOV FLAG,#00H SJMP MAIN1_START FUN1_EXIT: SJMP FUN1 ;---------------------------------------------------------------------------------------------- ;功能2界面:时间、日期动态切换,按1键切换模式(模式1显示时间->年->月日;模式2流水显示),按2键退出 FUN2_START: ;初始化,默认进入模式1 MOV FLAG,#00H MOV FLAG_ADD,#00H MOV FLAG_CLOSE,#00H MOV DATE_STYLE,#01H MOV FLAG_MOVING,#00H MOV COUNT_1S,#06H MOV COUNT_50MS,#00H MOV FLAG_DOWN_S,#0FFH MOV FLAG_1S,#01H FUN2: MOV A,DATE_STYLE ;读显示模式标志 CJNE A,#01H,FUN2_STYLE2 ;DATE_STYLE=01H,模式1处理,否则继续判断 MOV FLAG_MOVING,#00H ;流水显示初始化标志置0 LCALL FUN2_DATE_STYLE1 ;调用模式1显示函数 SJMP FUN2_KEY FUN2_STYLE2: CJNE A,#02H,FUN2_STYLE3 ;DATE_STYLE=02H,模式2处理,进入模式3处理 LCALL FUN2_DATE_STYLE2 SJMP FUN2_KEY FUN2_STYLE3: MOV FLAG_MOVING,#00H ;流水显示初始化标志置0 LCALL FUN2_DATE_STYLE3 ;调用模式3处理 FUN2_KEY: MOV A,KEY ;取键值 CJNE A,#02H,FUN2_J0 ;KEY=2,返回主菜单,否则继续判断 MOV KEY,#00H AJMP MAIN1_START FUN2_J0: CJNE A,#01H,FUN2_J1 ;KEY=1切换显示模式,否则停留在主界面 MOV KEY,#00H ;切换显示模式后标志初始化处理 WAIT_TIME: MOV P0,#00H MOV A,COUNT CJNE A,#00H,WAIT_TIME ;切换显示后等待COUNT类标志同步计时开始 MOV FLAG_1S,#01H MOV COUNT_1S,#06H MOV COUNT_50MS,#00H MOV FLAG,#02H MOV FLAG_DOWN_S,#0FFH INC DATE_STYLE MOV A,DATE_STYLE CJNE A,#04H,FUN2_J1 MOV DATE_STYLE,#01H ;DATE_STYLE复位 FUN2_J1: SJMP FUN2 ;---------------------------------------------------------------------------------------------- ;界面子函数1:日期显示动态显示模式1处理 FUN2_DATE_STYLE1: MOV A,COUNT_1S CLR C SUBB A,#06H JC FUN2_DATE_STYLE1_J0 ;COUNT_1S>=6,刚进入模式1显示,默认显示时分秒,否则跳过 MOV COUNT_1S,#00H FUN2_DATE_STYLE1_J0: MOV A,COUNT_1S ;取COUNT_1S CJNE A,#00H,FUN2_DATE_STYLE1_J1 ;COUNT_1S=00H,时间显示(4秒),否则继续判断 MOV FLAG,#01H ;FLAG为1或者2、3,显示时分秒 SJMP FUN2_DATE_STYLE1_EXIT FUN2_DATE_STYLE1_J1: CJNE A,#04H,FUN2_DATE_STYLE1_J2 ;COUNT_1S=04H,年显示(1秒),否则继续判断 MOV FLAG,#04H ;标志为4,显示年份 SJMP FUN2_DATE_STYLE1_EXIT FUN2_DATE_STYLE1_J2: CJNE A,#05H,FUN2_DATE_STYLE1_EXIT ;COUNT_1S=5,月份天数显示(1秒),否则返回 MOV FLAG,#05H ;标志为5或6,显示月份天数 FUN2_DATE_STYLE1_EXIT: LCALL BUF_TIME ;显示缓冲区准备 LCALL DISPLAY ;显示缓冲区内容 RET ;---------------------------------------------------------------------------------------------- ;界面子函数2:日期显示动态显示模式2处理 FUN2_DATE_STYLE2: MOV A,COUNT_1S CLR C SUBB A,#6 JNC FUN2_DATE_STYLE2_MOVING ;COUNT_1S<6,正常模式显示(持续6秒) MOV FLAG,#00H ;正常显示模式 MOV FLAG_1S,#01H ;重置1秒反转标志 LCALL BUF_TIME ;正常显示时间,调用时钟缓冲区处理函数 SJMP FUN2_DATE_STYLE2_J0 FUN2_DATE_STYLE2_MOVING: ;COUNT_1S>=6,流水显示 MOV A,FLAG_1S CJNE A,#01H,FUN2_DATE_STYLE2_J0;FLAG_1s=1,1S到,更新缓冲区,否则跳过 CPL A ;每隔1秒更新缓冲区(FLAG_1S=01H执行) INC A MOV FLAG_1S,A ;反转FLAG_1S,等待下一秒再调用 MOV FLAG,#0BH ;按键中断程序无需处理缓冲区 ACALL BUF_TEMPBUF ;临时缓冲区处理 ACALL BUF_TEMPBUF_TO_DISPBUF ;流水显示缓冲区处理 FUN2_DATE_STYLE2_J0: ACALL DISPLAY ;执行显示 RET ;---------------------------------------------------------------------------------------------- ;界面子函数3:日期显示动态显示模式3处理 FUN2_DATE_STYLE3: MOV A,COUNT_1S CLR C SUBB A,#4 JC FUN2_DATE_STYLE3_NEXT ;COUNT_1S>=4秒复位 MOV COUNT_1S,#00H ;COUNT_1S复位,等待下一个4s MOV FLAG_DOWN_S,#00H ;准备进入下滑显示第一阶段 MOV COUNT_50MS,#00H INC FLAG INC FLAG ;每4s切换显示信息(年->月日->时间) MOV A,FLAG CJNE A,#8,FUN2_DATE_STYLE3_NEXT ;FLAG复位处理 MOV FLAG,#02H FUN2_DATE_STYLE3_NEXT: MOV A,COUNT_50MS CJNE A,#04H,FUN2_DATE_STYLE3_EXIT ;每0.2s下移状态标志+1,否则跳过 MOV COUNT_50MS,#00H ;COUNT_50MS=0,等待下一0.2s INC FLAG_DOWN_S FUN2_DATE_STYLE3_EXIT: LCALL BUF_TIME ;缓冲 LCALL DISPLAY ;显示 RET ;---------------------------------------------------------------------------------------------- ;功能3界面:设置时间、日期,按1所选时段加1,按2切换时段(时->分->秒->年->月->日),按3返回主界面 FUN3_START: ;初始化 MOV FLAG,#01H ;默认显示时间(小时段闪烁) MOV FLAG_CLOSE,#01H ;开闪烁 MOV FLAG_ADD,#00H MOV KEY,#00H MOV COUNT_50MS,#00H FUN3: LCALL BUF_TIME LCALL DISPLAY MOV A,KEY ;取键值 CJNE A,#02H,FUN3_NOT_KEY1 ;KEY=2,切换时段,否则继续判断 MOV KEY,#00H INC FLAG ;标志+1 MOV A,FLAG CJNE A,#07H,FUN3_J0 ;时段标志过6后赋1 MOV FLAG,#01H FUN3_J0: SJMP FUN3 FUN3_NOT_KEY1: CJNE A,#01H,FUN3_NOT_KEY2 ;KEY=1,调时间(加1方式),否则继续判断 MOV KEY,#00H MOV FLAG_ADD,#01H ;时段进位标志=1 ACALL FORMAT_TIME ;调用格式调整函数,更新数据 SJMP FUN3 FUN3_NOT_KEY2: CJNE A,#03H,FUN3_NOT_KEY3 ;KEY=3,返回主界面,否则继续判断 MOV KEY,#00H MOV FLAG,#00H AJMP MAIN1_START FUN3_NOT_KEY3: CJNE A,#04H,FUN3 ;KEY=4,调时间(减1方式),否则停留在当前界面 MOV KEY,#00H MOV FLAG_ADD,#0FFH ;时段进位标志=-1补码 ACALL FORMAT_TIME ;调用格式调整函数,更新数据 SJMP FUN3 ;---------------------------------------------------------------------------------------------- ;功能4界面:设置闹铃,按1设置,按2切换指向开关状态、小时、分钟,按3切换闹铃,按4返回主菜单 FUN4_START: ;初始化 MOV FLAG,#08H ;默认显示开关状态 MOV FLAG_NUM,#01H ;默认显示第一个闹钟 MOV FLAG_CLOSE,#01H ;开闪烁 MOV KEY,#00H FUN4: ACALL BUF_ALARM ;缓存闹铃数据 ACALL DISPLAY ;执行显示 FUN4_J0: MOV A,KEY ;读键值 CJNE A,#02H,FUN4_J1 ;KEY=2,切换闹铃开关、小时、分钟,否则继续判断 MOV KEY,#00H INC FLAG ;时段标志+1 MOV A,FLAG CJNE A,#0BH,FUN4_J0_J0 ;时段标志过10后赋8(8为闹铃开关,9为闹铃小时,10为分钟) MOV FLAG,#08H FUN4_J0_J0: SJMP FUN4 FUN4_J1: CJNE A,#01H,FUN4_J2 ;KEY=1,调闹铃,否则继续判断 MOV KEY,#00H INC FLAG_ADD ;调整标志+1 ACALL FORMAT_ALARM ;调用处理程序 SJMP FUN4 FUN4_J2: CJNE A,#03H,FUN4_J3 ;KEY=3,切换闹钟,否则继续判断 MOV KEY,#00H INC FLAG_NUM ;闹铃号+1 MOV A,FLAG_NUM CJNE A,#04H,FUN4_J2_J0 ;闹铃号满4后置1 MOV FLAG_NUM,#01H FUN4_J2_J0: MOV FLAG,#08H ;切换闹铃后FLAG=8,显示当前闹钟状态 SJMP FUN4 FUN4_J3: CJNE A,#04H,FUN4_CONTINUE ;KEY=4,返回主菜单,否则停留当前界面 MOV KEY,#00H MOV FLAG,#00H AJMP MAIN1_START FUN4_CONTINUE: SJMP FUN4 ;---------------------------------------------------------------------------------------------- ;数据处理函数1:时间数据处理,FLAG_ADD为1时为自增调整时间,0FFH时为自减调整时间,0时为自动计时 FORMAT_TIME: MOV A,FLAG_ADD ;取时间设置标志 CJNE A,#00H,FORMAT_TIME_ADD_REFORM ;FLAG_ADD=0,否则进入时钟模式格式调整,否则进入设置模式格式调整(无进位) AJMP FORMAT_TIME_SECOND_REFORM FORMAT_TIME_ADD_REFORM: MOV A,FLAG ;取时段标志 CJNE A,#01H,FORMAT_TIME_NOT_HOUR ;FLAG=1,当前时段为小时,否则继续判断 MOV A,FLAG_ADD ADD A,HOUR CJNE A,#0FFH,FORMAT_TIME_NO_RESET_J0 ;小时减时复位处理 MOV A,#23 FORMAT_TIME_NO_RESET_J0: MOV HOUR,A ;小时设置 CLR C SUBB A,#24 JC FORMAT_TIME_J0 ;小时增时复位处理 MOV HOUR,#00H FORMAT_TIME_J0: AJMP FORMAT_TIME_DONE ;调整完毕,返回 FORMAT_TIME_NOT_HOUR: CJNE A,#02H,FORMAT_TIME_NOT_MINUTE ;FLAG=2当前时段为分钟,否则继续判断 MOV A,FLAG_ADD ADD A,MINUTE ;分钟设置 CJNE A,#0FFH,FORMAT_TIME_NO_RESET_J1 ;分钟减时复位处理 MOV A,#59 FORMAT_TIME_NO_RESET_J1: MOV MINUTE,A CLR C SUBB A,#60 JC FORMAT_TIME_J1 ;分钟增时复位处理 MOV MINUTE,#00H FORMAT_TIME_J1: AJMP FORMAT_TIME_DONE ;调整完毕,返回 FORMAT_TIME_NOT_MINUTE: CJNE A,#03H,FORMAT_TIME_NOT_SECOND ;FLAG=3,当前时段为秒,否则继续判断 MOV A,FLAG_ADD ADD A,SECOND ;秒设置 CJNE A,#0FFH,FORMAT_TIME_NO_RESET_J2 ;分钟减时复位处理 MOV A,#59 FORMAT_TIME_NO_RESET_J2: MOV SECOND,A CLR ET0 ;关计数器中断 MOV TL0,#0B8H MOV TH0,#3CH ;重置计数值 MOV COUNT,#00H SETB ET0 ;开计数器中断,设置秒后重新计秒 CLR C SUBB A,#60 JC FORMAT_TIME_J2 ;秒增时复位处理 MOV SECOND,#00H FORMAT_TIME_J2: AJMP FORMAT_TIME_DONE FORMAT_TIME_NOT_SECOND: CJNE A,#04H,FORMAT_TIME_NOT_YEAR ;FLAG=4,当前时段为年,否则继续判断 MOV A,FLAG_ADD ADD A,YEAR_L CJNE A,#0FFH,FORMAT_TIME_NO_RESET_J3 ;年份减时复位设置 MOV A,#99 DEC YEAR_H ;年高位借1 FORMAT_TIME_NO_RESET_J3: MOV YEAR_L,A MOV A,YEAR_H CJNE A,#0FFH,FORMAT_TIME_YEAR_REFORM ;年高位减时复位设置 MOV YEAR_H,#99 SJMP FORMAT_TIME_YEAR_REFORM ;跳至时钟模式下年复位调整 FORMAT_TIME_NOT_YEAR: CJNE A,#05H,FORMAT_TIME_NOT_MONTH ;FLAG=5,当前时段为月,否则继续判断 MOV A,FLAG_ADD ADD A,MONTH ;月份设置 CJNE A,#00H,FORMAT_TIME_NO_RESET_J4 ;月份减时复位设置 MOV A,#12 FORMAT_TIME_NO_RESET_J4: MOV MONTH,A ACALL FORMAT_YEAR ;调用闰年判断函数,月份改变后天数复位处理 MOV A,MONTH CLR C SUBB A,#13 JC FORMAT_TIME_DONE ;月份增时复位处理 MOV MONTH,#01H SJMP FORMAT_TIME_DONE FORMAT_TIME_NOT_MONTH: ;FLAG=6,当前时段为天数 MOV A,FLAG_ADD ADD A,DAY MOV DAY,A ACALL FORMAT_YEAR ;调用闰年判断函数,处理了天数调整 SJMP FORMAT_TIME_DONE FORMAT_TIME_SECOND_REFORM: MOV A,SECOND ;由最小位秒位开始调整 CJNE A,#60,FORMAT_TIME_DONE ;分无进位,调整结束 MOV SECOND,#00H ;秒复位 INC MINUTE ;分进位 MOV FLAG_KEEP,#00H ;每过1分钟关蜂鸣器(闹铃响1分钟) SETB P2.0 MOV A,MINUTE CJNE A,#60,FORMAT_TIME_DONE ;时无进位,调整结束 MOV MINUTE,#00H ;分复位 INC HOUR ;时进位 MOV A,FLAG_ADD CJNE A,#00H,FORMAT_NO_ALARM_HOUR ;设置时间状态下跳过整点报时检测 ACALL ALARM_HOUR FORMAT_NO_ALARM_HOUR: MOV A,HOUR CJNE A,#24,FORMAT_TIME_DONE ;天数无进位,调整结束 MOV HOUR,#00H ;时复位 INC DAY ;天数进位 ACALL FORMAT_YEAR ;调用闰年判断函数,处理了天数调整 MOV A,MONTH CJNE A,#13,FORMAT_TIME_DONE ;年低位无进位,调整结束 MOV MONTH,#01H ;月复位 INC YEAR_L FORMAT_TIME_YEAR_REFORM: MOV A,YEAR_L CJNE A,#100,FORMAT_TIME_DONE ;年高位无进位,调整结束 MOV YEAR_L,#00H ;年低位满100复位,向高位进1 INC YEAR_H ;高位进1 MOV A,YEAR_H CJNE A,#100,FORMAT_TIME_DONE ;高位 MOV YEAR_H,#00H ;年高位满100复位 FORMAT_TIME_DONE: MOV A,FLAG_ADD ;取时间设置标志 CJNE A,#00H,FORMAT_TIME_ADJUST ;FLAG=0,直接返回,否则执行设置后显示参数处理 SJMP FORMAT_TIME_EXIT FORMAT_TIME_ADJUST: MOV FLAG_ADD,#00H ;执行设置后,设置标志归0 MOV COUNT_50MS,#00H ;闪烁重新计秒 MOV FLAG_CLOSE,#01H ;调时间后处于相关位处于显示状态 FORMAT_TIME_EXIT: RET ;---------------------------------------------------------------------------------------------- ;数据处理1子函数:闰年判断以及月份、天数调整处理 FORMAT_YEAR: MOV A,MONTH CJNE A,#02H,DAY30_OR_DAY31 ;MONTH不为2,判断是30天还是31天 MOV A,YEAR_L ;取年低位 CJNE A,#00H,DIV4 ;不能被100整除话判断是否能被4整除 MOV A,YEAR_H ;能被100整除判断能否被400整除 MOV B,#4 DIV AB MOV A,B ;年高位除4 CJNE A,#00H,NOT_LEAP_YEAR SJMP LEAP_YEAR ;能被400整除,为闰年 DIV4: MOV A,YEAR_L MOV B,#4 DIV AB MOV A,B CJNE A,#00H,NOT_LEAP_YEAR ;被4整除? LEAP_YEAR: ;能被4整除,为闰年 MOV A,DAY CJNE A,#00H,FORMAT_YEAR_NO_RESET_J0 MOV DAY,#29 ;天数减时复位设置 FORMAT_YEAR_NO_RESET_J0: CLR C SUBB A,#30 ;闰年下,满30天天数复位 JC FORMAT_YEAR_EXIT SJMP FORMAT_YEAR_ADD_MONTH NOT_LEAP_YEAR: MOV A,DAY CJNE A,#00H,FORMAT_YEAR_NO_RESET_J1 MOV DAY,#28 ;天数减时复位设置 FORMAT_YEAR_NO_RESET_J1: CLR C SUBB A,#29 ;平年下,满29天天数复位 JC FORMAT_YEAR_EXIT SJMP FORMAT_YEAR_ADD_MONTH DAY30_OR_DAY31: MOV A,MONTH CJNE A,#4,NOT_M4 SJMP DAY30 ;为4月,有30天 NOT_M4: CJNE A,#6,NOT_M6 SJMP DAY30 ;为6月,有30天 NOT_M6: CJNE A,#9,NOT_M9 SJMP DAY30 ;为9月,有30天 NOT_M9: CJNE A,#11,DAY31 SJMP DAY30 ;为11月,有30天 DAY31: ;余下可能为31天 MOV A,DAY CJNE A,#00H,FORMAT_YEAR_NO_RESET_J2 MOV DAY,#31 ;天数减时复位设置 FORMAT_YEAR_NO_RESET_J2: CLR C SUBB A,#32 JC FORMAT_YEAR_EXIT ;大于31天,需要进位处理,否则返回 SJMP FORMAT_YEAR_ADD_MONTH DAY30: MOV A,DAY CJNE A,#00H,FORMAT_YEAR_NO_RESET_J3 MOV DAY,#30 ;天数减时复位设置 FORMAT_YEAR_NO_RESET_J3: CLR C SUBB A,#31 JC FORMAT_YEAR_EXIT ;大于30天,需要进位处理,否则返回 FORMAT_YEAR_ADD_MONTH: MOV DAY,#01H ;天数复位1 INC MONTH ;月进位 MOV A,FLAG_ADD ;取FLAG_ADD CJNE A,#00H,FORMAT_YEAR_ADJUST ;FLAG_ADD=0,返回,否则在设置日期时候产生天数归位不应产生月份进位,MONTH减1 SJMP FORMAT_YEAR_EXIT FORMAT_YEAR_ADJUST: DEC MONTH FORMAT_YEAR_EXIT: RET ;---------------------------------------------------------------------------------------------- ;数据处理函数2:闹铃数据处理 FORMAT_ALARM: MOV A,FLAG_ADD ;取设置标志 CJNE A,#00H,FORMAT_ALARM_NEXT ;FLAG_ADD=0,无修改,跳出,否则进入闹铃设置处理 SJMP FORMAT_ALARM_EXIT FORMAT_ALARM_NEXT: MOV R0,#A1_SWITCH MOV A,FLAG_NUM ;取当前闹钟标志 DEC A MOV B,#03H MUL AB MOV B,R0 ADD A,B MOV R0,A ;R0指向当前闹钟开关地址 MOV A,FLAG ;取FLAG CJNE A,#08H,FORMAT_ALARM_NOT_SWITCH MOV A,@R0 ;FLAG=8,修改开关状态 CPL ACC.0 ;开关状态取反 MOV @R0,A ;修改开关状态 SJMP FORMAT_ALARM_EXIT FORMAT_ALARM_NOT_SWITCH: CJNE A,#09H,FORMAT_ALARM_NOT_HOUR DEC R0 ;FLAG=9,修改小时,R0减1,指向小时地址 INC @R0 ;小时+1 MOV A,@R0 CJNE A,#24,FORMAT_ALARM_EXIT ;满24归0判断 MOV @R0,#00H SJMP FORMAT_ALARM_EXIT FORMAT_ALARM_NOT_HOUR: DEC R0 DEC R0 ;FLAG=10,修改分钟,R0减2,指向分钟地址 INC @R0 ;分钟+1 MOV A,@R0 CJNE A,#60,FORMAT_ALARM_EXIT ;满60归0判断 MOV @R0,#00H FORMAT_ALARM_EXIT: MOV A,FLAG_ADD ;取FLAG_ADD CJNE A,#01H,FORMAT_ALARM_J0 DEC FLAG_ADD ;FLAG_ADD=1,有修改,修改完成FLAG_ADD减1 MOV COUNT_50MS,#00H MOV FLAG_CLOSE,#01H ;修改后相应位处于显示状态 FORMAT_ALARM_J0: RET ;---------------------------------------------------------------------------------------------- ;缓冲区处理函数1:显示缓冲区存时间信息,0=<FLAG<=3,送时分秒,FLAG=4,送年份,5=<FLAG<=6,送月份天数,FLAG=7,送年低位月份天数 ;R1指向缓冲区起始地址,R0指向数据区起始地址,转移数据长度由FLAG判断,由R2储存 BUF_TIME: MOV R4,#00H ;日期显示标志 MOV A,#DISPBUF ADD A,#5 MOV R1,A ;R1存缓冲区最高位地址 MOV A,FLAG ;取标志 CJNE A,#07H,BUF_TIME_NOT_FUN1 ;FLAG=7,显示年低位、月份、天数,否则继续判断 MOV CLOSE_BIT,0FFH MOV A,#YEAR_L MOV R0,A ;R0指向年低位 MOV R2,#03H ;存6位 SJMP BUF_TIME_START ;开始搬运数据 BUF_TIME_NOT_FUN1: CJNE A,#04H,BUF_TIME_NOT_YEAR ;FLAG=4,显示年,否则继续判断 MOV CLOSE_BIT,#9FH ;10011111 MOV A,#YEAR_H ;FLAG=4,显示年份 MOV R0,A ;R0指向年高位 MOV R2,#02H ;存4位 SJMP BUF_TIME_START BUF_TIME_NOT_YEAR: CJNE A,#04H,BUF_TIME_J0 BUF_TIME_J0 : JNC BUF_TIME_MONTH_AND_DAY ;FLAG<=4,显示时间,否则显示天数 MOV CLOSE_BIT,0FFH ;显示时间时无屏蔽位 MOV A,#HOUR MOV R0,A ;R0指向时间 MOV R2,#03H ;存6位 SJMP BUF_TIME_START BUF_TIME_MONTH_AND_DAY: ;FLAG>4,显示月份、天数 MOV CLOSE_BIT,#0BFH ;10111111,屏蔽第6位 MOV A,#MONTH MOV R0,A ;R0指向日期 MOV R4,#01H ;R4=1 MOV R2,#02H ;存4位 BUF_TIME_START: ;数据处理,每次循环处理2位 MOV A,R4 ;取R4值 CJNE A,#01H,BUF_TIME_NO_ADD ;R4=1,月份和天数间加"-",否则不用加 MOV A,R2 ;取R2 CJNE A,#01H,BUF_TIME_NO_ADD ;R2=1,存完月份后加"-" MOV @R1,#11H DEC R1 BUF_TIME_NO_ADD: MOV A,@R0 MOV B,#10 DIV AB MOV @R1,A DEC R1 MOV A,B MOV @R1,A DEC R1 DEC R0 DJNZ R2,BUF_TIME_START ;R2=0,处理完毕,返回 RET ;---------------------------------------------------------------------------------------------- ;缓冲区处理函数2:显示缓冲区存闹铃信息 BUF_ALARM: MOV A,#DISPBUF ADD A,#05H MOV R1,A ;R1指向缓冲区高地址 MOV R0,#A1_SWITCH MOV A,FLAG_NUM ;FLAG_NUM为偏移量 DEC A MOV B,#03H ;每个闹铃长度为3 MUL AB MOV B,R0 ADD A,B MOV R0,A ;R0指向新闹铃开关地址 MOV A,FLAG_NUM MOV @R1,#00H MOV @R1,A ;先存闹铃号 DEC R1 DEC R1 ;R1指向缓冲区第3位 MOV A,FLAG ;取标志 CJNE A,#08H,BUF_ALARM_NOT_SWITCH ;FLAG=8,存开关状态,否则存闹铃时间 MOV @R1,#00H ;FLAG=8,存开关状态 DEC R1 ;第3位存0 MOV A,@R0 ;取状态 CJNE A,#00H,BUF_ALARM_SWITCH_ON ;A=0,开关为关,否则开关为开 MOV CLOSE_BIT,#0BBH ;10111011,开关为关时应屏蔽位 SJMP BUF_ALARM_J0 BUF_ALARM_SWITCH_ON: MOV CLOSE_BIT,#9BH ;10011011,开关为开时应屏蔽位 BUF_ALARM_J0: MOV DPTR,#SWITCH_TAB ;取开关显示状态表,存字符 MOV B,#02H MUL AB MOV R0,A MOVC A,@A+DPTR MOV @R1,A DEC R1 INC R0 MOV A,R0 MOVC A,@A+DPTR MOV @R1,A SJMP BUF_ALARM_EXIT BUF_ALARM_NOT_SWITCH: MOV CLOSE_BIT,#0FBH ;FLAG=9和10,屏蔽第2位 MOV R2,#02H ;循环2次,存小时和分钟 INC R1 ;R1指向缓冲区第2位 BUF_ALARM_LOOP: DEC R0 DEC R1 MOV A,@R0 MOV B,#10 DIV AB MOV @R1,A DEC R1 MOV @R1,B DJNZ R2,BUF_ALARM_LOOP BUF_ALARM_EXIT: RET ;---------------------------------------------------------------------------------------------- ;缓冲区处理函数3:显示缓冲区内容左移,最高位内容丢弃,最低位内容存在R6 BUF_DISPBUF_LEFT: MOV A,#DISPBUF ADD A,#5 MOV R1,A ;R1指向显示缓冲区高位地址 MOV R0,A DEC R0 ;R2指向上一位地址 MOV R3,#5 BUF_DISPBUF_LEFT_LOOP: MOV A,@R0 MOV @R1,A DEC R1 DEC R0 DJNZ R3,BUF_DISPBUF_LEFT_LOOP ;循环,将低位内容移向高位 MOV A,R6 MOV R0,A MOV A,@R0 MOV @R1,A ;R6内容存于最低位 RET ;---------------------------------------------------------------------------------------------- ;缓冲区处理函数4:临时缓冲区存时间信息(YYYY-MM-DD HHMMSS) BUF_TEMPBUF: MOV A,#MOVING_DISPBUF ADD A,#12H MOV R1,A ;R1指向临时缓冲区高地址 MOV @R1,#12H ;第一位无内容 DEC R1 MOV R0,#YEAR_H ;R0指向年高位地址 MOV R2,#2 BUF_TEMPBUF_L1: MOV A,@R0 ;L1循环存年 MOV B,#10 DIV AB MOV @R1,A DEC R1 MOV @R1,B DEC R1 DEC R0 DJNZ R2,BUF_TEMPBUF_L1 MOV R2,#2 BUF_TEMPBUF_L2: MOV @R1,#11H ;存横杆-,L2循环存月份,天数 DEC R1 MOV A,@R0 MOV B,#10 DIV AB MOV @R1,A DEC R1 MOV @R1,B DEC R1 DEC R0 DJNZ R2,BUF_TEMPBUF_L2 MOV @R1,#12H DEC R1 MOV @R1,#12H DEC R1 MOV R2,#3 BUF_TEMPBUF_L3: MOV A,@R0 ;L3循环存时分秒 MOV B,#10 DIV AB MOV @R1,A DEC R1 MOV @R1,B DEC R1 DEC R0 DJNZ R2,BUF_TEMPBUF_L3 RET ;---------------------------------------------------------------------------------------------- ;缓冲区处理函数5:临时缓冲区内容转至显示缓冲区(日期流水显示模式) BUF_TEMPBUF_TO_DISPBUF: MOV A,FLAG_MOVING ;读初始化标志 CJNE A,#00H,BUF_TEMPBUF_TO_DISPBUF_START;判断初始化标志,FLAG_MOVING=00H进行初始化 MOV FLAG,#00H MOV A,#MOVING_DISPBUF ADD A,#12H MOV R6,A ;R6存缓冲区1高地址 MOV CLOSE_BIT_TEMP,#00H MOV R5,#00H MOV FLAG_MOVING,#01H ;初始化完成,FLAG_MOVING置1 BUF_TEMPBUF_TO_DISPBUF_START: ACALL BUF_DISPBUF_LEFT ;临时缓冲区内容左移入显示缓冲区 DEC R6 INC R5 MOV A,R5 SETB C MOV A,CLOSE_BIT_TEMP RRC A MOV CLOSE_BIT_TEMP,A ;屏蔽位处理(由左至右逐渐开显示) RRC A ORL A,#10000001B MOV CLOSE_BIT,A ;规范化处理,赋给CLOSE_BIT MOV A,R5 ;取流水移动位数 CJNE A,#19,BUF_TEMPBUF_TO_DISPBUF_EXIT MOV FLAG_MOVING,#00H ;流水显示完毕,秒计数置0 MOV COUNT_1S,#00H ;流水显示完毕,秒计数复位 MOV CLOSE_BIT,#81H ;流水显示完毕,屏蔽位复位 BUF_TEMPBUF_TO_DISPBUF_EXIT: RET ;---------------------------------------------------------------------------------------------- ;显示处理函数:显示显示缓冲区内容,根据FLAG判断是否闪烁以及屏蔽位显示,DISPBIT存当前显示位,先取段码后取位码 DISPLAY: MOV A,P2 ANL A,#81H MOV P2,A ;防残影 MOV A,#DISPBUF ADD A,#05H ;指向缓冲区高位地址 CLR C SUBB A,DISPBIT ;位偏移量 MOV R0,A ;段偏移存储 MOV A,FLAG_DOWN_S ;取下滑显示标志FLAG_DOWN_S,FLAG_DOWN_S>=20无下滑处理 CJNE A,#0,DISPLAY_NEXT0 SJMP DISPLAY_NEXT1 DISPLAY_NEXT0: CJNE A,#1,DISPLAY_NOT_STATE1 MOV DPTR,#TABLE_STATE1 SJMP DISPLAY_CODE DISPLAY_NOT_STATE1: CJNE A,#2,DISPLAY_NOT_STATE2 MOV DPTR,#TABLE_STATE2 SJMP DISPLAY_CODE DISPLAY_NOT_STATE2: CJNE A,#18,DISPLAY_NOT_STATE3 MOV DPTR,#TABLE_STATE3 SJMP DISPLAY_CODE DISPLAY_NOT_STATE3: CJNE A,#19,DISPLAY_NORMAL_STATE MOV DPTR,#TABLE_STATE4 SJMP DISPLAY_CODE DISPLAY_NORMAL_STATE: MOV DPTR,#TABLE ;指向字形表 DISPLAY_CODE: MOV A,@R0 ;段偏移量 MOVC A,@A+DPTR ;取字形 ANL A,CLOSE_BIT_CODE ;屏蔽处理 DISPLAY_NEXT1: MOV P0,A ;字形选P1口显示 MOV A,DISPBIT ;位移偏量 MOV DPTR,#TAB ;指向位选 MOVC A,@A+DPTR ;取位选通 MOV R0,A MOV A,FLAG ;取标志FLAG CJNE A,#00H,DISPLAY_J0 ;FLAG=0,无需闪烁和屏蔽位,否则继续判断 SJMP DISPLAY_NO_FLASH ;跳过闪烁处理 DISPLAY_J0: CJNE A,#07H,DISPLAY_J1 ;FLAG=7,无需闪烁和屏蔽位,否则进入闪烁模式判断 SJMP DISPLAY_NO_FLASH ;跳过闪烁处理 DISPLAY_J1: MOV A,FLAG_CLOSE ;取FLAG_CLOSE(每1秒取反,非闪烁模式置0) CJNE A,#00H,DISPLAY_FLASH ;FLAG_CLOSE=0,不闪烁,否则进入闪烁处理 SJMP DISPLAY_NO_FLASH ;跳过闪烁处理 DISPLAY_FLASH: CJNE A,#01H,DISPLAY_CLOSE ;FLAG_CLOSE=1,正常显示,否则屏蔽相应位,实现闪烁 SJMP DISPLAY_NO_FLASH ;为1,跳过屏蔽输出处理 DISPLAY_CLOSE: MOV A,FLAG CJNE A,#01,DISPLAY_NOT_HOUR ;FLAG=1,指示小时,关闭12位,否则继续判断 SJMP CLOSE12 DISPLAY_NOT_HOUR: CJNE A,#02,DISPLAY_NOT_MINUTE ;FLAG=2,指示分钟,关闭34位,否则继续判断 SJMP CLOSE34 DISPLAY_NOT_MINUTE: CJNE A,#03,DISPLAY_NOT_SECOND ;FLAG=3,指示分钟,关闭56位,否则继续判断 SJMP CLOSE56 DISPLAY_NOT_SECOND: CJNE A,#04,DISPLAY_NOT_YEAR ;FLAG=4,指示年份,关闭1234位,否则继续判断 SJMP CLOSE1234 DISPLAY_NOT_YEAR: CJNE A,#05,DISPLAY_NOT_MONTH ;FLAG=5,指示月份,关闭12位,否则继续判断 SJMP CLOSE12 DISPLAY_NOT_MONTH: CJNE A,#06,DISPLAY_ALARM ;FLAG=6,指示天数,关闭45位,否则继续判断 SJMP CLOSE45 DISPLAY_ALARM: CJNE A,#08,DISPLAY_ALARM_HOUR ;FLAG=8,指示闹钟开关,关345位,否则继续判断 SJMP CLOSE345 DISPLAY_ALARM_HOUR: CJNE A,#09,DISPLAY_ALARM_MINUTE;FLAG=9,指示闹钟小时,关34位,否则继续判断 SJMP CLOSE34 DISPLAY_ALARM_MINUTE: SJMP CLOSE56 ;FLAG=10,指示闹钟分钟,关56位 CLOSE12: ANL CLOSE_BIT,#0F9H ;CLOSE_BIT&11111001,P2.1=P2.2=0 SJMP DISPLAY_NO_FLASH CLOSE34: ANL CLOSE_BIT,#0E7H ;CLOSE_BIT&11100111,P2.3=P2.4=0 SJMP DISPLAY_NO_FLASH CLOSE45: ANL CLOSE_BIT,#0CFH ;CLOSE_BIT&11001111,P2.4=P2.5=0 SJMP DISPLAY_NO_FLASH CLOSE56: ANL CLOSE_BIT,#9FH ;CLOSE_BIT&10011111,P2.5=P2.6=0 SJMP DISPLAY_NO_FLASH CLOSE1234: ANL CLOSE_BIT,#0E1H ;CLOSE_BIT&11100001,P2.1=P2.2=P2.3=P2.4=P2.5=P2.6=0 SJMP DISPLAY_NO_FLASH CLOSE345: ANL CLOSE_BIT,#0C7H ;11000111 SJMP DISPLAY_NO_FLASH CLOSE3456: ANL CLOSE_BIT,#87H ;10000111 DISPLAY_NO_FLASH: MOV A,P2 ;取P2口状态 ORL A,R0 ;输出位相与,输出不影响原P2.0状态 ANL A,CLOSE_BIT ;位屏蔽处理 MOV P2,A ;位选通送P2输出 INC DISPBIT ;位偏移量+1 MOV A,DISPBIT ;取位偏移量 CJNE A,#06H,DISPLAY_EXIT ;DISPBIT=6,复位,否则返回 MOV DISPBIT,#00H DISPLAY_EXIT: RET TABLE: DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH,77H,7CH,39H,5EH,79H,71H,0B7H,40H,00H TAB: DB 02H,04H,08H,10H,20H,40H ;选通输出表 SWITCH_TAB: DB 0FH,0FH,10H,10H TABLE_STATE1:DB 81H,80H,81H,81H,80H,81H,81H,80H,81H,81H,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,80H TABLE_STATE2:DB 0E2H,82H,0E0H,0C3H,82H,0C3H,0E3H,82H,0E3H,0C3H,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,81H TABLE_STATE3:DB 0D4H,84H,0CCH,0CCH,9CH,0D8H,0D8H,0C4H,0DCH,0DCH,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,88H TABLE_STATE4:DB 88H,80H,88H,88H,80H,88H,88H,88H,88H,88H,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,0FFH,80H ;---------------------------------------------------------------------------------------------- ;闹铃处理函数:闹铃、整点报时,响1秒,停1秒,R7存鸣报次数 ALARM: MOV A,FLAG_KEEP ;取闹钟持续鸣叫标志 CJNE A,#01,ALARM_NO_KEEP ;FLAG_KEEP=1,响铃处理,否则跳至整点报时 MOV A,COUNT MOV B,#10 DIV AB CJNE A,#01H,ALARM_L ;每0.5s改变P2.0电位,实现间断闹铃声 CLR P2.0 SJMP ALARM_EXIT ALARM_L: SETB P2.0 SJMP ALARM_EXIT ;返回,响闹铃时不处理整点报时 ALARM_NO_KEEP: MOV A,R7 ;取整点报时时间 CJNE A,#00,ALARM_J0 ;R7=0,无整点报时,返回,否则进入整点报时处理 SJMP ALARM_EXIT ALARM_J0: MOV A,FLAG_1S ;取1S取反标志 CJNE A,#01H,ALARM_J1 MOV FLAG_ALARM,#01H ;FLAG_1S=1,FLAG_ALARM置1 CLR P2.0 ;响蜂鸣器 SJMP ALARM_EXIT ;结束 ALARM_J1: SETB P2.0 ;FLAG_1S=0,关蜂鸣器 MOV A,FLAG_ALARM ;取FLAG_ALARM CJNE A,#01H,ALARM_EXIT DEC R7 ;FLAG_ALARM=1,为蜂鸣器开到蜂鸣器关转换时刻,已响1次,鸣报次数减1 MOV FLAG_ALARM,#00H ;FLAG_ALARM=0,蜂鸣器关状态 ALARM_EXIT: RET ;----------------------------------------------------------------------------------------------;闹铃处理子函数1:整点报时检测 ALARM_HOUR: MOV A,FLAG_ADD ;判断时间设置标志 CJNE A,#00H,ALARM_HOUR_EXIT ;FLAG_ADD=0,非设置时间模式,检测整点报时,否则返回 MOV FLAG_1S,#01H ;开1秒取反标志 MOV A,HOUR ;鸣铃次数处理(小时数转为12进制处理) MOV B,#12 DIV AB MOV R7,B MOV A,R7 ;鸣铃次数存于R6 CJNE A,#00H,ALARM_HOUR_EXIT MOV R7,#12 ;HOUR能被12整除,应鸣12次 ALARM_HOUR_EXIT: RET ;---------------------------------------------------------------------------------------------- ;闹铃处理子函数2:闹铃检测 ALARM_TIME: MOV R2,#03H ;循环条件,检测3个闹钟 MOV R1,#A1_SWITCH ;指向第一个闹钟开关状态地址 ALARM_TIME_LOOP: MOV A,R2 DEC A MOV B,#03 MUL AB ADD A,R1 MOV R0,A ;偏移量处理,指向下一个开关状态地址 MOV A,@R0 CJNE A,#01H,ALARM_TIME_NO_MATCH;开关状态判断,为0当前闹铃可跳过判断 DEC R0 MOV A,@R0 ;去闹铃小时 CLR C SUBB A,HOUR CJNE A,#00H,ALARM_TIME_NO_MATCH;判断小时是否符合 DEC R0 MOV A,@R0 CLR C SUBB A,MINUTE CJNE A,#00H,ALARM_TIME_NO_MATCH;判断分钟是否符合 MOV A,SECOND CJNE A,#00H,ALARM_TIME_NO_MATCH;判断秒是否为0,为0说明时间刚到,响蜂鸣器 MOV FLAG_KEEP,#01H ;FLAG_KEEP=1,一直响蜂鸣器 ; CLR P2.0 ;P2.0输出低电平,蜂鸣器发声 SJMP ALARM_TIME_EXIT ALARM_TIME_NO_MATCH: DJNZ R2,ALARM_TIME_LOOP ALARM_TIME_EXIT: RET ;---------------------------------------------------------------------------------------------- ;子功能函数:延时10ms DELAY10MS: MOV R1,#10 DELAY10MS_LOOP: MOV R2,#248 DJNZ R2,$ DJNZ R1,DELAY10MS_LOOP RET ;---------------------------------------------------------------------------------------------- ;INT_1中断处理函数:按键判断以及功能1显示处理,串口发送键值,KEY收取键值 INT_1: PUSH ACC ;保护现场 PUSH PSW MOV TEMP_R0,R0 MOV TEMP_R1,R1 MOV TEMP_R2,R2 ;保护 CLR EX1 ;关INT1中断 ; CLR P2.0 ;按下按键有响声 CLR P1.0 ;按键按下时LED灯亮 LCALL DISPLAY ;进入中断执行一次显示函数,优化显示效果 MOV KEY_TEMP,#00H ;默认键值为#00H ACALL DELAY10MS ;延时10ms,去抖动 JB P3.3,NOT_KEY ;P3.3若为高电平,无输入,跳出 SETB P3.4 ;P3.4置高电平 JNB P3.3,NOT_KEY1 ;P3.3电平判断 MOV KEY_TEMP,#01H ;P3.3电平被拉高,按键在P3.4上,KEY=1 MOV P3,#0FH SJMP WAIT NOT_KEY1: ;按键2判断,判断方式同上 SETB P3.5 JNB P3.3,NOT_KEY2 MOV KEY_TEMP,#02H MOV P3,#0FH SJMP WAIT NOT_KEY2: ;按键3判断,判断方式同上 SETB P3.6 JNB P3.3,NOT_KEY3 MOV KEY_TEMP,#03H MOV P3,#0FH SJMP WAIT NOT_KEY3: ;按键4 MOV KEY_TEMP,#04H MOV P3,#0FH WAIT: ;等待按键松开时继续调用显示函数 MOV A,FLAG ;判断FLAG,缓存闹铃还是时间信息 CJNE A,#0BH,INT_1_BUF SJMP INT_1_DISPLAY ;FLAG=11,不做缓冲处理 INT_1_BUF: CLR C SUBB A,#08H JC INT_1_TIME LCALL BUF_ALARM ;FLAG>7,缓存闹铃信息 SJMP INT_1_DISPLAY INT_1_TIME: LCALL BUF_TIME ;FLAG<7,缓冲时间信息 INT_1_DISPLAY: LCALL DISPLAY ;执行显示 JNB P3.3,WAIT ;等待按键松开(P3.3恢复高电平) NOT_KEY: MOV P3,#0FFH ;P3口复位,发送准备 MOV SBUF,KEY_TEMP ;发送键值KEY_TEMP JNB TI,$ ;等待发送完成 CLR TI ;TI清0 CLR RI ;RI清0,丢弃第一次结果,准备重新接收 MOV SBUF,KEY_TEMP ;重发键值KEY_TEMP JNB TI,$ ;等待发送完成 CLR TI ;TI清0 JNB RI,NO_RECEIVE MOV KEY,SBUF ;接收完成,接受缓冲区内容送KEY CLR RI ;RI清0 MOV A,KEY_TEMP CJNE A,KEY,NO_RECEIVE ;KEY与发送KEY_TEMP对比,不等话舍弃 SJMP NEXT NO_RECEIVE: MOV KEY,#00H ;接收不完成,KEY=0 NEXT: MOV P3,#0FH ;P3口恢复键盘监控状态 MOV FLAG_KEEP,#00H ;有按键按下,关闹铃 MOV R7,#00H ;有按键按下,关整点报时 SETB P2.0 ;关蜂鸣器 SETB P1.0 ;按键松开后关LED灯 LCALL DISPLAY ;中断返回前调用一次显示函数,优化显示效果 MOV R0,TEMP_R0 MOV R1,TEMP_R1 MOV R2,TEMP_R2 POP PSW POP ACC ;恢复现场 SETB EX1 ;开INT1中断 RETI ;---------------------------------------------------------------------------------------------- ;计数器T0中断处理函数:50ms调用一次,修正量取5(响应中断)+3(2个PUSH命令、1个MOV命令)=8机器周期,8us INT_T0: PUSH ACC PUSH PSW MOV TL0,#0B8H MOV TH0,#3CH ;重置计数值 INC COUNT ;记录调用次数 INC COUNT_50MS MOV A,COUNT_50MS ;取调用次数COUNT_50MS,COUNT_50MS可由其他函数复位,实现相对定时 CJNE A,#10,INT_T0_NEXT ;COUNT_50MS=10,满10次,0.5秒到,否则跳过 MOV A,FLAG_CLOSE ;满0.5秒FLAG_CLOSE取反操作 CPL A INC A MOV FLAG_CLOSE,A MOV COUNT_50MS,#00H INT_T0_NEXT: MOV A,COUNT ;取调用次数COUNT,COUNT为计数器内部使用 CJNE A,#20,INT_T0_EXIT ;COUNT=20,1秒到,COUNT清0,否则返回 MOV COUNT,#00H INC SECOND ;秒+1 INC COUNT_1S ;1S统计标志+1,由其他函数复位,实现相对定时 MOV A,FLAG_1S ;1S取反标志取反 CPL A INC A MOV FLAG_1S,A PUSH FLAG_ADD ;保护FLAG_ADD MOV FLAG_ADD,#00H ;FLAG_ADD=00H LCALL FORMAT_TIME ;时钟模式下调用数据处理 POP FLAG_ADD INT_T0_EXIT: POP PSW POP ACC ;恢复现场 RETI END
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值