Teensy 3.6 VGA driver

Hi qix,
I have a solution for my usb-host problem, I think.
I add two more channels to the FTM with modified timing. They have interrupts enabled and let me en/disable the usb-host thing and read gpios (gpio-access causes flicker, too (same periphal bus))

What do you think ? Does this make sense, and do you know a better way?
Edi: I greatly under-estimated the needed communication to the keyboard. Keypresses have a little lag now, but that's the maximum time to enable USB - a bit more causes flicker, again...

If the solution solves your usb-host problem, I think I can convert your code into DMA requests. There is already one request at end of each SRAM_U line, adding one more TCD should not be a problem. I have no DMA request on FTM0 but I should be able to attach a new DMA channel to it or maybe add TCD to existing TCDs.
 
I think I am close to find a solution.

I first tried to add various capacitors from 1-2pF to 100nF between latch VCC and GND but it changes nothing.

I then add capacitor on VGA connector between GND and red/green/blue lines. Very little capacitances (1-3pF) have very little effect except grey lines seems to less fluctuate. With 100nF, I have no picture at all :) The correct value seems to be between ~70-80pF and 470pF. At 470pF, the image is too blurry, IMHO.

This 1st image is without any capacitor.
IMG_20171025_182516.jpg

This 2nd image is with 82pF capacitor on all colors lines.
IMG_20171025_182429.jpg

The image has slightly lost sharpness but is better.

This 3rd image is with 220pF capacitors.
IMG_20171025_185849.jpg

I just wonder if I should add capacitors between latch outputs and resistors (8 capacitors required) or after resistors (only 3 capacitors)
 
Last edited:
If the solution solves your usb-host problem, I think I can convert your code into DMA requests. There is already one request at end of each SRAM_U line, adding one more TCD should not be a problem. I have no DMA request on FTM0 but I should be able to attach a new DMA channel to it or maybe add TCD to existing TCDs.

I need a bit more testing before, but I think that would be great.
I'd need
a) write to DAC every 2. rasterline on the right border
b) read all GPIO_PDIR on every rasterline, right border (Port A , B, C, D, E , 32 Bit to 32-Bit Variables)
c) disable/enable USB on every rasterline, right border (enabled completely on all lines between 593 and 51 (line not visible, black, or c64-screenborder))

Do you think this is possible ?
 
I need a bit more testing before, but I think that would be great.
I'd need
a) write to DAC every 2. rasterline on the right border
b) read all GPIO_PDIR on every rasterline, right border (Port A , B, C, D, E , 32 Bit to 32-Bit Variables)
c) disable/enable USB on every rasterline, right border (enabled completely on all lines between 593 and 51 (line not visible, black, or c64-screenborder))

Do you think this is possible ?

I think all of this is doable using either 1 DMA channel or one for each case. At the end of each pixel DMA transfer (= 1 VGA line), it is possible to trigger another DMA channel. This is how the copy of SRAM_U line into SRAM_L buffer is done to avoid conflict with pixel DMA transfer.

It is possible to chain a 2nd DMA channel after this line copy. This means there will be a little time shift on SRAM_U line compared to SRAM_L line.

  • a) and b) are easy to do and require one DMA channel using 2 TCD (1 for (a) and 1 for (b) as long as the 5 GPIO_PDIR values are to copy into a uint32_t array). If (a) uses a circular buffer, a 2nd DMA channel will be required for (b) or (b) must be performed before (a) [in fact, nothing can be done after (a) in with this settings].
  • c) is a bit more complex. The "enable" part is easy and can be added on the previous DMA channel using 1 additionnal TCD. The main problem is the "disable" part. The only solution I can see now is 1 additionnal DMA channel triggered when Hsync FTM restart its cycle but DMA channel will start on all lines and copy a value from an array to USB controller to start/stop it.
 
a) write to DAC every 2. rasterline on the right border
b) read all GPIO_PDIR on every rasterline, right border (Port A , B, C, D, E , 32 Bit to 32-Bit Variables)
c) disable/enable USB on every rasterline, right border (enabled completely on all lines between 593 and 51 (line not visible, black, or c64-screenborder))

I just taked a look to eDMA registers and make a first attempt (not yet pushed on github).

My code should already be able to trigger DMA channel in all modelines just after the last image pixel and before the first image pixel. In this case, it occurs in fact at X=0, Y=-1, not at X=-1, Y=0 but a DMA event already occurs at this time and I kind of piggyback on it :)

Triggering channel on each line is unexpectedly harder than expected because the minor loop channel num linking and minor loop address adjustment are stored in the same register and if linking is enabled, address adjustment has less bit to store adjustment to perform. I still don't know if it will be a problem. I will try to take a look tomorrow morning at work :eek:.

I expect to push a working code by sunday noon if all goes well.
 
I just taked a look to eDMA registers and make a first attempt (not yet pushed on github).

My code should already be able to trigger DMA channel in all modelines just after the last image pixel and before the first image pixel. In this case, it occurs in fact at X=0, Y=-1, not at X=-1, Y=0 but a DMA event already occurs at this time and I kind of piggyback on it :)

Triggering channel on each line is unexpectedly harder than expected because the minor loop channel num linking and minor loop address adjustment are stored in the same register and if linking is enabled, address adjustment has less bit to store adjustment to perform. I still don't know if it will be a problem. I will try to take a look tomorrow morning at work :eek:.

I expect to push a working code by sunday noon if all goes well.

Wow..that's fast :)
I'm still working on Audio.. the line-frequency is modeline.pixel_clock / modeline.vtotal = 37878.7878 Hz (452x600x60Hz), is this correct ?
37878.7878 seems to be ok, but is faster than my current setup - don't know if the teensy is fast enough to emulate the sid and all the rest.
The half (=every 2. scanline) would be 18939 Hz ... dunno if this is OK, too :) Again, need to try this, first..
Can the trigger configured to be a bit earlier ? I have many black pixel on the right border,so it would not be visible.
 
Wow..that's fast :)
I'm still working on Audio.. the line-frequency is modeline.pixel_clock / modeline.vtotal = 37878.7878 Hz (452x600x60Hz), is this correct ?
37878.7878 seems to be ok, but is faster than my current setup - don't know if the teensy is fast enough to emulate the sid and all the rest.
37.8MHz is the line frequency but if I trigger on end of pixel line, it is not the obtained frequency, it is 37.8Mhz * vres / vtotal ~= 36.18993Mhz because the trigger will not occur during Vsync line.

An alternative whould be to trigger on Hsync FTM restart because this interrupt exists with or without pixel on the line.

The half (=every 2. scanline) would be 18939 Hz ... dunno if this is OK, too :) Again, need to try this, first..

A solution would be to send 2 times the same value. That's how Vsync TCDs are built. There is 3 values LOW, HIGH, LOW but each are sent multiple times. For example, 703x300 VSync is 1*LOW, 5*HIGH, 23*LOW

As soon as I have a working code, I will explain exactly where and when each trigger occurs, that will be easier to choose the correct trigger(s)
Can the trigger configured to be a bit earlier ? I have many black pixel on the right border,so it would not be visible.

No, the trigger occurs when the last pixel is sent. Instead of adding black pixel on the right, why do you not create a custom resolution with a smaller UVGA_HREZ. This will trigger DMA channel earlier, use less RAM and lower DMA controller load.
 
Triggering channel on each line is unexpectedly harder than expected because the minor loop channel num linking and minor loop address adjustment are stored in the same register and if linking is enabled, address adjustment has less bit to store adjustment to perform. I still don't know if it will be a problem.

I made an error, minor loop channel num linking is not in the same register as address adjustment but with major loop counter.

Triggering will be a problem only if frame buffer height is greater than 511 lines with line repeat = 1 and frame buffer totally fitting in SRAM_L (or DMA forced to single). Technically the smallest resolution matching this description is ... 123x512 (assuming 2KB preallocated by teensyduino buffers), a very weird and unusual resolution.
 
Ok..

I have audio solved - is (with default setting) FTM channel 7 as trigger correct ? It sounds ok, at least..
It was a very simple change - only three lines:

In output_dac.cpp:
Code:
    //dma.triggerAtHardwareEvent(DMAMUX_SOURCE_PDB);
    dma.triggerAtHardwareEvent(DMAMUX_SOURCE_FTM0_CH7);
and
Code:
 audioSampleFreq = ((float)modeline.pixel_clock / (float) modeline.htotal);
...
 FTM0_C7SC |= FTM_CSC_DMA;
 
I'm still a little concerned about the keyboard lag that occurs with my "usb dis/enable" technique... at the moment, I tend to leave it enabled and accept the minor flicker..
Maybe we should postpone this until later.

The syncing of DAC and GPIO solved most of the issues. DAC is solved (I think) - so the remaining "TODO" is to read all GPIOs on every line :) Then, I can get rid of the time-consuming interrupt.
 
Ok..

I have audio solved - is (with default setting) FTM channel 7 as trigger correct ? It sounds ok, at least..
Code:
 FTM0_C7SC |= FTM_CSC_DMA;

That's also the solution I think of yesterday evening. This FTM channel cycle at the exact moment a new line start, between the last pixel of the previous line and the first pixel of the new line, roughly in the middle and unlike pixel DMA, it occurs on all lines, having pixel line or not.

Settings FTM0_C7SC should not be required because it is already set (3 last lines of uVGA::clocks_init function). In fact, I am even a bit surprise this solution does not disturbe image generation.

so the remaining "TODO" is to read all GPIOs on every line :)

probably the easiest of all things to do. Just trigger a DMA event at the end of DAC DMA copy. You can also chain a TCD to DMA TCD. A single TCD is enough. If I make no error with this, it should be ok:
  • SADDR = GPIOA_PDIR
  • SOFF = GPIOB_PDIR - GPIOA_PDIR
  • ATTR_SRC = DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_32BIT)
  • NBYTES = sizeof(uint32_t)
  • SLAST = -5 * (GPIOB_PDIR - GPIOA_PDIR)
  • DADDR = address of a 5 uint32_t array
  • DOFF = sizeof(uint32_t)
  • ATTR_DST = DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_32BIT);
  • CITER = 5
  • DLASTSGA = -5 * sizeof(uint32_t)
  • CSR= 0
  • BITER = 5
 
Last edited:
I just pushed a new set of changes.

The library now provides 3 possible triggers:
  • start of image: at X=0, Y=-1 to avoid image disturbance
  • end of image: just after last pixel of last line
  • start of display line: occurs when beam reaches left side of the screen. This lets a little time because technically, it is modeline.horizontal_position_shift =0 and no screen resolution uses 0 here, the smallest used value is 2. It occurs even if no pixels are on the line and during Vsync. Its frequency is horizontal frequency.

These changes requires 1 additionnal FTM channel. X1 FTM channel default value changed from 7 to 6 and the new FTM channel uses X1 FTM channel value + 1 (7 by default).

I also added to demo using these triggers and png showing when triggers... trigger :)

I will try to add the last trigger (end of image line) tomorrow.
 
New trigger added: end of line.

Note: this trigger is driven by Hsync FTM. It triggers at position hsync_start if Hsync polarity is positive and hsync_end if Hsync polarity is negative.
 
As I am kind of stuck on flexbus demuxing, I am trying a different approch using flexbus controller "twin", sdram controller :)

Unlike flexbus, sdram bus is not multiplexed and thus requires no additionnal component compared to "resistor only" version but keeps some of advantages of flexbus (more clocking capabilities, dedicated unused bus, ...). It seems the ideal solution but it has a major drawback, teensy pinouts.

It is not possible to have 8 or 16 bit data output. Kinetis K66 has no 32 bits data pins but 8 and 16 pins exist (named SDRAM_D16->SDRAM_D31). Unfortunately, half of them has no teensy pins. Even worse, all available pins are not contiguous. Availables pins are D16->D19 and D28->D31. This means on 16 bit data, only lowest nibble and highest nibble are available (mask = 0xF00F). Moreover D16 and D17 are pins 0 & 1 => usb no more usable.

2 data encoding solutions are possible to use this:
  • store 8 color bits into 16 bit => double amount of bytes used by frame buffer, not very realist
  • interlace pixels in ram

Normal pixels are stored like this: Aa Bb Cc Dd (A is pixel upper nibble, a is same pixel lower nibble).
If stored like this: A0 Ba Cb Dc 0d, it cost only 1 additionnal byte per line (if not already exists due to line size alignment to multiple of 16). Each time, 2 bytes are read but after reading these 2 bytes, DMA only adds 1 to compute next address to use. Note: I have not check if K66 is little or big endian, order is just given as an example

All graphic functions must be adapted to be able to work with such a weird encoding but it is doable.

Pins used by SDRAM controller here are (color bit 0 => 7) 1,0,32,31,45,44,46,43. Half of them are on teensy back but except 0 and 1, all of them are located on the same side, bottom left of teensy, near sdcard socket.

If I find no solution to flexbus black line problem, I will this as fallback.
 
displaying more than 256 colors using ... 8 bits

While googling to find a solution to my flexbus latch problem, I find an interesting page.

Instead of encoding pixel in RGB332 (RRRGGGBB), it is encoded in RGBI2222 (don't know if the term exists :cool:). Each component is encoded on 2 color bits +2 intensity bits. All components shares a common "intensity". No need to modify the library to use this mode, only wiring should be adapted

3.jpg

Technically, each pixel can only contains 64 colors but each pixel can use a different intensity. Technically, it is 64 colors sharing intensity among 4096 possible colors.
 
Hello,

today I tried to get it to work. after I soldered everything as its described I tried to upload an example file.
It shows me red message and vga output stays black.
If anyone could help me it would be really nice. :)

In file included from C:\Program Files (x86)\Arduino\libraries\uVGA-master/uVGA_valid_settings.h:28:0,

from C:\Program Files (x86)\Arduino\libraries\uVGA-master\examples\HelloWorldColour\HelloWorldColour.ino:8:

C:\Program Files (x86)\Arduino\libraries\uVGA-master/uVGA_valid_settings_240MHz.h:48:17: note: #pragma message: 240Mhz 703x300

#pragma message "240Mhz 703x300"

^

C:\Program Files (x86)\Arduino\libraries\uVGA-master\uVGA_gfx.cpp: In member function 'void uVGA::scroll(int, int, int, int, int, int, int)':

C:\Program Files (x86)\Arduino\libraries\uVGA-master\uVGA_gfx.cpp:1103:17: note: #pragma message: multiscroll not finished, where should col pixel be put if source and destination area intersect ?

#pragma message "multiscroll not finished, where should col pixel be put if source and destination area intersect ?"

^
 
These messages are perfectly OK and normal.
Try to connect VGA-PIN 9 to 5 Volt - Some Displays (eg my TV) seem to want this.
 
While googling to find a solution to my flexbus latch problem, I find an interesting page.

Instead of encoding pixel in RGB332 (RRRGGGBB), it is encoded in RGBI2222 (don't know if the term exists :cool:). Each component is encoded on 2 color bits +2 intensity bits. All components shares a common "intensity". No need to modify the library to use this mode, only wiring should be adapted

View attachment 11957

Technically, each pixel can only contains 64 colors but each pixel can use a different intensity. Technically, it is 64 colors sharing intensity among 4096 possible colors.
Somehow I missed this post !
That's amazing !! I want to try that as soon as possible (--hm.. but will take a week or more :-( )
 
These messages are perfectly OK and normal.
Try to connect VGA-PIN 9 to 5 Volt - Some Displays (eg my TV) seem to want this.

Thank you. Today the test is working without any change. For no reason yersterday it doesnt do anythying :D
 
As Frank B says, these messages are normal.

C:\Program Files (x86)\Arduino\libraries\uVGA-master/uVGA_valid_settings_240MHz.h:48:17: note: #pragma message: 240Mhz 703x300

#pragma message "240Mhz 703x300"

This first message acts as a reminder of the current resolution. Most of the time, default resolution is set but default resolution depends on teensy frequency.

C:\Program Files (x86)\Arduino\libraries\uVGA-master\uVGA_gfx.cpp: In member function 'void uVGA::scroll(int, int, int, int, int, int, int)':

C:\Program Files (x86)\Arduino\libraries\uVGA-master\uVGA_gfx.cpp:1103:17: note: #pragma message: multiscroll not finished, where should col pixel be put if source and destination area intersect ?

#pragma message "multiscroll not finished, where should col pixel be put if source and destination area intersect ?"

This message is a notice about special case occuring in scroll function where it is neither vertical nor horizontal scroll and when destination area overlaps source area. In this case, all pixels are properly copied from source area to destination area but source area is not filled with "reset" color because the area to reset is not a simple rectangle but has a L shape (vertically/horizontally mirrored depending on scroll direction). As I seriously have a lack of time, I just did not program this case :)
 
Thanks for this awesome library!
I used it for an old school demo in my little YM2149 emulation project, TY2149 :)

Great work you too. When I see your demo, we can tell teensy CPU has a lot of computing power because all is performed without any dedicated hardware.
 
Back
Top