/*-------------------------------------------------- 作 者:黄 建 雄 日 期:2016.04.07 功 能:STC89C51MCU模拟I2C时序,具体应用器件为AT24C02型EEPROM芯片 平 台:清翔电子 QX-X3 使用说明:1、KEY6按下,则写入数据到24C02,同时设定下一次被写入的数据 2、KEY7按下,则从24C02读出数据 3、LCD显示在循环中进行处理 */ ;-----------引脚声明-------------------------------- SDA EQU P2^0 ; 数据线 SCL EQU P2^1 ; 时钟线 RS EQU P3.5 ; 引脚声明 RW EQU P3.6 E EQU P3.4 LCD EQU P0 ; P0为数据端 BF EQU P0.7 ;--------------------------------------------------- ;-----------位声明---------------------------------- BT_SND BIT P1^5 ; P1.5脚用于数据发送 BT_REC BIT P1^6 ; P1.6脚用于数据接收 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 ;--------------------------------------------------- ORG 0000H LJMP BEGIN ORG 000BH LJMP TIM0_ISR ORG 0100H ;--------------------------------------------------- BEGIN: MOV 55H, #3 ; 写入24C02的4个字节的数据 MOV 56H, #5 MOV 57H, #4 MOV 58H, #7 LCALL TIM_INIT ; 定时器初始化 LCALL LCD_INIT ; LCD初始化 MOV P0, #0FFH ; 位选全关 SETB P2.7 CLR P2.7 LOOP: LJMP KEY2 LOOP1: LJMP CIRCUR ;--------------------------------------------------- /* 单片机写数据到24C02 */ WRITE: LCALL START ; 产生起始信号 MOV A, #0A0H ; A0H为写入时的寻址字节 1010 000 0 分别表示 器件地址、引脚地址、写操作 LCALL SEND ; 发送寻址字节 LCALL ACKI ; 接收应答 JC WRITEEND ; 从器件未响应,跳转至结束 MOV A, #00H ; 设置从器件片内地址 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 ; 产生终止信号 RET ; 子程序返回 ;--------------------------------------------------- /* 单片机读出24C02中的数据 */ READ: LCALL START ; 产生起始信号S MOV A, #0A0H ; A0H为写入时的寻址字节 LCALL SEND ; 发送寻址字节 LCALL ACKI ; 接收应答 JC OUTEND ; 无应答,转结束 MOV A, #00H ; 设置要读取从器件的片内地址 LCALL SEND ; 发送片内单元地址 LCALL ACKI ; 调用接收应答位子程序 JC OUTEND ; 无应答,转结束 LCALL START ; 再次发出起始信号(这条语句绝对不能少,否则程序就会出错) ORL A, #0A1H ; 设置寻址字节,准备接收 LCALL SEND ; 发送寻址字节 LCALL ACKI ; 接收应答 JC OUTEND ; 无应答,转结束 MOV R2, #4 ; 将要接收数据的字节数 MOV R1, #60H ; 接收字节存放在单片机片内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 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 ;--------------------------------------------------------- DELAY500MS: ;@11.0592MHz NOP NOP NOP PUSH 10H PUSH 31H PUSH 32H MOV 30H,#17 MOV 31H,#208 MOV 32H,#24 NEXT: DJNZ 32H,NEXT DJNZ 31H,NEXT DJNZ 30H,NEXT POP 32H POP 31H POP 30H RET ;--------------------------------------------------------- CIRCUR: MOV R1, #60H CIRCUR1:MOV A, @R1 ADD A, #30H LCALL W_DATA INC R1 CJNE R1, #64H,CIRCUR1 MOV A, #80H ; 若一轮显示完毕,则重设地址指针,下一次又从初始位置开始显示 LCALL W_CMD LJMP LOOP ; 返回主循环 ;--------------------------------------------------------- TIM_INIT: // TIM0初始化子程序 MOV TMOD, #0X02 ; T0配置为方式2,8位重载模式 CLR TR0 ; 在发送和接收时才开始使用 CLR TF0 ; 清0溢出标志位 MOV TH0, #0XA0 ; 重装值 9600bps,当晶振为11.0592M时,刚好为执行96条指令所用的时间 MOV TL0, TH0 ; 把TH0的内容赋给TL0 SETB ET0 ; 使能TIM0中断 SETB EA ; 使能总中断 RET /* 数据发送子程序 */ PSendChar: MOV A, R7 ; 把要发送的数据赋给A CLR F0 ; 清0标志F0 CLR BT_SND ; 起始位 0 MOV TL0, TH0 ; 装值 SETB TR0 ; 启动TIM0 JNB F0, $ ; 未完成发送则等待 MOV C, ACC0 ; 先送数据低位 MOV BT_SND, C ; 不能够直接从直接寻址位到直接寻址位 CLR F0 ; 清0标志位 JNB F0, $ ; 未完成发送则等待 MOV C, ACC1 MOV BT_SND, C CLR F0 JNB F0, $ MOV C, ACC2 MOV BT_SND, C CLR F0 JNB F0, $ MOV C, ACC3 MOV BT_SND, C CLR F0 JNB F0, $ MOV C, ACC4 MOV BT_SND, C CLR F0 JNB F0, $ MOV C, ACC5 MOV BT_SND, C CLR F0 JNB F0, $ MOV C, ACC6 MOV BT_SND, C CLR F0 JNB F0, $ MOV C, ACC7 MOV BT_SND, C CLR F0 JNB F0, $ SETB BT_SND ; 停止位 1 CLR F0 JNB F0, $ ; 未发送完毕则等待 CLR TR0 ; 关闭TIM0 RET /* 定时器0中断服务程序 */ TIM0_ISR: SETB F0 ; 标志位置1 RETI ;--------------------------------------------------------------- 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 ;----------------------------------------------------------------------- ; 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 ;--------------------------------------------------------------------- /*------------------------------------------------------------------*/ KEY2: LCALL KS ;键盘检测 JNZ K1 ;累加器不为0表可能有键按下,跳至K1再次检测 LCALL D10MS ;书上写了要去陡,但好像这里去不去陡没太大关系,不去也能行 AJMP LOOP1 ;无键按下返回重新检测 K1: LCALL D10MS ;去陡,再次检测 LCALL KS JNZ K2 AJMP LOOP1 ;无键按下返回重新检测 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 START2 ;出栈后跳至显示程序 KS: MOV P3, #0FH ;键盘检测,有键按下非0 MOV A, P3 XRL A, #0FH ;异或指令,判有无键按下,聪明呀! RET START2: MOV DPTR, #TABLE ;把管码表首地址给数据指针 ;数码管段位选初始化 CLR P2.6 CLR P2.7 KCHECK: CJNE A, #1, KCHECK2 ; 若KEY6按下,写数据,且各数据加1 LCALL WRITE INC 55H INC 56H INC 57H INC 58H LJMP DISPLAY KCHECK2:CJNE A, #2, DISPLAY LCALL READ DISPLAY: MOVC A, @A+DPTR SETB P2.7 ;开位选 MOV P0, #0FEH CLR P2.7 MOV P0, #00H ;消影 SETB P2.6 ;开段选 MOV P0, A CLR P2.6 ;锁存 LJMP LOOP1 ;显示完后跳至检测程序继续检测 TABLE: DB 3FH, 06H, 5BH, 4FH, 66H ;共阴数码管码表 DB 6DH, 7DH, 07H, 7FH, 6FH DB 77H, 7CH, 39H, 5EH, 79H DB 71H D10MS: MOV R7, #25 ;延时10MS D1: MOV R6, #200 DJNZ R6, $ ;美元符号表示原地跳转 DJNZ R7, D1 RET END ; 结束