/* 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 ; }