Porting LiquidCrystal to C

So, here’s the situation:

I do not use the default Arduino IDE, because of the many issues I find wrong with it. I do my coding strictly in C, using Eclipse with the AVR plugin. Everything works just fine and dandy, except for the bunch of libraries that are written in C++.

It so happens that I got my hands on a HD44780-compatible LCD screen. After a day of trouble, I figured out the correct pinout on the device, yeah, datasheets for it were non-existent and most sites had the pinout wrong.

I managed to get the Arduino IDE to compile the code, and after browsing thru my source directories, found the infamous core.a. Used avrdude to upload, since Arduino IDE refuses to make a lock for rxtx no matter what user groups I belong to, and voila, ‘hello world’ was successfully printed on the LCD screen.

Now, in order to use the library in my development environment of choice, I tried to port it to C. For some reason, all it parses is garbage. Can somebody look thru my code and tell me what’s going on there? Yes, it compiles fine.

/*
 * HD44780.h
 *
 *  Created on: Jul 20, 2009
 *      Author: Orlando Arias
 *     License: GPLv3
 *     Based off LiquidCrystal.h
 */

#ifndef HD44780_H_
#define HD44780_H_

#include <inttypes.h>
#include "WProgram.h"

/*
 * The following enum defines the two possible bit modes
 * in the LCD screen.
 *
 * If only 4 data pins are used, then bitmode must be set to
 * BIT_MODE_4, otherwise, all data pins are used and bitmode
 * must be set to BIT_MODE_8.
 */
typedef enum {
      BIT_MODE_4      =4,
      BIT_MODE_8      =8
} bmode;

typedef struct {
      int datapin[7];
      int rs_pin;
      int rw_pin;
      int enable_pin;
      bmode bitmode;
} LCD;

/*
 * function prototypes
 */
void clearLCD(LCD l);      //implemented
void goHome(LCD l);            //implemented
void initLCD(LCD l);      //implemented
void sendCommand(LCD l, uint8_t cmd);            //implemented
void setCursor(LCD l, int row, int col);      //implemented
void writeChar(LCD l, uint8_t ch);                  //implemented
void writeStr(LCD l, const char chArray[]);      //implemented

#endif /* HD44780_H_ */
/*
 * HD44780.c
 *
 *  Created on: Jul 20, 2009
 *      Author: Orlando Arias
 *     License: GPLv3
 *     Based off LiquidCrystal.cpp
 */

#include <inttypes.h>
#include "WProgram.h"
#include "HD44780.h"

// function prototype
void send(LCD l, uint8_t val, uint8_t mode);
void parseStr(LCD l, const char *chArray);

// code
void initLCD(LCD l){
      //DEBUG CODE: if BIT_MODE_4 light up LED
      pinMode(6, OUTPUT);
      digitalWrite(6, (l.bitmode == BIT_MODE_4) ? HIGH:LOW);

      // initialize pinout
      for (int i = 0; i < (l.bitmode == BIT_MODE_8) ? 8:4;i++)
            pinMode(l.datapin[i], OUTPUT);

      pinMode(l.rs_pin, OUTPUT);
      pinMode(l.rw_pin, OUTPUT);
      pinMode(l.enable_pin, OUTPUT);

      /*
       * Since it is unsafe to assume the LCD is ready to go when the Arduino
       * is powered, the following commands are run.
       *
       * function set: 8 bits, 1 line, 5x8 dots if BIT_MODE_8 (0x38) or
       *                          4 bits, 1 line, 5x8 dots if BIT_MODE_4 (0x28)
       * display control: turn display on, cursor off, no blinking (0x0C)
       * entry mode set: increment automatically, display
       *                            shift, right shift (0x06)
       */
      sendCommand(l, (l.bitmode == BIT_MODE_8) ? 0x38:0x28);
      sendCommand(l, 0x0C);
      sendCommand(l, 0x06);
      clearLCD(l);
}

void clearLCD(LCD l){
      // clear display, set cursor position to zero
      sendCommand(l, 0x01);
      delayMicroseconds(2000);
}

void goHome(LCD l){
      // set cursor position to zero
      sendCommand(l, 0x02);
      delayMicroseconds(2000);
}

void setCursor(LCD l, int row, int col){
      // I wish people would document their code as well as I try to do.
      int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
      sendCommand(l, 0x80 | (col + row_offsets[row]));
}

void sendCommand(LCD l, uint8_t cmd){
      send(l, cmd, LOW);
}

void writeChar(LCD l, uint8_t ch){
      send(l, ch, HIGH);
}

void writeStr(LCD l, const char chrArray[]){
      parseStr(l, chrArray);
}

void parseStr(LCD l, const char *chArray){
        while (*chArray)
          writeChar(l, *chArray++);
}

void send(LCD l, uint8_t val, uint8_t mode){
      digitalWrite(l.rs_pin, mode);
      digitalWrite(l.rw_pin, LOW);

      if (l.bitmode == BIT_MODE_4){
            for (int i = 0; i<4; i++)
                  digitalWrite(l.datapin[i], (val >> (i + 4)) & 0x01);

            digitalWrite(l.enable_pin, HIGH);
            digitalWrite(l.enable_pin, LOW);
      }

      for (int i = 0; i < (l.bitmode == BIT_MODE_8) ? 8:4; i++)
            digitalWrite(l.datapin[i], (val >> i) & 0x01);

      digitalWrite(l.enable_pin, HIGH);
      digitalWrite(l.enable_pin, LOW);
}

Notice that I am not asking how to get eclipse to compile C++ code, I do not care about that. I want my library to work and that’s what I would like help with. Any help would be appreciated. Thanks.

Are you using it in 8 bit mode?

As it is, the library is capable of using both 8bit and 4bit modes on the LCD controller. It does not matter whether or not the LCD is set to 8bit mode or 4bit mode as long as this is done before calling the initLCD() function.

//-std=c99
      LCD myLCD;
      myLCD.bitmode = BIT_MODE_4;
      myLCD.enable_pin = 10;
      myLCD.rs_pin = 12;
      myLCD.rw_pin = 11;

      for (int i = 0; i<4;i++)
            myLCD.datapin[i] = 5-i;

      initLCD(myLCD);

Let me ask the question a little more clearly. When you get your garbage display, are you driving the LCD in 8-bit mode?

The code I posted above is the initialization code for the LCD. As you can see, it sets the mode to 4bit mode, sets the pinout configuration, then, it runs the initialization routine, which sets the proper mode to the LCD, according to the bitmode variable.

Screen is garbled no matter whether any extra data is sent to the screen or not.

The reason I asked is because in your typedef you declare the array to hold the data pin numbers [7]. It occurred to me that if you tried to write 8 pin numbers to the array C would let you but you would overwrite RS, but it doesn't look like it would do that for 4 bit mode.

typedef struct {
      int datapin[7];
      int rs_pin;
      int rw_pin;
      int enable_pin;
      bmode bitmode;
} LCD;

Good point, small bug on my code, fixing that as we speak...

In any case, the screen still comes up garbled for some reason I can't explain.

I know the device is being set to 4bit mode because of the 'LED debug code' part.

I am sure that there's another error somewhere in the code, probably a stack overflow, since the LED on pin 13 is on. I'm going to build a probe and see what other is getting a wrong output.

Are you writing both nibbles in 4-bit mode?

the mode is never changed after being declared.

When writing to the LCD, the send() function is called in the end in one of two ways:

send(LCD, uint8_t, HIGH) if parsing a character send(LCD, uint8_t, LOW) if parsing a command

The function then determines whether to use 8bit mode or 4bit mode to send the data. If it detects that 4bit mode is being used, then it splits the byte in nibbles, thus why you see the if (l.bitmode == BIT_MODE_4) part with its own loop.

Couple of things. First, why are you passing a copy of the structure to all functions? Seems horribly wasteful…I’d recommend passing a pointer to an LCD structure rather than a copy of the structure itself.

Nicely written code, BTW. It’s a refreshing change from…errr…other postings.

Another problem I see is one of operator precedence in initLCD:

// initialize pinout
      for (int i = 0; i < (l.bitmode == BIT_MODE_8) ? 8:4;i++)
            pinMode(l.datapin[i], OUTPUT);

< has higher precedence than ?: so the above is actually parsed as:

(i < (l.bitmode == BIT_MODE_8)) ? 8:4

Clearly not what you want.

Same thing in the send() function.

I am not too pointer friendly, which is why I am doing it like that, I have to get the hang of pointers yet. The code is under the GPLv3, so feel free to have a whack at it using pointers, just let me know what you do, please.

That second part though, may explain quite a few things, which, being the case, may be the cause of the clunky errors I am getting. Adding a few parenthesis should help the case, which fixes it. Thanks so very much.

That being the case, here’s the code for the library:

/*
 * HD44780.h
 *
 *  Created on: Jul 20, 2009
 *      Author: Orlando Arias
 *     License: GPLv3
 */

#ifndef HD44780_H_
#define HD44780_H_

#include <inttypes.h>
#include "WProgram.h"

/*
 * The following enum defines the two possible bit modes
 * in the LCD screen.
 *
 * If only 4 data pins are used, then bitmode must be set to
 * BIT_MODE_4, otherwise, all data pins are used and bitmode
 * must be set to BIT_MODE_8.
 */
typedef enum {
      BIT_MODE_4      =4,
      BIT_MODE_8      =8
} bmode;

typedef struct {
      int datapin[8];
      int rs_pin;
      int rw_pin;
      int enable_pin;
      bmode bitmode;
} LCD;

/*
 * function prototypes
 */
void clearLCD(LCD l);      //implemented
void goHome(LCD l);            //implemented
void initLCD(LCD l);      //implemented
void sendCommand(LCD l, uint8_t cmd);            //implemented
void setCursor(LCD l, int row, int col);      //implemented
void writeChar(LCD l, uint8_t ch);                  //implemented
void writeStr(LCD l, const char chArray[]);      //implemented

#endif /* HD44780_H_ */
/*
 * HD44780.c
 *
 *  Created on: Jul 20, 2009
 *      Author: Orlando Arias
 *     License: GPLv3
 */

#include <inttypes.h>
#include "WProgram.h"
#include "HD44780.h"

// function prototype
void send(LCD l, uint8_t val, uint8_t mode);
void parseStr(LCD l, const char *chArray);

// code
void initLCD(LCD l){
      // initialize pinout
      for (int i = 0; i < ((l.bitmode == BIT_MODE_8) ? 8:4);i++)
            pinMode(l.datapin[i], OUTPUT);

      pinMode(l.rs_pin, OUTPUT);
      pinMode(l.rw_pin, OUTPUT);
      pinMode(l.enable_pin, OUTPUT);

      /*
       * Since it is unsafe to assume the LCD is ready to go when the Arduino
       * is powered, the following commands are run.
       *
       * function set: 8 bits, 1 line, 5x8 dots if BIT_MODE_8 (0x38) or
       *                          4 bits, 1 line, 5x8 dots if BIT_MODE_4 (0x28)
       * display control: turn display on, cursor off, no blinking (0x0C)
       * entry mode set: increment automatically, display
       *                            shift, right shift (0x06)
       */
      sendCommand(l, (l.bitmode == BIT_MODE_8) ? 0x38:0x28);
      sendCommand(l, 0x0C);
      sendCommand(l, 0x06);
      clearLCD(l);
}

void clearLCD(LCD l){
      // clear display, set cursor position to zero
      sendCommand(l, 0x01);
      delayMicroseconds(2000);
}

void goHome(LCD l){
      // set cursor position to zero
      sendCommand(l, 0x02);
      delayMicroseconds(2000);
}

void setCursor(LCD l, int row, int col){
      // I wish people would document their code as well as I try to do.
      int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
      sendCommand(l, 0x80 | (col + row_offsets[row]));
}

void sendCommand(LCD l, uint8_t cmd){
      send(l, cmd, LOW);
}

void writeChar(LCD l, uint8_t ch){
      send(l, ch, HIGH);
}

void writeStr(LCD l, const char chrArray[]){
      parseStr(l, chrArray);
}

void parseStr(LCD l, const char *chArray){
        while (*chArray)
          writeChar(l, *chArray++);
}

void send(LCD l, uint8_t val, uint8_t mode){
      digitalWrite(l.rs_pin, mode);
      digitalWrite(l.rw_pin, LOW);

      if (l.bitmode == BIT_MODE_4){
            for (int i = 0; i<4; i++)
                  digitalWrite(l.datapin[i], (val >> (i + 4)) & 0x01);

            digitalWrite(l.enable_pin, HIGH);
            digitalWrite(l.enable_pin, LOW);
      }

      for (int i = 0; i < ((l.bitmode == BIT_MODE_8) ? 8:4); i++)
            digitalWrite(l.datapin[i], (val >> i) & 0x01);

      digitalWrite(l.enable_pin, HIGH);
      digitalWrite(l.enable_pin, LOW);
}

And a sample on usage.

/*
 * main.c
 *
 *  Created on: Jul 21, 2009
 *      Author: Orlando Arias
 *     License: GPLv3
 */
#include "WProgram.h"

// Standard C entry point function
int main(void){
      // Arduino initialization routines
      init();

      // Program initialization routines.
      setup();

      // Program action loop
      for (;;) loop();

      // under normal circumstances, this is never reached
      // however, std C requires function to return something.
      return 0;
}
/*
 * core.c
 *
 *  Created on: Jul 21, 2009
 *      Author: Orlando Arias
 *     License: GPLv3
 */
#include "WProgram.h"
#include "HD44780.h"

// declares a global LCD struct
LCD myLCD;

// Program setup routine
void setup(){
      // set up pinout
      myLCD.bitmode = BIT_MODE_4;
      myLCD.enable_pin = 10;
      myLCD.rs_pin = 12;
      myLCD.rw_pin = 11;

      for (int i=0;i<4;i++)
            myLCD.datapin[i] = 5-i;

      // initialize LCD
      initLCD(myLCD);
}

// Display a blinking message on the screen.
void loop(){
      // write "Hello, World!" to the screen
      writeStr(myLCD, "Hello, World!");
      // wait for 500ms
      delay(500);
      // clear the screen
      clearLCD(myLCD);
      // wait for 500ms
      delay(500);
}

So there you have it. Thank you so very much and happy hacking!

Here is the library modified to use pointers to pass the LCD structure. It compiles OK but it hasn’t been tested.

/*
 * HD44780.h
 *
 *  Created on: Jul 20, 2009
 *  Modified: Jul 21, 2009 Rugged Circuits LLC
 *            Modified to use pointers to LCD structure
 *      Author: Orlando Arias
 *     License: GPLv3
 */

#ifndef HD44780_H_
#define HD44780_H_

#include <inttypes.h>
#include "WProgram.h"

/*
 * The following enum defines the two possible bit modes
 * in the LCD screen.
 *
 * If only 4 data pins are used, then bitmode must be set to
 * BIT_MODE_4, otherwise, all data pins are used and bitmode
 * must be set to BIT_MODE_8.
 */
typedef enum {
      BIT_MODE_4      =4,
      BIT_MODE_8      =8
} bmode;

typedef struct {
      int datapin[8];
      int rs_pin;
      int rw_pin;
      int enable_pin;
      bmode bitmode;
} LCD;

/*
 * function prototypes
 */
void clearLCD(LCD *l);      //implemented
void goHome(LCD *l);            //implemented
void initLCD(LCD *l);      //implemented
void sendCommand(LCD *l, uint8_t cmd);            //implemented
void setCursor(LCD *l, int row, int col);      //implemented
void writeChar(LCD *l, uint8_t ch);                  //implemented
void writeStr(LCD *l, const char chArray[]);      //implemented

#endif /* HD44780_H_ */
/*
 * HD44780.c
 *
 *  Created on: Jul 20, 2009
 *  Modified: Jul 21, 2009 Rugged Circuits LLC
 *            Modified to use pointers to LCD structure, static functions marked
 *            as such
 *      Author: Orlando Arias
 *     License: GPLv3
 */

#include <inttypes.h>
#include "WProgram.h"
#include "HD44780.h"

// function prototype
// RC: Modified to be static functions
static void send(LCD *l, uint8_t val, uint8_t mode);
static void parseStr(LCD *l, const char *chArray);

// code
void initLCD(LCD *l){
      // initialize pinout
      for (int i = 0; i < ((l->bitmode == BIT_MODE_8) ? 8:4);i++)
            pinMode(l->datapin[i], OUTPUT);

      pinMode(l->rs_pin, OUTPUT);
      pinMode(l->rw_pin, OUTPUT);
      pinMode(l->enable_pin, OUTPUT);

      /*
       * Since it is unsafe to assume the LCD is ready to go when the Arduino
       * is powered, the following commands are run.
       *
       * function set: 8 bits, 1 line, 5x8 dots if BIT_MODE_8 (0x38) or
       *                          4 bits, 1 line, 5x8 dots if BIT_MODE_4 (0x28)
       * display control: turn display on, cursor off, no blinking (0x0C)
       * entry mode set: increment automatically, display
       *                            shift, right shift (0x06)
       */
      sendCommand(l, (l->bitmode == BIT_MODE_8) ? 0x38:0x28);
      sendCommand(l, 0x0C);
      sendCommand(l, 0x06);
      clearLCD(l);
}

void clearLCD(LCD *l){
      // clear display, set cursor position to zero
      sendCommand(l, 0x01);
      delayMicroseconds(2000);
}

void goHome(LCD *l){
      // set cursor position to zero
      sendCommand(l, 0x02);
      delayMicroseconds(2000);
}

void setCursor(LCD *l, int row, int col){
      // I wish people would document their code as well as I try to do.
      int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
      sendCommand(l, 0x80 | (col + row_offsets[row]));
}

void sendCommand(LCD *l, uint8_t cmd){
      send(l, cmd, LOW);
}

void writeChar(LCD *l, uint8_t ch){
      send(l, ch, HIGH);
}

void writeStr(LCD *l, const char chrArray[]){
      parseStr(l, chrArray);
}

void parseStr(LCD *l, const char *chArray){
        while (*chArray)
          writeChar(l, *chArray++);
}

void send(LCD *l, uint8_t val, uint8_t mode){
      digitalWrite(l->rs_pin, mode);
      digitalWrite(l->rw_pin, LOW);

      if (l->bitmode == BIT_MODE_4){
            for (int i = 0; i<4; i++)
                  digitalWrite(l->datapin[i], (val >> (i + 4)) & 0x01);

            digitalWrite(l->enable_pin, HIGH);
            digitalWrite(l->enable_pin, LOW);
      }

      for (int i = 0; i < ((l->bitmode == BIT_MODE_8) ? 8:4); i++)
            digitalWrite(l->datapin[i], (val >> i) & 0x01);

      digitalWrite(l->enable_pin, HIGH);
      digitalWrite(l->enable_pin, LOW);
}
/*
 * core.c
 *
 *  Created on: Jul 21, 2009
 *  Modified: Jul 21, 2009 Rugged Circuits LLC
 *            Modified to use pointers to LCD structure
 *      Author: Orlando Arias
 *     License: GPLv3
 */
#include "WProgram.h"
#include "HD44780.h"

// declares a global LCD struct
LCD myLCD;

// Program setup routine
void setup(){
      // set up pinout
      myLCD.bitmode = BIT_MODE_4;
      myLCD.enable_pin = 10;
      myLCD.rs_pin = 12;
      myLCD.rw_pin = 11;

      for (int i=0;i<4;i++)
            myLCD.datapin[i] = 5-i;

      // initialize LCD
      initLCD(&myLCD);
}

// Display a blinking message on the screen.
void loop(){
      // write "Hello, World!" to the screen
      writeStr(&myLCD, "Hello, World!");
      // wait for 500ms
      delay(500);
      // clear the screen
      clearLCD(&myLCD);
      // wait for 500ms
      delay(500);
}

Got your changes merged and compiled, application compiles fine and runs normally too. Thank you very much. Also, congratulations, you are now officially a contributor to the C port of LiquidCrystal!

Well enough jokes for now. Time to cook some more code, and probably modify the demuxer library I wrote a while back.