Help with PWM and Serial communication with XBEE

Hi.
I write to you because right now I'm working on an audio generating program by PWM (the same that is located on the playground).
Now, i'm interested in getting the data from a serial communication with XBEE, so I can have more than 13000 samples that the EEPROM can store.
The problem here is that when I get the characters by the Serial comm, the audio is broken.
If somebody has done something alike or knows how to fix this, please help me.
Thanks in advice
This is the code:

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

int ledPin = 13;
int speakerPin = 11;
volatile uint16_t sample;
byte incomingByte;

// This is called at 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect) {
        OCR2A = Serial.read();
}

void startPlayback()
{
    pinMode(speakerPin, OUTPUT);
    Serial.println("---Iniciando playback---");
    // 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);

    // 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 = Serial.read();

    // 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);

    sample = 0;
    sei();
}

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);
}

void setup()
{
    pinMode(ledPin, OUTPUT);
    //startPlayback();
    //Iniciar el serial a 115200 bps
    Serial.begin(115200);
    pinMode(speakerPin, OUTPUT);
}

void loop()
{
      //Checar si estan llegando datos
      if(Serial.available()>0){
        startPlayback();
        delay(3000);
      }
}

Where is the serial data coming from? Are you sure that it's sending data fast enough?

This code has nothing to do with EEPROM, which can't store 13000 values, anyway.

Doing a serial read in an interrupt is not a good idea. Before the read is complete, you're going to be interrupted to read again.

Even if the read is completed quickly enough, you don't check that you got good data before using it.

The problem here is that when I get the characters by the Serial comm, the audio is broken.

What does "the audio is broken" mean?

How is the data being sent? Is it being sent as character data? If so, you need to convert it back to a number before using it.

Where is the serial data coming from? Are you sure that it's sending data fast enough?

It is coming from the XBEE, and it has a 115200 bps rate, so I think they're coming fast enough.

What does "the audio is broken" mean?

Audio broken means that you can hear only noise.
And the data yes, it's being sent as char, do you know how to convert a char array to a number? Because if I send the "123" number, the arduino gets the numbers separately, so it gets "1", "2","3".

Thanks in advice =)

The XBee is making up serial data?

If the sender is sending 123 as '1', '2', '3', setting OCR1A and OCR2A to '1' and '2' respectively will not even come close to doing anything useful.

A ok, I got it.

The XBee is making up serial data?

Yes, I have one XBEE sending the serial data to another XBEE connected to the arduino.
Like this:

COMPUTER---XBEE---XBEE---ARDUINO

Its just I'm a newbie on all this of arduino and xbee. I've never worked with AVR microcontrollers (only Microchip), and I'm having my doubts regarding to PWM management.

I'll try to make the sender send the whole number, so arduino gets it right.
Another doubt, in the Playground PWM audio generation example, do you know if the numbers that are generated in the .h file are taken as integers, bytes or chars by the arduino, so it can generate the audio?

Thank you very much =)) ;D :slight_smile:

Hello.
I have partially solved my problem about receiving the data and sending it to the PWM outputs.
Now, the problem is that the sound is getting too slow.
The code is the following one:

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

#define SAMPLE_RATE 8000

int a = 0;
int ledPin = 13;
int speakerPin = 11;
char inData[3];
char numero[3]; // Allocate some space for the string
char inChar; // Where to store the character read
byte index = 0; // Index into array; where to store the character
long arraySize = 999999;
byte num;


// This is called at 8000 Hz to load the next sample.
ISR(TIMER1_COMPA_vect) {  
  while(Serial.available() > 0) // Don't read unless you know there is data
   {
     digitalWrite(ledPin,HIGH);
       if(index < arraySize) // One less than the size of the array
       { 
            inChar = Serial.read(); // Read a character
            //Receive chars until a comma is get
              if (inChar!=','){
                    inData[index] = inChar; // Store it
                index++; // Increment where to write next
                inData[index] = '\0'; // Null terminate the string
              }
              //If the char is a comma, send the char array to another array
              if(inChar==','){
                for(int i = 0; i<3;i++){
                  numero[i]='\0';//Erase every char of "numero" to write again on it
                  if(inData[i]!='\0')
                    numero[i] = inData[i];
                }                   
                //Transform it to an integer
                char * thisChar = numero;
                a = atoi(thisChar);
               //Transform it to a byte
                num = a;
                index = 0; // Set index on position 0
              }
       }
    OCR2A = num;//Send the byte by OCR2A
   }
}

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);

    // 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 = num;

    // 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 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);
}

void setup()
{
    pinMode(ledPin, OUTPUT);
    //startPlayback();
    //Iniciar el serial a 115200 bps
    Serial.begin(115200);
    pinMode(speakerPin, OUTPUT);
    startPlayback();
}

void loop()
{
  while (true){
  }
}

What should I do about this?
Thanks in advice =)

An ISR is supposed to be very quick. Reading serial data in an ISR is not quick. I suspect that you are missing a lot of interrupts because they are happening while your ISR is running. During an ISR, interrupts are disabled, by default. You are not re-enabling them.

I've tried everything to do this work, but it seems that with serial data reading it won't be able to work at a such high speed as I want it.
Is it better for me to buy a DAC and change the project? or what else should I do? Please help me, I'm still a newbie here... thanks

I have found the problem.
As you said, it was interference. If I shut off all the cellphones around and disconnect the WiFi, there's no distortion on the sound.
How can I change the XBEE channel or frequency?
Thanks :slight_smile:

Using the X-CTU program, you can change the CH (channel) value. The radios operate on a fixed frequency.

O, ok, let me se what I can get.
Thanks :slight_smile:
If I solve this, I'll tell you
Any other ideas are very welcomed