2023-08-18 02:24 AM - edited 2023-08-18 02:29 AM
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).
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*).
Could you help me make sense of this?
Thanks in advance.
Solved! Go to Solution.
2023-08-18 10:05 AM - edited 2023-08-19 12:55 AM
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
I hope this bug has been fixed by now
2023-08-18 10:05 AM - edited 2023-08-19 12:55 AM
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
I hope this bug has been fixed by now
2023-08-21 02:26 AM - edited 2023-08-21 02:35 AM
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).
2023-08-21 04:25 AM - edited 2023-08-21 04:53 AM
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...
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.
2023-08-22 01:46 AM
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!
2023-08-22 04:07 AM
I'm glad if I was able to help
2024-05-16 09:12 AM
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.
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
2024-05-16 01:17 PM
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.
2024-05-17 01:56 AM
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...
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
2024-05-17 05:11 AM
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.