Vertical 16x2 LCD numbers made. Help with improvement?

I'm new to the community with a bit of experience in coding an electronics.


I've created a basic sketch to use the 16x2 LCD in a vertical position to display the speed and battery level of my scooter using a VESC.

I have a video testing both inputs( speed on top, battery level bottom).

I am looking for any help to maybe create a library which anyone can use and possibly evolve into a useful mainstream tool.

Thank you for your interest.

1 Like

Welcome to the forum

I am not sure what the image that you posted is supposed to illustrate. Can you please explain further

If you rotate the image you will see 48 on top and 47 below. I want to post the code but I'm unsure what to do. I know I need to surround the code so it is formatted properly for the site. Any pointers for that?

To post code properly you need to put it in code tags. The easiest way to do that is to use Edit/Copy for Forum in the IDE and paste what is copied here in a new post. The code tags will have been added for you

#include <Arduino.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <VescUart.h>

byte glyph_0[] = {
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B11111};

byte glyph_1[] = {
    B11111,
    B00001,
    B00001,
    B00001,
    B00001,
    B00001,
    B00001,
    B11111};

byte glyph_2[] = {
    B11111,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001};

byte glyph_3[] = {
    B11111,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B11111};

byte glyph_4[] = {
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B10001,
    B11111};

byte glyph_5[] = {
    B11111,
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
    B10000,
    B11111};

VescUart UART;
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address and dimensions

int inDigit;
int rpm;
float voltage;
float current;
int power;
float amphour;
float tach;
float distance;
float velocity;
float watthour;
float batpercentage;
float temp;
// maximum values
int maxa = 0;
int maxvel = 0;
int maxtemp = 0;

// avg consumption wh/km
float whkm = 0;

void vert_numbers()
{
  switch (inDigit)
  {
  case 0:
    lcd.write(byte(5));
    lcd.write(byte(1));
    break;
  case 1:
    lcd.write(byte(0));
    lcd.write(byte(0));
    break;
  case 2:
    lcd.write(byte(2));
    lcd.write(byte(4));
    break;
  case 3:
    lcd.write(byte(4));
    lcd.write(byte(4));
    break;
  case 4:
    lcd.write(byte(0));
    lcd.write(byte(5));
    break;
  case 5:
    lcd.write(byte(4));
    lcd.write(byte(2));
    break;
  case 6:
    lcd.write(byte(3));
    lcd.write(byte(2));
    break;
  case 7:
    lcd.write(byte(0));
    lcd.write(byte(1));
    break;
  case 8:
    lcd.write(byte(3));
    lcd.write(byte(3));
    break;
  case 9:
    lcd.write(byte(0));
    lcd.write(byte(3));
    break;
  }
}
void dashVelocity() {
  if (velocity >= 0 && velocity <= 9)
  {
    lcd.setCursor(14, 0);
    lcd.write(' ');
    lcd.setCursor(14, 1);
    lcd.write(' ');
    lcd.setCursor(14, 0);
    inDigit = 0;
    vert_numbers();
    lcd.setCursor(14, 1);
    inDigit = (velocity, 0);
    vert_numbers();
    delay(300);
  }
  else if (velocity >= 10 && velocity <= 99)
  {
    int firstDigit = int(velocity) / 10;
    int secondDigit = int(velocity) % 10;

    lcd.setCursor(14, 0);
    lcd.write(' ');
    lcd.setCursor(14, 1);
    lcd.write(' ');
    lcd.setCursor(14, 0);
    inDigit = firstDigit;
    vert_numbers();
    lcd.setCursor(14, 1);
    inDigit = secondDigit;
    vert_numbers();
    delay(300);
  }
}

void dashBattery() {
  if (batpercentage >= 0 && batpercentage <= 9)
  {
    lcd.setCursor(1, 0);
    lcd.write(' ');
    lcd.setCursor(1, 1);
    lcd.write(' ');
    lcd.setCursor(1, 0);
    inDigit = 0;
    vert_numbers();
    lcd.setCursor(1, 1);
    inDigit = (batpercentage, 0);
    vert_numbers();
    delay(200);
  }
  else if (batpercentage >= 10 && batpercentage <= 99)
  {
    int firstDigit = int(batpercentage) / 10;
    int secondDigit = int(batpercentage) % 10;

    lcd.setCursor(1, 0);
    lcd.write(' ');
    lcd.setCursor(1, 1);
    lcd.write(' ');
    lcd.setCursor(1, 0);
    inDigit = firstDigit;
    vert_numbers();
    lcd.setCursor(1, 1);
    inDigit = secondDigit;
    vert_numbers();
    delay(200);
  }
}

void dashTest()
{
  for (int i = 0; i < 80; i++)
  {
    batpercentage = i;
    velocity = i;
    dashVelocity();
    dashBattery();
  }
  for (int i = 80; i > 0; i--)
  {
    batpercentage = i;
    velocity = i;
    dashVelocity();
    dashBattery();
  }
}

void setup()
{
  Serial.begin(115200);
  Wire.begin();
  lcd.begin(16, 2); // Set the LCD dimensions
  lcd.backlight();  // Turn on the LCD backlight

  lcd.createChar(0, glyph_0);
  lcd.createChar(1, glyph_1);
  lcd.createChar(2, glyph_2);
  lcd.createChar(3, glyph_3);
  lcd.createChar(4, glyph_4);
  lcd.createChar(5, glyph_5);

  lcd.clear();
  while (!Serial)
  {
    ;
  }

  /** Define which ports to use as UART */
  UART.setSerialPort(&Serial);

  delay(1000);
}

void loop()
{
  ////////// Read values //////////
  if (UART.getVescValues())
  {

    rpm = (UART.data.rpm) / 14; // The '7' is the number of pole pairs in the motor. Most motors have 14 poles, therefore 7 pole pairs
    voltage = (UART.data.inpVoltage);
    current = (UART.data.avgInputCurrent);
    power = voltage * current;
    temp = (UART.data.tempMosfet);
    amphour = (UART.data.ampHours);
    watthour = amphour * voltage;
    tach = (UART.data.tachometerAbs) / 84;                          // The '42' is the number of motor poles multiplied by 3
    distance = tach * 3.142 * (1.0 / 1000.0) * 0.254 * (1.0 / 1.0); // Motor RPM x Pi x (1 / meters in a mile or km) x Wheel diameter x (motor pulley / wheelpulley)
    velocity = rpm * 3.142 * (60.0 / 1000.0) * 0.254 * (1.0 / 1.0); // Motor RPM x Pi x (seconds in a minute / meters in a mile) x Wheel diameter x (motor pulley / wheelpulley)
    batpercentage = (((voltage - 48) / 16) + 0.03) * 100;           // ((Battery voltage - minimum voltage) / number of cells) x 100

    delay(50);
  }

  if (current > maxa)
  {
    maxa = current;
  }

  if (velocity > maxvel)
  {
    maxvel = velocity;
  }

  if (temp > maxtemp)
  {
    maxtemp = temp;
  }

  whkm = watthour / distance;

  dashTest();

  delay(50);
}

If you want to turn this into a library then I think the first thing to do is to reduce the code to a minimum. Perhaps just a sketch that outputs a count to a couple of locations on the vertical LCD

As it stands the sketch is too tied to the use of a VESC which obfuscates how it works

Incidentally, you could eliminate the switch/case in the vert_numbers() function by using an array of structs defining the bytes to be output for each digit

Going to clean up the code an make the font a little more elegant. I have just written out the bytes. When I get home I can work on building a proper library.

Are you aware, there are a maximum number of custom characters allowed, and the number is small-ish? Have you confirmed that you can form all the numerals with the ones you have?

When you are developing a library, it is good to create it as additional .h and .cpp files in the same directory as the test sketch. They will appear as additional tabs for the project, in the IDE. Then when you're happy with it, you can begin to move it into a library folder.

There can only be eight custom characters at a time. Also, redefining a custom character that is currently being displayed will change what is displayed.

I am fully aware of the custom character limit. I am home now so give me a bit

Spent a little time on the library and came up with vNumbers

Here is the contents of the README:


vNumbers Library
================

The vNumbers library provides a way to display rotated numbers on an LCD display viewed vertically. It includes predefined conbinations of glyphs for numbers 0 to 9.

Usage
-----
1. Include the vNumbers library in your Arduino sketch by adding the following line at the beginning of your code:
   
   #include "vNumbers.h"

2. Create an instance of the vNB class:
   vNB vNumbers;

3. Call the `setup()` function to initialize the creation of the custom glyphs:
   vNumbers.setup();

4. Use the `vNumbers()` function to display the larger vertical number on the LCD display:
   
   
   vNumbers.vNumbers(42); // Displays the number 42
   
   
   int var = 42;
   vNumbers.vNumbers(var); // Will also display the number 42
   
	float myFloat;
    
	float randomVariable = 42.2;

    myFloat = randomVariable;

    vNumbers.vNumbers(int(myFloat)); // Also displays 42


   
   The `vNumbers()` function accepts an integer as input. It can display numbers from 0 to 99. If the input is  100 then 99 will be displayed, but nothing will be displayed for numbers outside this range  (this will most likely change soon).
  
Vertical position
-----------------

The position on the numbers is (13, 0) and (13, 1) making the top of the screen the right side in a horizontal position. I found this was the easiest placement with my current code.

Dependencies
------------
- Arduino.h: This library requires the Arduino core library for basic functionality.
- LiquidCrystal or LiquidCrystal_I2C: The vNumbers library depends on either the LiquidCrystal or LiquidCrystal_I2C library for controlling the LCD display. Make sure to include the appropriate library based on your LCD type.

License
-------
This library is unreleased. 
Author
------
The vNumbers library is developed and maintained by Vatrius.

Keep in mind this is just a rough outline of what I have so far.

Here's what the numbers look like so far.


I'm gonna need help turning this into a library if anyone has an interest.

I suggest

a) follow the Arduino Example for a first library:

b) the Library specification is a good source to learn about the internals:

c) read and apply the Arduino Style Guide for Creating Libraries

d) and finally the only externals source I would recommend as a starter is Sparkfuns Hot to write a great library


before you start to migrate your code into a library

  • make a clean stand alone code. You can post it to get feedback from the forum.
  • design the API for your library, think about your users and create the API of your library

specific to your usecase:

  • don't rely on a specific LCD library. Read about the LCD API 1.0 - rely only on the LCD API 1.0 functions only.
  • don't force the user to name his LCD object in a specific way, let the user hand over the LCD object you would like to use in your library
1 Like

@noiasca thank you

specific to your usecase:

  • don't rely on a specific LCD library. Read about the LCD API 1.0 - rely only on the LCD API 1.0 functions only.
  • don't force the user to name his LCD object in a specific way, let the user hand over the LCD object you would like to use in your library

exactly where I'm having issues

I will return tomorrow with more news after I take more time to work this library out.

Have you changed the font ? I get different shapes when I try your sketch.

UKHeliBob wrote about reducing it to a minimum.
You have a switch-case to create a number out of two bytes, but that information can be put into a table.

So this is what I got. You may use it or not, do what suits you best.

// 10 August 2023
// Code by: xvatriusx
// Forum: https://forum.arduino.cc/t/vertical-16x2-lcd-numbers-made-help-with-improvement/1156130
// Changes by Koepel:
//   Remove everything that is not for the vertical font.
//   Changed Arduino B00000 to 'C' language 0b00000.
//   Everything in a table, so it is easier to add PROGMEM.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

const byte glyph[6][8] =
{ 
  {
    0b00000,
    0b00000,
    0b00000,
    0b00000,
    0b00000,
    0b00000,
    0b00000,
    0b11111
  },
  {
    0b11111,
    0b00001,
    0b00001,
    0b00001,
    0b00001,
    0b00001,
    0b00001,
    0b11111
  },
  {
    0b11111,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001
  },
  {
    0b11111,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b11111
  },
  {
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b11111
  },
  {
    0b11111,
    0b10000,
    0b10000,
    0b10000,
    0b10000,
    0b10000,
    0b10000,
    0b11111
  }
};

const byte combine[10][2] =
{
  {5, 1},      // 0
  {0, 0},      // 1
  {2, 4},      // 2
  {4, 4},      // 3
  {0, 5},      // 4
  {4, 2},      // 5
  {3, 2},      // 6
  {0, 1},      // 7
  {3, 3},      // 8
  {0, 3},      // 9
};

LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD address and dimensions

void setup()
{
  Serial.begin(115200);
  Wire.begin();
  lcd.begin(16, 2); // Set the LCD dimensions
  lcd.backlight();  // Turn on the LCD backlight

  for( int i=0; i<6; i++)
  {
    lcd.createChar(i, glyph[i]);
  }

  lcd.clear();

  // Demonstration of all the numbers
  vert_number(0, 0, 0);
  vert_number(0, 1, 1);
  vert_number(1, 0, 2);
  vert_number(1, 1, 3);
  vert_number(2, 0, 4);
  vert_number(2, 1, 5);
  vert_number(3, 0, 6);
  vert_number(3, 1, 7);
  vert_number(4, 0, 8);
  vert_number(4, 1, 9);
}

void loop()
{
  delay(100);
}

// Maybe add that a number can be  placed halfway the vertical row ?
// The vertical row is 0...7
// The vertical column is 0...1
void vert_number(int row, int column, int inDigit)
{
  int x = 14 - (2 * row);
  int y = column;
  lcd.setCursor(x, y);          // x, y
  lcd.write(combine[inDigit][0]);
  lcd.write(combine[inDigit][1]);
}

Demonstration in Wokwi simulation:

based on the work of @Koepel

Vertical Numbers on a LCD Display - Split - Wokwi ESP32, STM32, Arduino Simulator

when you use a template, you can let the user handover "any" LCD Class

// create vertical LCD using the above LCD HW
//the Class      the LCD Class         object     (object of HW LCD)
VerticalLCD     <LiquidCrystal_I2C>  verticalLCD     (hwLCD);
1 Like

New font versus old font:
afbeelding
You have changed to three characters for a 15x8 font :smiley:

@Koepel yes, I have changed the font. I will post the code when I get home.
I'm looking at the possible placements on the screen and I have
1. Screen fit
The full screen can be filled from top to bottom with up to 10 large numbers.
2. Alt 1
The position on the numbers is shifted down but only 8 large numbers can occupy the screen at one time
3. Alt 2
The position of the numbers are shifted down one more space with 8 large numbers able to occupy the screen.

I'm seeing these positions being used to allow the user to define the position of the numbers and also allow numbers greater than 99 to be displayed. When I work out the kinks I believe the positions could also be used for vertical and horizontal scrolling numbers

I would create two functions (or two options for a single function), so both can be used. Like Celsius and Fahrenheit can both be used with some libraries.

First function: A font row of 0...4, starting at the top, leaving the bottom character unused.
Second function: A LCD row position of 0...13.

For example:

// Two different functions:
verticalLCD.number(0, 0, 9);
verticalLCD.numberLCDRow(5, 0, 9);

// A single function:
verticalLCD.number(0, -1, 0, 9);   // set unused variable to -1
verticalLCD.number(-1, 5, 0, 9);   // set unused variable to -1

I have a feeling that there must be a better way.

By the way: scrolling is hard.

and looks awfully on a character LCD anyway ...