Animating screen transitions on ILI9341 with Teensy 3.2

Status
Not open for further replies.

Ein

Member
I have been working on a prop over the last few weeks with a working screen - specifically one of the 2.4" ILI9341s without touch. I have been using the ILI9341_t3 library thusfar and been very happy with things. Teensy 3.2 has been the perfect little brain for this device, and I've spent a bit of time figuring out how I want the layout of a few 'pages' of information on the device to display.

I'm going to use the 'start' screen on the device as an example:



The screen starts as the dark blue background color, then these graphics are drawn overtop sequentially. I draw the main outer 'border', the glow at the left and right edges, and the scan lines running across the image. Then I redraw certain areas with the background color again, to overwrite the scanlines, and add my flash memory bitmaps and other data overtop. I use a structure called 'graphic' just to help me draw the necessary background fields with padding and other things I found useful.

Code:
struct graphic{int x;  int y;  int w;  int h;  int pad;};

This is the meat of the drawing bits for that visual:

Code:
void UI::drawLAPDStartGraphics(){
  //  We've set up the general UI, now to draw the extras.
  graphic LAPD = {0,100,184,58,7}; // Didn't set initial X position; will calculate that based on graphic width.
  LAPD.x = (screenWidth-LAPD.w)/2;
  graphic bgRect = {(screenWidth-LAPD.w)/2-LAPD.pad, LAPD.y-LAPD.pad, LAPD.w+LAPD.pad*2, LAPD.h+LAPD.pad*2, 0};
  graphic subText = {0, bgRect.y+bgRect.h, 24, 11, 7}; // Didn't set initial X position; will calculate that based on graphic width.
  subText.x = screenWidth/2-subText.w/2;
  graphic vkLogo = {0,277,160,16,6}; // Didn't set initial X position; will calculate that based on graphic width.
  vkLogo.x = screenWidth/2-vkLogo.w/2;
  graphic vkRect = {vkLogo.x-vkLogo.pad, vkLogo.y-vkLogo.pad/2, vkLogo.w+vkLogo.pad*2, vkLogo.h+vkLogo.pad, 0};
  //  Draw background colors to cover scan line effect in certain areas.
  tft->fillRect(bgRect.x, bgRect.y, bgRect.w, bgRect.h, BACKGROUND);
  tft->fillRect(subText.x-subText.pad, subText.y-subText.pad/2, subText.w+subText.pad*2, subText.h+subText.pad, BACKGROUND); //  vertical padding on this will only be half the horizontal, for aesthetics.
  tft->fillRect(vkRect.x, vkRect.y, vkRect.w, vkRect.h, BACKGROUND);         //  vertical padding on this will only be half the horizontal, for aesthetics.
  //  Draw grid lines behind LAPD
  drawVGridLines(bgRect.x, bgRect.y, bgRect.w, bgRect.h, 20, DARKBLUEUI);
  drawHGridLines(bgRect.x, bgRect.y, bgRect.w, bgRect.h, 8, DARKBLUEUI);
  //  Draw grid lines behind VK Logo
  drawVGridLines(vkRect.x, vkRect.y, vkRect.w, vkRect.h, 18, DARKBLUEUI);
  drawHGridLines(vkRect.x, vkRect.y, vkRect.w, vkRect.h, 2, DARKBLUEUI);
  //  Draw all necessary images - LAPD logo, Chinese subtext, VK logo at footer
  tft->drawBitmap(subText.x, subText.y+1, iconLAPDChinese, subText.w, subText.h, LIGHTBLUEUI);
  tft->drawBitmap(vkLogo.x, vkLogo.y, vk_logo, vkLogo.w, vkLogo.h, LIGHTBLUEUI);
  tft->drawBitmap((screenWidth-LAPD.w)/2,LAPD.y, lapdLogo, LAPD.w, LAPD.h, LIGHTBLUEUI);
}

All of this works fine. When I advance to the next screen, I have been simply doing a fillScreen(); and re-drawing the background color onto the display, then drawing the next visual elements I need accordingly.

I'm now at the point where, rather than simply flip between the pages, I want to add a bit more of an elaborate visual transition. I am... not feeling particularly smart here.

Here's an example of what I would like to try for this start screen:



And here is where I start running into problems. My first thought was "just use two fill rectangles, one on the top half, one on the bottom... then redraw the image, and draw the fill rectangles again, sequentially moving them up to the top and bottom edge respectively." This is obviously not a workable solution, because the end result is something that flashes and strobes the whole screen with the full graphic and then the over-written one.

It is at this point my digging through the internet starts to suggest maybe I'm looking for some kind of frame buffer, so I can draw everything virtually and only update the changes. I have no idea how to go about doing this, and would really appreciate guidance. I looked through the "DemoSauce" example, which seemed to include screen wipes and other transitional animations, but it is a magnitude beyond what I can understand at my level of coding and there's not much in the way of commenting for me to follow to try and slog through it.

The other approach, which I'm still trying to figure out, would probably be to dim the backlight all the way off, draw the visuals onto the screen, then use the readRect() function to try and capture the screen data into single-pixel-height, full-screen-width arrays, then fillScreen() my background color back on, turn the backlight on, and use the writeRect() function to sequentially draw the graphics back onto the screen from the middle out. This feels like it would be enormously inefficient, assuming I can get it working right? And having to dim the backlight to hide it doesn't seem effective for every page transition I'd want to do.

Is there a better/simpler/smarter way to approach this that doesn't involve me totally rebuilding the UI drawing functions I've already come up with? It kinda feels like I'm looking for a way to have the 'end goal' screen data in memory work my way towards it, which sounds like the buffering approach, but I don't know how to implement that.
 
I've spent a few hours of studying KurtE's forked ILI9341_t3n library and trying to merge it into my local ILI9341_t3 library, discarding the SPIN components in the process. I actually got it working, including the clipping functionality to restrict drawing to a certain region. This allowed me to animate the expansion-from-the-middle I wanted by repeatedly drawing the whole page image and expanding the clipping region. However, it flickers pretty badly during the drawing process - something a buffer would fix! ... But the tft.useFrameBuffer() function doesn't seem to do anything. I think, after reviewing Kurt's post history a little bit further, the Teensy 3.2 lacks the available memory to actually store the buffer data I need for a 240x320 display:

Also code could be added to detect a bounding rectangle of what was updated in the frame buffer since the last update and only output that portion.

Also needless to say when you turn on the FB it eats up a reasonable amount of memory 320*240*2 bytes. So only on T3.5/3.6

My best guess as to why the buffer is just... not doing anything is that the memory allocation for it fails? But I would assume there would be substantially worse consequences if that were the case. Not sure. Either way, Kurt's comment above seems to suggest I'm SOL, so I'm looking for other ideas for how to approach this issue.

I've thrown everything I'm working with up here on GitHub for review, if anyone has any guidance.
 
Yes - You need something like a T3.6 to be able to use the frame buffer code as it is.

As I mentioned recently in the main ili9341_t3n thread (, I was at times thinking it would be interesting to build a version that the backing frame was not using 16 bit colors, but maybe instead 8 bit or even 4 bit. Where the actual colors where stored in a color table and then you could use the writeRect8. or 4 to actually update the display. Should be able to work, but I don't know if I could get the DMA updates working with it or not... Sounded like @anthonysavatar maybe had some of this working?
 
Yes - You need something like a T3.6 to be able to use the frame buffer code as it is.

As I mentioned recently in the main ili9341_t3n thread (, I was at times thinking it would be interesting to build a version that the backing frame was not using 16 bit colors, but maybe instead 8 bit or even 4 bit. Where the actual colors where stored in a color table and then you could use the writeRect8. or 4 to actually update the display. Should be able to work, but I don't know if I could get the DMA updates working with it or not... Sounded like @anthonysavatar maybe had some of this working?

Well, the draw clipping functionality is still super useful, so there's that.

I'd simply switch to the bigger 3.5 or 3.6 Teensy, but I don't have room in the device for it, so the 3.2 was a good compromise for what I needed. Is there any other smart way of tackling this problem? I guess I could try and rewrite the way the screen populates from scratch to draw it from the middle-out.
 
It occurred to me that I was making this way harder for myself than I had to!

The draw clipping functionality by itself was enough to get the job done.

Code:
  int lineHeight = 10;
  for(int i = 0; i<=ui.screenHeight/2; i = i+lineHeight){
    //draw upper
    tft.setClipRect(0,ui.screenHeight/2-i, ui.screenWidth, lineHeight);
    ui.startLAPDScreen();
    //draw lower
    tft.setClipRect(0,ui.screenHeight/2+i, ui.screenWidth, lineHeight);
    ui.startLAPDScreen();
  }

I was struggling with the flickering before I realized there was no reason to be redrawing over the existing areas at all when I could control the drawing boundaries. Instead, I just have to do two diverging clipping regions, and fill in only those sections as I progress through the loop.

Still owe you a big thanks for the clipping code on that, @KurtE.
 
Status
Not open for further replies.
Back
Top