Electronics > Projects, Designs, and Technical Stuff

Graphics on an embedded system, TFT display - need help with some pointers

(1/3) > >>

I have not done this sort of thing, since the 1980s when I used a Z80 driving a graphics coprocessor https://en.wikipedia.org/wiki/Thomson_EF936x or an NEC UPD7220 https://en.wikipedia.org/wiki/NEC_%C2%B5PD7220. The 9365 project ended up an obscure but successful commercial product, while the 7220 one was never finished because we spent a year trying to decipher the NEC manual :) It was all done in assembler, of course, and actually worked quite well, using video buffer swapping. Then the PC came, everybody used 80x86 boards for this stuff, and the graphics coprocessor business died because those chips were not any faster.

I am just looking for some pointers on present-day software techniques.

Let's say I have a 256x256 TFT and I want to draw a clock. One can develop primitives like draw a filled rectangle or a filled polygon and use these to draw the hands. Then as the hands rotate, and bear in mind one hand will overlap the others, you have to draw the "last time image" in the background colour (to make all the hands vanish) and then draw all the hands afresh using the desired colour. This works ok because you aren't changing all that many pixels. It works much better if you can chuck the new image into a "background buffer" and then swap them. The clock is actually a bad example because you will obviously just swap the buffers at 1Hz, or maybe 5Hz, so this is a trivial way to achieve visual perfection, but not trivial if you are drawing a continuously sweeping hand(s).

More clever ways redraw just the parts which have changed, which generally means fewer pixels need writing, but the software is much more complex; you now need to have algorithms which deal with polygon overlap etc. This is obviously how computer games are done but they have fantastic power, in the CPU and even more in the GPU. I just have a 168MHz ARM 32F4 and a 21mbps SPI feeding the display :)

Do people do this stuff the slow (but intellectually obvious, and fast enough for simple graphics) way described above, or are there easy to use C libraries where you would define shapes, an overlap order of the shapes, and orientation (origin and rotation) of each shape, and the library draws it all for you, by updating only the pixels which actually changed?

Thank you for any hints.

Obviously I am not building a clock but the graphics complexity is very similar.

capt bullshot:
Well, that depends.
Especially on the memory bandwidth of how the display is hooked to the controller, and if you can switch framebuffers.

It's quite easy if you have a fast memory mapped framebuffer and can use double buffering:
Erase framebuffer 1, redraw everything on fb1, switch visible display to fb1
Erase framebuffer 2, redraw everything on fb2, switch visible display to fb2
rinse and repeat ad infinitum
works best if you can synchronize the switching to frame rate
Use your own code or any library you can find.

If you got only one framebuffer, but still memory mapped (fast access):
Erase and redraw small portions (so flickering won't be visible) or draw over small portions in fg/bg colour (depending on the object shown, overwriting works quite well for e.g. text fonts, geometric shapes work well by drawing the old in bg colour and the the new one in fg colour, element by element)

For SPI attached framebuffers:
check if the display can handle two frame buffers - then use the first strategy, otherwise the latter. If your SPI bandwidth is low, I'd recommend the latter anyway.
If your MCU has plenty of RAM, one can draw in internal memory first, and the transfer the delta only (you'd need a second internal buffer then), or transfer the whole framebuffer in one piece (maybe by DMA in the background). Depending on the memory size and bandwidth of the display interface, I found the bulk copy (by DMA if possible) method quite effective.

I've done most of these approaches before, chosen depending on the available HW / bandwidth. For example, a complete framebuffer bulk transfer to a SPI (or slowish parallel bus) display module might be faster and easier than drawing single pixels through individual commands, even with drawing reduced to changed pixels only.

There's a lot of free and commercial libraries available (don't ask me for examples), some of them might even offer ready made drivers for your particular displays. I don't know about any library that uses your "intelligent" approach, afaik they all draw the primitives completely in the order you call the API. Some STM32F4 and higher chips offer the DMA2D that can offload the CPU from e.g. copying font bitmaps or sprites to the frame buffer (works with the frame buffer in CPU / DMA addressable memory only, not through SPI).

Here's one example of a free library for smallish displays:
Most probably not suited for your needs, it uses a clever approach to run on low MCU ressources.

One interesting angle is that even a 256x256 px display, 24 bit colour, needs 196k of RAM, which is what the whole 32F4 has :)

One could do it using 8 bits/colour, perhaps which a colour mapping LUT in the display - assuming the system wasn't doing much else.

So even drawing a trivial clock, in 24 bit colour, is not feasible using a buffer in the CPU. Inside the CPU it all has to be "objects".

Fonts can also take up a lot of space, if you want nice ones (not the crappy 7x5 sort). Fortunately in my application there will be very little text.

One of the ST 32F4 development boards, the DISC1 Discovery Kit, came with an LCD of about 5x6cm but it was connected using a parallel interface. I can see a fossil entry in the linkfile of my project
MEMORY_B1 (rx)      : ORIGIN = 0x60000000
which is believed to relate to that. However that uses up a huge number of pins. But ST did supply some sort of library so you could do a printf() to the LCD.

This changes a bit depending on what sort of display you're driving and what you're trying to show. 

If the display has no internal frame buffer, then the frame buffer has to be in local memory, which is expensive in terms of memory consumption, but the tradeoff is that the local memory is fast to read as well as write, and in particular is a LOT faster in random access patterns (at least compared to the interfaces typical for these sorts of displays that DO have internal frame buffers).  That speed makes it a lot simpler to do more complicated graphics, because it's easier to update arbitrary sections of the screen.  Having internal memory also allows for DMA, and in fact some parts like the STM32F7/H7 family have DMA engines specifically designed to support graphical interfaces, to reduce CPU overhead.  If you have enough memory to double buffer, then there's a bonus advantage that you can swap the entire image at once and get nice crisp updates with no tearing.  However this sort of thing locks you into higher performance MCUs that can support the attendant memory and interface speed requirements (not to mention pin counts).

When the frame buffer is in the display, by contrast, you generally have a much more limited rate at which you can write any changes to the screen, because not only is the interface data rate lower but typically you have to transfer commands before writing or reading, which means that random accesses are particularly expensive.  This isn't such a problem for simple character/glyph/fill types of screens because those work well with writes to rectangular areas, which allows for more efficient bulk writes to the display.  Doing anything that involves partial transparency within a rectangular area, even something like a circle drawn on a background color, or text drawn over something other than a basic background color, is more complicated.  That sort of thing kind of needs a local frame buffer, which gets back to needing a bunch of memory.  Transferring the whole buffer to the display would still be expensive, especially on a particularly slow interface, and can result in visible tearing, so it can be beneficial to have all of the display module track the 'dirty' areas of the frame buffer and then only write those areas when it's time to update the display.  I've done this on OLED displays where the display only allows page-wide writes, the interface is slow, and the memory requirement is low, it's not that hard and has a significant performance improvement.  Below a certain display size the frame buffer can be a reasonable tradeoff for not having to keep track of what logical objects are supposed to be on the screen anyway, and it can greatly simplify the rest of the graphics software.

So you kind of have to do the math on how fast you need to be able to update the display and how long it will take to do various size writes to the display and see how that fits in your application requirements.  A full write to a 256x256x16bit display at 21Mbps ends up taking ~52ms (a little longer due to overhead, possible framing requirements, etc).  If that's acceptable, and you can spare 128kB for a frame buffer, then it would be pretty easy to do arbitrary redraws in the local buffer and just kick off a DMA transaction when you're ready.  If that's not fast enough, you can track the areas that are changed and only rewrite those.  If you don't have the memory for a frame buffer, then you're going to need to compensate with some extra computational complexity or simplify your graphics.

There are certainly some embedded graphics libraries out there, since your other threads have mentioned STM32 projects you might look at their libraries.  I don't know how good their graphics libraries in particular are, but if you're using an MCU that has their graphic-specific DMA engine you probably want to take advantage of that. 

I have done this kind of things both with my own libaries, and with third-party ones.
(I do admit that I have mostly used my own - and as with anything else, this is code built over the years and that can be reused. This is because I've had a rather strict policy of using the least third-party code possible. But that has evolved a bit.)

With that said, there are nice libraries these days for what you want to do, and if you have no existing code base and no time or will to write all this yourself, we can suggest a few options. One pretty nice option is LVGL: https://lvgl.io/ - I highly recommend it.


[0] Message Index

[#] Next page

There was an error while thanking
Go to full version