-
Update: New Piezo
03/20/2023 at 13:34 • 0 commentsI 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
03/20/2023 at 04:14 • 0 commentsThe 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
03/20/2023 at 03:52 • 0 commentsI'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
03/20/2023 at 03:27 • 0 commentsSo, 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 BugsThere 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
03/20/2023 at 03:18 • 0 commentsThe 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
03/20/2023 at 03:15 • 0 commentsThere 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
03/20/2023 at 03:12 • 0 commentsWhen 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.