Search code examples
cesp32freetype

Memory corruption caused by FT_Render_Glyph() API of FreeType


I have added FreeType 2.13.2 to my code and test run in Windows PC and it looks working fine. After move the same code to ESP32, reset exception happened, I try to location to problem and finally I found one of my display string (global 50byte char variable) is being corrupted after first calling to FT_Render_Glyph(), if I skip this API call everything goes fine and no exception. This is quite basic key function of FreeType that I just cannot drop it to avoid the problem, anyone experience the same/similar when using FreeType?

It is just weird that FT_Render_Glyph works fine in Windows PC but not in ESP32, compiler flags are almost the same except std=C99 (in PC) and std=gnu17 (in ESP32). The FT_Render_Glyph is not having any parameter that related to my global variable address, and it is suppose only operates her own face->glyph data, my global variable location is quite close to the face variable according to the .map file.

 .bss.menustring
                0x000000003fca2de0       0x32 esp-idf/main/libmain.a(mymain.c.obj)
                0x000000003fca2de0                menustring
 .bss.face      0x000000003fca2e18        0x4 esp-idf/main/libmain.a(mymain.c.obj)
                0x000000003fca2e18                face
 .bss.library   0x000000003fca2e1c        0x4 esp-idf/main/libmain.a(mymain.c.obj)
                0x000000003fca2e1c                library

below is the code snippet of problem detected when calling FT_Render_Glyph(), variable 100% corrupted after 1st call the FT_Render_Glyph().

void showchar(char *textmsg)
{
    uint32_t glyph_index;

    glyph_index = FT_Get_Char_Index( face, *textmsg );
//    ESP_LOGI(TAG, "Showchar: do nothing just return, FT_Get_Char_Index returns %"PRIu32" for char %c", glyph_index, *textmsg);
//    return;

    if( glyph_index == 0 )
    {
        ESP_LOGI(TAG, "Showchar: FT_Get_Char_Index returns 0 for char %c", *textmsg);
        return;
    }

    if( FT_Load_Glyph(face, glyph_index, 0) )
    {
        ESP_LOGI(TAG, "Showchar: FT_Load_Glyph returns error for index %"PRIu32"", glyph_index);
        return;
    }
//    ESP_LOGI(TAG, "Showchar: do nothing just return, FT_Get_Char_Index returns %"PRIu32" for char %c", glyph_index, *textmsg);
//    return;

    if( FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) )
    {
        ESP_LOGI(TAG, "Showchar: FT_Render_Glyph returns error");
        return;
    }
    ESP_LOGI(TAG, "Showchar: do nothing just return, FT_Get_Char_Index returns %"PRIu32" for char %c, FT_Load_Glyph & FT_Render_Glyph returns 0", glyph_index, *textmsg);
    return;

still not yet found a solution for this


Solution

  • Thanks all for responding this post. I try to build a small program of printing pages of string in both Windows and ESP32 to see whether able to trigger the same problem. The small program runs perfectly in Windows (same as FreeType integrated my original application in Windows), but it runs to unexpected result in ESP32 (as this is a thin program, it does not reset on corruption but still shows the same problem of memory corruption), it stopped at the 2nd page and loop again(due to I reload the string at start of the loop).

    the marco PCsimulator is the main switch to building Windows or ESP32.

    since there are 3 main FreeType API I have to call for printing every characters, I just print my string content after each call to see any memory corruption happen, and in ESP32, the corruption happen when calling FT_Render_Glyph for character K, you can see the string content is totally changed, from this test result, I would certify FreeType rendering API is having serious memory corruption problem when compile to ESP32 (I cannot say it is a bug as it do works in Windows, it only not working as expected in ESP32, may be RISC-V CPU too).

    Any other open source library recommended to replace FreeType? (I want to use scalable TTF at my project)

    OK, let's look at ESP tracelog:

    FT_Get_Char_Index return index 44 for char I
    after get_index String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    FT_Load_Glyph return ok
    after load_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    256levels width/height of the bitmap=3/19, left/top=1/19, advance.x=5
    after render_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    string position 17, xpos,ypos=8/70, advance.x/y=5/0
    FT_Get_Char_Index return index 77 for char j
    after get_index String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    FT_Load_Glyph return ok
    after load_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    256levels width/height of the bitmap=4/24, left/top=-1/19, advance.x=4
    after render_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    string position 18, xpos,ypos=12/70, advance.x/y=4/0
    FT_Get_Char_Index return index 45 for char J
    after get_index String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    FT_Load_Glyph return ok
    after load_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    256levels width/height of the bitmap=8/20, left/top=0/19, advance.x=9
    after render_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    string position 19, xpos,ypos=21/70, advance.x/y=9/0
    FT_Get_Char_Index return index 78 for char k
    after get_index String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    FT_Load_Glyph return ok
    after load_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    256levels width/height of the bitmap=7/19, left/top=1/19, advance.x=9
    after render_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    string position 20, xpos,ypos=30/70, advance.x/y=9/0
    FT_Get_Char_Index return index 46 for char K
    after get_index String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    FT_Load_Glyph return ok
    after load_glyph String content=aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+
    256levels width/height of the bitmap=10/19, left/top=1/19, advance.x=11
    after render_glyph String content=!?
    string position 21, xpos,ypos=41/70, advance.x/y=11/0
    I (2858) UKKan: Whole LCD update done!
    

    you can see when doing character K, the String content is corrupted after calling FT_Render_Glyph, this is exactly the same as my original application, the same thing happen after calling this API, and due to program size differences, it may not immediately trigger a reset, but memory corruption now confirmed not due to other part of my program, it MUST be due to FT_Render_Glyph API as it is 100% duplicated in this thin program. I put the source code below by removing the TTF font which 3000line of data code.

    //#define PCsimulator         // define it if compile to Windows; remark it if compile to ESP32//
    
    #include <stdio.h>
    #include <stdint.h>
    #include <stdbool.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/unistd.h>
    #include <sys/stat.h>
    
    #include <ft2build.h>
    #include FT_FREETYPE_H
    
    #ifdef PCsimulator
    #include <conio.h>
    #include <SDL2/SDL.h>
    #include <windows.h>
    #else
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/queue.h"
    #include "driver/gpio.h"
    #include "esp_log.h"
    #include "esp_task_wdt.h"
    #include "driver/spi_master.h"
    #include "esp_check.h"
    #include "sdkconfig.h"
    #include "esp_random.h"
    #endif
    
    
    // True Type Font file 39335_UniversCondensed.ttf
    const uint8_t ttffont[] =
    {
    ......
    };
    
    
    #define LCD_MAX_WIDTH 128
    #define LCD_MAX_HEIGHT 128
    #define fontmaxheight 23
    #define fontmaxwidth 17
    #ifdef PCsimulator
    #define Myexit return 0
    #else
    #define Myexit return
    
    
    #define LCD_HOST SPI2_HOST
    #define PIN_NUM_MOSI 35
    #define PIN_NUM_CLK 36
    #define PIN_NUM_CS 45
    #define PIN_NUM_DC 4
    #define PIN_NUM_RST 5
    #define SSD1351_CMD_WRITECGRAM 0x5C // SSD1351 command for writing data to CGRAM
    
    const char *TAG = "UKKan";
    #endif
    
    FT_Library library;
    FT_Face face;
    char menustring[200];
    FT_ULong  charcode;
    
    typedef struct BackgndColor
    {
       uint8_t BlueColor;               // this is to match SSD1351
       uint8_t GreenColor;              // 18bit 666 will drop LSB 2bit
       uint8_t RedColor;                
    }
    DSColor;
    
    DSColor CGRAM[LCD_MAX_HEIGHT*LCD_MAX_WIDTH];
    
    //const DSColor sysbkcolor =
    DSColor sysbkcolor =
    {
        0,
        0,
        0
    };
    
    DSColor fillcolor;
    
    
    #ifdef PCsimulator
    SDL_Window* window;
    SDL_Renderer* renderer;
    
    #else
    typedef struct
    {
        uint8_t cmd;
        uint8_t data[16];   // for SSD1351, max 3 bytes of data follow a command byte, but ST7789 use more bytes
        uint8_t databytes;  // No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
    } lcd_init_cmd_t;
    
    spi_device_handle_t spi;
    
    // Place data into DRAM. Constant data gets placed into DROM by default, which is not accessible by DMA.
    DRAM_ATTR const lcd_init_cmd_t ssd1351_init_cmds[]={
        {0xFD, {0x12}, 1},      // set command lock
        {0xFD, {0xB1}, 1},      // set command lock
        {0xAE, {0x00}, 0},      // set sleep mode on (display off)
        {0xB3, {0xF1}, 1},      // front clock divider/oscillator freq
        {0xCA, {127}, 1},       // set MUX ratio (128mux)
    //    {0xA0, {0xA2}, 1},      // set Re-map/Color Depth(Display RAM to Panel)
        {0xA0, {0xB0}, 1},      // set Re-map/Color Depth(Display RAM to Panel) remap row/col scan direction for LCD module orientation on PCB layout
        {0x15, {0x00, 0x7F}, 2},    // set column address, start addr=0, end addr=127
        {0x75, {0x00, 0x7F}, 2},    // set row address, start addr=0, end addr=127
        {0xA1, {0x00}, 1},      // set display start line
        {0xA2, {0x00}, 1},      // set display offset row
        {0xB5, {0x00}, 1},      // set GPIO
        {0xAB, {0x01}, 1},      // set function selection, enable internal Vdd regulator
        {0xB1, {0x32}, 1},      // set reset(phase1)/pre-charge period(phase2) (precharge not the same as reset value)
        {0xBE, {0x05}, 1},      // set Vcomh voltage (reset=0x05)
        {0xA6, {0x00}, 0},      // set display mode (reset to normal display)
        {0xC1, {0xC8, 0x80, 0xC8}, 3},  // set color A,B,C contract current to reset value(blue, green, red)
        {0xC7, {0x0F}, 1},      // set master contract current control(reset value)
        {0xB4, {0xA0, 0xB5, 0x55}, 3},  // set segment low voltage (Vsl to reset value)
        {0xB6, {0x01}, 1},      // set second pre-charge period (1DCLKS, diff from reset value 8DCLKS)
        {0xAF, {0x00}, 0},      // set sleep mode off (display on)
    //    {0xA5, {0x00}, 0},      // set display mode (reset to normal display)
        {0, {0}, 0xff}
    };
    
    
    //This function is called (in irq context!) just before a transmission starts. It will
    //set the D/C line to the value indicated in the user field.
    //void IRAM_ATTR lcd_spi_pre_transfer_callback(spi_transaction_t *spihandle)
    void lcd_spi_pre_transfer_callback(spi_transaction_t *spihandle)
    {
        uint32_t dc = (uint32_t)spihandle->user;
        gpio_set_level(PIN_NUM_DC, dc);
    }
    
    
    // Send one command byte to the LCD, which waits until the transfer is completed
    // cskeeplow = 1 means keep CS pin low for following data bytes; 0 means restore CS pin to high
    void lcd_cmd(spi_device_handle_t spi, uint8_t cmd, bool cskeeplow)
    {
        esp_err_t ret;
        spi_transaction_t t;
        memset(&t, 0, sizeof(t));   // Zero out the transaction
        t.length = 8;               // Command is 8 bits
        t.tx_buffer = &cmd;         // pionts to th cmd parameter address
        t.user = (void*)0;          // set DC# to 0 for command
        if( cskeeplow )
        {
            t.flags = SPI_TRANS_CS_KEEP_ACTIVE;     // Keep CS active(low) after data transfer
        }
    
        ret = spi_device_polling_transmit(spi, &t); // Transmit now!
        ESP_LOGI(TAG, "\033[0;36msending command %#04x, ret=%d: %s\033[0m", cmd, ret, esp_err_to_name(ret));
        assert( ret == ESP_OK );
    }
    
    
    // Send data byte(s) to the LCD, which waits until the transfer is completed
    void lcd_data(spi_device_handle_t spi, uint8_t *dataB, uint32_t NoOfByte)
    {
        esp_err_t ret;
        spi_transaction_t t;
        memset(&t, 0, sizeof(t));   // Zero out the transaction
        t.length = NoOfByte*8;      // transaction length is in bits
        t.tx_buffer = dataB;        // pointer to data bytes to be sent
        t.user=(void*)1;            // set DC# to 1 for data
        ret = spi_device_polling_transmit(spi, &t); // Transmit now!
        ESP_LOGI(TAG, "\033[0;36mSending LCD data %d, ret=%d: %s\033[0m", (uint16_t)NoOfByte, ret, esp_err_to_name(ret));
        assert( ret == ESP_OK );
    }
    
    
    //Initialize the SSD1351/ST7789 using either HW SPI or Software SPI
    void lcd_init(void)
    {
        int cmd=0;
        lcd_init_cmd_t* lcd_init_cmds=(lcd_init_cmd_t *)ssd1351_init_cmds;
    
        //Reset the SSD1351/ST7789 chip
        gpio_set_level(PIN_NUM_RST, 0);
        vTaskDelay(10 / portTICK_PERIOD_MS);
        gpio_set_level(PIN_NUM_RST, 1);
        vTaskDelay(10 / portTICK_PERIOD_MS);
        ESP_LOGI(TAG, "\033[0;31mLCD pin RST pulse done\033[0m");
    
       // When using SPI_TRANS_CS_KEEP_ACTIVE, bus must be locked/acquired
        spi_device_acquire_bus(spi, portMAX_DELAY);
    
        //Send all the commands until seeing an 0xFF
        while( lcd_init_cmds[cmd].databytes != 0xFF )
        {   // CS pin controlled inside lcd_cmd and lcd_data
            lcd_cmd(spi, lcd_init_cmds[cmd].cmd, true);
    
            if( ((lcd_init_cmds[cmd].databytes)&0x1F) != 0x00 )  // some LCD command comes with no data byte follows
            {
                lcd_data(spi, (uint8_t *)&(lcd_init_cmds[cmd].data), lcd_init_cmds[cmd].databytes&0x1F); // bit7 is for indication of delay between commands
            }
            if( lcd_init_cmds[cmd].databytes&0x80 ) // bit7 is for indication of delay between commands
            {
                vTaskDelay(100 / portTICK_PERIOD_MS);
            }
            cmd++;
        }
        // Release SPI bus (for SPI flash/PSRAM)
        spi_device_release_bus(spi);
    }
    #endif
    
    
    void lcd_updateCGRAM(void)
    {
    #ifdef PCsimulator
        uint32_t xpos;          // xpos and ypos is the position of point on the screen, not CGRAM address
        uint32_t ypos;          // assume CGRAM is arranged in first row RGB RGB RGB ... and then next lower row RGB RGB RGB ...
                                // 0,0 is at top left corner, same as the SDL2 window position arrangement
        uint32_t tmppos;        // tmppos is the DSColor position (3byte strucutre) in CGRAM array, not offset address
    
        for( ypos = 0; ypos < LCD_MAX_HEIGHT; ypos++ )
        {
            for( xpos = 0; xpos < LCD_MAX_WIDTH; xpos++ )
            {
                tmppos = (ypos * LCD_MAX_WIDTH) + xpos;     // copy the CGRAM pixel content to SDL2 back buffer
                SDL_SetRenderDrawColor(renderer, CGRAM[tmppos].RedColor, CGRAM[tmppos].GreenColor, CGRAM[tmppos].BlueColor, 255);
                SDL_RenderDrawPoint(renderer, xpos, ypos);
            }
        }
        SDL_RenderPresent(renderer);    // present the composed backbuffer to the screen
    #else
        esp_err_t ret;
        spi_transaction_t *rtrans;
        uint8_t updcnt;
        //Transaction descriptors. Declared static so they're not allocated on the stack; we need this memory even when this
        //function is finished because the SPI driver needs access to it.
        static spi_transaction_t trans[6];
    
        //In theory, it's better to initialize trans and data only once and hang on to the initialized
        //variables. We allocate them on the stack, so we need to re-init them each call.
        for(updcnt = 0; updcnt < 6; updcnt++)
        {
            memset(&trans[updcnt], 0, sizeof(spi_transaction_t));
        }
        trans[0].tx_data[0] = SSD1351_CMD_WRITECGRAM;
        trans[0].length = 8;                        // Command is 8 bits
        trans[0].user = (void*)0;                   // DC# needs to be set to 0
        trans[0].flags = SPI_TRANS_USE_TXDATA;      // Keep CS active after data transfer
    
        trans[1].tx_buffer = (uint8_t *)&CGRAM;
        trans[1].length = 128*3*26*8;               // Data bit length, in bits
        trans[1].user = (void*)1;                   // DC# needs to be set to 1
    
        trans[2].tx_buffer = (uint8_t *)(&CGRAM)+(128*3*26);
        trans[2].length = 128*3*26*8;               // Data bit length, in bits
        trans[2].user = (void*)1;                   // DC# needs to be set to 1
    
        trans[3].tx_buffer = (uint8_t *)(&CGRAM)+(128*3*26*2);
        trans[3].length = 128*3*26*8;               // Data bit length, in bits
        trans[3].user = (void*)1;                   // DC# needs to be set to 1
    
        trans[4].tx_buffer = (uint8_t *)(&CGRAM)+(128*3*26*3);
        trans[4].length = 128*3*26*8;               // Data bit length, in bits
        trans[4].user = (void*)1;                   // DC# needs to be set to 1
    
        trans[5].tx_buffer = (uint8_t *)(&CGRAM)+(128*3*26*4);
        trans[5].length = 128*3*24*8;                // Data bit length, in bits
        trans[5].user = (void*)1;                   // DC# needs to be set to 1
    
        ret = spi_device_queue_trans(spi, &trans[0], portMAX_DELAY);
        assert( ret == ESP_OK );
        ret = spi_device_queue_trans(spi, &trans[1], portMAX_DELAY);
        assert( ret == ESP_OK );
        ret = spi_device_queue_trans(spi, &trans[2], portMAX_DELAY);
        assert( ret == ESP_OK );
        ret = spi_device_queue_trans(spi, &trans[3], portMAX_DELAY);
        assert( ret == ESP_OK );
        ret = spi_device_queue_trans(spi, &trans[4], portMAX_DELAY);
        assert( ret == ESP_OK );
        ret = spi_device_queue_trans(spi, &trans[5], portMAX_DELAY);
        assert( ret == ESP_OK );
    
        //Wait for all 6 transactions to be done and get back the results.
        for( updcnt = 0; updcnt < 6; updcnt++ )
        {
            ret = spi_device_get_trans_result(spi, &rtrans, portMAX_DELAY);
            assert( ret == ESP_OK );
        }
        ESP_LOGI(TAG, "\033[0;31mWhole LCD update done!\033[0m");
    #endif
    }
    
    
    // fill the CGRAM with specific color in parameter
    void fillwith(DSColor *thiscolor)
    {
        uint32_t xpos;          // xpos and ypos is the position of point on the screen, not CGRAM address
        uint32_t ypos;          // assume CGRAM is arranged in first row RGB RGB RGB ... and then next lower row RGB RGB RGB ...
        uint32_t tmppos;        // tmppos is the DSColor position (3byte strucutre) in CGRAM array, not offset address
    
        for( ypos = 0; ypos < LCD_MAX_HEIGHT; ypos++ )
        {
            for( xpos = 0; xpos < LCD_MAX_WIDTH; xpos++ )
            {
                tmppos = (ypos * LCD_MAX_WIDTH) + xpos;     // copy DSColor value to all pixels of the CGRAM
                CGRAM[tmppos].RedColor = thiscolor->RedColor;
                CGRAM[tmppos].GreenColor = thiscolor->GreenColor;
                CGRAM[tmppos].BlueColor = thiscolor->BlueColor;
            }
        }
    }
    
    
    // clear screen to specific color in parameter
    void clearscreen(DSColor *thiscolor)
    {
        fillwith(thiscolor);
    }
    
    
    // draw a point to the CGRAM using thiscolor
    void DrawPoint(uint32_t xpos, uint32_t ypos, DSColor *thiscolor)
    {
        uint32_t tmppos;
    
        if( (xpos < LCD_MAX_WIDTH) && (ypos < LCD_MAX_HEIGHT) )
        {
            tmppos = (ypos * LCD_MAX_WIDTH ) + xpos;
            CGRAM[tmppos].RedColor = thiscolor->RedColor;
            CGRAM[tmppos].GreenColor = thiscolor->GreenColor;
            CGRAM[tmppos].BlueColor = thiscolor->BlueColor;
        }
    }
    
    
    
    #ifdef PCsimulator
    int main(int argc, char *argv[])        // PC app main function type
    #else
    void app_main(void)                     // ESP32 app main function, (a task in RTOS above min priority), is an indefinite loop
    #endif
    {
        uint32_t glyph_index,xpos,ypos;
        uint32_t x,y,s,mwidth,mheight,exitorloop=0;
    
        mwidth = fontmaxwidth;
        mheight = fontmaxheight;
    #ifdef PCsimulator
        SDL_Init(SDL_INIT_VIDEO);
        window = SDL_CreateWindow("RGB888", 8, 30, LCD_MAX_WIDTH, LCD_MAX_HEIGHT, 0);
        renderer = SDL_CreateRenderer(window, -1, 0);
    #else
        // init LCD on ESP32
        gpio_config_t io_conf = {};
        esp_err_t ret;
        spi_bus_config_t buscfg =
        {
            .miso_io_num = -1,              // no such pin for SSD1351
            .mosi_io_num = PIN_NUM_MOSI,
            .sclk_io_num = PIN_NUM_CLK,     // CS pin belongs to device config, D/C, RST pins are outside SPI signal
            .quadwp_io_num = -1,
            .quadhd_io_num = -1,
            .max_transfer_sz = 10248
        };
        spi_device_interface_config_t devcfg =
        {
            .clock_speed_hz = 16*1000*1000,     //Clock out max at 26 MHz, in PCB it is max 23, in dev board it is 5MHz
            .mode = 0,                          //SPI mode 0
            .spics_io_num = PIN_NUM_CS,         //CS pin
            .queue_size = 7,                    //We want to be able to queue 7 transactions at a time
            .pre_cb = lcd_spi_pre_transfer_callback,    // Specify pre-transfer callback to handle D/C line
        };
        // Initialize the SPI2 bus (main SPI2 pins exclude CS, DC, RST)
        ret=spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
        ESP_LOGI(TAG, "\033[0;31mSPI2 bus_initialize return %s\033[0m", (ret == ESP_OK? "ESP_OK" : "FAILED!!"));
        // Attach the LCD device to the SPI bus (ie. the CS pin on that LCD, get back handle)
        ret=spi_bus_add_device(LCD_HOST, &devcfg, &spi);
        ESP_LOGI(TAG, "\033[0;31mSPI2_bus_add_device return %s\033[0m", (ret == ESP_OK? "ESP_OK" : "FAILED!!"));
        //Initialize non-SPI GPIOs
        io_conf.pin_bit_mask = ((1ULL<<PIN_NUM_DC) | (1ULL<<PIN_NUM_RST));
        io_conf.mode = GPIO_MODE_OUTPUT;
        io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
        io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
        io_conf.intr_type = GPIO_INTR_DISABLE;
        gpio_config(&io_conf);
        ESP_LOGI(TAG, "\033[0;31mI/O pin LCD RST, DC init as output\033[0m");
        lcd_init();
    #endif
    
        if( FT_Init_FreeType(&library) )
        {
            printf("FreeType library init failed\n");
            Myexit;
        }
    
        if( FT_New_Memory_Face(library, ttffont, 45516, 0, &face) )
        {
            FT_Done_FreeType(library);
            printf("FreeType open face failed\n");
            Myexit;
        }
    
        if( FT_Set_Pixel_Sizes(face, mwidth, mheight) )
        {
            FT_Done_Face(face);
            FT_Done_FreeType(library);
            printf("FreeType set pixel failed\n");
            Myexit;
        }
        printf("FreeType init ok, face open and set pixel success\n");
    
        xpos=3;
        ypos=70;
        s=0;
        clearscreen(&sysbkcolor);
        lcd_updateCGRAM();
    
    StartTest:
        clearscreen(&sysbkcolor);
    
        strcpy(menustring, "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvvwWxXyYzZ0123456789aAbBcCdDeEfFgG!$#~@':;<>,./?|^&*()-_=+");
    
        do
        {
            glyph_index = FT_Get_Char_Index( face, menustring[s] );
            if( glyph_index == 0 )
            {
                Myexit;
            }
            printf("FT_Get_Char_Index return index %ld for char %c\n", glyph_index, menustring[s]);
            printf("after get_index String content=%s\n", menustring);
    
            if( FT_Load_Glyph(face, glyph_index, 0) )
            {
                Myexit;
            }
            printf("FT_Load_Glyph return ok\n");
            printf("after load_glyph String content=%s\n", menustring);
    
            if( FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL) )
            {
                Myexit;
            }
            printf("256levels width/height of the bitmap=%d/%d, left/top=%d/%d, advance.x=%ld\n", face->glyph->bitmap.width, face->glyph->bitmap.rows, face->glyph->bitmap_left, face->glyph->bitmap_top, face->glyph->advance.x >> 6);
            printf("after render_glyph String content=%s\n", menustring);
    
            for(y=0; y<face->glyph->bitmap.rows; y++)
            {
                for(x=0; x<face->glyph->bitmap.width; x++)
                {
                    if( face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x] != 0 )
                    {
    #ifdef PCsimulator
                        fillcolor.RedColor=face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x];
                        fillcolor.GreenColor=face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x];
    #else
                        fillcolor.RedColor=face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x] >>2;
                        fillcolor.GreenColor=face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x] >>2;
    #endif
                        fillcolor.BlueColor=0;
                        DrawPoint(xpos+(face->glyph->bitmap_left)+x, ypos-(face->glyph->bitmap_top)+y, &fillcolor);
    //                    printf("%03d ",face->glyph->bitmap.buffer[y*face->glyph->bitmap.width+x]);
                    }else
                    {
    //                    printf("    ");
                    }
                }
    //            printf(";\n");
            }
            xpos = xpos + (face->glyph->advance.x >> 6);
            printf("string position %ld, xpos,ypos=%ld/%ld, advance.x/y=%ld/%ld\n", s, xpos, ypos, face->glyph->advance.x >> 6, face->glyph->advance.y >> 6);
            s++;    // next char
        }while( (s<strlen(menustring)) && (xpos < LCD_MAX_WIDTH ) );
        lcd_updateCGRAM();
    
    #ifdef PCsimulator
        Sleep(1000);        // wait
    #else
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    #endif
    
        if( s >= strlen(menustring) )
        {
            s=0;
            mwidth=fontmaxwidth;
            mheight=fontmaxheight;
        }else
        {
            mwidth = mwidth + 4;
            mheight = mheight + 5;
        }
        xpos=3;
        FT_Set_Pixel_Sizes(face, mwidth, mheight);
    
    #ifdef PCsimulator
        if(_kbhit())
        {
            if(_getch() == 27)
            {
                exitorloop=1;
            }
        }
    #endif
        if(!exitorloop)
        {
            goto StartTest;
        }
    
    #ifdef PCsimulator
        FT_Done_Face(face);
        FT_Done_FreeType(library);
        SDL_DestroyRenderer(renderer);
        SDL_DestroyWindow(window);
        SDL_Quit();
    #endif
        Myexit;
    }