在另外一个项目中,无头联网设备需要显示当前的设备的IP地址,方便操作,所以用了一块LCD1602来显示。
因为LCD1602的使用非常简单,所以很方便的就能在Purple Pi开发板上使用。
LCD1602就是两行字符,每行16个,控制指令非常简单明了。
我手头的这块LCD1602已经添加了I2C模块:
所以,可以直接I2C接口来发送数据进行控制。
I2C接口的具体位置,可以查看背面丝印:
最下面,就有I2C0的接口,把LCD1602-IIC直接接上开干。
从这块板子的I2C模块上,可以看到A0、A1、A2都没有连接:
那么根据datasheet,可以得到其地址:
三开,所以地址就是0x0f了
我买的这个LCD1602,店家直接提供了标准的Linux下的驱动库程序:
#ifndef LCD1602_H
#define LCD1602_H
int lcd1602Init(int iChannel, int iAddr);
void lcd1602SetPulsePeriod(int val);
int lcd1602SetCursor(int x, int y);
int lcd1602Control(int bBacklight, int bCursor, int bBlink);
int lcd1602WriteString(char *szText);
int lcd1602Clear(void);
void lcd1602Shutdown(void);
#endif
#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>
#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 ret = write(file_i2c_hd, buf, len);
return ret;
}
static void lcd1602_writeCommand(unsigned char ucCMD)
{
unsigned char uc;
uc = (ucCMD & 0xf0) | iBackLight;
lcd1602_write(file_i2c, &uc, 1);
usleep(iPulsePeriod);
uc |= 4;
lcd1602_write(file_i2c, &uc, 1);
usleep(iPulsePeriod);
uc &= ~4;
lcd1602_write(file_i2c, &uc, 1);
usleep(CMD_PERIOD);
uc = iBackLight | (ucCMD << 4);
lcd1602_write(file_i2c, &uc, 1);
usleep(iPulsePeriod);
uc |= 4;
lcd1602_write(file_i2c, &uc, 1);
usleep(iPulsePeriod);
uc &= ~4;
lcd1602_write(file_i2c, &uc, 1);
usleep(CMD_PERIOD);
}
int lcd1602Control(int bBacklight, int bCursor, int bBlink)
{
unsigned char ucCMD = 0xc;
if (file_i2c < 0)
return 1;
iBackLight = (bBacklight) ? BACKLIGHT : 0;
if (bCursor)
ucCMD |= 2;
if (bBlink)
ucCMD |= 1;
lcd1602_writeCommand(ucCMD);
return 0;
}
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;
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;
lcd1602_write(file_i2c, ucTemp, 1);
usleep(iPulsePeriod);
ucTemp[0] &= ~4;
lcd1602_write(file_i2c, ucTemp, 1);
usleep(CMD_PERIOD);
text++;
i++;
}
return 0;
}
int lcd1602Clear(void)
{
if (file_i2c < 0)
return 1;
lcd1602_writeCommand(0x0E);
return 0;
}
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;
lcd1602_writeCommand(0x02);
lcd1602_writeCommand(0x28);
lcd1602_writeCommand(0x0c);
lcd1602_writeCommand(0x06);
lcd1602_writeCommand(0x80);
lcd1602Clear();
return 0;
}
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;
}
void lcd1602SetPulsePeriod(int val)
{
iPulsePeriod = val;
}
void lcd1602Shutdown(void)
{
iBackLight = 0;
lcd1602_writeCommand(0x08);
close(file_i2c);
file_i2c = -1;
}
上面的程序,经过一些小的修改,方便在其他地方调用。
另外,还需要获得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;
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 (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");
}
lcd1602SetCursor(0, 0);
sprintf(str, "%-16s", "Hello World!");
lcd1602WriteString(str);
lcd1602SetCursor(0, 1);
sprintf(str, "%-16s", "I'm Purple Pi!");
lcd1602WriteString(str);
sleep(3);
lcd1602SetCursor(0, 0);
sprintf(str, "%-16s", "My IP addr is:");
lcd1602WriteString(str);
lcd1602SetCursor(0, 1);
sprintf(str, "%-16s", ip);
lcd1602WriteString(str);
sleep(5);
lcd1602SetCursor(0, 0);
lcd1602WriteString(str);
lcd1602SetCursor(0, 1);
sprintf(str, "%-16s", "ENTER to quit");
lcd1602WriteString("ENTER to quit");
lcd1602Control(1, 0, 1);
printf("ENTER to quit...\n");
getchar();
lcd1602Shutdown();
return 0;
}
上述代码的逻辑,从上往下走,比较简单:
- 获取wlan0的ip地址;因为使用了无线联网
- 使用I2C0,地址0x3f,初始化LCD1602
- 输出Hello World信息
- 输出IP信息
- 输出回车退出信息
- 闪烁光标
要编译上面的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/再执行】
LCD1602就能显示出IP地址了。
虽然只能显示16*2的字符,但是作为一些简短信息的显示,还是非常方便的。