樹莓派通過I2C驅動LCD1602顯示屏

問題來源

在此之前學習了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;
	else
		temp &= 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 firstly
	buf = comm & 0xF0;
	buf |= 0x04;			// RS = 0(低電平0時選擇指令寄存器), RW = 0(此時發送指令), EN = 1
	write_word(buf);
	delay(2);
	buf &= 0xFB;			// Make EN = 0,EN從1——>0,下降沿,進行寫操作
	write_word(buf);

	// Send bit3-0 secondly
	buf = (comm & 0x0F) << 4;
	buf |= 0x04;			// RS = 0, RW = 0, EN = 1
	write_word(buf);
	delay(2);
	buf &= 0xFB;			// Make EN = 0
	write_word(buf);
}
/*******************  問題    ***********************************
此處發送數據的函數與Arduino鎖使用的LiquidCrystal_I2C.h函數區別很大;
問題是不論是發送命令還是發送數據,所傳送的一個字節的後4位都是用於
控制的,只有高4位纔是具有實際意義的,不知道爲什麼。官方在wiringPiI2C.h
介紹中wiringPiI2CWrite()函數介紹比較簡單。
****************************************************************/
//發送數據的函數
void send_data(int data){
	int buf;
	// Send bit7-4 firstly
	buf = data & 0xF0;
	buf |= 0x05;			// RS = 1, RW = 0, EN = 1
	write_word(buf);
	delay(2);
	buf &= 0xFB;			// Make EN = 0
	write_word(buf);

	// Send bit3-0 secondly
	buf = (data & 0x0F) << 4;
	buf |= 0x05;			// RS = 1, RW = 0, EN = 1
	write_word(buf);
	delay(2);
	buf &= 0xFB;			// Make EN = 0
	write_word(buf);
}

//初始化函數
void init(){
	send_command(0x33);	// Must initialize to 8-line mode at first
	delay(5);
	send_command(0x32);	// Then initialize to 4-line mode
	delay(5);
	send_command(0x28);	// 2 Lines & 5*7 dots
	delay(5);
	send_command(0x0C);	// Enable display without cursor
	delay(5);
	send_command(0x01);	// Clear Screen
	wiringPiI2CWrite(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 = 1
	write_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 firstly
	buf = comm & 0xF0;
	buf |= 0x04;			// RS = 0, RW = 0, EN = 1
	write_word(buf);
	delay(2);
	buf &= 0xFB;			// Make EN = 0
	write_word(buf);
	printf("send_command buf bit7-4 is %d\n", buf);
	delay(2000);

	// Send bit3-0 secondly
	buf = (comm & 0x0F) << 4;
	buf |= 0x04;			// RS = 0, RW = 0, EN = 1
	write_word(buf);
	delay(2);
	buf &= 0xFB;			// Make EN = 0
	write_word(buf);
	printf("send_command buf bit3-0 is %d\n", buf);
}

void send_data(int data){
	int buf;
	// Send bit7-4 firstly
	buf = data & 0xF0;
	buf |= 0x05;			// RS = 1, RW = 0, EN = 1
	write_word(buf);
	delay(2);
	buf &= 0xFB;			// Make EN = 0
	write_word(buf);
	printf("send_data buf bit7-4 is   %d\n", buf);
	delay(2000);


	// Send bit3-0 secondly
	buf = (data & 0x0F) << 4;
	buf |= 0x05;			// RS = 1, RW = 0, EN = 1
	write_word(buf);
	delay(2);
	buf &= 0xFB;			// Make EN = 0
	write_word(buf);
	printf("send_data buf bit3-0 is %d\n",buf);
}

void init(){
	send_command(0x33);	// Must initialize to 8-line mode at first
	delay(5);
	send_command(0x32);	// Then initialize to 4-line mode
	delay(5);
	send_command(0x28);	// 2 Lines & 5*7 dots
	delay(5);
	send_command(0x0C);	// Enable display without cursor
	delay(5);
	send_command(0x01);	// Clear Screen
	wiringPiI2CWrite(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
	addr = 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()輸出格式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章