目标
此汇编程序实现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 主机命令
0. Idle (待机), 无动作。
1. Start (起始命令)

2. Send data (发起数据命令)

3. Recv ACK (接收 ACK 命令)

4. RECV (接收数据命令)

5.Send ACK (发送ACK 命令)

6. 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 时钟源码:
$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
969

被折叠的 条评论
为什么被折叠?



