Regarding migration from LiquidCrystal_SR to LiquidCrystal_I2C

Hi guys I am building a project which is based on the following link - DIY Soldering Station with Hakko FX-888 Iron – Part 1 – Arduino++

I am very much aware that the code needs conversion from #LiquidCrystal_SR.h library to #LiquidCrystal_I2C.h since I don't have the hardware for "SR". Can you guys please help me migrate the code to work with I2C LCD display
PS. Just changing the header will not work I have checked.

Waiting for your replies
Unmesh

FX888-Controller-master.zip (1.9 MB)

Of course not, you cannot use the same hardware design either. Change the schematics and post it here, then try to change the software and post the results here. We will help you then.

1 Like

I believe spending a little time with a good search engine you can find your project complete with code.

Hi apologies for breaking the rule. I am not quiet sure how i can explain the code as it is divided into multiple (.h) files but the main files which use the LCD header are listed below with their code. Hope I have not made any mistakes.

The code under BarGraph.h

#pragma once

#include <LiquidCrystal_I2C.h>
#include "Debug.h"

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

// BarGraph ----------------------------------------------
// Draw a bargraph on the LCD display
class BarGraph
{
protected:
  const uint8_t COL_PER_CHAR = 5;
  const uint8_t ROW_PER_CHAR = 8;
  const uint8_t LCD_BLOCK_CHAR = 0xff;
  const uint8_t LCD_BLANK_CHAR = ' ';

  LiquidCrystal_I2C *_lcd;
  uint8_t   _row, _colStart;
  int8_t    _colLen;
  uint32_t  _valueMin, _valueMax;
  uint8_t   _charLCD;

public:
  BarGraph(LiquidCrystal_I2C *lcd, uint8_t row, uint8_t colStart, int8_t colLen) :
    _lcd(lcd), _row(row), _colStart(colStart), _colLen(colLen), _charLCD(0)
  {
    setRange(0, 100);
  };

  void begin(void) {};

  inline void setChar(uint8_t charLCD) { if (_charLCD < 8) _charLCD = charLCD; };
  inline void setRange(uint32_t valueMin, uint32_t valueMax) { _valueMin = valueMin; _valueMax = valueMax; };

  void show(uint32_t value)
    // Build a string with the graph characters and then display the string.
    // Assume the graph is being display L to R and then reverse the string
    // if it is to be displyed R to L. Just need to ensure that the end 
    // character of the bar graph is built in the correct direction.
  {
    uint8_t charMap[ROW_PER_CHAR];  // LCD user defined character
    char *szGraph = (char *)malloc((abs(_colLen) + 1) * sizeof(char));
    uint8_t lenGraph;               // size of the graph in pixel columns
    uint8_t barValue;
    uint8_t c;

    // Can't do much if we couldn't get RAM
    if (szGraph == NULL) return;

    // work out what value display means
    lenGraph = abs(_colLen) * COL_PER_CHAR;
    if (value > _valueMax) barValue = lenGraph;
    else if (value < _valueMin) barValue = 0;
    else barValue = map(value, _valueMin, _valueMax, 0, lenGraph);

    // create the correct orientation for the user defined character
    // create the first row, then copy it to the others
    c = barValue % COL_PER_CHAR;    // number of extra columns
    charMap[0] = 0;
    for (uint8_t i = 0; i<c; i++)
      bitSet(charMap[0], (_colLen < 0) ? i : COL_PER_CHAR - i);
    memset(&charMap[1], charMap[0], sizeof(charMap) - sizeof(charMap[0]));
    _lcd->createChar(_charLCD, charMap);

    // create the graph string as if it was being displayed L to R
    {
      uint8_t i;  // retain loop index within the code block

      c = barValue / COL_PER_CHAR;    // number of whole characters
      for (i = 0; i<c; i++)       // full block characters
        szGraph[i] = LCD_BLOCK_CHAR;
      szGraph[i++] = _charLCD;  // end of graph special character
      for (; i<abs(_colLen); i++)
        szGraph[i] = LCD_BLANK_CHAR;  // blanks where no blocks
      szGraph[abs(_colLen)] = '\0';   // end the string properly
    }

    // prepare to display
    if (_colLen > 0)
    {
      // just set the cursor at specified position - string is already correct
      _lcd->setCursor(_colStart, _row);
    }
    else
    {
      // need to reverse the string and set a different starting cursor position
      strrev(szGraph);
      _lcd->setCursor(_colStart + _colLen + 1, _row);
    }

    // display it and release the string memory
    _lcd->print(szGraph);
    free(szGraph);
  }
};

The code under BigNum.h

#pragma once

#include <LiquidCrystal_I2C.h>
#include "Debug.h"

#define ORIGINAL_FONT 0

// BigNum -----------------------------------------------
// Draw a large character over 2 lines on the LCD display

class BigNum
{
private:
#if ORIGINAL_FONT
  // Definitions for the number of characters and their size
  static const uint8_t NUM_ELEMENTS = 8; // elements required
  static const uint8_t ELEMENT_SIZE = 8; // bytes per element

  static const PROGMEM uint8_t fontSingle[NUM_ELEMENTS][ELEMENT_SIZE];
  static const PROGMEM uint8_t fontDouble[NUM_ELEMENTS][ELEMENT_SIZE];
#else
  // Definitions for the number of characters and their size
  static const uint8_t NUM_ELEMENTS = 7; // elements required
  static const uint8_t ELEMENT_SIZE = 8; // bytes per element

  static const PROGMEM uint8_t fontSingle[NUM_ELEMENTS][ELEMENT_SIZE];
#endif


protected:
  LiquidCrystal_I2C  *_lcd;

public:
  BigNum(LiquidCrystal_I2C *lcd) : _lcd(lcd)
  { };

  void begin(void)
  {
    setSingleFont();
  }

  void setSingleFont(void)
  {
    uint8_t c[NUM_ELEMENTS];

    for (uint8_t i = 0; i < NUM_ELEMENTS; i++)
    {
      memcpy_P(c, &fontSingle[i][0], ELEMENT_SIZE*sizeof(fontSingle[0][0]));
      _lcd->createChar(i, c);
    }
  };

  void setDoubleFont(void)
  {
    uint8_t c[NUM_ELEMENTS];

    for (uint8_t i = 0; i < NUM_ELEMENTS; i++)
    {
#if ORIGINAL_FONT
      memcpy_P(c, &fontDouble[i][0], ELEMENT_SIZE*sizeof(fontDouble[0][0]));
#else
      memcpy_P(c, &fontSingle[i][0], ELEMENT_SIZE*sizeof(fontSingle[0][0]));
#endif
      _lcd->createChar(i, c);
    }
  };

  void writeNumber(uint8_t row, uint8_t col, uint16_t num, uint8_t digits, bool leadZero = false)
    // write number num at top left coordinate (row, col) in a space 3 digits in size 
    // with leading zero if requested
  {
    for (int8_t i = digits; i > 0; i--)
    {
      uint8_t d = num % 10;

      // draw the digit depeding on zero or not and, if zero, position in the number or not
      if ((d != 0) || leadZero || (d == 0 && i == digits) || (d == 0 && num != 0))
        writeDigit(row, col + i - 1, d);
      else
      {
        _lcd->setCursor(col, row);
        _lcd->write(' ');
        _lcd->setCursor(col, row + 1);
        _lcd->write(' ');
      }

      num /= 10;
    }
  }

  void writeDigit(uint8_t row, uint8_t col, uint8_t digit)
  {
    // Define the elements for the top and bottom for each digit
#if ORIGINAL_FONT
    static const uint8_t top[10] = { 1, 0, 7, 7, 3, 4, 4, 7, 1, 1 };
    static const uint8_t bot[10] = { 3, 0, 4, 2, 0, 6, 3, 0, 5, 2 };
#else
    static const uint8_t top[10] = { 0, 5, 2, 2, 1, 3, 3, 6, 4, 4 };
    static const uint8_t bot[10] = { 1, 5, 3, 2, 6, 2, 4, 5, 4, 2 };
#endif // ORIGINAL_FONT

    if (digit > 9) return;

    _lcd->setCursor(col, row);
    _lcd->write((char)top[digit]);
    _lcd->setCursor(col, row + 1);
    _lcd->write((char)bot[digit]);
  };
};

// PROGMEM Font data for big numbers
//
#if ORIGINAL_FONT
// Character elements defined in the user chars in numerical order
// 0:    1: _  2: _  3:    4: _  5: _  6:    7: _
//     |   | |    _|   |_|   |_    |_|    _|     |

// Two types of fonts are defined - single and double stroke widths
// Single width strokes
const PROGMEM uint8_t BigNum::fontSingle[NUM_ELEMENTS][ELEMENT_SIZE] =
{
  { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // 0
  { 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }, // 1
  { 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f }, // 2
  { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f }, // 3
  { 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f }, // 4
  { 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f }, // 5
  { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f }, // 6
  { 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }  // 7
};

const PROGMEM uint8_t BigNum::fontDouble[NUM_ELEMENTS][ELEMENT_SIZE] =
{
  { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // 0
  { 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }, // 1
  { 0x1f, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x1f }, // 2
  { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f }, // 3
  { 0x1f, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x1f }, // 4
  { 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f }, // 5
  { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x0f, 0x1f }, // 6
  { 0x1f, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }  // 7
};
#else
// Character elements defined in the user chars in numerical order
// 0: _  1:    2: _  3: _  4: _  5:    6: _  7: not used
//   | |   |_|    _|   |_    |_|     |     |    

// Only one font is defined but it takes on less user defined character
// Single width strokes
const PROGMEM uint8_t BigNum::fontSingle[NUM_ELEMENTS][ELEMENT_SIZE] =
{
  { 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }, // 0
  { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f }, // 1
  { 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f }, // 2
  { 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f }, // 3
  { 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f }, // 4
  { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // 5
  { 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // 6
};
#endif

The code under LCDDisplay.h

#pragma once

#include <LiquidCrystal_I2C.h>
#include "Hardware.h"
#include "BarGraph.h"
#include "BigNum.h"
#include "Debug.h"

// LCD Display -------------------------------------------
// Manage the LCD display for the different modes of operation.
class LCD_Display : protected LiquidCrystal_I2C
{
private:
  // Assumes a 2 line 16 character LCD module display.
  static const uint8_t LCD_ROWS = 2;
  static const uint8_t LCD_COLS = 16;

  // There are different types of displays, defined by the fields
  struct fieldDef_t { uint8_t row, col, len; };

  //
  // * MAIN DISPLAY *
  //  
  // +----------------+   D Double height current value
  // |DDDo M SSS XXXXX|   S SP for current value
  // |DDDU GGGGGGGGGGG|   M Control mode (Power or Temperature)
  // +----------------+   X Mode of Operation
  //  0123456789012345    G Bar graph of power applied
  //                      o degree symbol
  //                      C units of temperature
  //
  const fieldDef_t MDF_CV   = { 0, 0, 3 };
  const fieldDef_t MDF_DEG  = { 0, 3, 1 };
  const fieldDef_t MDF_UOM  = { 1, 3, 1 };
  const fieldDef_t MDF_PT   = { 0, 5, 1 };
  const fieldDef_t MDF_SP   = { 0, 7, 3 };
  const fieldDef_t MDF_MODE = { 0, 11, 5 };
  const fieldDef_t MDF_BG   = { 1, 5, 11 };

  //
  // * CALIBRATE *
  // +----------------+   
  // |DDDo LLLLLLLLLLL|   L Label for the parameter
  // |DDDU SSSSSSSSSSS|   S value for setpoint/value
  // +----------------+
  //  0123456789012345 
  //
  const fieldDef_t CDF_CV  = { 0, 0, 3 };
  const fieldDef_t CDF_DEG = { 0, 3, 1 };
  const fieldDef_t CDF_UOM = { 1, 3, 1 };
  const fieldDef_t CDF_LBL = { 0, 5, LCD_COLS };
  const fieldDef_t CDF_DTA = { 1, 5, LCD_COLS };

  //
  // * ERROR *
  // +----------------+   
  // |MMMMMMMMMMMMMMMM|   M Message Line 1 (Label)
  // |NNNNNNNNNNNNNNNN|   N Message LIne 2 (Error)
  // +----------------+
  //  0123456789012345 
  // 
  const fieldDef_t EDF_1 = { 0, 0, LCD_COLS };
  const fieldDef_t EDF_2 = { 1, 0, LCD_COLS };

  // Variables
  uint16_t   _pctGauge;
  uint16_t  _CV, _SP;
  char      _szLabel[LCD_COLS + 1];
  char      _szError[LCD_COLS + 1];
  BarGraph  *_bg;
  BigNum    *_bn;

  enum dispMode { DM_MAIN, DM_CONFIG, DM_ERROR } _dispMode;
  enum opMode   { OM_READY, OM_HEAT, OM_COOL } _opMode;
  enum tempMode { TM_DEGC, TM_DEGF, TM_RAW } _tempMode;
  enum ctlMode  { CM_TEMP, CM_PWR, CM_NONE } _ctlMode;

public:
  LCD_Display(uint8_t DAT, uint8_t CLK) : LiquidCrystal_I2C(DAT, CLK)
  {
    _bg = new BarGraph(this, MDF_BG.row, MDF_BG.col, MDF_BG.len);
    _bn = new BigNum(this);

    setMainDisplay();
    setModeReady();
    setCV(0);
    setSP(0);
    setGaugePct(0);
    setTempCel();
    setLabel("");
  }

  ~LCD_Display()
  {
    delete _bn;
    delete _bg;
  }

  inline void clear(void) { LiquidCrystal_I2C::clear(); }

  inline void setCV(uint16_t data) { _CV = data; }   // Set the current value for the measured variable
  inline void setSP(uint16_t data) { _SP = data; }   // Set the Set Point for the measured variable
  inline void setGaugePct(uint8_t data) { if (data < 100) _pctGauge = data; }  // Set the gauge as a %age

  // Set various display types
  inline void setMainDisplay(void) { _dispMode = DM_MAIN; }
  inline void setCfgDisplay(void)  { _dispMode = DM_CONFIG; }
  inline void setErrDisplay(void)  { _dispMode = DM_ERROR; }

  // Set various operating modes
  inline void setModeHeating(void) { _opMode = OM_HEAT; }
  inline void setModeCooling(void) { _opMode = OM_COOL; }
  inline void setModeReady(void)   { _opMode = OM_READY; }
  
  // Set temperature display
  inline void setTempCel(void)   { _tempMode = TM_DEGC; }
  inline void setTempFar(void)   { _tempMode = TM_DEGF; }
  inline void setTempRaw(void)   { _tempMode = TM_RAW; }

  // Set the control mode display
  inline void setCtlPower(void)  { _ctlMode = CM_PWR; }
  inline void setCtlTemp(void)   { _ctlMode = CM_TEMP; }
  inline void setCtlNone(void)   { _ctlMode = CM_NONE; }

  void begin(void)
  {
    // initialise LCD LCD_Display
    LiquidCrystal_I2C::begin(LCD_COLS, LCD_ROWS);
    clear();
    noAutoscroll();
    noCursor();

    // initialise dependent objects
    _bn->begin();
    _bg->begin();
    _bg->setChar(7);  // 0-6 are used by bigNum
  }

  void setLabel(const char *szMesg)
  {
    strncpy(_szLabel, szMesg, sizeof(_szLabel) - 2);
    _szLabel[sizeof(_szLabel) - 1] = '\0';
  }

  void setError(const char *sz1, const char *sz2)
  {
    strncpy(_szLabel, sz1, sizeof(_szLabel) - 2);
    _szLabel[sizeof(_szLabel) - 1] = '\0';
    strncpy(_szError, sz2, sizeof(_szError) - 2);
    _szLabel[sizeof(_szError) - 1] = '\0';
  }

  void update(void)
  {
    char szLine[LCD_COLS + 1];

    this->noDisplay();    // temporarily disable the LCD display

    this->clear();
    switch (_dispMode)
    {
    case DM_MAIN:
      {
        // write the CV as double height digits
        _bn->writeNumber(MDF_CV.row, MDF_CV.col, _CV, MDF_CV.len);

        // units of measure
        this->setCursor(MDF_DEG.col, MDF_DEG.row);
        if (_tempMode == TM_DEGC || _tempMode == TM_DEGF)
          this->write(0xdf);
        else
          this->write(' ');
        this->setCursor(MDF_UOM.col, MDF_UOM.row);
        this->print(strMode(_tempMode));

        // Control mode
        this->setCursor(MDF_PT.col, MDF_PT.row);
        this->print(strMode(_ctlMode));

        // Set point
        this->setCursor(MDF_SP.col, MDF_SP.row);
        sprintf(szLine, "%%0%dd", MDF_SP.len);    // format string (* parameter not working?)
        sprintf(szLine, szLine, _SP);
        this->print(szLine);

        // Status message
        this->setCursor(MDF_MODE.col, MDF_MODE.row);
        this->print(strMode(_opMode));

        _bg->show(_pctGauge);
      }
      break;

    case DM_CONFIG:
      {
        // write the CV as double height digits
        _bn->writeNumber(CDF_CV.row, CDF_CV.col, _CV, CDF_CV.len);

        // Units of measure
        this->setCursor(CDF_DEG.col, CDF_DEG.row);
        if (_tempMode == TM_DEGC || _tempMode == TM_DEGF)
          this->write(0xdf);
        else
          this->write(' ');
        this->setCursor(CDF_UOM.col, CDF_UOM.row);
        this->print(strMode(_tempMode));

        // Label and Set point value
        this->setCursor(CDF_LBL.col, CDF_LBL.row);
        this->print(_szLabel);
        this->setCursor(CDF_DTA.col, CDF_DTA.row);
        this->print(_SP);
      }
      break;

    case DM_ERROR:
      {
        // Two line message
        this->setCursor(EDF_1.col, EDF_1.row);
        this->print(_szLabel);
        this->setCursor(EDF_2.col, EDF_2.row);
        this->print(_szError);
      }
      break;

    default:
      this->setCursor(0, 0);
      this->print("?Display mode");
      break;
    }

    this->display();    // re-enable the LCD display
  }

private:
  const char *strMode(enum opMode op)
  {
    switch (op)
    {
    case OM_READY: return("Ready");
    case OM_HEAT:  return(" Heat");
    case OM_COOL:  return(" Cool");
    }

    return("?????");
  }

  const char *strMode(enum tempMode op)
  {
    switch (op)
    {
    case TM_DEGC: return("C");
    case TM_DEGF: return("F");
    case TM_RAW:  return(" ");
    }

    return("?");
  }

  const char *strMode(enum ctlMode op)
  {
    switch (op)
    {
    case CM_TEMP: return("T");
    case CM_PWR:  return("P");
    case CM_NONE: return(" ");
    }

    return("?");
  }
};

My search is on for the same.

That probably won't work as LiquidCrystal_I2C doesn't have such a constructor. For the I2C version you have to specify the I2C address as well as the columns and rows your display has.

How did you change the hardware? Post the schematics!

@unmesh
what is the I2C Address of your display?

If you know it (0x3F, 0x27 ...) change in LCD_Display.h

  LCD_Display(uint8_t DAT, uint8_t CLK) : LiquidCrystal_I2C(0x3F, 16, 2)

around row 129 replace

LiquidCrystal_SR::begin(LCD_COLS, LCD_ROWS);

by

LiquidCrystal_I2C::init();

replace in all tabs LiquidCrystal_SR by LiquidCrystal_I2C

try to compile.
If you get an error post it.

I can't test it because most of the links for the included libraries are outdated.
If you don't provide actual links - you must do all the work on your own.

1 Like

Thank you all for your replies. The original writer of the code has updated to me on email that he will update the code to I2C since it is the most commonly used protocol and highly requested update on the website.

Hey everyone the author of the code has left me hanging on the edge and the quest is still on. Hoping for everyone's help wherever possible.

Update : I2C address is - 0x3F

OMG you are genius. It started working!!!!!! Thank you so much!!!! Can you help me in turning the LCD backlight on, it turned off after I uploaded the code.

after the init, clear, noAutoscroll noCursor

add an

LiquidCrystal_I2C::backlight();

P.S.: you can say thank you to the helpers with marking their posts with an heart if you think they helped you to understand the code. Furthermore you can mark ONE post as solution if you think it solved your problem.

Yes I figured it out. Thank you once again for the same. I feel I am being greedy at this point but can you help me reduce the screen flickering/ refreshing/ updating? It is more a cosmetic rather than a functional issue but if you could help out a bit.

I think I can't help to much with that issue. That seems to be more a logical error that the display gets activated to often and not only when new data is available.

The sketch does have an awful lot of included libraries without a description where to get them. I can't compile it.

If you could tell me which libraries you need I can share them with you. I have successfully compiled and run the code on my nano

ResponsiveAnalogRead-master.zip (11.1 KB)
MD_KeySwitch-master.zip (382.6 KB)
MD_REncoder-master.zip (19.0 KB)

"Debug.h" ,"Hardware.h", "ConfigData.h", "Bargraph.h", "LCD_Display.h" - these are few files which are present in the zip file i shared in the beginning of the thread.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.