In a previous video we built this circuit that generates a valid VGA signal, and when we plugged a monitor in
we saw that the monitor recognized that it was in 800 by 600 mode.
And that's because we're sending at the right horizontal and vertical sync pulses for that mode.
We're sending a 3.2 µs horizontal sync pulse 38-whatever thousand times per second and
we're sending a 0.1056 ms sync pulse
60 times per second. And the way this all works is we have a binary counter here that's counting from 0 to 264
you know over and over again, and it's counting at exactly 10 million times per second.
So we can look for these particular values to get the pulse width and the pulse frequency
to match exactly; and then same thing in the vertical direction, you know
we've got another counter here that counts each line on the screen from 0 to 628
and of course, you know
we measured all the timing and everything in the last video to make sure it matched the specs and it did and
so we saw the monitor recognize it as a valid signal. So if you want to know more about what's going on here,
I definitely recommend you check out the first video, but now you know
we probably want to actually display something on the screen.
And to do that, the VGA interface has a few more signals for red, green and blue. and
Because we're using the sync signals here to stay synchronized with the monitor
we can use our horizontal and vertical counters to keep track of what part of the screen the monitor is currently
"painting" at the moment. And so the 0 to 200 tells us where it is from left to right, and then the 0 to 600
Will tell us where it is from top to bottom and so as the monitor paints the screen
You know, left to right, top to bottom: the same way you'd read a page in a book.
These red green and blue signals is how we tell the monitor what colour pixel we want at that particular location.
In other words: because we're synchronized, we can use our counters to know exactly what pixel the monitor is.painting at the moment
So for example if I take just one bit from one of these counters
(so this is the the vertical counter) if I take those, you know 1 2 4 8 16s place
Let's say this 16 s placed bit right here
you know as we count down the screen from 0 to 600 down the screen this bit is going to flip on or off every
16 scan lines.
So if I take that bit and hook it up to the the green signal and I'm hooked it up through a
resistor (for reasons that I'll get into here in a minute)
There we go
You know
You can see every 16 lines the green flips on and off and that's because as our article counter counts from 0 to 600
every 16 counts this
16 places bit flips on and off because that's how binary counters work
Now if I hooked the red up here to the eights place in the same way
There we go
and now you now you can see the green and the red as well as
Yellow when the two mix and then of course, you know, I can also hook up blue
So, let me just do that for the sake of completeness
And now you can see the three primary colors mixed to give us actually 16 distinct colors
but you know as much fun as colorful stripes are what I really want to do is display a more complex picture and
for that to work
We need the pixel data stored in some memory somewhere
Normally in a computer the data for whatever's on the screen is stored in a part of RAM
that way the software running on the computer could just write new data to the same Ram location and the image on the screen will
Change right away, but I'm not gonna hook this to a computer at least not yet
So I'm just gonna store the image on an EEPROM and this is a 28 C
256 EEPROM so it'll hold 32 k of data which should be enough for an image of some sort
and the way this will work is we already have our horizontal and vertical counters giving us an x and a y
position and if we feed all of those signals into the addresses of the
EEPROM
Then the EEPROM will give us a byte stored at that address and that byte could be the color of the pixel for that particular
XY location
So that's how we'll store the image in the EEPROM
But there's one weird issue which is because we went with the 10 megahertz pixel clock instead of 40 megahertz
we only have 200 pixels by 600 instead of 800 by 600 and
200 by 600 is kind of a weird resolution
No
The pixels would be kind of stretched out because really what we're doing is we're repeating each pixel four times as we go across
So it'd be nice to maybe slow our vertical counter down. So we repeat each line four times as well
And it's actually pretty easy to do if we have our counter counting in binary like this
You know here we're just counting 0 1 2 3 all the way up to 16, you know, just counting in binary
Well, if we lop off that last bit
now it goes 0
0 1 1
2 2 and so forth up to 8 and if we lop off another bit
Then it repeat each number four times and only counts up to 4 instead of counting to 16
So if we just ignore the bottom 2 bits of our Y counter that all divided the counter by 4
We actually still have another problem which is the EEPROM. I have the
22:56 it actually only has 15 address lines so we couldn't fit all these address lines here
Even if we wanted to so we've actually got to get it rid of at least three address lines in order
For it to actually fit in this EEPROM
So given what we've got with this EEPROM and to keep the right
proportions end up having to drop three bits from our Y counter and one bit from the X counter and that leaves us with a
Final image resolution of 100 pixels by 75 pixels which you know, I don't know it's maybe not the most impressive resolution
But I mean, what do you want for me? I'm trying to build a video card on bread boards
So 100 by 75 is is what we get. So let's hook the EEPROM up like this
I'll start by adding another breadboard and connecting power and ground to the EEPROM
Then I'll tie the right enable pin high since it will only be reading and it's active low and
Output enable and chip enable are both active low as well
So I'll tie them both low, so everything's enabled for output
Then the first seven address lines go down to our horizontal counter
and again
We're skipping the first bit and the next seven address lines go to the vertical counter and this time skipping the first three bits
So that's 14 address lines 7 for X 7 for Y. There is a 15th address line, which we're not going to use
So I'll just tie that to ground
So now we've got all of our address lines connected like this
So for each of our 100 by 75 pixels, we should be getting a byte of data out here
That'll presumably tell us what color that pixel should be
But how are we going to turn this 8-bit data into the signal that the VGA monitor expects?
well
the VGA interface has three pins red green and blue and each expects a voltage between zero and point 7 volts and
Depending on what that voltage is
Whether it's closer to 0 volts are closer to 0.7 volts
Determines how much of each color is mixed together to determine the color of the pixel?
But we've got eight bits here of data that are either 0 volts or 5 volts. How do we get something that's point 7 volts
well is the case where we have one voltage and we need a lower voltage and so we can use a voltage divider and
A voltage divider is just two resistors like this. So relative to ground down here
We've got 0 volts and up at the top. We've got 5 volts and in the middle here
It's going to be somewhere between 0 and 5 volts and how far it is from 0 volts to 5 volts
Depends on how big r2 is compared to the total resistance
So if r2 is half of the total resistance that is r1 and r2 or the same value then in the middle here
We're gonna have half the voltage. So two and a half volts if r2 were 10% of the total resistance
Then we'd have 10 percent of the total voltage or half a volt
So this expression just describes that?
You look at the proportion of r2 to the total resistance
And then that tells you how much of the total voltage you get
Now the VGA spec says that the red green and blue signals it says, you know zero to 0.7 volts
It also says it's got this 75
ohm input impedance and
You can roughly think of that as meaning that inside the monitor
Those red green and blue inputs are kind of connected to ground like this through a 75
Ohm load of some kind
so if we've got five volts and we want to get it down to
0.7 volts as it's going into the monitor here. We can just add a resistor like this and that creates a voltage divider, right?
So we've got five volts at the top
We've got ground here at the bottom and we want this to be 0.7 volts here in the middle
Now if the monitors input impedance is 75, ohms
we can't change that but we can put whatever resistor we want here between our 5 volts and the input to the monitor and
You know to figure out what resistor that needs to be in order to get 0.7 volts
well, we could just look at this expression here and say you know
When would this expression equal 0.7 volts given that r2 is is going to be 75, ohms
well, we can put
75 ohms in here for r2 and set it equal to 0.7 volts and we just need to solve for R
So 5 times 75 is is 375 and then R + 75 times 0.7 is gonna be 0.7 R + 52 and 1/2
Subtract that 52 and a half from both sides we get 0.7 R
Equals 3 22 and a half we can divide that by 0.7 and we get R equal to four hundred and sixty point seven. Ohms
So if we take five volts and we put it through a four hundred and sixty point seven
Ohm resistor will have point seven volts here going into our monitor, you know given that there's a 75. Ohm load inside that monitor
But ideally we don't just want point seven volts or zero volts going into the monitor here, you know
ideally
we'd be able to have a range of voltages between zero and point seven volts so we can get different brightnesses of red green and
Blue, yeah, so that we're able to have different shades of colors
so for example
If we wanted four different voltages from zero at 0.7
Evenly-spaced so we could get four different shades of red green and blue
We'd have to solve this equation essentially for each of those different voltages to find out what resistor we need to get that particular voltage
So that's what I've done here. That's what these different resistances are and
This is for you know, one-third brightness two thirds brightness, and then of course full brightness at 0.7 volts
Which is what we just we just figured out is four hundred sixty point seven
And we could use these different resistances to get these different voltages so that we end up with these different
Brightness levels for each of the colors and of course, you know, they don't make fifteen hundred fifty five point four
Ohm resistors they make you know fifteen hundred. Ohm resistors and they don't make
720 two point nine. Ohm resistors, but you know, I've got 680 ohms which is maybe close enough
So if we use those resistors to build something like this that's got two inputs over here that can be you know
Either 0 or 5 volts and an output over here and check this out
You know if both inputs are zero like this then the output table. These are going to be 0 volts, right?
But if one input here is 5 volts
So the one hooked to the 1.5 cave' resistor is 5 volts and this is a zero then essentially we're kind of in this scenario
Here where we have, you know, approximately 1,500
1,500 ohm resistor and we're gonna get about
0.23 volts over here
But if we flip that around and we we don't have anything here and we have five volts down here
Then we're going through the 680
Ohm resistor and we're gonna have you know about 700 or well we're gonna have about 0.47 volts
But then if we have ones on both of these inputs, so both of these inputs are 5 volts
Well, then these resistors are going to combine in parallel and to combine resistors in parallel
You use this expression here, which is sort of the sum of the reciprocal of the sum of the reciprocals
So to combine 1,500, ohms and 680. Ohms in parallel like this
It's going to appear as a single resistance from 5 volts to this point over here of 468
Which is pretty close to here and so we're going to get 0.7 volts, so
this takes two inputs that can either be 0 or 5 volts and
gives us the 4 different possibilities that give us these 4 different voltage levels from 0 up to 0.7 volts and
So with just a couple resistors
we can take 2 bits of binary data and convert that into one of 4 different voltage levels from 0 to 0.7 volts and
If we do that for red green and blue
we can use six bits of data coming out of our EEPROM to get four different shades of red green and blue which combine into
64 different colors and so this is the mapping for those different colors
If you're familiar with these hex codes for colors
But you can see the last two bits here controls how much blue there is now the middle two bits controls
how much green there is and then the first two bits controls how much red there is and
Then this here is what those actual colors look like
And so those are the 64 colors that were able to generate with a circuit like this
So let's actually hook these resistors up to the outputs of our EEPROM and to here the 1.5 K resistors
And of course, there's three of them one for red green and blue and here are the 680
Ohm resistors and so hook the first two data bits up for blue
And the first one is going to go to a 1.5 K resistor and then the next one will go to a 680
Ohm resistor, then the next two bits will be for green. So go to a 1.5 K resistor and then to a 680
Ohm resistor and then finally the last two bits are gonna be for red
The 1.5 k resistor and the 680
Ohm resistor and then we just need to tie this side of the the resistors for each color together
So they'll do blue
Green and red. And so now over on this side we should be getting that voltage between zero and 0.7 volts
So now we can try hooking this up to the monitor again
So here's our 15 pin vga connector and i've got the the same sync signals before in the ground, of course
But now i've got the red green and blue hooked up to pins 1 2 & 3
so we can hook up ground and hook up our sync signals as before so the horizontal sync and
The vertical sync and then the colors we could just hook up over on this side of the resistors
So there's blue there's green and there's red
now if we power up our circuit and plug in the monitor
So we basically see the same thing as before so the monitor comes alive
so it's detecting the sync signal but it's still a blank screen and
Of course, that could be because what's in the EEPROM is just blank
But I happen to know that this is an erased EEPROM and when you were to race an EEPROM it it just writes all one's
So we should actually see a white screen and we're not
but I think the problem is that if every address in the EEPROM is
Set to all ones then
We're gonna be outputting a white for every position and that includes even in the blanking time here
And we really shouldn't be putting out any pixels in this blanking time because well it should be blank
And in fact, this is one of those things that might actually damage a CRT monitor and hopefully we won't damage my monitor
But the way we can fix that
Is use these signals that were detecting here for the blanking intervals, right?
Because we're actually detecting when we're in this horizontal blanking interval
Right here with this flip-flop and we're detecting when we're in the the vertical blanking interval down here
So really what we need to do is we need to figure out are we in the blanking interval and those together?
and we could actually use the result of that for the output enable signal for our EEPROM so we can essentially turn the output of
The EEPROM off if we're in that blanking interval
so what I'm going to do is add a NAND gate here and I'm using a NAND gate so that the output is
inverted because our chip enable or not our tripping well actually both
But the output enable which is the one we're going to use the output enable is active low
So that way if if two inputs here are high
then the output will be low and then the two inputs that we'll use is well look at if we're in the display period for
horizontal and the display period for vertical
so, let me hook up power and ground for our NAND gate and
so the first input here is going to say are we in the horizontal display period
So in other words, this is saying are we in this time here?
and then the next input is going to say are we in the vertical display period
So that's going to say are we in this time here vertically?
If the answer is yes
if we're in both of those then what we want to do is we want to take the
Output of that and use that instead of just always tying our output enable low. So here we'll say output enable
Only when both of these conditions are true
So now let's reconnect our monitor and see what we get
And that's still not working
But it's possible that I've got these mixed up and so I'm actually outputting only during the blanking interval
So, let me just switch this over to
the
inverted outputs on both of these
And there we go now it actually looks like we're getting looks like a white screen
So if we want to display something a little more interesting then we've got to come up with a little more interesting image
Let's find an image. That's nice and colorful. This looks good public domain. That's perfect
So I'll download I guess, you know, we're gonna reduce the resolution here quite a bit. So it doesn't matter too much
So then if I load that image up in Photoshop, we can resize it
So we want our image to be 100 by 75
So 100 by well make it 114 by 75 and then we can crop it to 100 by 75
Okay, so there's our image with the resolution that we have on our screen. It's now we need to change the color modes
Who are using indexed color and we want a custom palette?
and
So these are the 64 colors that we can produce with our resistor voltage dividers and they're also in the in the right order
so for example an index of 8 is this
You know green color and if we have an 8 in our EEPROM then that's going to produce that same green color
So it's pretty important that we have the right colors in here and we have them in the right order
So if we apply that
This is the image we get so those are the 64 colors that we support and it's a 100 by 75
So that is that is the best our wonderful breadboard video card can do so, let's go ahead and save this
We'll call it image dot PNG
And if we actually look at what's in that file if we see, you know, there's more than just a pixel data, right?
There's a header here PNG. There's you know appears to be some XML stuff in here
So there's a whole bunch of stuff in here that we definitely don't want to write to our EEPROM
so what we've got to do is we've got to convert this PNG data into the pixel bytes that we want to put into our
Indoor EEPROM and the best way I can think to do that is just with a little Python
Script, we can use a Python image library to make a little bit easier. So pale is this Python image library
And we can open up an image file
and then we can load the image and when we load the image what we get is we just get a
two-dimensional array of the pixels and
because we're using index colors with 8 bits and
You know, we've got our colors in the right order here with the right index values
the pixels that we end up loading are going to be the values that we want to actually
output from our EEPROM to get those particular colors
So really what we want to do is we just want to write all those pixels to a file without any of the PNG header
Information without any compression without anything like that
We just want to write the raw pixels to a file and then we can write that file the EPROM
That's what I'm gonna do is just go through all the pixels. And since our image is 100 by 75
I'll just go through all 75 lines and then go through all hundred pixels per line
and then I just want to write the value of that pixel to a file so
I'll create an output file
just call it image bin and we'll just write to this as a binary file and
What we'll write to it is we'll write a character that has the value of the pixel
so the pixel is going to be coming from our pixels array and it's just gonna be at the
XY value that we're at
So if we run this that'll create that image bin file
And if we look at the image that bin file what we should see in here is we should see all of the pixels
so, I don't know if this looks like a bird to you, but
One sign that we're on the right track here is that all of these values in here are less than 64?
Or while they're in hex, but they're less than 40 hex which is which is 64
So at least everything seems to be you know in the right range
But this isn't quite how we want to organize the data in the EEPROM
because remember we're using
7 bits for the exposition and 7 bits for the Y position and so because we have a hundred pixels per row
We have this X counter that goes up to 99, but once it gets to 99
It doesn't just go to 100
it goes back to zero and then we increment our Y position and so the actual address in the EEPROM will jump from 99 to
128 because these all go to zero and then this bit will be a 1 and that's the hundred twenty eights place
so in the EEPROM really even though we only have a hundred pixels per line on the screen in the EEPROM we want to take
Up 128 pixels per line, but that's easy enough. We can just for each line instead of a hundred we can just do 128 and
Then because we're writing that 128 times we'll end up writing 128 things to the file. So let's run the conversion again and
we get an error so image index out of range and that's because you know
our image only has only has 100 pixels across so
You know when we're trying to get pixel 128 or even pixel 101 or 100 that pixel is not actually in our image
So what we can do is we can just try to catch that condition
So we get an index error when we try to get that pixel. That's not actually part of our picture
What we can do is we can still write to the file
But the character will write to the file would just be I mean, it could be I guess we'll just write a zero
So let's run that and now if we look at our image
We still see the data but what we see is we see, okay
This must be the first hundred pixels, and then we have a bunch of zeros that Pat it out to
128 and so you can see like this block here
That's the first line and then this next block here with all the zeros padding it out to the end
That's the next line
And so this should be the format of data that we want to put into the EEPROM
Now I suppose I could use the EEPROM programmer that I built in a previous video
But in this case because I've got all the data in a file and everything already
It's probably easiest just to use a commercially prom programmer. It's also a lot faster. Let's pop our EEPROM out put it in the programmer
Then I'll use Mini Pro which is just an open source EEPROM programmer tool that works with this programmer and
Will tell it we're using the 1828 C 256 which is the EEPROM
We've got and we want to write the image bin file to it and it says incorrect file size
9600 so that's because I guess it's expecting a file. That's the exact size of the EEPROM and we have a 32 K EEPROM
So we need to give it a 32k file
And the reason our file is is smaller is because our Y is only counting from 0 to 74 even though you know
This is 7 bits so it could go up to 128
And actually there's an a 14 right that's that's this extra address line that we just tied to zero
So there's actually another 8 bits here
So our Y value really could go from 0 to 255
But we're only writing the first 70 75 values of that and that's just so that our you know image aspect ratio makes sense
We can do the same thing here. We can just change this to 256 and
Then anything that's out of range will just write a zero to the file
So let's reconvert and then if we take a look at our image file
you can see the image but then if we go all the way down to the
you know if we get all the way down at the bottom it says
we have got a row of zeros and then I think this star just means everything is zeros between there and
Eid 0 0 0 which is 32,000
So let's try to write it again. And there we go. It's writing it
And so now let's put our EEPROM back in and see if we get the picture of that bird
Hey and there it goes and
So this is our completed video card, but you know, it's producing an image from the ROM. So I'm going to call that a success
Well, you might notice these thin black vertical lines and I think that's because the ROM I'm using is relatively slow. Yes
I'm using a 10 megahertz clock which means it's counting 10 million times per second or you know
Once every hundred nanoseconds remember the monitor still assumes I'm using a 40 megahertz clock
so really the monitor is expecting a pixel every 25 nanoseconds and
So here's the problem if we look at the data sheet for the EEPROM that we're using
There's a delay from when the address goes valid. The address lines are valid to when the output is valid
That's this TAC C or access time and if we look at access time here the addressed output delay
It's a maximum up to 150 nanoseconds for the dash 15, which is the one I'm using
Well 25 nanoseconds per pixel
150 nanosecond delay could mean up to six pixels could be
invalid in this in this period from when the address changes to when the output is actually valid and you know
It makes sense that sometimes invalid might just mean that the output is zero
And so I think that's why we sometimes see a few black pixels like this
Now in a computer like I mentioned before the image would be stored in SDRAM
Which is designed to be very fast access for this very reason
And of course the you know the software running on the computer could write new data to that RAM and the image would change right?
away
whereas here the only way to change the image is to reprogram and/or swap the EEPROM which as
You can see even if I speed it up when I edit. The video is is pretty slow
But you know, that's not really the point is it, you know
Really the goal was just to get some kind of image on the screen and I'd say this is a success
And if you want to try this yourself, you can head over to my website
I've got schematics data sheets and where you can get all the parts and as always, you know
I want to thank my patrons for making it possible for me to create videos like this as you can imagine
these were rather time-consuming videos to plan out and produce and
without the support of these people and all my patrons who are just viewers like you
Making this kind of video just wouldn't be possible. So, thank you