I am a software developer by vocation, but I recently discovered that hardware tinkering can be just as fun... So I went ahead and spent the amazing amount of 6 Euros (!) to buy the necessary parts for tinkering with an ATmega168...
Many thanks to Denilson Figueiredo for the excellent tutorial that allowed me to program the microcontroller with just a parallel port circuit.
(An overall view,
zooming-in on the buzzer)
(Close-up recording,
with sound)
First with just one LED, to test the parallel port programmer |
5 cables are soldered to a parallel port jack, and then the jack is plugged in |
Using a PC power supply |
Finally, using the buzzer |
You can download the code I wrote (and its Makefile) from here. Make sure you have avr-gcc installed (if you use Debian or Ubuntu, simply apt-get install gcc-avr). Here's how I drive the microcontroller:
/* Plays the birthday music on a buzzer attached to ATmega168 pin 24 */ /* Written by Thanassis Tsiodras, June 2009 */ /* I've dropped the CKDIV8 in lfuse, so my ATmega168 runs at 8MHz */ #define F_CPU 8000000 #include <avr/io.h> /* this contains all the IO port definitions */ #include <util/delay.h> /* for _delay_us and _delay_ms */ #define UPPER_LIMIT_IN_DELAY_US (768000000/F_CPU) /* The song, in pairs of frequency and duration (in milliseconds) */ struct _Note { int frequency; int durationMS; } g_Notes[] = { {587,500}, {659,500}, {587,500}, {784,500}, {740,700}, {0,200}, {587,500}, {659,500}, {587,500}, {880,500}, {784,700}, {0,200}, {587,500}, {1175,500}, {988,500}, {784,500}, {740,500}, {659,500}, {1046,500}, {988,500}, {784,500}, {880,500}, {784,700}, }; /* The calculation "cache" that helps us avoid floating point in the loop */ /* (read further below) */ unsigned g_remainderTicks[ sizeof(g_Notes)/sizeof(struct _Note) ]; /* Create a specific frequency for a specified amount of ms. */ void buzz(int idx) { /* From the desired frequency, we want the half-period, since we will */ /* set the buzzer on, sleep for half a period, set it off, sleep for */ /* half a period, and then repeat... */ /* <--Period--> */ /* ____ _____ _ */ /* | | | | | */ /* | |_____| |_____| */ /* <-H-> */ /* H=half period */ /* We want to use _delay_us to sleep for half a period, but... */ /* _delay_us has a limitation: the largest value it can sleep for is */ /* Upper limit in microSeconds = 768 / FrequencyInMHz */ /* If using a 1MHz clock, this value is 768us... The lowest note in */ /* the "Happy birthday" tune is however 587Hz, so it requires a half */ /* period of 1000000/(2*587) = 851us, larger than the limit... */ /* Not to mention that I have already removed the CKDIV8 fuse, so my */ /* ATmega168 runs at 8MHz: the limit there is 96us ! */ /* We therefore sleep the half periods in steps of "upper limit", */ /* until we have a remainder less than UPPER_LIMIT_IN_DELAY_US. */ int freq = g_Notes[idx].frequency, ms = g_Notes[idx].durationMS; /* We first calculate the total micro-seconds for this note. */ int32_t total_us = (((int32_t) ms)*1000LL); register unsigned desiredHalfPeriod_us = 500000/freq; /* How many loops will we run, delaying one Period each time? */ int loops = total_us/(2*desiredHalfPeriod_us); /* Originally, at this point the following code existed: */ /* while(loops--) { */ /* PORTC ^= 0x02; // Buzzer on */ /* _delay_us(desiredHalfPeriod); // sleep for... */ /* PORTC ^= 0x02 // Buzzer off; */ /* _delay_us(desiredHalfPeriod); // sleep for... */ /* } */ /* It turned out however, that this code did not work... */ /* The implementation of _delay_us does floating point arithmetic */ /* to calculate the number of ticks to wait, and since _delay_us is */ /* called all the time, we end up doing lots of floating point work */ /* which takes too much time and messes up our timing... */ /* Two things, after reading the code in util/delay.h : */ /* First, we know that the UPPER_LIMIT_IN_DELAY_US is actually the */ /* same as a _delay_loop_1(255), so we will be "eating up" the */ /* desiredHalfPeriod calling this directly - and knowing that we */ /* "eat" UPPER_LIMIT_IN_DELAY_US each time we do it. */ /* Second, this will eventually get us to a stage where the remaining */ /* microseconds will be less than UPPER_LIMIT_IN_DELAY_US; We can't */ /* use _delay_us for them (floating point is slow...), so we... */ /* pre-calculate the "remainder" for each note, at the beginning of */ /* the main() function below, and store it inside g_remainderTicks... */ /* We therefore prepare a "calculation" cache, that we use per loop...*/ /* And here, in all its glory, the main oscillator loop, without any */ /* slow floating point anywhere... */ while(loops--) { register unsigned counter = desiredHalfPeriod_us; PORTC ^= 0x02; /* Buzzer on */ /* Now sleep for half a period */ while(counter) { if (counter >= UPPER_LIMIT_IN_DELAY_US) { _delay_loop_1(255); counter -= UPPER_LIMIT_IN_DELAY_US; } else { _delay_loop_1(g_remainderTicks[idx]); break; } } PORTC ^= 0x02; /* Buzzer off */ /* Now sleep for half a period */ counter = desiredHalfPeriod_us; while(counter) { if (counter >= UPPER_LIMIT_IN_DELAY_US) { _delay_loop_1(255); counter -= UPPER_LIMIT_IN_DELAY_US; } else { _delay_loop_1(g_remainderTicks[idx]); break; } } } } int main() { int idx = 0; DDRC = 0x0F; /* PC0..PC3 as output */ PORTC = 0x00; /* all PORTC output pins Off */ /* First, update the "remainder" table for each note (a "cache", */ /* read the comments inside the function "buzz" above) */ while(idx<sizeof(g_Notes)/sizeof(struct _Note)) { unsigned desiredHalfPeriod_us = 500000/g_Notes[idx].frequency; uint8_t __ticks; double __tmp = ((F_CPU) / 3e6) * (desiredHalfPeriod_us % UPPER_LIMIT_IN_DELAY_US); if (__tmp < 1.0) __ticks = 1; else if (__tmp > 255) { __ticks = 255; } else __ticks = (uint8_t)__tmp; g_remainderTicks[idx++] = __ticks; } /* Then, play the notes (and the silences), over and over again... */ while(1) { idx = 0; while(idx<sizeof(g_Notes)/sizeof(struct _Note)) { PORTC ^= 0x01; if (g_Notes[idx].frequency == 0) _delay_ms(g_Notes[idx].durationMS); else buzz(idx); idx ++; } } return 0; }
Index | CV | Updated: Sat Oct 8 12:33:59 2022 |
The comments on this website require the use of JavaScript. Perhaps your browser isn't JavaScript capable; or the script is not being run for another reason. If you're interested in reading the comments or leaving a comment behind please try again with a different browser or from a different connection.