Arduino, ESP and i2c devices: control a remote LCD over Wi-Fi

Da raspibo.

This project permits to control a remote LCD display connected via an i2c port expander via Wifi.

I have successfully tested this program on an ESP-12 (ESP8266) using the Arduino IDE.

Indice

hardware

The hardware "ingredients" needed for this experiments are:

  • a ESP8266 (in the example it is a ESP12).
  • an lcd-plug by jeelabs including the display
  • A "console cable" (but any USB to TTL converter is okay).

wiring

i2c: SDA is GP04, SCL is GP05. The pins of jeelabs' lcd plug (this cabling works for all i2c based jeelabs' plugs):

  • P -> +5V or disconnected (unused for lcd-plug)
  • D -> GP04 (SDA)
  • G -> GND
  • + -> +3V3
  • A -> GP05 (SCL)
  • I -> NC (unused for lcd-plug, can be connected to another GPIO for other boards providing useful interrupts).

Console cable: using an Adafruit like cable:

  • Black -> GROUND
  • While -> TXD on ESP
  • Green -> RXD on ESP

DO NOT CONNECT THE RED PIN TO YOUR ESP! The red pin is +5V while ESP need +3V3 so either you have a step-down circuit to convert 5V to 3.3V or you'll need another power source of the right voltage for yout ESP.

Software

Here is the source code. (I have left in the code several unused functions for the management of the display that can be useful in other applications. (the code is deeply inspired by Jeelabs libraries).

#include <Wire.h>
#include <ESP8266WiFi.h>

const char *ssid = "ESPap";
const char *password = "forknife";

WiFiServer server(23);
WiFiClient client;

#define DISP_ADDRESS      (0x24)  
#define LCD_MAX_MESSAGE_LENGTH 40
#define LCD_DISPLAY_LINES      2  
#define HIGH 1
#define LOW 0
enum {
  MCP_IODIR, MCP_IPOL, MCP_GPINTEN, MCP_DEFVAL, MCP_INTCON, MCP_IOCON,
  MCP_GPPU, MCP_INTF, MCP_INTCAP, MCP_GPIO, MCP_OLAT
};

// bits 0..3 and D4..D7, the rest is connected as follows
#define MCP_BACKLIGHT   0x80
#define MCP_ENABLE      0x40
#define MCP_OTHER       0x20
#define MCP_REGSEL      0x10

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

static int file;
static uint8_t functionset_flags;
static uint8_t displaycontrol_flags;
static uint8_t entrymodeset_flags;

void setupmcp(void)
{
  Wire.beginTransmission(DISP_ADDRESS);
  Wire.write((byte)MCP_IODIR);
  Wire.write((byte)0);
  Wire.endTransmission();
}

void turnoff(void)
{
  Wire.beginTransmission(DISP_ADDRESS);
  Wire.write((byte)MCP_GPIO);
  Wire.write((byte)0);
  Wire.endTransmission();
}

void write1byte(unsigned int value)
{
  Wire.beginTransmission(DISP_ADDRESS);
  Wire.write((byte)MCP_GPIO);
  Wire.write((byte)(value | MCP_BACKLIGHT));
  Wire.endTransmission();
}

void write4bits(unsigned int value)
{
  write1byte(value);
  delayMicroseconds(2);
  write1byte(value | MCP_ENABLE);
  delayMicroseconds(2);
  write1byte(value);
  delayMicroseconds(100);
}

void send(unsigned int value, unsigned int mode)
{
  //printf("SEND %x %d\n",value,mode);
  if (mode)
    mode = MCP_REGSEL;
  write4bits((value>>4) | mode);
  write4bits((value &0xf) | mode);
}

#define command(v) send((v),LOW);

void lcd_clear (void)
{
  command (LCD_CLEARDISPLAY);  // Clear display, set cursor position to zero.
  delayMicroseconds(2000);   // This command takes a long time.
}

void lcd_home (void)
{
  // Set cursor position to zero and undo any scrolling that is in effect.
  command (LCD_RETURNHOME);
  delayMicroseconds(2000);  // This command takes a long time.
}

void lcd_set_cursor_position (uint8_t col, uint8_t row)
{
  // If given an invalid row number, display on last line.
  if ( row >= LCD_DISPLAY_LINES ) {
    row = LCD_DISPLAY_LINES - 1;    // We count rows starting from 0.
  }

  // Positions of the beginnings of rows in LCD DRAM.
  const int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };

  command (LCD_SETDDRAMADDR | (col + row_offsets[row]));
}

void lcd_display_off (void)
{
  displaycontrol_flags &= ~LCD_DISPLAYON;
  command (LCD_DISPLAYCONTROL | displaycontrol_flags);
}

void lcd_display_on (void)
{
  displaycontrol_flags |= LCD_DISPLAYON;
  command (LCD_DISPLAYCONTROL | displaycontrol_flags);
}

void lcd_blinking_cursor_off (void)
{
  displaycontrol_flags &= ~LCD_BLINKON;
  command (LCD_DISPLAYCONTROL | displaycontrol_flags);
}

void lcd_blinking_cursor_on (void)
{
  displaycontrol_flags |= LCD_BLINKON;
  command (LCD_DISPLAYCONTROL | displaycontrol_flags);
}

void lcd_underline_cursor_off (void)
{
  displaycontrol_flags &= ~LCD_CURSORON;
  command (LCD_DISPLAYCONTROL | displaycontrol_flags);
}

void lcd_underline_cursor_on (void)
{
  displaycontrol_flags |= LCD_CURSORON;
  command (LCD_DISPLAYCONTROL | displaycontrol_flags);
}

void lcd_scroll_left (void) {
  command (LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}

void lcd_scroll_right (void) {
  command (LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

size_t lcd_write (char value)
{
  send ((uint8_t) value, HIGH);
  return 1;   // Assume success
}

size_t lcd_write_string (const char *buffer)
{
  size_t size = strlen (buffer);
  size_t n = 0;
  while ( size-- ) {
    n += lcd_write ((uint8_t) (*buffer++));
  }
  return n;
}

int lcd_printf (const char *format, ...)
{
  char message_buffer[LCD_MAX_MESSAGE_LENGTH + 1];

  va_list ap;
  va_start (ap, format);
  int chars_written
    = vsnprintf (message_buffer, LCD_MAX_MESSAGE_LENGTH, format, ap);
  va_end (ap);

  lcd_write_string (message_buffer);

  return chars_written;
}

void setup() {
  Wire.begin();
  delay(1000);
  Serial.begin(115200);
  Serial.println();
  Serial.print("Configuring access point...");
  /* You can remove the password parameter if you want the AP to be open. */
  WiFi.softAP(ssid, password);

  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);

  server.begin();
  server.setNoDelay(true);

  Serial.println("i2s lcd driver\n");

  setupmcp();
  write4bits(0x03);
  delayMicroseconds(4500);
  write4bits(0x03);
  delayMicroseconds(4500);
  write4bits(0x03);
  delayMicroseconds(150);
  write4bits(0x02);

  functionset_flags = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS;
  // Finally, set # lines, font size, etc.
  command (LCD_FUNCTIONSET | functionset_flags);

  // Turn the display on with no cursor or blinking cursor.
  displaycontrol_flags = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
  command (LCD_DISPLAYCONTROL | displaycontrol_flags);

  // Clear display. 
  lcd_clear ();

  // Initialize to supported text direction (for romance languages).
  entrymodeset_flags = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
  command (LCD_ENTRYMODESET | entrymodeset_flags);
}

int nline=0;
int nchar=0;
char contents[LCD_DISPLAY_LINES][LCD_MAX_MESSAGE_LENGTH+1];
void loop()
{
  if (!client.connected()) {
    if (client) client.stop();
    if (server.hasClient())
      client = server.available();
  } else {
    int incomingByte = 0;

    if (client.available() > 0) {
      incomingByte = client.read();

      if (incomingByte == '\r' || incomingByte == '\n') {
        Serial.println();
        client.println();
        lcd_home ();
        lcd_clear ();
        lcd_set_cursor_position(0,0);
        lcd_printf(contents[0]);
        lcd_set_cursor_position(0,1);
        lcd_printf(contents[1]);
        nline = (nline + 1) % LCD_DISPLAY_LINES;
        nchar = 0;
      } else if (incomingByte == 5) { //ctrl-E
        for (nline=0; nline<LCD_DISPLAY_LINES; nline++)
          contents[nline][0]=0;
        nchar = nline = 0;
        turnoff();
      } else if (nchar < LCD_MAX_MESSAGE_LENGTH) {
        contents[nline][nchar++]=incomingByte;
        contents[nline][nchar]=0;
        Serial.print((char)incomingByte);
        client.print((char)incomingByte);
      }
    }
    if(Serial.available()){
      client.write(Serial.read());
      delay(1);
    }
  }
}

Testing

Load the program on you ESP. Remember that GP00 must be connected to ground at power-up to set the ESP in "flashing mode".

Unplug GP00 otherwise at the next power cycle your ESP will be in "flashing mode" again. If GP00 is unconnected, the program is restarted at power up.

When this program is running, your ESP is a Wi-FI access point (you'll see it on your devices under the name, or ESSID, "ESPap". The password is "forknife".)

It does not provide a DHCP server so too automagic connectivity tools like those running on smartphones and dumb operating systems may have troubles to connect. It you use linux you can get a connection. The Esp itself will get the address 192.168.4.1/24 so you should assign by hand another address on the same subnet (say 192.168.4.2/24) to your wlan interface.

Now you can connect to your ESP using nc or socat. I prefer socat;

socat /dev/tty,rawer,escape=0x03 TCP:192.168.4.1:23
  • each line typed in will be shown on the display (when you type return)
  • crtl-E turns the display off (handled by the program on ESC).
  • crtl-C terinates the connection (socat parameter escape=0x03).
Strumenti personali
Namespace

Varianti
Azioni
Navigazione
Strumenti