Arduino Library for HX8347-I Router Front Panel LCD

 Posted by:   Posted on:   Updated on:  2019-04-26T19:49:00Z

Library for 3-wire SPI Truly TFT3P5026-E 2.8" TFT color display with Himax HX8347-I controller. Part of the front panel of Sercomm SHG1500 LCDv6.

A while ago I managed to wire the front panel of a Sercomm router to a STM32 blue pill development board and I have attempted to control the color 2.8" LCD and the capacitive keypad found on the front panel of this type of router. Long story short: if you own a discarded Sercomm SHG1500 router that reports LCDv6 "gateway hardware version", this is the front panel you can control. OpenWrt can't be currently installed on these routers and there is no known way of changing their firmware or having access to bootloader.

But the front panel can be a great module if you're into electronics. It has a 2.8 inch LCD and a 5 keys capacitive keypad next to it. Hardware version LCDv6 uses a HX8347-I based LCD with SPI write only interface. The keypad uses I2C bus and an additional pin to trigger an interrupt when a key is touched. More on that in another post. Interfacing the front panel to common 2.54 mm pinheaders is not straightforward since it uses a 2x15 pinheader with 1.27 mm pitch. But I built an adapter you can find in the other post.

Arduino Library for HX8347-I Router Front Panel LCD

Truly HX8347-I LCD showing the SMPTE pattern

The LCD is manufactured by Truly and its part number is TFT3P5026-E. No information can be found about it. Neither the -I version of HX8347 has too much documentation available. Yet several open source libraries suggest -I is compatible with -D, -G and HX8367-A. This was the starting point to writing my own library. But a graphics library isn't very easy to develop, even for small microcontrollers. So I ended up subclassing Adafruit_GFX. It was easier than I thought. I had to write my own display initialization functions and Adafruit library only needed to know how to set the color of a pixel at specific coordinates.

Let's see some technical details about SPI transfers to LCD. SPI mode is 3 (CPOL=1, CPHA=1). Clock frequency can be as high as 50 MHz (not very sure about that). Commands and registers are set in 2 bytes packets. No other LCD pins are wired to front panel connector except minimal SPI (CLK, MOSI, CS) and reset.

Send SPI data about registers

Send SPI data about registers

This is what the function void sendSPIPacket(uint8_t data, uint8_t type); does. To set a register, two such packets are sent. First containing register address with with RS=0 and the second with RS=1 meaning register value.

To inform LCD that you want to write pixel color data to display RAM, you must send a packet with RS=0 and value 0x22. Then you can write multiple pixel colors, knowing that pixel address autoincrements. Let's see how to set the color of a pixel.

Send SPI pixel color in RGB565 format

Send SPI pixel color in RGB565 format

This time, a 3 bytes packet is needed containing first byte with RS=1 followed by 16-bit color (RGB565). The function that does this looks like that:

void Truly_HX8347I::setCurrentPixelColor(uint16_t color) {
 setCSPinStatus(LOW);

 SPI.transfer(HX8347I_DATA); // 0x72 (ID = 0x70 and RS = 1)
 SPI.transfer(color >> 8);
 SPI.transfer(color & 0xFF);

 setCSPinStatus(HIGH);
}

But Adafruit_GFX sets pixels at specified coordinates. That's why, before writing pixel color, you must set an active display memory window (area). This can be as small as 1 pixel. It is done by writing some registers.

void Truly_HX8347I::setWindow(int16_t x, int16_t y, int16_t x1, int16_t y1) {
 writeRegister(0x02, x >> 8); // Column address start 2
 writeRegister(0x03, x & 0xFF); // Column address start 1
 writeRegister(0x04, x1 >> 8); // Column address end 2
 writeRegister(0x05, x1 & 0xFF); // Column address end 1
 writeRegister(0x06, y >> 8); // Row address start 2
 writeRegister(0x07, y & 0xFF); // Row address start 1
 writeRegister(0x08, y1 >> 8); // Row address end 2
 writeRegister(0x09, y1 & 0xFF); // Row address end 1

 sendSPIPacket(0x22, HX8347I_CMD);
}

The function does one more thing before returning. It prepares the RAM for pixel color data by sending the packet with 0x22.

The described functions are used in required drawPixel() implementation. After a quick validity check, active 1 pixel RAM window is set and color is sent to controller.

void Truly_HX8347I::drawPixel(int16_t x, int16_t y, uint16_t color) {
 if (x < 0 || y < 0 || x >= width() || y >= height())
         return;

 setWindow(x, y, x, y);
 setCurrentPixelColor(color);
}

This works and makes all features of Adafruit_GFX available. But not very fast. There are a lot of situations when you can use the autoincrement function of the controller. For example, to draw a filled rectangle, you should set an active window with the dimensions of the rectangle then write width * height pixels. It's much faster than taking each pixel, setting its window then color. I took care of this by reimplementing fillRect() and fillScreen(). Same for vertical and horizontal line drawing functions which are just rectangles with 1 pixel width or height.

These are internal routines of the library. More important is how you initialize and use the display.

#include "Adafruit_GFX.h"
#include "Truly_HX8347I.h"

#define SPI_CS  PA4 // SPI CS pin
#define LCD_RST PA2 // LCD reset pin (mandatory here)
#define BKL_CTL PA3 // backlight control pin (optional)

Truly_HX8347I tft(SPI_CS, LCD_RST, BKL_CTL);

void setup() {
  tft.begin();
  tft.setRotation(1); // 90 degrees, landscape

  tft.fillScreen(TFT_BLACK);
  tft.backlightOn();
}

Truly_HX8347I object requires at least two parameters: SPI chip select and LCD reset. If you specify a third parameter, this will be the backlight control pin. begin() and backlightOn() are really required. You may replace the latter with backlightIntensity(intensity, true) where intensity can be any number between 0 and 31. Only the first call to backlightIntensity() should contain the second boolean parameter set to true. This will perform a reset of the backlight controller. After setting the intensity, you can call backlightOn() with boolean argument to turn on or off the backlight. Previously set intensity will be kept. Calling backlightIntensity() can turn on the backlight with a new intensity. The backlight can also be controlled independently with the library I wrote in another post.

That's about everything you need to know to get it working. The library has only been tested on STM32 platform. It should work for ESP8266 too. All the low level functions can be found at the end of Truly_HX8347I.cpp file (GPIO and SPI initialization, pin setting and SPI transfers), making it easy to port to other platforms or use alternate ports. Remember that both the display, its backlight controller and the keypad controller are 3.3 V devices without 5 V compatibility. Yet the front panel needs 5 V for backlight LEDs (of the LCD and keypad).

Library can be downloaded from GitHub. The next step is to write the keypad driver and then I'll have a fully functional front panel.

13 comments :

  1. The controller can also do 18-bit RGB data where you have 6 bits for each color. Tried this on a different display with success.

    ReplyDelete
  2. just saw that the Adafruit library also uses 565, so this would also have to be adapted.
    Another drawback is that you need to transfer three bytes (containing 6 unused bits) unless you can generate 18-bit SPI transfers. This slows down things a bit.
    Here is code for a test image, assuming register 0x17 is set to 0x06 (sorry for unformatted text; don't know how to display as code):
    #define CMD_REG_W 0x74 //write register; Bit 2 must be set for my display
    #define CMD_CONT_W 0x76 //write contents; Bit 2 must be set for my display
    #define REG_SRAM_WRITE_CONTROL 0x22

    //create test image with three bars of base color with increasing intensity,
    //framed by one pixel white. Colour mapping may be wrong, depending on the display.
    void testImage(char PinSS)
    {
    long i;
    digitalWrite(PinSS, LOW);
    SPI.transfer(CMD_REG_W);
    SPI.transfer(REG_SRAM_WRITE_CONTROL);
    digitalWrite(PinSS, HIGH);
    for(i = 0; i < 76800; i++) {
    unsigned char intensity = (i / 240 / 5) & 0x3F;
    unsigned char red;
    unsigned char blue;
    unsigned char green;
    unsigned char col = i % 240;
    if (col < 80) {
    red = intensity;
    blue = 0;
    green = 0;
    }
    else if (col < 160) {
    red = 0;
    blue = intensity;
    green = 0;
    }
    else {
    red = 0;
    blue = 0;
    green = intensity;
    }
    digitalWrite(PinSS, LOW);
    SPI.transfer(CMD_CONT_W);
    if (col == 0 || col == 239 || i < 240 || i >= 319l *240) {
    SPI.transfer(0xFF);
    SPI.transfer(0xFF);
    SPI.transfer(0xC0);
    }
    else {
    SPI.transfer((red << 2) | (green >> 4));
    SPI.transfer((green << 4) | (blue >> 2));
    SPI.transfer((blue << 6));
    }
    digitalWrite(PinSS, HIGH);
    }
    }

    ReplyDelete
  3. Hello i have too this hardware, i tried with ESP32, ESP8266 and ESP8285, but i keep receiving this error when i compile

    In file included from C:\Users\Samu\Desktop\sketch_nov14a\sketch_nov14a.ino:2:
    C:\Users\Samu\Documents\Arduino\libraries\truly/Truly_HX8347I.h:1:1: error: expected unqualified-id before '{' token
    1 | {"payload":{"allShortcutsEnabled":false,"fileTree":{"Truly_HX8347I":{"items.......
    | ^
    sketch_nov14a:8:1: error: 'Truly_HX8347I' does not name a type
    8 | Truly_HX8347I tft(SPI_CS, LCD_RST, BKL_CTL);
    | ^~~~~~~~~~~~~
    C:\Users\Samu\Desktop\sketch_nov14a\sketch_nov14a.ino: In function 'void setup()':
    sketch_nov14a:11:3: error: 'tft' was not declared in this scope
    11 | tft.begin();
    | ^~~
    sketch_nov14a:14:18: error: 'TFT_BLACK' was not declared in this scope
    14 | tft.fillScreen(TFT_BLACK);
    | ^~~~~~~~~
    exit status 1
    'Truly_HX8347I' does not name a type

    ReplyDelete
    Replies
    1. I tries Everything, i am using ESP8266 and unfortunately i receive this error too when compiling, at the moment i would want to try on Arduino then i want to try to configure on esphome, using custom components

      Delete
    2. Hello. Thats nice, I follow the procedure to download the file, then, I use the same code:
      #include "Adafruit_GFX.h"
      #include "Truly_HX8347I.h"

      #define SPI_CS PA4 // SPI CS pin
      #define LCD_RST PA2 // LCD reset pin (mandatory here)
      #define BKL_CTL PA3 // backlight control pin (optional)

      Truly_HX8347I tft(SPI_CS, LCD_RST, BKL_CTL);

      void setup() {
      tft.begin();
      tft.setRotation(1); // 90 degrees, landscape

      tft.fillScreen(TFT_BLACK);
      tft.backlightOn();
      }
      But I receive the same error

      Delete
    3. Please change pin definitions according to your board. I managed to compile this successfully on Arduino Uno and ESP8266. Based on compiler output, it is very probable you forgot to put a semicolon somewhere in your code.

      Delete
    4. Please can you share your code, as example? I am not familiar with arduino unfortunately, thanks in advantage

      Delete
    5. Sorry forgot, i am using ESP8266

      Delete
    6. I'm using the example from library and it works. Make sure you have Adafruit GFX Library installed.

      Delete
  4. I was able to compile for ESP8285 the backlight is turning on for some seconds then goes off, also forcing the light on with the switch, I see only white ( I am using smpte_test.ino), i am using GPIO 2,4,3 but I notice looking at your picture that SPI_DATA is used

    ReplyDelete
    Replies
    1. The library uses the default SPI port pins to communicate with LCD (with ESP8266, GPIO14 is SPI_CLK, GPIO13 is SPI_DATA and GPIO15 is SPI_CS). You also need two additional generic output pins for LCD_RST and LCD_BACKLIGHT_CTL (I guess LCD_RST can be connected to reset pin of ESP8266 and LCD_BACKLIGHT_CTL may be pulled to 3.3 V to get maximum backlight).

      Delete
    2. Edit: LCD_RST must have its own pin and cannot be connected to ESP8266 reset (at least in the current version of the library).

      Delete

Please read the comments policy before publishing your comment.