There have been some changes to the ChipKit libraries since release, some of these changes caused the Pong sketch to stop working. After a helpful bug report from someone trying to build their own I had a look at the source to see if I could fix it. After a bit of investigation I discovered that in the old code using a
digitalWrite() after an
analogWrite() would leave the PWM timer running in the background. This isn't power efficient, so it's been fixed, however the new code tests to see if any
analogWrite() calls on other pins have occurred and if not shuts off Timer 2. Unfortunately this means writing any of the PWM capable pins in my code shuts off Timer 2 as I don't use analogWrite() but set up PWM manually. The solution I came up with was simply to move the enable lines for the two player scores to pins 11 and 12, from 8 and 9 avoiding using any PWM capable pins. My Life demo is unaffected as I didn't use any pins for output other than the VGA pins. Updated schematics and source are included.
Following on from my review of the new ChipKIT Uno32 from Farnell, I've done a little project to test out what it's capable of. I had never programmed for an Arduino before and I've learned a lot about the way the system works from this project. One of the early revelations which amazed me was that the Arduino "Wiring" language is in fact not a language at all but simply a couple of header files that wrap up some boiler plate C++ code. I can see the reason that it was marketed as an "easy to use language" and the mention of C++ was avoided initially as it made the platform more attractive to artists and hobbyists not interested in learning a big scary "real" programming language. The fact that you can just use any old PIC (or on the original AVR) compatibile C code makes it a lot more attractive for those wanting to dig a bit deeper.
My code uses some features of the Arduino software suite and some of the low-level hardware code derived from the PIC headers. This allows rapid development of the slow user-interface parts of the system (control input and score display) whilst allowing the video generation in real time by using the low-level hardware controls.
VGA DAC Board: The VGA DAC is just a bunch of discrete resistors on a scrap of stripboard.
For details on how VGA signal timings are used by the monitor and the voltages involved, see my VGA Primer. The hardware I built for the VGA connection is shown on the left. All the signals are derived off one 8x2 pin header with a small piece of stripboard on it to keep signal integrity high. Ground and 5V are on flying leads which come from the breadboard. The resistors on the left of the image are the 8bit three-channel DAC for generating the colour signals, the other two are the sync signals. To generate the two sync signals for the screen, Timer 2 is configured with output compare and a suitable period/duty to make the H_SYNC (line-sync) signal. There's also a timer interrupt attached which occurs at the end of each sync period (just after the active pulse). This interrupt routine keeps track of which line is being drawn and manually controls the V_SYNC line with a pair of
digitalWrite() commands. During the active screen period there is a block of if statements that decide which if any of the bats and ball are on this line. Each of the three visible items have their own timers that are started at the beginning of each line. If the item appears then the period of the timer is set so that the timer interrupt will occur when drawing has got far enough across the screen, if the item does not appear the timer period is set to longer than the line time so the interrupt won't occur before the next new-line and the timer reset.
Each of the objects is drawn using the parallel master port peripheral on the PIC set in 8bit mode. This drives the inner row of additional digital I/O pins on the ChipKIT adjacent to the original digital 0-7. These pins are wired up with a set of resistors forming 3 separate 0-1 volt DACs for red, green and blue. The parallel port on the PIC has the ability to add wait-states to prolong the data signal for slower peripherals. To make the objects on screen appear with a decent width, I set the wait states to maximum. This means that writing a value to the parallel port (a very fast, non-blocking operation) will continue to output that value to the three DACs and then reset to black with no further instructions. This means that each of the three timer interrupt functions is practically identical, they each write a colour value to the parallel port, sets the period for the appropriate timer to longer than the line to make sure this object doesn't get drawn again on this line and then clear the interrupt flag.
The priority of the three interrupts that do the drawing is higher to the right, so if the ball is close to the left bat then the ball draw interrupt can interrupt the left bat drawing interrupt while it is still dealing with clearing the flags etc. There is a visible jitter when this takes place because of the interrupt calling overheads in the CPU but it generally works okay.
There are also a few artifacts that can be seen, such as single line flickers where all objects are delayed by a few pixels and an occasional blank frame becuase of a bad sync signal. These are believed to be caused by the Timer1 interrupt which is being used in the Arduino wrapper code for doing the delay() functions. This could be avoided by removing the wrapper code but this would make reading the analogue controls more complicated.
3 bit 0-1V VGA DAC
Fig. 1: One channel 3 bit DAC overview.
Fig. 2: Example for the case B0-2=011
Fig. 3: Simplified circuit with just two equivalent parallel resistors.
The colour signals are generated by 3 simple DACs. The blue signal is a 2 bit DAC, red and green are generated with three bit DACs. In Fig. 1 B0-B2 (lsb-msb) are 3.3V CMOS logic signals. When they are all low, there is no current flowing and the colour is 0V (black), in all other cases the values of all the resistors from a logical 1 are considered in parallel, and all resistors connected to a logical 0 are considered parallel with the 75Ω termination in the monitor, this simplifies to a 2 resistor voltage divider. For example if the colour code <B2:B0> was <0,1,1> (i.e. the MSB was zero, the other two were 1) then the 240Ω resistor would be connected to ground in parallel with the 75Ω in the monitor while the other two would be connected in parallel to 3.3V. Fig. 2 shows the circuit moved around a bit to make it a clearer what is in parallel with what. Using parallel resistor combining the circuit can be simplified to that shown in Fig. 3, this is easy to solve the voltage divider problem for and find that the colour signal will be 0.45V. Using the same technique for all the other bit permutations the range of voltages shown in the table are found.
This isn't 100% accurate as the actual output from the logic high is not 3.3V, the data sheet specifies that it will be 2.4V or more. If you are really in need of maximum colour resolution it is worth considering a proper DAC or at least tweaking these resistor values a bit, the values here were rounded to the nearest I had in my parts box.
The score display is done on a pair of seven segment LED displays. Because of the somewhat primitive graphical capablilities it seemed impractical to display the scores on the screen. Instead they take advantage of another block of additional I/O on the ChipKIT. The displays are common cathode so the segments are connected directly to the output pins with just a 330Ω current-limiting resistor. The common pins of the two displays are driven by a pair of 2N2222 NPN switching transistors, these are overkill and a 2N3904 should be sufficient. There's a 1kΩ base resistor from each of the transistors to the control pins on the ChipKIT. This allows the displays to be driven alternately with each players score being displayed in turn. An exclusive or operation is performed on a variable in memory toggling the least significant bit every time the user interface loop is executed. When the bit is set player 1's score is displaayed and the first common pin pulled to ground so it displays on the left, when the bit is zero then player 2's score is displayed and the second common enable line activated to show the score on the right. This happens nearly 100 times a second so there is no visible flicker of the scores.
The user input is perhaps the simplest bit of the code, there are two potentiometers connected to analogue channels 0 and 1. The potentiometers are connected between 0 and 3.3V with the wiper connected to the analogue channel. This means that the value returned by
analogueRead() varies across the full scale, between 0 and 1024. To calculate the line number the bat starts on this is just divided by 3 and has an offset added to keep it clear of the top of the screen.
The ball direction is defined by two variables, an x speed and a y speed. They are just added to the ball each time the display is updated. When the ball position gets to the upper or lower limit of the screen the y speed is multiplied by -1 to make the ball "bounce" off in the opposite direction. At the ends the ball position is compared with the position of the bat and if they intersect then the direction of the ball is based on how close to the end of the bat it was hit. If the ball misses, the score of the opposing player is increased and that player "serves" the ball. This means it is set to the centre of the screen and sent toward the player who just lost a point.
|Sketch (source code) Updated 17 Mar 2012||Feb. 26, 2016, 4:22 p.m.||10.7 KB|
|Schematic Updated 20 May 2012 (Fixed H_SYNC pinout)||Feb. 26, 2016, 4:22 p.m.||51.0 KB|