#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <errno.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/gpio.h>
#include <dev/spi/spi_io.h>
#define GPIO "/dev/gpio0"
#define PIN_RESET 12
#define PIN_SELECT 25
#define SPI "/dev/spi0"
#define MODE 3
#define ADDR 0
#define _X(a,b) ((a)?(b):0)
/* table 0 */
#define CLEAR_DISPLAY 1
#define RETURN_HOME 2
#define ENTRY_MODE_SET(id,s) (0x04 | ((id)?2:0) | ((s)?1:0))
#define DISPLAY_ONOFF(d,c,b) (0x08 | _X(d,4) | _X(c,2) | _X(b,1))
#define CURSOR_OR_DISPLAY_SHIFT(sc,rl) (0x10 | _X(sc,8) | _X(rl,4))
#define FUNCTION_SET(dl,n,dh,is) (0x20 | _X(dl,16) | _X(n,8) | _X(dh,4) | ((is) & 0x3))
#define SET_CGRAM(ac) (0x40 | ((ac) & 0x3f))
#define SET_DDRAM(ac) (0x80 | ((ac) & 0x7f))
/* table 1 */
#define BIAS_SET(bs,fx) (0x10 | _X(bs,8) | _X(fx,1))
#define SET_IRAM(ac) (0x40 | ((ac) & 0xf))
#define POWER_SET(ion,bon,c) (0x50 | _X(ion,8) | _X(bon,4) | (((c)>>4)&0x3))
#define FOLLOWER_CONTROL(fon,rab) (0x60 | _X(fon,8) | ((rab) & 0x7))
#define CONTRAST_SET(c) (0x70 | ((c) & 0xf))
/* table 2 */
#define DOUBLE_HEIGHT(ud) (0x10 | _X(ud,8))
typedef struct {
int gfd;
int sfd;
int select_pin;
int reset_pin;
int rows;
int columns;
int addr;
int enabled;
int cursor;
int blink;
int dbl;
} lcd_t;
void
delay(double seconds)
{
unsigned hz = 100;
unsigned micros = 1000000u / hz;
useconds_t ticks = ceil(seconds * hz);
usleep(ticks * micros);
}
int
gpio_set(int fd, int pin, int val)
{
struct gpio_req gp;
gp.gp_name[0] = '\0';
gp.gp_pin = pin;
gp.gp_value = val;
if (ioctl(fd, GPIOWRITE, &gp) == -1)
return errno;
return 0;
}
int
spi_configure(int fd, int addr, uint32_t mode, uint32_t speed)
{
spi_ioctl_configure_t sic;
sic.sic_addr = addr;
sic.sic_mode = mode;
sic.sic_speed = speed;
if (ioctl(fd, SPI_IOCTL_CONFIGURE, &sic) == -1)
return errno;
return 0;
}
int
spi_transfer(int fd, int addr, void *send, size_t slen, void *recv, size_t rlen)
{
spi_ioctl_transfer_t sit;
sit.sit_addr = addr;
sit.sit_send = send;
sit.sit_sendlen = slen;
sit.sit_recv = recv;
sit.sit_recvlen = rlen;
if (ioctl(fd, SPI_IOCTL_TRANSFER, &sit) == -1)
return errno;
return 0;
}
int
read_busy(lcd_t *lcd)
{
uint8_t buf;
spi_transfer(lcd->sfd, lcd->addr, NULL, 0, &buf, 1);
return (int)buf;
}
void
write_char(lcd_t *lcd, uint8_t c)
{
gpio_set(lcd->gfd, lcd->select_pin, GPIO_PIN_HIGH);
spi_transfer(lcd->sfd, lcd->addr, &c, 1, NULL, 0);
delay(50e-6);
}
void
write_string(lcd_t *lcd, uint8_t *s)
{
gpio_set(lcd->gfd, lcd->select_pin, GPIO_PIN_HIGH);
while (*s) {
spi_transfer(lcd->sfd, lcd->addr, s++, 1, NULL, 0);
delay(30e-6);
}
}
void
write_instruction_set(lcd_t *lcd, int set)
{
uint8_t cmd = FUNCTION_SET(1, 1, lcd->dbl, set);
gpio_set(lcd->gfd, lcd->select_pin, GPIO_PIN_LOW);
spi_transfer(lcd->sfd, lcd->addr, &cmd, 1, NULL, 0);
delay(30e-6);
}
void
write_command(lcd_t *lcd, uint8_t cmd, int set)
{
gpio_set(lcd->gfd, lcd->select_pin, GPIO_PIN_LOW);
write_instruction_set(lcd, set);
spi_transfer(lcd->sfd, lcd->addr, &cmd, 1, NULL, 0);
delay(30e-6);
}
void
update_display_mode(lcd_t *lcd)
{
write_command(lcd,
DISPLAY_ONOFF(lcd->enabled,lcd->cursor,lcd->blink),
0);
write_command(lcd,
DOUBLE_HEIGHT(lcd->dbl >> 1),
2);
}
void
set_bias(lcd_t *lcd, int bias)
{
write_command(lcd, BIAS_SET(bias, 1), 1);
}
void
set_contrast(lcd_t *lcd, int contrast)
{
write_command(lcd, POWER_SET(1,1,contrast), 1);
write_command(lcd, FOLLOWER_CONTROL(1,3), 1);
delay(200e-3);
write_command(lcd, CONTRAST_SET(contrast), 1);
}
void
set_display_mode(lcd_t *lcd, int enabled, int cursor, int blink)
{
lcd->enabled = enabled;
lcd->cursor = cursor;
lcd->blink = blink;
update_display_mode(lcd);
}
void
enable_cursor(lcd_t *lcd, int set)
{
lcd->cursor = set;
update_display_mode(lcd);
}
void
enable_blink(lcd_t *lcd, int set)
{
lcd->blink = set;
update_display_mode(lcd);
}
void
set_cursor_offset(lcd_t *lcd, int offset)
{
write_command(lcd, SET_DDRAM(offset), 0);
delay(50e-6);
}
void
set_cursor_position(lcd_t *lcd, int row, int column)
{
int stride, n;
switch (lcd->rows) {
case 1: stride = 0; break;
case 2: stride = 64; break;
case 3: stride = 16; break;
default:
return;
}
n = row * stride + column;
if (n >= 0 && n < 128)
set_cursor_offset(lcd, n);
}
void
home(lcd_t *lcd)
{
set_cursor_position(lcd, 0, 0);
}
void
clear(lcd_t *lcd)
{
write_command(lcd, CLEAR_DISPLAY, 0);
delay(2e-3);
}
void
reset(lcd_t *lcd)
{
gpio_set(lcd->gfd, lcd->reset_pin, GPIO_PIN_LOW);
delay(0.001);
gpio_set(lcd->gfd, lcd->reset_pin, GPIO_PIN_HIGH);
}
void
setup(lcd_t *lcd)
{
lcd->select_pin = PIN_SELECT;
lcd->reset_pin = PIN_RESET;
lcd->rows = 3;
lcd->columns = 16;
lcd->addr = ADDR;
lcd->enabled = 1;
lcd->cursor = 0;
lcd->blink = 0;
lcd->dbl = 1;
spi_configure(lcd->sfd, lcd->addr, MODE, 0);
spi_configure(lcd->sfd, lcd->addr, MODE, 1000000);
gpio_set(lcd->gfd, lcd->select_pin, GPIO_PIN_HIGH);
reset(lcd);
delay(40e-3);
}
int main()
{
lcd_t L;
int i;
L.gfd = open(GPIO, O_RDWR);
if (L.gfd == -1)
err(1,"open %s",GPIO);
L.sfd = open(SPI, O_RDWR);
if (L.sfd == -1)
err(1,"open %s",SPI);
setup(&L);
update_display_mode(&L);
write_command(&L, ENTRY_MODE_SET(1,0), 0);
set_bias(&L, 1);
set_display_mode(&L, 1, 0, 0);
set_contrast(&L, 40);
clear(&L);
for (;;) {
time_t now;
struct tm *tm;
static int mday;
char *tz;
char buf[26];
time(&now);
tm = localtime(&now);
asctime_r(tm, buf);
tz = tm->tm_zone;
buf[10] = '\0';
buf[19] = '\0';
buf[24] = '\0';
if (tm->tm_mday != mday) {
set_cursor_position(&L, 0, 0);
write_string(&L, &buf[0]);
set_cursor_position(&L, 0, 12);
write_string(&L, &buf[20]);
mday = tm->tm_mday;
}
set_cursor_position(&L, 1, 0);
write_string(&L, &buf[11]);
set_cursor_position(&L, 1, 9);
write_string(&L, tz);
delay(1.0);
}
//printf("%d\n", read_busy(&L));
close(L.sfd);
close(L.gfd);
}