Playing Audio from 1MB SPI Flash on Arduino Uno

Post here about your Arduino projects, get help - for Adafruit customers!

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
User avatar
bbcentral
 
Posts: 20
Joined: Fri Jan 17, 2014 11:40 am

Playing Audio from 1MB SPI Flash on Arduino Uno

Post by bbcentral »

Hi,

I'm working on a "simple" project which involves playing audio. Basically there's a box with a button on the front. You press the button, the music starts playing (a 10 second continuous loop). You press the button again, it stops playing...or perhaps the volume goes up, or perhaps it stops for 3 minutes and starts playing again. I haven't decided on that bit yet :P

Anyway, I bought all the components needed to build the Trinket Audio Player:
https://learn.adafruit.com/trinket-audio-player
I followed the tutorials, made some modifications, wasted a lot of time messing about with Windows 8 issues before finally getting it working. It worked great, until I went to solder it all together for the final version.

If you haven't already figured out the fatal flaw in my project idea yet, it's that the Trinket Audio Player project uses up all 5 of the Trinket's pins, leaving none for the button! Somehow along the way I never realised this, one of the downsides of only being able to work on projects for a few hours a month.

I didn't want to go down the path of using a WaveShield with an SD Card just to play a looping 10 second audio clip, so I did some digging and discovered this thread:
http://forums.adafruit.com/viewtopic.php?f=25&t=49324
So it definitely seems possible.

I have a spare Arduino Uno, so I wired up the Trinket Audio Player circuit to the Uno and managed to get it to read from the SPI Flash chip. It's definitely reading, it told me the correct sample rate and I can get it to dump out the bytes to the Serial console. The Adafruit_Tinyflash library made that very easy to achieve, I was up and running with an hour.

The problem is that all the code related to the playback (in the TrinketPlayer sketch) is targeted specifically to the Trinket, and I can't decipher much of it. I've been hacking and modifying the code, removing lines that cause compiler errors and scouring forums trying to convert it from "Trinket" to "Uno".

I did some research and I believe I can use Digital Pin #3 for a PWM output pin, so I've connected the amplifier input to that. It plays a short burst of audio over and over, like it's stuck on the first frame.

I don't understand any of the timer/counter code, it all goes right over my head. Randomly changing values isn't going to solve the problem, and I don't understand any of the tutorials I've found explaining this.

Here's the code I have, it's a giant mess but it's as far as I've got:

Code: Select all


#include <SPI.h>
#include <Adafruit_TinyFlash.h>


Adafruit_TinyFlash flash;
uint16_t           sample_rate, delay_count;
uint32_t           samples;
volatile uint32_t  index = 0L;

void setup() {
  Serial.begin(57600);
  uint8_t  data[6];
  uint32_t bytes;
  if(!(bytes = flash.begin())) {     // Flash init error?
    for(;; PORTB ^= 2, delay(250));  // Blink 2x/sec
  }

  // First six bytes contain sample rate, number of samples
  flash.beginRead(0);
  for(uint8_t i=0; i<6; i++) data[i] = flash.readNextByte();
  sample_rate = ((uint16_t)data[0] <<  8)
              |  (uint16_t)data[1];
  samples     = ((uint32_t)data[2] << 24)
              | ((uint32_t)data[3] << 16)
              | ((uint32_t)data[4] <<  8)
              |  (uint32_t)data[5];

  Serial.print("Sample Rate: ");
  Serial.println(sample_rate);

  // Audio begins at next byte, so DON'T endRead() here

  pinMode(3, OUTPUT);                // Enable PWM output pin

  // NOTE: CODE BELOW IS PROBABLY COMPLETELY WRONG

/*  PLLCSR |= _BV(PLLE);               // Enable 64 MHz PLL
  delayMicroseconds(100);            // Stabilize
  while(!(PLLCSR & _BV(PLOCK)));     // Wait for it...
  PLLCSR |= _BV(PCKE);               // Timer1 source = PLL*/

  // Set up Timer/Counter1 for PWM output
  TIMSK0  = 0;                        // Timer interrupts OFF

 TCCR2B  = _BV(CS10);                // 1:1 prescale
  GTCCR  = _BV(COM2B1); // PWM B, clear on match
  OCR1A  = 255;                      // Full 8-bit PWM cycle
  OCR1B  = 127;                      // 50% duty at start


  // Set up Timer/Counter0 for sample-playing interrupt.
  // TIMER0_OVF_vect is already in use by the Arduino runtime,
  // so TIMER0_COMPA_vect is used.  This code alters the timer
  // interval, making delay(), micros(), etc. useless (the
  // overflow interrupt is therefore disabled).

  // Timer resolution is limited to either 0.125 or 1.0 uS,
  // so it's rare that the playback rate will precisely match
  // the data, but the difference is usually imperceptible.
  TCCR0A = _BV(WGM01) | _BV(WGM00);  // Mode 7 (fast PWM)
  if(sample_rate >= 31250) {
    TCCR0B = _BV(WGM02) | _BV(CS00); // 1:1 prescale
    OCR0A  = ((F_CPU + (sample_rate / 2)) / sample_rate) - 1;
  } else {                           // Good down to about 3900 Hz
    TCCR0B = _BV(WGM02) | _BV(CS01); // 1:8 prescale
    OCR0A  = (((F_CPU / 8L) + (sample_rate / 2)) / sample_rate) - 1;
  }
  //TIMSK1 = _BV(OCIE0A); // Enable compare match, disable overflow
}

void loop() {
  //OCR1B = flash.readNextByte();      // Read flash, write PWM reg.
  analogWrite(3,flash.readNextByte());
  //Serial.println(flash.readNextByte());
  if(++index >= samples) {           // End of audio data?
    index = 0;                       // We must repeat!
    flash.endRead();
    flash.beginRead(6);              // Skip 6 byte header
  }
}


If anyone can give me a few pointers it would be greatly appreciated. I'm not afraid to work things out for myself, but right now I'm just going around in circles.

Thanks

Edit: This post did help a little, but I'm still wading through it: http://www.protostack.com/blog/2010/09/ ... atmega168/

User avatar
adafruit_support_mike
 
Posts: 67454
Joined: Thu Feb 11, 2010 2:51 pm

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by adafruit_support_mike »

You're not completely out of luck with the Trinket. The Flash chip does use all the pins, but theres no rule that says you have to use them all the time.

Depending on how (or more specifically, when) you want to control the Trinket, you can do quite a lot with pin #3. When you're playing sounds, it provides the Chip Select signal that tells the Flash chip to pay attention. As soon as you stop playing the sound though, that pin is free to do other things.

One of the most interesting things it can do is read analog input. If you put a series of pushbuttons between pin #3 and points along a voltage divider, you can get a whole range of input signals from just that one pin. Better yet, the buttons would be open during playback, so they wouldn't interfere with pin #3's CS function while the Trinket is talking to the Flash chip.

The same is true in general for all the pins.. if you're willing to have the functions take turns, a single pin can do several different things.

User avatar
bbcentral
 
Posts: 20
Joined: Fri Jan 17, 2014 11:40 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by bbcentral »

Wow! Those Trinkets are even more versatile than I thought, now I'm glad I bought the 6-pack :P

I'll stop working on the Uno code and see if I can get a button working with the Trinket. Right now I'm trying to work out why it's only playing the first frame/byte of the audio, I transferred the circuit from a breadboard to a PCB to make it permanent, and something has gone wrong in the process. Could be a bad connection somewhere, or a short. I'll keep troubleshooting, then I'll see if I can get a play/stop button to work.

Thanks for your help!

User avatar
adafruit_support_mike
 
Posts: 67454
Joined: Thu Feb 11, 2010 2:51 pm

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by adafruit_support_mike »

Yeah, microcontrollers are pretty cool. ;-)

Swapping pin functions is kind of an advanced topic, but is definitely possible. With more complicated chips you can stack input, output, and various kinds of interrupts onto a single pin.

The resistor-ladder/analog-input trick is one of those tricks someone with a tight pin budget worked out one time and then everyone copied. There's another version that uses RC charging times to read multiple buttons from a single digital pin, but the ADC version is easier if you have the option.

Microchip has a couple of good "Tips and Tricks" app notes that provide clever solutions for common problems:

http://ww1.microchip.com/downloads/en/D ... 01146B.pdf
http://www.newark.com/pdfs/techarticles ... sBrchr.pdf

User avatar
bbcentral
 
Posts: 20
Joined: Fri Jan 17, 2014 11:40 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by bbcentral »

I've been working on this for a week with no success. The problem is that I really have no idea what I'm doing, I don't understand the code I'm working with, I'm just mashing examples together to see if anything works.

Here's what I can do:
  • I can get it to play the audio in the default example
  • I can get the Trinket to read a button input on Pin #3 and turn an LED on/off
But when I try to merge the two projects together they don't work, the audio doesn't start playing.

To reiterate what I'm doing, I have a 7-second audio loop on the flash chip.
When you press the button, it starts playing from the start of the audio loop.
It continues playing the 7-second loop over and over again.
When you press the button again, it stops playing immediately.

Here's how my code looks:

Code: Select all

// Audio playback sketch for Adafruit Trinket.  Requires 3.3V
// Trinket board and Winbond W25Q80BV serial flash loaded with
// audio data.  PWM output on pin 4; add ~25 KHz low-pass filter
// prior to amplification.  Uses ATtiny-specific registers;
// WILL NOT RUN ON OTHER ARDUINOS.

#include <Adafruit_TinyFlash.h>

#if(F_CPU == 16000000L)
#error "Compile for 8 MHz Trinket"
#endif

Adafruit_TinyFlash flash;
uint16_t           sample_rate, delay_count;
uint32_t           samples;
volatile uint32_t  index = 0L;

// constants won't change. They're used here to
// set pin numbers:
const int buttonPin = 3;    // the number of the pushbutton pin

// Variables will change:
int isPlaying = false;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

// the following variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers


void setup() {
  // Setup the button as an INPUT, and set the pullup resistor to HIGH
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH);

  uint8_t  data[6];
  uint32_t bytes;

  if(!(bytes = flash.begin())) {     // Flash init error?
    for(;; PORTB ^= 2, delay(250));  // Blink 2x/sec
  }

  // First six bytes contain sample rate, number of samples
  flash.beginRead(0);
  for(uint8_t i=0; i<6; i++) data[i] = flash.readNextByte();
  sample_rate = ((uint16_t)data[0] <<  8)
              |  (uint16_t)data[1];
  samples     = ((uint32_t)data[2] << 24)
              | ((uint32_t)data[3] << 16)
              | ((uint32_t)data[4] <<  8)
              |  (uint32_t)data[5];
  // Audio begins at next byte, so DON'T endRead() here

  PLLCSR |= _BV(PLLE);               // Enable 64 MHz PLL
  delayMicroseconds(100);            // Stabilize
  while(!(PLLCSR & _BV(PLOCK)));     // Wait for it...
  PLLCSR |= _BV(PCKE);               // Timer1 source = PLL

  // Set up Timer/Counter1 for PWM output
  TIMSK  = 0;                        // Timer interrupts OFF
  TCCR1  = _BV(CS10);                // 1:1 prescale
  GTCCR  = _BV(PWM1B) | _BV(COM1B1); // PWM B, clear on match
  OCR1C  = 255;                      // Full 8-bit PWM cycle
  OCR1B  = 127;                      // 50% duty at start

  pinMode(4, OUTPUT);                // Enable PWM output pin

  // Set up Timer/Counter0 for sample-playing interrupt.
  // TIMER0_OVF_vect is already in use by the Arduino runtime,
  // so TIMER0_COMPA_vect is used.  This code alters the timer
  // interval, making delay(), micros(), etc. useless (the
  // overflow interrupt is therefore disabled).

  // Timer resolution is limited to either 0.125 or 1.0 uS,
  // so it's rare that the playback rate will precisely match
  // the data, but the difference is usually imperceptible.
  TCCR0A = _BV(WGM01) | _BV(WGM00);  // Mode 7 (fast PWM)
  if(sample_rate >= 31250) {
    TCCR0B = _BV(WGM02) | _BV(CS00); // 1:1 prescale
    OCR0A  = ((F_CPU + (sample_rate / 2)) / sample_rate) - 1;
  } else {                           // Good down to about 3900 Hz
    TCCR0B = _BV(WGM02) | _BV(CS01); // 1:8 prescale
    OCR0A  = (((F_CPU / 8L) + (sample_rate / 2)) / sample_rate) - 1;
  }
  TIMSK = _BV(OCIE0A); // Enable compare match, disable overflow
}

void loop() {

  // read the state of the switch into a local variable:
  int reading = digitalRead(buttonPin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH),  and you've waited
  // long enough since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
        isPlaying = !isPlaying;
      }
    }
  }
  lastButtonState = reading;
}

ISR(TIMER0_COMPA_vect) {

  if(isPlaying) {
    // If the button has been pressed and isPlaying=true, then play the loop

    OCR1B = flash.readNextByte();      // Read flash, write PWM reg.
    if(++index >= samples) {           // End of audio data?
      index = 0;                       // We must repeat!
      flash.endRead();
      flash.beginRead(6);              // Skip 6 byte header
    }
  }

}

As far as wiring, the circuit is just the TrinketPlayer circuit with a pushbutton connected to PIN3, a 10K resistor, and 3V.

From what I can tell, the ISR() function is checking for the isPlaying variable (I can set it to true and playback begins, if I set it to false there's nothing played).
What I don't know:
  • Is the button input being read?
  • Have I wired the button up correctly?
  • Is my code correct?
  • Is the 'isPlaying' variable even changing when the button is pressed?
I can't come up with a way to test any of these things, so my progress has ground to a complete halt. If it was another Arduino I'd just spit out values using the Serial console, but that isn't available. I did try to get the Trinket LED to switch on/off when the button was pressed (and it was working), but that stopped working once the flash chip code was restored to the project, probably because PIN1 is already being used.

I tried reading the button input inside the ISR() function instead of loop() (in case of a conflict with the timers), but that didn't work.

Where do I go from here?

User avatar
adafruit_support_rick
 
Posts: 35092
Joined: Tue Mar 15, 2011 11:42 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by adafruit_support_rick »

I'm not really familiar with the setup - do you have a spare output pin? If so, you can use SoftwareSerial for debugging.

Also, have you thought about checking out the overall program logic on a regular Arduino?

User avatar
bbcentral
 
Posts: 20
Joined: Fri Jan 17, 2014 11:40 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by bbcentral »

adafruit_support_rick wrote:I'm not really familiar with the setup - do you have a spare output pin? If so, you can use SoftwareSerial for debugging.
I don't think I have any spare pins, all five are being used by the Trinket Audio Player circuit:
https://learn.adafruit.com/trinket-audi ... d-playback
adafruit_support_rick wrote:Also, have you thought about checking out the overall program logic on a regular Arduino?
I've had little success converting the Trinket program into one that will run on an Arduino Uno, as explained in my first post here. It will take a substantial rewrite to get it working because it's so different to the Trinket.

I also believe I'm going to run into issues with the debounce code I included, it says in the Trinket Audio Player comments that this affects the delay(), micros() and probably also millis() functions.

User avatar
adafruit_support_mike
 
Posts: 67454
Joined: Thu Feb 11, 2010 2:51 pm

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by adafruit_support_mike »

Post a photo of your hardware and connections and we'll see if we can find anything there.

User avatar
bbcentral
 
Posts: 20
Joined: Fri Jan 17, 2014 11:40 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by bbcentral »

adafruit_support_mike wrote:Post a photo of your hardware and connections and we'll see if we can find anything there.
I don't understand why that would make any difference, I'm 99.9% sure it's a code issue.
But in any case, here it is:
Image

Yes, it's literally just your Adafruit Trinket Audio Player circuit, but with a push button connected to Pin #3 (do I need to load Photoshop and draw this?).

The default Audio Player circuit (as detailed in your Tutorial) works perfectly. I connect the power and it starts playing.
I've also been able to connect a push button to Pin #3 and use it to turn an LED on and off (by stripping out all code related to the audio/flash circuit).

Combining the two circuits is what's failing, and I'm sure it's because of my code.

To quote your Tutorial:
You could make an electronic greeting card with your own customized message or song, ... or create the world’s smartest whoopee cushion.
How would I go about doing any of these things? If it's not possible to control the playback with a Play/Stop button, then I need to know now so I can abandon this project. But your previous response (and the tutorial) gave me the impression it was possible.

Goal:
1) Connect the power to the Trinket
2) Press the button, playback begins and keeps looping
3) Press the button again, playback stops

Is this actually achievable with the Trinket? If not, you may want to update your tutorial to explain that you can only control playback by connecting/disconnecting the power.

User avatar
adafruit_support_rick
 
Posts: 35092
Joined: Tue Mar 15, 2011 11:42 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by adafruit_support_rick »

You're using millis() in your debounce routine. But the comments say this:

Code: Select all

// Set up Timer/Counter0 for sample-playing interrupt.
  // TIMER0_OVF_vect is already in use by the Arduino runtime,
  // so TIMER0_COMPA_vect is used.  This code alters the timer
  // interval, making delay(), micros(), etc. useless (the
  // overflow interrupt is therefore disabled).
millis() will also be useless - since the interrupt is disabled, the value returned will never change.

User avatar
bbcentral
 
Posts: 20
Joined: Fri Jan 17, 2014 11:40 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by bbcentral »

adafruit_support_rick wrote:You're using millis() in your debounce routine. But the comments say this:

Code: Select all

// Set up Timer/Counter0 for sample-playing interrupt.
  // TIMER0_OVF_vect is already in use by the Arduino runtime,
  // so TIMER0_COMPA_vect is used.  This code alters the timer
  // interval, making delay(), micros(), etc. useless (the
  // overflow interrupt is therefore disabled).
millis() will also be useless - since the interrupt is disabled, the value returned will never change.
Yes, I mentioned that in my post:
I also believe I'm going to run into issues with the debounce code I included, it says in the Trinket Audio Player comments that this affects the delay(), micros() and probably also millis() functions.
I tried to achieve this without the millis() and loop() by moving the button code straight to the ISR() function, but I don't even know if that's possible.

I noticed that when I try to read PIN #3 it slows down the audio loop, so I tried to access the pin directly. There's no debounce function there for now, I can worry about that later.

I can successfully control whether or not playback is occurring by setting isPlaying to true or false, but I can't control that variable by using a push button.

Here's how my code currently looks.

Code: Select all

// Audio playback sketch for Adafruit Trinket.  Requires 3.3V
// Trinket board and Winbond W25Q80BV serial flash loaded with
// audio data.  PWM output on pin 4; add ~25 KHz low-pass filter
// prior to amplification.  Uses ATtiny-specific registers;
// WILL NOT RUN ON OTHER ARDUINOS.

#include <Adafruit_TinyFlash.h>

#if(F_CPU == 16000000L)
#error "Compile for 8 MHz Trinket"
#endif

Adafruit_TinyFlash flash;
uint16_t           sample_rate, delay_count;
uint32_t           samples;
volatile uint32_t  index = 0L;

// constants won't change. They're used here to
// set pin numbers:
#define TRINKET_PINx  PINB
#define PIN_PLAYBUTTON 3

// Variables will change:
int isPlaying = false;         // the current state of the output pin
int atStart = true;

// the following variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers


void setup() {

  uint8_t  data[6];
  uint32_t bytes;

  if(!(bytes = flash.begin())) {     // Flash init error?
    for(;; PORTB ^= 2, delay(250));  // Blink 2x/sec
  }

  // First six bytes contain sample rate, number of samples
  flash.beginRead(0);
  for(uint8_t i=0; i<6; i++) data[i] = flash.readNextByte();
  sample_rate = ((uint16_t)data[0] <<  8)
              |  (uint16_t)data[1];
  samples     = ((uint32_t)data[2] << 24)
              | ((uint32_t)data[3] << 16)
              | ((uint32_t)data[4] <<  8)
              |  (uint32_t)data[5];
  // Audio begins at next byte, so DON'T endRead() here

  PLLCSR |= _BV(PLLE);               // Enable 64 MHz PLL
  delayMicroseconds(100);            // Stabilize
  while(!(PLLCSR & _BV(PLOCK)));     // Wait for it...
  PLLCSR |= _BV(PCKE);               // Timer1 source = PLL

  // // Set up Timer/Counter1 for PWM output
  TIMSK  = 0;                        // Timer interrupts OFF
  TCCR1  = _BV(CS10);                // 1:1 prescale
  GTCCR  = _BV(PWM1B) | _BV(COM1B1); // PWM B, clear on match
  OCR1C  = 255;                      // Full 8-bit PWM cycle
  OCR1B  = 127;                      // 50% duty at start

  pinMode(4, OUTPUT);                // Enable PWM output pin

  // Set up Timer/Counter0 for sample-playing interrupt.
  // TIMER0_OVF_vect is already in use by the Arduino runtime,
  // so TIMER0_COMPA_vect is used.  This code alters the timer
  // interval, making delay(), micros(), etc. useless (the
  // overflow interrupt is therefore disabled).

  // Timer resolution is limited to either 0.125 or 1.0 uS,
  // so it's rare that the playback rate will precisely match
  // the data, but the difference is usually imperceptible.
  TCCR0A = _BV(WGM01) | _BV(WGM00);  // Mode 7 (fast PWM)
  if(sample_rate >= 31250) {
    TCCR0B = _BV(WGM02) | _BV(CS00); // 1:1 prescale
    OCR0A  = ((F_CPU + (sample_rate / 2)) / sample_rate) - 1;
  } else {                           // Good down to about 3900 Hz
    TCCR0B = _BV(WGM02) | _BV(CS01); // 1:8 prescale
    OCR0A  = (((F_CPU / 8L) + (sample_rate / 2)) / sample_rate) - 1;
  }
  TIMSK = _BV(OCIE0A); // Enable compare match, disable overflow
}

void loop() { }

ISR(TIMER0_COMPA_vect) {

  if (bit_is_set(TRINKET_PINx, PIN_PLAYBUTTON)) {
    isPlaying = true;
  }
  else {
    isPlaying = false;
  }

  if(isPlaying) {
    // If the button has been pressed and isPlaying=true, then play the loop
    if(atStart) {
      index = 0;                       // We must repeat!
      flash.endRead();
      flash.beginRead(6);              // Skip 6 byte header
      atStart = false;
    }
    OCR1B = flash.readNextByte();      // Read flash, write PWM reg.
    if(++index >= samples) {           // End of audio data?
      atStart = true;
    }
  }
  else {
    atStart = true;
  }
}
Any ideas how I could toggle isPlaying using a push button on Pin #3 without interfering with the flash Chip Select function?

User avatar
adafruit_support_rick
 
Posts: 35092
Joined: Tue Mar 15, 2011 11:42 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by adafruit_support_rick »

Well, the reason your button isn't working is that pin 3 is set to output. To read it, you're going to have to change the pin mode. You want to change the logic, as well - have your button short to GND rather than to 3.3V. That way, pushing the button won't necessarily interfere with any SPI operations. BTW - you don't need the resistor - just the button between pin3 and GND.

Code: Select all

ISR(TIMER0_COMPA_vect) {
  
  pinMode(PIN_PLAYBUTTON, INPUT_PULLUP);
  if (bit_is_set(TRINKET_PINx, PIN_PLAYBUTTON)) {
    isPlaying = true;
  }
  else {
    isPlaying = false;
  }
  pinMode(PIN_PLAYBUTTON, OUTPUT);
  
  if(isPlaying) {
    . . . etc . . .

User avatar
bbcentral
 
Posts: 20
Joined: Fri Jan 17, 2014 11:40 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by bbcentral »

adafruit_support_rick wrote:Well, the reason your button isn't working is that pin 3 is set to output. To read it, you're going to have to change the pin mode. You want to change the logic, as well - have your button short to GND rather than to 3.3V. That way, pushing the button won't necessarily interfere with any SPI operations. BTW - you don't need the resistor - just the button between pin3 and GND.
I was sure I had a version of that code previously (I figured I'd need to repeatedly switch PIN 3 between INPUT and OUTPUT), but I've applied the change. It's still not working unfortunately.

Here's the code:

Code: Select all

// Audio playback sketch for Adafruit Trinket.  Requires 3.3V
// Trinket board and Winbond W25Q80BV serial flash loaded with
// audio data.  PWM output on pin 4; add ~25 KHz low-pass filter
// prior to amplification.  Uses ATtiny-specific registers;
// WILL NOT RUN ON OTHER ARDUINOS.

#include <Adafruit_TinyFlash.h>

#if(F_CPU == 16000000L)
#error "Compile for 8 MHz Trinket"
#endif

Adafruit_TinyFlash flash;
uint16_t           sample_rate, delay_count;
uint32_t           samples;
volatile uint32_t  index = 0L;

// constants won't change. They're used here to
// set pin numbers:
#define TRINKET_PINx  PINB
#define PIN_PLAYBUTTON 3

// Variables will change:
int isPlaying = false;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = LOW;   // the previous reading from the input pin

int atStart = true;

// the following variables are long's because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers


void setup() {

  uint8_t  data[6];
  uint32_t bytes;

  if(!(bytes = flash.begin())) {     // Flash init error?
    for(;; PORTB ^= 2, delay(250));  // Blink 2x/sec
  }

  // First six bytes contain sample rate, number of samples
  flash.beginRead(0);
  for(uint8_t i=0; i<6; i++) data[i] = flash.readNextByte();
  sample_rate = ((uint16_t)data[0] <<  8)
              |  (uint16_t)data[1];
  samples     = ((uint32_t)data[2] << 24)
              | ((uint32_t)data[3] << 16)
              | ((uint32_t)data[4] <<  8)
              |  (uint32_t)data[5];
  // Audio begins at next byte, so DON'T endRead() here

  PLLCSR |= _BV(PLLE);               // Enable 64 MHz PLL
  delayMicroseconds(100);            // Stabilize
  while(!(PLLCSR & _BV(PLOCK)));     // Wait for it...
  PLLCSR |= _BV(PCKE);               // Timer1 source = PLL

  // // Set up Timer/Counter1 for PWM output
  TIMSK  = 0;                        // Timer interrupts OFF
  TCCR1  = _BV(CS10);                // 1:1 prescale
  GTCCR  = _BV(PWM1B) | _BV(COM1B1); // PWM B, clear on match
  OCR1C  = 255;                      // Full 8-bit PWM cycle
  OCR1B  = 127;                      // 50% duty at start

  pinMode(4, OUTPUT);                // Enable PWM output pin

  // Set up Timer/Counter0 for sample-playing interrupt.
  // TIMER0_OVF_vect is already in use by the Arduino runtime,
  // so TIMER0_COMPA_vect is used.  This code alters the timer
  // interval, making delay(), micros(), etc. useless (the
  // overflow interrupt is therefore disabled).

  // Timer resolution is limited to either 0.125 or 1.0 uS,
  // so it's rare that the playback rate will precisely match
  // the data, but the difference is usually imperceptible.
  TCCR0A = _BV(WGM01) | _BV(WGM00);  // Mode 7 (fast PWM)
  if(sample_rate >= 31250) {
    TCCR0B = _BV(WGM02) | _BV(CS00); // 1:1 prescale
    OCR0A  = ((F_CPU + (sample_rate / 2)) / sample_rate) - 1;
  } else {                           // Good down to about 3900 Hz
    TCCR0B = _BV(WGM02) | _BV(CS01); // 1:8 prescale
    OCR0A  = (((F_CPU / 8L) + (sample_rate / 2)) / sample_rate) - 1;
  }
  TIMSK = _BV(OCIE0A); // Enable compare match, disable overflow
}

void loop() { }

ISR(TIMER0_COMPA_vect) {

  pinMode(PIN_PLAYBUTTON, INPUT_PULLUP);
  if (bit_is_set(TRINKET_PINx, PIN_PLAYBUTTON)) {
    isPlaying = true;
  }
  else {
    isPlaying = false;
  }
  pinMode(PIN_PLAYBUTTON, OUTPUT);

  if(isPlaying) {
    // If the button has been pressed and isPlaying=true, then play the loop
    if(atStart) {
      index = 0;                       // We must repeat!
      flash.endRead();
      flash.beginRead(6);              // Skip 6 byte header
      atStart = false;
    }
    OCR1B = flash.readNextByte();      // Read flash, write PWM reg.
    if(++index >= samples) {           // End of audio data?
      atStart = true;
    }
  }
  else {
    atStart = true;
  }
}
I connected a push button between PIN #3 and Ground.

What happens now is I connect the power and hear a light crackle from the speaker. The Trinket LED lights up, then switches off (because isPlaying is false, so it doesn't play yet). If I press the button though, nothing happens at all.

I think we're narrowing it down though. When I hard-code "isPlaying=true" but leave the pinMode functions, playback still doesn't work. In this example I've disconnected the button entirely.

Code: Select all

ISR(TIMER0_COMPA_vect) {

  pinMode(PIN_PLAYBUTTON, INPUT_PULLUP);
  if (bit_is_set(TRINKET_PINx, PIN_PLAYBUTTON)) {
    isPlaying = true;
  }
  else {
    isPlaying = true;
  }
  pinMode(PIN_PLAYBUTTON, OUTPUT);

  if(isPlaying) {
(etc)
So setting the pinMode() might be interfering with the SPI functions?

I also tried this, but the same thing happened (no playback):

Code: Select all

ISR(TIMER0_COMPA_vect) {

  pinMode(PIN_PLAYBUTTON, INPUT_PULLUP);
  isPlaying = true;
  pinMode(PIN_PLAYBUTTON, OUTPUT);

  if(isPlaying) {
If I run this code (with no button functions included at all):

Code: Select all

ISR(TIMER0_COMPA_vect) {

  isPlaying = true;
  if(isPlaying) {
then obviously it starts playing, UNTIL I connect a button between GND and PIN #3. If I press the button at the start of the loop, there's no playback until the next loop.

I'm not sure what else I can try.

User avatar
adafruit_support_rick
 
Posts: 35092
Joined: Tue Mar 15, 2011 11:42 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by adafruit_support_rick »

Interesting.
Try the button on a different pin. Pin 0 is already an input, so you won't have to change modes.
If it still doesn't work, then do the pinMode thing; change it to INPUT_PULLUP, check the button, then change it back to plain INPUT.

User avatar
bbcentral
 
Posts: 20
Joined: Fri Jan 17, 2014 11:40 am

Re: Playing Audio from 1MB SPI Flash on Arduino Uno

Post by bbcentral »

adafruit_support_rick wrote:Interesting.
Try the button on a different pin. Pin 0 is already an input, so you won't have to change modes.
If it still doesn't work, then do the pinMode thing; change it to INPUT_PULLUP, check the button, then change it back to plain INPUT.
When I do this, pressing the button turns the onboard Trinket LED (on PIN #1) on and off, but doesn't affect the playback (I had to press the button several times to get it to register the press, because of the lack of debounce code).

One thing I tried was disconnecting the button entirely, and just toggling pinMode in the ISR() function.
It played back the audio at about 1/4 speed, I think the pinMode function is too slow to be using here (the same reason why I switched from "digitalRead()" to "bit_is_set()").

Code: Select all

ISR(TIMER0_COMPA_vect) {

  // Note: pinMode() slows down the playback
  pinMode(0, INPUT_PULLUP);
  isPlaying = true;
  pinMode(0, INPUT);
In any case, it's still not able to recognise the button input (if it was playing at 1/4 speed in response to the button then this would be a different problem).

Locked
Please be positive and constructive with your questions and comments.

Return to “Arduino”