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

Status
Not open for further replies.

Frank B

Senior Member
Hi,

i'm working on a library that provides DMA for the ILI9341. It uses a full screen buffer (size:153600 Bytes).
A first minimal version is already working.
First tests show that full-screen refreshes with >50 Hz (@ 144MHz CPU) are possible, with overclocking the bus.

I plan to adopt some of the functions of the original ILI9341_t3 lib - so here is my Question:

Which functions of ILI9341_t3 do you need, and which are not needed anymore ?

50Hz is fast enough for video - which is the best way to feed the Teensy with videodata ? Which software on PC-side can be used ?

Edit:
Preview here:
https://github.com/FrankBoesing/ILI9341_t3DMA/blob/master/ILI9341_t3DMA.cpp
(but don't expect a flawlessly working version or documentation...)
 
Last edited:
Here is a very first test (CPU only 144MHz in this test.. will be much faster with 240Mhz) :
Code:
ILI9341 Test!
Display Power Mode: 0xEF
MADCTL Mode: 0x36
Pixel Format: 0x3
Image Format: 0x0
Self Diagnostic: 0xF0
Benchmark                Time (microseconds)
Screen fill              1947
Text                     0
Lines                    8289
Horiz/Vert Lines         2279
Rectangles (outline)     1449
Rectangles (filled)      22524
Circles (filled)         8550
Circles (outline)        3529
Triangles (outline)      2219
Triangles (filled)       18537
Rounded rects (outline)  2459
Rounded rects (filled)   37538
Done!
Text is not yet implemented.

Both libs, the ILI9341_t3 and the new one, ILI9341_t3DMA can be used concurrently. Of course, you must stop the dma-refresh to use the old ones.


There are some new functions:

Code:
    void refresh(void);    //starts continously refreshing the screen
    void stopRefresh(void);  //stops refreshing the screen
    void refreshOnce(void); //one single screen refresh
    void wait(void); //waits until current refresh is done

Edit: Test with 240MHz:
Code:
ILI9341 Test! Display Power Mode: 0xCE
 MADCTL Mode: 0x24
 Pixel Format: 0x2
 Image Format: 0x0
 Self Diagnostic: 0xE0
 Benchmark                Time (microseconds)
 Screen fill              1165
 Lines                    4966
 Horiz/Vert Lines         1366
 Rectangles (outline)     869
 Rectangles (filled)      13500
 Circles (filled)         5126
 Circles (outline)        2112
 Triangles (outline)      1328
 Triangles (filled)       11117
 Rounded rects (outline)  1473
 Rounded rects (filled)   22506
 Done!
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)

EDIT:
Test with original ILI93411_t3: (240MHz)
Code:
ILI9341 Test! Display Power Mode: 0xCE
 MADCTL Mode: 0x24
 Pixel Format: 0x2
 Image Format: 0x0
 Self Diagnostic: 0xE0
 Benchmark                Time (microseconds)
 Screen fill              112458
 Text                     6297
 Lines                    29230
 Horiz/Vert Lines         9205
 Rectangles (outline)     5844
 Rectangles (filled)      233934
 Circles (filled)         35479
 Circles (outline)        28396
 Triangles (outline)      7068
 Triangles (filled)       77386
 Rounded rects (outline)  12811
 Rounded rects (filled)   255385
 Done!
 
Last edited:
Hi Frank,

Looks interesting. Would be interesting to see the difference in speed here versus standard one.

Also assuming you are currently only doing for SPI and not SPI1 and SPI2? As I mentioned in beta threads, I have version that runs on the different SPI busses on both 3.5 and 3.6.
However you run into issues like:

SPI1/2 only have a queue of 1 item not 4.

You are limited on CS pins. On SPI1 there is only 1 CS configured (So made library work using hardware CS for DC but software for CS. If SDCard pins available then have 3 cs pins

More importantly for your case, DMA for SPI on T3.5, SPI1 and SPI2 have both source and target as the same DMA source... So not sure how well it will work. So for example DMASPI test cases fail on SPI1 and SPI2 on T3.5.
 
Hi Frank,

Looks interesting. Would be interesting to see the difference in speed here versus standard one.

Also assuming you are currently only doing for SPI and not SPI1 and SPI2? As I mentioned in beta threads, I have version that runs on the different SPI busses on both 3.5 and 3.6.
However you run into issues like:

SPI1/2 only have a queue of 1 item not 4.

You are limited on CS pins. On SPI1 there is only 1 CS configured (So made library work using hardware CS for DC but software for CS. If SDCard pins available then have 3 cs pins

More importantly for your case, DMA for SPI on T3.5, SPI1 and SPI2 have both source and target as the same DMA source... So not sure how well it will work. So for example DMASPI test cases fail on SPI1 and SPI2 on T3.5.

Hi Kurt,
the ili9341_t3 benchmarks are here in the forum. There is a large ILI9341-thread ... (sry, i have no link at the moment). The test is the same (ok, "text" isn't ready, yet)
It is not directly comparable, because the new library-functions (like dfillRect or ddrawLine) do not more than accessing the RAM - all DMA is done in the background, (almost) without cpu-usage.
The old lib had to write to the SPI and wait for transfers. I guess it is minimum 10..100 times slower.

If DMA works with SPI1 or SPI2 - would be great ! I need no fifo.
It will take some time until i can test it.

Edit:
first, i want make the text-outputs and some anti-aliasing or alphablending (which is way more easy now, due to the ram-buffer..)

Edit: i guess, the next teensy needs 1MB RAM.... this would allow really cool things..:cool:
 
Last edited:
Sounds great:

I did a quick run of the display on Beta 1 board using Standard ILI9341_t3 library and see:
Code:
ILI9341 Test!
Display Power Mode: 0xCE
MADCTL Mode: 0x24
Pixel Format: 0x2
Image Format: 0x0
Self Diagnostic: 0xE0
Benchmark                Time (microseconds)
Screen fill              224684
Text                     10682
Lines                    58320
Horiz/Vert Lines         18360
Rectangles (outline)     11671
Rectangles (filled)      467095
Circles (filled)         68159
Circles (outline)        52127
Triangles (outline)      14080
Triangles (filled)       153120
Rounded rects (outline)  24026
Rounded rects (filled)   509053
Done!

But I am guessing that the times are maybe Apples and Oranges? These times show times for the start of the test until end and made sure that all screen updates are done.
Warning I may be complete off here, but I would expect that if you did:
Code:
unsigned long testFillScreen() {
  unsigned long start = micros();
  tft.fillScreen(ILI9341_BLACK);
  tft.fillScreen(ILI9341_RED);
  tft.fillScreen(ILI9341_GREEN);
  tft.fillScreen(ILI9341_BLUE);
  tft.fillScreen(ILI9341_BLACK);
  return micros() - start;
}
And the goal was that you would see a full black screen and then a full red screen and... Then my gut tells me that the tests would take similar time, as I believe we are pretty much maxing out the SPI buss between the processor and the display? as you can see in the Logic Analyzer output:
screenshot.jpg
But of course the big gains will be that the CPU is not having to hang around while the screen update is happening. Also having the offscreen version will be great for doing some cool stuff.

Will be very interesting to see how this all plays out.

Great stuff
 
Yes, if you want to see the screen-fills you have to add a wait() or refreshOnce() for the screen-refresh. Then it will be almost equal to the max refresh-rate which depends on the SPI-speed.
You're right, as i said in post #4, the benchmarks are not directly compareable. But the additional speeds helps to do more screen-updates in less time (and, hopefully, with less flickering)
 
Here is a very first test (CPU only 144MHz in this test.. will be much faster with 240Mhz) :
Code:
ILI9341 Test!
Display Power Mode: 0xEF
MADCTL Mode: 0x36
Pixel Format: 0x3
Image Format: 0x0
Self Diagnostic: 0xF0
Benchmark                Time (microseconds)
Screen fill              1947
Text                     0
Lines                    8289
Horiz/Vert Lines         2279
Rectangles (outline)     1449
Rectangles (filled)      22524
Circles (filled)         8550
Circles (outline)        3529
Triangles (outline)      2219
Triangles (filled)       18537
Rounded rects (outline)  2459
Rounded rects (filled)   37538
Done!

[/code]

Hello Frank, is that the time the micro needed or the time to complete the entire task and be ready to do more with the screen?
 
Great stuff, but as you said in #7, you might call Screenfill 5 times, but probably at most the screen has actually drawn once. I think it is great stuff as the majority of the time you are not wanting to flash the screen or the like, but simply doing outputs.

But to give a better sense of this, it might be interesting if you actually gave two numbers here per test.
For example to do the Text output, I am sure it did not take 0 time for the stuff to actually show up on the screen.

So might be nice if you showed delta time for code to run to end of test, plus then do a wait or refresh to actually get the stuff on the screen and then show the delta time including that.
 
Great stuff, but as you said in #7, you might call Screenfill 5 times, but probably at most the screen has actually drawn once. I think it is great stuff as the majority of the time you are not wanting to flash the screen or the like, but simply doing outputs.

But to give a better sense of this, it might be interesting if you actually gave two numbers here per test.
For example to do the Text output, I am sure it did not take 0 time for the stuff to actually show up on the screen.

So might be nice if you showed delta time for code to run to end of test, plus then do a wait or refresh to actually get the stuff on the screen and then show the delta time including that.

Let me explain how it works..
There are two ways to refresh the display -

a) it refreshes automatically, as fast as possible (up to more than 50 Hz). This way is used in the tests above, because i wanted to use the same test sketch which is used by the original library. Indeed, the code is almost identical (I'll upload it). The same way is used in the "Play video" sketch https://forum.pjrc.com/threads/36751-Teensy-3-6-plays-video-from-SD-Card?p=114615 ("play video" uses this library)

b) you can all a function to refresh it one time (on request, with a) disabled ).

All drawing operations are done in Teensy-RAM, and are not visible until it's contens are copied to the screen (with a) or b) from above).
Usually, it makes no sense to call fillScreen and wait for the result. Instead, you want to fillScreen, and call many other functions afterwards - like write some text draw lines or copy an "icon" to the screen. You want to display it when this "page" is ready to display, not every single step. And exactly this is the point where it makes sense that your tasks get done as fast as possible :) You can wait after all operations are done. Waiting before makes no sense, cause you'll see only a part of the "page".

Then, a benchmarks which measures delays is not really good.. :) and pretty useless. The "Graphicstest"-sketch spends most of the time in delays between the tasks ( well, not on other Arduinos.. ;-) )

No, the textoutput was not ready.... i wrote that in post#2. That was the only "test" which did not work that time. Therfore the "0".
If you want a "delta"..well, use your own test from above, or an other lib and the "graphicstest" sketch.



To make it more clear , here is drawPixel():
Code:
DMAMEM uint16_t screen[ILI9341_TFTHEIGHT][ILI9341_TFTWIDTH];
.
.

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;
[TABLE="class: highlight tab-size js-file-line-container"]
[TR]
[/TR]
[TR]
[/TR]
[/TABLE]
}


p.s.
way c) is, how all other libraries work. Draw a single pixel and send it to the display. You can see how your "page" renders... you have much overhead to send x/y screen-coordinates (most extreme with a 45° line)
 
Last edited:
This is the sketch (textoutput works now):
Code:
/***************************************************
  This is our GFX example for the Adafruit ILI9341 Breakout and Shield
  ----> http://www.adafruit.com/products/1651

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/


#include "SPI.h"
#include <ILI9341_t3DMA.h>

#define TFT_DC      15
#define TFT_CS      10
#define TFT_RST     4  // 255 = unused, connect to 3.3V
#define TFT_MOSI    11
#define TFT_SCLK    13
#define TFT_MISO    12

ILI9341_t3DMA tft = ILI9341_t3DMA(TFT_CS, TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK, TFT_MISO);

void setup() {
  tft.begin();

  //START DMA MODE
  tft.refresh();
    
  tft.dfillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(2);
  tft.println("Waiting for Arduino Serial Monitor...");

  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  Serial.println("ILI9341 Test!"); 
/*
  // read diagnostics (optional but can help debug problems)
  uint8_t x = tft.readcommand8(ILI9341_RDMODE);
  Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDMADCTL);
  Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDPIXFMT);
  Serial.print("Pixel Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDIMGFMT);
  Serial.print("Image Format: 0x"); Serial.println(x, HEX);
  x = tft.readcommand8(ILI9341_RDSELFDIAG);
  Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); 
*/


  
  Serial.println(F("Benchmark                Time (microseconds)"));

  Serial.print(F("Screen fill              "));
  Serial.println(testFillScreen());
  delay(200);

  Serial.print(F("Text                     "));
  Serial.println(testText());
  delay(600);

  Serial.print(F("Lines                    "));
  Serial.println(testLines(ILI9341_CYAN));
  delay(200);

  Serial.print(F("Horiz/Vert Lines         "));
  Serial.println(testFastLines(ILI9341_RED, ILI9341_BLUE));
  delay(200);

  Serial.print(F("Rectangles (outline)     "));
  Serial.println(testRects(ILI9341_GREEN));
  delay(200);

  Serial.print(F("Rectangles (filled)      "));
  Serial.println(testFilledRects(ILI9341_YELLOW, ILI9341_MAGENTA));
  delay(200);

  Serial.print(F("Circles (filled)         "));
  Serial.println(testFilledCircles(10, ILI9341_MAGENTA));

  Serial.print(F("Circles (outline)        "));
  Serial.println(testCircles(10, ILI9341_WHITE));
  delay(200);

  Serial.print(F("Triangles (outline)      "));
  Serial.println(testTriangles());
  delay(200);

  Serial.print(F("Triangles (filled)       "));
  Serial.println(testFilledTriangles());
  delay(200);

  Serial.print(F("Rounded rects (outline)  "));
  Serial.println(testRoundRects());
  delay(200);

  Serial.print(F("Rounded rects (filled)   "));
  Serial.println(testFilledRoundRects());
  delay(200);

  Serial.println(F("Done!"));
  tft.stopRefresh();


}


void loop(void) {
    
 //Rotation does not work (only on init..) :-(
 /*
  for(uint8_t rotation=0; rotation<4; rotation++) {
    
    testText();
    tft.start();
    tft.fill();   
    tft.setRotation(rotation);  
    delay(1000);
  }
  */
}

unsigned long testFillScreen() {
  unsigned long start = micros();
  tft.dfillScreen(ILI9341_BLACK);
  tft.dfillScreen(ILI9341_RED);
  tft.dfillScreen(ILI9341_GREEN);
  tft.dfillScreen(ILI9341_BLUE);
  tft.dfillScreen(ILI9341_BLACK);
  return micros() - start;
}

unsigned long testText() {

  tft.dfillScreen(ILI9341_BLACK);
  unsigned long start = micros();
 
  tft.setCursor(0, 0);
  tft.setTextColor(ILI9341_WHITE);  tft.setTextSize(1);
  tft.println("Hello World!");
  tft.setTextColor(ILI9341_YELLOW); tft.setTextSize(2);
  tft.println(1234.56);
  tft.setTextColor(ILI9341_RED);    tft.setTextSize(3);
  tft.println(0xDEADBEEF, HEX);
  tft.println();
  tft.setTextColor(ILI9341_GREEN);
  tft.setTextSize(5);
  tft.println("Groop");
  tft.setTextSize(2);
  tft.println("I implore thee,");
  tft.setTextSize(1);
  tft.println("my foonting turlingdromes.");
  tft.println("And hooptiously drangle me");
  tft.println("with crinkly bindlewurdles,");
  tft.println("Or I will rend thee");
  tft.println("in the gobberwarts");
  tft.println("with my blurglecruncheon,");
  tft.println("see if I don't!");
 
  return micros() - start;
}

unsigned long testLines(uint16_t color) {
  unsigned long start, t;
  int           x1, y1, x2, y2,
                w = tft.width(),
                h = tft.height();

  tft.dfillScreen(ILI9341_BLACK);

  x1 = y1 = 0;
  y2    = h - 1;
  start = micros();
  for(x2=0; x2<w; x2+=6) tft.ddrawLine(x1, y1, x2, y2, color);
  x2    = w - 1;
  for(y2=0; y2<h; y2+=6) tft.ddrawLine(x1, y1, x2, y2, color);
  t     = micros() - start; // fillScreen doesn't count against timing

  tft.dfillScreen(ILI9341_BLACK);

  x1    = w - 1;
  y1    = 0;
  y2    = h - 1;
  start = micros();
  for(x2=0; x2<w; x2+=6) tft.ddrawLine(x1, y1, x2, y2, color);
  x2    = 0;
  for(y2=0; y2<h; y2+=6) tft.ddrawLine(x1, y1, x2, y2, color);
  t    += micros() - start;

  tft.dfillScreen(ILI9341_BLACK);

  x1    = 0;
  y1    = h - 1;
  y2    = 0;
  start = micros();
  for(x2=0; x2<w; x2+=6) tft.ddrawLine(x1, y1, x2, y2, color);
  x2    = w - 1;
  for(y2=0; y2<h; y2+=6) tft.ddrawLine(x1, y1, x2, y2, color);
  t    += micros() - start;

  tft.dfillScreen(ILI9341_BLACK);

  x1    = w - 1;
  y1    = h - 1;
  y2    = 0;
  start = micros();
  for(x2=0; x2<w; x2+=6) tft.ddrawLine(x1, y1, x2, y2, color);
  x2    = 0;
  for(y2=0; y2<h; y2+=6) tft.ddrawLine(x1, y1, x2, y2, color);

  return micros() - start;
}

unsigned long testFastLines(uint16_t color1, uint16_t color2) {
  unsigned long start;
  int           x, y, w = tft.width(), h = tft.height();

  tft.dfillScreen(ILI9341_BLACK);
  start = micros();
  for(y=0; y<h; y+=5) tft.ddrawFastHLine(0, y, w, color1);
  for(x=0; x<w; x+=5) tft.ddrawFastVLine(x, 0, h, color2);

  return micros() - start;
}

unsigned long testRects(uint16_t color) {
  unsigned long start;
  int           n, i, i2,
                cx = tft.width()  / 2,
                cy = tft.height() / 2;

  tft.dfillScreen(ILI9341_BLACK);
  n     = min(tft.width(), tft.height());
  start = micros();
  for(i=2; i<n; i+=6) {
    i2 = i / 2;
    tft.ddrawRect(cx-i2, cy-i2, i, i, color);
  }

  return micros() - start;
}

unsigned long testFilledRects(uint16_t color1, uint16_t color2) {
  unsigned long start, t = 0;
  int           n, i, i2,
                cx = tft.width()  / 2 - 1,
                cy = tft.height() / 2 - 1;

  tft.dfillScreen(ILI9341_BLACK);
  n = min(tft.width(), tft.height());
  for(i=n; i>0; i-=6) {
    i2    = i / 2;
    start = micros();
    tft.dfillRect(cx-i2, cy-i2, i, i, color1);
    t    += micros() - start;
    // Outlines are not included in timing results
    tft.ddrawRect(cx-i2, cy-i2, i, i, color2);
  }

  return t;
}

unsigned long testFilledCircles(uint8_t radius, uint16_t color) {
  unsigned long start;
  int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2;

  tft.dfillScreen(ILI9341_BLACK);
  start = micros();
  for(x=radius; x<w; x+=r2) {
    for(y=radius; y<h; y+=r2) {
      tft.dfillCircle(x, y, radius, color);
    }
  }

  return micros() - start;
}

unsigned long testCircles(uint8_t radius, uint16_t color) {
  unsigned long start;
  int           x, y, r2 = radius * 2,
                w = tft.width()  + radius,
                h = tft.height() + radius;

  // Screen is not cleared for this one -- this is
  // intentional and does not affect the reported time.
  start = micros();
  for(x=0; x<w; x+=r2) {
    for(y=0; y<h; y+=r2) {
      tft.ddrawCircle(x, y, radius, color);
    }
  }

  return micros() - start;
}

unsigned long testTriangles() {
  unsigned long start;
  int           n, i, cx = tft.width()  / 2 - 1,
                      cy = tft.height() / 2 - 1;

  tft.dfillScreen(ILI9341_BLACK);
  n     = min(cx, cy);
  start = micros();
  for(i=0; i<n; i+=5) {
    tft.ddrawTriangle(
      cx    , cy - i, // peak
      cx - i, cy + i, // bottom left
      cx + i, cy + i, // bottom right
      tft.color565(0, 0, i));
  }

  return micros() - start;
}

unsigned long testFilledTriangles() {
  unsigned long start, t = 0;
  int           i, cx = tft.width()  / 2 - 1,
                   cy = tft.height() / 2 - 1;

  tft.dfillScreen(ILI9341_BLACK);
  start = micros();
  for(i=min(cx,cy); i>10; i-=5) {
    start = micros();
    tft.dfillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
      tft.color565(0, i, i));
    t += micros() - start;
    tft.ddrawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i,
      tft.color565(i, i, 0));
  }

  return t;
}

unsigned long testRoundRects() {
  unsigned long start;
  int           w, i, i2,
                cx = tft.width()  / 2 - 1,
                cy = tft.height() / 2 - 1;

  tft.dfillScreen(ILI9341_BLACK);
  w     = min(tft.width(), tft.height());
  start = micros();
  for(i=0; i<w; i+=6) {
    i2 = i / 2;
    tft.ddrawRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(i, 0, 0));
  }

  return micros() - start;
}

unsigned long testFilledRoundRects() {
  unsigned long start;
  int           i, i2,
                cx = tft.width()  / 2 - 1,
                cy = tft.height() / 2 - 1;

  tft.dfillScreen(ILI9341_BLACK);
  start = micros();
  for(i=min(tft.width(), tft.height()); i>20; i-=6) {
    i2 = i / 2;
    tft.dfillRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(0, i, 0));
  }

  return micros() - start;
}
There only minor differences is upon initialization, and the drawing functions are prepended with a "d".

I think i'll change this and don't use the original ILI9341_t3 lib - too many drawbacks. Have to think about it...
 
Last edited:
The reason I ask about timing is I would like to have more then 1 screen option for the CC Dummy Load project, right now my only easily viable screen is the 2x20 and 4x20 screens. And I would be forced to break up a screen refresh into 10 character segments to avoid colliding with the ADC loop (limitation is the screen not the micro).

As it stands for me based on my coding skills, the only way I see using any graphical LCD's is if I use a second micro to do all the LCD controls and just send data to it via serial. Using a second micro adds cost and complexity. Which if the screen capabilities are nice enough may be worth it.

Im currently just trying to figure out what options are available so that I can place any needed circuits on the board. I would like to give anyone making the Dummy Load the option to choose what kind of screen they want. It just comes down to what is feasible based on space/connections.
 
Using the text-output on the ili9341 is as easy as printing via serial.. :)
Just take a look at the library-examples..
 
Hm, i can't promise that touch will work with my DMA-Lib.. :)
I did not try it.. but it's likely that the ILI-touch-library needs an update..
 
Hm, i can't promise that touch will work with my DMA-Lib.. :)
I did not try it.. but it's likely that the ILI-touch-library needs an update..

I can use the normal ADC library, I actually had it working with the T3.1 when I was playing with them for my EgoCart Project. It was not insanely accurate but it got the job done. Speaking of the gocart, just got some parts from Digi so back to the shed for me.
 
Hi Frank,

Thought I would mention, that now that the Kickstarter/Beta is over, I have taken a little time playing with my UP board. In particular I have an ILI9341 display connected up to its "RPI Connector" and I had earlier converted the Adafruit code to work on Linux using MRAA (earlier for Edison), which uses spidev to do SPI at user mode. Note: I could take the simple route and use the FBTFT device driver, but was sort of curious how well there user level stuff is working.

Got it sort of working now on the UP and while it works, the speed is not exactly spectacular...
Code:
ILI9341 Test!
Display Power Mode: 0x9C
MADCTL Mode: 0x48
Pixel Format: 0x5
Image Format: 0x9C
Self Diagnostic: 0xC0
Benchmark                Time (microseconds)
Screen fill              991839
Text                     680243
Text 2                   186166
Lines                    13200669
Horiz/Vert Lines         144368
Rectangles (outline)     137679
Rectangles (filled)      2065566
Circles (filled)         2922998
Circles (outline)        5957815
Triangles (outline)      4217531
Triangles (filled)       2110649
Rounded rects (outline)  1840688
Rounded rects (filled)   3128059
Why I mention this, is I remember you going to the frame buffer like setup here (although you are using DMA), but wondered how much it would help in my case. So I made a version of the code that output everything to a 320x240x2 byte array and currently I am doing an explicit call of update to say put it to the screen. Again this can go so far speed wise as the SPIDEV appears to have a max transfer size of 4K, so I am updating the display with 3840 byte writes (40 calls for full screen). Again timings are not spectacular, and would expect fill screen might be slower as doing the work to fill memory first and then more or less same calls to SPI... But some of the others are showing pretty good speed ups...

Code:
ILI9341 Test!
Display Power Mode: 0x9C
MADCTL Mode: 0x48
Pixel Format: 0x5
Image Format: 0x9C
Self Diagnostic: 0x0
Benchmark                Time (microseconds)
Screen fill              995694
Text                     199784
Text 2                   204201
Lines                    199464
Horiz/Vert Lines         203455
Rectangles (outline)     199383
Rectangles (filled)      5087
Circles (filled)         203179
Circles (outline)        199966
Triangles (outline)      202983
Triangles (filled)       2386
Rounded rects (outline)  203553
Rounded rects (filled)   210488
Done!
Also code still in place for the 4 different orientations...
Some of the slower timings like Text is because: I am redrawing the whole screen, if I play much more, may keep a dirty range and only update those portions that need updating....

Or could use the real hardware driver ;)
 
Hi Frank, that is sort of a hard question to answer. Right now I have it outputting the bytes at 8mhz or 8.33... The UP hardware can go to 25mhz...

BUT! - The user level to system level SPI driver is a killer! Maybe easier to demonstrate by showing a Logic Analyzer output:

screenshot.jpg
As you can see, there is a BIG gap between the output of each call to SPIDEV, the good news is once it gets there, there are no gaps between bytes (unlike Edison), so for the fill screen, I can get the 4K of bytes output pretty fast, which I will put in. Currently doing it as even divide of screen size which takes 40 calls with 3840, but at output of 4096 bytes, can get down to 38...

Also I am having to drive CS/DC using software calls, which adds to these delays as well. But again it is for my own learning, to decide how I wish for UP to talk to Teensy and who does what. More details on my playing with the different things like GPIO, I2C... up on the thread:
https://up-community.org/forum/general-discussion/876-mraa-and-the-up-board
 
Thanks for sharing this awesome work! I've been watching it but I just now got the opportunity to try it out. I have very long wire lengths so I was not really planning on trying it, but I couldn't stop wondering if it would work. So yesterday I opened up my enclosure of my 3.2-based project where I've basically just completed the hardware and starting to write software. I made the necessary modifications to get my new 3.6 in there optionally (i.e. insert either thanks to the pin-compatibility). Long story short, it works beautifully! I haven't gotten to much aside from altering the example to my hardware as a proof of concept, but the performance is pretty astonishing.

As for your initial question to people using the library - based on my extremely limited experience so far I think the biggest thing I would personally need is to have the rotation function. Not dynamically though like the previous library; I'm not even really sure if that's possible here. But doing it once before allocating the RAM seems possible, correct?
 
Rotation is a bit complicated.. but i guess a Single rotation for setup is OK? That would be doable.

On the 3.6, the lib is fast enough to play video :)
 
Rotation is a bit complicated.. but i guess a Single rotation for setup is OK? That would be doable.

On the 3.6, the lib is fast enough to play video :)
Hi Frank,

I think I have rotation working on my Linux version I was playing with yesterday and it did not add that much complication.
If you interested in looking at it, it is in my RPI github project: https://github.com/KurtE/Raspberry_Pi/tree/master/Adafruit_ILI9341B

I am not doing DMA, but the stuff I think would work the same. I simply keep the "Frame Buffer" as a single dimensional array. Mine is a member variable:
Code:
 // Define Frame buffer
  uint8_t _fbtft[320*240*2]; // Define a memory location big enough to hold full screen image
.

My drawPixel looks like:
Code:
void Adafruit_ILI9341::drawPixel(int16_t x, int16_t y, uint16_t color) {

  if((x < 0) ||(x >= _width) || (y < 0) || (y >= _height)) return;

  // Lets start off by simply addressing memory
  register uint8_t *pfbtft_pixel = &_fbtft[(y*_width + x) * 2];
  *pfbtft_pixel++  = (color>>8) & 0xff;
  *pfbtft_pixel = color & 0xff;
}

Note: I suspect this may add a slight more overhead as I am now multiplying by a member variable versus the two dimensional array case, where you are multiplying by a constant

And the setRotation looks like:
Code:
void Adafruit_ILI9341::setRotation(uint8_t m) {

  spi_begin();
  writecommand(ILI9341_MADCTL);
  rotation = m % 4; // can't be higher than 3
  switch (rotation) {
   case 0:
     writedata(MADCTL_MX | MADCTL_BGR);
     _width  = ILI9341_TFTWIDTH;
     _height = ILI9341_TFTHEIGHT;
     break;
   case 1:
     writedata(MADCTL_MV | MADCTL_BGR);
     _width  = ILI9341_TFTHEIGHT;
     _height = ILI9341_TFTWIDTH;
     break;
  case 2:
    writedata(MADCTL_MY | MADCTL_BGR);
     _width  = ILI9341_TFTWIDTH;
     _height = ILI9341_TFTHEIGHT;
    break;
   case 3:
     writedata(MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR);
     _width  = ILI9341_TFTHEIGHT;
     _height = ILI9341_TFTWIDTH;
     break;
  }
  spi_end();
}

Again I am explicitly doing the SPI calls to write the data out (SPIDEV) versus using DMA... Actually to be honest, it may be using DMA under me...
 
Last edited:
Status
Not open for further replies.
Back
Top