Close

High Level Low Level Architecture

A project log for Attiny10 Chiptunes

Chiptunes and simple musical games on a 36 cent chip. Some assembly required.

legionlabsLegionlabs 03/20/2023 at 03:150 Comments

There are detailed comments in the code, but this is the general story.

A song is composed of sound frequencies played for varying times, often separated by pauses. That means that we can play a song by defining two timers – one used to create the sound waveform (timing when to toggle the output pin, generating a square wave in audio frequencies), and one that determines how long to play that waveform (disables output when the note is done playing).

The attiny10 has only one general-purpose timer (timer/counter0). It’s 16-bit, so when running on the 1Mhz internal oscillator, we can generate output in the range of about 500khz to about 15Hz without using a prescaler. That’s a really wide range, and we can configure the timer to use its output compare feature such that it automagically toggles a pin and restarts the timer every time the timer hits some maximum value. That’s perfect for generating audio frequencies!

...but then how do we time notes? We could define a timer in software, e.g. increment a register value until it overflows a bunch of times – but that’s really an inefficient use of power as you have to keep the CPU clock running – timer/counter0 can run independently of the CPU clock, letting you halt the CPU to save power.

The watchdog timer is generally used to make your code run reliably – it defines a fail state. For example, if your code locks up due to some really unlikely situation, the watchdog timer can trigger a reset condition after a certain amount of time so your code can continue operating. During normal operation, you periodically reset the watchdog timer, so this never occurs unless something goes wrong. This timer runs on a separate, inaccurate, slower clock. It’s also great for applications where you just need to wake up every few seconds, check a thing, and then go back to sleep.

A key feature for us is that the attiny10 lets you configure the watchdog timer to generate an interrupt instead of resetting the chip! We use that feature to make the WDT work as a metronome. So we configure it to trigger every 0.125 seconds, and duration of our notes must be in a multiple of that time. We also set the pause between notes as equal to that time.

This is a better approach because it lets us halt the CPU while notes are playing. The CPU only needs to wake up for a very brief time every 0.125 seconds to decide whether to continue playing the current note, or to switch to the next note.

The are multiple sleep modes available to this chip. So far, the sleep mode we used has always been ‘idle’. This is the lightest sleep mode, as it halts the CPU but keeps most other peripherals running like timer/counter0, the I/O clock, and the analog to digital converter.

We don’t use the analog to digital converter in this design though, so we turn that off to save power.

Finally, it would be a bit irritating if there was no way to half playback. So we additionally define a deeper sleep mode (power down). On reset or power on, the chip explicitly turns off the watchdog timer (to save even more power) and sleeps in this mode.

A low level on the INT0 pin wakes it, at which point, it starts a timer for a short while. The timer halts on rising edge of INT0. This timer value is used as an entropy source to decide how many notes to play the song (looping if necessary). So we’re using an element of switch bouncing (the delay between switch release and first logic level change, measured in microseconds) as an entropy source! More on why we do this in the final section.

Discussions