How it works
Upgrading the crystal to 28.6363Mhz allows us to run at a multiple (8x CPU clock, 4x SPI output) of the NTSC subcarrier frequency. By spitting out bits in the right pattern at the right time we can generate NTSC colorburst and different colors based on the relative phase of the pattern.
Black (0000), White (1111), gray_5 (0101) and gray_10 (1010) don’t have a chroma component; the other 12 colors do. By inserting or skipping an extra nop in rendering kernel we can select between two different phases at the start of a line yielding 12+12+(black,white,gray) = 27 simultaneous colors on screen simultaneously. You can then add more by dithering etc.
The Art of Artifacts
Left – Pixels being emitted from the TX port, Right – Colors as they appear on NTSC
Every HSYNC interrupt the cpu emits a 3.57Mhz colorburst signal then sends pixel data from carefully timed tile or RLE video kernels. These are tricky to maintain in C, as helpful compiler optimizations often alter timing in unexpected ways. They probably all need to move to asm at some point. See note above.
The higher layer code uses a RLE format (BallBlaster) or tiles to represent game content. Because we don’t have enough memory for a full frame buffer individual lines of tiles and sprites are composed in a loop that is in lock step with the HSYNC interrupt.
The Audio driver has two parts. The low level kernel runs every HSYNC, stepping each of the 4 voices though it’s wavetable, mixing the sampled voices together based on their current volume and emitting a sample to the PWM/resistor single bit DAC. This corresponds to a sample rate of 15734Hz.
The high level task runs every frame at 60Hz and adjusts envelope, modulates frequency of the underlying channels etc. It is responsible for parsing data structures containing music tracks and sound effects, adjusting volume envelopes and frequencies, swapping wavetables for different instruments etc.
IR input Joysticks, Keyboards etc
The GPIO attached to the IR sensor is sampled at HSYNC and fed to one of a number of IR decoders. The 15734Hz sample rate is enough to accurately parse nearly all IR protocols. For performance reasons only one is enabled at a time; check the #ifdefs in ir_input.cpp.
Let me know if you have some fun with it.