MCP23017 plus LCD display


#1

Hello

I’m using a photon and 2 MCP23017 port extenders for a school project. I want to add an LCD to show some information while a function is running. Due to my photon running out of ports, I want to use one MCP23017, to run the LCD.
My LCD is not I2C, so I want to know if the MCP23017’s ports are able to comunicate, as the photon will, to show the data, and how to.

I did this, it compile, but the LCD just show squares.

#include <LiquidCrystal.h>
#include <Adafruit_MCP23017.h>

LiquidCrystal lcd(9, 10, 11, 12, 13, 14);  //must be mcp ports

Adafruit_MCP23017 mcp1;
Adafruit_MCP23017 mcp2;

void setup()
{
    mcp1.begin(0);
    mcp2.begin(1);
    for(int i = 0; i <= 15; i++)
    {
        mcp1.pinMode(i, OUTPUT);
        mcp2.pinMode(i, OUTPUT);
    }
    lcd.begin(16,2);  
    lcd.print("Hello world");
    Serial.begin(9600);

}

void loop() 
{
    lcd.setCursor(0, 1);
    lcd.print(millis()/1000);

    //this is not important
    if(digitalRead(D7) == HIGH)
    {
        mcp1.digitalWrite(0, HIGH);
        mcp2.digitalWrite(0, HIGH);
    }
    else
    {
        mcp1.digitalWrite(0, LOW);
        mcp2.digitalWrite(0, LOW);   
    }
}

I’m not a fluent programmer, so I don’t know the correct syntaxis or ways to declare it. but, when declaring the LCD ports as the mcp do i do it like this?

LiquidCrystal lcd(mcp2(9, 10, 11, 12, 13, 14));

or how is the correct way to do it?

Thanks


#2

The LiquidCrystal library has no knowledge of the port expander but assumes that the given pins are ordinary GPIOs which it can directly access. Since they aren’t you cannot use that library for it.

In order to control the LCD via an expander you’d have to change the library to explicitly employ the expander pins.

Can you elaborate on how you have “exhaustes” all the existing Photon pins already?
With a shortage of pins you may really want to consider using a smart display (I2C, SPI, UART).


#3

Thank you, for so fast reply.

Blockquote
Can you elaborate on how you have “exhaustes” all the existing Photon pins already?

I’m using 6 pins(4 of them are for SPI) to communicate with a RFM69 module, 2 other pins for I2C for the MCP, 3 for a RGB, 1 for a neopxl, 2 push bottoms, and 2 for a L293.

Maybe I will need to re-acomodate the outputs.

But, even though, I will need to change the library because it work only with the Dx outputs. I can’t use some Ax mix with Dx pins.


#4

I must admint I don’t know about your actual intent, but having a single RGB LED (3 pins) plus a NeoPixel (1 pin) does seem odd as two RGB NeoPixels would do the same job.
Also, if you have pins free on your MCPs why not attach the two push buttons and the L293 to one of these?

BTW, after seeing your “BOM” I came across this

It also features several buttons, an LCD and controls the RGB backlight with the MCP23017. Maybe having a look at their library might provide some hints on how to control the LCD via an MCP23017.


#5

Lol, some outputs are to fulfill “task requirements” not any practical use but just check an objective as is a class project.

Even if I move the pushb and the L293, D0 and D1 are in use for I2C, and D6 and D2 for the RFM module. And this pins are in use for the LCD library.

It seems that I might need to change the library or buy a smart display.

Thank you for the RGB Shield, I might need to look for one here.


#6

I think the library allows you to reassign D0, D1, D2, D6 to other free pins on your Photon.
Remind you, you can also use RX and TX as GPIOs (as DAC and WKP too).


#7

Hi ,ScruffR
I tried to modify the library included for the LiquidCristal, and it wont allow me to make any change to it, so I copy it and hit the + buttom next to my .ino and copy the .cpp and .h library of the liquid cristal and change pin D5 to A0 like this just as a first attempt, but I didn’t work, is there something I’m missing to do?

This is the example code

// This #include statement was automatically added by the Particle IDE.
#include "LiquidCrystal.h"

LiquidCrystal lcd(5, 4, 3, 2, 1, 0);

void setup() {
  lcd.begin(16, 2);
  lcd.print("Time since reset");
}

void loop() {
  lcd.setCursor(0, 1);
  lcd.print(millis()/1000);
}

this is the .cpp

#include "application.h"

#include "LiquidCrystal.h"

// When the display powers up, it is configured as follows:
//
// 1. Display clear
// 2. Function set: 
//    DL = 1; 8-bit interface data 
//    N = 0; 1-line display 
//    F = 0; 5x8 dot character font 
// 3. Display on/off control: 
//    D = 0; Display off 
//    C = 0; Cursor off 
//    B = 0; Blinking off 
// 4. Entry mode set: 
//    I/D = 1; Increment by 1 
//    S = 0; No shift 
//
// Note, however, that resetting the Arduino doesn't reset the LCD, so we
// can't assume that its in that state when a sketch starts (and the
// LiquidCrystal constructor is called).

LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
                             uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
                             uint8_t d4, uint8_t A0, uint8_t d6, uint8_t d7)
{
  init(0, rs, rw, enable, d0, d1, d2, d3, d4, A0, d6, d7);
}

LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t enable,
                             uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
                             uint8_t d4, uint8_t A0, uint8_t d6, uint8_t d7)
{
  init(0, rs, 255, enable, d0, d1, d2, d3, d4, A0, d6, d7);
}

LiquidCrystal::LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
                             uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3)
{
  init(1, rs, rw, enable, d0, d1, d2, d3, 0, 0, 0, 0);
}

LiquidCrystal::LiquidCrystal(uint8_t rs,  uint8_t enable,
                             uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3)
{
  init(1, rs, 255, enable, d0, d1, d2, d3, 0, 0, 0, 0);
}

void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
                         uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
                         uint8_t d4, uint8_t A0, uint8_t d6, uint8_t d7)
{
  _rs_pin = rs;
  _rw_pin = rw;
  _enable_pin = enable;
  
  _data_pins[0] = d0;
  _data_pins[1] = d1;
  _data_pins[2] = d2;
  _data_pins[3] = d3; 
  _data_pins[4] = d4;
  _data_pins[5] = A0;
  _data_pins[6] = d6;
  _data_pins[7] = d7; 
  
  // Always 4-bit mode, don't waste pins!
  //
  //if (fourbitmode)
    _displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
  //else 
    //_displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_5x8DOTS;
}

void LiquidCrystal::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
  if (lines > 1) {
    _displayfunction |= LCD_2LINE;
  }
  _numlines = lines;
  _currline = 0;
  
  pinMode(_rs_pin, OUTPUT);
  // we can save 1 pin by not using RW. Indicate by passing 255 instead of pin#
  if (_rw_pin != 255) { 
    pinMode(_rw_pin, OUTPUT);
  }
  pinMode(_enable_pin, OUTPUT);

  // for some 1 line displays you can select a 10 pixel high font
  if ((dotsize != 0) && (lines == 1)) {
    _displayfunction |= LCD_5x10DOTS;
  }

  // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
  // according to datasheet, we need at least 40ms after power rises above 2.7V
  // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50
  delayMicroseconds(50000); 
  // Now we pull both RS and R/W low to begin commands
  digitalWrite(_rs_pin, LOW);
  digitalWrite(_enable_pin, LOW);
  if (_rw_pin != 255) { 
    digitalWrite(_rw_pin, LOW);
  }

  // 4-Bit initialization sequence from Technobly
  write4bits(0x03);         // Put back into 8-bit mode
  delayMicroseconds(5000);

  write4bits(0x08);         // Comment this out for V1 OLED
  delayMicroseconds(5000);  // Comment this out for V1 OLED
  
  write4bits(0x02);         // Put into 4-bit mode
  delayMicroseconds(5000);
  write4bits(0x02);
  delayMicroseconds(5000);
  write4bits(0x08);
  delayMicroseconds(5000);
  
  command(LCD_DISPLAYCONTROL);                  // Turn Off
  delayMicroseconds(5000);
  command(LCD_FUNCTIONSET | _displayfunction);  // Set # lines, font size, etc.
  delayMicroseconds(5000);
  clear();                                      // Clear Display
  command(LCD_ENTRYMODESET | LCD_ENTRYLEFT);    // Set Entry Mode
  delayMicroseconds(5000);
  home();                                       // Home Cursor
  delayMicroseconds(5000);
  command(LCD_DISPLAYCONTROL | LCD_DISPLAYON);  // Turn On - enable cursor & blink
  delayMicroseconds(5000);
}

/********** high level commands, for the user! */
void LiquidCrystal::clear()
{
  command(LCD_CLEARDISPLAY);  // clear display, set cursor position to zero
  delayMicroseconds(5000);  // this command takes a long time!
}

void LiquidCrystal::home()
{
  command(LCD_RETURNHOME);  // set cursor position to zero
  delayMicroseconds(5000);  // this command takes a long time!
}

void LiquidCrystal::setCursor(uint8_t col, uint8_t row)
{
  int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
  if ( row > _numlines ) {
    row = _numlines-1;    // we count rows starting w/0
  }
  command(LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

// Turn the display on/off (quickly)
void LiquidCrystal::noDisplay() {
  _displaycontrol &= ~LCD_DISPLAYON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::display() {
  _displaycontrol |= LCD_DISPLAYON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turns the underline cursor on/off
void LiquidCrystal::noCursor() {
  _displaycontrol &= ~LCD_CURSORON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::cursor() {
  _displaycontrol |= LCD_CURSORON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// Turn on and off the blinking cursor
void LiquidCrystal::noBlink() {
  _displaycontrol &= ~LCD_BLINKON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}
void LiquidCrystal::blink() {
  _displaycontrol |= LCD_BLINKON;
  command(LCD_DISPLAYCONTROL | _displaycontrol);
}

// These commands scroll the display without changing the RAM
void LiquidCrystal::scrollDisplayLeft(void) {
  command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}
void LiquidCrystal::scrollDisplayRight(void) {
  command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

// This is for text that flows Left to Right
void LiquidCrystal::leftToRight(void) {
  _displaymode |= LCD_ENTRYLEFT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This is for text that flows Right to Left
void LiquidCrystal::rightToLeft(void) {
  _displaymode &= ~LCD_ENTRYLEFT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This will 'right justify' text from the cursor
void LiquidCrystal::autoscroll(void) {
  _displaymode |= LCD_ENTRYSHIFTINCREMENT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// This will 'left justify' text from the cursor
void LiquidCrystal::noAutoscroll(void) {
  _displaymode &= ~LCD_ENTRYSHIFTINCREMENT;
  command(LCD_ENTRYMODESET | _displaymode);
}

// Allows us to fill the first 8 CGRAM locations
// with custom characters
void LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) {
  location &= 0x7; // we only have 8 locations 0-7
  command(LCD_SETCGRAMADDR | (location << 3));
  for (int i=0; i<8; i++) {
    write(charmap[i]);
  }
}

/*********** mid level commands, for sending data/cmds */

inline void LiquidCrystal::command(uint8_t value) {
  send(value, LOW);
}

inline size_t LiquidCrystal::write(uint8_t value) {
  send(value, HIGH);
  return 1;
}

/************ low level data pushing commands **********/

// write either command or data, with automatic 4/8-bit selection
void LiquidCrystal::send(uint8_t value, uint8_t mode) {
  digitalWrite(_rs_pin, mode);

  // if there is a RW pin indicated, set it low to Write
  if (_rw_pin != 255) { 
    digitalWrite(_rw_pin, LOW);
  }
  
  if (_displayfunction & LCD_8BITMODE) {
    write8bits(value); 
  } else {
    write4bits(value>>4);
    write4bits(value);
  }
}

void LiquidCrystal::pulseEnable(void) {
  digitalWrite(_enable_pin, LOW);
  delayMicroseconds(1);    
  digitalWrite(_enable_pin, HIGH);
  delayMicroseconds(1);    // enable pulse must be >450ns
  digitalWrite(_enable_pin, LOW);
  delayMicroseconds(100);   // commands need > 37us to settle
}

void LiquidCrystal::write4bits(uint8_t value) {
  for (int i = 0; i < 4; i++) {
    pinMode(_data_pins[i], OUTPUT);
    digitalWrite(_data_pins[i], (value >> i) & 0x01);
  }
  pulseEnable();
}

void LiquidCrystal::write8bits(uint8_t value) {
  for (int i = 0; i < 8; i++) {
    pinMode(_data_pins[i], OUTPUT);
    digitalWrite(_data_pins[i], (value >> i) & 0x01);
  }
  pulseEnable();
}

this is the .h

#ifndef LiquidCrystal_h
#define LiquidCrystal_h
   
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

class LiquidCrystal : public Print {
public:
  LiquidCrystal(uint8_t rs, uint8_t enable,
                uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
                uint8_t d4, uint8_t A0, uint8_t d6, uint8_t d7);
  LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
                uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
                uint8_t d4, uint8_t A0, uint8_t d6, uint8_t d7);
  LiquidCrystal(uint8_t rs, uint8_t rw, uint8_t enable,
                uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3);
  LiquidCrystal(uint8_t rs, uint8_t enable,
                uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3);

  void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable,
            uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3,
            uint8_t d4, uint8_t A0, uint8_t d6, uint8_t d7);
    
  void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS);

  void clear();
  void home();

  void noDisplay();
  void display();
  void noBlink();
  void blink();
  void noCursor();
  void cursor();
  void scrollDisplayLeft();
  void scrollDisplayRight();
  void leftToRight();
  void rightToLeft();
  void autoscroll();
  void noAutoscroll();

  void createChar(uint8_t, uint8_t[]);
  void setCursor(uint8_t, uint8_t); 
  virtual size_t write(uint8_t);
  void command(uint8_t);
private:
  void send(uint8_t, uint8_t);
  void write4bits(uint8_t);
  void write8bits(uint8_t);
  void pulseEnable();

  uint8_t _rs_pin; // LOW: command.  HIGH: character.
  uint8_t _rw_pin; // LOW: write to LCD.  HIGH: read from LCD.
  uint8_t _enable_pin; // activated by a HIGH pulse.
  uint8_t _data_pins[8];

  uint8_t _displayfunction;
  uint8_t _displaycontrol;
  uint8_t _displaymode;

  uint8_t _initialized;

  uint8_t _numlines,_currline;
};

#endif

This is the error when compiling

…/hal/photon/pinmap_defines.h:38:12: expected ‘,’ or ‘…’ before numeric constant
error
…/hal/photon/pinmap_defines.h:38:12: expected ‘,’ or ‘…’ before numeric constant
error
…/hal/photon/pinmap_defines.h:38:12: expected ‘,’ or ‘…’ before numeric constant

Thank you


#8

For that you don’t need to modify the library - as I said above

All you need to do in order to substitute pins would have been this

// instead of
// LiquidCrystal lcd(5, 4, 3, 2, 1, 0);
// which would be better written as this anyhow
// LiquidCrystal lcd(D5, D4, D3, D2, D1, D0);
// just write 
LiquidCrystal lcd(A0, D4, D3, D2, D1, D0);

You cannot change d5 (which is a variable name) to a pin label (A0 - which is a macro for the number literal 10).
When A0 gets expanded to its value your parameter definition would read uint8_t 10 which is of course not allowed.


#9

Hi

For that you don’t need to modify the library - as I said above

Sorry, I misunderstood what you meant before, now that I read it again I understand what you were trying to tell me. Thank you, it worked now.


#10

Hi,

Maybe having a look at their library might provide some hints on how to control the LCD via an MCP23017.

Just came back to it, to see if it could be done with the library of the RGBShield.
And it worked too!

May be, this will work for someone else
With this wiring the LCD display(monochrome) should work:

MCP Pin–>LCD 16x2(monochrome)
2------------>D7
3------------>D6
4------------>D5
5------------>D4
6------------>D
7------------>E
8------------>RW
9------------>RS
27----------->K(with 220 ohm resistor)
Pin 21-25 of the mcp are inputs for the buttons(left,right,select, up,down)

General-----------> LCD16x2(monochrome)
Ground------------>VSS
Voltage------------>VDD
Voltage------------>A
Pot----------------->VO