Phasor
Phasor, like Craft, is a demo running on a custom
minimalistic ATmega88-based demo platform. But it generates a composite video
signal (PAL) instead of
a VGA signal.
Download
It was my contribution to the Console / Real Wild compo at Breakpoint 2010, and ended up
on 2nd place. Here's the Pouët
page for Phasor.
Sorry for the low quality camera rip, but the composite signal was too lo-fi
for the recording equipment at Breakpoint. They made a heroic effort, trying to
filter the signal through all sorts of hardware, but to no avail. It works well
with every CRT and TFT monitor I've come across, though. If anybody replicates
the board and manages to record the signal, please let me know!
How does it work?
The composite video signal is a strange beast. Back when television was
black and white, an analogue voltage was used to represent luminance (light
intensity), as the electron beam swept across the TV one line at a time.
0.3 V represented black, 1.0 V represented white, and the voltages in
between represented shades of grey. Horizontal and vertical sync signals were
added in the form of brief pulses of 0 V, i.e. "blacker than
black".
Since then, colour has been introduced by adding a small sinusoidal
variation (technically not a phasor, but that's where I got
the name from) on top of the black and white signal. The amplitude of the sine
wave corresponds to colour intensity, or saturation. The phase of the
sine wave is used to encode hue. Equivalently, you could say that the
colour information is quadrature
modulated in the sine and cosine components of the wave, and those
components correspond to U and V in the YUV colourspace. The frequency of
the colour wave is fixed at 4.43361875 MHz (because, somewhat simplified,
this was determined to be the frequency that would interfere the least with old
black and white equipment).
Two tricks
In this demo, the microcontroller is clocked at 17.73447 MHz, exactly
four times the frequency of the colour carrier wave. In other words, four clock
cycles per "colour pixel", and in those four cycles one has to generate a sine
wave with controlled amplitude, phase and constant offset. First of all, there
is no chance of generating a smooth sine wave, because the CPU is limited to a
maximum output resolution of one sample per clock cycle. But even this is not
really feasible to do entirely in software, except for very basic visuals, such
as a bunch of static colour bars.
But the effort can be cut in half thanks to a loophole in the PAL encoding
scheme. In PAL, the phase of the colour signal is inverted on every other line
(PAL = Phase Alternate Line). This is quite clever, because it causes
transmission errors to affect saturation rather than hue, and the brain is more
sensitive to errors in hue. At the receiving end, a PAL TV or monitor compares
each incoming pixel with the one above it, and uses a sum and difference
formula to determine the correct colour. The vertical resolution of the colour
information is effectively halved because of this. But this redundancy enables
a really neat hack: By transmitting different values on odd and even raster
lines, it is possible to get away with generating pixel data at every other
clock cycle, which is only half the nominal rate.
This is what the signal is supposed to look like, if the time dimension is
reduced to four discrete time slots (corresponding to four CPU cycles):
| Cycle | Value on odd line | Value on even line |
| 1 | Y + U cos(0) + V sin(0) | Y + U cos(-0) + V sin(-0) |
| 2 | Y + U cos(π/2) + V sin(π/2) | Y + U cos(-π/2) + V sin(-π/2) |
| 3 | Y + U cos(π) + V sin(π) | Y + U cos(-π) + V sin(-π) |
| 4 | Y + U cos(3π/2) + V sin(3π/2) | Y + U cos(-3π/2) + V sin(-3π/2) |
Simplify the expressions:
| Cycle | Value on odd line | Value on even line |
| 1 | Y + U | Y + U |
| 2 | Y + V | Y - V |
| 3 | Y - U | Y - U |
| 4 | Y - V | Y + V |
The monitor first separates Y from U and V using bandpass and notch filters.
Then it expects to get roughly the same U and V from vertically adjacent pixels
(unless there is a sharp colour change in the image at that exact spot, in
which case there will be unwanted artifacts), and applies a sum and difference
formula to reduce transmission errors:
| Cycle | Value on odd line | Value on even line | Sum | Difference |
| 1 | +U | +U | +2U | 0 |
| 2 | +V | -V | 0 | +2V |
| 3 | -U | -U | -2U | 0 |
| 4 | -V | +V | 0 | -2V |
The sum and difference columns are sinusoids with amplitudes proportional to
U and V, respectively.
My demo transmits a signal that would result in very different U and V
values on each line, and relies on the sum and difference formula to make sure
that the monitor displays a single, solid colour.
| Cycle | Value on odd line | Value on even line | Sum | Difference |
| 1, 2 | +U + V | +U - V | +2U | +2V |
| 3, 4 | -U - V | -U + V | -2U | -2V |
As you can see, the sum and difference columns are still sinusoids with
amplitudes proportional to U and V. And that's how to get away with emitting
pixels at half the nominal rate.
The second trick is that toggling between two voltages can be done with the
help of one of the timers inside the ATmega88. The
circuit below consists of two emitter followers in parallel.
Each transistor tries to force the voltage at the emitter to be 0.7 V
below the voltage at the base. But they can only pull the voltage upwards to a
higher level. The upshot of this is that the output voltage will be 0.7 V
below the highest of the two input voltages
(Out = max(VA, VB) - 0.7).
The diodes allow two microcontroller pins to independently pull each input
down to 0.7 V, causing the other input to be "chosen" by the emitter
followers. Timer 1 in the ATmega88 can be configured to generate a
two-cycles-high, two-cycles-low square wave on one output compare pin, and its
inverse on another output compare pin. That way, the software only has to
provide the two input values (four bits each, conveniently the high and low
nibbles of PORTD), and the timer, diodes and transistors will work together to
create a square wave, oscillating between these two voltages automatically. Of
course, the software still has to transmit different values on odd and even
raster lines and keep in phase with the timer, so timing is crucial. But we get
just enough support from the hardware to get by without having to spend all cpu
cycles generating the colour wave.
The reason for going with Timer 1 rather than Timer 0 is that the
output compare unit of Timer 0 is sharing pins with PORTD,
and I wanted all eight bits of PORTD to go into the DACs. But Timer 1 is
the only 16-bit timer on the chip, and I needed a raster interrupt to occur
every 1135 cycles. Fortunately, 1135 happens to be an even multiple of
five, so I'm using Timer 0 to generate interrupts every 227 cycles,
and letting the software disregard four out of five of them.
Music
Sound is generated during the horizontal blanking periods. That gives a
sample rate of 15.625 kHz. Of course, only the really timing critical part
(waveform generation) is performed during the horizontal blanking. Melody,
rhythm, amplitude envelopes, arpeggios etc. are handled by a playroutine which gets called
once for every video frame, during vertical blanking.
There are four sound channels in total, each with its own fixed waveform.
The waveforms are 4-bit triangle, filtered variable pulse, unfiltered variable
pulse and white noise. The filter is a two-pole non-resonant low-pass filter.
The noise is generated by means of a 15-bit shift register. The volume of each
channel can be individually controlled.
Schematics
This is how it all fits together, hardware-wise. If you are interested in
the physical layout used on the breadboard, you can find it along with firmware
and source code near the top of the page.
.---__---. + 10uF
(to programmer) --- RESET | | PC5 ---[ 2K ]----------+---|(-----
| | .-[ 1K ]-' Audio
GND --[ 2K ]-+-[ 2K ]--- PD0 | | PC4 ---[ 2K ]-+--------. GND ---
.-[ 1K ]-' | | .-[ 1K ]-'
`--------+-[ 2K ]--- PD1 | | PC3 ---[ 2K ]-+--------.
VCC VCC --[ 2K ]-. .-[ 1K ]-' | | .-[ 1K ]-'
\ GND -[ 442 ]-+ `--------+-[ 2K ]--- PD2 | | PC2 ---[ 2K ]-+--------.
C\| B | .-[ 1K ]-' | | .-[ 1K ]-'
|---+------[ 442 ]-+--+----------[ 2K ]--- PD3 | | PC1 ---[ 2K ]-+--------.
E/| | | | .-[ 1K ]-'
/ | GND --[ 2K ]-+-[ 2K ]--- PD4 | | PC0 ---[ 2K ]-+-[ 2K ]---- GND
| | .-[ 1K ]-' | |
| | | VCC | | GND
| | | | |
| | | GND | | AREF (n.c.)
| | | 22pF | |
--------+ | | GND -||--+- XTAL1 | | VCC
Video | | | 17.73447 MHz [ ] | |
-- GND | | | GND -||--+- XTAL2 | | SCK ---- (to programmer)
| | | 22pF | |
| | `--------+-[ 2K ]--- PD5 | | MISO --- (to programmer)
| | VCC --[ 2K ]-. .-[ 1K ]-' | |
\ | GND -[ 442 ]-+ `--------+-[ 2K ]--- PD6 | | MOSI --- (to programmer)
E\| B | | .-[ 1K ]-' | |
|---|-+----[ 442 ]-+--+----------[ 2K ]--- PD7 | | OC1B --------.
C/| | | | | |
/ | | (n.c.) PB0 | | OC1A --. |
VCC | | `--------' _|_ _|_
| | /_\ /_\
| | | |
| `----------------------------------------------------------' |
`------------------------------------------------------------------'
If you want to learn more, I suggest you dive into the source code, starting
with video.S and main.S. The compressed data tables are
generated by pack.c.
Update
I am very grateful to Barta Zoli from Hungary, for building a replica
of the circuit, making a high quality video capture and allowing me to share it
here. You can find it in the Download section above.