I doubt if that would be fast enough. See my thread about connecting to a VGA monitor.
Borrowing from that page, so as to not make a link-only answer ...
For 640x480 pixels of active video you have something like this:
Let's start with the vertical sync pulses. In fact, we'll show how all the timing data can be derived from three figures:
- The screen refresh rate (eg. 60 Hz)
- The screen resolution (eg. 640 x 480)
So what is the refresh rate? It's the rate at which the entire screen is redrawn. If you ever looked at Windows screen resolution you probably saw something like this:
That's the refresh rate: 60 Hz (60 times a second). This figure was probably originally chosen because it is the mains frequency in the USA, so that would minimize the artifact of mains hum bars appearing on the screen, in the days of CRT monitors.
The other figure of interest is the screen resolution.
In our case we are going for the minimum we can (640 x 480) and seeing where those figures lead us.
Starting with the refresh rate, a 60 Hz refresh rate will require a "vertical sync" pulse 60 times a second, or a period of 1/60 (16.66 ms).
Generating vertical sync pulses
This code in my VGA output sketch generates the vertical sync pulses:
// Timer 1 - vertical sync pulses
pinMode (vSyncPin, OUTPUT);
Timer1::setMode (15, Timer1::PRESCALE_1024, Timer1::CLEAR_B_ON_COMPARE);
OCR1A = 259; // 16666 / 64 uS = 260 (less one)
OCR1B = 0; // 64 / 64 uS = 1 (less one)
The clock period is 62.5 ns (1/16000000). By applying a prescaler of 1024 the timer counts up once every 64 µs. So to get a period of 1/60 of a second (16666 µs) we need to count up to 260 (16666 / 64 = 260). Since the timer counts are zero-relative we set OCR1A to count up to 259. This sets the frequency of Timer 1.
Then we need to set the pulse width by putting the correct value in OCR1B. We want a pulse width of two lines. One line is 1 / 60 / 525, namely 31.7 µs. So two lines would be 63.4 µs. This is close enough to exactly one timer count (64 µs). So OCR1B is set to zero (being zero-relative, making it zero gives a count of 1).
Timer 1 is configured to "clear B on compare" which effectively means that it toggles the output pin (D10 on the Uno) so it is high for the duty cycle width (64 µs) and low the rest of the time. If you wanted the opposite sync pulse polarity, change CLEAR_B_ON_COMPARE to SET_B_ON_COMPARE.
The timer is also set up to generate an interrupt, which is used to tell the code that we are starting another vertical cycle, by setting the line count to zero:
vLine = 0;
messageLine = 0;
backPorchLinesToGo = verticalBackPorchLines;
} // end of TIMER1_OVF_vect
Horizontal sync pulses tell the monitor when to start drawing each line.
To calculate the horizontal sync frequency we need to divide the overall frame rate (60 Hz) by the number of total lines (525 if you count the sync pulse itself, and the front and back porches).
In other words:
(1/60) / 525 * 1e6 = 31.74 µs
We also need to know the sync pulse width. That is documented to be 96 pixels, so we need to know the width of one pixel. That would be the figure above, divided by 800 (being the total screen width including the sync pulse, and front and back porches).
Thus that is:
((1/60) / 525 * 1e9) / 800 = 39.68 ns
1 / (((1/60) / 525 * 1e6) / 800) = 25.2 MHz
So our pixel width is 39.68 ns and the pixel clock is 25.2 mHz.
The horizontal sync pulse width is 96 pixels, so we want a pulse of:
96 * 39.68 ns = 3.8 µs
Generating horizontal sync pulses
Now we are ready to set up the timer for the horizontal sync pulses:
// Timer 2 - horizontal sync pulses
pinMode (hSyncPin, OUTPUT);
Timer2::setMode (7, Timer2::PRESCALE_8, Timer2::CLEAR_B_ON_COMPARE);
OCR2A = 63; // 32 / 0.5 µs = 64 (less one)
OCR2B = 7; // 4 / 0.5 µs = 8 (less one)
The clock period is 62.5 ns (1/16000000). By applying a prescaler of 8 the timer counts up once every 0.5 µs. So to get a period of 32 µs we need to count up to 64 (32 / 0.5 = 64). Since the timer counts are zero-relative we set OCR2A to count up to 63. This sets the frequency of Timer 2.
Then we need to set the pulse width by putting the correct value in OCR2B. We want a pulse width of 96 pixels (3.8 µs). So OCR2B is set to 7 (being zero-relative, making it 7 gives a count of 8, which is 4 / 0.5).
Again, if you wanted the opposite sync pulse polarity, change CLEAR_B_ON_COMPARE to SET_B_ON_COMPARE.
The timer is also set up to generate an interrupt, which has the sole purpose of waking the processor up from sleep, so it can draw each line with exactly the same delay after the pulse. If it wasn't asleep, there would be a variation of two to three clock cycles (since an interrupt cannot occur during a single instruction) and this gives very bad-looking "jitter" on the screen.
} // end of TIMER2_OVF_vect
Now things get tricky ...
Let's see how long we have to draw 640 pixels:
((1/60) / 525 * 1e9) / 800 * 640 = 25396.82 ns (25.39 µs)
It just can't be done on this processor. The clock period itself is only 62.5 ns, so there is no way we can output a pixel every 39.68 ns (that is: 25396.82 / 640 = 39.68).
The fastest way we can get bits "out the door" is using the SPI hardware. That can run at a maximum clock rate of twice the system clock, that is, one pixel every 125 ns. So we will have to settle for having the pixels 4 times as wide. A horizontal resolution therefore of 160 pixels. Since we will display 8-pixel characters, that gives us 20 characters per line.
Even at 25 MHz processor speed (which exceeds its spec) you won't be able to clock out pixels fast enough for 640 pixel width (since the fastest SPI speed is half the processor clock speed).
I would expect that so long we can feed the signal fast enough that would be enough.
That's the tricky bit. To output data you have to do more than merely send it. You have to read it from memory, so it would take a least a couple of clock cycles to do that.
Example of working at that resolution
A screen shot of my sketch in action (160 characters wide - 640 pixels and 30 characters high - 480 pixels, which is 16 pixels each as they were "doubled" in height to make them look in proportion). The font data was 8x8 pixels, and thus they appear as 16x16 on the screen.
So does that mean that there really isn't a direct relation between clock speed and resolution? Other than, the clock must be faster than this
To work even at that speed the processor speed needs to be a multiple of the VGA clock, or close enough for the monitor to accept. (Looking at my figures the pixel rate is not in fact a multiple of 39.68 ns, so I am guessing that my monitor was able to adjust).
You could get better results by having a system clock which was an exact multiple of 39.68 ns (around 25 MHz - precisely 25.201613 MHz) - which is what you suggested in the question. However that just gives more accurate timing, you would still get similar resolution to what I show here.
So yes, to work on more monitors you should have a direct relation between clock speed and resolution.
I seem to have a 25.175 MHz crystal in my parts drawer, so perhaps I experimented with this a while back. Of course, you have to recalculate the other timings if you use that, and the processor is running out of spec.