ILI9341 with fullscreen DMA Buffer for Teensy 3.5 / Teensy 3.6 only

Status
Not open for further replies.
I'm assuming based on your comments about alpha-blending and antialiased fonts that this is planned to support the higher res fonts that Paul added a while back for the ili9341_t3 library, or even the google fonts? I don't mean to make it sound like I'm making any demands on your generosity, I'm just curious because I'm pretty excited about the possibilities this library presents!

As reference, my usage of this library for my current project is probably entirely unique to me, so I'm trying to only mention issues that will pertain to the standard usage you intend. However, I'll go ahead and mention my project because I think people might get a kick out of what I'm doing. My project actually uses 11 2.2" ILI9341-based displays. They all share the entire SPI bus (including chip select) using 4-16 decoder chips (and an analog mux for MISO) - meaning I can declare one object. Declaring one object was initially out of convenience when I was based on the 3.2 and the ILI9341_t3 library. Obviously now with your library it's the only way it could hope to work seeing as there is only enough RAM for 1 frame buffer.

Anyway, I'm playing around with filling the buffer, then updating each screen individually. First, I tried filling the buffer, then sending that data out to each screen. That worked great after a lot of tinkering between defining the start and stop refresh periods. refreshOnce() has never worked in this context but I assume it has to do with my weird usage. Using refreshOnce() always distorts the colors and often the position of data on the screen. I only mention this here out of curiosity since this is such a weird usage.

Next, I tried filling the buffer with different data before refreshing each screen since that's much more like my actual use-case when I get past tinkering. Again this worked great with very little kinks to work out.

Here's my loop of a test program for context:

Code:
void loop(void) {

  //toggle between two sets of different information on screen
  for (ind=0; ind<11; ind+=1) {
    setMuxChannel(ind);
    switch (ind) {
      case 0: testFillScreen(); break;
      case 1: testText(); break;
      case 2: testLines(ILI9341_CYAN); break;
      case 3: testFastLines(ILI9341_RED, ILI9341_BLUE); break;
      case 4: testRects(ILI9341_GREEN); break;
      case 5: testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA); break;
      case 6: 
        testFilledCircles(10, ILI9341_MAGENTA);
        testCircles(10, ILI9341_WHITE);
        break;
      case 7: testRoundRects(); break;
      case 8: testFilledRoundRects(); break;
      case 9: testFillScreen(); break;
      case 10: testText(); break;
      case 11: testLines(ILI9341_CYAN); break;
    }
    tft.start();
    tft.fill();
    delayMicroseconds(2);//required to avoid white screens
    tft.stopRefresh();
  }   
    
  for (ind=0; ind<11; ind+=1) {
    setMuxChannel(ind);
    switch (ind) {
      case 0: testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA); break;
      case 1: 
        testFilledCircles(10, ILI9341_MAGENTA);
        testCircles(10, ILI9341_WHITE);
        break;
      case 2: testRoundRects(); break;
      case 3: testFilledRoundRects(); break;
      case 4: testFillScreen(); break;
      case 5: testText(); break;
      case 6: testLines(ILI9341_CYAN); break;
      case 7: testFillScreen(); break;
      case 8: testText(); break;
      case 9: testLines(ILI9341_CYAN); break;
      case 10: testFastLines(ILI9341_RED, ILI9341_BLUE); break;
      case 11: testRects(ILI9341_GREEN); break;      
    }
    tft.start();
    tft.fill();
    delayMicroseconds(2);//required to avoid white screens
    tft.stopRefresh();
  }
}

Everything is pretty self-explanatory so I didn't include the entire program. setMuxChannel() just switches the "channel" on the MUX setup I'm using to connect a single screen to the SPI bus. It seems like this would be the perfect time to use refreshOnce() but it either corrupts the color/position data or it actually makes the screens go white periodically - never to come back to life until a power cycle. Even with the code above you'll see where I had to use a delay to avoid a screen occasionally going white.

With this library and my usage, I'm able to get a combined 20 - 22 Hz (overall) refresh rate, or in the neighborhood of 2 Hz for each screen. 22Hz when flushing the same data to all screens, and more like 20 Hz when filling each screen with unique data like in the snippet above. It's well below the theoretical max of a divided ~50 Hz, but I think it's pretty good for what I'm doing with it. Again I just want to mention how excited I am about this awesome library. :D

Forgot to mention I edited the example functions to all be void. Will help when reading :).
 
Last edited:
Glad that the lib works for you :)
It's very preliminary - i want to make some heavy changes to it, like removing the underlying ili9341_t3 (but it will stay more or less compatible) .
The TTF fonts and most other features will work with the new version.

It will take some weeks, because i'm on a other project at the moment.
But i'll accept pull-request if someone wants to help..
 
Last edited:
This is great!

Only problem is I need text going the other axis, how big of a challenge is fixing rotation, or perhaps I could wip up a rotated text hack? It would be easy enough for me to just swap the x/y for all my other graphic stuff which would be the same as rotation. If you think rotation will be fixed in an update soon I might just wait, but the performance gains are really appealing!

I'm making a synth that has an oscilloscope view, this would be great for a phosphor emulation, right now I have to draw a black line over the last frame and then draw the new line every frame to get a high fps. Also the benefit of saving CPU is huge.
 
Last edited:
Here is a very first test (CPU only 144MHz in this test.. will be much faster with 240Mhz) :
Automatic screen refresh was enabled during these tests.


There are ony still-pictures... this is because, the test runs way too fast. For example 1.1 milliseconds for screen-fill - test ... (which is internally 5 screen-fills)

Not trying to hijack your thread, but I'm wondering how you connected the Teensy to the display. Is the purple test board hidden behind the display or are you using some other means? I probably have additional questions, but I'll see what you have to say first.
 
It will take some weeks, because i'm on a other project at the moment.
But i'll accept pull-request if someone wants to help..

I would love to contribute but so far I haven't been able to figure out anything helpful, hah. I'm nearly a beginner - one of the quintessential MIDI controller builders. I probably shouldn't have started with rotation though based on your comments...

This is great!

Only problem is I need text going the other axis, how big of a challenge is fixing rotation, or perhaps I could wip up a rotated text hack? It would be easy enough for me to just swap the x/y for all my other graphic stuff which would be the same as rotation.

I'm in the same boat, the rest of my screen is fine no matter the rotation. However, using this library for everything but the text and using the base t_3 library for text really slows things down and brings back that flicker. It also has some quirks but they can be avoided. If you do figure out a solution for the text then please share it!
 
Last edited:
Draconis, one of my own custom Boards, intended for the 3.2 (thats why you see the 3.6... that time, i had no Board for the 3.6)
I use the FlexiBoard now.
 
FlexiBoard?

Basically I'm trying to find out what components are needed to connect one of these things to the 3.6. On Paul's test board there appear to be some power related components, but then in his video demonstrating his optimized library their doesn't appear to be a connection to the barrel connector. In your video I only see a cable for powering the 3.6. I could be missing something in either case. As I said elsewhere I'm a CS, not a hardware guy, so when it comes to hardware I pretty much need somebody to show me what is needed to make connections that work and don't destroy equipment. I can generally understand a schematic after it is designed, but I can't design it myself.
 
@Drac - the Teensy 3.2 and above can provide the power as needed - the PJRC Product web page has the needed connections. Just needs USB power and the wires indicated there.

The PJRC Purple board can provide power - but doesn't have to be fully complete to work (resistor to display power, the 10K pullups to the CS lines, I jumpered power across one missing cap(?)). Or just wires - or in Frank's case - one of his own PCB's that replaces that. There is a FlexiBoard thread if you want to see that.
 
I wire mine on the 3.6 just like you would with a 3.2. I even use the 3.3v, which is under the 3.6v the ili9341 is spec'd for but is safe and stable. The only component is a resistor for the backlight.
http://imgur.com/a/mkFKh This is wired for the alternate pins listed to be used when also using the audio shield.

I'm about to put in some time on a rotated text hack, should be done in a couple hours?

edit: I think I'm going to have to turn in my mouse and keyboard and give up programming. I've spent a bunch of time trying to figure out where ili9341's println is coming from, I've found write inside adafruit_gfx but I've searched all the included files and stumped on where println is or where any functions are that draw more than a single character. The only function I've found that even accepts char arrays as a parm is getTextBounds. I've previously hacked up the adafruit_gfx to do essentially this same thing for a persistance of vision project so I had some confidence going in, and now I want to give up C++. Or switch out of this damned arduino IDE to one that would allow me to easily trace the function to its origin file.

edit: works
http://pastebin.com/dmEctbf6
 
Last edited:
@defragster, @ohnoitsaninja Thanks for your responses. Finally the light has dawned. Not sure why I got hung up on that test board and overlooked the connection table.

The Flexiboard looks like just the ticket to me, but it would never survive my non-existent soldering skills. Yes, I've watched the videos. My father is a retired electronics engineer, so he's tried teaching me many times over the years, but in ~40 years of trying I still suck. I can sit down at a workbench with the nice heat controlled iron a tech was just using and I can't get the dang thing to melt solder, much less make a connection! That stuff HATES me. I managed to solder headers on my 3.6, apparently without killing anything, and consider that a minor miracle.

@ohno Is this what you're looking for? hardware/teensy/avr/cores/teensy3/Print.h
 
Hi Frank,

I thought I would take a quick look at the code to see how hard it would be to adapt it to handle the different orientations of the screen. As I mentioned earlier I adapted some of this stuff (minus DMA) to a version of my code for Linux (my RPI project). Some of the differences I did was to have the screen array part of the object, such that in theory could have multiple displays. But probably on Teensy, not likely to have multiple of frame buffers of the size plus the number of dmasettings...

Also I am novice at using DMA, so forgive any obvious questions.

In my code, I have frame buffer defined like: uint8_t _fbtft[320*240*2]; // Define a memory location big enough to hold full screen image.
I will probably modify in my own code to use the defines instead of hardcoded 320 and 240. Currently I have defined as bytes as to avoid the byte order issue, but maybe better to define as uint16_t as you have done.

In your code, the first thing I don't understand is, the define: DMAMEM uint16_t screen[ILI9341_TFTHEIGHT+22][ILI9341_TFTWIDTH];
What is the 22?

My assumption looking through your code is each DMA transfer can do a max of 65536 bytes, so you figure out how many DMA transfers you need to do for the screen (3)

Again I am trying to understand sequence of how things work with the DMA. From what I am gathering here, what the refresh code does, is it first draws the screen once, using normal code, which, sets up the rectangle for the draw (whole screen), and then starts a write operation and then draws all of the screen. You then start up DMA operations, which if I am understanding correctly, the timing of the Frames is simply how fast the DMA can output the data for a screen? Was looking for somethings like an interval timer, but instead you link the DMA transfers, such that the last one points back to the first one.

Code:
void ILI9341_t3DMA::refresh(void) {
  if (!started) {
	start();
	fill();//TODO : Why is fill() needed ?
	started = 1;

  }
  dmasettings[SCREEN_DMA_NUM_SETTINGS - 1].TCD->CSR &= ~DMA_TCD_CSR_DREQ; //disable "disableOnCompletion"
  SPI0_RSER |= SPI_RSER_TFFF_DIRS |	 SPI_RSER_TFFF_RE;	 // Set DMA Interrupt Request Select and Enable register
  SPI0_MCR &= ~SPI_MCR_HALT;  //Start transfers.
  dmatx.enable();
  rstop = 0;
  autorefresh = 1;
}
Guess for why you needed fill. DC was probably still asserted after writecommand_cont(ILI9341_RAMWR);

So assuming I understand this correctly, suppose I change the screen definition to:
DMAMEM uint16_t screen[ILI9341_TFTHEIGHT * ILI9341_TFTWIDTH]; // And maybe 22?

Is there any reason you need to transfer whole rows or columns per dma transfer. Or can we simply transfer: 65536, 65536, 22538 bytes
If this woould work, then I just would then need to change all of the functions that touch screen from :
Code:
void ILI9341_t3DMA::ddrawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return;
  screen[y][x] = color;
  //screen16[y*_height+x*_width*2] = color;
}
to
Code:
void ILI9341_t3DMA::ddrawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return;
      screen16[y*_width + x] = color;
}

Should I try it?

Thanks
Kurt
 
Yes, try it :) PLEASE :)
I've no time at the moment :-(

Yes, the Chipselects remain asserted, maybe, that's a bug..
But for autorefresh, of course, it is intentional.
Autorefresh is not compatible eith other SPI Devices, and must be disabled before accessing other SPI devices.

The +22 :
That's a very dirty trick.. it's there to a) have some memory for audio (video example) and b) to be able to read large blocks without having to split video+audiodata.
LOL, yes, it is dirty, but this lib is "PRE_ALPHA" :) so, please excuse..
The +22 are for the video-example only.


Maybe there are other problems, too..

@all : I'd be happy if you make it better and perhaps do github-"Pullrequests".. i'll merge them.
 
Last edited:
Here is my hack for a working rotated text with transparent background, rotated the same as setRotation(3), horizontal with pins on the left, as it should be imho. It doesn't support extra fonts or have some of the error checking of the default lib but it should be kinda fast. Would be faster by replacing ddrawPixel with screen[][] but I reused it for it's error checking.

Code:
// ILI9341_t3DMA.h additions
 
    void ddrawRotChar(unsigned char c);
    void ddrawRotText(const  char* c);

Code:
void ILI9341_t3DMA::ddrawRotText(const char* c){
	while (*c != '\0'){
      ddrawRotChar(*c++);
      cursor_x -= 6;
    }
}

void ILI9341_t3DMA::ddrawRotChar(unsigned char c) {
  uint_fast8_t j = 0;
  uint_fast16_t c5 = c * 5;
  uint16_t *color;
  if (textcolor != textbgcolor) {
    while (j < 5) {
      uint_fast8_t mask = 0x01, i = 7;
      while (i--) {
        ddrawPixel(cursor_y - i, cursor_x - j, (glcdfont[c5 + j] & mask) ? textcolor : textbgcolor);
        mask = mask << 1;
      }
      j++;
    }
  }
  else {
    while (j < 5) {
      uint_fast8_t mask = 0x01, i = 7;
      while (i--) {
        if (glcdfont[c5 + j] & mask) {
          ddrawPixel(cursor_y - i, cursor_x - j, textcolor);
        }
        mask = mask << 1;
      }
      j++;
    }
  }
}

I'm using ddrawpixel as it reads each pixel of the character, perhaps it would be better to wait until all the characters are written to screen[][] and then do a dma-bitblt of just that screen[][] rect? I know nothing of the technicals with DMA/SPI, but what I lack in knowledge I make up for in relentless benchmarking. Also shouldn't screen be a single dimension for speed?

I'm new to github and how collab on it works, and I also don't feel I'm coding up to the same standards as the stuff in the libs I see, but I'd be glad to help how I can however you think is productive towards a polished ili9341DMA lib. Ultimately I feel like my code should be trashed and replaced by the real rotation code, which seems beyond my scope for fixing.

I bashed my head into the my screen for a while trying to figure out how to get the length of char pointer for ddrawRotText() before figuring out to check for '\0', C++ is sometimes so esoteric and unintuitive. One might think it possible to be able to use strln or sizeof, but nopee, makes sense now though, the pointer contains no information other than there is a character at that address.

edit: I've updated it a bit and added support for both transparent and solid backgrounds while preserving the speed boost of using transparent bg.

I've always wanted to compress the blank space between characters, so while I was here I went and threw that in.

edit3: Fixed up even more, forked and pull requested my first github project
TZW8Qug.jpg

Wow this pic showed up way too big on the forums. Anyways it shows rotated text through the buffer, color and transparent backgrounds, and optional collapsed white space and shorted space characters.
 
Last edited:
Yes, try it :) PLEASE :)
I've no time at the moment :-(

Yes, the Chipselects remain asserted, maybe, that's a bug..
But for autorefresh, of course, it is intentional.
Autorefresh is not compatible eith other SPI Devices, and must be disabled before accessing other SPI devices.

The +22 :
That's a very dirty trick.. it's there to a) have some memory for audio (video example) and b) to be able to read large blocks without having to split video+audiodata.
LOL, yes, it is dirty, but this lib is "PRE_ALPHA" :) so, please excuse..
The +22 are for the video-example only.
Maybe there are other problems, too..
Hi Frank,

I have versions of the functions (#ifdef) that do all of the updates into the screen16 array using computed indexes. This part now appears to be working.

So I thought I would try to enable the loop code that draws the text screen in the 4 different directions. However the system hangs.
Code looks like:
Code:
void loop(void) {
  for(uint8_t rotation=0; rotation<4; rotation++) {
    Serial.printf("Try Rotation: %d\n\r", rotation);
    tft.setRotation(rotation);
    Serial.println("before testText");
    testText();
    Serial.println("before refreshOnce");
    tft.refreshOnce();
    delay(1000);
  }
}
Obviously when working will remove a the Serial.print... stuff. So far I have tracked it back into refreshOnce, which then calls refresh.

First problem, the (!started). The started flag is only set in this function and never cleared. So the startTransaction code will not be called again, nor the setup of the rectangle... So first attempt fix was to clear started at the end of the stopRefresh.

Tried again, added more debug statements and it is now hanging in start in the call to setAddr.

My gut tells me, that it is probably the interaction between using the SPI using the default fifo queue and using it for DMA. My guess is that once you called dmatx.enable(), there are problems with any other SPI outputs...

Not sure the proper way to switch back? maybe a call to dmatx.disable() ?

I was wondering have you used refreshOnce?

Will keep playing.

Update:
- Looks like calling the dmatx.disable at the end of stopRefresh fixed it :D
 
Last edited:
FYI - I pushed my stuff up on github, under a new fork and branch. I then issed a Pull Request. So you can take a look at it and see what you think.

Kurt
 
I could never get refreshOnce() to work fully. It wouldn't cause my program to hang but it somehow caused color and position corruption of the frame buffer's data. However, note my unusual usage in my long post above. Either way, I wrote a simple refreshOnce() derivative in my sketch that works. I'm going from memory but I think it came from this portion of my code linked above:

Code:
    tft.start();
    tft.fill();
    delayMicroseconds(2);//required to avoid white screens
    tft.stopRefresh();

I think my hardware is what requires the delay, so I wouldn't expect that to be generally needed.
 
Here is my hack for a working rotated text with transparent background, rotated the same as setRotation(3), horizontal with pins on the left, as it should be imho. It doesn't support extra fonts or have some of the error checking of the default lib but it should be kinda fast. Would be faster by replacing ddrawPixel with screen[][] but I reused it for it's error checking.

Code:
// ILI9341_t3DMA.h additions
 
    void ddrawRotChar(unsigned char c);
    void ddrawRotText(const  char* c);

Code:
void ILI9341_t3DMA::ddrawRotText(const char* c){
	while (*c != '\0'){
      ddrawRotChar(*c++);
      cursor_x -= 6;
    }
}

void ILI9341_t3DMA::ddrawRotChar(unsigned char c) {
  uint_fast8_t j = 0;
  uint_fast16_t c5 = c * 5;
  uint16_t *color;
  if (textcolor != textbgcolor) {
    while (j < 5) {
      uint_fast8_t mask = 0x01, i = 7;
      while (i--) {
        ddrawPixel(cursor_y - i, cursor_x - j, (glcdfont[c5 + j] & mask) ? textcolor : textbgcolor);
        mask = mask << 1;
      }
      j++;
    }
  }
  else {
    while (j < 5) {
      uint_fast8_t mask = 0x01, i = 7;
      while (i--) {
        if (glcdfont[c5 + j] & mask) {
          ddrawPixel(cursor_y - i, cursor_x - j, textcolor);
        }
        mask = mask << 1;
      }
      j++;
    }
  }
}

I'm using ddrawpixel as it reads each pixel of the character, perhaps it would be better to wait until all the characters are written to screen[][] and then do a dma-bitblt of just that screen[][] rect? I know nothing of the technicals with DMA/SPI, but what I lack in knowledge I make up for in relentless benchmarking. Also shouldn't screen be a single dimension for speed?

I'm new to github and how collab on it works, and I also don't feel I'm coding up to the same standards as the stuff in the libs I see, but I'd be glad to help how I can however you think is productive towards a polished ili9341DMA lib. Ultimately I feel like my code should be trashed and replaced by the real rotation code, which seems beyond my scope for fixing.

I bashed my head into the my screen for a while trying to figure out how to get the length of char pointer for ddrawRotText() before figuring out to check for '\0', C++ is sometimes so esoteric and unintuitive. One might think it possible to be able to use strln or sizeof, but nopee, makes sense now though, the pointer contains no information other than there is a character at that address.

edit: I've updated it a bit and added support for both transparent and solid backgrounds while preserving the speed boost of using transparent bg.

I've always wanted to compress the blank space between characters, so while I was here I went and threw that in.

edit3: Fixed up even more, forked and pull requested my first github project
TZW8Qug.jpg

Wow this pic showed up way too big on the forums. Anyways it shows rotated text through the buffer, color and transparent backgrounds, and optional collapsed white space and shorted space characters.

I've tried to start this a few times but I can't seem to figure it out. Do you have any suggestions on how to accomplish a similar rotation(3) simulating hack that would use Frank's converted fonts from here: https://github.com/FrankBoesing/fonts/tree/master/ofl
 
Do you have any suggestions on how to accomplish a similar rotation(3) simulating hack that would use Frank's converted fonts[/URL]

We're just going to have to wait for actual rotation(3) support. I ran into so many issues trying to convert the rest of my project via X/Y flipping I haven't used the text rotation hack or DMA lib in the project I'm working on.
 
I've tried to start this a few times but I can't seem to figure it out. Do you have any suggestions on how to accomplish a similar rotation(3) simulating hack that would use Frank's converted fonts from here: https://github.com/FrankBoesing/fonts/tree/master/ofl
Hi sandalhat,

I think there are a few issues here,

1) Is that the current code is hard coded that the display is hard coded for a specific width and height (DMAMEM uint16_t screen[ILI9341_TFTHEIGHT+22][ILI9341_TFTWIDTH];)
disregard the +22 fudge factor, which Frank commented on earlier (Audio stuff for demo program)

Earlier I did a potential Pull request, that instead of using the screen array used the Screen16 single dimensional array which is defined to be at the same memory location. The code then used the current _width and _height to compute the index, so at least in my playing attempt, I had the rotating font screens working at the end of graphic test...

2) I believe you are trying to use a font to output your stuff and I don't believe Frank has implemented a version of drawFontChar and the underlying subroutines to use his screen buffer.

Not sure what to suggest here. I decided for my own stuff, not to use the DMA, but really liked the idea of optional frame buffer, so I did it in my own code... But I took a different approach, instead of duplicating all of the draw methods to add a D to the name, like drawRect ->ddrawRect, I set a mode flag and updated each of the low level graphic primitives to know if they are going to the screen or to frame buffer...
 
As i said at the beginning, i'll change the architecture of this lib.
Ill remove the underlying ili9341_t3.h completely.
All fonts will be supported, with rotation.

My plan is to finalize my flexiboard 2 ( with some new features) first, i hope, this weekend.
Next week, i work on this lib.
 
Thanks .)
The buffer opens some new possibilities - reading from it is as fast as writing and, maybe, i can add scrolling in all directions.
Maybe, in the future, we can add "Sprites" like on C64 (uh, the c64 emu is one of my stalled projects - i continue working on this, soon), or, with images stored in the flash, some nice alphablending-effects.
We have two layers, the buffer, and DMA for the hardware, so maybe it is possible to use other displays, too..
Let's see, what the future brings..
 
Last edited:
Teensy 3.5 debugging attempt

Hi all,

I gave the library a try on my T3.5, as I do not own a T3.6.

I was not able to get it to work with refresh().
A single refreshOnce() call does the job, but a second call seems to throw an exception... I suppose (?)
Damn, debuging with only serial output is a real pain.

I was in the process of checking DMA registers addresses and content when my wife called me to bed yesterday... so it did not lead to anything yet, other than bed.
Does anyone have another idea of which direction I should look into ? ?

I mean, from what I saw, registers of MK64FX512 and MK66FX1MO seem to be the same... so it's maybe not that... but I don't know what else to check, without a proper debugger.
 
Status
Not open for further replies.
Back
Top