/*
Hello world code for an LCD display.
* Compile (NB: needs downloaded version of lcdBinary.o):
gcc -c -o lcd-hello.o lcd-hello.c
gcc -o lcd-hello lcd-hello.o lcdBinary.o
* Run:
sudo ./lcd-hello
* based on:
* wiringPi libraries by
* Copyright (c) 2012-2013 Gordon Henderson.
***********************************************************************
* This file is part of wiringPi:
* https://projects.drogon.net/raspberry-pi/wiringpi/
*
* wiringPi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* wiringPi 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with wiringPi. If not, see .
***********************************************************************
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef TRUE
# define TRUE (1==1)
# define FALSE (1==2)
#endif
#define PAGE_SIZE (4*1024)
#define BLOCK_SIZE (4*1024)
#define INPUT 0
#define OUTPUT 1
#define LOW 0
#define HIGH 1
// APP constants ---------------------------------
// Wiring (see call to lcdInit in main, using BCM numbering)
#define STRB_PIN 24
#define RS_PIN 25
#define DATA0_PIN 23
// #define DATA1_PIN 17
#define DATA1_PIN 10
#define DATA2_PIN 27
#define DATA3_PIN 22
// char data for the CGRAM, i.e. defining new characters for the display
static unsigned char newChar [8] =
{
0b11111,
0b10001,
0b10001,
0b10101,
0b11111,
0b10001,
0b10001,
0b11111,
} ;
/* bit pattern to feed into lcdCharDef to define a new character */
static unsigned char hawoNewChar [8] =
{
0b11111,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b10001,
0b11111,
} ;
// data structure holding data on the representation of the LCD
struct lcdDataStruct
{
int bits, rows, cols ;
int rsPin, strbPin ;
int dataPins [8] ;
int cx, cy ;
} ;
static int lcdControl ;
/* ***************************************************************************** */
/* INLINED fcts from wiringPi/devLib/lcd.c: */
// HD44780U Commands (see Fig 11, p28 of the Hitachi HD44780U datasheet)
#define LCD_CLEAR 0x01
#define LCD_HOME 0x02
#define LCD_ENTRY 0x04
#define LCD_CTRL 0x08
#define LCD_CDSHIFT 0x10
#define LCD_FUNC 0x20
#define LCD_CGRAM 0x40
#define LCD_DGRAM 0x80
// Bits in the entry register
#define LCD_ENTRY_SH 0x01
#define LCD_ENTRY_ID 0x02
// Bits in the control register
#define LCD_BLINK_CTRL 0x01
#define LCD_CURSOR_CTRL 0x02
#define LCD_DISPLAY_CTRL 0x04
// Bits in the function register
#define LCD_FUNC_F 0x04
#define LCD_FUNC_N 0x08
#define LCD_FUNC_DL 0x10
#define LCD_CDSHIFT_RL 0x04
// Mask for the bottom 64 pins which belong to the Raspberry Pi
// The others are available for the other devices
#define PI_GPIO_MASK (0xFFFFFFC0)
static volatile unsigned int gpiobase ;
static volatile uint32_t *gpio ;
// protos
int failure (int fatal, const char *message, ...);
void waitForEnter (void);
/* ------------------------------------------------------- */
/* low-level interface to the hardware */
/* digitalWrite is now in a separate file: lcdBinary.c */
void digitalWrite (uint32_t *gpio, int pin, int value);
/* ------------------------------------------------------- */
/* aux functions */
int failure (int fatal, const char *message, ...)
{
va_list argp ;
char buffer [1024] ;
if (!fatal) // && wiringPiReturnCodes)
return -1 ;
va_start (argp, message) ;
vsnprintf (buffer, 1023, message, argp) ;
va_end (argp) ;
fprintf (stderr, "%s", buffer) ;
exit (EXIT_FAILURE) ;
return 0 ;
}
/*
* waitForEnter:
*********************************************************************************
*/
void waitForEnter (void)
{
printf ("Press ENTER to continue: ") ;
(void)fgetc (stdin) ;
}
/*
* delay:
* Wait for some number of milliseconds
*********************************************************************************
*/
void delay (unsigned int howLong)
{
struct timespec sleeper, dummy ;
sleeper.tv_sec = (time_t)(howLong / 1000) ;
sleeper.tv_nsec = (long)(howLong % 1000) * 1000000 ;
nanosleep (&sleeper, &dummy) ;
}
/*
* delayMicroseconds:
* This is somewhat intersting. It seems that on the Pi, a single call
* to nanosleep takes some 80 to 130 microseconds anyway, so while
* obeying the standards (may take longer), it's not always what we
* want!
*
* So what I'll do now is if the delay is less than 100uS we'll do it
* in a hard loop, watching a built-in counter on the ARM chip. This is
* somewhat sub-optimal in that it uses 100% CPU, something not an issue
* in a microcontroller, but under a multi-tasking, multi-user OS, it's
* wastefull, however we've no real choice )-:
*
* Plan B: It seems all might not be well with that plan, so changing it
* to use gettimeofday () and poll on that instead...
*********************************************************************************
*/
void delayMicroseconds (unsigned int howLong)
{
struct timespec sleeper ;
unsigned int uSecs = howLong % 1000000 ;
unsigned int wSecs = howLong / 1000000 ;
/**/ if (howLong == 0)
return ;
#if 0
else if (howLong < 100)
delayMicrosecondsHard (howLong) ;
#endif
else
{
sleeper.tv_sec = wSecs ;
sleeper.tv_nsec = (long)(uSecs * 1000L) ;
nanosleep (&sleeper, NULL) ;
}
}
/* ------------------------------------------------------- */
/* medium-level interface functions (all in C) */
/* from wiringPi:
* strobe:
* Toggle the strobe (Really the "E") pin to the device.
* According to the docs, data is latched on the falling edge.
*********************************************************************************
*/
void strobe (const struct lcdDataStruct *lcd)
{
// Note timing changes for new version of delayMicroseconds ()
digitalWrite (gpio, lcd->strbPin, 1) ; delayMicroseconds (50) ;
digitalWrite (gpio, lcd->strbPin, 0) ; delayMicroseconds (50) ;
}
/*
* sentDataCmd:
* Send an data or command byte to the display.
*********************************************************************************
*/
void sendDataCmd (const struct lcdDataStruct *lcd, unsigned char data)
{
register unsigned char myData = data ;
unsigned char i, d4 ;
if (lcd->bits == 4)
{
d4 = (myData >> 4) & 0x0F;
for (i = 0 ; i < 4 ; ++i)
{
digitalWrite (gpio, lcd->dataPins [i], (d4 & 1)) ;
d4 >>= 1 ;
}
strobe (lcd) ;
d4 = myData & 0x0F ;
for (i = 0 ; i < 4 ; ++i)
{
digitalWrite (gpio, lcd->dataPins [i], (d4 & 1)) ;
d4 >>= 1 ;
}
}
else
{
for (i = 0 ; i < 8 ; ++i)
{
digitalWrite (gpio, lcd->dataPins [i], (myData & 1)) ;
myData >>= 1 ;
}
}
strobe (lcd) ;
}
/*
* lcdPutCommand:
* Send a command byte to the display
*********************************************************************************
*/
void lcdPutCommand (const struct lcdDataStruct *lcd, unsigned char command)
{
#ifdef DEBUG
fprintf(stderr, "lcdPutCommand: digitalWrite(%d,%d) and sendDataCmd(%d,%d)\n", lcd->rsPin, 0, lcd, command);
#endif
digitalWrite (gpio, lcd->rsPin, 0) ;
sendDataCmd (lcd, command) ;
delay (2) ;
}
void lcdPut4Command (const struct lcdDataStruct *lcd, unsigned char command)
{
register unsigned char myCommand = command ;
register unsigned char i ;
digitalWrite (gpio, lcd->rsPin, 0) ;
for (i = 0 ; i < 4 ; ++i)
{
digitalWrite (gpio, lcd->dataPins [i], (myCommand & 1)) ;
myCommand >>= 1 ;
}
strobe (lcd) ;
}
/*
* lcdHome: lcdClear:
* Home the cursor or clear the screen.
*********************************************************************************
*/
void lcdHome (struct lcdDataStruct *lcd)
{
#ifdef DEBUG
fprintf(stderr, "lcdHome: lcdPutCommand(%d,%d)\n", lcd, LCD_HOME);
#endif
lcdPutCommand (lcd, LCD_HOME) ;
lcd->cx = lcd->cy = 0 ;
delay (5) ;
}
void lcdClear (struct lcdDataStruct *lcd)
{
#ifdef DEBUG
fprintf(stderr, "lcdClear: lcdPutCommand(%d,%d) and lcdPutCommand(%d,%d)\n", lcd, LCD_CLEAR, lcd, LCD_HOME);
#endif
lcdPutCommand (lcd, LCD_CLEAR) ;
lcdPutCommand (lcd, LCD_HOME) ;
lcd->cx = lcd->cy = 0 ;
delay (5) ;
}
/*
* lcdPosition:
* Update the position of the cursor on the display.
* Ignore invalid locations.
*********************************************************************************
*/
void lcdPosition (struct lcdDataStruct *lcd, int x, int y)
{
// struct lcdDataStruct *lcd = lcds [fd] ;
if ((x > lcd->cols) || (x < 0))
return ;
if ((y > lcd->rows) || (y < 0))
return ;
lcdPutCommand (lcd, x + (LCD_DGRAM | (y>0 ? 0x40 : 0x00) /* rowOff [y] */ )) ;
lcd->cx = x ;
lcd->cy = y ;
}
/*
* lcdDisplay: lcdCursor: lcdCursorBlink:
* Turn the display, cursor, cursor blinking on/off
*********************************************************************************
*/
void lcdDisplay (struct lcdDataStruct *lcd, int state)
{
if (state)
lcdControl |= LCD_DISPLAY_CTRL ;
else
lcdControl &= ~LCD_DISPLAY_CTRL ;
lcdPutCommand (lcd, LCD_CTRL | lcdControl) ;
}
void lcdCursor (struct lcdDataStruct *lcd, int state)
{
if (state)
lcdControl |= LCD_CURSOR_CTRL ;
else
lcdControl &= ~LCD_CURSOR_CTRL ;
lcdPutCommand (lcd, LCD_CTRL | lcdControl) ;
}
void lcdCursorBlink (struct lcdDataStruct *lcd, int state)
{
if (state)
lcdControl |= LCD_BLINK_CTRL ;
else
lcdControl &= ~LCD_BLINK_CTRL ;
lcdPutCommand (lcd, LCD_CTRL | lcdControl) ;
}
/*
* lcdPutchar:
* Send a data byte to be displayed on the display. We implement a very
* simple terminal here - with line wrapping, but no scrolling. Yet.
*********************************************************************************
*/
void lcdPutchar (struct lcdDataStruct *lcd, unsigned char data)
{
digitalWrite (gpio, lcd->rsPin, 1) ;
sendDataCmd (lcd, data) ;
if (++lcd->cx == lcd->cols)
{
lcd->cx = 0 ;
if (++lcd->cy == lcd->rows)
lcd->cy = 0 ;
// TODO: inline computation of address and eliminate rowOff
lcdPutCommand (lcd, lcd->cx + (LCD_DGRAM | (lcd->cy>0 ? 0x40 : 0x00) /* rowOff [lcd->cy] */ )) ;
}
}
/*
* lcdPuts:
* Send a string to be displayed on the display
*********************************************************************************
*/
void lcdPuts (struct lcdDataStruct *lcd, const char *string)
{
while (*string)
lcdPutchar (lcd, *string++) ;
}
/* ----------------------------------------------------------------------------- */
int main (int argc, char *argv[])
{
int i ;
struct lcdDataStruct *lcd ;
int bits, rows, cols ;
unsigned char func ;
struct tm *t ;
time_t tim ;
int fd ;
char buf [32] ;
// hard-coded: 16x2 display, using a 4-bit connection
bits = 4;
cols = 16;
rows = 2;
printf ("Raspberry Pi LCD driver, for a %dx%d display (%d-bit wiring) \n", cols, rows, bits) ;
if (geteuid () != 0)
fprintf (stderr, "setup: Must be root. (Did you forget sudo?)\n") ;
// -----------------------------------------------------------------------------
// constants for RPi2
gpiobase = 0x3F200000 ;
// -----------------------------------------------------------------------------
// memory mapping
// Open the master /dev/memory device
if ((fd = open ("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC) ) < 0)
return failure (FALSE, "setup: Unable to open /dev/mem: %s\n", strerror (errno)) ;
// GPIO:
gpio = (uint32_t *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, gpiobase) ;
if ((int32_t)gpio == -1)
return failure (FALSE, "setup: mmap (GPIO) failed: %s\n", strerror (errno)) ;
// ------
// INLINED version of hawoLcdInit (can only deal with one LCD attached to the RPi):
// Create a new LCD:
lcd = (struct lcdDataStruct *)malloc (sizeof (struct lcdDataStruct)) ;
if (lcd == NULL)
return -1 ;
// hard-wired GPIO pins
lcd->rsPin = RS_PIN ;
lcd->strbPin = STRB_PIN ;
lcd->bits = 4 ;
lcd->rows = rows ; // # of rows on the display
lcd->cols = cols ; // # of cols on the display
lcd->cx = 0 ; // x-pos of cursor
lcd->cy = 0 ; // y-pos of curosr
lcd->dataPins [0] = DATA0_PIN ;
lcd->dataPins [1] = DATA1_PIN ;
lcd->dataPins [2] = DATA2_PIN ;
lcd->dataPins [3] = DATA3_PIN ;
// lcd->dataPins [4] = d4 ;
// lcd->dataPins [5] = d5 ;
// lcd->dataPins [6] = d6 ;
// lcd->dataPins [7] = d7 ;
// lcds [lcdFd] = lcd ;
digitalWrite (gpio, lcd->rsPin, 0) ; pinMode (gpio, lcd->rsPin, OUTPUT) ;
digitalWrite (gpio, lcd->strbPin, 0) ; pinMode (gpio, lcd->strbPin, OUTPUT) ;
for (i = 0 ; i < bits ; ++i)
{
digitalWrite (gpio, lcd->dataPins [i], 0) ;
pinMode (gpio, lcd->dataPins [i], OUTPUT) ;
}
delay (35) ; // mS
// Gordon Henderson's explanation of this part of the init code (from wiringPi):
// 4-bit mode?
// OK. This is a PIG and it's not at all obvious from the documentation I had,
// so I guess some others have worked through either with better documentation
// or more trial and error... Anyway here goes:
//
// It seems that the controller needs to see the FUNC command at least 3 times
// consecutively - in 8-bit mode. If you're only using 8-bit mode, then it appears
// that you can get away with one func-set, however I'd not rely on it...
//
// So to set 4-bit mode, you need to send the commands one nibble at a time,
// the same three times, but send the command to set it into 8-bit mode those
// three times, then send a final 4th command to set it into 4-bit mode, and only
// then can you flip the switch for the rest of the library to work in 4-bit
// mode which sends the commands as 2 x 4-bit values.
if (bits == 4)
{
func = LCD_FUNC | LCD_FUNC_DL ; // Set 8-bit mode 3 times
lcdPut4Command (lcd, func >> 4) ; delay (35) ;
lcdPut4Command (lcd, func >> 4) ; delay (35) ;
lcdPut4Command (lcd, func >> 4) ; delay (35) ;
func = LCD_FUNC ; // 4th set: 4-bit mode
lcdPut4Command (lcd, func >> 4) ; delay (35) ;
lcd->bits = 4 ;
}
else
{
failure(TRUE, "setup: only 4-bit connection supported\n");
func = LCD_FUNC | LCD_FUNC_DL ;
lcdPutCommand (lcd, func ) ; delay (35) ;
lcdPutCommand (lcd, func ) ; delay (35) ;
lcdPutCommand (lcd, func ) ; delay (35) ;
}
if (lcd->rows > 1)
{
func |= LCD_FUNC_N ;
lcdPutCommand (lcd, func) ; delay (35) ;
}
// Rest of the initialisation sequence
lcdDisplay (lcd, TRUE) ;
lcdCursor (lcd, FALSE) ;
lcdCursorBlink (lcd, FALSE) ;
lcdClear (lcd) ;
lcdPutCommand (lcd, LCD_ENTRY | LCD_ENTRY_ID) ; // set entry mode to increment address counter after write
lcdPutCommand (lcd, LCD_CDSHIFT | LCD_CDSHIFT_RL) ; // set display shift to right-to-left
// ------
// -----------------------------------------------------------------------------
// INLINED:
fprintf(stderr,"Printing welcome message ...\n");
lcdPosition (lcd, 0, 0) ; lcdPuts (lcd, "Hello world!") ;
lcdPosition (lcd, 0, 1) ; lcdPuts (lcd, " ") ;
waitForEnter () ; // -------------------------------------------------------
lcdClear (lcd) ;
// TODO: print a longer message and make it scroll on the display
return 0 ;
}