折腾了近3天,终于搞通了使用HC18M003单片机驱动IIC LCD1602的例程。中间经历了太多的波折,调试也是相当花时间和精力,好在终于调通了。感觉使用IIC外设固然是一种选择,但好像也没有那么令人心动。和自己预想的那种,只需要把数据扔过去啥都不用管的模式不一样啊。需要及时响应中断,以及判断数据传输状态。否则就有可能出错。有机会再试试用DMA方式和IIC外设交互,看看是不是能省事儿点吧。
以下是驱动例程:
/** * 模块性能介绍 * 1、双线通讯,支持主机以及从机模式 * 2、支持多主机通讯时钟仲裁功能 * 3、支持地址可编程 * 4、支持标准速率(最多100kbps)和快速(最多400kbps) * * 以IIC方式驱动LCD1602,无视中断时IIC设备的状态值,只是按顺序发送数据 * ************************************************************************************ * 代码配置注意事项 * 1、当CPU运行在8M时,BOR必须使能为4.2V; * 2、当CPU运行在4M时,BOR必须使能为3.0V及以上; * 3、当CPU运行在2M时,BOR必须使能为2.0V及以上,常规情况下不建议关闭BOR; * 4、使能BOR时除程序中修改,建议OPTION也配置相同电压。 * ************************************************************************************ * LCD1602模块: VCC - 5V * (PCF8547) GND * SCL - P3.2 * SDA - P3.3 * PCF8574T LCD1602 *================================================================================ * P7 DB7 * P6 DB6 * P5 DB5 * P4 DB4 * P3 控制背光灯 * P0 RS * P1 RW * P2 CS *================================================================================ * LCD1602的操作 * 写指令: RS=0, RW=0, CS(E)上升沿 * 写数据: RS=1, RW=0, CS(E)上升沿 *================================================================================ * LCD1602的操作指令 * Bit RS RW 7 6 5 4 3 2 1 0 *----------------------------------------------------------------------------------- * 1.清除显示 0 0 0 0 0 0 0 0 0 1 * 2.光标返回 0 0 0 0 0 0 0 0 1 * * 3.设输入模式 0 0 0 0 0 0 0 1 I/D S I/D:光标移动方向,1-右移;0-左移 * S:屏幕上所有文字是否左移或右移,1表示有效,0表示无效 * 4.显示开关 0 0 0 0 0 0 1 D C B D=1开显示,D=0关显示 * C=1显示光标,C=0关闭光标 * B=1光标闪烁,B=0光标不闪烁 * 5.光标字符移位 0 0 0 0 0 1 S/C R/L * * S/C 1-显示移动的文字,0-移动光标 * R/L 左右方向 * 6.设置功能 0 0 0 0 1 DL N F * * DL: 1-4位总线;0-8位总线 * N: 1-双行显示;0-单行显示 * F: 1-5X10点阵, 0-5x7点阵 * 7.设置字符发生器地址 0 0 0 1 字符发生器RAM * 8.设置数据存储器地址 0 0 1 显示数据存储器地址 *----------------------------------------------------------------------------------- * 9.读忙标志或地址 0 1 BF 计数器地址 * 10.写数到CGRAM或DDRAM 1 0 要写的数据内容 * 11.从CGRAM或DDRAM读 1 1 读出的数据 *=================================================================================== * 数据指针设置 80H + 地址码(0-27H(第一行),40H-67H(第二行)) ** LCD1602 四线驱动方式下,一个字节数据传输2回 **/ #include"holychip_define.h" // Debug用定义 //#define DEBUG //--------------OLED参数定义--------------------- #define LINE_1_ADDR 0x40 #define LINE_2_ADDR 0x80 #define WIDTH 128 #define HEIGHT 32 #define OLED_CMD 0 //写命令 #define OLED_DATA 1 //写数据 #define IIC_SLAVE_ADDR 0x4E //IIC器件地址: #define LCD_ADDR_W 0x4E #define LCD_ADDR_R 0x4F #define IIC_STATUS_START_OK 0x08 // START发送完成后的状态(ACK应答)(STA,STO,SI,AA)=(1,0,0,X) #define IIC_STATUS_REPEAT_START_OK 0x10 // START发送完成后的状态(ACK应答)(STA,STO,SI,AA)=(1,0,0,X) #define IIC_STATUS_WADDR_ACK_OK 0x18 // 向从机发送写指令+地址,发送完成后的状态(ACK应答)(STA,STO,SI,AA)=(X,0,0,X) #define IIC_STATUS_DATA_ACK_OK 0x28 // 向从机发送数据,完成后的状态(ACK应答)(STA,STO,SI,AA)=(0,0,0,X) #define IIC_STATUS_WADDR_NACK_OK 0x20 // 向从机发送写指令+地址,发送完成后的状态(NACK应答)(STA,STO,SI,AA)=(X,0,0,X) #define IIC_STATUS_DATA_NACK_OK 0x30 // 向从机发送数据,完成后的状态(NACK应答)(STA,STO,SI,AA)=(0,0,0,X) //unsigned char HEXCHR[16]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; unsigned int gms=0; // 延时变量 /*************************************************************************************** * @说明 1毫秒单位的延时 * @参数 ms 毫秒数 * @返回值 无 * @注 比较准确, ***************************************************************************************/ void delay_ms(unsigned int ms) { gms=ms; while(gms > 0) { // 等待 gms = 0 } } /*************************************************************************************** * @说明 10微秒单位的延时 * @参数 ms 毫秒数 * @返回值 无 * @注 不准确, ***************************************************************************************/ void delay_10us(unsigned int wm) { unsigned char a,b,c; for(c=0; c<wm; c++) { for(b=0; b<40; b++) { } } } /*************************************************************************************** * @说明 取得十六进制字符 * @参数 chr 字符 * h_l_flag 0 - 低四位对应16进制字符;1 - 高四位对应字符 * @返回值 无 * @注 无 ***************************************************************************************/ unsigned char toHexChar(unsigned char chr, unsigned char h_l_flag) { unsigned char val = chr; unsigned char result=0; if (h_l_flag) { val = (val & 0xF0)>>4; } else { val = val & 0x0f; } switch (val) { case 0: result = '0';break; case 1: result = '1';break; case 2: result = '2';break; case 3: result = '3';break; case 4: result = '4';break; case 5: result = '5';break; case 6: result = '6';break; case 7: result = '7';break; case 8: result = '8';break; case 9: result = '9';break; case 10: result = 'A';break; case 11: result = 'B';break; case 12: result = 'C';break; case 13: result = 'D';break; case 14: result = 'E';break; case 15: result = 'F';break; default: result = '-';break; } return result; } /*************************************************************************************** * @说明 判断字符是否能显示 * @参数 chr * @返回值 0 - 不可显示; 1 - 可显示 * @注 ***************************************************************************************/ unsigned char isDispChar(unsigned char chr) { if (chr >= 32 && chr <= 126) { return 1; } return 0; } /*************************************************************************************** * @说明 串口发送一个字符 * @参数 chr 字符 * @返回值 无 * @注 无 ***************************************************************************************/ void send_char(unsigned char chr) { SBUF = chr; //发送8位串口数据 while(!TXIF); TXIF = 0; //清除发送中断标志位 } /*************************************************************************************** * @说明 串口发送一个字符,以十六进制方式表示 * @参数 chr 字符 * @返回值 无 * @注 无 ***************************************************************************************/ void send_char_hex(unsigned char chr) { SBUF = '0'; while(!TXIF); TXIF = 0; //清除发送中断标志位 SBUF = 'x'; while(!TXIF); TXIF = 0; //清除发送中断标志位 SBUF = toHexChar(chr, 1); while(!TXIF); TXIF = 0; //清除发送中断标志位 SBUF = toHexChar(chr, 0); while(!TXIF); TXIF = 0; //清除发送中断标志位 SBUF = ' '; while(!TXIF); TXIF = 0; //清除发送中断标志位 } /*************************************************************************************** * @说明 串口发送一个字符,以十进制方式表示 * @参数 chr 字符 * @返回值 无 * @注 无 ***************************************************************************************/ void send_char_dec(unsigned char chr) { if (chr > 99) { SBUF = toHexChar(chr/100, 0); while(!TXIF); TXIF = 0; //清除发送中断标志位 } if (chr > 9) { SBUF = toHexChar((chr%100)/10, 0); while(!TXIF); TXIF = 0; //清除发送中断标志位 } SBUF = toHexChar(chr%10, 0); while(!TXIF); TXIF = 0; //清除发送中断标志位 } /*************************************************************************************** * @说明 串口发送一个字符串 * @参数 str 字符串 * @返回值 无 * @注 无 ***************************************************************************************/ void send_str(unsigned char *str) { unsigned char chr = '\0'; while (chr=*str) { send_char(chr); str++; } } /*************************************************************************************** * @说明 中断服务函数 * @参数 无 * @返回值 无 * @注 包含定时器2中断和IIC中断 ***************************************************************************************/ void interrupt all_isr(void) { // IIC中断 if (IICIF) { PORTA0 = ~PORTA0; } // // 串口接收中断,收到的数据存入数据缓冲区(对应两行32个字符) // if(RXIF) { // guc_Uartbuf = SBUF; // 转存8位串口接收数据 // guc_Uartflag = 1; // 接收中断标志 // // // 如果是可显示的字符,保存 // if (isDispChar(guc_Uartbuf)) { // guc_lcdchar[lcdchar_addr] = guc_Uartbuf; // lcdchar_addr = (lcdchar_addr+1)%32; // 显示位置指向下一个 // } // // SCON &= ~0x10; //禁止串口继续继续接收数据 // RXIF = 0; //清除接收中断标志位 // } // 定时器0中断 if(T0IF) { T0 = 0xF0; //T0定时时间1ms if (gms>0) { gms=gms-1; } T0IF = 0; //清除T0中断标志位 } } /*************************************************************************************** * @说明 通过IIC发送一个字节给PCF8574T,PCF8574T是作为IIC从机存在的 * @参数 dat 呈现到 P7 ~ P0管脚上,连接到LCD引脚 * P7 -- LCD160-DB7 * P6 -- LCD160-DB6 * P5 -- LCD160-DB5 * P4 -- LCD160-DB4 * P3 -- LCD1602-背光灯 * P2 -- LCD1602-E * P1 -- LCD1602-RW * P0 -- LCD1602-RS * @返回值 无 * @注 发送给LCD1602,要通过PCF8574T实现 ***************************************************************************************/ unsigned char send_pcf8574Byte(unsigned char dat) { unsigned char iic_status = 0; // 每次发送完成后获得的IIC状态码 unsigned char result = 0; // 返回值 //send_str((unsigned char *)"\r\n<send_pcf8574Byte>"); // 清除IIC中断标志 IICIF = 0; //启动IIC模块 IICCON |= 0x40; delay_10us(1); // 使IIC外设向IIC总线发送“START”信号 IICCON |= 0x20; // 产生开始信号 #ifdef DEBUG send_char_hex(0xA1); #endif // 等待中断发生 while (!IICIF); iic_status=IICSTA; #ifdef DEBUG send_char_hex(iic_status); #endif if(iic_status == 0x08) { #ifdef DEBUG send_char_hex(0xA2); #endif // 发送子机地址 IICCON &=~ 0x20; // STA=0,禁止继续发START信号 IICCON |= 0x04; // AA=1,确认子机回复ACK信号? IICDAT = IIC_SLAVE_ADDR; IICIF = 0; } else { #ifdef DEBUG send_char_hex(0xA0); #endif result = 1; goto send_pcf8574Byte_end; } // 等待中断发生 while (!IICIF); iic_status=IICSTA; #ifdef DEBUG send_char_hex(iic_status); #endif if(iic_status == 0x18) { #ifdef DEBUG send_char_hex(0xA3); #endif // 发送数据 IICCON &=~ 0x20; // STA=0,禁止继续发START信号 IICCON |= 0x04; // AA=1,确认子机回复ACK信号? IICDAT = dat; IICIF = 0; } else { #ifdef DEBUG send_char_hex(0xA0); #endif result = 2; goto send_pcf8574Byte_end; } // 等待中断发生 while (!IICIF); iic_status=IICSTA; #ifdef DEBUG send_char_hex(iic_status); #endif if(iic_status == 0x28) { #ifdef DEBUG send_char_hex(0xA9); #endif } else { #ifdef DEBUG send_char_hex(0xA0); #endif result = 3; goto send_pcf8574Byte_end; } IICCON |= 0x10; // 停止信号 IICCON &= ~0x40; // 关闭IIC return 0; send_pcf8574Byte_end: IICCON |= 0x10; // 停止信号 delay_ms(1); IICCON &= ~0x40; // 关闭IIC delay_ms(1); return result; } /*************************************************************************************** * @说明 向LCD1602发送指令 * @参数 cmd 指令 * @返回值 无 * @注 通过PCF8574T转成引脚输出,适配LCD1602方式 * ----------------------------------- * dat PCF8574T LCD1602 *------------------------------------ * D7 P7 DB7 * D6 P6 DB6 * D5 P5 DB5 * D4 P4 DB4 * D3 P3 控制背光灯 * D2 P0 RS * D1 P1 RW * D0 P2 E ***************************************************************************************/ void write_lcd1602_cmd(unsigned char cmd) { unsigned char cmd1, cmd2; //send_str((unsigned char *)"\r\n<write_lcd1602_cmd>"); cmd1=cmd|0x0f; // 发送高4位 send_pcf8574Byte(cmd1 & 0xfc); // 11111100 : CS=1,RW=0, RS=0 delay_10us(1); send_pcf8574Byte(cmd1 & 0xf8); // 11111000 : CS=0,RW=0, RS=0 // 发送低4位,要移位到高4位位置上 cmd2=cmd<<4; cmd2=cmd2|0x0f; send_pcf8574Byte(cmd2 & 0xfc); // 11111100 : CS=1,RW=0, RS=0 delay_10us(1); send_pcf8574Byte(cmd2 & 0xf8); // 11111000 : CS=0,RW=0, RS=0 } /*************************************************************************************** * @说明 向LCD1602发送数据 * @参数 dat 指令 * @返回值 无 * @注 通过PCF8574T转成引脚输出,适配LCD1602方式 * ----------------------------------- * dat PCF8574T LCD1602 *------------------------------------ * D7 P7 DB7 * D6 P6 DB6 * D5 P5 DB5 * D4 P4 DB4 * D3 P3 控制背光灯 * D2 P0 RS * D1 P1 RW * D0 P2 E ***************************************************************************************/ void write_lcd1602_data(unsigned char dat) { unsigned char dat1, dat2; // send_str((unsigned char *)"\r\n<write_lcd1602_data>"); dat1=dat|0x0f; // 发送高4位 send_pcf8574Byte(dat1 & 0xfd); // 11111101 : CS=1,RW=0, RS=1 delay_10us(1); send_pcf8574Byte(dat1 & 0xf9); // 11111001 : CS=0,RW=0, RS=1 // 发送低4位,要移位到高4位位置上 dat2=dat<<4; dat2=dat2|0x0f; send_pcf8574Byte(dat2 & 0xfd); // 11111101 : CS=1,RW=0, RS=1 delay_10us(1); send_pcf8574Byte(dat2 & 0xf9); // 11111001 : CS=0,RW=0, RS=1 } /*************************************************************************************** * @说明 初始化LCD * @参数 无 * @返回值 无 * @注 无 ***************************************************************************************/ void lcd_init(void) { // send_str((unsigned char *)"\r\n<lcd_init>"); // write_lcd1602_cmd(0x38); // 设置显示 4 (0x38:8) // delay_ms(5); write_lcd1602_cmd(0x33); // 设置显示 4 (0x38:8) delay_ms(5); write_lcd1602_cmd(0x32); // 设置4线控制 delay_ms(5); write_lcd1602_cmd(0x28); // 设置16*2, 5*7, 4线初始化 delay_ms(5); write_lcd1602_cmd(0x06); // 地址加1,数据不移动,地址移动 delay_ms(5); write_lcd1602_cmd(0x0C); // 不显示光标,光标不闪烁 delay_ms(5); // write_lcd1602_cmd(0x0f); // 显示光标,光标闪烁 // delay_ms(5); write_lcd1602_cmd(0x01); // 清屏 delay_ms(5); write_lcd1602_cmd(0x80); // 起始地址 delay_ms(100); } /*************************************************************************************** * @说明 设置显示位置 * @参数 row 垂直位置(0~1) * col 水平位置(0~15) * @返回值 无 * @注 无 ***************************************************************************************/ void lcd1602_GotoXY(unsigned char row, unsigned char col) { // send_str((unsigned char *)"\r\n<lcd1602_GotoXY>row="); // send_char_dec(row); // send_str((unsigned char *)",col="); // send_char_dec(col); if(row == 0) write_lcd1602_cmd(0x80 + col); if(row == 1) write_lcd1602_cmd(0x80 + 0x40 + col); } /*************************************************************************************** * @说明 使LCD在当前位置显示字符串 * @参数 str 字符串 * @返回值 无 * @注 无 ***************************************************************************************/ void LCD1602_Display_NoXY(unsigned char *str) { //send_str((unsigned char *)"\r\n<LCD1602_Display_NoXY>str="); //send_str(str); while(*str != '\0') { write_lcd1602_data(*str); str++; } } /*************************************************************************************** * @实现效果 IIC主机发送与接收 ***************************************************************************************/ void main() { //unsigned char arry[]="This is test!"; //unsigned char old_ptr = 0; // LCD显示字符位置 unsigned char sta=0; /*************************系统初始化*************************/ OSCCON = 0x04; //Fosc=32M Fcpu=4M(Fosc4分频 2T) /*************************IO初始化***************************/ ANSELA = 0x01; //PA0设为数字形式 TRISA = 0x01; //PA0为输出口-中断指示 ANSELC = 0xFF; //PC口的B0、B1、B2、B4、B6、B7为数字模式,对TSSOP20封装,只有PC0,PC1,PC2,PC4 // 对TSSOP20封装,PORTC2、PORTD4、PORTB6、可对应于IIC_SDA; // PORTC2和PORTB7可对应于IIC_SCK; // 此处使用PORTC2和PORTC0映射为IIC的SCK和SDA IICMAP = 0x11; // PORTC2为输出模式 //TRISC = 0x04; TRISC = 0x05; // PC2和PC0输出模式 /*************************IIC初始化*****************************/ // IICCON构成:CR2\IICEN\STA\STO\-\AA\CR1\CR0 // 0x40: CR2~CR0:000,Fcpu=4M模式下,通讯速度=15.63KHz // IICEN=1,启动IIC模块 // STA=0,不发送起始信号 // STO=0,不发送停止信号 // AA=1,回复ACK(SDA上为低电平) IICCON = 0x44; //启动IIC模块 /********************T0配置初始化********************/ OPTION = 0X07; // 分频寄存器配置256分频 T0CS = 0; // T0 模式选择寄存器:定时器模式,计数时钟Fcpu,休眠和绿色模式下停止 T0OSCEN = 0; // 禁止定时器模块0使用计数时钟 T0SE = 0; // 定时器模式,计数时钟Fcpu /********************UART配置初始化********************/ ANSELB = 0xC0; //PB7 PB6设为数字模式 TRISB = 0x40; //PB7设为输入模式 PB6设为输出模式 INTMAP = 0x20; UARTMAP = 0x50; //映射 PB7口作为RX PB6口作为TX // 波特率等设置 BRTH = 0xFF; BRTL = 0xE6; //Baudrate_9600 SCON2 = 0x24; //UART功能选择 8位 SCON |= 0x10; //使能UART接收 /********************中断设置********************/ T0IE = 1; //打开T0中断 //IICIE = 1; //允许IIC中断 PEIE = 1; //允许未屏蔽中断 GIE = 1; //允许总中断 send_str((unsigned char *)"\r\n<main>"); // 关闭中断指示LED PORTA0 = 1; // 初始化LCD lcd_init(); // 在第一行第一列显示字符A lcd1602_GotoXY(0,0); write_lcd1602_data('A'); // 在第二行第一列显示字符串:Test OK! lcd1602_GotoXY(1,0); LCD1602_Display_NoXY("Test OK!"); while(1) { } }