目标
此汇编程序实现DS3231 实时时钟 (RTC),并在 LCD1602 显示年/月/日/星期/时:分:秒。
为了更流畅,本程序只在第一次运行及当时间为00:00:00时才更新 年/月/日/星期,以减低显示屏的工作量,避免秒数不连贯。
网上 DS3231 程序多是用C++ 写成的,本程序反而用汇编语言写成的,一来看看汇编语言是否比较稳定,LCD1602 会不会出现闪烁现象; 二来充实一下STC32G12K128 单片机汇编语言程式库。
I2C 串口通讯简介
DS3231使用 I2C 串口通讯与MCU连接,I2C的好处是线路要求十分简单,只需两条电线。I2C 可以串联多个设备,不会因为增加设备而需要添加 GPIO 端口的数目。
STC32G12K128 单片机内部集成了一个 I2C 串行总线控制器。I2C 是一种高速同步通讯总线, 通讯使用 SCL (串行时钟线) 和 SDA (串行数据线) 两线进行同步通讯。STC32G12K128单片机的I2C总线提供了两种操作模式:主机模式 (SCL 为输出口, 发送同步时钟信号) 和 从机模式 (SCL 为输入口, 接收同步时钟信号), 本程序使用主机模式 (Master State)。
线路图:

执行结果:

设定 I2C 功能脚位

P_SW2 地址为 0BAH
I2C_S = 00, SCL 脚位对应P1.5, SDA 脚位对应P1.4。
设定 I2C 总线速度
DS3231 支援总线速度最高为 400Kbps。
I2C 配置寄存器 Configuration Register (I2CCFG)
地址 7EFE80H

ENI2C = 1 允许I2C 功能
MSSL = 1 主机模式
MSSPEED = 13 (二进制1101)
I2C 总线速度 = SYSCLK / 2 / (MSSPEED * 2 + 4) = 24 MHz / 2 / (13 * 2 + 4 )
= 24000 KHz / 60 = 400 Kbps
由于 I2CCFG 是扩展特殊功能寄存器,逻辑地址位于XDATA 区域内,访问前需要将P_SW2寄存器的最高位 (EAXFR) 置1,然后使用 MOV @DRk, Rm 和MOV Rm, @DRk 指令进行访问,例如:

MOV A, #0CDH ;R11 即是 ACC
MOV WR6, #WORD0 I2CCFG
MOV WR4, #WORD2 I2CCFG
MOV @DR4, R11 ; MOV I2CCFG, #0CDH 使能I2C 主机模式, 总线速度 400K
; 受STC32G12K128 寻址方式限制, MOV @DR4, R11 不可以写成 MOV @DR4, A。
I2C Master State Control Register 主机控制寄存器 (I2CMSCR)
地址 7EFE81H

EMSI = 1 允许主机模式的中断
I2C 总线数据通讯协定

MSCMD 主机命令
-
Idle (待机), 无动作。
-
Start (起始命令)

- Send data (发起数据命令)

- Recv ACK (接收 ACK 命令)

- RECV (接收数据命令)

5.Send ACK (发送ACK 命令)

- Stop (停止命令)

I2C Master State Status Register主机状态寄存器 (I2CMSST)

地址 7EFE82H
MSIF (Master State Interrupt Flag 主机模式中断标志位) 当 I2C 主机完成任务后,MSIF 自动为1,向CPU请求中断,MSIF 位必须软件清0。
MSACKO (Master State Acknowledgement Output) -- 发送ACK 命令之后,控制器读取此位的数据当作ACK发送到SDA。
DS3231 寄存器
I2C 地址 68H
|----|-----|---------------|---------------------------------|
| 地址 | 寄存器 | 范围 | 备注 |
| 00 | 秒 | 00-59 | bit 7 不用, 常是0 |
| 01 | 分 | 00-59 | bit 7不用, 常是0 |
| 02 | 时 | 01-12 或 00-23 | bit 6: 1=12小 时模式, bit 5: 1 = PM |
| 03 | 星期 | 1-7 | 1=星期一, 7 = 星期日 |
| 04 | 日 | 01-31 | |
| 05 | 月 | 01-12 | bit 7 世纪位 |
| 06 | 年 | 00-99 | |
校准时钟
先计算,偏移量 =
÷0.1
有效值为 -128 至 +127, 即每天可以调快或调慢1秒左右。
将此偏移量写入Aging offset 寄存器内(地址10H),如果时钟走慢了, 取负值; 走快了, 取正值。
例如:DS3231 二天快了 2 秒,
偏移量 =
÷ 0.1 = 115
Calibrate:
CALL Start ; 发起起始命令
MOV A, #0D0H ; 发起设置地址 (0x68) + 写命令 (0)
CALL SendData
CALL RecvACK
MOV A, #10H ; 发起存储地址(Aging offset)
CALL SendData
CALL RecvACK
MOV A, #115 ; 写数据 115
CALL SendData
CALL RecvACK
CALL STOP ; 发送停止命令
RET
DS3231 RTC 时钟源码:
bash
$NOMOD51
#include "STC32G.INC"
Fosc_KHZ EQU 24000 ; 系统时钟 24 MHz
;========================================================================
; STC32G 测试 DS3231 (I2C) 并显示于 LCD1602
;========================================================================
; --- I2C 引脚定义 ---
I2C_SCL BIT P1.5 ; 串行时钟线
I2C_SDA BIT P1.4 ; 串行数据线
hours DATA 30H ; 时 (BCD)
minute DATA 31H ; 分 (BCD)
second DATA 32H ; 秒 (BCD)
days DATA 33H ; 日 (BCD)
months DATA 34H ; 月 (BCD)
years DATA 35H ; 年 (BCD)
week DATA 36H ; 星期 (1~7)
first_run DATA 37H ; 第一次运行标志 (0代表第一次,1代表之后)
ORG 0000H
LJMP MAIN
ORG 0200H
MAIN:
MOV SP, #0C0H
MOV WTST, #0 ; CPU 执行程序的速度设置为最快
MOV CKCON, #0 ; 设置外部数据总线速度为最快
ORL P_SW2,#080H ; 使能访问XFR
MOV P0M0, #0 ; 准双向口模式
MOV P0M1, #0 ; 准双向口模式
MOV P1M0, #030H ; P1.4, P1.5 开漏模式, 需外加上拉电阻
MOV P1M1, #030H ; P1.4, P1.5 开漏模式, 需外加上拉电阻
MOV P2M0, #0 ; 准双向口模式
MOV P2M1, #0 ; 准双向口模式
ANL P_SW2, #0CFH ; I2C_S = 00 SCL 和 SDA 引脚映射至 P1.4 及 P1.5
MOV first_run, #0 ; 此是第一次运行
;================= 初始化 LCD1602=============================
LCALL F_Initialize_LCD
MOV A, #0
LCALL F_ClearLine
MOV A, #1
LCALL F_ClearLine
;================= 初始化 I2C ================================
MOV A, #0CDH ; R11 即是 ACC, 但不可用 MOV @DR4, A
MOV WR6, #WORD0 I2CCFG
MOV WR4, #WORD2 I2CCFG
MOV @DR4, R11 ; MOV I2CCFG, #0CDH 使能I2C 主机模式, 总线速度 400K
MOV A, #0
MOV WR6, #WORD0 I2CMSST
MOV WR4, #WORD2 I2CMSST
MOV @DR4, R11 ; MOV I2CMSST, #0 I2CMSST 寄存器清零
JMP DISP ; 此行前如果加 ; 设定年/月/日/星期/时/分/秒
;================= 设置 DS3231 时间 ==========================
CALL Start ; 发起起始命令
MOV A, #0D0H ; 发起设置地址 (0x68) + 写命令 (0)
CALL SendData
CALL RecvACK
MOV A, #0 ; 发起存储地址(秒)
CALL SendData
CALL RecvACK
MOV A, #0 ; 写数据 0秒 (BCD)
CALL SendData
CALL RecvACK
MOV A, #15H ; 写数据 15分 (BCD)
CALL SendData
CALL RecvACK
MOV A, #14H ; 写数据 14时 (BCD)
CALL SendData
CALL RecvACK
MOV A, #06H ; 星期六 (1=Mon, 2=Tue... 6=Sat, 7=Sun)
CALL SendData
CALL RecvACK
MOV A, #27H ; 27日 (BCD)
CALL SendData
CALL RecvACK
MOV A, #06H ; 06月 (BCD)
CALL SendData
CALL RecvACK
MOV A, #26H ; 26年 (2026年 BCD)
CALL SendData
CALL RecvACK
CALL STOP ; 发送停止命令
CALL Delay ; 等待设备写数据
;================= 显示 DS3231 时间 ==========================
DISP:
;--- 1. 每次都读取 时、分、秒 ---
CALL Start ; 发起起始命令
MOV A, #0D0H ; 发起设置地址 (0x68) + 写命令 (0)
CALL SendData
CALL RecvACK
MOV A, #0 ; 发起存储地址(秒)
CALL SendData
CALL RecvACK
CALL Start ; 发起起始命令
MOV A, #0D1H ; 发起设置地址 (0x68) + 读命令 (1)
CALL SendData
CALL RecvACK
CALL RecvData ; 读取数据 (秒)
MOV second, A
CALL SendACK
CALL RecvData ; 读取数据 (分)
MOV minute, A
CALL SendACK
CALL RecvData ; 读取数据 (时)
MOV hours, A
CALL SendNAK
CALL STOP ; 发送停止命令
CALL DisplayRTC ; 在 LCD1602 显示时分秒
;--- 2. 判断是否需要更新第 1 行 (年月日星期) ---
MOV A, first_run
JZ UPDATE_YMD ; 如果第一次运行 first_run == 0,必須更新
; 检查是否为 00:00:00
MOV A, second
JNZ SKIP_YMD ; 秒 != 0,跳过
MOV A, minute
JNZ SKIP_YMD ; 分 != 0,跳过
MOV A, hours
JNZ SKIP_YMD ; 时 != 0,跳过
UPDATE_YMD:
MOV first_run, #1 ; 标记已经执行过第一次了
;--- 3. 读取 DS3231 的星期、日、月、年 (地址 03H~06H) ---
CALL Start ; 发起起始命令
MOV A, #0D0H ; 发起设置地址 (0x68) + 写命令 (0)
CALL SendData
CALL RecvACK
MOV A, #03H ; 从 03H (星期) 开始读
CALL SendData
CALL RecvACK
CALL Start ; 发起起始命令
MOV A, #0D1H ; 发起设置地址 (0x68) + 读命令 (1)
CALL SendData
CALL RecvACK
CALL RecvData ; 读取 星期
MOV week, A
CALL SendACK
CALL RecvData ; 读取 日
MOV days, A
CALL SendACK
CALL RecvData ; 读取 月
MOV months, A
CALL SendACK
CALL RecvData ; 读取 年
MOV years, A
CALL SendNAK
CALL STOP
CALL DisplayYMD ; 调用函数:刷新 LCD 第 1 行
SKIP_YMD:
CALL Delay ; 延時 150ms
JMP DISP ; 不断循环
;================= I2C 程序库 ==========================
Wait:
MOV WR6,#WORD0 I2CMSST
MOV WR4,#WORD2 I2CMSST
MOV R11,@DR4 ; MOV A, I2CMSST
JNB ACC.6, Wait ; 等待直至中断标志位(MSIF) 为 1
; 清除中断标志位(MSIF), 其他位元不变
ANL A, #0BFH ; 屏蔽 bit 6 (0xBF = ~0x40)
MOV WR6, #WORD0 I2CMSST
MOV WR4, #WORD2 I2CMSST
MOV @DR4, R11 ; MOV I2CMSST, A 写入 I2CMSST
RET
Start:
MOV A, #1 ; 发送 Start 命令
MOV WR6, #WORD0 I2CMSCR
MOV WR4, #WORD2 I2CMSCR
MOV @DR4, R11 ; MOV I2CMSCR, #1
CALL Wait
RET
SendData:
MOV WR6, #WORD0 I2CTXD
MOV WR4, #WORD2 I2CTXD
MOV @DR4, R11 ; MOV I2CTXD, A (由 ACC 发送数据到 TX buffer)
MOV A, #2 ; 发送 Send 命令
MOV WR6, #WORD0 I2CMSCR
MOV WR4, #WORD2 I2CMSCR
MOV @DR4, R11 ; MOV I2CMSCR, #2
CALL Wait
RET
RecvACK:
MOV A, #3 ; 发送 接收 ACK 命令
MOV WR6, #WORD0 I2CMSCR
MOV WR4, #WORD2 I2CMSCR
MOV @DR4, R11 ; MOV I2CMSCR, #3
CALL Wait
RET
RecvData:
MOV A, #4 ; 发送 RECV 命令
MOV WR6, #WORD0 I2CMSCR
MOV WR4, #WORD2 I2CMSCR
MOV @DR4, R11 ; MOV I2CMSCR, #4
CALL Wait
MOV WR6, #WORD0 I2CRXD
MOV WR4, #WORD2 I2CRXD
MOV R11, @DR4 ; MOV A, I2CRXD (由 RX buffer 接收数据到 ACC)
RET
SendACK:
MOV WR6, #WORD0 I2CMSST
MOV WR4, #WORD2 I2CMSST
MOV R11, @DR4 ; MOV A, I2CMSST
ANL A, #0FEH ; ACK 信号 0 到 SDA
MOV @DR4, R11
MOV A, #5 ; 发送 ACK 命令
MOV WR6, #WORD0 I2CMSCR
MOV WR4, #WORD2 I2CMSCR
MOV @DR4, R11 ; MOV I2CMSCR, #5
CALL Wait
RET
SendNAK:
MOV WR6, #WORD0 I2CMSST
MOV WR4, #WORD2 I2CMSST
MOV R11, @DR4 ; MOV A, I2CMSST
ORL A, #1 ; NAK 信号 1 到 SDA
MOV @DR4, R11
MOV A, #5 ; 发送 ACK 命令
MOV WR6, #WORD0 I2CMSCR
MOV WR4, #WORD2 I2CMSCR
MOV @DR4, R11 ; MOV I2CMSCR, #5
CALL Wait
RET
Stop:
MOV A, #6 ; 发送 STOP 命令
MOV WR6, #WORD0 I2CMSCR
MOV WR4, #WORD2 I2CMSCR
MOV @DR4, R11 ; MOV I2CMSCR, #6
CALL Wait
RET
;========================================================================
; 函数: F_DisplayRTC
; 描述: 显示时钟函数
; 参数: none.
; 返回: none.
;========================================================================
DisplayRTC:
MOV R2, #1 ;第2行 (第一行是 '0')
MOV R3, #4 ;第4个字符 (最左是 '0')
MOV A, hours
MOV B, A
SWAP A
ANL A, #0FH ; 时十位
ADD A, #'0' ;(2,5,hours / 10 + '0')
LCALL F_WriteChar
INC R3 ;第5个字符
MOV A, B ; B = 时个位
ANL A, #0FH
ADD A, #'0' ;(2,6,hours % 10 +'0')
LCALL F_WriteChar
INC R3 ;第6个字符
MOV A, #':' ;(2,7,':')
LCALL F_WriteChar
INC R3 ;第7个字符
MOV A, minute
MOV B, A
SWAP A
ANL A, #0FH ; 分十位
ADD A, #'0' ;(2,8,hours / 10 + '0')
LCALL F_WriteChar
INC R3 ;第8个字符
MOV A, B ; B = 分个位
ANL A, #0FH
ADD A, #'0' ;(2,9,minute % 10 +'0')
LCALL F_WriteChar
INC R3 ;第9个字符
MOV A, #':' ;(2,10,':')
LCALL F_WriteChar
INC R3 ;第10个字符
MOV A, second
MOV B, A
SWAP A
ANL A, #0FH ; 秒十位
ADD A, #'0' ;(2,11,second / 10 + '0')
LCALL F_WriteChar
INC R3 ;第11个字符
MOV A, B ; B = 秒个位
ANL A, #0FH
ADD A, #'0' ;(2,12,second % 10 +'0')
LCALL F_WriteChar
RET
;========================================================================
; 函数: DisplayYMD
; 描述: 在LCD1602第1行显示 年/月/日 星期 (例如: 2026/06/27 Sat)
;========================================================================
DisplayYMD:
MOV R2, #0 ; 第 1 行 (暂存器为0)
MOV R3, #1 ; 从第 1 个字元开始写
; 固定显示 "20"
MOV A, #'2'
LCALL F_WriteChar
INC R3
MOV A, #'0'
LCALL F_WriteChar
INC R3
; 显示 年 (year)
MOV A, years
MOV B, A
SWAP A
ANL A, #0FH
ADD A, #'0'
LCALL F_WriteChar ; 年十位
INC R3
MOV A, B
ANL A, #0FH
ADD A, #'0'
LCALL F_WriteChar ; 年个位
INC R3
MOV A, #'/' ; 分隔符
LCALL F_WriteChar
INC R3
; 显示 月 (month)
MOV A, months
MOV B, A
SWAP A
ANL A, #0FH
ADD A, #'0'
LCALL F_WriteChar ; 月十位
INC R3
MOV A, B
ANL A, #0FH
ADD A, #'0'
LCALL F_WriteChar ; 月个位
INC R3
MOV A, #'/' ; 分隔符
LCALL F_WriteChar
INC R3
; 显示 日 (day)
MOV A, days
MOV B, A
SWAP A
ANL A, #0FH
ADD A, #'0'
LCALL F_WriteChar ; 日十位
INC R3
MOV A, B
ANL A, #0FH
ADD A, #'0'
LCALL F_WriteChar ; 日个位
INC R3
MOV A, #' ' ; 空格
LCALL F_WriteChar
INC R3
; --- 显示星期字串 ---
; 根据 week (1~7) 计算字串查表偏移量。每个字串含结尾0共4字节 (week * 4)
MOV A, week
ANL A, #07H ; 安全遮罩
ADD A, ACC ; A = week * 2
ADD A, ACC ; A = week * 4
MOV DPTR, #String_Week
ADD A, DPL
MOV DPL, A
MOV A, #0
ADDC A, DPH
MOV DPH, A ; DPTR 现在指向对应星期的字串首地址
; 呼叫 F_PutString 来显示星期
LCALL F_PutString
RET
Delay: ;延时150ms
MOV A, #150
F_delay_ms:
PUSH 02H ;入栈R2
PUSH 03H ;入栈R3
PUSH 04H ;入栈R4
MOV R4, A ; 将传递回来的毫秒数(A)赋给外层循环计数器R4
L_delay_ms_1:
MOV WR2, #(Fosc_KHZ / 5) ; 1T, WR2 = 4800 (使每轮循环耗时1ms)
L_delay_ms_2:
DEC WR2, #1 ; 1T
JNE L_delay_ms_2 ; 3/4T 4799*5 + 4 = 23,999T
DJNZ R4, L_delay_ms_1 ; 1/3T (Acc-1)*24,003 + 24000T
POP 04H ;出栈R4
POP 03H ;出栈R3
POP 02H ;出栈R2
RET
;************* LCD1602 驱动 **************
;8位数据访问方式 LCD1602
LineLength EQU 16 ;16x2
/************* 定义脚位 **************************************/
LCD_BUS DATA P0 ;P0--0x80, P1--0x90, P2--0xA0, P3--0xB0
LCD_B7 BIT LCD_BUS.7 ;D7 -- Pin 14 LED- -- Pin 16
LCD_B6 BIT LCD_BUS.6 ;D6 -- Pin 13 LED+ -- Pin 15
LCD_B5 BIT LCD_BUS.5 ;D5 -- Pin 12 Vo -- Pin 3
LCD_B4 BIT LCD_BUS.4 ;D4 -- Pin 11 VDD -- Pin 2
LCD_B3 BIT LCD_BUS.3 ;D3 -- Pin 10 VSS -- Pin 1
LCD_B2 BIT LCD_BUS.2 ;D2 -- Pin 9
LCD_B1 BIT LCD_BUS.1 ;D1 -- Pin 8
LCD_B0 BIT LCD_BUS.0 ;D0 -- Pin 7
LCD_ENA BIT P2.7 ;Pin 6
LCD_RW BIT P2.6 ;Pin 5 LCD_RS R/W DB7--DB0 功能
LCD_RS BIT P2.5 ;Pin 4 0 0 输入 写入命令
; 0 1 输出 读取 BF 和 AC 指针数据
; 1 0 输入 写入数据
; 1 1 输出 读取数据
;共两行, 16x2= 32 (内部显存一行有40个字符)
;第一行位址: 0~15
;第二行位址: 64~79
;指令集
; ==================== 1. 基本指令 ====================
C_CLEAR EQU 0x01 ; 清屏 (光标归零,DDRAM填满空格)
C_HOME EQU 0x02 ; 归位 (光标归零,画面不改变)
; ==================== 2. 输入模式设置 (Entry Mode) ====================
C_CUR_L EQU 0x04 ; 输入后:光标左移,画面不动
C_DISP_R EQU 0x05 ; 输入后:光标左移,画面【右】平移
C_CUR_R EQU 0x06 ; 输入后:光标右移,画面不动 (最常用)
C_DISP_L EQU 0x07 ; 输入后:光标右移,画面【左】平移 (滚动字幕效果)
; ==================== 3. 显示/光标开关控制 ====================
C_OFF EQU 0x08 ; 关闭 LCD 显示
C_ON EQU 0x0C ; 开启 LCD, 无光标, 不闪烁
C_FLASH EQU 0x0D ; 开启 LCD, 无光标, 仅光标所在位置闪烁
C_CURSOR EQU 0x0E ; 开启 LCD, 有光标, 不闪烁
C_FLASH_ALL EQU 0x0F ; 开启 LCD, 有光标, 且光标闪烁
; ==================== 4. 光标/画面位移 (不影响DDRAM) ====================
C_CURSOR_LEFT EQU 0x10 ; 仅将光标向左平移一个字符位
C_CURSOR_RIGHT EQU 0x14 ; 仅将光标向右平移一个字符位
C_PICTURE_LEFT EQU 0x18 ; 将整个画面向左平移一个字符位
C_PICTURE_RIGHT EQU 0x1C ; 将整个画面向右平移一个字符位
; ==================== 5. 功能设置 (Function Set) ====================
C_BIT4 EQU 0x20 ; 4位总线,1行显示,5x7点阵
C_4bitL2DOT7 EQU 0x28 ; 4位总线,2行显示,5x7点阵 (4位模式最常用)
C_BIT8 EQU 0x30 ; 8位总线,1行显示,5x7点阵
C_L1DOT10 EQU 0x34 ; 8位总线,1行显示,5x10点阵
C_L2DOT7 EQU 0x38 ; 8位总线,2行显示,5x7点阵 (8位模式最常用)
; ==================== 6. 地址设置 ====================
C_CGADDRESS0 EQU 0x40 ; CGRAM 起始位址 (存储自定义字符,后面加偏移量 0~63)
C_DDADDRESS0 EQU 0x80 ; DDRAM 起始位址 (屏幕显示坐标,第一行0x80+x,第二行0xC0+x)
;========================================================================
; 函数: F_LCD_DelayNop
; 描述: 延时函数
; 参数: none.
; 返回: none.
;========================================================================
F_LCD_DelayNop:
NOP ;NOP(15T)
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
RET
;========================================================================
; 函数: F_CheckBusy
; 描述: 检测忙 (有超时)
; 物理时序: RS=0, RW=1, E=1 -> 读LCD_B7 -> E=0
; 参数: none.
; 返回: none.
;========================================================================
F_CheckBusy:
PUSH ACC
PUSH R2
PUSH R3
MOV R2, #HIGH 5000 ;设置超时计数器
MOV R3, #LOW 5000
L_CheckBusyLoop:
MOV LCD_BUS, #0FFH ; 读准双向口前,必须先将端口置1
CLR LCD_RS ; RS=0 读状态
SETB LCD_RW ; RW=1 读操作
SETB LCD_ENA ; E脉冲拉高
LCALL F_LCD_DelayNop ; 稍微延时让电平稳定
MOV A, LCD_BUS ; 读取 LCD 总线端口状态
CLR LCD_ENA ; E脉冲拉低,完成一次读周期
JNB LCD_B7, L_QuitCheckBusy ; 5T, 如果 LCD_B7(BF) == 0,代表不忙,跳出
; 超时处理逻辑
MOV A, R3 ; 1T
CLR C ; 1T
SUBB A, #1 ; 2T
MOV R3, A ; 1T
MOV A, R2 ; 1T
SUBB A, #0 ; 2T
MOV R2, A ; 1T
ORL A, R3 ; 1T
JNZ L_CheckBusyLoop ; 4T 若未超时则继续检测
L_QuitCheckBusy:
POP R3
POP R2
POP ACC
RET
;========================================================================
; 函数: F_IniSendCMD
; 描述: 初始化写命令(不检测忙)
; 参数: ACC: 要写的命令.
; 返回: none.
;========================================================================
F_IniSendCMD:
CLR LCD_RW ; RW=0: 写
MOV LCD_BUS, A ; 将命令数据送到总线
LCALL F_LCD_DelayNop
SETB LCD_ENA ; E 产生正脉冲
LCALL F_LCD_DelayNop
CLR LCD_ENA
MOV LCD_BUS, #0xFF ; 释放总线
RET
;========================================================================
; 函数: F_Write_CMD
; 描述: 写命令 (先测忙,再写入)
; 参数: ACC: 要写的命令.
; 返回: none.
;========================================================================
F_Write_CMD:
LCALL F_CheckBusy ; 确保 LCD 当前处于空闲状态
CLR LCD_RS ; RS=0: 命令
CLR LCD_RW ; RW=0: 写
MOV LCD_BUS, A ; 将命令数据送到总线
LCALL F_LCD_DelayNop
SETB LCD_ENA ; E 产生正脉冲
LCALL F_LCD_DelayNop
CLR LCD_ENA
MOV LCD_BUS, #0xFF ; 释放总线
RET
;========================================================================
; 函数: F_Write_DIS_Data
; 描述: 写显示数据 (先测忙,再写入)
; 参数: ACC: 要写的数据.
; 返回: none.
;========================================================================
F_Write_DIS_Data:
LCALL F_CheckBusy ; 确保 LCD 当前处于空闲状态
SETB LCD_RS ; RS=1: 数据 (区别于写命令的关键)
CLR LCD_RW ; RW=0: 写
MOV LCD_BUS, A ; 将字符ASCII码送到总线
LCALL F_LCD_DelayNop
SETB LCD_ENA ; E 产生正脉冲
LCALL F_LCD_DelayNop
CLR LCD_ENA
MOV LCD_BUS, #0xFF ; 释放总线
RET
;========================================================================
; 函数: F_Initialize_LCD
; 描述: 初始化LCD函数
; 参数: none.
; 返回: none.
;========================================================================
F_Initialize_LCD:
CLR LCD_ENA
CLR LCD_RS
CLR LCD_RW
MOV A, #15
LCALL F_delay_ms ;LCD上电需要至少15ms的稳定时间
MOV A, #C_BIT8 ;8位元模式 (不测忙)
LCALL F_IniSendCMD
MOV A, #5
LCALL F_delay_ms
MOV A, #C_BIT8 ;再发送多一次8位元模式 (不测忙)
LCALL F_IniSendCMD
MOV A, #C_L2DOT7 ;两行显示,5*7 点阵字符
LCALL F_Write_CMD
MOV A, #C_CLEAR ;清屏
LCALL F_Write_CMD
MOV A, #C_CUR_R ;光标向右移动一个字符位
LCALL F_Write_CMD
MOV A, #C_ON ;开启 LCD
LCALL F_Write_CMD
RET
;========================================================================
; 函数: F_ClearLine
; 描述: 清除1行
; 参数: ACC: 行(0或1)
; 返回: none.
;========================================================================
F_ClearLine:
MOV C, ACC.0
MOV ACC.6, C
ANL A, #0x40
ORL A, #0x80
LCALL F_Write_CMD ;写命令 (0x80)
PUSH R2
MOV R2, #LineLength
L_ClearLine_Loop:
MOV A, #' '
LCALL F_Write_DIS_Data
DJNZ R2, L_ClearLine_Loop
POP R2
RET
;========================================================================
; 函数: F_WriteChar
; 描述: 指定行、列和字符, 写一个字符
; 参数: R2: 行(0或1), R3: 第几个字符(0~15), ACC: 要写的字符.
; 返回: none.
;========================================================================
F_WriteChar:
PUSH ACC
MOV A, R2
MOV C, ACC.0
MOV ACC.6, C
ANL A, #0x40
ORL A, #0x80
ADD A, R3
LCALL F_Write_CMD ;写命令 (0x80)
POP ACC
LCALL F_Write_DIS_Data
RET
;========================================================================
; 函数: F_PutString
; 描述: 写一个字符串,指定行、列和字符串首地址
; 参数: R2: 行(0或1), R3: 第几个字符(0~15), DPTR: 要写的字符串首地址.
; 返回: none.
;========================================================================
F_PutString:
MOV A, R2
MOV C, ACC.0
MOV ACC.6, C
ANL A, #0x40
ORL A, #0x80
ADD A, R3
LCALL F_Write_CMD ;写命令 (0x80)
L_PutString_Loop:
CLR A
MOVC A, @A+DPTR
JZ L_QuitPutString ;遇到停止符0结束
LCALL F_Write_DIS_Data
INC DPTR
INC R3
MOV A, R3
CJNE A, #LineLength, L_PutString_Loop
L_QuitPutString:
RET
;******************** LCD1602 驱动程序完 ***************************
; 星期字串
String_Week:
DB " ", 0 ; 0 (不用)
DB "Mon", 0 ; 1
DB "Tue", 0 ; 2
DB "Wed", 0 ; 3
DB "Thu", 0 ; 4
DB "Fri", 0 ; 5
DB "Sat", 0 ; 6
DB "Sun", 0 ; 7
END