Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- speaker_pcm
- Plays 8-bit PCM audio on pin 11 using pulse-width modulation (PWM).
- For Arduino with Atmega168 at 16 MHz.
- Uses two timers. The first changes the sample value 8000 times a second.
- The second holds pin 11 high for 0-255 ticks out of a 256-tick cycle,
- depending on sample value. The second timer repeats 62500 times per second
- (16000000 / 256), much faster than the playback rate (8000 Hz), so
- it almost sounds halfway decent, just really quiet on a PC speaker.
- Takes over Timer 1 (16-bit) for the 8000 Hz timer. This breaks PWM
- (analogWrite()) for Arduino pins 9 and 10. Takes Timer 2 (8-bit)
- for the pulse width modulation, breaking PWM for pins 11 & 3.
- References:
- http://www.uchobby.com/index.php/2007/11/11/arduino-sound-part-1/
- http://www.atmel.com/dyn/resources/prod_documents/doc2542.pdf
- http://www.evilmadscientist.com/article.php/avrdac
- http://gonium.net/md/2006/12/27/i-will-think-before-i-code/
- http://fly.cc.fer.hr/GDM/articles/sndmus/speaker2.html
- http://www.gamedev.net/reference/articles/article442.asp
- Michael Smith <michael@hurts.ca>
- */
- #include <stdint.h>
- #include <avr/interrupt.h>
- #include <avr/io.h>
- #include <avr/pgmspace.h>
- #define SAMPLE_RATE 8000
- #define SAMPLE_RATEF (float)SAMPLE_RATE
- #define BUFLEN 450
- /*
- The audio data needs to be unsigned, 8-bit, 8000 Hz, and small enough
- to fit in flash. 10000-13000 samples is about the limit.
- sounddata.h should look like this:
- const int sounddata_length=10000;
- const unsigned char sounddata_data[] PROGMEM = { ..... };
- You can use wav2c from GBA CSS:
- http://thieumsweb.free.fr/english/gbacss.html
- Then add "PROGMEM" in the right place. I hacked it up to dump the samples
- as unsigned rather than signed, but it shouldn't matter.
- http://musicthing.blogspot.com/2005/05/tiny-music-makers-pt-4-mac-startup.html
- mplayer -ao pcm macstartup.mp3
- sox audiodump.wav -v 1.32 -c 1 -r 8000 -u -1 macstartup-8000.wav
- sox macstartup-8000.wav macstartup-cut.wav trim 0 10000s
- wav2c macstartup-cut.wav sounddata.h sounddata
- (starfox) nb. under sox 12.18 (distributed in CentOS 5), i needed to run
- the following command to convert my wav file to the appropriate format:
- sox audiodump.wav -c 1 -r 8000 -u -b macstartup-8000.wav
- */
- int ledPin = 13;
- int speakerPin = 11;
- byte sin_pc[BUFLEN];
- int scnt = 0, ncnt = 0;
- float osc_phase = 0;
- float phaselen = 1.0;
- // gaga telephone intro
- byte notes[] = {
- 15, 8, 13, 8, 11, 8, 10, 11,
- 6, 11, 15, 17, 18, 15, 20, 18,
- 17, 8, 13, 5, 8, 1, 10, 8,
- 11, 8, 3, 11, 8, 3, 11, 8
- };
- void stopPlayback()
- {
- // Disable playback per-sample interrupt.
- TIMSK1 &= ~_BV(OCIE1A);
- // Disable the per-sample timer completely.
- TCCR1B &= ~_BV(CS10);
- // Disable the PWM timer.
- TCCR2B &= ~_BV(CS10);
- digitalWrite(speakerPin, LOW);
- }
- // This is called at every SAMPLE_RATE
- ISR(TIMER1_COMPA_vect) {
- int tsnd;
- tsnd = sin_pc[(uint16_t)osc_phase];
- tsnd -= 128;
- //tsnd = tsnd * sin_pc[scnt/6]/256;
- tsnd = tsnd * 32 / (scnt / 24 + 32);
- tsnd += 128;
- OCR2A = (byte)tsnd;
- osc_phase += phaselen;
- if (osc_phase > BUFLEN - 1) {
- while (osc_phase > BUFLEN - 1)
- osc_phase -= BUFLEN;
- }
- if (++scnt > 2000) {
- phaselen = BUFLEN / 14.0 * pow(2, 1.0 + (float)notes[ncnt] / 12.0);
- ncnt = (++ncnt) % 32;
- scnt = 0;
- }
- }
- void calcTables() {
- for (int i = 0; i < BUFLEN; i++) {
- sin_pc[i] = (byte)(sin((float)i * 3.14159265359 * 2.0 / (float)BUFLEN) * 120 + 128);
- }
- }
- void startPlayback()
- {
- pinMode(speakerPin, OUTPUT);
- // Set up Timer 2 to do pulse width modulation on the speaker pin.
- // Use internal clock (datasheet p.160)
- ASSR &= ~(_BV(EXCLK) | _BV(AS2));
- // Set fast PWM mode (p.157)
- TCCR2A |= _BV(WGM21) | _BV(WGM20);
- TCCR2B &= ~_BV(WGM22);
- if (speakerPin == 11) {
- // Do non-inverting PWM on pin OC2A (p.155)
- // On the Arduino this is pin 11.
- TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
- TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
- // No prescaler (p.158)
- TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
- // Set initial pulse width to the first sample.
- //OCR2A = pgm_read_byte(&sounddata_data[0]);
- }
- // Set up Timer 1 to send a sample every interrupt.
- cli();
- // Set CTC mode (Clear Timer on Compare Match) (p.133)
- // Have to set OCR1A *after*, otherwise it gets reset to 0!
- TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
- TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
- // No prescaler (p.134)
- TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
- // Set the compare register (OCR1A).
- // OCR1A is a 16-bit register, so we have to do this with
- // interrupts disabled to be safe.
- OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 8000 = 2000
- // Enable interrupt when TCNT1 == OCR1A (p.136)
- TIMSK1 |= _BV(OCIE1A);
- sei();
- }
- void setup()
- {
- calcTables();
- pinMode(ledPin, OUTPUT);
- digitalWrite(ledPin, HIGH);
- startPlayback();
- }
- void loop()
- {
- delay(100);
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement