0
\$\begingroup\$

I love the Arduino boards. They're super easy to use and give me access to fairly powerful microcontrollers for all of about $12. Unfortunately, 16MHz isn't quite fast enough to display more than 120x60, 2-bit colour VGA (which is what I have right now on my Uno, using the VGAX library). Even trying to squeeze out 3- or 4-bit colour requires a resolution drop.

I'm making an Arduino-powered games console for the fun of it (I love my Mega Drive and thought it might be fun to see what I could achieve with Arduino). My plan WAS to have an Arduino Mega2560 running the game code and storing all the sprites in its 8KB of RAM, loading and unloading as necessary to/from an SD card or other external storage. The Mega would then send the 2-bit pixel data, 4 pixels at a time, via an 8-bit parallel bus (literally 8 jumper wires with two "acknowledge" lines) to an Arduino Uno, which would act as the display unit and use 95%+ of its processing power just spewing pixels out to the monitor/TV. That was fine, and worked well... but 2-bit colour just isn't quite enough to reach Mega Drive-era games, and is pretty hard to work with. So I decided to try to invent some sort of custom VGA controller...


Ideally, what I would like to do would be have a small SRAM chip (64KB would be HEAPS, even 8KB would be enough, though 16 would be better), which the Arduino talks to. The Arduino would create an array (i.e. the front buffer) in the first section of RAM, then just update the pixels as fast as it can. Then a custom circuit on the other side of the RAM, clocked at VGA-suitable speeds (~25MHz?) would read the pixels from the RAM (using some sort of counter to move through the array?). That way, even if the Arduino couldn't keep up, the VGA controller wouldn't be left without pixel data - rather, it would just be the pixels from the last frame. So you'd get tearing but that's not a major issue.

There's probably numerous reasons that would be hard to do. Different clock speeds, for a start. I don't really know how SRAM works but as I understand it having two different clock speeds trying to write and read at the same time might be bad. So...


I came across this post. In it they mention FIFO memory. Bingo! I didn't even know this was a thing! They refer to the SN74ALVC7804, a 512x18-bit FIFO memory chip. It sounds perfect. It would appear that the chip doesn't care if the clock is consistent or not, or whether the input and output clocks differ, as long as they don't go above 40MHz. It has pins to show when it's nearly full (and when it's full). It's 18-bit, meaning I can spew 3 pixels of 6-bit colour (nice and neat - a 2-bit resistor DAC on each colour wire, giving me 64 colours to play with) into the buffer so as to keep up with the VGA controller on the other side as it reads each of the 6 bits of data... and this is where I'm stuck.


So here is my real question: What would you suggest? I could always just buy an Arduino Zero/Due and be done with it, but that's no fun (and very expensive in Australia). I want some challenge, just so long as it's not physically impossible (i.e. trying to extract 120x60 4-bit colour from an Uno). I would need some way of switching between the first, second and third set of 6 bits from the FIFO. I'd also need some way of actually timing the pixel outputs (probably the hardest part, now that I think about it - not so much the timing itself but rather counting out the sync pulses, front/back porches, etc.). I'm not looking for someone to give me a straight-up BOM, just point me in the direction.

I love electrical engineering, but I don't have much experience with it. Plus I have no clue what kind of ICs you can buy to achieve this kind of thing. And finally, even though I'm a programmer first and EE second, FPGA programming just melts my brain.

\$\endgroup\$
5
  • \$\begingroup\$ The suggestions below include only the frame buffer and rasterizer. Think, how did a console with a 8MHz 68K cpu beat a 486 senseless in terms of 2d graphics capabilities? \$\endgroup\$
    – Lior Bilia
    Commented Nov 18, 2015 at 16:55
  • \$\begingroup\$ How do you mean? I don't know how the Mega Drive displays graphics. \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 22:57
  • \$\begingroup\$ @LiorBilia Forgot to tag you. \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 22:57
  • \$\begingroup\$ Character generation (font), overlays, sprites, color palette changes and so on. For example, how would you do overlays? Add them to the signal right in front of the DAC or do them on the current frame memory? This rules out the simplistic solutions, and basically forces you to use an FPGA. \$\endgroup\$
    – Lior Bilia
    Commented Nov 19, 2015 at 5:40
  • \$\begingroup\$ @LiorBilia Basically the plan was to do the actual rendering to the back or front buffer (depending on whether I had a back buffer) with the Arduino. The main issue I have is with the VGA signal generation itself. Rendering will be something to solve further down the line :) \$\endgroup\$
    – Clonkex
    Commented Nov 19, 2015 at 11:11

3 Answers 3

3
\$\begingroup\$

In effect, you're trying to recreate a color CRT controller with memory interface. This is perfectly possible, but it's much more involved than you realize. The physical implementation can be either an FPGA, as alex forencich suggests, or discrete chips. The discrete section will need something like the 74FCT series for horizontal timing, and can easily get by with 74HC for vertical timing.

First, as you realize, you'll have to generate display timing at 25 MHz - except that you won't. A 25 MHz VGA pixel clock implies that you're trying for 640 x 480 pixels, and this cannot be stored in a 64 kB RAM - it would require a 512 kB RAM. Instead, a 64 kB RAM will only support a 256 x 256 display, and this will only require a pixel clock of about 12.5 MHz. This is straightforward using a 9-bit binary synchronous counter, and can be realized with 3 74FCT161 counters. The vertical timing also uses a 9-bit counter, but 74HC161s can be used, since vertical timing is much slower than horizontal. The outputs of the two counters feed at least one static RAM, and there are at least 3 different approaches you can use for the interface.

1) FIFO - This is your first thought, but it's more complicated than you think. First, it only makes sense to transfer one byte (or 6 bits) of intensity data at a time, but you also have to store the address as well as the data. If you're going with a 64kB RAM, this means 16 bits of address along with 6 to 8 bits of intensity, and you'll need more than one FIFO. This in turn means that you'll need to ensure that the FIFOs remain synchronized. You'll also need to provide a mechanism to monitor the FIFO empty line and generate a write pulse to video RAM whenever the FIFO is not empty: that is, whenever there is data in the FIFO waiting to be written. Furthermore, you'll also need to provide a mechanism to keep memory writes from interfering with display reads. You can do this either by running the video RAM at 25 MHz, but alternating read and write cycles, or by permitting writes to RAM only during the non-display portions of the scan. This will occur during front porch, back porch, sync, or vertical blanking intervals.

2) Dual port RAM - Here's another device technology to look at. In this case you use the DPRAM as the video buffer, and feed one side from the video controller and the other from the Arduino. Be forewarned, a 64k x 8 DPRAM requires a package with a lot of pins.

3) Bank Switching - In this technique, you provide 2 video RAMs, and at any time one is being written to while the other is being read from. The state of the RAM is controlled by a flip-flop which can be triggered by the Arduino. So first (let's say) you read from bank A while writing to bank B. When you've completed writing a complete frame, you toggle the bank selector and the video is now read from B while A is being written to. This is in some ways more straightforward than the other two, but it does not permit local overwriting of areas of the image in the same way the other two approaches do.

\$\endgroup\$
10
  • \$\begingroup\$ Ah, excellent answer, thank you! You've done a bang-up job of clearing my head and sorting out my thoughts, although it would seem that you've got some of my numbers mixed up in the process :P For example, I actually knew that a 25MHz was only required for 640x480 but completely forgot that. I'm definitely not trying for full-res VGA, just 120x60 for the time being. Also my suggestion of 64KB of ram was a random number I pulled out of the air. I'll respond to each of the numbered sections in a separate comment. \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 11:17
  • \$\begingroup\$ 1) FIFO: No, you've gotten two of my ideas mixed up. My plan for FIFO was to store the front buffer (a 120x60, 4-bit bitmap) in the Arduino's memory (obviously requiring the 8KB of ram that the Mega2560 has) and then feed that data in the order that the display needs it to the FIFO (as fast as possible, pausing when the FIFO is full). The VGA controller ICs would then take that data out of the FIFO as fast as needed. It occurs to me that the more data you feed into the FIFO, the further the display might be behind the actual gameplay, kinda like pre-rendering frames on modern GPUs. \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 11:26
  • \$\begingroup\$ 2) Dual port RAM: I didn't know this was a thing (or I might have, but forgot)! If I went the RAM approach this is what I would use, since I wouldn't need more than 4KB of RAM for a 120x60 4-bit grid, or 8KB if I went 6- or 8-bit colour. \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 11:33
  • \$\begingroup\$ 3) Bank Switching: Excellent, didn't think of this! A great alternative to dual-port RAM! But it DOES permit local overwriting of areas of the front buffer, only instead of being pixels 1 frame behind it'll be 2 frames behind... right? That makes sense, doesn't it? \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 11:39
  • \$\begingroup\$ One of the biggest issues I'm having with this project right now is lack of part availability. In the past I have almost exclusively bought all my electronics on eBay (Amazon doesn't really exist in Australia - I gather Amazon is the normal one to use in the US?), and eBay is exceptionally good for electronics. But now I'm starting to search for such niche items that I don't know where to look. I wish there was just a website where I could just say, "I want one of these little ICs, and one of these, and one of these, now post them to me in reasonable time without charging insane amounts"... \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 11:47
2
\$\begingroup\$

This is the perfect application for a small FPGA. It would be possible to implement a relatively powerful 2D drawing engine on the FPGA that can be controlled over SPI or I2C that can output full 8-bit color with a simple R-2R DAC. It would even be possible to have frame buffers that can be double-buffered or even composited on the fly. It would also be possible to implement hardware sprite drawing modules.

You could even get a slightly bigger FPGA and stick the AVR core on the FPGA as well to simplify the interface in between. You could also get an FPGA that has a built-in hard CPU core, such as the Zynq.

So, even if an FPGA is the 'right tool for the job', suppose you don't have one or don't want to use one. I would recommend using small FIFO chips, such as the CD40105 or 74ALS232. These are asynchronous 16 word 4 bit FIFOs. I would suggest using 3 of these, one for red, one for green, and one for blue. Construct simple R-2R DACs on the outputs of all 3. For the inputs, tie them in parallel to a GPIO port on your MCU. To write data, put 4 bits of red, green, or blue data on the port and pulse the corresponding write strobe. If you are careful about how you write your code, no extra delays should be required here - the FIFOs can deal with a very short pulse. Feed the data in ready output to another GPIO pin. After each write, check that pin to see if there is space left in the FIFO. On the output, tie all of the read strobes to a PWM pin. Set up the timer to generate the pixel clock. Now the FIFO write timing is just about completely decoupled from the pixel clock. You may even be able to handle this inside of an ISR that fires at at least pixel clock/16.

Edit: Now that I actually took a look at the code in vgax, it looks like your best option may be to simply modify vgax. The code is written to output one pixel every other clock cycle: https://github.com/smaffer/vgax/blob/master/VGAX.cpp#L141 . This is pretty much the absolute upper limit in terms of how quickly you can shove stuff out the door. If you want to do 6 bit pixels, you can rewrite this to read out another byte on each clock cycle instead of reading one byte and then unpacking it. However, you're going to run out of SRAM on the 328p: 120*60/4 = 1800 bytes, leaving only 200 bytes for everything else. However, if you use a larger chip with 8k of ram, 120*60 = 7200 bytes, and you can fit an entire frame buffer with 800 bytes to spare. One thing to note: you can read your sprites directly out of program memory. So if you switch to the Arduino Mega, you may be able to do everything on one chip, including the video output. Or you might be able to use a pair of Arduino Megas if you won't have enough memory on one. It may also make sense to extend the interface protocol between the two chips to include some way to selectively update parts of the frame buffer.

A dual-port RAM is also a possibility, but using one of those would require driving a rather large number of address lines. Doable, but you will almost certainly need to drive it with an Arduino Mega. One possible chip would be the IDT 7005 8K x 8 dual port SRAM, or its larger cousin the IDT 7025 8K x 16 dual port SRAM. These are both relatively easy to find on ebay AU. This has 12 address lines and 8 data lines on each port, along with RW, CE, and OE. The 16 bit version adds UB and LB. You will probably have to burn 3 ports (~24 pins) on your micro to talk to it. However, reading it out on the other side could be done with a simple 12-bit counter, built from discrete logic, and driven from a PWM pin, cleared to zero at the beginning of each frame. If you use the 16 bit chip, then you can play some more interesting tricks with an 8 bit multiplexer and another I/O port. Consider the 8Kx16 RAM as a pair of 4Kx16 RAMs, with the highest order address bit selecting which one to access. This gives you double buffering for the price of one GPIO pin to select which half of the ram gets sent to the display. 16 bits wide means you can write two pixels in parallel. For the 12 bit readout counter, connect bits 1-11 to address bits 0-10. Connect bit 0 to an 8 bit mux that selects which of the two halves of the RAM output port will be sent to the display.

Now that I think about it some more, it seems like you will actually need 2 counters on the output. The reason is that the RAM only holds 60 or 64 lines (128*64=8192), so each line needs to be repeated several times. This can't be done just by resetting a single counter, so what you need is a 5 bit counter and a 7 bit counter. The 5 bit counter counts the lines, and the 7 bit counter counts the columns. The two of these concatenated will connect to all 12 address lines (or 11 address lines and a mux). Just reset the column counter at the beginning of each line, increment the column counter from a PWM output, and increment the line counter when necessary.

\$\endgroup\$
12
  • \$\begingroup\$ Well I guess I should try to wrap my head around FPGA programming then... argh, FPGAs sound awesome in theory but the code is so extremely foreign... Plus they're not at all cheap. A MojoV3, which, as far as I can tell, is a pretty small one, is $160!! :O \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 2:18
  • \$\begingroup\$ There is a good list of boards to choose from here; there is no appreciable difference in software support (i.e. all Xilinx boards will require Xilinx ISE for development): joelw.id.au/FPGA/CheapFPGADevelopmentBoards \$\endgroup\$ Commented Nov 18, 2015 at 3:27
  • \$\begingroup\$ Also, if you're new to FPGAs, the way to think about the 'code' is that you are describing a circuit with the code, not writing a program. If you think in terms of writing a program, you're going to end up with HDL that is either not synthesizable or horribly inefficient. \$\endgroup\$ Commented Nov 18, 2015 at 3:49
  • \$\begingroup\$ Huh, I actually had that page open (twice) from my previous googling, but hadn't read it yet. Good resource, thanks :) I've had another idea, though. Posted about it on the Arduino site where I also asked the above question: forum.arduino.cc/index.php?topic=360181.msg2483549#msg2483549 \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 5:21
  • 1
    \$\begingroup\$ "create a gigantic hack job of highly optimized code that is extremely limited in its capability" This is goal number 1, although it won't be a hack at all.... "On an FPGA, outputting 640x480 VGA is trivial" Exactly. That's not what I want. It would be about the same cost and be far, far easier for me to simply buy a Zero or Due than an FPGA, but I want it to be limited. I want the challenge of fitting a fun and playable game onto limited hardware, much like the days of the Mega Drive. I already make high-res games with interesting graphics on my PC. \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 5:49
2
\$\begingroup\$

Here are a few comments on the answers you got so far.

  1. Do NOT use discrete (74 family) ICs. You will need a large breadboard and a lot of wires. It will take you months to put the SRAM interface together plus the proper timing for VGA sync pulses. I did it once in my lifetime long time ago but today it's not worth the effort.

  2. Do NOT use serial bus to interfaces your video RAM. For goodness sake I2C runs at 100kbps which will take more then 5 seconds to update 64KB RAM. SPI is better as 25Mbps is quite common but a parallel bus is the way to go.

  3. FPGA implementation is ok but most FPGAs need an additional config SRAM and the parts and eval boards tend to be pricey. I used FPGAs quite often but this project is too simple for an FPGA.

Therefore I would suggest a CPLD. Xilinx or Altera have a bunch of very cheap (< $10) CPLDs. I would use the PLCC packaged ones as you can easily find a socket that will convert it to a 100 mil spaced breadboard. You can buy a USB blaster programmer for Altera CPLDs for about $12.

Here's a good link to a page where you can get an idea how to start: http://hackaday.com/2011/05/05/dabbling-with-cpld-generated-vga-signals/#more-42268

\$\endgroup\$
4
  • \$\begingroup\$ I like your thinking! As I've said in response to many different suggestions, I didn't know this (CPLD) was a thing! Hackaday's CPLD tutorial says this, which I really like the sound of: "This analogy isn’t perfect, but we like it: where FPGAs are a reprogrammable processor core, a CPLD is a reprogrammable circuit board or breadboard. FPGAs replace microcontrollers, memory, and other components. CPLDs absorb logic ICs, and work well with a microcontroller." \$\endgroup\$
    – Clonkex
    Commented Nov 18, 2015 at 22:38
  • \$\begingroup\$ @Clonkex: still you need program it in VHDL. \$\endgroup\$ Commented Nov 19, 2015 at 3:23
  • \$\begingroup\$ @RespawnedFluff Yeah I'm aware of that, but the thinking is slightly different. Besides, one of the biggest blocks to using an FPGA was cost, whereas I can get sufficiently-fast-for-full-VGA-output CPLD, a PLLC socket and a compatible USB blaster for all of about $30. Then I'd just need an oscillator of some sort; I have all the necessary resistors and prototyping boards and assorted headers and stuff. At this point I really just need time to experiment. Once I've worked out a solution I'll make sure to post another comment (or a full answer if it suits better) :) \$\endgroup\$
    – Clonkex
    Commented Nov 19, 2015 at 11:25
  • 1
    \$\begingroup\$ @Clonkex there are now moderately priced FPGA dev boards available, I have a Digilent Arty board for ~US$100 which has a quite capable FPGA. Digilent also makes a VGA adapter that can plug into this particular board. \$\endgroup\$ Commented Jun 22, 2018 at 5:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.