/*------------------------------------------------------------------------------------------------------- 作 者:黄 建 雄 日 期:2016.04.07 实验平台:清翔电子 QX - X3平台 功 能:一、STC89C51MCU模拟I2C时序,具体应用器件为AT24C02型EEPROM芯片,LCD显示,由按键存入值并选择显示 二、实时时钟,LCD显示,通过按键可调节参数 使用说明:1、KEY11按下,则写入数据1到24C02,同时写入次数计数器加1 2、KEY15按下,则写入数据0到24C02,同时写入次数计数器加1 3、KEY7按下,则从24C02中读出上一次写入的全部数据,并在LCD上按写入顺序显示 4、KEY6用做实时时钟功能选择: KEY5按下1次,调整日参数;2次,调月;3次,调年; 4次,调分;5次,调时;6次,关光标及其闪烁 5、KEY10用做数值累加:每按一下,当前值加1 6、KEY14用做数值递减:每按一下,当前值减1 7、KEY8按下则关闭LCD显示,同时清0与24C02操作有关的写入次数计数器、写地址存储器、读地址存储器 注意事项:(1)按键增减功能在BST-V51平台有效,但在QX-X3平台有错误 原因分析:LCD引脚与按键冲突; 理由:LCD的RS/RW/LCDEN的接线与矩阵键盘前3排按键接口引脚相同,故而LCD写数据出错。 (2)在初次打开LCD显示之前,要先进行一次24C02写入操作,否则会出现乱码 原因在于此时读写的24C02地址为不确定值 -------------------------------------------------------------------------------------------------------*/ ;-----------引脚声明-------------------------------- SDA EQU P2^0 ; 数据线 SCL EQU P2^1 ; 时钟线 T_CLK BIT P2.5 ; 定义DS1302时钟线引脚 T_IO BIT P2.4 ; 定义DS1302数据线引脚 T_RST BIT P2.3 ; 定义DS1302复位线引脚 RS EQU P3.5 ; LCD引脚声明 RW EQU P3.6 E EQU P3.4 LCD EQU P0 ; P0为数据端 BF EQU P0.7 ;--------------------------------------------------- ;-----------位声明---------------------------------- ACC0 BIT ACC^0 ACC1 BIT ACC^1 ACC2 BIT ACC^2 ACC3 BIT ACC^3 ACC4 BIT ACC^4 ACC5 BIT ACC^5 ACC6 BIT ACC^6 ACC7 BIT ACC^7 ADDR_W EQU 20H // 写操作时器件片内地址 ADDR_R EQU 21H // 读操作时器件片内地址 WR_CNT EQU 22H // 写入字节计数 SECOND EQU 60H ; 时间存储区域首地址 SelFuc EQU 23H ; 功能选择键按下标志 POS EQU 24H ; 用于光标定位 ;--------------------------------------------------- ORG 0000H LJMP BEGIN ORG 0100H ;--------------------------------------------------- BEGIN: MOV ADDR_W, #0 ; 写入次数计数及读写操作地址初始化为0 MOV ADDR_R, #0 MOV WR_CNT, #0 MOV P0, #0FFH ; 位选全关 SETB P2.7 CLR P2.7 MOV SP, #30H ; 重新设置堆栈指针 MOV POS, #95H ; 95H为光标的第一次闪烁位置 MOV A, #00H MOV R0, #60H MOV R2, #32 CLR_BUF: MOV @R0, A ; 片内首地址为60H的32个RAM显示缓冲单元清0 INC R0 DJNZ R2, CLR_BUF MOV 60H, #00H ; 时钟初始值 这里末尾加H只是为了便于后续显示处理,并不用做16进制值 MOV 61H, #59H MOV 62H, #23H MOV 63H, #04H MOV 64H, #04H MOV 65H, #01H MOV 66H, #16H MOV 67H, #20H LCALL SET1302 ; 调用设置DS1302的子程序 MOV DPTR, #200 LCALL WTMS // 调用延时子程序 延时时间:200 / 2 * 100us = 10ms ;--------------------------------------------------------- LOOP: NOP LCALL GET1302 ; 获取时钟数据 // MOV A, #01H ; 清屏指令 // LCALL W_CMD ; 写入命令 01H LCALL WRTIME ; 调用向LCD写入时间子程序 ;--------------------------------------------------------- CIRCUR: MOV A, #98H LCALL W_CMD MOV R1, #80H MOV R2, #0 CIRCUR1:MOV A, @R1 ADD A, #30H LCALL W_DATA INC R1 INC R2 MOV A, R2 CJNE A, WR_CNT, CIRCUR1 // MOV A, #98H ; 若一轮显示完毕,则重设地址指针,下一次又从初始位置开始显示 // LCALL W_CMD ;------------------------------------------------------ MOV A, POS ; 光标定位 ACALL W_CMD MOV DPTR, #200 ; DPTR赋值为800 这个时间短一些也是可以的 MOV A, #1 ; A赋值为1 这是时间刷新的频率参数 LCALL WTSEC ; 调用延时程序 LOOP2: LJMP KEY2 ;------------------------------------------------------ /* 单片机写数据到24C02 */ WRITE: LCALL START ; 产生起始信号 MOV A, #0A0H ; A0H为写入时的寻址字节 1010 000 0 分别表示 器件地址、引脚地址、写操作 LCALL SEND ; 发送寻址字节 LCALL ACKI ; 接收应答 JC WRITEEND ; 从器件未响应,跳转至结束 MOV A, ADDR_W ; 设置从器件片内地址 LCALL SEND ; 发送片内地址 LCALL ACKI ; 接收应答 JC WRITEEND ; 从器件不响应,跳转至结束 MOV R2, #4 ; 设置发送字节数 MOV R1, #55H ; 主器件中要发送数据的首地址 OUT1: MOV A, @R1 ; 要发送的数据送A LCALL SEND ; 发送数据 LCALL ACKI ; 调用接收应答位子程序 JC WRITEEND ; 从器件不响应,转结束 // INC R1 ; 地址增1 // DJNZ R2, OUT1 ; 若发送未结束,则继续发送,否则住下执行 WRITEEND: LCALL STOP ; 产生终止信号 INC ADDR_W RET ; 子程序返回 ;--------------------------------------------------- /* 单片机读出24C02中的数据 */ READ: LCALL START ; 产生起始信号S MOV A, #0A0H ; A0H为写入时的寻址字节 LCALL SEND ; 发送寻址字节 LCALL ACKI ; 接收应答 JC OUTEND ; 无应答,转结束 MOV A, ADDR_R ; 设置要读取从器件的片内地址 LCALL SEND ; 发送片内单元地址 LCALL ACKI ; 调用接收应答位子程序 JC OUTEND ; 无应答,转结束 LCALL START ; 再次发出起始信号(这条语句绝对不能少,否则程序就会出错) ORL A, #0A1H ; 设置寻址字节,准备接收 LCALL SEND ; 发送寻址字节 LCALL ACKI ; 接收应答 JC OUTEND ; 无应答,转结束 MOV R2, WR_CNT ; 将要接收数据的字节数 MOV R1, #80H ; 接收字节存放在单片机片内RAM的60H IN0: LCALL REV ; 接收一个字节 LCALL ACK ; 单片机向24C02发送应答信号 MOV @R1, A ; 把接收到的数据存入片内RAM单元中 INC R1 ; 片内RAM单元指针增1 DEC R2 ; 接收数据的字节数减1 CJNE R2, #1, IN0 ; 判断是否只剩下最后一个接收字节,否,跳转到IN0,继续接收 LCALL REV ; 接收最后一个字节 LCALL NACK ; 发送非应答位(主器件发送终止信号前应发送非应答 ; 信号,向从器件表明读操作将要结束) MOV @R1, A ; 把接收到的数据存入到存储单元中 OUTEND: LCALL STOP ; 主器件发出终止信号P INC ADDR_R RET ; 接收一个字节子程序 REV: MOV R0, #08H REV0: SETB SCL ; 拉高时钟线 LCALL DELAY4 ; 延时4us以上 MOV C, SDA ; 读取SDA线的状态 RLC A ; 累加器左环移 CLR SCL ; 接收完1位就拉低时钟线 LCALL DELAY4 // 这里为何还要延时4us ? 保证足够的时间让从器件改变SDA线的状态 DJNZ R0, REV0 ; 判断是否接收完1个字节 RET ; 发送一个字节子程序 SEND: MOV R0, #08H CLR SCL ; 拉低时钟线,接下来进行数据线上数据的改变 SEND0: RLC A ; 带进位循环左移 ACC.7移入进位位Cy, Cy移入ACC.0 MOV SDA, C ; 把进位位值赋给SDA,即为将要传送的数据 SETB SCL ; SCL拉高 LCALL DELAY4 ; 延时4us以上 CLR SCL ; 数据传送完毕,拉低时钟线 DJNZ R0, SEND0 ; 判断是否传完8位,未完则继续传送 SETB SDA ; 1个字节传送完毕后拉高SDA线,以便接收方接下来送出低电平的应答信号A RET ; 接收应答位子程序 ACKI: SETB SCL ; 拉高时钟线 ACALL DELAY4 ; 延时4us以上 MOV C, SDA ; 读取SDA线的状态 此时应为从器件发出的低电平应答信号 CLR SCL ; 读取完毕后拉低时钟线 RET /*-----------------各信号输出子程序--------------------*/ /* 发送起始信号S子程序 */ START: SETB SDA ; SDA = 1 SETB SCL ; SCL = 1 ACALL DELAY47 ; 延时4.7us以上 CLR SDA ; SDA = 0 ACALL DELAY4 ; 延时4us以上 CLR SCL ; SCL = 0 NOP RET /* 发送终止信号P子程序 */ STOP: CLR SDA ; SDA = 0 SETB SCL ; SCL = 1 ACALL DELAY4 ; 延时4us以上 SETB SDA ; SDA = 1 ACALL DELAY47 ; 延时4.7us以上 CLR SCL ; SCL = 0 NOP RET /* 发送应答位“0”子程序 */ ACK: CLR SDA ; SDA = 0 SETB SCL ; SCL = 1 ACALL DELAY4 ; 延时4us以上 CLR SCL ; SCL = 0 NOP SETB SDA ; SDA = 1 RET /* 发送非应答位“1”子程序 */ NACK: SETB SDA ; SDA = 1 SETB SCL ; SCL = 1 ACALL DELAY4 ; 延时4us以上 CLR SCL ; SCL = 0 CLR SDA ; SDA = 0 RET ;--------------------------------------------------------- /* 延时子程序,时间 > 4.7us */ DELAY47:NOP NOP NOP NOP RET /* 延时子程序,时间 > 4us */ DELAY4: NOP NOP NOP RET ;--------------------------------------------------------- ;--------------------------------------------------------------- LCD_INIT: MOV A, #01H ; 清屏指令 ACALL W_CMD ; 写入命令01H MOV A, #38H ; 功能设置指令 ACALL W_CMD ; 写入命令38H MOV A, #0CH ; 显示控制指令:开显示,不显示光标 ACALL W_CMD ; 写入命令0CH MOV A, #14H ; 光标移位指令 ACALL W_CMD ; 写入命令14H MOV A, #80H ; 12864第1行首地址:80H ACALL W_CMD ; 写入命令80H RET ;-------------------------------------------------------------------- ;--------------------------------------------------------------------- WRTIME: MOV A, #80H ; LCD第一行显示定位为06H,即86H = 80 + 06H ACALL W_CMD WRSTR1: CLR A ; 清空累加器 MOV A, SECOND + 2 ; 把时钟数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA ; LCD写数据 小时低位 MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, SECOND + 1 ; 把分钟数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA MOV A, #2DH ; 字符“-” ACALL W_DATA MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, SECOND ; 把秒钟数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA ; 秒钟低位 MOV A, #90H ; 第二行定位 ACALL W_CMD MOV A, SECOND + 7 ; 把年份数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA MOV A, SECOND + 6 ; 把年份数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA MOV A, #2DH ; 字符“-” ACALL W_DATA MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, SECOND + 4 ; 把月份数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA MOV A, #2DH ; 字符“-” ACALL W_DATA MOV A, #2DH ; 显示字符“-” ACALL W_DATA MOV A, SECOND + 3 ; 把日期数据存储空间的地址赋给A MOV B, #10H DIV AB ADD A, #30H ACALL W_DATA ; LCD写数据 小时高位 MOV A, B ADD A, #30H ACALL W_DATA WRSTR2: RET ;------------------------------------------------------------ WTSEC: PUSH ACC ; 等待(A)秒 LCALL WTMS ; 延时40ms POP ACC DEC A JNZ WTSEC ; 累加器不为0则转移 RET ;------------------------------------------------------------ ; 延时等待(DPTR)毫秒 WTMS: XRL DPL, #0FFH ; 按位异或 XRL DPH, #0FFH INC DPTR WTMS1: MOV TL0, #09CH MOV TH0, #0FFH ; 计数周期为100,12M时钟下为100us MOV TMOD, #1 ; 定时器T0方式1 SETB TR0 ; 定时器T0运行 WTMS2: JNB TF0, WTMS2 ; 判TF0的状态,TF0 = 0即未溢出时,则原地跳转等待 CLR TR0 ; TF0 = 1, 定时器T0停止 CLR TF0 ; 清TF0 INC DPTR MOV A, DPL ORL A, DPH ; DPTR加到65535后,再加就会为0,即DPH = DPL = 0,此时便可退出循环 JNZ WTMS1 ; 累加器不为0则转移 RET /*------------------------------------------------------------------------------ ; 调 用:RTINPUTBYTE ; 入口参数:初始时间在:SECOND, MINUTE, HOUR, DAY, MONTH, WEEK, YEARL(地址连续) ;------------------------------------------------------------------------------*/ SET1302:CLR T_RST ; 设置DS1302初始时间,并启动计时。RST = 0 CLR T_CLK ; SCLK = 0(RST置为1时,SCLK必须为0) SETB T_RST ; RST = 1 MOV B, #8EH ; 写保护寄存器写入命令8EH LCALL RTINPUTBYTE ; 调用向DS1302写入一字节子程序 MOV B, #00H ; 写操作前WP = 0 LCALL RTINPUTBYTE ; 调用向DS1302写入一字节子程序 SETB T_CLK CLR T_RST MOV R0, #SECOND ; 秒送入R0 MOV R7, #7 ; 秒、分、时、日、月、星期、年 MOV R1, #80H ; 80H为秒寄存器“写”命令 S13021: CLR T_RST CLR T_CLK SETB T_RST MOV B, R1 ; 写秒寄存器写入命令 LCALL RTINPUTBYTE ; 向1302写入命令字 MOV A, @R0 ; 写入秒数据 MOV B, A LCALL RTINPUTBYTE ; 向1302写入一字节 INC R0 ; 存储地址增1 INC R1 INC R1 ; R1内容增2,即分寄存器“写”命令82H,后面依次为时、日...寄存器“写”命令 SETB T_CLK CLR T_RST DJNZ R7, S13021 ; 循环,直到7个寄存器全部被初始化 /* 写保护配置 */ CLR T_RST ; RST置0 CLR T_CLK ; SCLK置0 SETB T_RST ; RST置1 MOV B, #8EH ; 写保护寄存器“写”命令 LCALL RTINPUTBYTE ; 写入一字节 MOV B, #80H ; 控制, WP = 1, 写保护 LCALL RTINPUTBYTE ; 写入一字节 SETB T_CLK CLR T_RST RET ;-------------------------------------------------------------------- GET1302:MOV R0, #SECOND ; 从DS1302读时间/日历子程序,时间/日历保存在: ; SECOND, MINUTE, HOUR, DAY, MONTH, WEEK, YEARL中 MOV R7, #7 MOV R1, #81H ; 秒寄存器的“读”命令81H G13021: CLR T_RST CLR T_CLK SETB T_RST MOV B, R1 LCALL RTINPUTBYTE ; 先写 命令字 LCALL RTOUTPUTBYTE ; 再读出相应地址存储的数据 MOV @R0, A ; 取出数据放在R0指向的地址 INC R0 ; 存储地址值增1 INC R1 INC R1 ; R1内容加2,即81H + 2 = 83H(分寄存器读命令) SETB T_CLK CLR T_RST DJNZ R7, G13021 ; 未读完7个寄存器,继续 RET RTINPUTBYTE: ; 串行写入DS1302一字节子程序 MOV R4, #8 ; R4用做计数器 INBIT1: MOV A, B ; 串行移入8位数据 RRC A ; A的内容带进位位循环右移 MOV B, A ; 这里B用做数据中间值保存 MOV T_IO, C ; 数据通过DS1302的I/O脚串行移入DS1302 SETB T_CLK ; 拉高时钟 CLR T_CLK ; 拉低时钟 手册上规定时钟高电电平最长保持时间1000ns = 1us,CLR指令刚好1us DJNZ R4, INBIT1 ; 8次移位未完成,则跳INBIT1处继续 RET ; 子程序返回 RTOUTPUTBYTE: ; 从DS1302串行输出一字节子程序 MOV R4, #8 ; 计数器 OUTBIT1:MOV C, T_IO ; DS1302的I/O脚串行移出数据 RRC A ; A的内容带进位位循环右移 SETB T_CLK ; 拉高时钟 CLR T_CLK ; 拉低时钟 DJNZ R4, OUTBIT1 ; 8次移位未完成,则跳INBIT1处继续 RET ; 子程序返回 /***************************************************************************/ KEY2: LCALL KS ;键盘检测 JNZ K1 ;累加器不为0表可能有键按下,跳至K1再次检测 LCALL D10MS ;书上写了要去陡,但好像这里去不去陡没太大关系,不去也能行 AJMP LOOP ;无键按下返回重新检测 K1: LCALL D10MS ;去陡,再次检测 LCALL KS JNZ K2 AJMP LOOP ;无键按下返回重新检测 K2: MOV R2, #0FEH ;每次只有一列电平置低,即每次只扫描一列 MOV R4, #00H ;列号,下同 K3: MOV P3, R2 ;开始扫描 L0: JB P3.4, L1 ;端口值为高则说明此列无键按下,跳到下一端口检测程序 MOV A, #00H ;行首键号送累加器 AJMP LK L1: JB P3.5, L2 MOV A, #04H AJMP LK L2: JB P3.6, L3 MOV A, #08H AJMP LK L3: JB P3.7, NEXT2 ;端口值为高则说明此列无键按下,准备扫描下一列 MOV A, #0CH LK: ADD A, R4 ;行的首键号+列号算出键值 PUSH ACC ;因后面还要用到ACC,所以读出键值后需要把ACC入栈保存 NEXT2: INC R4 MOV A, R2 JNB ACC.3, K4 ;判是否扫描完,完则跳转到松手检测 RL A ;左环移,扫描下一列应送的值 MOV R2, A AJMP K3 ;未扫描完跳至K3再次扫描 K4: LCALL KS ;键盘检测,判松手 JNZ K4 ;未松开则原地等待 POP ACC ;累加器出栈 LJMP KCHECK ;出栈后跳至健值处理程序 KS: MOV P3, #0FH ;键盘检测,有键按下非0 MOV A, P3 XRL A, #0FH ;异或指令,判有无键按下,聪明呀! RET KCHECK: CJNE A, #6, KCHECK1 ; 若KEY11按下,写数据1,且写入次数计数加1 MOV 55H, #1 LCALL WRITE INC WR_CNT LJMP DISPLAY KCHECK1:CJNE A, #10, CLEAR ; 若KEY15按下,写数据0,写入次数计数同样加1 MOV 55H, #0 LCALL WRITE INC WR_CNT LJMP DISPLAY CLEAR: CJNE A, #3, KCHECK2 ; 若KEY8按下,关显示,同时3个计数值清0 MOV A, #08H ; LCD显示关闭命令 LCALL W_CMD MOV WR_CNT, #0 ; 24C02写计数清0 MOV ADDR_W, #0 ; 写地址存储器清0 MOV ADDR_R, #0 ; 读地址存储器清0 LJMP DISPLAY KCHECK2:CJNE A, #2, FUNCTION ; 若KEY7按下,读24C02数据,同时初始化LCD,开显示 LCALL READ LCALL LCD_INIT ; LCD初始化 LJMP DISPLAY FUNCTION: CJNE A, #1, INCREASE ; 若KEY6按下,则SelFuc加1 INC SelFuc MOV A, SelFuc // 把KEY6按下次数值赋给A CJNE A, #1, CASE1 MOV A, #0FH ; 若KEY6第1次按下,则设置显示光标、光标闪烁 LCALL W_CMD MOV POS, #0X95 ; 设置光标位置 LJMP DISPLAY CASE1: CJNE A, #2, CASE2 ; 若KEY6第2次按下,则设置第二次光标显示位置,以下类推 MOV POS, #0X93 LJMP DISPLAY CASE2: CJNE A, #3, CASE3 MOV POS, #0X91 LJMP DISPLAY CASE3: CJNE A, #4, CASE4 MOV POS, #0X82 LJMP DISPLAY CASE4: CJNE A, #5, CASE5 MOV POS, #0X80 LJMP DISPLAY CASE5: CJNE A, #6, DISPLAY MOV A, #0CH ; 若KEY6第6次按下,则关闭光标 LCALL W_CMD MOV SelFuc, #0 ; 同时SelFuc计数值清0 LJMP DISPLAY ;---------------------------------------------------------------- INCREASE: CJNE A, #5, DECREASE ; 若KEY10按下,且SelFuc不为0,则光标对应参数值加1 MOV A, SelFuc CJNE A, #1, INC1 ; 若SelFuc为1,则日参数加1 INC 63H LJMP SETINC MOV A, 63H // 这里是参数边界处理 CJNE A, #30, INC2 ADD A, #33 MOV 63H, A INC1: CJNE A, #2, INC2 ; 若SelFuc为2,则月参数加1,下同 INC 64H LJMP SETINC INC2: CJNE A, #3, INC3 INC 66H LJMP SETINC INC3: CJNE A, #4, INC4 INC 61H LJMP SETINC INC4: CJNE A, #5, DISPLAY INC 62H SETINC: LCALL SET1302 ; 把更改后的参数写入1302 LJMP DISPLAY ;----------------------------------------------------------------- DECREASE: CJNE A, #9, DISPLAY ; 若KEY14按下,且SelFuc不为0,则光标对应参数值减1 MOV A, SelFuc // 把KEY6按下次数值赋给A CJNE A, #1, DEC1 ; 若SelFuc为1,则日参数减1 DEC 63H LJMP SETDEC DEC1: CJNE A, #2, DEC2 ; 若SelFuc为2,则月参数减1,下同 DEC 64H LJMP SETDEC DEC2: CJNE A, #3, DEC3 DEC 66H LJMP SETDEC DEC3: CJNE A, #4, DEC4 DEC 61H LJMP SETDEC DEC4: CJNE A, #5, DISPLAY DEC 62H SETDEC: LCALL SET1302 ; 把更改后的参数写入1302 ;---------------------------------------------------------------- DISPLAY: LJMP LOOP ; 重新回到主循环 D10MS: MOV R7, #25 ;延时10MS D1: MOV R6, #200 DJNZ R6, $ ;美元符号表示原地跳转 DJNZ R7, D1 RET ;----------------------------------------------------------- ; WAIT子程序用于检测1602是否处于忙状态,直到1602空闲时才退出 WAIT: MOV LCD, #0FFH CLR RS SETB RW CLR E NOP SETB E JB BF, WAIT RET ; 写入命令子程序,入口参数A中存储了向1602写入的命令 W_CMD: ACALL WAIT MOV LCD, A CLR RS ; RS与RW清0 CLR RW SETB E ; 令E端产生下降沿,命令写入1602 NOP CLR E RET ; 写入数据子程序,入口参数A中存储了向1602写入的数据 W_DATA: ACALL WAIT MOV LCD, A SETB RS ; 将RS置 1 CLR RW ; 将RW清 0 SETB E ; 令E端产生下降沿,数据写入1602 NOP CLR E RET ;------------------------------------------------------------ END ; 结束