LCD Module Horizontal Bar Graph

BarGraphA bar graph is a horizontal graphical representation of an analog value, where the length of the graph is proportional to its full scale value. I recently needed to display a bar chart on a LCD character display module, but searching the web for code I didn’t find what I needed, so I wrote my own. In case it is useful to someone else, here it is.

I was looking for code that allows the bar chart to be anchored either on the left or right side and grow towards the other side of the display. I also wanted to minimize the amount of LCD resource required – most of the code I found used up to 5 of the 8 LCD user-defined characters when, properly managed, there is only need for 1 such character per bar graph displayed.

Software Implementation

The code for the bar graph is implemented as a class and it uses the LiquidCrystal library to manage the LCD display. The entire Arduino sketch is attached at the end of this article.

The bar graph is defined by a range, a starting point and a length in characters. A negative length means growing right to left. The class has 3 methods:

  • setRange() to set the full range value for the bar graph. Default is 0 to 100.
  • setChar() to set the user defined LCD character. The default is 1. If more than one chart is required then additional bar charts need to have a different character defined.
  • show() to display the value specified on the chart.

I decided to build up the bar graph as a string, and then write it out all at once. This creates a limitation that the user defined LCD character at ASCII 0 (zero) cannot be used, as it would terminate the string prematurely.

I also decided to build the string L to R and then reverse it if required. The only difference between the two is the type of user defined character that needs to be created, as the ‘ragged’ edge of the chart needs to be flipped.

BarGraph1

The sketch below uses an analog potentiometer to test drive display. It implements 2 bar charts – one full length chart growing to the right on the first line, and one half length, centered, chart growing to the left, on the second line – producing the display shown above.

// Draw a horizontal bar graph on a character LCD display (Hitachi type module)
// 
// Varying input provided by through the analog input ANALOG_IN,
// connected to a pot for testing purposes

#include <LiquidCrystal.h>

// Analog input
const uint8_t ANALOG_IN = A5;

// LCD display definitions
const uint8_t LCD_ROWS = 2;
const uint8_t LCD_COLS = 16;

const uint8_t LCD_RS = 8;
const uint8_t LCD_ENA = 9;
const uint8_t LCD_D4 = 4;
const uint8_t LCD_D5 = LCD_D4+1;
const uint8_t LCD_D6 = LCD_D4+2;
const uint8_t LCD_D7 = LCD_D4+3;

static LiquidCrystal lcd(LCD_RS, LCD_ENA, LCD_D4, LCD_D5, LCD_D6, LCD_D7);

class BarGraph
{
public:
  BarGraph(LiquidCrystal *lcd, uint8_t row, uint8_t colStart, int8_t colLen) : 
     _lcd(lcd), _row(row), _colStart(colStart), _colLen(colLen)
  { 
    setRange(0, 100); 
    setChar(1); 
  };
  void setRange(uint32_t valueMin, uint32_t valueMax) { _valueMin = valueMin; _valueMax = valueMax; };
  void setChar(uint8_t charLCD) { if ((charLCD > 0) && (_charLCD < 8)) _charLCD = charLCD; };
 
  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);
  }

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 *_lcd;
  uint8_t _row, _colStart;
  int8_t _colLen;
  uint32_t _valueMin, _valueMax; 
  uint8_t _charLCD;
};

BarGraph bg0(&lcd, 0, 0, LCD_COLS); // left to right, full line
BarGraph bg1(&lcd, 1, LCD_COLS-(LCD_COLS/4)-1, -LCD_COLS/2); // right to left, half line, centered

void setup()
{
  // initialize hardware pins
  pinMode(ANALOG_IN, INPUT);
 
  // initialise LCD display
  lcd.begin(LCD_COLS, LCD_ROWS);
  lcd.clear();
  lcd.noAutoscroll();
  lcd.noCursor();

  // initialise BarGraph parameters
  bg0.setRange(0, 1000);
  bg1.setRange(0, 1000);
  bg1.setChar(2);
}

void loop()
{
  static uint16_t lastValue = 0;
  uint16_t v = 0;

  if ((v = analogRead(ANALOG_IN)) != lastValue)
  {
    bg0.show(v);
    bg1.show(v);
    lastValue = v;
  }
}
Advertisements

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s