LCD bar graph question

Hi,

For a project I’m building I was wondering if it is possible to let a bar graph with origin in the center of the LCD row to move from the center to the right or center to the left?
I’m building a Center of Gravity meter to determine my balance of my RC planes. I’m using some strain gauge with HX711 to get the weights. Not only do I want to measure the balance from tail to nose as well the balance from the left wing to the right wing.
When the weight on the left main gear and the weight on the right main gear are equal the plane is in balance. When one side is heavier than the other side I want the bar graph to increase to the heavier side.
In case 4 I display the weight from the left and right main gear. I was thinking by subtracting both weights from each other to use that value to increase the bar graph. The problem is I don’t know how to write it down. I once did something for a fuel gauge but it took a lot of writing. Maybe there’s a much easier way?

#include <LiquidCrystal_I2C.h>
#include <menuLCDs.h>
#include <Wire.h>
#include <menuFields.h>
#include <quadEncoder.h>//quadrature encoder driver and fake stream
#include <keyStream.h>//keyboard driver and fake stream (for the encoder button)
#include <chainStream.h>// concatenate multiple input streams (this allows adding a button to the encoder)
#include <HX711.h>

//LiquidCrystal lcd1(7, 6, 5, 4, 3, 2);
LiquidCrystal_I2C lcd1(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the lcd1 I2C address

const float calibration_factor_LA = 218.25;//This value is obtained using the SparkFun_HX711_Calibration sketch
const float calibration_factor_RA = 219.5; //This value is obtained using the SparkFun_HX711_Calibration sketch
const float calibration_factor_Nose = 219.75; //This value is obtained using the SparkFun_HX711_Calibration sketch
const float Zwaartekracht =9.81;

#define DOUT_RA  40
#define CLK_RA   41
#define DOUT_LA  36
#define CLK_LA   37
#define DOUT_Nose  38
#define CLK_Nose   39

HX711 scale_RA(DOUT_RA, CLK_RA);
HX711 scale_LA(DOUT_LA, CLK_LA);
HX711 scale_Nose(DOUT_Nose, CLK_Nose);

////////////////////////////////////////////
// ENCODER (aka rotary switch) PINS
// rotary
#define encA  10
#define encB 12
//this encoder has a button here
#define encBtn 13

byte CGL[8] = {B00011,B00100,B01000,B01000,B01111,B01111,B00111,B00011};
byte CGR[8] = {B11000,B11100,B11110,B11110,B00010,B00010,B00100,B11000};


static unsigned long NextTrigger = 0;
int opdracht = 0;
int plotDelay = 200;
bool pollmenu = true;

//aux function
void nothing() {}
void Afzonderlijke_Gewichten(){opdracht = 1; pollmenu = false; lcd1.clear();}  // Hier zijn alle afzonderlijke gewichten te zien
void Totaal_Gewicht(){opdracht = 2; pollmenu = false; lcd1.clear();}  // totaal gewicht toestel
void CGmeten(){opdracht = 3; pollmenu = false; lcd1.clear();}    // Hier wordt het toe te voegen of af te trekken gewicht weergegeven op het corectiepunt CP
void Balance(){opdracht = 4; pollmenu = false; lcd1.clear();}    // Balance AC L/R

int MainToNose = 0;
int MainToCG = 0;
int CoGToCP = 0;

/////////////////////////////////////////////////////////////////////////
// MENU DEFINITION
// here we define the menu structure and wire actions functions to it
// empty options are just for scroll testing
  
MENU(Instellingen,"Instellingen",   // Hier zijn de afstanden aan te passen van de onderlinge punten
  FIELD(MainToNose,"Main to Nose","mm",-2000,2000,10,1,nothing),
  FIELD(MainToCG,"Main to CG","mm",-2000,2000,10,1,nothing),
  FIELD(CoGToCP,"CoG to CP","mm",-2000,2000,10,1,nothing)
  );

MENU(Gewichten,"Gewichten",
  OP("Gewichten",Afzonderlijke_Gewichten),
  OP("Totaal Gewicht",Totaal_Gewicht)
 ); 

MENU(mainMenu,"Main menu",
  OP("CG Meten",CGmeten),
  OP("Balance", Balance),
  SUBMENU(Gewichten),
  SUBMENU(Instellingen)
);


//the quadEncoder
quadEncoder quadEncoder1(encA,encB);//simple quad encoder driver
quadEncoderStream enc(quadEncoder1,5);// simple quad encoder fake Stream

//a keyboard with only one key :D, this is the encoder button
keyMap encBtn_map[]={{-encBtn,menu::enterCode}};//negative pin numbers means we have a pull-up, this is on when low
keyLook<1> encButton(encBtn_map);

//multiple inputs allow conjugation of the quadEncoder with a single key keyboard that is the quadEncoder button
Stream* in[]={&enc,&encButton};
chainStream<2> quadEncoder_button(in);

//describing a menu output, alternatives so far are Serial or LiquidCrystal lcd1
menuLCD lcd(lcd1,20,4);

/////////////////////////////////////////////////////////////////////////
void setup() { 
  quadEncoder1.begin();
  pinMode(encBtn, INPUT);
  digitalWrite(encBtn,1);
  Serial.begin(9600);

  lcd1.begin(20,4);
  lcd1.clear();
  lcd1.setCursor(0,1);
  lcd1.print("   CoG Calculator   ");
  lcd1.setCursor(0,3);
  lcd1.print("    By Benovitch    ");
  lcd1.createChar(1,CGL);
  lcd1.createChar(2,CGR);
  
 long zero_factor_LA = scale_LA.read_average(5);
 long zero_factor_RA = scale_RA.read_average(5);
 long zero_factor_Nose = scale_Nose.read_average(5);
  
    delay(1500);
    
  scale_RA.set_scale(calibration_factor_RA); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale_RA.set_offset(zero_factor_RA); //Zero out the scale using a previously known zero_factor
  scale_LA.set_scale(calibration_factor_LA); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale_LA.set_offset(zero_factor_LA); //Zero out the scale using a previously known zero_factor
  scale_Nose.set_scale(calibration_factor_Nose); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale_Nose.set_offset(zero_factor_Nose); //Zero out the scale using a previously known zero_factor
  
scale_LA.tare();
scale_RA.tare();
scale_Nose.tare();
  delay(10);

}

  
void loop() {
  if (millis() > NextTrigger){
      int Gewicht_RA = 900 + scale_RA.get_units() ;
      int Gewicht_LA = 900 + scale_LA.get_units() ;
      int Gewicht_Nose =1000 + scale_Nose.get_units() ;
      
      float Totaal_Gewicht_AC = (Gewicht_RA + Gewicht_LA + Gewicht_Nose); //Totaal_Gewicht = functie --> naam variable gewijzigd
      float Gewicht_MainGear = (Gewicht_RA + Gewicht_LA);
      float Afstand_CGtoNose = (MainToNose - MainToCG); //CG ligt dus tussen Main gear en Nose gear? --> Moment voor de CG = negatief, Moment achter de CG = positief?
      float Moment_CGtoMG = abs(Gewicht_MainGear * MainToCG);
      float Moment_CGtoNG = abs(Gewicht_Nose * Afstand_CGtoNose);
      //float Total_Moment = (Moment_CGtoMG - Moment_CGtoNG) ;
      float Gewicht_CP = ((Moment_CGtoMG - Moment_CGtoNG)/CoGToCP) ;     // Hiermee wordt het eigenlijke gewicht op het CP berekend --> CP???
      //float Gewicht_CP = ((5000*9.81*4.5)-(1500*9.81*12))/(12*9.81);
      
      switch (opdracht){
  
.....

                   case 4:
          lcd1.setCursor(6,0);
          lcd1.print("Balance");
          lcd1.setCursor(0,1);
          lcd1.print(Gewicht_LA);
          lcd1.print(" gr");
          lcd1.print(" ");
          lcd1.setCursor(13,1);
          lcd1.print(Gewicht_RA);
          lcd1.print(" gr");
          lcd1.print(" ");
          lcd1.setCursor(9,2);
          lcd1.write (byte(1));
          lcd1.setCursor(10,2);
          lcd1.write (byte(2));
         break;
          
        default:
         pollmenu = true;
        break;
     }
      
     if (opdracht != 0) {      // om vanuit de berekeningen terug naar het menu te gaan
       int stateButton = digitalRead(encBtn);
       if(stateButton == 0) {
         opdracht = 0;
         pollmenu = true;
         lcd.clear();
         lcd.drawn=NULL;
         delay(500);
       }
     } 

  NextTrigger = (long)(millis() + plotDelay);
 }

if (pollmenu) {mainMenu.poll(lcd, quadEncoder_button);}; 
}

A part of the code from the fuel gauge:

  if (fuel >= 10) {
    lcd.setCursor(0,3);
    lcd.write(byte(4));
  } 
  else {
    lcd.setCursor(0,3);
    lcd.print(" ");
  }
  if (fuel >= 20) {
    lcd.setCursor(1,3);
    lcd.write(byte(4));
  } 
  else {
    lcd.setCursor(1,3);
    lcd.print(" ");
  }
  if (fuel >= 30) {
    lcd.setCursor(2,3);
    lcd.write(byte(4));
  } 
  else {
    lcd.setCursor(2,3);
    lcd.print(" ");
  }

...

  }
  if (fuel >= 190) {
    lcd.setCursor(18,3);
    lcd.write(byte(4));
  } 
  else {
    lcd.setCursor(18,3);
    lcd.print(" ");
  }
  if (fuel >= 200) {
    lcd.setCursor(19,3);
    lcd.write(byte(4));
  } 
  else {
    lcd.setCursor(19,3);
    lcd.print(" ");
  }

Here is my effort at a center out bargraph. Hope you can use it, or parts anyway. Using this library liquid cryatal library

/*center out bargraph
by c.goulding
using F. Malpartida liquid crystal library
https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home
and  4x20 I2C display
pot connected to pin A0

*/


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

#define I2C_ADDR    0x27  // Define I2C Address where the PCF8574A is
#define BACKLIGHT_PIN     3
#define En_pin  2
#define Rw_pin  1
#define Rs_pin  0
#define D4_pin  4
#define D5_pin  5
#define D6_pin  6
#define D7_pin  7


LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

byte box[8] =
{
    0b11111,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b11111
};

byte solid[8] =
{
    0b11111,
    0b11111,
    0b11111,
    0b11111,
    0b11111,
    0b11111,
    0b11111,
    0b11111
};

byte half[8] =
{
    0b11111,
    0b10001,
    0b10001,
    0b10001,
    0b11111,
    0b11111,
    0b11111,
    0b11111
};

byte threeQuarter[8] =
{
    0b11111,
    0b10001,
    0b11111,
    0b11111,
    0b11111,
    0b11111,
    0b11111,
    0b11111
};

byte oneQuarter[8] =
{
    0b11111,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b10001,
    0b11111,
    0b11111
};

const byte anaInPin = A0;
int anaInCounts;
const char lcdWide = 18; // keeps sides even and not overflowing to line 
const byte center = (lcdWide / 2) + 1;  
const int adCountsFull = 1024;  // yeah 1023 for the pedantic
const int countsPerBox =  adCountsFull / lcdWide;  //scale boxes to ad count
float numBoxes;  
// lcd update timer vars
unsigned long lcdUpdateInterval = 500;
unsigned long lcdUpdate = 0;

void setup()
{
    Serial.begin(9600);
    lcd.begin (20,4);
    delay(1000);
    lcd.createChar(1, solid);
    lcd.createChar(2, box);
    lcd.createChar(3, half);
    lcd.createChar(4, threeQuarter);
    lcd.createChar(5, oneQuarter);
    // Switch on the backlight
    lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
    lcd.setBacklight(HIGH);
    lcdClearPos(3,0);
    lcd.print("0               1023");
}

void loop()
{
    if(millis() - lcdUpdate > lcdUpdateInterval)
    {
        lcdUpdate = millis();
        anaInCounts = (analogRead(anaInPin));
        lcdClearPos(0,5);
        lcd.print("ADC  ");
        lcd.print(anaInCounts);
        int mapToCenter = map(anaInCounts, 0,1023,-512,511);  
        numBoxes = float(mapToCenter) / countsPerBox;
        lcdClearPos(1,4);
        lcd.print("Boxes  ");
        lcd.print(numBoxes);
        float halfBox = abs(numBoxes - int(numBoxes));  // get fractional part
        lcdClearPos(2, center);
        char centerChar ;
        // choose center char fo fine tuning (0 to 0.5)
        if(abs(numBoxes) < 0.51)
        {
            byte temp = halfBox * 100;
            switch(temp)
            {
            case 0 ... 9:
                centerChar = 2;
                break;

            case 10 ... 19:
                centerChar = 5;
                break;

            case 20 ... 29:
                centerChar = 3;
                break;

            case 30 ... 39:
                centerChar = 4;
                break;

            case 40 ... 50:
                centerChar = 1;
                break;
            }
        }
        else
        {
             centerChar = 2;   
        }
        if(numBoxes >= 0)
        {
            lcd.leftToRight();
            lcd.write(centerChar);
            for(int n = 0; n < int(numBoxes); n++)
            {
                lcd.write(1);
            }
            if(halfBox > 0.5)
            {
                lcd.write(3);
            }
        }
        else
        {
            lcd.rightToLeft();
            lcd.write(centerChar);
            for(int n = 0; n < abs(int(numBoxes)); n++)
            {
                lcd.write(1);
            }
            if(halfBox > 0.5)
            {
                lcd.write(3);
            }
            lcd.leftToRight();
        }
    }
}


void lcdClearLine(byte line)
{
    lcd.setCursor(0, line);
    lcd.print("                    ");
    lcd.setCursor(0, line);
}

void lcdClearPos(byte line, byte pos)
{
    lcd.setCursor(0, line);
    lcd.print("                    ");
    lcd.setCursor(pos, line);
}

My 2 cents.

Horizontal bargraph.

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

uint8_t bar0[8]  = {  // EMPTY
  0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
};
uint8_t bar1[8]  = { // 1/5
  0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10
};
uint8_t bar2[8]  = { // 3/5
  0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C
};
uint8_t bar3[8]  = { // FULL
  0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F
};

uint8_t bar4[8]  = { // -3/5
  0x07, 0x07,0x07,0x07,0x07,0x07,0x07,0x07,
};

uint8_t bar5[8]  = {// -1/5
  0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
};

uint8_t bar6[8]  = { // empty (again)
  0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
};

LiquidCrystal_I2C lcd(0x20, 20, 4); // set the LCD address to 0x20 for a 16 chars and 2 line display
void setup()
{
  Serial.begin(115200);
  lcd.init();                      // initialize the lcd
  lcd.backlight();
  lcd.createChar(0, bar0);
  lcd.createChar(1, bar1);
  lcd.createChar(2, bar2);
  lcd.createChar(3, bar3);
  lcd.createChar(4, bar4);
  lcd.createChar(5, bar5);
  lcd.createChar(6, bar6);
  lcd.home();
  lcd.print("Hello world...");
  delay(1000);
}

void loop()
{
  bargraph_LTR(analogRead(A0), 0,1023, 2, 10, 20);
  bargraph_RTL(analogRead(A0), 0,1023, 2, 9, 0); // notice the last to numbers are 9 then 0 
}

void bargraph_RTL(int data, int low, int high, int row, int start, int end)
{
  static int i, j = 0, number = 0;
  
  j = map<long>(data, low, high, 0, (4 * start) - (4 * (end-1)));
  
  if (number <= j)
  {
    for (number; number < j; number++)
    {
      i = number / 4;
      lcd.setCursor(start - i, row);
      lcd.write(6-(number%4));
    }
  }
  else
  {
    for (number; number > j; number--)
    {
      i = number / 4;
      lcd.setCursor(start - i, row);
      lcd.write(6-(number%4));
    }
  }
}

void bargraph_LTR(int data, int low, int high, int row, int start, int end)
{
  static int i, j = 0, number = 0;
  
  j = map<long>(data, low, high, 0, (4 * end) - (4 * start));
  
  if (number <= j)
  {
    for (number; number < j; number++)
    {
      i = number / 4;
      lcd.setCursor(i + start, row);
      lcd.write(number % 4);
    }
  }
  else
  {
    for (number; number > j; number--)
    {
      i = number / 4;
      lcd.setCursor(i + start, row);
      lcd.write(number % 4);
    }
  }
}

I did it a like I did the fuel gauge for now. With the formula:

float Balance = (Gewicht_RA - Gewicht_LA);

In this case I get full chars displaying after each other. Maybe I look to get it to increment line/line of the chars later.

   if (Balance >= 5) {
    lcd.setCursor(10,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(10,3);
    lcd.print(" ");
  }
   if (Balance >= 10) {
    lcd.setCursor(11,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(11,3);
    lcd.print(" ");
  }    
  if (Balance >= 15) {
    lcd.setCursor(12,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(12,3);
    lcd.print(" ");
  }    
  if (Balance >= 20) {
    lcd.setCursor(13,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(13,3);
    lcd.print(" ");
  }    
  if (Balance >= 25) {
    lcd.setCursor(14,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(14,3);
    lcd.print(" ");
  }    
  if (Balance >= 30) {
    lcd.setCursor(15,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(15,3);
    lcd.print(" ");
  }
      if (Balance >= 35) {
    lcd.setCursor(16,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(16,3);
    lcd.print(" ");
  }        
      if (Balance >= 40) {
    lcd.setCursor(17,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(17,3);
    lcd.print(" ");
  }        
      if (Balance >= 45) {
    lcd.setCursor(18,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(18,3);
    lcd.print(" ");
  }        
  if (Balance >= 50) {
    lcd.setCursor(19,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(19,3);
    lcd.print(" ");
  }           
            
   if (Balance <= -5) {
    lcd.setCursor(9,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(9,3);
    lcd.print(" ");
  }
   if (Balance <= -10) {
    lcd.setCursor(8,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(8,3);
    lcd.print(" ");
  }    
  if (Balance <= -15) {
    lcd.setCursor(7,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(7,3);
    lcd.print(" ");
  }    
  if (Balance <= -20) {
    lcd.setCursor(6,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(6,3);
    lcd.print(" ");
  }    
  if (Balance <= -25) {
    lcd.setCursor(5,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(5,3);
    lcd.print(" ");
  }    
  if (Balance <= -30) {
    lcd.setCursor(4,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(4,3);
    lcd.print(" ");
  }
      if (Balance <= -35) {
    lcd.setCursor(3,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(3,3);
    lcd.print(" ");
  }        
      if (Balance <= -40) {
    lcd.setCursor(2,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(2,3);
    lcd.print(" ");
  }        
      if (Balance <= -45) {
    lcd.setCursor(1,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(1,3);
    lcd.print(" ");
  }        
  if (Balance <= -50) {
    lcd.setCursor(0,3);
    lcd.write(byte(3));
  } 
  else {
    lcd.setCursor(0,3);
    lcd.print(" ");
  }