Close
0%
0%

Attiny10 Chiptunes

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

Similar projects worth following
You know those epoxy blob chips that play little chiptunes on musical greetings cards and dollar store children’s toys? Lets make those!

Arguably, no one likes those things because they are annoying, but lets put that aside and ask another question – what if we could use modern microcontrollers and sleep modes to make them work really efficiently so they can play for impressive periods of time?

OK, maybe that just makes them annoying for longer – but what if we could do it with a 36-cent microcontroller (attiny10) and very few external components, so we can reduce not just the energy cost of using the device, but also the energy that was invested in creating it, so you can pump out hundreds of them?

Ok, maybe that’s the last thing you would actually want in your life, but bear with me -- what if we implemented it in assembly language so that you can really fit quite a lot of chiptune in 1kb of memory?

Still not sold? Well, here we go anyway.

Build details are in the project log. I'd recommend starting at the oldest and reading towards the most recent.

Assembly code (including comments) is attached as a file. I've added a lot of code comments -- most people asking online about assembly seem to be students struggling with an undergrad class. I've commented with that in mind.

If that describes you, then note that the general techniques I've laid out will probably work on whatever AVR your professor has chosen, but the specifics (such as register addresses) will need to change. The information you need to do this is in the datasheet for your chip -- it's highly unlikely your professor has chosen the attiny10 to teach a class!

Board design was very simple, so I just sketched it up in Inkscape. I've provided the file, already reversed for toner transfer.

Power Consumption

While in deep sleep (waiting for the user to press the pushbutton), the circuit consumes about 100 nanoamperes (I measured this directly with a multimeter). This is negligible compared to the self-discharge of the CR2032 battery -- mine are rated for 10 years, but ignoring that, the circuit would run for about 240 years in this standby mode.

During song playback, the chip will consume 350 microamps @3V while the MCU is in full operation at 1Mhz -- however because of our code, it is only in that mode for a tiny fraction of the time. It almost always sits in idle sleep mode with watchdog timer enabled -- which should consume on the order of 50 microamperes (~35uA for idle mode plus around 5uA for the watchdog timer and finally 10uA for timer/counter0). The waveform generation is done in hardware peripherals, letting us turn off the main MCU clock. I did not directly measure this for this project, it's calculated from the datasheet (although I did measure this sleep mode in previous projects with the same chip and it matched the datasheet).

We also turn off all ADC hardware and internal pullup resistors, to save additional power.

The power consumption that remains is absolutely dominated by the piezo element. The one I used (not shown) was really awful in how quiet it was. I'm trying to dig up one of the piezo buzzers from a motherboard POST sequence to replace it.

Anyway, common piezos consume at most 10mA, which the pins of the Attiny10 are capable of directly sourcing (so we save a MOSFET).

As such we can round the power consumption to be very approximately whatever the consumption of the piezo element is. Expect at least 21 hours of playback time from a CR2032, and arbitrarily long standby time.

tunes.mp3

A recording of the song, now that I found a better piezo element. It's pretty bad, but I can write a better one later -- it works.

MPEG Video - 279.91 kB - 03/20/2023 at 13:10

Download

chiptunes.asm

Commented assembly code for the attiny10.

asm - 11.35 kB - 03/20/2023 at 04:47

Download

chiptunes.svg

Open in e.g. Inkscape. Tile it how you want, if you want to make multiple boards. Board is already horizontally flipped for toner transfer.

svg+xml - 7.75 kB - 03/20/2023 at 04:30

Download

  • 1 × attiny10 Microprocessors, Microcontrollers, DSPs / ARM, RISC-Based Microcontrollers
  • 1 × pull up resistor, 500k You can use internal pullups too, but I thought an external one might save a little power.
  • 1 × CR2032 Batteries and Battery Accessories / Batteries
  • 1 × CR2032 Holder
  • 1 × Piezo Buzzer Don't be like me -- get one that works well at 3V. Salvage a greeting card if you need.

View all 6 components

  • Update: New Piezo

    Legionlabs03/20/2023 at 13:34 0 comments

    I found a much better piezo in my parts bin. It makes a world of difference, it's quite loud! The size was also perfect, I could just stick it to the battery with some double sided foam tape.

    Song still sounds bad, but that's a story for another day.

    After checking the datasheet, I'm driving the piezo with a current that exceeds specification by a bit (nearly 5mA vs 3mA). A resistor in series or a different piezo would last longer. It seems to be doing fine for now though.

  • Notes on the Boards

    Legionlabs03/20/2023 at 04:14 0 comments

    The boards are made from copper-clad kapton. I keep this around for rapid prototyping:

    1) It's thin, so toner transfer is very fast! I can just stick it on a flat metal sheet, put that on a hotplate, and press down for a few seconds with a cloth.

    2) I can cut it with scissors.

    3) Most of my boards are single sided, and use SMD components. Although I can do double sided with this material. I 'plate' vias by just hammering a thumbtack through the board and bridging the two layers with solder. Or use a PCB drill bit if I'm feeling fancy. 

    4) It's highly resistant to temperature and chemistry, and extremely low profile and lightweight. Sometimes this lets me do unexpected things.

    5) in previous systems, it's thin enough that I can do direct printing by just taping it to a piece of paper. Presently I don't have that so it's indirect toner transfer and etch.

    Apologies for the messy soldering job. I did use solder paste, but it was really old and dried out. So not as nice and clean as it could be.

  • Notes on Programming the Attiny10

    Legionlabs03/20/2023 at 03:52 0 comments

    I'll detail some tips on programming the Attiny10 here.

    TPI Programming

    First, the attiny10 uses TPI programming, not ISP. You can build a TPI programmer using an Arduino (https://github.com/james-tate/Arduino-TPI-Programmer), or buy an AVR-ICE.

    As an IDE, you've got microchip studio, which is a bit bloated and Windows-only. To be fair though, it is also very feature-rich and the AVR simulator seems reasonably accurate. The latter item is an improvement over AVR Studio 4, which I used to use.

    There is also the more minimalist Attiny10IDE (https://github.com/wholder/ATTiny10IDE) which is cross platform. Either of these can produce hex files that you will paste into the serial monitor, if using an Arduino as a programmer. If using the AVR-ICE, you'll likely be programming the chip from Microchip Studio.

    Dealing with SOT-23-6

    Next up, the chip is SOT-23-6. Sockets for that are sort of expensive. So I bought 2 breakout boards that convert it to DIP-6. The first, I solder a chip to -- this one goes into a little board with header pins exposed that I can use to hook up to my scope and test code performance. The other, I leave unpopulated. It goes into a programming board (via a DIP-8 socket) that connects the correct pins from the AVR-ICE or Arduino to the attiny10. To program a chip, I just sort of drop it on the pads and press it down with my finger -- this works just fine. However, you must observe polarity or you'll lose the chip and burn yourself!

    Bugs on the AVR-ICE

    Note that there's a bug on the AVR-ICE circuit board. The SWD header is soldered in upside down. So the red mark on the ribbon cable is pin 10, not pin 1. This is correctly indicated on the solder mask. If using the full AVR-ICE kit this is not problem -- they use a weird little adapter board to silently flip the cable a second time. However if you're like me and the idea of paying such a high price for a plastic case and cable is physically painful to consider, they will sell you the AVR-ICE circuit board alone for a reasonable price! You can then connect your own SWD cable, as long as you remember to reverse pin order. Also note that the AVR-ICE will not power the chip being programmed. You'll need to do that as well.

    If the engineer who swapped the header reads this one day -- thanks for correctly labeling the solder mask. Otherwise I'm not sure how I would have worked this out.

  • Final Notes on the Code, and a Bonus Game

    Legionlabs03/20/2023 at 03:27 0 comments

    So, why did we use entropy to vary the notes the song plays? Well, many years ago, I had a children’s toy called ‘Hot Potato’. It played ‘pop goes the weasel’, looping for a number of notes. Actually, it only had a few distinct places it could stop, so you could cheat a little by paying careful attention, but that’s besides the point. You tossed it around with your fellow children until the music stopped, and then the person holding it was out of the game. I figured it would be neat to replicate that function, so that we have a simple game made from the attiny10.

    The actual chiptune sounds a bit off, because I have the musical talent of a stone (the stationary kind, not the precocious rolling variety). With patience I’ll hammer it out later. I also don't have a piezo that works well at 3V handy. Overall it works pretty OK though. I'll try to add a recording to the project files, but expect to be able to do better yourself.


    Known Bugs

    There are occasional memory glitches, probably from a bit of naive addressing on my part. I’ve added support for a zero-length note so you can push notes to less problematic memory addresses, troubleshooting it in detail is beyond the scope of what I set out to learn here. Once in a while, adjusting the note duration or frequency seems to fix rare bugs, which is weird. It shouldn’t help at all. It does though, and such is life. I’m sure when I find the reason some day, it will be something really cool.

  • Song Storage

    Legionlabs03/20/2023 at 03:18 0 comments

    The problem of playing the notes solved, we need to store what notes to play somewhere. Normally I’d use EEPROM, but the attiny10 doesn’t have any. Thankfully, the chip can fully address (read and write!) the entirety of it’s program flash, which is a generous 1kb. That 1kb is organized into 64 pages each containing 8 words, which themselves contain 2 bytes.

    Our program is quite small, occupying about 194 bytes (depending mildly on the revision) – yes, I could trim it down a little further, but it’s small enough for now: if we define a format where the output compare unit values (16 bits) are stored in a first word, and the note duration in a second word, we’ve got enough space for 207 notes, sufficient for several songs! I could also use only a single byte for note duration, but I left myself some space in the format in case I need to add some new features in later, and don't want to rewrite the songs.

    Sadly the (excellent) datasheet is a bit light on the details of actually reading the flash. So I wrote a program to figure it out for me.

    First, I used the Z-pointer to store the 16-bit flash address. This is really just a pair of 8-bit registers that together work more or less like a 16-bit one. When I point those to flash memory, I can just load ‘whatever’ is stored in that address and it should work fine. However, I could not work out the addressing scheme from the datasheet.

    So instead I loaded a value not present anywhere else in memory at a fixed flash address (0x0150) using an org directive. Then I iterated through all possible Z-pointers, comparing the value read from memory until it matched the value I was looking for. I set a breakpoint to halt the CPU when that happened, and read the value of the Z-index. With those 2 values (the address 0x0150, and the Z-index that addresses that), and the memory structure, it was pretty easy to figure out the addressing scheme.

  • High Level Low Level Architecture

    Legionlabs03/20/2023 at 03:15 0 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.

  • Learning Objectives

    Legionlabs03/20/2023 at 03:12 0 comments

    When I began this project, the objective was to get some practice using the attiny10 microcontroller. It has a spacious 1kb of flash, 32 bytes of RAM, a whopping 4 ADC channels, and by default runs at 1Mhz without external components. In full operation, it consumes around 200 microamps, and much less in sleep modes. That's a lot of fun packed into a 6 pin package half the size of the grain of rice!


    So I bought around 100 of them for 36$, because I know that's going to be a lot of entertainment for the price of a night out.


    Here are the learning objectives I've set for this project:

    1) Storing things that aren’t program code in program memory, because you have no EEPROM.

    2) Waveform generation in hardware.

    3) Using the Watchdog Timer as a normal timer, when you don’t have enough normal timers.

    4) Various sleep modes.

    5) Using components of switch bounce to aid in entropy generation.

    Since a lot of my objectives were low-power adjacent anyway, I figured it would be fun to enter it into  contest.

View all 7 project logs

  • 1
    Assembly

    The 'some assembly required' pertains mostly to the programming. Physically assembling the project is dead simple.

    1) Program the chip with your code. See the project log for notes on how to do this.

    2) Etch some boards. I've included an SVG of the board in the files section (already reversed), you can just tile it how you like and print.

    3) Put some solder paste on the pads for the attiny10 and the pull-up resistor. I use a through-hole resistor to do this (the leads are the right thickness), then wipe it clean before putting it back in my toolbox. I also do this on top of a plastic sheet only used for this purpose to avoid contaminating parts of my house with lead.

    4) Heat up the board with a hot air rework station. Use a soldering iron to add a blob of solder to the pads.

    5) Use double sided tape to adhere the board to the back of a CR2032 battery holder.

    6) A pushbutton can clip on nicely to a bit of the battery holder. See images.

    7. Use a little magnet wire to connect everything. Then secure it with cyanoacrylate glue ("super glue").

    8. In case it's not clear, this is the pin assignment for the Attiny10:

    ---------------------------------

    1: Piezo Output

    2: GND (note there's a separate pad provided to attach every component that needs a GND connection)

    3: NC

    4: Pulled up to VCC via resistor (~500k ohm). Can connect to ground via pushbutton.

    5: VCC

    6: RESET (connected directly to VCC)

View all instructions

Enjoy this project?

Share

Discussions

Michael Möller wrote 03/21/2023 at 08:05 point

Nicely commented code 

  Are you sure? yes | no

Legionlabs wrote 03/22/2023 at 10:19 point

Thanks! I actually enjoy assembly language, I'd like it if other people could too.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates