0

So I set up an Arduino circuit that while a button was pressed, a sound effect would play and an LED would activate until I stopped pressing the button. Since I didn't use a micro sd card module, I had to use wav2c to convert the audio to numeric values that I then used the code here to implement the audio. The numeric values for the audio were placed in a header file that was referenced in the code. The code and my circuit work but I want to see if I can use one of my new ATTiny85's to run the code and perform the same operations as the Arduino did. I have set up my Arduino Mega 2560 to program the ATTiny85. I have tested it with Blink and it works. However, after I had moved the pins of the speaker, LED, and button to those of the ATTin85 and edited the appropriate code, it will not upload to the ATTiny85. It gives error messages that certain variables have not been declared in this scope. One such is "TCCR1B" and when I comment it out, it claims another variable has not been declared in this scope. Any idea how I can overcome this and if it's even possible to use the ATTiny in this capacity?

The Code I tried uploading to the ATTiny85

#include <stdint.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

#define SAMPLE_RATE 20000
#include "Test.h"

int ledPin = 0;
int speakerPin = 1; // Can be either 3 or 11, two PWM outputs connected to Timer 2
const byte pinSwitch1 = 2;
volatile uint16_t sample;
byte lastSample;
int buttonState = 0;
const byte interruptPin = 2;
volatile byte state = LOW;

void stopPlayback()
{
    digitalWrite(ledPin, LOW);
    // 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 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect) {
    if (sample >= sounddata_length) {
        if (sample == sounddata_length + lastSample) {
            stopPlayback();
        }
        else {
            if(speakerPin==11){
                // Ramp down to zero to reduce the click at the end of playback.
                OCR2A = sounddata_length + lastSample - sample;
            } else {
                OCR2B = sounddata_length + lastSample - sample;                
            }
        }
    }
    else {
        if(speakerPin==11){
            OCR2A = pgm_read_byte(&sounddata_data[sample]);
        } else {
            OCR2B = pgm_read_byte(&sounddata_data[sample]);            
        }
    }

    ++sample;
}

void startPlayback()
{
    digitalWrite(ledPin, HIGH);
    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]);
    } else {
        // Do non-inverting PWM on pin OC2B (p.155)
        // On the Arduino this is pin 3.
        TCCR2A = (TCCR2A | _BV(COM2B1)) & ~_BV(COM2B0);
        TCCR2A &= ~(_BV(COM2A1) | _BV(COM2A0));
        // No prescaler (p.158)
        TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);

        // Set initial pulse width to the first sample.
        OCR2B = 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);

    lastSample = pgm_read_byte(&sounddata_data[sounddata_length-1]);
    sample = 0;
    sei();
        //digitalWrite(ledPin, HIGH);
        //delay(1000);
        //digitalWrite(ledPin, LOW);
}


void setup()
{
    pinMode( pinSwitch1, INPUT );
    pinMode(ledPin, OUTPUT);
    pinMode(interruptPin, INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(interruptPin), stopPlayback, CHANGE);

}

void loop()
{
//startPlayback();
//delay(5000);
//stopPlayback();
buttonState = digitalRead(pinSwitch1);


stopPlayback();

if (buttonState == LOW) {
    startPlayback();
    delay(1150);

}

}

The header file with the audio

#ifndef _HEADERFILE_H    // Put these two lines at the top of your file.
#define _HEADERFILE_H    // (Use a suitable name, usually based on the file name.)


const int sounddata_length=32000;
//const int sounddata_sampleRate=20000;

const unsigned char sounddata_data[] PROGMEM = {
  15,1,49,0,150,0,138,0,219,255,133,0,176,0,15,1,210,

//There are a lot more numbers in here to generate the audio but I cut most out to save space

};

#endif // _HEADERFILE_H    // Put this line at the end of your file.
  • 3
    The ATTINY85 doesn't have the same set of timers as the board this was written for. That looks like code for an UNO (ATMEGA328) or a Mega (ATMEGA2560). The ATTINY85 doesn't have a corresponding timer to the 16 bit Timer1 on those chips. You really can't use this on an 85. – Delta_G Jul 4 at 6:09
1

As @delta_G pointed out, this code is written specifically to access the timer hardware on the chip, and the ATTINY85 has different timer hardware.

It would not be hard to change this code to work with your just need to output the next step in the list of audio values 8000 timer per second.

There are two ways you could do this...

  1. You could figure out the corresponding settings for the ATTINY85 timer hardware and use those instead. The timer registers are documented in the ATTINY85 datasheets and you can google lots of info on how to set those registers, but it is complicated.

  2. You can bit-bang the audio data out though a digital pin. Bing banging means you write code that manually sets the pin high or low at the appropriate times.

Since it does not look like you need to do anything else while the sound is playing, I think bit banging is the simpler approach here.

Do do bit banging, you get rid of the start and end playback functions and instal have a playSound() function that looks something like this...

void playSound() {
    pinMode(speakerPin, OUTPUT);  // This really belongs in setup()
    for(unsigned sample=0; sample<sounddata_length; sample++) {      // step though the audio samples
        sample_value=pgm_read_byte(&sounddata_data[sample])/2;    // grab next value from array in flash 

        // Note that we divide the value by 2. This is because our target rate is                                                                  
        // 8000 samples per second, which is 125us per sample. Each sample value is 0-255, so /2 gives us
        // a value 0-127. If we spend about 127us on each sample then we are in the right ballpark
        // for playback speed (ok, a bit slow). 


        digitalWrite( speakerPin , 1);     // poor man's single cycle bitbang PWM
        _delay_us( sample_value );
        digitalWrite( speakerPin , 0);
        _delay_us( 127-sample_value );

     }

    }
}

This is just pseudo code and I have not checked to see if this even compiles, but you should get the idea.

This is also a hack, so it does not take into account how long the digitalWrite() takes or the overhead of the loop. It also explicitly stretches the sample time from 125us to 127us for math simplicity and speed (dividing the 0-255 by 2 is very fast). All these will likely make the playback sound slow and low, but i'm not sure how much.

I also do not take into account that the playback can get interrupted which would cause audio glitches. You can fix this by putting cli()/sei() around the playSound() code.

Finally, each sample only gets a single PWM cycle so I don't expect audio to be CD quality, but maybe good enough. If you need more PWM cycles then there are ways to do this, but then probably better to use the hardware timer.

LMK if you can get this working (post a video!) or have any questions!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.