Trying to adapt BigNum.h for use with I2C LCD

Hello,

A while ago Marco Colli made this library that allows you to make two-row tall characters. However, it was not designed for use with I2C based LCD’s, which is what I am trying to use it with.

So now I am trying to make it work with the LCD_I2C library. I’ve managed to edit the .h file and the example sketch of the BigNum library to extent that I can. I am very inexperienced when it comes to editing libraries, so I would like some help.

I have tried other big number libraries, none of which have worked.

This is what I have so far:

The example sketch that I edited:

// Use two lines of an LCD display to show large numbers

// The large font spans one characetr with over 2 LCD display lines
// to give a font which is similar to a 7-segment display

#include <LCD_I2C.h>
#include "BigNum.h"


LCD_I2C lcd(0x27);
static BigNum bn(&lcd);


// Waiting time between numbers in milliseconds
#define  WAIT_DELAY  1000

void setup()
{
  Serial.begin(57600);
  
  // initialise LCD display
  lcd.begin();
  lcd.print(F("LCD Big Chars"));

  // initialise bigchars
  bn.begin();
  
  lcd.clear();  // clear the display and ...
  lcd.noCursor(); // ... display the cursor
}

void allDigits(void)
{
  for (uint8_t i=0; i<10; i++)
    bn.writeNumber(0, i, i, 1);
}

void loop(void)
{
  static uint16_t  count = 0;

  bn.setSingleFont();
  allDigits();
  delay(10*WAIT_DELAY);
  lcd.clear();
  
  bn.setDoubleFont();
  allDigits();
  delay(10*WAIT_DELAY);
  lcd.clear();
  
  while (true)
  {
    bn.writeNumber(0, 0, count++, 4, true);
    delay(WAIT_DELAY);
  }
}

And the .h file that I edited:

#pragma once

#define ORIGINAL_FONT 0

#include "Arduino.h"
#include "LCD_I2C.h"

// BigNum -----------------------------------------------
// Draw a large character over 2 lines on the LCD display
// Marco Colli July 2016

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

// 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

// Two types of fonts are defined - single and double stroke widths
// Single width strokes
const PROGMEM uint8_t 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 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
//   | |   |_|    _|   |_    |_|     |     |    

// 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

// Only one font is defined but it takes on less user defined character
// Single width strokes
const PROGMEM uint8_t 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

class BigNum
{
protected:
  LCD_I2C lcd;

public:
  BigNum(LCD_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]);
  };
};

Right now, when I try and upload the example sketch with the .h file included, I get this error message.

C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:61:5: note:   candidate expects 1 argument, 0 provided
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:58:7: note: candidate: constexpr LCD_I2C::LCD_I2C(const LCD_I2C&)
 class LCD_I2C : public Print
       ^~~~~~~
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:58:7: note:   candidate expects 1 argument, 0 provided
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:58:7: note: candidate: constexpr LCD_I2C::LCD_I2C(LCD_I2C&&)
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:58:7: note:   candidate expects 1 argument, 0 provided
exit status 1
class 'BigNum' does not have any field named '_lcd'

Which highlights this line in the .h file:

BigNum(LCD_I2C*lcd) : _lcd(lcd)

I am not sure how to proceed from here. Any help/advice would be greatly appreciated!

The original line:

  BigNum(LiquidCrystal *lcd) : _lcd(lcd)

Your edited line:

BigNum(LCD_I2C*lcd) : _lcd(lcd)

You left out the space before the *.

I hoped it would be a simple fix, but now this error message is showing:

In file included from C:\Users\lawre\Documents\Arduino\bignumtest\bignumtest.ino:7:0:
sketch\BigNum.h: In constructor 'BigNum::BigNum(LCD_I2C*)':
BigNum.h:77:26: error: class 'BigNum' does not have any field named '_lcd'
   BigNum(LCD_I2C *lcd) : _lcd(lcd)
                          ^~~~
BigNum.h:77:34: error: no matching function for call to 'LCD_I2C::LCD_I2C()'
   BigNum(LCD_I2C *lcd) : _lcd(lcd)
                                  ^
In file included from C:\Users\lawre\Documents\Arduino\bignumtest\bignumtest.ino:6:0:
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:61:5: note: candidate: LCD_I2C::LCD_I2C(uint8_t)
     LCD_I2C(uint8_t address) { _address = address; }
     ^~~~~~~
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:61:5: note:   candidate expects 1 argument, 0 provided
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:58:7: note: candidate: constexpr LCD_I2C::LCD_I2C(const LCD_I2C&)
 class LCD_I2C : public Print
       ^~~~~~~
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:58:7: note:   candidate expects 1 argument, 0 provided
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:58:7: note: candidate: constexpr LCD_I2C::LCD_I2C(LCD_I2C&&)
C:\Users\lawre\Documents\Arduino\libraries\LCD_I2C\src/LCD_I2C.h:58:7: note:   candidate expects 1 argument, 0 provided
exit status 1
class 'BigNum' does not have any field named '_lcd'

This error message highlights the same line as the previous one.

I tried messing around with the class a bit and got managed to find something that throws a much simpler error message, although it might be the completely wrong approach.

Changed class:

BigNum(LCD_I2C(lcd));

New error message:

C:\Users\lawre\AppData\Local\Temp\ccfBWRKh.ltrans0.ltrans.o: In function `global constructors keyed to 65535_0_bignumtest.ino.cpp.o.2033':
<artificial>:(.text.startup+0xc8): undefined reference to `BigNum::BigNum(LCD_I2C)'
collect2.exe: error: ld returned 1 exit status
exit status 1
Error compiling for board Arduino Nano.

Look at you edits in the class in the header file.

Original line:

 LiquidCrystal  *_lcd;

Your line:

 LCD_I2C lcd;

Then look at the error message:

class 'BigNum' does not have any field named '_lcd'

Now can you see the error?
Hint, you have changed more than just the type of the variable.

--- bill

Unfortunately, I simply do not have a good enough understanding of how classes work to figure it out based on your hint.

I'm really not sure where to begin, so a more in-depth explanation would be greatly appreciated.

Thanks!

boxed:
Unfortunately, I simply do not have a good enough understanding of how classes work to figure it out based on your hint.

I'm really not sure where to begin, so a more in-depth explanation would be greatly appreciated.

Thanks!

In this case you do not need to have any understanding of how classes work.
This is a case of paying close attention to the error message and then playing "one of things does not look like the other" comparing the original declaration and what you changed it to.

Again, error code says:
class 'BigNum' does not have any field named '_lcd'

Here is some more:

            Pointer ('star' means pointer)
  Type         |
    |
LiquidCrystal  *_lcd;
                  |
               Variable Name is _lcd

Now compare that to what you changed.
Like I said; you change more than just the type.

--- bill

I made these changes to the class:

class BigNum
{
protected:
  LCD_I2C *lcd;

public:
  BigNum(LCD_I2C(lcd));

I am getting this error now:

C:\Users\lawre\AppData\Local\Temp\ccTVNMP6.ltrans0.ltrans.o: In function `global constructors keyed to 65535_0_bignumtest.ino.cpp.o.2031':
<artificial>:(.text.startup+0xc8): undefined reference to `BigNum::BigNum(LCD_I2C)'
collect2.exe: error: ld returned 1 exit status
exit status 1
Error compiling for board Arduino Nano.

I feel completely lost right now, but I really appreciate the help. What else can I try?

boxed:
Unfortunately, I simply do not have a good enough understanding of how classes work
I'm really not sure where to begin

It might sound a bit harsh, but begin with a C++ course, book or tutorial.
Trial-and-error is a really inefficient way to learn programming. There are well-defined rules and patterns for how to write classes, without them, you're lost.

Pieter

Pieter, I completely agree. I figured this would be a simple task, and I guess I was wrong. I am just asking for some help so I can get my project up and running.

You seem to be struggling with some basic things like syntax, which is absolutely fine, everyone starts out like that, but an online forum is not the right place to get help with that, nobody on here wants to spend time to teach it to every new user that comes along. This is something that you should learn from other resources like books, online tutorials, lectures, etc.

To answer your question, you have to provide both a declaration and a definition for each function. If you only provide a declaration, you’ll get an error undefined reference to <function name>.

Try this:

class BigNum {
protected:
  LCD_I2C *_lcd;

public:
  BigNum(LCD_I2C *lcd) : _lcd(lcd) {}

  [...]

};

Here, BigNum(LCD_I2C *lcd) is a declaration, : _lcd(lcd) {} is a definition.

Pieter, thank you very much for the help. What you suggested worked perfectly. I agree that this is not the place to come for help with basic things like syntax, and I will refrain from asking questions like that in the future.

Glad to hear you got it working :slight_smile:

I don't want to scare you away from asking questions on this forum of course, there are many scenarios where finding an expert to answer your (for him/her) simple question is much more efficient than spending hours or days struggling yourself.
On the other hand, for basic questions that are answered by most textbooks, a structured book/lecture/tutorial is much more efficient as a learning tool than the fragmented information you find on a forum like this one.

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