问题来源
在此之前学习了Arduino驱动LCD1602(Arduino通过I2C控制1602LCD显示屏),其过程比较简单,现在想通过树莓派实现控制功能,提升树莓派编程控制能力,在此过程中虽然能正常让LCD显示文字,但是对于网上代码有些不理解,遂形成这篇博文,希望有人能解答文末问题。
LCD1602知识
-
LCD1602引脚
-
指令寄存器(IR)和数据寄存器(DR)
本系列模块内部具有两个 8 位寄存器:指令寄存器(IR)和数据寄存器(DR)。用户可以通过 RS 和 R/W 输入信号的组合选择指定的寄存器,进行相应的操作。下表中列出了组合选择方式:
-
LCD1602的基本操作
1. 读状态:输入RS=0,RW=1,E=高脉冲。输出:D0—D7为状态字。
2. 读数据:输入RS=1,RW=1,E=高脉冲。输出:D0—D7为数据。
3. 写命令:输入RS=0,RW=0,E=低脉冲。输出:无。(写完置E=高脉冲)
4. 写数据:输入RS=1,RW=0,E=低脉冲。输出:无。
注意:E(或EN)端为使能(enable)端,写操作时,下降沿使能。读操作时,E高电平有效 -
显示位置设置
若想在00H处显示数据的话,则必须将00H加上80H,即0x00H+0x80H,若要在01H处显示数据,也必须加0x80H, 例如要将某字符显示在第2行第5列,则确定地址的指令代码应为80H+44H=C4H。依次类推
I2C转接板引脚
- I2C引脚
GND ------ 地线
VCC ------ 电源(5V or 3.3v 电源不同显示效果有点差别)
SDA ------ I2C 数据线
SCL ------ I2C 时钟线
- 树莓派40Pin引脚图
这个接线还是比较简单的。
树莓派设置
- 安装wiringPi库
相关操作自行搜索,有很多。也可以参看官方指南 - 开启I2C
树莓派需要开启I2C功能,这个可以通过VNC远程或者直接树莓派接显示屏进行设置,相关操作搜索一下有很多。一个是在raspi-config中设置,另一个是在图形界面start按钮中修改树莓派preference。注意:这两个有可能是重复的,我不清楚,我是啷个都设置了
程序代码
将下列代码保存为".c"文本,树莓派终端编译运行即可。在此请看下一节对代码提出的一个问题,我没搞懂。此外,程序中的初始化未必是必须的,因为我看1602手册时说上电电压超过4.5V时会自动初始化。
#include <stdio.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include <string.h>int LCDAddr = 0x27; //LCD设备地址
int BLEN = 1; //BLEN是控制啥的
int fd;void write_word(int data){int temp = data;//BLEN是控制啥的 if ( BLEN == 1 )temp |= 0x08;elsetemp &= 0xF7;wiringPiI2CWrite(fd, temp);
}
/****************************************************************
RS为寄存器选择,高电平1时选择数据寄存器、低电平0时选择指令寄存器
R/W为读写选择,高电平(1)时进行读操作,低电平(0)时进行写操作。
E(或EN)端为使能(enable)端,写操作时,下降沿使能。读操作时,E高电平有效
****************************************************************///发送命令的函数
void send_command(int comm){int buf;// Send bit7-4 firstlybuf = comm & 0xF0;buf |= 0x04; // RS = 0(低电平0时选择指令寄存器), RW = 0(此时发送指令), EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0,EN从1——>0,下降沿,进行写操作write_word(buf);// Send bit3-0 secondlybuf = (comm & 0x0F) << 4;buf |= 0x04; // RS = 0, RW = 0, EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0write_word(buf);
}
/******************* 问题 ***********************************
此处发送数据的函数与Arduino锁使用的LiquidCrystal_I2C.h函数区别很大;
问题是不论是发送命令还是发送数据,所传送的一个字节的后4位都是用于
控制的,只有高4位才是具有实际意义的,不知道为什么。官方在wiringPiI2C.h
介绍中wiringPiI2CWrite()函数介绍比较简单。
****************************************************************/
//发送数据的函数
void send_data(int data){int buf;// Send bit7-4 firstlybuf = data & 0xF0;buf |= 0x05; // RS = 1, RW = 0, EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0write_word(buf);// Send bit3-0 secondlybuf = (data & 0x0F) << 4;buf |= 0x05; // RS = 1, RW = 0, EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0write_word(buf);
}//初始化函数
void init(){send_command(0x33); // Must initialize to 8-line mode at firstdelay(5);send_command(0x32); // Then initialize to 4-line modedelay(5);send_command(0x28); // 2 Lines & 5*7 dotsdelay(5);send_command(0x0C); // Enable display without cursordelay(5);send_command(0x01); // Clear ScreenwiringPiI2CWrite(fd, 0x08);
}
//清屏
void clear(){send_command(0x01); //clear Screen
}void write(int x, int y, char data[]){int addr, i;int tmp;if (x < 0) x = 0;if (x > 15) x = 15;if (y < 0) y = 0;if (y > 1) y = 1;// Move cursor//第一行地址起止为00H,第二行起止为40H;//但这个0x80干嘛的,为什么要加?addr = 0x80 + 0x40 * y + x;send_command(addr);tmp = strlen(data);for (i = 0; i < tmp; i++){send_data(data[i]);}
}void main(){fd = wiringPiI2CSetup(LCDAddr);init();write(0, 0, "Greetings!");write(1, 1, "WWW.HNZHIYU.CN");delay(2000);//clear();
}
问题
此处发送数据的函数与Arduino锁使用的LiquidCrystal_I2C.h函数区别很大;问题是不论是发送命令还是发送数据,所传送的一个字节的后4位都是用于控制的,只有高4位才是具有实际意义的,不知道为什么。官方在wiringPiI2C.h介绍中wiringPiI2CWrite()函数介绍比较简单。
而LCD1602的手册中发送数据是可以一次发送8位的,如下图,
不知是否有专业人士解答一下这个。
另外在写数据是,写数据函数执行了两次。我觉得原因是第一次只是是EN变成高电平,第二次是EN变成低电平,因为EN是下降沿有效。
buf = comm & 0xF0;buf |= 0x04; // RS = 0(低电平0时选择指令寄存器), RW = 0(此时发送指令), EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0,EN从1——>0,下降沿,进行写操作write_word(buf);
解答
- 地址中0x80的原因
0x80是因为在设置DDRAM地址时,DB7固定是为1的。DB7是BF忙碌标志位,
- 输出字符与命令为什么分两次
因为LCD1602字符发生器RAM中 GROM 中,字符码与字符字模之间的对应关系决定。每个字符分高四位与第四位,对应关系如下图,可以看到字母A高四位为LHLL,第四位为LLLH,即0100 0001,在上述程序输出字符的函数中,每次输出时添加打印函数printf()输出到终端,可以发现字母A的两次输出数值为65(0100 0001),与17(0001 0001),这两个数字高四位拼起来为 0100 0001,即与字符A对应。
在附上另一种表达形式的表
- 附验证所需代码,去除了标注
#include <stdio.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include <string.h>int LCDAddr = 0x27;
int BLEN = 1;
int fd;void write_word(int data){int temp = data;if ( BLEN == 1 )temp |= 0x08;else{temp &= 0xF7;printf("BLEN is not 1\n");}wiringPiI2CWrite(fd, temp);
}void send_command(int comm){int buf;// Send bit7-4 firstlybuf = comm & 0xF0;buf |= 0x04; // RS = 0, RW = 0, EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0write_word(buf);printf("send_command buf bit7-4 is %d\n", buf);delay(2000);// Send bit3-0 secondlybuf = (comm & 0x0F) << 4;buf |= 0x04; // RS = 0, RW = 0, EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0write_word(buf);printf("send_command buf bit3-0 is %d\n", buf);
}void send_data(int data){int buf;// Send bit7-4 firstlybuf = data & 0xF0;buf |= 0x05; // RS = 1, RW = 0, EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0write_word(buf);printf("send_data buf bit7-4 is %d\n", buf);delay(2000);// Send bit3-0 secondlybuf = (data & 0x0F) << 4;buf |= 0x05; // RS = 1, RW = 0, EN = 1write_word(buf);delay(2);buf &= 0xFB; // Make EN = 0write_word(buf);printf("send_data buf bit3-0 is %d\n",buf);
}void init(){send_command(0x33); // Must initialize to 8-line mode at firstdelay(5);send_command(0x32); // Then initialize to 4-line modedelay(5);send_command(0x28); // 2 Lines & 5*7 dotsdelay(5);send_command(0x0C); // Enable display without cursordelay(5);send_command(0x01); // Clear ScreenwiringPiI2CWrite(fd, 0x08);
}void clear(){send_command(0x01); //clear Screen
}void write(int x, int y, char data[]){int addr, i;int tmp;if (x < 0) x = 0;if (x > 15) x = 15;if (y < 0) y = 0;if (y > 1) y = 1;// Move cursoraddr = 0x80 + 0x40 * y + x;send_command(addr);tmp = strlen(data);for (i = 0; i < tmp; i++){send_data(data[i]);
// wiringPiI2CWrite(fd, data[i]);printf("Now %c is printed\n", data[i]);}
}void main(){fd = wiringPiI2CSetup(LCDAddr);init();write(0, 0, "A1 B2!");write(1, 4, "");delay(2000);//clear();
}
参考
http://www.elecfans.com/xianshi/20171020567470.html
http://www.51hei.com/bbs/dpj-83245-1.html
http://bbs.elecfans.com/jishu_451276_1_4.html
LCD1602模块如何显示自定义字符
printf()输出格式