Teensy 3.6 VGA driver

My code is still in flux - so many numbers to see :)

NOTE: I went to F_BUS 60 back to 120 and redid the Auto Adjust - now the text is properly left justified to the true edge? Register() should draw lines not triangles because the side points are blobs not points.

<edit> I found another number out of 1,000,000 Us/sec the:: RunTime:746854 is the time spent outside the loop that does VGA update with 60Hz 'full' screen writes with beamWait(), doing only one third update each of 60 Hz gives RunTime:879384. Also I found there are worse places/ways to reset the elapsedMicros.

I took out the always printing Register lines- just 5 seconds in setup. With all the rectangles drawing most all the screen at 60 Hz ( one small gap on bottom third ) the draw time looks to be 3.1ms, and I am keying off of waitBeam() [ not waitSync() ] -it looks good (no waitBeam() is UGLY) and using elapsedMicros to control update I end up waiting 934 Us on average for the Beam to go off. Using waitSync() takes over 12 ms and limits refresh to 30 Hz - which looks smoother because 60 Hz is unnaturally fast to follow doubling POV - unless you follow with your eyes.
millis::22758 F_CPU:240000000 F_BUS:60000000 fb_height:300 fb_width:703
TimeSumBeam:1646
TimeSumSync:15183
DrawTime:3173
TimeBeamWait:934
TimeSumSyncVar:15670
DrawHz:60

There is one tear/fold in the upper right corner - that is that same 60Hz w/waitBeam or 30 Hz w/waitSync. Also the same F_BUS at 60 or 120. And with bars static or moving.

Above numbers at F_BUS=60 as indicated - here is the same series at F_BUS =120
millis::136820 F_CPU:240000000 F_BUS:120000000 fb_height:300 fb_width:703
TimeSumBeam:1646
TimeSumSync:15012
DrawTime:3173
TimeBeamWait:1046
TimeSumSyncVar:15555
DrawHz:60

The TimeSumxxxx is a loop average of 10 times of waitBeam or waitSync - the Sync measures faster with the F_BUS=120, DrawTime is the same - but my funny math to adjust TimeSumSyncVar for elapsedMicros gets worse for F_BUS=120 - so that needs to be improved.

I am using Raw_HID for serial SPEW and I see NO JITTER (set doXXX()'s param 3 to zero) - I added some test code to watch for Hid ( !Serial ) to come online - it takes over 6 seconds with TyCommander. I want to add keyboard input to allow changes without having to recompile.

Here is the code - anything useful?:
Code:
// Lines and Color wiring test
// Do lines not triangles in Register()
// Keyboard input - show Register(), [stop start advance] rotate of bars, change refresh limitation <60 Hz

#include <uVGA.h>
void yield(void){};

// Prep for USB input adjust : First use -- Send Enter to toggle
bool DrawAll = HIGH; // Draw All three bands each time - or not then just one in turn
bool DrawReg = HIGH; // Draw Registration lines each time - or not


uVGA uvga;

#define UVGA_DEFAULT_REZ
#include <uVGA_valid_settings.h>

const byte cmap[] =
{
  0b00000000, 0b11100000, 0b11100100, 0b11101000, 0b11101100, 0b11110000, 0b11110100, 0b11111000, 0b11111100,
  0b11011100, 0b10111100, 0b10011100, 0b01111100, 0b01011100, 0b00111100, 0b00011100, 0b00011101, 0b00011110,
  0b00011111, 0b00011011, 0b00010111, 0b00010011, 0b00001111, 0b00001011, 0b00000111, 0b00000011, 0b00100011,
  0b01000011, 0b01100011, 0b10000011, 0b10100011, 0b11000011, 0b11100011, 0b11100010, 0b11100001, 0b11100000, 0b00000000
};
const byte cmap2[] =
{ 0b00000000,
  0b11100000, 0b11000000, 0b10100000, 0b10000000, 0b01100000, 0b01000000, 0b00100000,
  0b00011100, 0b00011000, 0b00010100, 0b00010000, 0b00001100, 0b00001000, 0b00000100,
  0b00000011, 0b00000010, 0b00000001
};

int fb_width, fb_height;

elapsedMicros TimeTest = 0;
uint32_t TimeSumSync = 0;
uint32_t TimeSumBeam = 0;
uint32_t TimeSumSyncVar = 0;
uint32_t TimeSumBeamVar = 0;
uint32_t TimeBeamWait = 0;
uint32_t TimeBeamWaitCnt = 1;
uint32_t DrawTime = 0;
uint32_t DrawHz = 0;
uint32_t RunTime = 0;
void setup()
{
  int ret;
  int Snot = 0;
  Serial.begin( 1200 );
  ret = uvga.begin(&modeline);
  if (ret != 0)
    while (1) {
      if ( !Snot && Serial ) {
        Serial.println("fatal error");
        Snot = 1;
      }
    }
  uvga.get_frame_buffer_size(&fb_width, &fb_height);
  doRegister( fb_width, fb_height, 5000 );
  TimeSumSync = 0;
  for ( int ii = 0; ii < 10; ii++ ) {
    TimeTest = 0;
    uvga.waitSync();
    TimeSumSync += TimeTest;
  }
  TimeSumSync /= 10;
  TimeSumSyncVar = TimeSumSync;

  TimeSumBeam = 0;
  for ( int ii = 0; ii < 10; ii++ ) {
    uvga.waitBeam();
    delayMicroseconds(15);
    TimeTest = 0;
    uvga.waitBeam();
    TimeSumBeam += TimeTest;
  }
  TimeSumBeam /= 10;
  TimeSumBeamVar = TimeSumBeam;
  testPrint();
}

int Snot = 0;
elapsedMicros DoScreen = 0;
elapsedMillis DoUpdate = 0;
void loop()
{
  if ( DoScreen >= TimeSumSyncVar )
  {
    TimeTest = 0;
    // uvga.waitSync();
    uvga.waitBeam();
    TimeBeamWait += TimeTest;
    TimeBeamWaitCnt++;
    DoScreen = 0; // BAD >> -= TimeSumSync; // = 0;
    DrawHz++;

    doLines( fb_width, fb_height, -1 ); // disable draw lines with negative wait on param 3
    // uvga.clear(0);
    // uvga.clear(0b11111111);

    // these each take a third of the screen given H and W, Param 3 is bar +/-/0 indexing between draws, Param 4 is time to delay before return
    if ( ( DrawHz % 3 ) || DrawAll )
      doCmap( fb_width, fb_height, 1, 0 );
    if ( ( DrawHz % 3 ) || DrawAll )
      doPCmap( fb_width, fb_height, -1, 0 );
    if ( ( DrawHz % 3 ) || DrawAll )
      doColors( fb_width, fb_height, 1, 0 );
    if ( DrawReg )
      doRegister( fb_width, fb_height, 0 );
    DrawTime += DoScreen;
    RunTime += TimeTest;
  }
  if ( DoUpdate >= 1000 && Serial ) {
    DoUpdate = 0;
    testPrint();
    if ( !Snot) {
      Serial.print("\n WE HAVE SERIAL ");
      Snot = 1;
    }
  }
  if ( !Serial ) {
    Snot = 0;
  }
}

void doCmap( int fb_width, int fb_height, int Nshift, int dtime ) {
  int col;
  static uint16_t pre_adv = 0;
  pre_adv += Nshift;
  uint16_t adv = pre_adv;
  int xx = fb_width / sizeof(cmap);
  int yy = (fb_height - 1) / 3;
  for (col = 0; col < sizeof(cmap); col++) {
    uvga.fillRect(col * xx, 0, (col * xx) + xx - 1, yy - 1, cmap[(col + adv) % sizeof(cmap)]);
  }
  delay( dtime );
}

void doPCmap( int fb_width, int fb_height, int Nshift, int dtime ) {
  int col;
  static uint16_t pre_adv = 0;
  pre_adv += Nshift;
  uint16_t adv = pre_adv;
  int xx = fb_width / sizeof(cmap);
  int yy = (fb_height - 1) / 3;
  for (col = 0; col < sizeof(cmap); col++) {
    uvga.fillRect(col * xx, yy, (col * xx) + xx - 1, 2 * (yy) - 1, 0xff - cmap[(col + adv) % sizeof(cmap)]);
  }
  delay( dtime );
}


void doColors( int fb_width, int fb_height, int Nshift, int dtime ) {
  int col;
  static uint16_t pre_adv = 0;
  pre_adv += Nshift;
  uint16_t adv = pre_adv;
  int yy = (fb_height - 1) / 3;
  int xx = fb_width / (sizeof(cmap2) * 2);

  int bb = fb_width - (xx * sizeof(cmap2) * 2); // put the unused space in the center
  for (col = 0; col < sizeof(cmap2); col++) {
    uvga.fillRect(col * xx, 2 * yy, (col * xx) + xx - 1, fb_height - 1, cmap2[(col + adv) % sizeof(cmap2)]);
    uvga.fillRect(bb + sizeof(cmap2)*xx + (col * xx), 2 * yy, bb + sizeof(cmap2)*xx + ((col * xx) + xx - 1), fb_height - 1, 0xff - cmap2[(col + adv) % sizeof(cmap2)]);
  }
  delay( dtime );

}


void doLines( int fb_width, int fb_height, int dtime ) {
  int col;
  if ( 0 > dtime ) return;
  // uvga.waitSync();
  //uvga.clear(0b11111111);
  for (col = 0; col < fb_width; col += 18) {
    uvga.drawLine(col, 0, col, fb_height, 0b00011100);
    uvga.drawLine(col + 9, 0, col + 9, fb_height, 0b00000011);
    Serial.print(".");
  }
  delay( dtime );
}

void doRegister( int fb_width, int fb_height, int dtime ) {
  if ( 0 > dtime ) return;
  fb_width--;
  fb_height--;
  uvga.drawTri(fb_width, fb_height / 2, 0, fb_height / 2, fb_width / 2, 0, 0b01101111);
  uvga.drawTri(fb_width, fb_height / 2 + 1, 0, fb_height / 2 + 1, fb_width / 2, fb_height, 0b10010001);
  uvga.drawRect( 0, 0, fb_width, fb_height, 0b10010010);
  uvga.drawLine( 0, 0, fb_width, fb_height, 0b10011111 );
  uvga.drawLine( 0, fb_height, fb_width, 0, 0b10011111 );
  if ( dtime > 99 ) {
    uvga.print("\nF_CPU: ");
    uvga.println(F_CPU);
    uvga.print("\tF_BUS: ");
    uvga.println(F_BUS);
    uvga.print("\tfb_height: ");
    uvga.println(fb_height + 1);
    uvga.print("\tfb_width: ");
    uvga.println(fb_width + 1);
    uvga.print("A234567890B234567890C234567890D234567890E234567890F234567890G234567890H234567890I234567890xyz");
    uvga.print("\n.............................................................................................");
  }

  delay( dtime );
}



void testPrint() {
  //    doRegister( fb_width, fb_height, 0 );
  //  int fb_width, fb_height;
  //  uvga.get_frame_buffer_size(&fb_width, &fb_height);
  Serial.print("\n millis::");
  Serial.print(millis());
  Serial.print("  F_CPU:");
  Serial.print(F_CPU / 1000000);
  Serial.print("   F_BUS:");
  Serial.print(F_BUS / 1000000);
  Serial.print("   fb_ht:");
  Serial.print(fb_height);
  Serial.print("   fb_wid:");
  Serial.println(fb_width);
  Serial.print("   TimeSumBeam:");
  Serial.println(TimeSumBeam);
  Serial.print("   TimeSumSync:");
  Serial.println(TimeSumSync);
  TimeBeamWait /= TimeBeamWaitCnt;
  DrawTime /= TimeBeamWaitCnt;
  Serial.print("   DrawTime:");
  Serial.println(DrawTime);
  TimeBeamWaitCnt = 0;
  Serial.print("   TimeBeamWait:");
  Serial.println(TimeBeamWait);

  // Adjust 60 Hz wait to update time
  //  TimeSumSyncVar = TimeSumSync + TimeBeamWait / 2 + 20;  // for F_BUS=60 at cpu=240 yields: TimeBeamWait:934
  if ( TimeBeamWait > 100 )
    TimeSumSyncVar = TimeSumSync + TimeBeamWait;// // for F_BUS=120 at cpu=240 yields: TimeBeamWait: 1 or 2 Us
  Serial.print("   TimeSumSyncVar:");
  Serial.println(TimeSumSyncVar);
  Serial.print("   DrawHz:");
  Serial.println(DrawHz);
  DrawHz = 0;
  Serial.print("   RunTime:");
  Serial.println(1000000 - RunTime);
  RunTime = 0;
  int doSwap = 0;
  while ( Serial.available() ) {
    doSwap = 1;
    Serial.read();
  }
  if ( doSwap ) {
    DrawAll = !DrawAll;
    DrawReg = !DrawReg;
  }
}
 
Last edited:
I think I know a bit more, now.

For Teensy 3.6:
GPIO and Serial5 are connected to the same "Periphal Bridge 1" . If both are accessed the same time, the visible pixel-delay occurs - because the accesses need to be serialized and two Periphals can not the bus at the same time. It is clocked with F_BUS.
The other Serial ports are connected to "Periphal Bridge 0".

So, I think, nothing can be done to prevent the effect without "synced" access
It will occour with every periphal on bridge 1.

See chapter "5.5 Peripheral bridge (AIPS-Lite0 and AIPS-Lite1) memory map" in the manual.

Periphal Bridge 1 connected periphals are:

- RNGA
- USB OTG
- USBHS
- FlexCan
- SPI 2
- SDHC
- FTM 2
- FTM 3
- ADC 1
- Ethernet MAC and IEE timers
- LPUART 0
- TPM 1
- TPM 2
- DAC 0
- DAC 1
- I2C2
- I2C3
- UART 4
- GPIO

Access without the pixel delay is still possible - if done during the blanking (I hope).
Edit: Perhaps the easiest way - for the interrupt driven periphals, especially UART4 - is to disable their interrupts during display of a line(?)
More time can be obtained with inserting some black lines on the upper/lower screen borders.
 
Last edited:
Updated code in post #126.

Simple change got TimeBeamWait:1 - which means elapsedMicros is gating entry update request in sync in the sample. Drawing is clean and smooth and under 0.120ms wait per second for beam to leave the screen and start draw.

When run as posted the Register() code now draws lines and the triangles.
> When they are drawing the torn upper corner can be seen to hold the new lines, then they wash out in a band to the top left corner?
> Why wont this rectangle draw within the screen: uvga.drawRect( 0, 0, fb_width-1, fb_height-1, 0b10010010);
-- left and right edges are not full 2 pixels wide?
-- not visible with normal updates in above sketch without tweaks or delay like on the setup() call
> added simple toggle when 'Enter' is sent in over USB - stops Register draw and only updates 1 of 3 bars per cycle
-- WOW - adding 4 conditional tests for selective drawing adds 1.2ms to the DrawTime! { 3.172ms goes to 4.341ms }


millis::196855 F_CPU:240 F_BUS:120 fb_ht:300 fb_wid:703
TimeSumBeam:1646
TimeSumSync:14968
DrawTime:2115
TimeBeamWait:1
TimeSumSyncVar:16582
DrawHz:60
RunTime:875048
 
Another update: Framerate adjustable:: #define SET_FRAMERATE 10 // #10 results in 60 Hz update rate, #5 is 30 Hz, #4 is 20 Hz, #3 is 15 Hz, #2 is 12 Hz
TimeBeamWait generally works out 1 Us, but not all rates as written.

<edited>: Added key response to USB input. Checked once each second during USB print of instrumentation numbers.
'?' + Enter to view. +/- to vary update rate, 'r' toggle registration lines, 'p' toggle Pause of bar shift, 'l' toggle vertical lines.
NOTE: Original LINES are back and they do show jitter on HID!!!! (less than USB? Doesn't stop when SerMon disconnected)

Tested at 240 Mhz and viewed at 180 and 120 MHz, no changes with similar issues. [note resolution changes with speed and background is not cleared and bars integer divide to fill the width]
millis::90832 F_CPU:120 F_BUS:60 fb_ht:240 fb_wid:426
TimeSumBeam:0
TimeSumSync:15954
DrawTime:1445
TimeBeamWait:3
TimeSumSyncVar:16681
DrawHz:60
RunTime:914427

At 180 Mhz the refresh drops to 56 Hz?
millis::69796 F_CPU:180 F_BUS:60 fb_ht:300 fb_wid:566
TimeSumBeam:3541
TimeSumSync:16439
DrawTime:1309
TimeBeamWait:1
TimeSumSyncVar:17779
DrawHz:56
RunTime:927845

Hardcoded to only redraw one of three screen bands in 1.056 ms without Registration() and 3.241ms with it when drawing bands continues in place showing flicker in upper third.

User input 'Enter' toggles: 'stopped with registration' or 'moving without registration lines'. Good to set Auto Adjust and observe flicker and the dark lines between bars.

Very convoluted code - but adjustable and instrumentation shows a few things:
: top right corner flicker
: missing edge pixels
: darker areas between rectangles
: Timing for draw

Code:
// Lines and Color wiring test

#include <uVGA.h>
void yield(void){};

#define SET_FRAMERATE 10  // #10 results in 60 Hz update rate, #5 is 30 Hz, #4 is 20 Hz, #3 is 15 Hz, #2 is 12 Hz

// Prep for USB input adjust : Enter '?' for HELP
bool DrawAll = !HIGH; // Draw All three bands each time - or not then just one in turn
bool DrawReg = HIGH; // Draw Registration lines each time - or not
bool DrawLines = HIGH; // Draw Verical lines each time - or not


uVGA uvga;

#define UVGA_DEFAULT_REZ
#include <uVGA_valid_settings.h>

const byte cmap[] =
{
  0b00000000, 0b11100000, 0b11100100, 0b11101000, 0b11101100, 0b11110000, 0b11110100, 0b11111000, 0b11111100,
  0b11011100, 0b10111100, 0b10011100, 0b01111100, 0b01011100, 0b00111100, 0b00011100, 0b00011101, 0b00011110,
  0b00011111, 0b00011011, 0b00010111, 0b00010011, 0b00001111, 0b00001011, 0b00000111, 0b00000011, 0b00100011,
  0b01000011, 0b01100011, 0b10000011, 0b10100011, 0b11000011, 0b11100011, 0b11100010, 0b11100001, 0b11100000, 0b00000000
};
const byte cmap2[] =
{ 0b00000000,
  0b11100000, 0b11000000, 0b10100000, 0b10000000, 0b01100000, 0b01000000, 0b00100000,
  0b00011100, 0b00011000, 0b00010100, 0b00010000, 0b00001100, 0b00001000, 0b00000100,
  0b00000011, 0b00000010, 0b00000001
};

int fb_width, fb_height;

elapsedMicros TimeTest = 0;
uint32_t TimeSumSync = 0;
uint32_t TimeSumSync_Keep = 0;
uint32_t FrameRate_Hold = SET_FRAMERATE;
uint32_t TimeSumBeam = 0;
uint32_t TimeSumSyncVar = 0;
uint32_t TimeSumBeamVar = 0;
uint32_t TimeBeamWait = 0;
uint32_t TimeBeamWaitCnt = 1;
uint32_t DrawTime = 0;
uint32_t DrawHz = 0;
uint32_t RunTime = 0;
void setup()
{
  int ret;
  int Snot = 0;
  Serial.begin( 1200 );
  ret = uvga.begin(&modeline);
  if (ret != 0)
    while (1) {
      if ( !Snot && Serial ) {
        Serial.println("fatal error");
        Snot = 1;
      }
    }
  uvga.get_frame_buffer_size(&fb_width, &fb_height);
  doRegister( fb_width, fb_height, 1000 );
  TimeSumSync_Keep = 0;
  for ( int ii = 0; ii < 10; ii++ ) {
    TimeTest = 0;
    uvga.waitSync();
    TimeSumSync_Keep += TimeTest;
  }
  TimeSumSync = TimeSumSync_Keep / FrameRate_Hold;

  TimeSumSyncVar = TimeSumSync;

  TimeSumBeam = 0;
  for ( int ii = 0; ii < 10; ii++ ) {
    uvga.waitBeam();
    delayMicroseconds(15);
    TimeTest = 0;
    uvga.waitBeam();
    TimeSumBeam += TimeTest;
  }
  TimeSumBeam /= 10;
  TimeSumBeamVar = TimeSumBeam;
  testPrint();
}

int Snot = 0;
elapsedMicros DoScreen = 0;
elapsedMillis DoUpdate = 0;
void loop()
{
  if ( DoScreen >= TimeSumSyncVar )
  {
    TimeTest = 0;
    // uvga.waitSync();
    uvga.waitBeam();
    TimeBeamWait += TimeTest;
    TimeBeamWaitCnt++;
    DoScreen = 0; // BAD >> -= TimeSumSync; // = 0;
    DrawHz++;

    // uvga.clear(0);
    // uvga.clear(0b11111111);

    // these each take a third of the screen given H and W, Param 3 is bar +/-/0 indexing between draws, Param 4 is time to delay before return
    switch ( DrawHz % 3 ) {
      case 0:
        doCmap( fb_width, fb_height, 1 * DrawAll, 0 );
        break;
      case 1:
        doPCmap( fb_width, fb_height, -1 * DrawAll, 0 );
        break;
      case 2:
        doColors( fb_width, fb_height, 1 * DrawAll, 0 );
        break;
    }
    doRegister( fb_width, fb_height, DrawReg );
    doLines( fb_width, fb_height, -1*DrawLines ); // disable draw lines with negative wait on param 3
    DrawTime += DoScreen;
    RunTime += TimeTest;
  }
  if ( DoUpdate >= 1000 && Serial ) {
    DoUpdate = 0;
    testPrint();
    if ( !Snot) {
      Serial.print("\n WE HAVE SERIAL ");
      Snot = 1;
    }
  }
  if ( !Serial ) {
    Snot = 0;
  }
}

void doCmap( int fb_width, int fb_height, int Nshift, int dtime ) {
  int col;
  static uint16_t pre_adv = 0;
  pre_adv += Nshift;
  uint16_t adv = pre_adv;
  int xx = fb_width / sizeof(cmap);
  int yy = (fb_height - 1) / 3;
  for (col = 0; col < sizeof(cmap); col++) {
    uvga.fillRect(col * xx, 0, (col * xx) + xx - 1, yy - 1, cmap[(col + adv) % sizeof(cmap)]);
  }
  delay( dtime );
}

void doPCmap( int fb_width, int fb_height, int Nshift, int dtime ) {
  int col;
  static uint16_t pre_adv = 0;
  pre_adv += Nshift;
  uint16_t adv = pre_adv;
  int xx = fb_width / sizeof(cmap);
  int yy = (fb_height - 1) / 3;
  for (col = 0; col < sizeof(cmap); col++) {
    uvga.fillRect(col * xx, yy, (col * xx) + xx - 1, 2 * (yy) - 1, 0xff - cmap[(col + adv) % sizeof(cmap)]);
  }
  delay( dtime );
}


void doColors( int fb_width, int fb_height, int Nshift, int dtime ) {
  int col;
  static uint16_t pre_adv = 0;
  pre_adv += Nshift;
  uint16_t adv = pre_adv;
  int yy = (fb_height - 1) / 3;
  int xx = fb_width / (sizeof(cmap2) * 2);

  int bb = fb_width - (xx * sizeof(cmap2) * 2); // put the unused space in the center
  for (col = 0; col < sizeof(cmap2); col++) {
    uvga.fillRect(col * xx, 2 * yy, (col * xx) + xx - 1, fb_height - 1, cmap2[(col + adv) % sizeof(cmap2)]);
    uvga.fillRect(bb + sizeof(cmap2)*xx + (col * xx), 2 * yy, bb + sizeof(cmap2)*xx + ((col * xx) + xx - 1), fb_height - 1, 0xff - cmap2[(col + adv) % sizeof(cmap2)]);
  }
  delay( dtime );

}


void doLines( int fb_width, int fb_height, int dtime ) {
  int col;
  if ( 0 > dtime ) return;
  // uvga.waitSync();
  //uvga.clear(0b11111111);
  for (col = 0; col < fb_width; col += 18) {
    uvga.drawLine(col, 0, col, fb_height, 0b00011100);
    uvga.drawLine(col + 9, 0, col + 9, fb_height, 0b00000011);
    Serial.print(".");
  }
  delay( dtime );
}

void doRegister( int fb_width, int fb_height, int dtime ) {
  if ( 0 >= dtime ) return;
  fb_width--;
  fb_height--;
  uvga.drawTri(fb_width, fb_height / 2, 0, fb_height / 2, fb_width / 2, 0, 0b01101111);
  uvga.drawTri(fb_width, fb_height / 2 + 1, 0, fb_height / 2 + 1, fb_width / 2, fb_height, 0b10010001);
  uvga.drawRect( 0, 0, fb_width, fb_height, 0b10010010);
  uvga.drawLine( 0, 0, fb_width, fb_height, 0b10011111 );
  uvga.drawLine( 0, fb_height, fb_width, 0, 0b10011111 );
  if ( dtime > 99 ) {
    uvga.print("\nF_CPU: ");
    uvga.println(F_CPU);
    uvga.print("\tF_BUS: ");
    uvga.println(F_BUS);
    uvga.print("\tfb_height: ");
    uvga.println(fb_height + 1);
    uvga.print("\tfb_width: ");
    uvga.println(fb_width + 1);
    uvga.print("A234567890B234567890C234567890D234567890E234567890F234567890G234567890H234567890I234567890xyz");
    uvga.print("\n.............................................................................................");
    uvga.print("\n :: USB cmd: '?' this screen");
    uvga.print("\n :: USB cmd: '+' faster '-' slower update");
    uvga.print("\n :: USB cmd: 'r' show screen Registration lines");
    uvga.print("\n :: USB cmd: 'p' toggle Pause of bar shift");
    uvga.print("\n :: USB cmd: 'l' toggle draw of vertical lines");
  }

  delay( dtime );
}



void testPrint() {
  //    doRegister( fb_width, fb_height, 0 );
  //  int fb_width, fb_height;
  //  uvga.get_frame_buffer_size(&fb_width, &fb_height);
  Serial.print("\n millis::");
  Serial.print(millis());
  Serial.print("  F_CPU:");
  Serial.print(F_CPU / 1000000);
  Serial.print("   F_BUS:");
  Serial.print(F_BUS / 1000000);
  Serial.print("   fb_ht:");
  Serial.print(fb_height);
  Serial.print("   fb_wid:");
  Serial.println(fb_width);
  Serial.print("   TimeSumBeam:");
  Serial.println(TimeSumBeam);
  Serial.print("   TimeSumSync:");
  Serial.println(TimeSumSync);
  TimeBeamWait /= TimeBeamWaitCnt;
  DrawTime /= TimeBeamWaitCnt;
  Serial.print("   DrawTime:");
  Serial.println(DrawTime);
  TimeBeamWaitCnt = 0;
  Serial.print("   TimeBeamWait:");
  Serial.println(TimeBeamWait);

  int incomingByte = 0;
  while ( Serial.available() ) {
    incomingByte = Serial.read();
    switch ( incomingByte ) {
      case 'p':
        DrawAll = !DrawAll;
        break;
      case 'r':
        DrawReg = !DrawReg;
        break;
      case 'l':
        DrawLines = !DrawLines;
        break;
      case '?':
        doRegister( fb_width, fb_height, 5000 );
        break;
      case '+':
        if ( FrameRate_Hold < 5 ) FrameRate_Hold++;
        else if ( FrameRate_Hold < 10 ) FrameRate_Hold = 10;
        break;
      case '-':
        if ( FrameRate_Hold > 5 ) FrameRate_Hold = 5;
        else if ( FrameRate_Hold > 1 ) FrameRate_Hold--;
        break;
      default:
        break;
    }
    TimeSumSync = TimeSumSync_Keep / FrameRate_Hold;
    TimeBeamWait = 0;
    TimeSumSyncVar = TimeSumSync;
  }
  // Adjust Refresh elapsed delay between updates
  if ( TimeBeamWait > 200 )
    TimeSumSyncVar = TimeSumSync + TimeBeamWait; // for F_BUS=120 or 60, at cpu=240 yields: TimeBeamWait: 1 or 2 Us
  Serial.print("   TimeSumSyncVar:");
  Serial.println(TimeSumSyncVar);
  Serial.print("   DrawHz:");
  Serial.println(DrawHz);
  DrawHz = 0;
  Serial.print("   RunTime:");
  Serial.println(1000000 - RunTime);
  RunTime = 0;
}
 
Last edited:
Quick half an update - my code is improving - but still too ugly no time to share and finish the utility.

I did get this quick pic showing the color gradations in RGB on the bottom row - followed by their complement. One oddity that is apparent is the dark line after the 4th green bar. Code cycles those bars and the bar comes and goes. That same dark area is visible in the top third row after the 4th blue bar - and the same spot on the 2nd row which is a complement of the top row. Some of the other divider lines may have similar artifacts - perhaps this is the monitor scaling - except it isn't top down line?

View attachment 11642


I checked and it is a monitor glitch. If you draw a black or white Vline at x=228, you can see the line width (1 pixel) is bigger than the size of the dark line. The funny thing is we both have the same artifact despite use of different monitor and the artifact seems to be repeated regularly on each line.
 
I think I know a bit more, now.

For Teensy 3.6:
GPIO and Serial5 are connected to the same "Periphal Bridge 1" . If both are accessed the same time, the visible pixel-delay occurs - because the accesses need to be serialized and two Periphals can not the bus at the same time. It is clocked with F_BUS.
The other Serial ports are connected to "Periphal Bridge 0".

So, I think, nothing can be done to prevent the effect without "synced" access
It will occour with every periphal on bridge 1.

See chapter "5.5 Peripheral bridge (AIPS-Lite0 and AIPS-Lite1) memory map" in the manual.

Periphal Bridge 1 connected periphals are:

- RNGA
- USB OTG
- USBHS
- FlexCan
- SPI 2
- SDHC
- FTM 2
- FTM 3
- ADC 1
- Ethernet MAC and IEE timers
- LPUART 0
- TPM 1
- TPM 2
- DAC 0
- DAC 1
- I2C2
- I2C3
- UART 4
- GPIO

.

Nice find, I totally missed this part of the reference manual.

This morning, I tried my new flexbus hardware and I discovered several things:
  • soldering a 74LVC1G04 (NOT gate) in SOT353-1/TSSOP5 package on a SSOP20 breakout pcb is really hard because pins (0.425mm long) are barely long enough to reach the pads but it works :) IMG_20170930_130247.jpg Yes, I know, it is not very nice :p (0.65mm is the space between the middle of 2 pins, it is really small, at least for me)
  • the bad news: a simple latch, even a fast one (3ns) is not the correct component. The result is far better than with the 17ns latch but the latch seems to take too much time to take into account LE signal when it goes up. A positive trigger latch should be the solution. I should receive one in few days now.
  • the very good news: even with arduino console opened, the image is not disturbed by Serial.print. This should have been expected as flexbus usage requires no peripheral access and flexbus settings are made only one time at the beginning. Moreover, my code uses 4 bytes read/write instead of 1 byte access which increase a lot SRAM brandwidth. This also means USB-host should be ok.
 
I checked and it is a monitor glitch. If you draw a black or white Vline at x=228, you can see the line width (1 pixel) is bigger than the size of the dark line. The funny thing is we both have the same artifact despite use of different monitor and the artifact seems to be repeated regularly on each line.

Okay, good to know. That may be a scaling artifact from the displays finding a suitable resolution for the given output? I saw it here on a second LCD as well.

Good news on advancing with the SRAM. Very good it can be stable with USB running. I'm not sure why but now with the colored bars instead of white background in my last code - enabling the GB lines showed minor but prevalent jitter with Raw_HID connected or not - and not problem when Serial but not connected. And the rectangles I used show no jitter.
 
USB-HOST:
This helps: USBHS_USBCMD &= ~ ( USBHS_USBCMD_ASE);
Asynchronous Schedule Enable
Controls whether the controller skips processing the asynchronous schedule. Only used in host mode.
0 Do not process asynchronous schedule.
1 Use the ASYNCLISTADDR register to access asynchronous schedule.
 
Nice Frank. I was wondering about trying other displays - not at my computer ( on battery and no USB) - so I programmed a 'screen Saver' mode in when the lines.ino starts without Serial. It runs through variations of the things I added to the sketch. Can also be started with USB by sending 's'. Other commands show up with '?'.
View attachment LINES.ino
 
I wonder if a video with 256 colors looks good.... unfortunately, SDHC is on AIPS-1, too-
Edit: All fast devices are on AIPS-1.. :-(
 
Last edited:
that top-border flicker in lines.ino is interesting... where does it come from ?

That is a good question for qix67? I wasn't sure if it was just my display. It is really obvious with the Registration lines on screen - which is why that is the default view that comes up. Hopefully It looks the same for him. The option to slow refresh - even down to 6 draws/second still shows that ( actually at 6Hz that portion is only drawn 2 times/second and still flashes each time). It may be my drawing extends across a beam start? I do uvga.waitBeam(); before I start - but drawing Registration lines extends too long - but doing another waitBeam() ( or waitSync ) before DoRegister() makes ALL the lines FLASH - not just the upper portion.

The other interesting thing noted is the upper right corner when the bar scroll is active ( send USB 'p' to unpause ) - with or without Registration.

Also Frank pointed out my code has had a bogus yield() replacement - not that I think it matters here but line #4 should be :: void yield(void){};
 
That is a good question for qix67? I wasn't sure if it was just my display.

I also have it. This kind of problem appears when something is not drawn fast enough to be present on each frame. Here is an example.

From the start of Vsync, do the following things:
  • Clear screen
  • draw several bars from top of screen to bottom of screen, drawing them from left to right
Clearing screen takes a lot of time and each rectangle, due to its size also takes a lot of time. Taking all this into account and it is possible the top of right bars may flicker because when DMA sends these bytes to the screen, the CPU has not always drawn them.

It is the same problem in the spheres demo. When a large number of spheres are present, you can see some of them partially drawn at the top of screen but if you look better, not all of them. It occurs because the first spheres are drawn during vsync (thus always ok) but the last ones when DMA is still pushing data.

To limit the problem, the only solution is to draw all top screen elements first.
 
Makes sense there may be some timing issues - that is a lot of data moving on those regular sync passes - all under DMA with 92% CPU free.

A saw the Sphere demo degrade on the top - then recovered IIRC. Odd my Demo draws the top third first - and current version only does one third per waitBeam at 60 Hz, that shows some top corner error - of course then the Registration lines are drawn much later - but putting them after another waitBeam or waitSync was worse as noted. Since I was doing full screen overwrite I took out clear screens in my sample.

Seems my sample didn't show any new issues - which is good ... for your work.

It would be cool to have a callback() when the Beam goes off - or the proper Sync time to update the screen data. I got locked in pretty well with ElapsedMicros to have a short or no wait to start.

When running Flexbus SRAM - can DMA still control all of that timing needed? I suppose the 'switches' are to allow Teensy write RAM update - then change addresses and to enable output of the data needed to feed the RGB lines? Maybe I missed a note about the process - would be interesting to know.
 
It would be cool to have a callback() when the Beam goes off - or the proper Sync time to update the screen data. I got locked in pretty well with ElapsedMicros to have a short or no wait to start.

Technically, it is possible to have an interrupt when the last pixel of the frame buffer is sent by the DMA but polling a variable set by interrupt will be slower than polling DMA register directly.

When running Flexbus SRAM - can DMA still control all of that timing needed? I suppose the 'switches' are to allow Teensy write RAM update - then change addresses and to enable output of the data needed to feed the RGB lines? Maybe I missed a note about the process - would be interesting to know.

Yes, no problem with timing. Only Vsync is generated by DMA, Hsync remains on flextimer.

The process is nearly identically between GPIO and Flexbus. The only difference is that instead of writing each frame buffer byte into GPIO port D register, it is written as flexbus data. 1 or 2 additionnal components are required because the CPU used by the teensy cannot enable non-multiplexed flexbus bus mode (it is a CPU limit, not linked to missing pins on teensy pcb). In multiplexed mode, the same lines will carry alternatively data and address. When address is sent to the monitor, it displays a blueish black pixel. In my case, address is not used, I can write at any address in flexbus area because there is no active component connected. Flex bus is justed used as 8 SPI lines working simultaneously (like GPIO port D).

My first tries show that when flexbus runs at full speed on 240Mhz teensy, the 703x300 rez uses less than 1/3 screen width because DMA+flexbus is very/too fast but adding 2, 3 or 4 wait state brings back correct result.
 
Callback: An other option is to just use the PIN for an Interrupt:

attachInterrupt(digitalPinToInterrupt(29), myint , RISING); - FALLING is a bit later - I don't know the exact VGA timing (qix??), but one of those should give a reliable signal.
 
Callback: An other option is to just use the PIN for an Interrupt:

attachInterrupt(digitalPinToInterrupt(29), myint , RISING); - FALLING is a bit later - I don't know the exact VGA timing (qix??), but one of those should give a reliable signal.

I though this just worked only when pin is used as input ?

In any case, even is this work on output pin, you may waste some useful time. An interrupt on DMA end occurs when the last pixel of frame buffer. Your interrupt will be triggered when modeline's vsync_start is reached which in the best condition occurs exactly on the line after the last framebuffer line (you lose hblank time) but may also be a lot later. For example, in 240Mhz 560x240 mode, the last image line is 480 and vsync start at line 490.
 
I though this just worked only when pin is used as input ?

In any case, even is this work on output pin, you may waste some useful time. An interrupt on DMA end occurs when the last pixel of frame buffer. Your interrupt will be triggered when modeline's vsync_start is reached which in the best condition occurs exactly on the line after the last framebuffer line (you lose hblank time) but may also be a lot later. For example, in 240Mhz 560x240 mode, the last image line is 480 and vsync start at line 490.

No, pin interrups work in any mode (not "disabled") as far as i know.
 
The waitBeam 'delay' function timing can lose a lot of time - would be nice to have a way to locate the best and longest break in DMA access. Can get decent SYNC and <2 Us wait for waitBeam() using elapsedMicros - is there a ref doc that shows the timing and good bad times/durations for updates?

No, pin interrups work in any mode (not "disabled") as far as i know.
Seems I tested that as well.
 
Can someone try these 50Hz settings please ?
It's PAL- Timing

They work on my displays (but - still - not with my TV)

Code:
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
#ifdef UVGA_240M_360X288
// 240Mhz, 720x576@50Hz, FB resolution: 360x288
#pragma message "240Mhz 360x288"

#define UVGA_HREZ 360
#define UVGA_VREZ 576
#define UVGA_RPTL 2
uVGAmodeline modeline = {
   .pixel_clock = 27000000,
   .hres = UVGA_HREZ,
   .hsync_start = 732,
   .hsync_end = 795,
   .htotal = 864,
   .vres = UVGA_VREZ,
   .vsync_start = 576,
   .vsync_end = 581,
   .vtotal =586,
   .h_polarity = UVGA_NEGATIVE_POLARITY,
   .v_polarity = UVGA_NEGATIVE_POLARITY,
   .img_color_mode = UVGA_RGB332,
   .repeat_line = UVGA_RPTL,
   .horizontal_position_shift = 14,
    .pixel_h_stretch = UVGA_HSTRETCH_ULTRA_WIDE,
    .dma_settings = UVGA_DMA_AUTO,
    };
#endif

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++
#ifdef UVGA_240M_720X288
// 240Mhz, 720x576@50Hz, FB resolution: 720x288
#pragma message "240Mhz 720x288"

#define UVGA_HREZ 360
#define UVGA_VREZ 576
#define UVGA_RPTL 2
uVGAmodeline modeline = {
   .pixel_clock = 27000000,
   .hres = UVGA_HREZ,
   .hsync_start = 732,
   .hsync_end = 795,
   .htotal = 864,
   .vres = UVGA_VREZ,
   .vsync_start = 576,
   .vsync_end = 581,
   .vtotal =586,
   .h_polarity = UVGA_NEGATIVE_POLARITY,
   .v_polarity = UVGA_NEGATIVE_POLARITY,
   .img_color_mode = UVGA_RGB332,
   .repeat_line = UVGA_RPTL,
   .horizontal_position_shift = 14,
    .pixel_h_stretch = UVGA_HSTRETCH_WIDE,
    .dma_settings = UVGA_DMA_AUTO,
    };
#endif

Edit: The display is 2/3 wide :)
Edit: How can I calculate the position shift ? I guess, it should be near 180 (this give a nice "letterbox" format 4:3 on my 16:9 display), but what's the formular?
Edit: Modeline taken from here: https://www.mythtv.org/wiki/Modeline_Database#PAL625_itu-r.2Fbt:_470_601_656
 
Last edited:
Put those in >> I:\tCode\libraries\uVGA\uVGA_valid_settings_240MHz.h

Both compile and display a portion of the screen image expected.

About: #define UVGA_240M_360X288
>> Monitor reports :: 640x480 at 53 Hz
> only ~2/3'rds wide on my screen - black bar area to the right
> only about 5/6'ths is on the screen - some half of the bottom third is below the screen

About: UVGA_240M_720X288
>> Monitor reports 640x480 at 53 Hz
> only ~4/5'ths wide on my screen - black bar area to the right
> only about 5/6'ths is on the screen - some half of the bottom third is below the screen

On both of these doing Auto Adjust brings the width in then scrolls to the bottom, resets to top of image and that leaves the bottom cut off.
This is on the DELL 1908FP ( old 19" display roughly square with USB HUB }
 
Can someone try these 50Hz settings please ?
It's PAL- Timing

They work on my displays (but - still - not with my TV)

Edit: The display is 2/3 wide :)
Edit: How can I calculate the position shift ? I guess, it should be near 180 (this give a nice "letterbox" format 4:3 on my 16:9 display), but what's the formular?
Edit: Modeline taken from here: https://www.mythtv.org/wiki/Modeline_Database#PAL625_itu-r.2Fbt:_470_601_656

I will try them on my TV this evening because my monitor seems to reject all non-60Hz modelines.

Position shifts have no formula, they are obtained through experiments. The only constraints are they must be between 0 and hsync_end. Values in the middle of the range will probably give nothing and if the values are close to hsync_end, the first image line will be empty.
 
Edit: How can I calculate the position shift ? I guess, it should be near 180 (this give a nice "letterbox" format 4:3 on my 16:9 display), but what's the formular?

I plan to add new parameters to modeline settings to create something like letterbox image. Only 2 new parameters should be required, the number of lines to display as empty lines at top and bottom of screen.
 
Can someone try these 50Hz settings please ?
It's PAL- Timing

They work on my displays (but - still - not with my TV)


Edit: The display is 2/3 wide :)
Edit: How can I calculate the position shift ? I guess, it should be near 180 (this give a nice "letterbox" format 4:3 on my 16:9 display), but what's the formular?
Edit: Modeline taken from here: https://www.mythtv.org/wiki/Modeline_Database#PAL625_itu-r.2Fbt:_470_601_656

As expected, my monitor rejects both mode because they are not 60Hz. My TV also fails to recognized them and cycle between backlight on and off.

Does these modes works with 60MHz F_BUS ?
 
ok, thanks. I've tried 120MHz only.
I'm still wondering why my TV does not display anything, in any mode... or, maybe the VGA input is not working. Don't want to move my PC to the TV...
 
Back
Top