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,
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.
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,
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: