cancel
Showing results for 
Search instead for 
Did you mean: 

How to configure TouchGFX with a monochrome LCD on FMC ?

Watchinofoye
Associate II

Hi,

For the purposes of a project, I have a board driven by an STM32F746ZET6, which communicates with an LCD screen (C12864WO from Newhaven Displays) driven by an ST7565P. Communication with the display is via an FMC.

I manage to display pixels on the screen directly via FMC communication. But now I'd like to use TouchGFX to ease the creation of the graphic interface with TouchGFX Designer. And that's where it gets tricky.

The screen is rather atypical for TouchGFX. It's monochrome and the resolution of the 8080 parallel interface is 8 bits. TouchGFX can handle FMC, but only for 16-bit.
What's more, the pixel arrangement is not like on other screens I've seen so far. GRAM is made up of 8 pages which divide the screen into 8 lines 128 pixels long by 8 pixels high. When I send 8 bits via FMC, instead of filling 8 bits horizontally as on other screens, the screen fills the 8 bits in the first column of the first page, before moving on to the next column.

So I'm trying to adapt the code for this rather special display. Based on the information on this page (unfortunately very 16-bit screens oriented): https://support.touchgfx.com/docs/development/touchgfx-hal-development/scenarios/scenarios-fmc
I need to be able to manipulate the framebuffer and I don't understand at all how it's arranged. I've configured STM32CubeMX for a resolution of 128x64 pixels, with a color depth of BW (so logically 1 bpp).

stm32cubeide_0WrSOOkPe2.png

But in the code, the framebuffer is managed by a uint32_t array of (((128 + 7) / 8 ) * 64 + 3) / 4 elements. And the getTFTFrameBuffer() function returns the same array cast to a 16-bit pointer (uint16_t*).

stm32cubeide_o0mKMMp38a.pngstm32cubeide_YpkiMOWcsO.png

Could you help me make sense of this?

Thanks in advance.

1 ACCEPTED SOLUTION

Accepted Solutions
Panchev68
Senior

I suggest you use a similar approach...

 

/// \brief CDisplayDriver class
/// \file CDisplayDriver.hpp
/// \version 1 \date 06.09.2021

#pragma once

#include "BSP/Display/CSSD1306.hpp"
#include "Middlewares\ST\touchgfx\framework\include\touchgfx\hal\Types.hpp"
#include "TouchGFX\target\generated\TouchGFXGeneratedHAL.hpp"

namespace BSP
{
    class CDisplayDriver
    {
        constexpr static inline uint8_t BIT_MASK_LSB [] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

        constexpr static inline size_t DISP_WIDTH_IN_BYTES = 128 / 8;

    public:

        /// \brief CDisplayDriver class destructor
        ~CDisplayDriver () = default;

        /// \brief Initialize the Display Driver
        /// \return bool
        bool init()
        {
            _TFT_Buffer    = TouchGFXGeneratedHAL::getInstance ()->getTFTFrameBuffer ();
            return CSSD1306::getInstance ().init ();
        }

        /// \brief Update the Display
        void update ()
        {
            auto copyBlock8bytes = [this] (uint8_t * src, uint8_t * dst) {
                uint8_t buf [8];

                // rotate the buffer
                for (auto y = 0; y < 8; y++) buf [y] = src [y * DISP_WIDTH_IN_BYTES];

                // rotate the output buffer (90 grad)
                for (auto i = 0; i < 8; i++)
                {
                    auto in = buf [i];
                    for (auto j = 0; j < 8; j++)
                    {
                        auto & out = dst [7 - j];
                        out        = ((in & BIT_MASK_LSB [j]) != 0) ? out | BIT_MASK_LSB [i] : out & ~BIT_MASK_LSB [i];
                    }
                }
            };
            auto & SSD1306 = CSSD1306::getInstance ();

            auto src=reinterpret_cast<uint8_t *> (_TFT_Buffer);
            auto dst = SSD1306.getBufferData ();

            for (auto y = 0; y < 8; y++)
            {
                for (auto x = 0; x < 16; x++) copyBlock8bytes (src + x, dst + (x * 8));

                src += 128;
                dst += 128;
            }
            SSD1306.update ();
        }

        /// \brief Get the Instance of singleton class
        /// \return CDisplayDriver&
        static CDisplayDriver & getInstance ()
        {
            static CDisplayDriver instance;
            return instance;
        }

    private:

        /// \brief CDisplayDriver class constructor
        CDisplayDriver () = default;

        /// \brief Touch GFX Display Buffer
        uint16_t * _TFT_Buffer = nullptr;
    };

}        // namespace BSP

/// \brief Update Display
/// \param rect - not used
/// \note The processing of the entire display is too small to use individual parts. 
/// The entire display is updated each time. Which greatly facilitates processing
inline void updateDisplay (const touchgfx::Rect & rect)
{
    CDisplayDriver::getInstance ().update ();
}

// *******************************************************************
//                         Haw To Implement
// *******************************************************************

/* file : TouchGFX\target\TouchGFXHAL.cpp

extern void updateDisplay (const touchgfx::Rect& rect)

void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
    updateDisplay (rect);
    TouchGFXGeneratedHAL::flushFrameBuffer(rect);
}
*/

 

TouchGFX works with a 16-bit buffer, no matter if Framebuffer Pixel Format = BW is set it should sync with the display buffer when there is a change.

its address is taken with the function

 

TouchGFXGeneratedHAL::getInstance ()->getTFTFrameBuffer ();

 

As a reminder: If B/W Display is used, there must be at least 1 shape in one of the screens for it to work !!! Otherwise it gives an error-

identifier "LCD1bpp" is undefined TouchGFX\target\generated\TouchGFXConfiguration.cpp 35 

LhvxCdPciq8NG3b-1690114689619-4.png

I hope this bug has been fixed by now

 

View solution in original post

10 REPLIES 10
Panchev68
Senior

I suggest you use a similar approach...

 

/// \brief CDisplayDriver class
/// \file CDisplayDriver.hpp
/// \version 1 \date 06.09.2021

#pragma once

#include "BSP/Display/CSSD1306.hpp"
#include "Middlewares\ST\touchgfx\framework\include\touchgfx\hal\Types.hpp"
#include "TouchGFX\target\generated\TouchGFXGeneratedHAL.hpp"

namespace BSP
{
    class CDisplayDriver
    {
        constexpr static inline uint8_t BIT_MASK_LSB [] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

        constexpr static inline size_t DISP_WIDTH_IN_BYTES = 128 / 8;

    public:

        /// \brief CDisplayDriver class destructor
        ~CDisplayDriver () = default;

        /// \brief Initialize the Display Driver
        /// \return bool
        bool init()
        {
            _TFT_Buffer    = TouchGFXGeneratedHAL::getInstance ()->getTFTFrameBuffer ();
            return CSSD1306::getInstance ().init ();
        }

        /// \brief Update the Display
        void update ()
        {
            auto copyBlock8bytes = [this] (uint8_t * src, uint8_t * dst) {
                uint8_t buf [8];

                // rotate the buffer
                for (auto y = 0; y < 8; y++) buf [y] = src [y * DISP_WIDTH_IN_BYTES];

                // rotate the output buffer (90 grad)
                for (auto i = 0; i < 8; i++)
                {
                    auto in = buf [i];
                    for (auto j = 0; j < 8; j++)
                    {
                        auto & out = dst [7 - j];
                        out        = ((in & BIT_MASK_LSB [j]) != 0) ? out | BIT_MASK_LSB [i] : out & ~BIT_MASK_LSB [i];
                    }
                }
            };
            auto & SSD1306 = CSSD1306::getInstance ();

            auto src=reinterpret_cast<uint8_t *> (_TFT_Buffer);
            auto dst = SSD1306.getBufferData ();

            for (auto y = 0; y < 8; y++)
            {
                for (auto x = 0; x < 16; x++) copyBlock8bytes (src + x, dst + (x * 8));

                src += 128;
                dst += 128;
            }
            SSD1306.update ();
        }

        /// \brief Get the Instance of singleton class
        /// \return CDisplayDriver&
        static CDisplayDriver & getInstance ()
        {
            static CDisplayDriver instance;
            return instance;
        }

    private:

        /// \brief CDisplayDriver class constructor
        CDisplayDriver () = default;

        /// \brief Touch GFX Display Buffer
        uint16_t * _TFT_Buffer = nullptr;
    };

}        // namespace BSP

/// \brief Update Display
/// \param rect - not used
/// \note The processing of the entire display is too small to use individual parts. 
/// The entire display is updated each time. Which greatly facilitates processing
inline void updateDisplay (const touchgfx::Rect & rect)
{
    CDisplayDriver::getInstance ().update ();
}

// *******************************************************************
//                         Haw To Implement
// *******************************************************************

/* file : TouchGFX\target\TouchGFXHAL.cpp

extern void updateDisplay (const touchgfx::Rect& rect)

void TouchGFXHAL::flushFrameBuffer(const touchgfx::Rect& rect)
{
    updateDisplay (rect);
    TouchGFXGeneratedHAL::flushFrameBuffer(rect);
}
*/

 

TouchGFX works with a 16-bit buffer, no matter if Framebuffer Pixel Format = BW is set it should sync with the display buffer when there is a change.

its address is taken with the function

 

TouchGFXGeneratedHAL::getInstance ()->getTFTFrameBuffer ();

 

As a reminder: If B/W Display is used, there must be at least 1 shape in one of the screens for it to work !!! Otherwise it gives an error-

identifier "LCD1bpp" is undefined TouchGFX\target\generated\TouchGFXConfiguration.cpp 35 

LhvxCdPciq8NG3b-1690114689619-4.png

I hope this bug has been fixed by now

 
Watchinofoye
Associate II

I'm sorry, I'm not sure to understand all of your code. The framebuffer of TouchGFX might be the same on both of our sides, but how is implemented the framebuffer for SSD1306 (the one you get by calling getBufferData()) ? I see it's a uint8_t array, but how is it organized ?

For the shape, I've already added a Box shape so I have a white background to see the foreground elements (otherwise, the background is transparent and I see nothing in simulator).

Since in a B/W display every 1 bit is a pixel (8 pixels per byte), 16-bit pixels have to be converted to 1-bit pixels. if the pixel value is 0x0000 (black) the recorded pixel is 0 otherwise it is 1.  It's a bit more complicated with OLED because the pixels are arranged vertically. Maybe that's why the code is a bit confusing...

img.png

else getBufferData() returns the pointer to the display buffer 1024 bytes ((128x64) /8). Which is thrown every time by SSD1306.update();.This function sends the entire display buffer to the display controller.

I am deliberately not posting the SSD1306 driver. Because it inherits several library classes. Which will lead to more misunderstandings.

Watchinofoye
Associate II

I ended up testing your code as is, adapting a bit (both my code and yours), and in the end it works fine. I'll have a look at how to clean it up, because I'm not sure everything's clear on my side. Anyway, I'm switching the subject to Solved, your solution is perfect! Thanks a lot!

I'm glad if I was able to help

azMikro
Associate II

Hi @Panchev68  @Watchinofoye , 

Actually I'm facing somewhat of a similar issue. Well, I'm using st7541 lcd with my stm32f407vgt6. 

 

I have created a project using cubeMx and configured SPI1 for it since I am using SPI interface of my LCD. I have created a box on my screen and flash the code but it doesn't seem to display anything...

If you guys are still familiar with your old project any help would be great. 

 

azMikro_0-1715875502384.png

 

I was following a tutorial to setup my project but the guy added the driver files into the project but i am unable to find the driver files for my driver. I did find a GitHub repository from a guy it was used for stm32l151rdt6 here.  I tried adding that file into my project but it ended up giving a lot or errors. 

I have attached my source file also for reference.

Also, I haven't used any LCD with STM before so if I'm missing anything here, please point it out. 

Thanks

With just the main file, we can't tell what could be wrong in all the code (you didn't even shared the custom driver, just the main file, as generated by CubeMX for I can see).

But my idea would be that you need to call OSWrappers::signalVSync() regularly to refresh the interface. Just for testing, you could put it into TouchGFXConfiguration.cpp (TouchGFX/target:generated/TouchGFXConfiguration.cpp), at the beginning of touchgfx_taskEntry() function.

Maybe it will work after that.

Well, I tried the fix but I'm not sure if it'll work considering I don't have the custom driver. 

The tutorial that I mentioned in the above post shows that he adds the driver for the lcd but what I fail to understand is that in the main file only the touchGFX engine is started...

azMikro_0-1715935668191.png

 

but no other configuration is done, and the SPI is also initialized in the main function so how does the TouchGFX communicate with the LCD. Because even without adding the driver files the code still build without error. But gives no output on the lcd. I have simply created a box on the lcd in the TouchGFX application. I have attached my whole project its basically just a beginner touchgfx application. 

 

As far as the driver is concerned I tried to find one for my driver but only found one and it was ported for a different devkit so I can try changing it for my STM32f407. 

 

the driver I talked about is here

Watchinofoye
Associate II

The main program is perfectly normal. It simply initializes all the modules and, in this case, loops the TouchGFX routine to tell it to refresh its display.

TouchGFX simply manages the entire display and generates a framebuffer to send to your screen. But without a driver to tell it what to do with its framebuffer, nothing will be displayed.

You'll have to implement this driver manually, either using the documentation of the display controller (ST7541), or taking an existing driver and adapting it to your hardware (which is what I recommend, and the example you've found looks pretty good).

Once the driver has been implemented, all that remains is to use it to send the framebuffer data generated by TouchGFX to the screen controller.