Bit banger is my most constrained and minimalistic microcontroller-based demo yet. It won the Oldschool 4k compo at Revision 2011.
What's all this then?
Bit banger is built around an ATtiny15 microcontroller, which runs at 1.6 MHz and has 1 kB of flash ROM and a claustrophobic 32 bytes of RAM. In fact, those 32 bytes are the CPU registers. Only the most basic AVR instructions are supported; they occupy at least two bytes each, and can obviously not be compressed since they are executing from ROM, so a maximum of 512 instructions will fit inside the chip (fewer if static data is needed).
The microcontroller supports interrupts, but they would have been too costly to use. Instead, the entire demo is cycle counted.
At a clock rate of 1.6 MHz, the visible part of each line of the VGA signal swooshes by in exactly 36 clock cycles. The entire line, including horizontal blanking, is 51 clock cycles wide. During this time, both graphics and sound must be generated.
I quickly arrived at the following overall design: Three registers make up a 24-bit frame buffer, organized as a 3x8 grid. Every 60 raster lines, these registers are rotated one bit, to prepare for the next row of the grid. At three different positions along the visible part of the line, the MSB of the corresponding frame buffer register is interpreted as an instruction to either keep or invert the current colour; the resulting colour is then transmitted onto an output pin. At the end of the visible line, black is selected.
In the gaps between these four positions and the two places where the horizontal sync signal is flipped, sound must be generated and emitted. The ATtiny15 luckily has a PWM output that runs on a separate peripheral clock at a staggering 25.6 MHz, which is high enough for 8-bit audio output. Writing a sample to the PWM output is a simple one-cycle instruction; the challenge is to calculate the value of the sample during the remaining clock cycles.
Here's an excerpt from the source code, so you get an idea of what I was up against:
; Lines 0-479 displine: add r6, r10 ; t0 out PORTB, r2 ; t1 adc r7, r11 ; t2 mov r17, r22 ; t3 sbrc r24, 7 ; t4 eor r17, r23 ; t5 out PORTB, r17 ; t6 add r4, r8 ; t7 adc r5, r9 ; t8 brcc 1f ; t9 neg r12 ; t10 1: lsl r20 ; t11 rol r21 ; t12 brvc 1f ; t13 subi r20, 0xfe ; t14 1: in r16, TCNT0 ; t15 sbrc r25, 7 ; t16 eor r17, r23 ; t17 out PORTB, r17 ; t18 and r16, r21 ; t19 sbrc r16, 0 ; t20 neg r14 ; t21 ldi r16, ARPVOL ; t22 cp r7, r3 ; t23 brcs 1f ; t24 neg r16 ; t25 1: add r16, r12 ; t26 add r16, r14 ; t27 sbrc r15, 7 ; t28 eor r17, r23 ; t29 out PORTB, r17 ; t30 subi r16, 256-102 ; t31 out OCR1A, r16 ; t32 subi r27, 1 ; t33 brcc noadvance ; t34 lsl r24 ; t35 adc r24, r31 ; t36 lsl r25 ; t37 adc r25, r31 ; t38 lsl r15 ; t39 adc r15, r31 ; t40 mov r27, r1 ; t41 displine_back: out PORTB, r2 ; t42 sbrc r26, GF_INJ_FLICKER ; t43 subi r23, COLOURLSB ; t44 ldi r16, HSYNC ; t45 out PORTB, r16 ; t46 subi r18, 1 ; t47 sbc r19, r31 ; t48 brcc displine ; t49 rjmp blanking ; t50 noadvance: DE4 ; t36 rjmp displine_back ; t40
The peculiar indentation signifies that there are two things going on: The graphics output in the left column and the sound generation in the right column. Pixels are emitted at times t6, t18 and t30. If we should advance to the next row in the grid, the frame buffer registers are rotated at t35 through t41; otherwise we branch down to noadvance, delay for 4 clock cycles, and then jump back. Cycle counting at its finest. In the sound column, we see two 16-bit oscillators being updated (phase is accumulated in r7:r6 and r5:r4, frequency is taken from r11:r10 and r9:r8. White noise is generated by means of a linear feedback shift register in r21:r20. One of the oscillators has a variable pulse width, which is kept in r3.
Outside the visible area, 45 raster lines make up the vertical blanking space. During these lines, horizontal (and vertical) sync pulses must still be generated, but since there are no visible pixels we can afford the luxury of generating the sound in a subroutine, rather than inline. Hence, out of 51 clocks, 25 are spent in the sound subroutine, and the rest can be used freely to prepare the next frame and to perform music playback. This work can thus be divided up into at most 45 small code snippets, of no more than 26 cycles each.
In practice, music playback and frame calculation is intermingled; state information from the music player is used as parameters for the visual effects. This saves space and has the added benefit of creating effects that are synced with the music. The music is based around a single 32-step pattern, one byte per step, expressing the bass part and the drums. There is also an arpeggio table with the four chords. The code maintains a current offset into the pattern, which is in fact the lower bits of a 16-bit frame counter for overall demo progress. Other bits in this counter turn glitches on and off.
Most of the glitches are implemented in a semi-controlled fashion, by trashing the registers that will be used during the upcoming frame. In other words, most of them weren't designed as much as discovered. I tried to ensure that the timing of the sync signals would remain perfectly correct no matter how wildly the other parameters would vary. I'm pretty sure I failed somehow, because an assortment of monitors refuse to display the resulting VGA signal.
All of the above creates a very primitive digital output: A stable (well...) VGA signal, 3x8 huge pixels and sound. The microcontroller has five user-controllable I/O pins. Two of them are needed for horizontal and vertical sync, and one is the sound output. The remaining two pins express pixel colour; this allows no more than four colours, one of which must be black.
To improve the situation, the sound PWM doubles as a pixel colour bit as well; however, the VGA signal must be black outside the visible area, even when the sound is playing. A transistor is connected as a poor man's NOR-gate, to mask this signal whenever one of the regular pixel outputs is high. Another transistor, a capacitor, and a couple of resistors are also employed to recombine the pixel colour signals into red, green and blue voltages, because I wished to avoid being stuck with the standard coder colours. The capacitor introduces a low-pass characteristic, which is responsible for the clearly visible horizontal gradient effect.
Bit banger was a success in the compo, which was somewhat unexpected for such an experimental production. Naturally I was very happy!
Some people claim that I abused the rules for the Oldschool 4k compo, since the ATtiny15 is a modern chip. The other option was the Wild compo, which is typically a catch-all category when a production doesn't fit any other compo. I insist, however, that both the aesthetics of the demo and the technical limitations faced by the programmer are definitely more oldschool than newschool in this case.
Posted Monday 13-Jun-2011 12:23
Discuss this page
Disclaimer: I am not responsible for what people (other than myself) write in the forums. Please report any abuse, such as insults, slander, spam and illegal material, and I will take appropriate actions. Don't feed the trolls.
Jag tar inget ansvar för det som skrivs i forumet, förutom mina egna inlägg. Vänligen rapportera alla inlägg som bryter mot reglerna, så ska jag se vad jag kan göra. Som regelbrott räknas till exempel förolämpningar, förtal, spam och olagligt material. Mata inte trålarna.
Mon 13-Jun-2011 14:58
Mon 13-Jun-2011 20:00
Tue 14-Jun-2011 17:38
Fri 17-Jun-2011 21:30
Fri 17-Jun-2011 23:59
Tue 21-Jun-2011 06:19
Wed 22-Jun-2011 18:08
yeah, i also would really appreciate a recording of this tune! it is excellent!
Mon 27-Jun-2011 23:55
Sun 8-Jan-2012 01:12
Sat 14-Apr-2012 02:04
Fri 27-Apr-2012 00:02
Thu 14-Jun-2012 16:36
Most people like it, even those who have never heard of the words scene, demo or the combination of those words. Thanks for the layout!
Thu 28-Jun-2012 03:03
Mon 10-Sep-2012 14:43
Fri 27-Mar-2015 23:16
Fri 1-May-2015 00:29
Surf around, you'll see he sometimes does. I think you'll see that his habit of doing awesome stuff instead of trawling forums that prevents him from replying :)