-
Schematic
11/28/2016 at 10:43 • 9 commentsI just realized that I never wrote about the schematic of the board. Sure, it's very simple, but for completeness' sake:
-
ATtiny13
11/28/2016 at 08:04 • 3 commentsI'm still waiting for my chips, but @davedarko has a bunch of ATtiny13s handy, and he reports that there is a small problem: ATtiny13 doesn't have Timer1. A quick look at the datasheet confirms that -- so I will need to use Timer0 instead. However, there are two problems with that. First, Timer0 cannot directly control the PB4 pin that I used for the piezo on the boards, and second, its prescaler is not nearly as accurate, so I can't use it for selecting octaves. But let's try and see what we can do.
The first problem is simple: I undusted my old trusty time machine, went back a week, and modified the original board design, to make one of the cats (the middle right one) different from others -- it has piezo on pin PB0, and separate LEDs on PB3 and PB4, so it can blink its eyes independently.
The second one is a bit worse -- we will have to compromise. I'm setting the prescaler to a static value, and I'm shifting the timer counters right by the octave number -- this way the higher the octave, the smaller the numbers -- so higher frequency. (You may remember from your music lessons, that each octave has twice the frequency of the previous one). The problem with this is that I lose accuracy at the higher frequencies. Oh well. Also, I had to switch the chip to 1Mhz, because then I can get the prescaler at the right spot.
The end result isn't bad, and it's only 2 bytes larger than the ATtiny85 version (because I now blink the other eye too, otherwise it would have been smaller). Here it is:
void play(const byte *notes, byte length) { union { struct { unsigned int tone: 3; unsigned int octave: 2; unsigned int length: 1; } note; byte raw; } note; while (length --> 0) { // "Goes to 0" operator. note.raw = pgm_read_byte_near(notes++); if (note.note.octave) { TCCR0B = 2; // Pre-scaler /64 OCR0A = pgm_read_byte_near(FREQ + note.note.tone) >> (note.note.octave - 1); } if (note.note.length) { _delay_ms(220); TCCR0B = 0; _delay_ms(30); } else { _delay_ms(110); TCCR0B = 0; _delay_ms(15); } PORTB ^= 1<<3 | 1<<4; } } int main() { DDRB = 1<<0 | 1<<3 | 1<<4; // Output pins PB0, PB3, PB4. TCCR0A |= 1<<COM0A0 | 1<<WGM01; // Enable output on PB0 and set CTC mode. PORTB ^= 1<<4; // Close one eye, so they blink one at a time. play(INTRO_NOTES, 26); while(1) { play(MELODY_NOTES, 216); } }
-
The Power of Plain C
11/26/2016 at 13:57 • 5 commentsSo I gave up on using that formula, and instead tabulated the frequencies -- that's just 8 bytes, after all. I also changed the encoding of the notes, so that it's easier to read (in octal). The 3 least significant bits are the pointer to the table of frequencies, the next two bits are the octave (with 0 being a pause), and the sixth bit is the duration. That got me down to about 800 bytes.
Hmm, but I'm sure I can do better. At this point, I'm basically not using anything from Arduino itself, except maybe for the delay. So what if I just go with plain C, like the good old days? A quick google found me a ready Makefile for the ATtiny85 (complete with fuse flashing), and a bit of looking around got me the _delay_ms() function. Perfect.
The result? See for yourself:
AVR Memory Usage ---------------- Device: attiny85 Program: 492 bytes (6.0% Full) (.text + .data + .bootloader) Data: 8 bytes (1.6% Full) (.data + .bss + .noinit)
If I'm reading this right, that is exactly 500 bytes total. Woo!
The code (with the makefile) is available in the repository here (no rickrolling this time, I promise): https://bitbucket.org/thesheep/nyan/src/495dfa41484dade01bdf7ff79b41f9337f430e5b/main/?at=default
-
Tabulate your Data
11/26/2016 at 12:30 • 3 commentsSo I tried to shrink this program even more. There are those two lookup tables in there, one with frequencies of the notes and one with octaves. By sorting the notes and replacing the frequencies with a simple formula (Wolfram Alpha helped me to come up with it), I got:
120 + (354 + a * 6363 + a * a * 293) // 1000
Very happy with it, I put it in my program. BAM! 500 bytes larger.Why? Well, I blame 16-bit division and modulo that i had to use...
-
Shrunken Cat
11/24/2016 at 20:59 • 4 commentsSo I tried the technique described in that link in the previous log, and somehow it didn't work that well for me. I must be doing something wrong, but I didn't understand all details enough to be able to tell what exactly.
But since I already had the ATtiny85 out, I decided to go ahead and make a version of the original code that doesn't rely on the Tone() function (because that function brings about 1500kB of binaries). So instead I just used the good old Timer1.
const unsigned char FREQ[] = { 0, 134, 134, 127, 127, 225, 225, 213, 201, 201, 201, 190, 190, 190, 169, 169, 150, 150, }; const unsigned char OCTAVE[] = { 0, 4, 5, 4, 5, 5, 4, 5, 4, 5, 6, 4, 5, 6, 4, 5, 4, 5, }; const signed char INTRO_NOTES[] = { 9, 12, -15, -4, 9, 12, 15, 4, 6, 10, 6, 2, -4, -15, 9, 12, -15, -4, 6, 2, 4, 6, 13, 10, 13, 4, 0 }; const signed char MELODY_NOTES[] = { -15, -17, 9, 9, 0, 3, 7, 5, 3, 0, -3, -5, -7, 7, 5, 3, 5, 9, 15, 17, 9, 15, 5, 9, 3, 5, 3, -9, -15, 17, 9, 15, 5, 9, 3, 7, 9, 7, 5, 3, 5, -7, 3, 5, 9, 15, 5, 9, 5, 3, -5, -3, -5, -15, -17, 9, 9, 0, 3, 7, 5, 3, 0, -3, -5, -7, 7, 5, 3, 5, 9, 15, 17, 9, 15, 5, 9, 3, 5, 3, -9, -15, 17, 9, 15, 5, 9, 3, 7, 9, 7, 5, 3, 5, -7, 3, 5, 9, 15, 5, 9, 5, 3, -5, -3, -5, -3, 14, 16, -3, 14, 16, 3, 5, 9, 3, 12, 9, 12, 15, -3, -3, 14, 16, 3, 14, 12, 9, 5, 3, 14, 8, 11, 14, -3, 14, 16, -3, 14, 16, 3, 3, 5, 9, 3, 14, 16, 14, -3, 3, 1, 3, 14, 16, 11, 12, 9, 12, 15, -3, -1, -3, 14, 16, -3, 14, 16, 3, 5, 9, 3, 12, 9, 12, 15, -3, -3, 14, 16, 3, 14, 12, 9, 5, 3, 14, 8, 11, 14, -3, 14, 16, -3, 14, 16, 3, 3, 5, 9, 3, 14, 16, 14, -3, 3, 1, 3, 14, 16, 3, 12, 9, 12, 15, -3, -5 }; void play(const signed char *notes, const unsigned int length) { for (int n = 0; n < length; ++n) { // signed char f = pgm_read_byte_near(notes + n); signed char note = notes[n]; unsigned char duration, pause; if (note < 0) { duration = 220; pause = 30; note = -note; } else { duration = 110; pause = 15; } if (note) { TCCR1 = 1<<CTC1 | (11 - OCTAVE[note]); OCR1C = FREQ[note] - 1; } delay(duration); TCCR1 = 1<<CTC1; delay(pause); PORTB ^= 1<<3; } } void setup() { DDRB = 1<<4 | 1<<3; GTCCR |= 1<<COM1B0; play(INTRO_NOTES, 26); } void loop() { play(MELODY_NOTES, 216); }
I'm still using delay(), because I'm too lazy to setup my own counters for that. Apparently, this is enough:Sketch uses 938 bytes (11%) of program storage space. Maximum is 8,192 bytes. Global variables use 289 bytes (56%) of dynamic memory, leaving 223 bytes for local variables. Maximum is 512 bytes.
I will probably work on it some more when I get bored, but the basics are working. Oh, that version is for the 8Mhz internal clock. For the 1Mhz clock, change the 11 to 8. -
Efficient Tone
11/23/2016 at 22:57 • 0 commentsOne change that I will need to do this time will be to completely rewrite the tone-generating function. Originally, I just used whatever was available in the Arduino core that I used -- and that turns out to be quite a bit of code. Way over the 1k limit.
But hey, generating a PWM signal at a specified frequency while the microcontroller doesn't have anything else to do is not rocket science, right? Even if I completely fail to understand the timers, in the worst case I can just have a bunch of empty loops spinning there, busy-waiting for the right moment to flip the pin.
I didn't have much time today to do any actual experiments with the ATtiny85, but I did find an interesting article about a technique, that will maybe let me do a much better thing than a square wave: http://www.technoblogy.com/show?QVN
-
Second Version
11/21/2016 at 21:12 • 2 commentsI ran out of Nyan Boards to give out, so I decided to order another batch of 60 cats. However, this time I made the PCB layout a little bit cleaner and more compact, and I plan to fit the code in the 1kB required for the recent contest. Oh, also the cats are black.
-
Doing Useful Work
01/06/2016 at 12:18 • 0 commentsSome of the comments here and on HaD.com suggested, that this project is pretty much useless, apart from its artistic value. I would like to say that this is not true, and that there is at least one copy of the Nyan Cat doing useful work out there. Behold:
This is a Nyan Cat connected to the door unlocking mechanism in MechArtLab in Zürich. Thanks to @Tillo for setting it up and letting me know!
-
Now Using PROGMEM
11/09/2015 at 22:15 • 3 commentsThanks to @davedarko's persistence and his #Me building projects from hackaday.io project, there is now version of this song that keeps all the note data in PROGMEM, and thus uses so little memory, that it runs on ATTiny45. The updated code is in the repository linked on the left.
-
Great Success
10/23/2015 at 14:45 • 0 commentsI have the first cat working. For some reason my program suddenly stopped working, and nothing helped until I switched to a different Arduino core... I swear it worked before, so no idea what is going on there. Anyways, after some trial-and-error, desoldering, resoldering, reprogramming, etc. I finally got it to work how I wanted. Behold:
A little underwhelming, I know, but still fun. In the coming days I will prepare proper build instructions and will send the boards to people, who have requested one.