/ Trinket_Question_Block_Sound_Jewelry / Trinket_Question_Block_Sound_Jewelry.ino
Trinket_Question_Block_Sound_Jewelry.ino
  1  // SPDX-FileCopyrightText: 2018 Phillip Burgess/paintyourdragon for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /* -----------------------------------------------------------------------
  6     Super Mario Bros-inspired coin sound for Adafruit Trinket & Gemma.
  7  
  8     Requires piezo speaker between pins 0 & 1, vibration sensor or
  9     momentary button between pin 2 & GND.  Tap for "bling!" noise.
 10     Optional LED+resistor on pin 4 for light during play.
 11  
 12     Runs equally well on a 16 MHz or 8 MHz Trinket, or on Gemma.  Use what
 13     you've got, no need to get all HOMG MOAR MEGAHURTZ!!1! about it.  :)
 14  
 15     This is NOT good beginner code to learn from...there's very little
 16     resemblance to a "normal" Arduino sketch as we poke around with ATtiny
 17     peripheral registers directly; will NOT run on other Arduino boards.
 18     Commented like mad regardless, might discover fun new stuff.
 19  
 20     Written by Phillip Burgess for Adafruit Industries.  Public domain.
 21     ----------------------------------------------------------------------- */
 22  
 23  #include <avr/power.h>
 24  #include <avr/sleep.h>
 25  
 26  // These variables are declared 'volatile' because their values may change
 27  // inside interrupts, independent of the mainline code.  This keeps the
 28  // optimizer from removing lines it would otherwise regard as unnecessary.
 29  // 'quietness' is basically the inverse of volume -- the code was a little
 30  // smaller expressing it this way. 0 = max volume, 127 = quietest.
 31  // 'count' is incremented while generating a square wave.  Used for timing,
 32  // and bit 0 indicates whether this is the 'high' or 'low' part of the wave.
 33  volatile uint8_t  quietness;
 34  volatile uint16_t count;
 35  
 36  // ONE-TIME INITIALIZATION -----------------------------------------------
 37  
 38  void setup() {
 39  #if (F_CPU == 16000000L)
 40    clock_prescale_set(clock_div_1);
 41  #endif
 42  
 43    // ATtiny85 has a special high-speed 64 MHz PLL mode than can be used
 44    // as an input to Timer/Counter 1.  The ATmega chips don't have this!
 45    // Requires a little song and dance to set this up...
 46    PLLCSR |= _BV(PLLE);           // Enable 64 MHz PLL
 47    delayMicroseconds(100);        // Allow time to stabilize
 48    while(!(PLLCSR & _BV(PLOCK))); // Wait for it...wait for it...
 49    PLLCSR |= _BV(PCKE);           // Timer1 source = PLL!
 50  
 51    // Enable Timer/Counter 1 PWM, OC1A & !OC1A output pins, 1:1 prescale.
 52    GTCCR = TIMSK = 0; // Timer interrupts OFF
 53    OCR1C = 255;       // 64M/256 = 250 KHz
 54    OCR1A = 127;       // 50% duty at start = off
 55    TCCR1 = _BV(PWM1A) | _BV(COM1A0) | _BV(CS10);
 56  
 57    // Normally the Arduino core library uses Timer/Counter 1 for functions
 58    // like delay(), millis(), etc.  Having changed the cycle time above,
 59    // and turning off the overflow interrupt, these functions won't work
 60    // after this.  Keeping track of time is our own responsibility now.
 61  
 62    // The Timer/Counter 1 PWM output doesn't time the output square wave;
 63    // the frequency (250 KHz) is much too fast for that.  Rather, the
 64    // speaker itself physically acts as a filter, with the average duty
 65    // cycle determining the cone position; center=off, 0,255=extremes.
 66    // A separate timer (using the actual sound frequency) then toggles the
 67    // duty cycle to adjust amplitude, providing volume control...
 68  
 69    // Configure Timer/Counter 0 for PWM (no interrupt enabled yet).
 70    TCCR0A = _BV(WGM01) | _BV(WGM00); // PWM mode
 71  
 72    // An external interrupt (INT0, pin 2 pulled to GND) wakes the chip
 73    // from sleep mode to play the sound.
 74    MCUCR &= ~(_BV(ISC01) | _BV(ISC00)); // Low level (GND) trigger
 75  }
 76  
 77  // -----------------------------------------------------------------------
 78  
 79  void loop() {
 80  
 81    // To maximize power savings, pins are set to inputs with pull-up
 82    // resistors enabled (except for pins 1&4, because LEDs).
 83    DDRB = B00000000; PORTB = B00101101;
 84  
 85    // The chip is then put into a very low-power sleep mode...
 86    power_all_disable();                 // All peripherals off
 87    GIMSK |=  _BV(INT0);                 // Enable external interrupt
 88    set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Deepest sleep
 89    sleep_mode();                        // Stop, will resume here on wake
 90    // Code resumes when pin 2 is pulled to GND (e.g. button press).
 91    GIMSK &= ~_BV(INT0);                 // Disable external interrupt
 92  
 93    // Only the two timer/counters are re-enabled on wake.  All other
 94    // peripheras remain off for power saving.  This means no ADC, I2C, etc.
 95    power_timer0_enable();
 96    power_timer1_enable();
 97  
 98    DDRB  = B00010011; // Output on pins 0,1 (piezo speaker), 4 (LED)
 99    PORTB = B00010000; // LED on
100  
101    // Play first note.  B5 = 987.77 Hz (round up to 988)
102    pitch(988); // Sets up Timer/Counter 1 for this frequency
103    // The pitch() function configures the timer for 2X the frequency, an
104    // interrupt then alternates between the 'high' and 'low' parts of the
105    // square wave.  988 Hz = 1976 interrupts/sec.  'count' keeps track.
106    // First note is 0.083 sec.  1976 * 0.083 = 164 interrupt counts.
107    // Combined length of notes is 0.92 sec, or 1818 interrupt counts at
108    // this frequency.  The amplitude (volume) fades linearly through the
109    // duration of both notes.  So this calculates the portion of that drop
110    // through the first note...
111    while(count < 164) quietness = 128L * count / 1818;
112    // This uses fixed-point (integer) math, because floating-point is slow
113    // on the AVR and uses lots of program space.  A large integer multiply
114    // (32-bit intermediate result) precedes an integer division, result is
115    // effectively equal to floating-point multiply of 128.0 * 0.0 to 1.0.
116  
117    pitch(1319); // Init second note.  E6 = 1318.51 Hz, round up to 1319.
118    // 1319 Hz tone = 2638 Hz interrupt.  To maintain the duration and make
119    // the volume-scaling math continue from the prior level, counts need to
120    // be adjusted to take this timing change into account.  The total
121    // length at this rate would be 2638 * 0.92 = 2427 counts, and first
122    // note duration would have been 2638 * 0.083 = 219 counts...
123    count = 219;
124    // Rather than counting up to the duration, just keep playing until the
125    // effective volume is zero.
126    do {
127      quietness = 128L * count / 2427;
128    } while(quietness < 127);
129  
130    // Finished playing both notes.  Disable the timer interrupt...
131    TIMSK = 0;
132  
133    // Before finishing, the piezo speaker is eased in a controlled manner
134    // from the volume-neutral position (127) to its off position (0) in
135    // order to avoid an audible 'pop' when the code goes to sleep.
136    for(uint8_t i=127; i--; ) {
137      OCR1A = i;                                     // Speaker position
138      for(volatile uint16_t x = F_CPU/32000; --x; ); // Easy, not too fast
139    }
140  }
141  
142  // -----------------------------------------------------------------------
143  
144  // These tables list available timer/counter prescaler values and their
145  // configuration bit settings.  Normally I'd PROGMEM these, but for these
146  // short tables the code actually compiles a little smaller this way!
147  const uint16_t prescale[] = { 1, 8, 64, 256, 1024 };
148  const uint8_t  tccr[] = {
149    _BV(WGM02)                         | _BV(CS00),
150    _BV(WGM02)             | _BV(CS01),
151    _BV(WGM02)             | _BV(CS01) | _BV(CS00),
152    _BV(WGM02) | _BV(CS02),
153    _BV(WGM02) | _BV(CS02)             | _BV(CS00),
154  };
155  
156  // Configure Timer/Counter 0 for the requested frequency
157  void pitch(uint16_t freq) {
158  
159    uint8_t  i;
160    uint16_t f2 = freq << 1; // 2X frequency
161    uint32_t o;
162  
163    // Find CPU prescale that can accommodate requested frequency
164    for(i=0; i < (sizeof(prescale) / sizeof(prescale[0])) &&
165      (o = (((F_CPU / (uint32_t)prescale[i]) + freq) /
166            (uint32_t)f2) - 1) >= 256L; i++);
167  
168    TCCR0B = tccr[i];     // Prescale config bits
169    OCR0A  = (uint8_t)o;  // PWM interval for 2X freq
170    count  = 0;           // Reset waveform counter
171    TIMSK  = _BV(OCIE0A); // Enable compare match interrupt
172  }
173  
174  // TIMER0_OVF vector is already claimed by the Arduino core library,
175  // can't use that.  So the compare vector is used instead...
176  ISR(TIMER0_COMPA_vect) {
177    // Bit 0 of count indicates high or low side of square wave.
178    // OCR1A sets average speaker pos, quietness adjusts amplitude.
179    OCR1A = (count++ & 1) ? 255 - quietness : quietness;
180  }