Bit banging VGA output with Teensy 4.0

Status
Not open for further replies.

bloodline

Well-known member
I've finally managed to get something working, so I thought it would be a good idea to post my progress just in case it is of use to others.

Theory
The VGA display consists of 525 line of 800 pixels, updated every 16.6667ms. This means that each line is drawn within 31,777.30ns... dividing by 800 means that a pixel needs to be drawn every 39.72ns.

A VGA line is made up of 4 parts, a "Front Porch", a "Sync Pulse", a "Back Porch", and finally an "Active Pixel block", these are sized like this:
Front Porch = 16 pixels
Sync Pulse = 96 pixels
Back Porch = 48 pixels
Active Pixel Block = 640 pixels

My idea was to divide these blocks by the lowest common denominator, and generate an interrupt at that frequency. In this case all sections are divisible by the Front Porch time. The Front Porch duration is 635.55ns, and all other sections are a multiple of that.

To use the interval timer, I had to edit the IntervalTimer.h code to remove the 17 bus cycle limit (which limits the interval to around 700ns, and seems oddly arbitrary since the CPU frequency is much faster than the bus frequency), I then implemented an interval timer callback as a simple state machine, to update the display as required. The vertical stuff is is handled in the front porch state and is not doing much more than generating a vSync pulse for two lines when needed.

Currently only a single pixel is drawn per active pixel block interrupt (alternating black and white), which should draw 40 vertical lines on the screen, but the hope will be to use this time to draw 16 pixels, this will block the interrupt for the duration, but since only the visible (where vLine is greater than 44) active pixel blocks will be blocking, this should still be useable. Or if I figure out how to DMA or some other I/O method, then pixel drawing won't need any CPU time :)

You will notice that the interval timer is not super stable (which messing with my monitor...), which I guess is due to the PIT being based on the 24MHz bus clock (for VGA the ideal clock frequency is 25.1752Mhz)... If we can increase this freq it might be better?

So, Please comment, point out what I've done wrong... or could improve? Thanks, :D

As always, my quest is to make the code as simple as possible and to use as few nonstandard features (editing the IntervalTimer.h is not something I'm happy about, but I think Paul should remove this limit) to ensure upward compatibility.

Code:
#define H_FRONT_PORCH_START 0
#define H_SYNC_START 1
#define H_BACK_PORCH_START 7
#define H_PIXEL_BLOCK_START 10
#define H_LAST_PIXEL_BLOCK 50


IntervalTimer hEvent;

int state = 0;
int hSyncPin = 13;
int vSyncPin = 14;
int pixPin = 16;
int hState = 0;
int vLine = 0;

void hRun(){

  if(hState == H_FRONT_PORCH_START){
    digitalWriteFast(pixPin,LOW);  

    //update vertical state
    if(vLine == 11){
      digitalWriteFast(vSyncPin,HIGH);    
    }

    if(vLine == 13){
      digitalWriteFast(vSyncPin,LOW);    
    }

    vLine++;
    if(vLine == 526){vLine = 0;};
    
    hState++;
    return;
  }

  if(hState == H_SYNC_START){
    digitalWriteFast(hSyncPin,HIGH);  
    hState++;
    return;
  }

  if(hState == H_BACK_PORCH_START){
    digitalWriteFast(hSyncPin,LOW);  
    hState++;
    return;
  }

  if(hState == H_LAST_PIXEL_BLOCK){
    hState= 0;
    state = 0;
    digitalWriteFast(pixPin, state);    
    return;
  }

  //DRAW PIXELS!!
  if(hState >= H_PIXEL_BLOCK_START && vLine >= 44){
    state = 1 - state;
    digitalWriteFast(pixPin, state);    
  }
  
    hState++; 
}

void setup() {
  // put your setup code here, to run once:
  pinMode(hSyncPin,OUTPUT);
  pinMode(vSyncPin,OUTPUT);
  pinMode(pixPin,OUTPUT);
  hEvent.begin(hRun,0.63555); // need to edit IntervalTimer.h and remove the 17 cycle limit
}

void loop() {
  // put your main code here, to run repeatedly:

}
 
Last edited:
You probably may want to look here - it does all with DMA in the background, so the CPU can other things :)

Hi Frank, thanks for sharing this, defragster has already pointed me to this thread.

I love what you have done with integrating your C64 emu on the Teensy 3.6...

As you can probably guess I have similar plans, now we have a microcontroller running at 600Mhz, I want to see if I can integrate the Amiga Emulator I wrote in a similar fashion!

I need minimum, 12bits per pixel... so I may have to wait until a pjrc release a larger form factor Teensy 4...
 
I watched the video - great!

It took a huge amount of work just to get it this far... The Blitter is still missing a couple of modes, sprites aren’t emulated at all (except sprite 0, which is emulated outside of the DMA sequencer)... But it runs system friendly software well, and quite a few games amazingly work too!
 
I don't know much about the technical details of Amiga.
If the sprites are nearly as complex (i guess they are more complex) as on C64...
It was an incredible amount of work to get them work on the 3.6 (they need to be incredible fast) with 50Hz - and it took its time to get their collision detection working. Finally, I used a "screen buffer" for one single rasterline, rendered one line of the sprites there with "on the fly" collision detection. A "hair puller" to get this fast enough... since they can be behind the background or in the front (or in front of sprites with lower priority..) ,pixel by pixel.

In the 80ies, the sound and graphics of AMIGA was incredible.

I hope I can play a AMIGA game on the Teensy 4.x this year ;)
 
I don't know much about the technical details of Amiga.
If the sprites are nearly as complex (i guess they are more complex) as on C64...

If you have any free time have a read of this.

http://amigadev.elowar.com/read/ADCD_2.1/Hardware_Manual_guide/node00AE.html

The C64 and Amiga sprite systems are somewhat similar (though the Amiga’s Sprite system heritage is, amusingly, from the 8bit Atari line). What makes the Amiga really difficult to emulator is the beam synchronised Co-processor...

It was an incredible amount of work to get them work on the 3.6 (they need to be incredible fast) with 50Hz - and it took its time to get their collision detection working. Finally, I used a "screen buffer" for one single rasterline, rendered one line of the sprites there with "on the fly" collision detection. A "hair puller" to get this fast enough... since they can be behind the background or in the front (or in front of sprites with lower priority..) ,pixel by pixel.

Hence why I’ve avoided sprites in my Amiga Emualtor for the time being... they are a lot of work, and I didn’t need them for my original project goal.

In the 80ies, the sound and graphics of AMIGA was incredible.

I hope I can play a AMIGA game on the Teensy 4.x this year ;)

:D Well, I hope so too... I think I’m going to need a lot of help from everyone here, I don’t have as much time as I used to have ;)
 
I think the scope of my project is too ambitious at this time. It’s a real struggle to switch the IO fast enough to generate the pixel data, not to mention the DA conversion... I’ve looking into using Shift registers like the 74HC595 and R2R DACs but this is increasingly drifting out of my project scope, for a “single chip” solution. It looks like the T4 does actually have some kind of display generation hardware on the die, but I can’t really figure out if it’s useful for this.

I have pondered if using a chip like the BT816 might be a sensible way forward, but the form factor of the package make it difficult for me to use.

https://www.mouser.co.uk/datasheet/2/880/Bridgetek_09252018_BT815Q-R-1483592.pdf
 
Last edited:
Hi bloodline,

Just discovered this mail thread today.
Nice project to restart an amiga emulator from scratch.

I was also my dream to run an Amiga on the T4.0
I had troubles to port UAE for some time by I finally managed.
The main issue was to have a contiguous 512k of RAM for the chipram in the T4. The malloc memory of the Teensy 4 is exactly 512K but with the overhead of the allocator and some memory allocated by some libraries, it was problematic.
At the end I used the same technique as on the Atari ST castaway port I had done. Split in 2 times 256k.
It is problematic in UAE as blitter, copper and bitmap routines are using pointers directly.
By ignoring this, some games are working as long the 68000 PC is handled properly.
Also, what is an amiga with 512k only nowadays...
At the end it kind of worked on the T4.0 without audio. Audio libs needed some extra memory that was not available.


When the T4.1 was released, all was resolved with the PSRAM driver MMAPED
I think Frank did some of the work there?

Last w-e I posted a video+code about the status in another thread (Amiga emulation on T4.1 in general discussion)
You can put comments there.

https://youtu.be/LE4IWPG5z6g

https://github.com/Jean-MarcHarvengt...ster/README.md

I hope to restart from a more recent version of UAE to fix some issues...
 
Status
Not open for further replies.
Back
Top