STC32G12K128 单片机 I2C 应用 - DS3231实时时钟

目标

汇编程序实现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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值