Forum Rule: Always post complete source code & details to reproduce any issue!
Page 2 of 2 FirstFirst 1 2
Results 26 to 36 of 36

Thread: Yet another highly optimzed ILI9341 library: for T4/4.1 with diff updates and vsync !

  1. #26
    Member
    Join Date
    Oct 2016
    Location
    Paris
    Posts
    37
    Quote Originally Posted by KurtE View Post
    If you are curious I thought I would show the differences in Logic Analyzer differences of running your library with your 99luft... example as the logic analyzer screen on top and running with my code in ILI9341_t3n, but again with software run DC...

    Your code is showing a reasonable progression of numbers, Mine so far is not...
    They both output the 0x45 have about a 3us gap then output two 0s... The one difference I see is yours is holding the clock line high between the 0x45 and the first 0 output... They both are asking for CONT bit. But maybe difference for DC... Still playing.
    Attachment 22705
    Isn't you code also holding the DC pin low during the 3us wait ?
    Maybe that is the problem as the 0x45 command is only registered once the dc pin goes high again and that is when the wait should begin no ?

  2. #27
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    9,567
    Quote Originally Posted by vindar View Post
    Isn't you code also holding the DC pin low during the 3us wait ?
    Maybe that is the problem as the 0x45 command is only registered once the dc pin goes high again and that is when the wait should begin no ?
    Thanks @vindar,

    Actually if you look at both analyzers in the picture, the top is from your 99... sketch and it also has DC..

    But: the main issue was I was shooting myself in the foot. As I mentioned in the OV7670 thread, the test board with the camera has all of the pins wired for the display and I did not set the CS pin for the touch screen high, so it was screwing around with the MISO pin... I found my code was working on test sketch on the breadboard that I setup with your wiring and code. Also worked when I moved DC to a none CS pin, and then I wired up another breadboard with the SPI pins I was using on OV... board and it worked... Then the Oops throught came to mind about the CS pins... Should have put on PD resistors on all of the CS pins...

    Yesterday in a fork of the ILI9341_t3n library I added simple:
    Code:
    void ILI9341_t3n::setFrameRateControl(uint8_t mode) {
      // Do simple version
      beginSPITransaction(_SPI_CLOCK);
      writecommand_cont(ILI9341_FRMCTR1);
      writedata8_cont((mode >> 4) & 0x3); // Output DIVA setting (0-3)
      writedata8_cont(0x10 + (mode & 0xf)); // Output RTNA
      endSPITransaction();
    }
    The OV7670 CSI code sketch I added similar functions like you have in your library to measure the frame rate:
    Code:
    void sampleRefreshRate()
    {
    #ifdef USE_ILI9488
      Serial.println("sampleRefreshRate: only supported in ILI9341_t3n");
    #else    
      Serial.printf("sampleRefreshRate start");
      static const uint32_t NB_SAMPLE_FRAMES = 10;
      static const uint32_t MAX_SAMPLE_LOOP_COUNT = 10000; 
      elapsedMicros em;
      uint32_t sum = 0;
      uint32_t loop_count = 0;
      uint32_t count_frame_samples = 0;
      for (count_frame_samples = 0; count_frame_samples < NB_SAMPLE_FRAMES; count_frame_samples++)
      {
        delayMicroseconds(5000); // must be less than 200 FPS so wait at least 5ms
        loop_count=0;
        while ((tft.readScanLine() != 0) && (++loop_count < MAX_SAMPLE_LOOP_COUNT)); // wait to reach scanline 0      
        if (loop_count >= MAX_SAMPLE_LOOP_COUNT) break;
    
        loop_count=0;
        while ((tft.readScanLine() != 1) && (++loop_count < MAX_SAMPLE_LOOP_COUNT)); // wait to reach scanline 1      
        if (loop_count >= MAX_SAMPLE_LOOP_COUNT) break;
    
        em = 0; // start counter at begining of scanline 1
        delayMicroseconds(5000); // must be less than 200 FPS so wait at least 5ms
        loop_count=0;
        while ((tft.readScanLine() != 0) && (++loop_count < MAX_SAMPLE_LOOP_COUNT)); // wait to reach scanline 0      
        if (loop_count >= MAX_SAMPLE_LOOP_COUNT) break;
    
        loop_count=0;
        while ((tft.readScanLine() != 1) && (++loop_count < MAX_SAMPLE_LOOP_COUNT)); // wait to reach scanline 1      
        if (loop_count >= MAX_SAMPLE_LOOP_COUNT) break;
        sum += em; // stop counter and add to current sum.
      }
    
      if (loop_count >= MAX_SAMPLE_LOOP_COUNT) {
        Serial.printf("sampleRefreshRate: loop_index:%u\n", count_frame_samples );
    
      } else {
        uint32_t _period = (uint32_t)round(((double)sum) / NB_SAMPLE_FRAMES);
        Serial.printf("sum:%lu count:%lu period:%lu frames per second:%u\n", sum, NB_SAMPLE_FRAMES, _period,
            (uint32_t)(1000000l / _period));
      }
    #endif 
    }
    I did add timeouts and the like as if something goes wrong like CS pins screw up, won't have the code completely hang.

    Then added simple F <number> command in the Serial input check code, that called off to:

    Code:
    void set_frame_rate_control() {
      #ifdef USE_ILI9488
        Serial.println("frame rate control: only supported in ILI9341_t3n");
      #else    
      uint8_t diva_rtna = Serial.parseInt();
      Serial.printf("Setting Frame Rate control to %x\n", diva_rtna);
      tft.setFrameRateControl(diva_rtna);
    
      // get frame rate info.
      sampleRefreshRate();
    
      #endif
    }
    The thing I notice is the image on the screen changes when I change the Frame Rate register.. Like less detail more grain? ... Back to playing

  3. #28
    Member
    Join Date
    Oct 2016
    Location
    Paris
    Posts
    37
    Quote Originally Posted by KurtE View Post
    Thanks @vindar,

    Actually if you look at both analyzers in the picture, the top is from your 99... sketch and it also has DC..

    But: the main issue was I was shooting myself in the foot. As I mentioned in the OV7670 thread, the test board with the camera has all of the pins wired for the display and I did not set the CS pin for the touch screen high, so it was screwing around with the MISO pin... I found my code was working on test sketch on the breadboard that I setup with your wiring and code. Also worked when I moved DC to a none CS pin, and then I wired up another breadboard with the SPI pins I was using on OV... board and it worked... Then the Oops throught came to mind about the CS pins... Should have put on PD resistors on all of the CS pins...

    Yesterday in a fork of the ILI9341_t3n library I added simple:
    Code:
    void ILI9341_t3n::setFrameRateControl(uint8_t mode) {
      // Do simple version
      beginSPITransaction(_SPI_CLOCK);
      writecommand_cont(ILI9341_FRMCTR1);
      writedata8_cont((mode >> 4) & 0x3); // Output DIVA setting (0-3)
      writedata8_cont(0x10 + (mode & 0xf)); // Output RTNA
      endSPITransaction();
    }
    The OV7670 CSI code sketch I added similar functions like you have in your library to measure the frame rate:
    Code:
    void sampleRefreshRate()
    {
    #ifdef USE_ILI9488
      Serial.println("sampleRefreshRate: only supported in ILI9341_t3n");
    #else    
      Serial.printf("sampleRefreshRate start");
      static const uint32_t NB_SAMPLE_FRAMES = 10;
      static const uint32_t MAX_SAMPLE_LOOP_COUNT = 10000; 
      elapsedMicros em;
      uint32_t sum = 0;
      uint32_t loop_count = 0;
      uint32_t count_frame_samples = 0;
      for (count_frame_samples = 0; count_frame_samples < NB_SAMPLE_FRAMES; count_frame_samples++)
      {
        delayMicroseconds(5000); // must be less than 200 FPS so wait at least 5ms
        loop_count=0;
        while ((tft.readScanLine() != 0) && (++loop_count < MAX_SAMPLE_LOOP_COUNT)); // wait to reach scanline 0      
        if (loop_count >= MAX_SAMPLE_LOOP_COUNT) break;
    
        loop_count=0;
        while ((tft.readScanLine() != 1) && (++loop_count < MAX_SAMPLE_LOOP_COUNT)); // wait to reach scanline 1      
        if (loop_count >= MAX_SAMPLE_LOOP_COUNT) break;
    
        em = 0; // start counter at begining of scanline 1
        delayMicroseconds(5000); // must be less than 200 FPS so wait at least 5ms
        loop_count=0;
        while ((tft.readScanLine() != 0) && (++loop_count < MAX_SAMPLE_LOOP_COUNT)); // wait to reach scanline 0      
        if (loop_count >= MAX_SAMPLE_LOOP_COUNT) break;
    
        loop_count=0;
        while ((tft.readScanLine() != 1) && (++loop_count < MAX_SAMPLE_LOOP_COUNT)); // wait to reach scanline 1      
        if (loop_count >= MAX_SAMPLE_LOOP_COUNT) break;
        sum += em; // stop counter and add to current sum.
      }
    
      if (loop_count >= MAX_SAMPLE_LOOP_COUNT) {
        Serial.printf("sampleRefreshRate: loop_index:%u\n", count_frame_samples );
    
      } else {
        uint32_t _period = (uint32_t)round(((double)sum) / NB_SAMPLE_FRAMES);
        Serial.printf("sum:%lu count:%lu period:%lu frames per second:%u\n", sum, NB_SAMPLE_FRAMES, _period,
            (uint32_t)(1000000l / _period));
      }
    #endif 
    }
    I did add timeouts and the like as if something goes wrong like CS pins screw up, won't have the code completely hang.

    Then added simple F <number> command in the Serial input check code, that called off to:

    Code:
    void set_frame_rate_control() {
      #ifdef USE_ILI9488
        Serial.println("frame rate control: only supported in ILI9341_t3n");
      #else    
      uint8_t diva_rtna = Serial.parseInt();
      Serial.printf("Setting Frame Rate control to %x\n", diva_rtna);
      tft.setFrameRateControl(diva_rtna);
    
      // get frame rate info.
      sampleRefreshRate();
    
      #endif
    }
    The thing I notice is the image on the screen changes when I change the Frame Rate register.. Like less detail more grain? ... Back to playing
    Ahah, I completely sympathize. I made the exact same mistake last week and spent 4 hours pulling my hair and wondering why my screen was behaving erratically. I had just moved from a breadboard to a pcb where the touchscreen shares the spi bus with the screen and had left the cs touch pin floating in my code :-)

    As for changing the frame rate. I agree it changes the aspect somewhat but I cannot really tell if it is better or worse. I guess it depends.

    Maybe for a camera with a noisy image, having a faster framerate and and no syncing may amount, somehow, to performing some temporal averaging, similar as adding a random blur that smoothes the image a bit ?

  4. #29
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    9,567
    The stuff I am trying to understand with tearing and knowing the scan line, is in cases like you wish to update the whole display, how much does it help?

    That is: if my quick elapsedMicros output around a full screen writeRect takes about:
    WriteRect time: 41051 so little over 41ms so you can get about: 24 frames per second at 30mhz.
    And running at 40mhz it looks like: 30809 so about 32 frames per second...
    50mhz 25687 or almost 39
    60... 20571 or 48.6...

    Which actually worked...

    The question is if the time to refresh the frame is < time to output new frame, the output will overlap the scanline? So does it buy anything visually to break up the writeRect itnto multiple pieces that don't collide with the scan line? Again I am no expert here, just wondering how that works. In both cases you will see frames with partial data updated.

  5. #30
    Member
    Join Date
    Oct 2016
    Location
    Paris
    Posts
    37
    Quote Originally Posted by KurtE View Post
    The stuff I am trying to understand with tearing and knowing the scan line, is in cases like you wish to update the whole display, how much does it help?

    That is: if my quick elapsedMicros output around a full screen writeRect takes about:
    WriteRect time: 41051 so little over 41ms so you can get about: 24 frames per second at 30mhz.
    And running at 40mhz it looks like: 30809 so about 32 frames per second...
    50mhz 25687 or almost 39
    60... 20571 or 48.6...

    Which actually worked...

    The question is if the time to refresh the frame is < time to output new frame, the output will overlap the scanline? So does it buy anything visually to break up the writeRect into multiple pieces that don't collide with the scan line? Again I am no expert here, just wondering how that works. In both cases you will see frames with partial data updated.
    Hi KurtE

    I think the main point to notice is that you do not need the duration of a frame upload to be smaller than a refresh period but only to be smaller than (a little less than) 2 times the refresh period, which is much better !

    Indeed, imagine that you start drawing just behind the scanline when it starts displaying a frame (so the scanline is a line 0). And imagine also that the scanline is moving 50% faster than your upload does, then when the screen refresh terminate (i.e. when the scanline reached 320), you will have only drawn 2/3 of the screen and the scanline will immediately start again at 0 displaying the new frame but that is OK because you will finish your upload when the scanline will be around the middle of the screen. So, for this refresh, the scanline will only 'see' the new frame! Then, you just need to wait until the scanline goes back to 0 and repeat the operation, uploading a new frame behind the scanline while it displays your previously uploaded frame a second time... With this stategy, there is never any screen tear since each refresh displays exactly one frame (and each frame uploaded is displayed twice). This is the purpose of my setVsyncSpacing() method which locks the framerate to be an integer divide of the refresh rate (but there is not advantage to setting it to any value larger than 2).

    Sorry, I think I am doing a bad job at explaining this. My point is that, if you can upload full frame at more than 45 FPS (say 48FPS to be sure), then you can get tear free display with a refresh rate of 90Hz and obtain a stable framerate of 45FPS...

    To illustrate this, you can try the code below but first you will need to pull the latest version of my library on github as I made some breaking changes recently. In this code, the whole framebuffer is redrawn every time as it completely changes color. Setting SPI at 60Mhz gives a frame upload speed around 48 fps which is enough to get a stable 45FPS without screen tear when setting a refresh rate around 90Hz. You can try it and then compare to what happens when you set tft.setVSyncSpacing(0) in the code (this disables vsync).

    Better yet, if you screen can manage it, set the SPI at 80Mhz and use tft.setRefreshRate(120) and you will get 'tear free' full frames at 60FPS, almost like a real computer screen :-)
    Of course, setting the SPI that high may not be safe (I do not know really) and that is one reason I wanted to add differential updates to the library in order to increase the upload rate whenever possible.

    Code:
    #include "ILI9341Driver.h"
    
    #define PIN_SCK			27
    #define PIN_MISO		1
    #define PIN_MOSI		26
    #define PIN_DC			0
    #define PIN_RESET		29
    #define PIN_CS			30
    #define PIN_BACKLIGHT   28  // 255 if not connected to MCU. 
    #define PIN_TOUCH_IRQ	32  // 255 if not used (or not on the same spi bus)
    #define PIN_TOUCH_CS	31  // 255 if not used (or not on the same spi bus)
    
    
    #define SPI_SPEED		60000000
    
    
    // the screen driver object
    ILI9341_T4::ILI9341Driver tft(PIN_CS, PIN_DC, PIN_SCK, PIN_MOSI, PIN_MISO, PIN_RESET, PIN_TOUCH_CS, PIN_TOUCH_IRQ);
    
    
    uint16_t fb[320*240];     // memory framebuffer.
    
    
    void setup()
        {
        while (!Serial);
    
        if (!tft.begin(SPI_SPEED)) Serial.println("Oops! I did it again..");
    
        pinMode(PIN_BACKLIGHT, OUTPUT);
        digitalWrite(PIN_BACKLIGHT, HIGH);
    
        tft.setRotation(0); // portrait mode 240 x320 is the fastest mode!
        tft.setRefreshRate(90); // can be set to 120 with 80Mhz SPI
        tft.setVSyncSpacing(2); // framerate = refreshrate/2. Setting this to 0 disables vsync.
        }
    
    
    int nbf = 0; // counts the number of frames drawn. 
    
    const uint16_t RED = 31 << 11;
    const uint16_t BLUE = 31;
    
    void loop()
        {
        // draw the framebuffer full blue for even nbf and full red for odd ones. 
        for (int i = 0; i < 320 * 240; i++) fb[i] = (nbf & 1) ? RED : BLUE;
    
        tft.update(fb); // push the framebuffer to the screen
    
        if (++nbf % 1000 == 200) tft.printStats();
        }
    Last edited by vindar; 12-03-2020 at 07:54 PM. Reason: tried to make real english sentence :-)

  6. #31
    Senior Member+ KurtE's Avatar
    Join Date
    Jan 2014
    Posts
    9,567
    Thanks,

    I understand, and remembered that from before... That is with the turtle and the hare going around the track. We just need the turtle to advance fast enough that it does not get totally lapped

    I am also not sure if 60mhz is totally safe and reliable. Obviously probably not if running over jumper wires.

    I probably have enough stuff in my main library to try it out and see if my psuedo video mode looks better, with a simple startup sync... Will again probably start simple and experiment.

  7. #32
    Member
    Join Date
    Oct 2016
    Location
    Paris
    Posts
    37
    Quote Originally Posted by KurtE View Post
    Thanks,

    I understand, and remembered that from before... That is with the turtle and the hare going around the track. We just need the turtle to advance fast enough that it does not get totally lapped
    Yes exactly :-)

    Quote Originally Posted by KurtE View Post
    I am also not sure if 60mhz is totally safe and reliable. Obviously probably not if running over jumper wires.

    I probably have enough stuff in my main library to try it out and see if my psuedo video mode looks better, with a simple startup sync... Will again probably start simple and experiment.
    Yes, I think it should work in your case since there is no way your will upload rate will be faster than the refresh rate. In my case, differential updates can be way faster than the refresh rate so I must check regularly and slow down if needed (this is what the IntervalTimer is for, so that there is no busy wait).
    Also, you will only be able to use this strategy for for orientation mode 0. Other mode will still require multiple sync and ordering correctly the uploaded pixels. But anyway, it does not work as well for other orientation mode so maybe having it for mode 0 is good enough....

  8. #33
    Member
    Join Date
    Oct 2016
    Location
    Paris
    Posts
    37
    Hi,

    Just bumping this thread to say that I made quite a few optimizations to the library.
    It now performs almost identically in any orientation mode. CPU usage has been significantly reduced and asynchronous updates now make use of the SPI FIFO to increase the upload rate.

    @KurtE. I ported the Demosauce example from your library to see how differential updates would help there. Indeed, it gives a nice speed bump. This is especially noticeable at lower SPI speed (e.g. apart from 'plasmacloud' the demo is rather watchable with SPI@15Mhz).

  9. #34
    @vindar thank you very much for this library!! I'm easily getting 60 fps with diff buffers and vsync. I put together a proof of concept with the same code that runs using your library, Kurt's ILI9341_t3n, and an opengl version here: https://pigweed-review.googlesource....roject/+/28320

    On device:
    Click image for larger version. 

Name:	test2.gif 
Views:	56 
Size:	199.1 KB 
ID:	23041

    On host machine:
    Click image for larger version. 

Name:	Peek 2020-12-30 21-00.gif 
Views:	47 
Size:	182.9 KB 
ID:	23039
    Attached Thumbnails Attached Thumbnails Click image for larger version. 

Name:	test.jpg 
Views:	38 
Size:	13.9 KB 
ID:	23040  

  10. #35
    Member DIYLAB's Avatar
    Join Date
    Jun 2020
    Location
    Germany
    Posts
    64
    I've been using this driver for some time now for all my projects and I'm really addicted to it ;o)

    Before I used the ILI9341_t3n from KurtE.
    There I miss the differential updates and vSync, or I haven't found or understood them yet. Clipping areas are costly and asynchronous updates brought significant problems in my use cases - but maybe I am not smart enough to use this method.
    Kurt, please don't misunderstand, your ILI9341_t3n is wonderful, but Vindar's driver is more workable for me. I don't want to question your work in any way.

    Meanwhile I play with the ILI9488 Diaplay and would be very happy if I could use Arvind's driver.
    I often read that porting from ILI9341 to ILI9488 is relatively easy - is that true?
    But my knowledge is far from sufficient for this.

    I realize that Arvind's driver would use tons of RAM for the buffers on a 320x480 display, but it still fits on the Teensy 4.x. Arvind is very busy at the moment, so I wanted to ask if someone can tell me if a port to ILI9488 can be done, even if you don't have that much experience. Thanks!

  11. #36
    From the ili9488 datasheet looks like it does not support RGB565 data formatting over SPI. It uses RGB666 which would take up 3bytes per pixel (one extra byte). Some work would be needed to support that. That would make transfers slower and take up more ram.

    RGB565 is supported on the 8 and 16-bit parallel interfaces though.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •