Jump to content

SDRAM, pixmaps and oscilloscope


king2

Recommended Posts

Hi! 

I'm trying to implement oscilloscope widget on STM32F429 with LTDC screen 800x480 and SDRAM attached to it. I'm using ChibiOS as RTOS.

I have tried to just draw traces on screen, but with relatively high refresh rate (~25fps) it start to flicker, because each time I draw changes on screen, I need to fill area with black, draw border, ticks and, finally, traces, so I can see my widget in different stages of this process (which looks like flickering).

I have tried to use pixmaps, but pixmap for my oscilloscope area (709x150) requires ~212k of RAM, which is definitely does not fit to MCU SRAM. I have tried different things and found:

  1. Using pixmaps really helps against flicker
  2. Using pixmaps slows down refresh rate for 5 or even 10 times
  3. Using pixmaps with HEAP located on SDRAM slows down refresh rate for 10 to 30 times

Slowing down things SO MUCH with pixmaps was really unexpected for me. Maybe I forgot to turn some defines or something like this?

Pixmap init uses gfxAlloc, so I can use or heap on SDRAM, or does not use pixmaps at all (in my case). Maybe there is a way to use pixmaps on SDRAM while keeping system heap in SRAM?

Here is part of code (init):

gw->pixmap = gdispPixmapCreate(gw->w.g.width, gw->w.g.height);
gw->surface = gdispPixmapGetBits(gs->pixmap);

Usage:

    // draw fresh field for everything
    gdispGFillArea(gs->pixmap, 0, 0, gw->g.width, gw->g.height, gw->pstyle->background); // clear the area
    gdispGDrawBox(gs->pixmap, 0, 0, gw->g.width, gw->g.height, (gw->g.flags & GWIN_FLG_SYSENABLED) ? gw->pstyle->enabled.edge : gw->pstyle->disabled.edge); // draw border
    for (unsigned short x = gw->g.x+SCOPE_PIXELS_PER_TICK; x < gw->g.x+gs->size_x; x += SCOPE_PIXELS_PER_TICK) {
        gdispGDrawLine(gs->pixmap, 1, gw->g.height-6, 1, gw->g.height-2, Gray);
    }

      ... loop for traces
      
          gdispGDrawLine(gs->pixmap, x, prev_pixel, x+1, y, scope_colors[trace]);

      ... end of loop
      
    gdispBlitArea(gw->g.x, gw->g.y, gw->g.width, gw->g.height, gs->surface);

Can you give me advice?

Thanks!

Link to comment
Share on other sites

There are much easier and faster ways to draw the traces. Have a look at the oscilloscope demo in the repository. It demonstrates doing an oscilloscope using a really slow processor and even demonstrates a mechanism to do software triggering. From memory there are two versions, one in the Gaudio demos and one in the gadc demos.

Link to comment
Share on other sites

Those demos uses redraw each time when new pixel was arrived, I receive data at frequency of 100Hz.

As I do not know previous state of display, I cannot delete old trace, because:

  • I use lines, not points
  • pixel data can be shifted by more than one or two pixels

My problem is:

  • when using pixmaps, it is slow
  • when using direct draw, it is flickers, because draws executing immediately and wi=rites directly to display buffer 

But maybe, i do not understand something. 

Where is a main difference between my approach and approach of gaudio demo?

As I understand, I use 'area clear' when gaudio uses pixel-by-pixel deletion of old trace, and I do not know what is faster (because area fill should be inclredibly fast using DMA2D).

Link to comment
Share on other sites

Dma2d is not significantly faster than using the CPU with a well written block routine. It's advantage is that it releases the CPU to prepare for the next operation thus reducing latency between operations.

In your case, with a 800x480 display on a f4 you are approaching the limits of the sdram bandwidth and therefore the number of pixels you need to write is definitely a consideration that will affect performance. Writing over a trace will definitely be much faster than block filling and will provide a better visual appearance.

If you really want to do the block write there is a method that will be much better than using a pixmap but will require a custom extension to the ltdc driver. The method is to use the ltdc's ability to change the base sdram address. Using this you could create 2 display pages and swap between the pages, drawing on one while displaying the other. You still need 2 framebuffers of ram but it doesn't require the bandwidth sucking framebuffer copy that using pixmap requires. Ram bandwidth will still be a limiting factor but just not as limiting as with your current strategy.

With ram bandwidth make sure that you are not using the display sdram chips for any other purpose even if there is spare ram available as the extra CPU access to the ram will contend with display refresh and drawing bandwidth.

I hope that helps in finding a solution that will work for your situation.

Link to comment
Share on other sites

Thank you for clarification!

I like idea with double buffering, but how can I control moment when I should rotate framebuffers?

OK, I have primary framebuffer 1 and shadow framebuffer 2. I write to FB2, then switch it to be main. But I cannot write to ex-primary buffer, because it contains not current info, I should copy all 800x480 pixels from new primary to new shadow (ex-primary) buffer, and then I can draw something on it, isn't it?

Or I will have two buffers each filled with half of my screen changes (because not only my Scope widget draws on screen, I have also other button, labels and other widgets which updated from another threads).

Have you an example of such double buffering and some best practices how to do it with uGFX?

Link to comment
Share on other sites

Double buffering is complex and how to do it is invariably application specific.

In your case I would do what you are doing now - just drawing traces to alternative buffers.

Don't try copying from one buffer to the other, you would have lost all the benefits of using 2 pages. Instead draw each frame from scratch or just wipe the old traces (by block fill or redrawing the trace). Yes buttons will be a problem but if your swap speed is fast enough it may not be noticeable.

Another hint that may help is to use the 2nd ltdc layer to display the outer area with the buttons and then double page the centre background with the traces. Note this will however increase ram bandwidth requirements. This is probably the approach I would use provided the bandwidth numbers match.

Another hint is to use a lower colour depth. Using RGB565 will halve the bandwidth compared to RGB888. The ltdc driver already support both - just change the gdidp_lld_config.h for the ltdc controller.

Link to comment
Share on other sites

I have complicated interface with imagebuttons, audio levels, memory settings, labels with live data from another devices and so on, and scope inside all this. If I will change framebuffers, I can get sutiation when uGFX from another thread changed text of label, and this change will be applied to current framerbuffer only, so I will get two framebuffers with different text, and will get label's text changing on screen 25 or 50 times per second, which is definitely not right :)

Or I should copy data from current framebuffer to shadow one before I will write something new to shadow framebuffer, which is slow, as you mentioned.

I'm already using single layer with RGB565 (turning 2nd layer causes flicker, RGB888 - then same), and I still near bandwidth limits. I have AT070TN92 display, and it should be configured for 60Hz refresh rate). Having USB limits me to 168MHz clock, and SDRAM works at 84MHz, this slows down thing a little bit more.

I copied gdisp_lld_STM32LTDC.c to another place and modified it  - removed replaced LayerOff with driverCfg.fglayer, filled up second layer definition with my data (707x145 pixels with L8).

As uGFX cannot use different color schemes for two displays, my scope widget just uses framebuffer as piece of memory - draws points or lines by itself.

So I have normal widget in main screen, that draws container box and ticks only once - when widget appears on screen. It is not important what will be inside - it covers with second (foreground) layer, where scope traces will appear.

I use two framebuffers for scope located at different internal banks of SDRAM, and switch between them each second frame, so I got almost stable picture of graphs without flickering and similar effects. Also I have to disable second layer when switching pages and turn it back when returning to main page. 

All this was done by price of lowering LCD refresh rate to 1/2 of its minimal rate. It works, but looks not so cool than at normal rate.

At this point I found that ChibiOS SDRAM example uses CAS3 and HCLK/3 as clock. I have reviewed all SDRAM parameters, changed clock to HCLK/2, and got everything working without framerate flickering.

Buy I have a question: does uGFX draws everything immediately or it can just remember 'draw tasks' and do it later? I have an idea: increase vertical porch of  LTDC to free up time where I can access to SDRAM without any interference with display, and call actual drawing in this time (in interrupt, for example)?

Thanks!

Link to comment
Share on other sites

2 hours ago, Joel Bodenmann said:

with no external RAM

This is a reason of smooth displaying. This display have its internal framebuffer, not loading MCU's bus for pixel reading (and as I understand, can prioritize pixel reads for itself).

This is good reason for me to not use LTDC + external RAM next time :)

Link to comment
Share on other sites

  • 3 weeks later...

Many years ago we had a stored write command style interface. We dropped it very quickly however for two reasons; 1. Noone was using it, and 2. It consumed lots of RAM and could quickly become unbounded in resource use and the complexity of controlling that got onorous.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...