ARM技术论坛
直播中

HonestQiao

9年用户 577经验值
擅长:嵌入式技术
私信 关注
[经验]

【触觉智能 Purple Pi开发板试用】用简单实用的LCD1602显示IP

LCD1602

在另外一个项目中,无头联网设备需要显示当前的设备的IP地址,方便操作,所以用了一块LCD1602来显示。
因为LCD1602的使用非常简单,所以很方便的就能在Purple Pi开发板上使用。

LCD1602就是两行字符,每行16个,控制指令非常简单明了。

我手头的这块LCD1602已经添加了I2C模块:
image.png

所以,可以直接I2C接口来发送数据进行控制。

I2C接口的具体位置,可以查看背面丝印:

1.底板图.jpeg

最下面,就有I2C0的接口,把LCD1602-IIC直接接上开干。
image.png

image.png

从这块板子的I2C模块上,可以看到A0、A1、A2都没有连接:
image.png

那么根据datasheet,可以得到其地址:
image.png
三开,所以地址就是0x0f了

我买的这个LCD1602,店家直接提供了标准的Linux下的驱动库程序:

  • lcd1602.h:
#ifndef LCD1602_H
#define LCD1602_H
//
// LCD1602 2 line by 16 character LCD library
//
// Written by Larry Bank - 12/6/2017
// Copyright (c) 2017 BitBank Software, Inc.
// bitbank@pobox.com
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

//
// Opens a file system handle to the I2C device
// Turns on the LCD, sets the 4-bit mode and clears the memory
// Returns 0 for success, 1 for failure
//
int lcd1602Init(int iChannel, int iAddr);

//
// Set the LCD Pulse Period
//
void lcd1602SetPulsePeriod(int val);

//
// Set the cursor position on the LCD
//
int lcd1602SetCursor(int x, int y);

//
// Control the backlight, cursor, and blink
//
int lcd1602Control(int bBacklight, int bCursor, int bBlink);

//
// Print a zero-terminated character string
// Only 1 line at a time can be accessed
//
int lcd1602WriteString(char *szText);

//
// Clear the characters from the LCD
//
int lcd1602Clear(void);

//
// Turn off the LCD and backlight
// close the I2C file handle
//
void lcd1602Shutdown(void);

#endif // LCD1602_H
  • lcd1602.c
//
// 2x16 LCD display (HD44780 controller + I2C chip)
//
// Copyright (c) BitBank Software, Inc.
// Written by Larry Bank
// bitbank@pobox.com
// Project started 12/6/2017
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/i2c-dev.h>
//
// The LCD controller is wired to the I2C port expander with the upper 4 bits
// (D4-D7) connected to the data lines and the lower 4 bits (D0-D3) used as
// control lines. Here are the control line definitions:
//
// Command (0) / Data (1) (aka RS) (D0)
// R/W                             (D1)
// Enable/CLK                      (D2)
// Backlight control               (D3)
//
// The data must be manually clocked into the LCD controller by toggling
// the CLK line after the data has been placed on D4-D7
//
#define PULSE_PERIOD 500
#define CMD_PERIOD 4100

#define BACKLIGHT 8
#define DATA 1
static int iBackLight = BACKLIGHT;
static int file_i2c = -1;
static int iPulsePeriod = PULSE_PERIOD;

static int lcd1602_write(int file_i2c_hd, const void *buf, size_t len)
{
	// int j;
	// for(j = 0; j < len; ++j)
	// 	printf("HEX: %02x\n", ((uint8_t*) buf)[j]);
	int ret = write(file_i2c_hd, buf, len);
	return ret;
}

static void lcd1602_writeCommand(unsigned char ucCMD)
{
	unsigned char uc;

	uc = (ucCMD & 0xf0) | iBackLight; // most significant nibble sent first
	lcd1602_write(file_i2c, &uc, 1);
	usleep(iPulsePeriod); // manually pulse the clock line
	uc |= 4;			  // enable pulse
	lcd1602_write(file_i2c, &uc, 1);
	usleep(iPulsePeriod);
	uc &= ~4; // toggle pulse
	lcd1602_write(file_i2c, &uc, 1);
	usleep(CMD_PERIOD);
	uc = iBackLight | (ucCMD << 4); // least significant nibble
	lcd1602_write(file_i2c, &uc, 1);
	usleep(iPulsePeriod);
	uc |= 4; // enable pulse
	lcd1602_write(file_i2c, &uc, 1);
	usleep(iPulsePeriod);
	uc &= ~4; // toggle pulse
	lcd1602_write(file_i2c, &uc, 1);
	usleep(CMD_PERIOD);

} /* lcd1602_writeCommand() */

//
// Control the backlight, cursor, and blink
// The cursor is an underline and is separate and distinct
// from the blinking block option
//
int lcd1602Control(int bBacklight, int bCursor, int bBlink)
{
	unsigned char ucCMD = 0xc; // display control

	if (file_i2c < 0)
		return 1;
	iBackLight = (bBacklight) ? BACKLIGHT : 0;
	if (bCursor)
		ucCMD |= 2;
	if (bBlink)
		ucCMD |= 1;
	lcd1602_writeCommand(ucCMD);

	return 0;
} /* lcd1602Control() */

//
// lcd1602_write an ASCII string (up to 16 characters at a time)
//
int lcd1602WriteString(char *text)
{
	unsigned char ucTemp[2];
	int i = 0;

	if (file_i2c < 0 || text == NULL)
		return 1;

	while (i < 16 && *text)
	{
		ucTemp[0] = iBackLight | DATA | (*text & 0xf0);
		lcd1602_write(file_i2c, ucTemp, 1);
		usleep(iPulsePeriod);
		ucTemp[0] |= 4; // pulse E
		lcd1602_write(file_i2c, ucTemp, 1);
		usleep(iPulsePeriod);
		ucTemp[0] &= ~4;
		lcd1602_write(file_i2c, ucTemp, 1);
		usleep(iPulsePeriod);
		ucTemp[0] = iBackLight | DATA | (*text << 4);
		lcd1602_write(file_i2c, ucTemp, 1);
		ucTemp[0] |= 4; // pulse E
		lcd1602_write(file_i2c, ucTemp, 1);
		usleep(iPulsePeriod);
		ucTemp[0] &= ~4;
		lcd1602_write(file_i2c, ucTemp, 1);
		usleep(CMD_PERIOD);
		text++;
		i++;
	}
	return 0;
} /* lcd1602_writeString() */

//
// Erase the display memory and reset the cursor to 0,0
//
int lcd1602Clear(void)
{
	if (file_i2c < 0)
		return 1;
	lcd1602_writeCommand(0x0E); // clear the screen
	return 0;

} /* lcd1602Clear() */

//
// Open a file handle to the I2C device
// Set the controller into 4-bit mode and clear the display
// returns 0 for success, 1 for failure
//
int lcd1602Init(int iChannel, int iAddr)
{
	char szFile[32];
	int rc;

	sprintf(szFile, "/dev/i2c-%d", iChannel);
	file_i2c = open(szFile, O_RDWR);
	if (file_i2c < 0)
	{
		fprintf(stderr, "Error opening i2c device; not running as sudo?\n");
		return 1;
	}
	rc = ioctl(file_i2c, I2C_SLAVE, iAddr);
	if (rc < 0)
	{
		close(file_i2c);
		fprintf(stderr, "Error setting I2C device address\n");
		return 1;
	}
	iBackLight = BACKLIGHT;		// turn on backlight
	lcd1602_writeCommand(0x02); // Set 4-bit mode of the LCD controller
	lcd1602_writeCommand(0x28); // 2 lines, 5x8 dot matrix
	lcd1602_writeCommand(0x0c); // display on, cursor off
	lcd1602_writeCommand(0x06); // inc cursor to right when writing and don't scroll
	lcd1602_writeCommand(0x80); // set cursor to row 1, column 1
	lcd1602Clear();				// clear the memory

	return 0;
} /* lcd1602Init() */

//
// Set the LCD cursor position
//
int lcd1602SetCursor(int x, int y)
{
	unsigned char cCmd;

	if (file_i2c < 0 || x < 0 || x > 15 || y < 0 || y > 1)
		return 1;

	cCmd = (y == 0) ? 0x80 : 0xc0;
	cCmd |= x;
	lcd1602_writeCommand(cCmd);
	return 0;

} /* lcd1602SetCursor() */

//
// Set the LCD Pulse Period
//
void lcd1602SetPulsePeriod(int val)
{
	iPulsePeriod = val;
} /* lcd1602SetPulsePeriod() */

void lcd1602Shutdown(void)
{
	iBackLight = 0;				// turn off backlight
	lcd1602_writeCommand(0x08); // turn off display, cursor and blink
	close(file_i2c);
	file_i2c = -1;
} /* lcd1602Shutdown() */

上面的程序,经过一些小的修改,方便在其他地方调用。

另外,还需要获得IP的程序,经过了解,得到如下的代码:

// 获取本机ip  eth_inf网卡名称 调用方法get_local_ip("wlan0");
int get_local_ip(const char *eth_inf)
{
    int sd;
 
    sd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sd)
    {
        printf("socket error: %s\n", strerror(errno));
        return -1;
    }
 
    strncpy(ifr.ifr_name, eth_inf, IFNAMSIZ);
    ifr.ifr_name[IFNAMSIZ - 1] = 0;
 
    // if error: No such device  
    if (ioctl(sd, SIOCGIFADDR, &ifr) < 0)
    {
        printf("ioctl error: %s\n", strerror(errno));
        close(sd);
        return -1;
    }
    
    printf("interfac: %s, ip: %s\n", eth_inf, inet_ntoa(((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr)); 

    close(sd);
    return 0;
}

有了上面的程序,就可以直接编写主程序lcd1602_demo.c了:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>  
#include <net/if.h>  
#include <arpa/inet.h> 
#include <sys/ioctl.h>  
#include <sys/types.h>  
#include <sys/time.h> 

#include "include/lcd1602.h"

struct ifreq ifr;

// 获取本机ip  eth_inf网卡名称 调用方法get_local_ip("wlan0");
int get_local_ip(const char *eth_inf)
{
    int sd;
 
    sd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sd)
    {
        printf("socket error: %s\n", strerror(errno));
        return -1;
    }
 
    strncpy(ifr.ifr_name, eth_inf, IFNAMSIZ);
    ifr.ifr_name[IFNAMSIZ - 1] = 0;
 
    // if error: No such device  
    if (ioctl(sd, SIOCGIFADDR, &ifr) < 0)
    {
        printf("ioctl error: %s\n", strerror(errno));
        close(sd);
        return -1;
    }
    
    printf("interfac: %s, ip: %s\n", eth_inf, inet_ntoa(((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr)); 

    close(sd);
    return 0;
}

int main(int argc, char *argv[])
{
	int rc;
	char str[17] = {0};
	char ip[16] = {0};

	get_local_ip("wlan0");
	sprintf(ip, "%s", inet_ntoa(((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr));

	rc = lcd1602Init(0, 0x3f);
	if (rc)
	{
		printf("Initialization LCD1602 failed; aborting...\n");
		return 0;
	} else {
		printf("Initialization LCD1602 ok!\n");
	}

	// welcome
	lcd1602SetCursor(0, 0);
	sprintf(str, "%-16s", "Hello World!");
	lcd1602WriteString(str);
	lcd1602SetCursor(0, 1);
	sprintf(str, "%-16s", "I'm Purple Pi!");
	lcd1602WriteString(str);

	sleep(3);

	// ip
	lcd1602SetCursor(0, 0);
	sprintf(str, "%-16s", "My IP addr is:");
	lcd1602WriteString(str);
	lcd1602SetCursor(0, 1);
	sprintf(str, "%-16s", ip);
	lcd1602WriteString(str);

	sleep(5);

	// quit
	lcd1602SetCursor(0, 0);
	lcd1602WriteString(str);
	lcd1602SetCursor(0, 1);
	sprintf(str, "%-16s", "ENTER to quit");
	lcd1602WriteString("ENTER to quit");

	// blonk
	lcd1602Control(1, 0, 1); // backlight on, underline off, blink block on

	printf("ENTER to quit...\n");
	getchar();

	// shutdown
	lcd1602Shutdown();
	return 0;
} /* main() */

上述代码的逻辑,从上往下走,比较简单:

  1. 获取wlan0的ip地址;因为使用了无线联网
  2. 使用I2C0,地址0x3f,初始化LCD1602
  3. 输出Hello World信息
  4. 输出IP信息
  5. 输出回车退出信息
  6. 闪烁光标

要编译上面的LCD1602驱动库程序,还需要编写一个Makefile:

CFLAGS=-c -Wall -O2
LIBS = -lm -lpthread

all: liblcd1602.a

liblcd1602.a: lcd1602.o
	$(AR) -rc liblcd1602.a lcd1602.o ;\
	cp liblcd1602.a ./lib ;\
	cp lcd1602.h ./include

lcd1602.o: lcd1602.c
	$(CC) $(CFLAGS) lcd1602.c

clean:
	rm *.o liblcd1602.a

首先交叉编译库文件和demo程序,然后在上传到开发板:

mkdir lib include
make CC=arm-unknown-linux-gnueabihf-gcc AR=arm-unknown-linux-gnueabihf-ar

arm-unknown-linux-gnueabihf-gcc lcd1602_demo.c -lm -llcd1602 -lpthread -L./lib -o lcd1602_demo

ls -lh lcd1602_demo

scp lcd1602_demo root@192.168.1.30:/root/

编译上传完成后,到开发板上执行:【注意cd切换到/root/再执行】
image.png

LCD1602就能显示出IP地址了。
image.png

虽然只能显示16*2的字符,但是作为一些简短信息的显示,还是非常方便的。

LCD1602

更多回帖

发帖
×
20
完善资料,
赚取积分