Arduino Library for HX8347-I Router Front Panel LCD

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

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.

No comments :

Post a Comment

Please read the comments policy before publishing your comment.