51单片机使用 8线/4线/PCF8574_I2C驱动 LCD1602
一、8线驱动
接线图(共需要 8 + 2 = 10 个 IO 控制)
#define LCD1602_Port P2 //LCD1602数据端口
//*****控制端口定义*****//
//RW 接地,只写不读
sbit LcdRs = P3 ^ 3;
sbit LcdEn = P3 ^ 2;
void LCD1602_Write(uchar style, uchar input)
{
LcdEn = 0;
if(style == 0){
LcdRs = 0; //命令'0'
}
else{
LcdRs = 1; //数据'1'
}
Delay1ms();
LCD1602_Port = input;
Delay1ms();
LcdEn = 1;
Delay1ms();
LcdEn = 0;
Delay1ms();
}
void LCD1602_Initial()
{
LcdEn = 0;
LCD1602_Write(0, 0x38); //8位数据端口,2行显示,5*7点阵
LCD1602_Write(0, 0x0c); //开启显示, 无光标
LCD1602_Write(0, 0x06); //AC递增, 画面不动
LCD1602_Write(0, 0x01); //清屏
Delay10ms();
}
void LCD1602_GotoXY(uchar x, uchar y) //X为显示指针的位置,即为各行的第几个位置,Y选行
{
if(x == 0)
LCD1602_Write(0, 0x80 + y);
if(x == 1)
LCD1602_Write(0, (0x80 + 0x40 + y));
}
void LCD1602_Display_NoXY(uchar *str) //向LCD写入字符串
{
while(*str != '\0') {
LCD1602_Write(1, *str);
str++;
}
}
void main(void)
{
LCD1602_Initial();
Delay10ms();
LCD1602_GotoXY(0, 2);
LCD1602_Display_NoXY("Hello Word!");
while(1);
}
二、4线驱动
接线图(共需要 4 + 2 = 6 个 IO 控制)
#define LCD_Port P0 //数据口
sbit RS = P3 ^ 5;
sbit EN = P3 ^ 4;
uchar table1[] = "LCD1602A DISPLAY"; //16
uchar table2[] = "By wxhntmy";//10
//LCD使能
void lcd_en_enable()
{
EN = 1;
Delay100us();
EN = 0;
Delay100us();
}
//写命令
void LCD_Write_Com(uchar cmd)
{
uchar high, low, temp;
high = cmd & 0xF0;
low = (cmd << 4) & 0xF0;
EN = 0;
Delay100us();
RS = 0;
temp = LCD_Port & 0x0F; //保持P0口低四位不变
LCD_Port = temp | high; //写入命令高四位
Delay100us();
lcd_en_enable();
temp = LCD_Port & 0x0F; //保持P0口低四位不变
LCD_Port = temp | low; //写入命令低四位
Delay100us();
lcd_en_enable();
}
//写数据
void LCD_Write_Data(uchar dat)
{
uchar high, low, temp;
high = dat & 0xF0;
low = (dat << 4) & 0xF0;
EN = 0;
Delay100us();
RS = 1;
temp = LCD_Port & 0x0F; //保持P0口低四位不变
LCD_Port = temp | high; //写入数据高四位
Delay100us();
lcd_en_enable();
temp = LCD_Port & 0x0F; //保持P0口低四位不变
LCD_Port = temp | low; //写入数据低四位
Delay100us();
lcd_en_enable();
}
//X为显示指针的位置,即为各行的第几个位置,Y选行
void LCD1602_GotoXY(uchar x, uchar y)
{
if(x == 0)
LCD_Write_Com(0x80 + y);
if(x == 1)
LCD_Write_Com(0x80 + 0x40 + y);
}
//向LCD写入字符串
void LCD1602_Display_NoXY(uchar *str)
{
while(*str != '\0') {
LCD_Write_Data(*str);
str++;
}
}
//初始化
void LCD_Init()
{
Delay200ms();//等待供电稳定
LCD_Write_Com(0x30);
Delay5ms();
LCD_Write_Com(0x30);
Delay5ms();
LCD_Write_Com(0x30);
Delay1ms();
LCD_Write_Com(0x20);
Delay1ms();
LCD_Write_Com(0x28);//设置4位格式,2行,5x7
lcd_en_enable();
LCD_Write_Com(0x28);//设置4位格式,2行,5x7
LCD_Write_Com(0x0C);//整体显示,关光标,不闪烁
LCD_Write_Com(0x06);//设定输入方式,增量不移位
LCD_Write_Com(0x01);//清除屏幕显示
Delay10ms();
}
//主函数
void main(void)
{
LCD_Init(); //液晶屏初始化
//显示字符串
LCD1602_GotoXY(0, 0);
LCD1602_Display_NoXY(table1);
LCD1602_GotoXY(1, 3);
LCD1602_Display_NoXY(table2);
while(1);
}
三、PCF8574(I2C) 驱动
1、PCF8574 介绍
- 操作电压 2.5~6.0V
- 低备用电流(≤10μA)
- I2C 并行口扩展电路
- 开漏中断输出
- I2C 总线 实现 8 位远程 I/O 口
- 与大多数 MCU 兼容
- 口输出锁存,具有大电流驱动能力,可直接驱动 LED
- 通过 3 个硬件地址引脚可寻址 8 个器件(PCF8574A 可多达 16 个)
- DIP16,SO16 或 SSOP20 形式封装
PCF8574 是 CMOS 电路。它通过两条双向总线(I2C)可使大多数 MCU 实现远程 I/O 口扩展。该器件包含一个 8 位准双向口和一个 I2C 总线接口。PCF8574 电流消耗很低,且口输出锁存具有大电流驱动能力,可直接驱动 LED。它还带有一条中断接线(INT)可与 MCU 的中断逻辑相连。通过 INT 发送中断信号,远端 I/O 口不必经过 I2C 总线通信就可通知 MCU 是否有数据从端口输入。这意味着 PCF8574可以作为一个单被控器。
PCF8574 和 PCF8574A 的唯一区别仅在于器件地址不相同。
PCF8574 和 PCF8574A 的从地址:
PCA8574 地址图(A2 A1 A0 悬空默认为1):
PCA8574A 地址图(A2 A1 A0 悬空默认为1):
写模式(IO 用作输出)
I2C 主机写入地址后,接着写入八位数据,这八位数据表示 PCF8574 八个 IO 的输出电平,例如写入的数据第 6 位为 1,则 PCF8574 的 P5 口输出高电平。
I2C 主机写一字节数据流程:start > write_write_address > master_receive_ack > write_data > master_receive_ack > stop
读模式(IO 用作输入)
I2C 主机写入地址后,接着读取八位数据,这八位数据表示 PCF8574 八个 IO 的输入电平,例如读取的数据第 6 位为 1,则 PCF8574 的 P5 口输入为高电平。
I2C 主机读一字节数据流程:start > write_read_address > master_receive_ack > receive_data > master_send_nack > stop
2、接线图
3、示例代码
#define LCD_I2C_ADDRESS 0x27 << 1 //I2C地址,需要左移一位
#define BACKLIGHT 0x08 //PCF8574 P3口,背光开关
#define En 0x04 //使能位,PCF8574 P2口
#define Rw 0x02 //读/写位,PCF8574 P1口
#define Rs 0x01 //选择位,PCF8574 P0口
#define setbit(x,y) x |= (1 << y) //指定的某一位数置1
#define clrbit(x,y) x &= ~(1 << y) //指定的某一位数置0
#define reversebit(x,y) x ^= (1 << y) //指定的某一位数取反
uchar table1[] = "LCD1602A PCF8574"; //16
uchar table2[] = "By wxhntmy";//10
//i2c写一字节数据
void i2c_write_char(uchar address, uchar dat)
{
start();
send_data(address);
recv_ack();
send_data(dat);
recv_ack();
stop();
}
//i2c读一字节数据
uchar i2c_read_char(uchar address)
{
uchar dat;
start();
send_data(address + 1);
recv_ack();
dat = recv_data();
send_nack();
stop();
return dat;
}
//LCD使能
void lcd_en_enable(uchar value)
{
i2c_write_char(LCD_I2C_ADDRESS, value | En); //EN = 1
Delay100us();
clrbit(value, 2);
i2c_write_char(LCD_I2C_ADDRESS, value); //EN = 0
Delay100us();
}
//lcd写命令/数据,mode=0命令,mode=1数据
void lcd_write(uchar mode, uchar cmd)
{
uchar high, low;
uchar temp;
high = cmd & 0xF0 | BACKLIGHT; //高四位
low = (cmd << 4) & 0xF0 | BACKLIGHT; //低四位变高四位
temp = i2c_read_char(LCD_I2C_ADDRESS); //获取引脚电平
temp = temp & 0xF0; //保留高四位电平
i2c_write_char(LCD_I2C_ADDRESS, temp | BACKLIGHT);//en = 0,写引脚时保持高四位电平不变
Delay100us();
if(mode == 0) {
i2c_write_char(LCD_I2C_ADDRESS, temp | BACKLIGHT);//rs = 0,写引脚时保持高四位电平不变
} else {
i2c_write_char(LCD_I2C_ADDRESS, temp | Rs | BACKLIGHT);//rs = 1,写引脚时保持高四位电平不变
}
temp = i2c_read_char(LCD_I2C_ADDRESS); //获取引脚电平
temp = temp & 0x0F; //保留低四位电平
i2c_write_char(LCD_I2C_ADDRESS, high | temp);//写高四位数据时保持低四位引脚电平不变
Delay100us();
lcd_en_enable(high | temp);
i2c_write_char(LCD_I2C_ADDRESS, low | temp);//写高四位数据时保持低四位引脚电平不变
Delay100us();
lcd_en_enable(low | temp);
}
//lcd初始化
void lcd_init()
{
uchar temp;
Delay50ms();
Delay50ms();
Delay50ms();
Delay50ms();//等待供电稳定
lcd_write(0, 0x30);
Delay5ms();
lcd_write(0, 0x30);
Delay5ms();
lcd_write(0, 0x30);
Delay1ms();
lcd_write(0, 0x20);
Delay1ms();
//用四线时,1602的初始化只需要高四位数据就可以完成,在初始化完成之后必须再传入四位数据,
//执行完“lcd_write(0, 0x28);”之后液晶已经初始化,其实在执行了一半的时候就已经初始化完成,
//此时又传入了四位数据(一个写语句会传入8位数据),这时候如果直接写数据的话,就会形成乱码,所以需要两次功能性初始化。
lcd_write(0, 0x28); //4位数据端口,2行显示,5*7点阵
temp = i2c_read_char(LCD_I2C_ADDRESS); //获取引脚电平
lcd_en_enable(temp);
lcd_write(0, 0x28); //4位数据端口,2行显示,5*7点阵
lcd_write(0, 0x0c); //开启显示, 无光标
lcd_write(0, 0x06); //AC递增, 画面不动
lcd_write(0, 0x01); //清屏
Delay50ms();
}
//Y为显示指针的位置,即为各行的第几个位置,X选行
//Y:0-15
//X:0-1
void LCD1602_GotoXY(uchar x, uchar y)
{
if(x == 0)
lcd_write(0, 0x80 + y);
if(x == 1)
lcd_write(0, (0x80 + 0x40 + y));
}
//向LCD写入字符串
void LCD1602_Display_NoXY(uchar *str)
{
while(*str != '\0') {
lcd_write(1, *str);
str++;
}
}
void main()
{
init_i2c();
lcd_init();
//显示字符串
LCD1602_GotoXY(0, 0);
LCD1602_Display_NoXY(table1);
LCD1602_GotoXY(1, 3);
LCD1602_Display_NoXY(table2);
while(1);
}