One lucky day you wake up to find your dream of creating sophisticated graphics user interface for MCU projects comes true, because STMicroelectronics has released a free version of SEGGER emWin for STM32 line of ARM controllers. But your excitement is quickly balanced by the frustration that your favourite LCD panel is not supported. That is a typical day of mine, and my favourite LCD is a 2.6″ 400×240 IPS panel, model TFT1P5971-E by Truly. This post is about how to make the LCD usable with emWin.

Background information

STemWin is available at http://www.st.com/web/en/catalog/tools/PF259225 (Do not complain if the link does not work. ST did great SEO job by changing URL regularly. Google is your friend…). Basically STMicroelectronics licensed it from SEGGER and distribute the library in binary form. At the time I’m writing this post STemWin is at verison 1.1, corresponding to SEGGER emWin v5.22. The license term for STemWin is pretty obscure according to my English standard. But as far as I can interpret the usage of STemWin with STM32 micro controllers is free for both commercial or non-commercial cases. SEGGER emWin is also available from NXP, carrying a similar license, or from Keil MDK-ARM. The later requires a professional license which is far deeper than my pocket.

The Truly LCD costs as low as $4  from taobao. I suspect it is some sort of left-overs from mobile phone manufacture. The seller told me he has 10K+ ready stock. The LCD controller is Himax HX8352C. I erroneously assume that emWin supports HX8352 and HX8352B so it should support HX8352C, until I wire up the LCD and the garbage churns out.

As a sincere RTFM user I looked into STemWin documentation. There is a chapter “29.7.20 GUIDRV_Template – Template for a new driver”, roughly 1/3 page long, talking about how to write a display driver. Don’t blame SEGGER or ST for not providing more details. SEGGER charges €1,100 for the source code of a driver. They need to make a profitable business after all. And the 1/3 page paragraph proves to be very useful indeed.

For impatients or who want to criticise my code straight away, please jump to GUIDRV_HX8352C

Bare metal LCD driver

Before interfacing the LCD panel with emWin, it is always necessary to make the panel work with bare metal code first. My testing hardware is on a STM32F103RCT6 minimalism board. The reason I choose a 64-pin RCT6 instead of a 100-pin monster is that I wanted to test GPIO to LCD driver performance. If a 100-pin device is used, we can drive LCD using FSMC port and it will be much easier. My LCD wiring is as follows :

HX8352C wiring diagram
HX8352C wiring diagram

We’ll need to implement the following functions for hardware accessing:

void lcdReset(void);
void lcdInit(void);
void lcdWriteData(uint16_t data);
void lcdWriteReg(uint16_t data);
uint16_t lcdReadData(void);

As the name suggests, these functions handles LCD reset, LCD initialization and read/write data/register from/to the LCD controller. Implementation of these functions is as simple as following the LCD datasheet. My actual implementation is within the GitHub repository linked below. A simple main() function to test the LCD interface:

// Note: This code assumes 16-bit parallel interface and HX8352C controller.
void main(void)
{
    lcdInit(); // Internally calls lcdReset()
    lcdWriteReg(0x0002);
    lcdWriteData(0x0000);
    lcdWriteReg(0x0003);
    lcdWriteData(0x0000);
    lcdWriteReg(0x0006);
    lcdWriteData(0x0000);
    lcdWriteReg(0x0007);
    lcdWriteData(0x000);
    // Write data
    lcdWriteReg(0x0022);
    lcdWriteData(0xFFFF);
    while(1) {}
}

This shall display a white dot at coordinate (0, 0). If it works, congratulations you’re halfway towards success. If it doesn’t, look into the controller datasheet and debug your software/hardware.

Looking into a template driver

STemWin comes with some sample codes for various ST development boards, but display driver is not one of them. Since the limited documentation actually tells us the sample driver is called GUIDRV_Template, try Google “GUIDRV_Template.c” will lead you to the same file at ST forum. (I try not to host the file here as I’m not sure about its license.) Another way to obtain this file is download the trial version of MDK-ARM, as bonus you’ll have all other emWin samples.

Now look at the template driver. The only thing it exposes to the public is a structure GUIDRV_Template_API, which consists a number of function pointers:

/*********************************************************************
*
*       GUI_DEVICE_API structure
*/
const GUI_DEVICE_API GUIDRV_Template_API = {
  //
  // Data
  //
  DEVICE_CLASS_DRIVER,
  //
  // Drawing functions
  //
  _DrawBitmap,
  _DrawHLine,
  _DrawVLine,
  _FillRect,
  _GetPixelIndex,
  _SetPixelIndex,
  _XorPixel,
  //
  // Set origin
  //
  _SetOrg,
  //
  // Request information
  //
  _GetDevFunc,
  _GetDevProp,
  _GetDevData,
  _GetRect,
};

The documentation actually tells us that among all the functions , we only need to adapt the _GetPixelIndex and _SetPixelIndex. That is more or less correct. I show you how.

Quick and Dirty emWin driver

Go to GUIDRV_Template.c, find _GetPixelIndex and _SetPixelIndex. SEGGER already put the comments there saying “Write into hardware … Adapt to your system”, so adapt them as follows

// Note: Pseudo code below, adapt to your implementation
static void _SetPixelIndex(GUI_DEVICE *pDevice, int x, int y, int color)
{
    ....
    {
        //Draw_Pixel(x,y,PixelIndex);
        // Move cursor
        lcdWriteReg(0x0002);
        lcdWriteData(HIBYTE(x));
        lcdWriteReg(0x0003);
        lcdWriteData(LOBYTE(x));
        lcdWriteReg(0x0006);
        lcdWriteData(HIBYTE(y));
        lcdWriteReg(0x0007);
        lcdWriteData(LOBYTE(y));
        // Write data
        lcdWriteReg(0x0022);
        lcdWriteData(color);
    }
    ....
}

static unsigned int _GetPixelIndex(GUI_DEVICE *pDevice, int x, int y)
{
    U16 reads[3];
    ....
    {
        //PixelIndex = ili9320_GetPoint(x,y);
        // Move cursor
        lcdWriteReg(0x0002);
        lcdWriteData(HIBYTE(x));
        lcdWriteReg(0x0003);
        lcdWriteData(LOBYTE(x));
        lcdWriteReg(0x0006);
        lcdWriteData(HIBYTE(y));
        lcdWriteReg(0x0007);
        lcdWriteData(LOBYTE(y));
        // Start read data
        lcdWriteReg(0x0022);
        lcdReadData(reads[0]);
        lcdReadData(reads[1]);
        lcdReadData(reads[2]);
        // Pack RGB into RGB565, see HX8352C datasheet for 16-bit interface read
        PixelIndex = ((reads[1] & 0xF800) | ((reads[1] & 0x00FC) << 3) | (reads[2] >> 11));
    }
    ....
}

To let emWin use the template driver, LCDConf.c must be provided. Sample LCDConf.c can be found inside STemWin folder, modify LCD_X_Config like this:

....
//
// Physical display size
//
#define XSIZE_PHYS  240
#define YSIZE_PHYS  400
....
void LCD_X_Config(void)
{
    ....
    pDevice = GUI_DEVICE_CreateAndLink(GUIDRV_Template_API, GUICC_565, 0, 0);
    ....
}

....

int LCD_X_DisplayDriver(unsigned LayerIndex, unsigned Cmd, void * pData)
{
    ....
    switch (Cmd)
    {
        case LCD_X_INITCONTROLLER:
        {
            lcdInit();
            return 0;
        }
        ....
    }
    ....
}

Added other necessary files (GUIConf.c, various header files, etc) into project, write a test main as

....
void main(void)
{
    ....
    // Setup STM32 system (clock, PLL and Flash configuration)
    SystemInit();
    // Necessary for STemWin to authenticate that it is runing on STM32 processor
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);

    GUI_Init();
    GUI_DispString("Hello world!");

    ....
}

And you’re golden.

HX8352C emWin development board
HX8352C emWin development board

Re-organisation code

Thanks for following me till this point. I must apologise that I did not prepare any ready examples  that you can modify on top. I deem providing such an example not necessary because all what we’ve been doing is “quick and dirty”. If you look into the SEGGER provided drivers and examples, you will find that the hardware interfacing, the LCD drawing primitives and the LCD panel configuration are nicely segregated into different modules. This enables SEGGER to distribute the LCD drawing primitives as hardware independent binary “drivers” without restrict the actual panel and hardware connectivity. Although I have no intention to distribute the custom driver in binary form, adopting such architecture will ensure optimal readability and portability.

Let’s go back look at the GUIDRV_Template_API structure. We have already implemented the drawing functions. The remaining 4 functions, namely _GetDevFunc_GetDevProp_GetDevData_GetRect, are critical for emWin core to query necessary information from the driver. The reference implementation in GUIDRV_Template.c involves a lot of runtime settings, e.g., VRAM, panel dimension and orientation, so that user is able to configure the binary driver using external code. Since I’m working on a single panel and I’m open source,  these settings can be hard-coded (SEGGER called it compile-time). With some debug tracing it is not hard to figure out how these functions are called by emWin, and a calling digram of emWin initialization is as below:

emWin initialization
emWin initialization

I have organized the functions as SEGGER’s other binary driver, with GUIDRV_HX8352C.c handles all the controller interfacing, LCDConf.c links controller module with the hardware interface, and HX8352C_GPIO handles GPIO hardware access to the LCD controller.

Driver optimization

Now look at the template driver’s drawing functions. We already have _GetPixelIndex and _SetPixelIndex. The remaining 4 functions, _DrawBitmap_DrawHLine_DrawVLine and  _FillRect, are implemented as repeatedly calling  _GetPixelIndex and _SetPixelIndex, which is very inefficient. The correct way of writing these functions is to utilize the LCD controller’s hardware clipping mechanism, and fill GRAM directly. 

A lower level of optimization targets at improving GPIO driving efficiency. For this part I have referenced Andy Brown’s excellent stm32plus library. Most GPIO driving functions are implemented as ARM assembly to ensure correct timing. Andy further introduced a way of fast GPIO writing by utilizing data already on the databus and toggle WR signal repeatedly. This is very useful when doing block filling or line drawing. When all these optimization I have archived 8,846,000 pixel/second filling rate with 72MHz processor clock.

Performance can be further enhanced with a higher pin count STM32 processor using FSMC port access.


GUIDRV_HX8352C

The source for this driver is available at GitHub. Several notes:

  1. Demo application is from SEGGER.
  2. The building environment is Eclipse + GNU Make + ARM GCC, as described in my previous blog.
  3.  The STM32 development board is from taobao. I choose this because it uses 4 wire SWD interface, saving many I/O pins.
  4. Theoretically the driver supports emWin “multi-layer” or “multi-display”. But I have not tested it yet.
  5. Panel orientation is hard-coded in LCDConf.h, to maintain the compatibility with other emWin drivers.
  6. Due to the limited LCD GRAM and RAM in STM32 processor, not all emWin examples are usable. The video below shows those I’ve been able run on a STM32F103RCT6.

23 thoughts on “HOWTO: Write a display driver for SEGGER emWin

  1. Hello, may I ask you a question about how do you know configuring driver for your LCD?I have SSD1289 and I’m still confuse about how to make it run in STeMwin. Thank you very much for your kindly help.

    Reply
  2. Dear Baoshi,

    did you used the precompiled version of emWin?

    I´m asking because I´m trying it to the NXP LPC2387 and I successfully built the hardware access routines. Unfortunately, something is still wrong, as you can see here (http://laboratoriosacme.net/wp/?p=13543) and here (http://laboratoriosacme.net/wp/?p=13542). The message on display should be `Hello World!´.

    I´m wondering if I need to write my own color correction routines… and if those routines will be recognized by the precompiled version of emWin…

    Thank you very much,

    Reply
    • From the picture I guess you are right, the color format is incorrect. I think your hardware access routine only deals with bus read/write so you’re not able to access display RAM directly. In this case you can check if the GUI_DEVICE_CreateAndLink() is given the correct color format, and if the controller initialization sequence is good.

      Reply
      • I forced another byte write operation per data write and voilà! (http://laboratoriosacme.net/wp/?p=13544) and (http://laboratoriosacme.net/wp/?p=13545). It´s still wrong, but our guess is right: the driver is sending just 2 bytes per pixel and the SSD1963 expects 3 bytes per pixel.

        I had sent and email to NXP support asking for a bug correction. The NXP precompiled version of emWin doesn´t has the GUIDRV_Template file and I don´t know if it will accept some “external driver”.

        Just for information, I´ll post updates here, if you think it´s of interest. Thank you one more time!

        Reply
        • I don’t understand why emWin choose to send 2 bytes instead of 3. By right you can choose color format in GUI_DEVICE_CreateAndLink(), set RGB888 format will force driver to use 24bit color. Maybe NXP has hardcoded it? Or is there any way to set SSD1963 in 16-bit mode?

          Reply
          • I´d tried the GUICC_888 format, but the driver still sends only 16 bits. I think most people only uses the 16 or 24 bit interface and, thus, this bug just never was really noted.

            And I contacted the tech support of the Solomon, but the SSD1963 doesn´t supports the 16 bit (two byte) interface.

            I sent an email to the NXP support. Let´s see what they say…

  3. Well, while waiting for the NXP/Sagger correction, I made a small patch: using the 16 bit interface of FlexColor (GUIDRV_FlexColor_SetFunc(pDevice, &PortAPI, GUIDRV_FLEXCOLOR_F66720, GUIDRV_FLEXCOLOR_M16C0B16);), my Write Data command disassemble the word in 3 bytes to SSD1963, and the results you can see here (http://laboratoriosacme.net/wp/?p=13553).

    An ugly patch, but I works now. 🙂

    Reply
    • Arrh Marcellus you’re right. I totally forget this function. Yes you need this setting when using FlexColor driver. I looked into some of my old code for ILI9325 8-bit mode and found the same thing:
      void LCD_X_Config(void)
      {
      GUI_DEVICE * pDevice;
      CONFIG_FLEXCOLOR Config = {0};
      GUI_PORT_API PortAPI = {0};
      //
      // Set display driver and color conversion
      //
      pDevice = GUI_DEVICE_CreateAndLink(GUIDRV_FLEXCOLOR, GUICC_565, 0, 0);
      //
      // Display driver configuration, required for Lin-driver
      //
      LCD_SetSizeEx (0, XSIZE_PHYS , YSIZE_PHYS);
      LCD_SetVSizeEx(0, VXSIZE_PHYS, VYSIZE_PHYS);
      //
      // Orientation
      //
      Config.Orientation = GUI_SWAP_XY | GUI_MIRROR_Y;
      Config.NumDummyReads = 2;
      GUIDRV_FlexColor_Config(pDevice, &Config);
      //
      // Set controller and operation mode
      //
      PortAPI.pfWrite8_A0 = LcdWriteReg;
      PortAPI.pfWrite8_A1 = LcdWriteData;
      PortAPI.pfWriteM8_A1 = LcdWriteDataMultiple;
      PortAPI.pfReadM8_A1 = LcdReadDataMultiple;
      GUIDRV_FlexColor_SetFunc(pDevice, &PortAPI, GUIDRV_FLEXCOLOR_F66708, GUIDRV_FLEXCOLOR_M16C0B8);
      }

      😀

      Reply
    • Hi Marcellus! To do this patch, did you use PortAPI.pfWrite16 functions? How did you disassemble the word in 3 bytes into your Write Data command wthout affect another configurations data commands?
      Thank you so much!

      Reply
  4. hi,

    is it possible to modify the the driver file and make it work for RA8875 driver? I`m struggling with that. but no experience is emWin yet. I think it is going to be so easy for you.

    Thanks.
    Emjey

    Reply
    • emWin’s GUIDRV_FlexColor driver supports RA8875 directly. You can follow the samples came with STemWin. Mainly you just need to modify LCD_X_Config() and provide all the hardware access functions.

      Reply
      • well the thing is that I don`t use ST . I use cypress Psoc and this library from ST is modified to be used for ST chips.
        modifying Flex from ST is easier or modifying the template?

        for example there is no PortAPI in Emwin for Psoc.
        I didn`t understand clearly that what should I exactly provide in LCD_X_Config ? which registers should be written in the controller in this section? which actions should be taken care of in this part?

        can you please explain more?

        is it possible that I send you my project and you check it? if so, would you please send me an email ?

        Reply
  5. I want to use STemwin GUI library for one of my project. Interfaced successfully STM32F207 with SSD1963 LCD driver (480×272). But when used emwin library standalone application it gets hardfault handler after GUI_X_Init. Seen on disassembly there was some register data corrupted. Changed the SRAM driver as according to my target device & also changed the LCD display initialization routine as my LCD is not on FSMC, its’ on simple GPIO data lines.

    Please suggest if anyone used emwin without LCD on FSMC.

    Reply
    • The default implementation in STemwin contains nothing in GUI_X_Init. It could hang somewhere else in the initial process. Normally you can set breakpoints at all your written functions to catch where the program went into default handler.
      There is nothing different between GPIO or FSMC, just using GPIO you must set WR/RD signals and DATA port manually, while using FSMC you just access the peripheral as memory.

      Reply
  6. does anyone had a working SSD1289 Driver available for me ? I had seen in the beginning of this Blog some discussion about it, but i could’t find an example or so for that.
    I would like to use this small Display to build a messuring Instrument for displaying the “Standing Wave Ratio” of a Ham-Radio antenna.
    Tnx Jupp

    Reply
  7. Great Post! It helped me a lot with HX8352B. If you have a HX8352B model use this pseudo-function to write a pixel on the screen:

    void LCD_XY(uint16 x, uint16 y, uint16 cor)
    {

    Set_LCD_8B_REG(0x0002,HI8(x));
    Set_LCD_8B_REG(0x0003,LO8(x));

    Set_LCD_8B_REG(0x0004,HI8(x));
    Set_LCD_8B_REG(0x0005,LO8(x));

    Set_LCD_8B_REG(0x0006,HI8(y));
    Set_LCD_8B_REG(0x0007,LO8(y));

    Set_LCD_8B_REG(0x80,HI8(y));
    Set_LCD_8B_REG(0x81,LO8(y));

    Set_LCD_8B_REG(0x82,HI8(x));
    Set_LCD_8B_REG(0x83,LO8(x));

    // Write data
    Set_LCD_8B_REG(0x0022,cor);

    }

    Reply
  8. Hi all, I have a problem with SSD2119. I have a working porting of the driver but I’m not able to use alpha. Every time I set alpha and then I use some drawing function the application crash. Same behaviour if I try to display a png with transparency. Any idea?

    Reply
  9. Hello, there’re two things in connection with transparency, read back and memory. You can try write some texts on top of a color block to check if read back is working (otherwise the text will look like on black background). For memory, check the amount of memory configured in GUIConf. For many of the small MCUs because we cannot allocate too much memory to emWin, transparency is not possible.

    Reply
  10. How much memory is sufficient memory?
    I have 160k RAM , transparent text is displayed but changing transparent text like time, or numbers or a rolling text display is not able to erase background before drawing new one.

    Reply

Leave a Reply