Sound with piezo: From IDE to AVR

Go To Last Post
9 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,

I'm very new to Microcontrollers programming, therefore I want to learn the basic and not always use the Arduino IDE. I want to generate sound with PWM.

I found this Script which works with Arduino IDE:

  int speakerPin = 3;
  int length = 70;
  String notes[] = {"G4","G4", "G4", "D#4/Eb4", "A#4/Bb4", "G4", "D#4/Eb4","A#4/Bb4", "G4", "D5", "D5", "D5", "D#5/Eb5", "A#4/Bb4", "F#4/Gb4", "D#4/Eb4","A#4/Bb4", "G4", "G5","G4","G4","G5","F#5/Gb5", "F5","E5","D#5/Eb5","E5", "rest", "G4", "rest","C#5/Db5","C5","B4","A#4/Bb4","A4","A#4/Bb4", "rest", "D#4/Eb4", "rest", "F#4/Gb4", "D#4/Eb4","A#4/Bb4", "G4" ,"D#4/Eb4","A#4/Bb4", "G4"};
  int beats[] = { 8, 8, 8, 6, 2, 8, 6 , 2 ,16 , 8, 8, 8, 6, 2, 8, 6, 2, 16,8,6,2,8,6,2,2, 2, 2,6,2,2,8,6,2,2,2,2,6,2,2,9,6,2,8,6,2,16  };
  int tempo = 50;

  void playTone(int tone, int duration) {
    for (long i = 0; i < duration * 1000L; i += tone * 2) {
      digitalWrite(speakerPin, HIGH);
      delayMicroseconds(tone);
      digitalWrite(speakerPin, LOW);
      delayMicroseconds(tone);
    }
  }

  void playNote(String note, int duration) {
    String noteNames[] = { "D#4/Eb4", "E4", "F4", "F#4/Gb4", "G4", "G#4/Ab4", "A4", "A#4/Bb4", "B4", "C5", "C#5/Db5", "D5", "D#5/Eb5", "E5", "F5", "F#5/Gb5", "G5", "G#5/Ab5", "A5", "A#5/Bb5", "B5", "C6", "C#6/Db6", "D6", "D#6/Eb6", "E6", "F6", "F#6/Gb6", "G6" };
    int tones[] = { 1607, 1516, 1431, 1351, 1275, 1203, 1136, 1072, 1012, 955, 901, 851, 803, 758, 715, 675, 637, 601, 568, 536, 506, 477, 450, 425, 401, 379, 357, 337, 318 };
    for (int i = 0; i < 29; i++) {
      if (noteNames[i] == note) {
        playTone(tones[i], duration);
      }
    }
  }

  void setup() {
    pinMode(speakerPin, OUTPUT);
  }

  void loop() {
    for (int i = 0; i < length; i++) {
      if (notes[i] == "rest") {
        delay(beats[i] * tempo);
      } else {
        playNote(notes[i], beats[i] * tempo);
      }
      delay(tempo / 2);
    }
  }

An transformed it to native c and avr libs. This is my result:

#include <avr/io.h>
#define F_CPU 14745600L
#include <util/delay.h>
#include <stdio.h>
#include <string.h>
#define NUMBER_OF_STRING 70
#define MAX_STRING_SIZE 10
int length = 70;
char notes[][MAX_STRING_SIZE] = {"G4","G4", "G4", "D#4/Eb4", "A#4/Bb4", "G4", "D#4/Eb4","A#4/Bb4", "G4",
				  "D5", "D5", "D5", "D#5/Eb5", "A#4/Bb4", "F#4/Gb4", "D#4/Eb4","A#4/Bb4",
				  "G4", "G5","G4","G4","G5","F#5/Gb5", "F5","E5","D#5/Eb5","E5", "rest",
				  "G4", "rest","C#5/Db5","C5","B4","A#4/Bb4","A4","A#4/Bb4", "rest",
				  "D#4/Eb4", "rest", "F#4/Gb4", "D#4/Eb4","A#4/Bb4", "G4" ,"D#4/Eb4","A#4/Bb4", "G4"};
int beats[] = { 8, 8, 8, 6, 2, 8, 6 , 2 ,16 , 8, 8, 8, 6, 2, 8, 6, 2, 16,8,6,2,8,6,2,2, 2, 2,6,2,2,8,6,2,2,2,2,6,2,2,9,6,2,8,6,2,16  };
int tempo = 40;

void playTone(int tone, int duration) {
    for (long i = 0; i < duration * 1000L; i += tone * 2) {
      PORTD |= (1<<PORTD3);
      _delay_us(tone);
      PORTD &= ~(1<<PORTD3);
      _delay_us(tone);
    }
};

void playNote(char note[MAX_STRING_SIZE], int duration) {
    char noteNames[][MAX_STRING_SIZE] = { "D#4/Eb4", "E4", "F4", "F#4/Gb4", "G4", "G#4/Ab4", "A4", "A#4/Bb4", "B4", "C5", "C#5/Db5", "D5", "D#5/Eb5", "E5", "F5", "F#5/Gb5", "G5", "G#5/Ab5", "A5", "A#5/Bb5", "B5", "C6", "C#6/Db6", "D6", "D#6/Eb6", "E6", "F6", "F#6/Gb6", "G6" };
    int tones[] = { 1607, 1516, 1431, 1351, 1275, 1203, 1136, 1072, 1012, 955, 901, 851, 803, 758, 715, 675, 637, 601, 568, 536, 506, 477, 450, 425, 401, 379, 357, 337, 318 };
    for (int i = 0; i < 29; i++) {
      if (strcmp(noteNames[i],note) == 1) {
            playTone(tones[i], duration);
      }
    }
};

void setup() {
    DDRD |= (1<<DDD3);
};

void loop() {
    for (int i = 0; i < length; i++) {
      if (strcmp(notes[i], "rest")== 1) {
            _delay_ms(beats[i] * tempo);
      } else {
            playNote(notes[i], beats[i] * tempo);
      }
      _delay_ms(tempo / 2);
    }
}

int main(){

// Pin ansteuern
// DDRD --> data direct register von Port DDRD, 8 bit --> 00011010 --> 0 ist input, 1 output
setup();

loop();
}
	

If I flash my microcontroller with that, the first three tones are correct but then it tangles up and doesn't play the right notes and uses the right tempo.

 

Can you help me what is the problem?

 

Thanks

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
int main(){

// Pin ansteuern
// DDRD --> data direct register von Port DDRD, 8 bit --> 00011010 --> 0 ist input, 1 output
setup();

loop();
}

Note that the Arduino version of this is more like:

int main(){

   // Pin ansteuern
   // DDRD --> data direct register von Port DDRD, 8 bit --> 00011010 --> 0 ist input, 1 output
   setup();

   while(1) {
      loop();
   }
}

That is loop() is called repeatedly, not just once. BTW don't do this:

void playTone(int tone, int duration) {
    for (long i = 0; i < duration * 1000L; i += tone * 2) {
      PORTD |= (1<<PORTD3);
      _delay_us(tone);
      PORTD &= ~(1<<PORTD3);
      _delay_us(tone);
    }
};

The user manual for _delay_ms/us() says this:

 

https://www.nongnu.org/avr-libc/user-manual/group__util__delay.html

 

Note

In order for these functions to work as intended, compiler optimizations must be enabled, and the delay time must be an expression that is a known constant at compile-time. If these requirements are not met, the resulting delay will be much longer (and basically unpredictable), and applications that otherwise do not use floating-point calculations will experience severe code bloat by the floating-point library routines linked into the application.

The way you are using (passing "tone" into the function then using _delay_us(tone)) means that the value used is NOT a value known at compile time. What you can do is create your own:

void delay_us(uint8_t n) {
    for(; n; n--) {
        _delay_us(1);
    }
}

In this case the call to _delay_us() does now meet the requirements because '1' is a value known at compile time. You now call:

delay_us(tone);

(in fact the very reas _delay_ms() and _delay_us() have an underscore at the start of the name is to denote that they "belong" ot the C library - this leaves the way clear for you to implement your own delay_ms() without underscore).

 

The only downside of all this (though the above is FAR more efficient that what will currently be happening!) is that the CALL/RET to actually get into and out of delay_us() will be a few cycles and also the for(n) loop has an overhead of a few cycles. When dealing in the realms of "ms" a few added cycles don't matter but when down at the "us" level then, depending on CPU speed, a few cycles may actually be a few additional microseconds. One way to mitigate this is:

__attribute__((always_inline)) static inline void delay_us(uint8_t n) {
    for(; n; n--) {
        _delay_us(1);
    }
}

if you do this then at the point where you write delay_us(tone) it will actually put the for() loop inline (so you may end up with multiple copies) so there'l be no time wasted in doing CALL and RET.

 

BTW your note encoding system and this whole strcmp()ing loop look like a hugely inefficient way to encode the tune. In his:

    for (int i = 0; i < 29; i++) {
      if (strcmp(noteNames[i],note) == 1) {

it will have done 29 strcmp()s when you happen to play "G6" ! Also if you are to do it like this then:

    char noteNames[][MAX_STRING_SIZE] = { "D#4/Eb4", "E4", "F4", "F#4/Gb4", "G4", "G#4/Ab4", "A4", "A#4/Bb4", "B4", "C5", "C#5/Db5", "D5", "D#5/Eb5", "E5", "F5", "F#5/Gb5", "G5", "G#5/Ab5", "A5", "A#5/Bb5", "B5", "C6", "C#6/Db6", "D6", "D#6/Eb6", "E6", "F6", "F#6/Gb6", "G6" };
    int tones[] = { 1607, 1516, 1431, 1351, 1275, 1203, 1136, 1072, 1012, 955, 901, 851, 803, 758, 715, 675, 637, 601, 568, 536, 506, 477, 450, 425, 401, 379, 357, 337, 318 };

these are two separate 29 element arrays. It is surely a much better idea to group them into an array of structs?

typedef struct {
    char * name;
    int tone;
} notes_t;

const __flash notes_t noteData[] = {
    { "D#4/Eb4", 1607 },
    { "E4", 1516 },
    { "F4", 1431 },
    etc.
};

then...

      if (strcmp(noteData[i].name, note) == 1) {
            playTone(noteData[i].tone, duration);
      }

YMMV.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey, 

thanks for the tips.

 

Right now i just changed the delay methods like:

__attribute__((always_inline)) static inline void delay_us(uint8_t n) {
    for(; n; n--) {
        _delay_us(1);
    }
} 
__attribute__((always_inline)) static inline void delay_ms(uint8_t n) {
    
    
    for(; n; n--) {
        _delay_ms(1);
    }
}

So I only calling these two functions for delay. But still it sounds like mess.

 

What is still wrong?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Did you put the while(1) around loop()?

 

But anyway, I'd add a facility to "step" through the tune so it maybe waits for some kind of input after each note so you can more accurately determine where things go wrong. If you have a debugger then obviously you could achieve the same with breakpoints.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, this is my code right now. The string comparison issue I will add later, I don't think that this is the problem.

 

#include <avr/io.h>
#define F_CPU 14745600L
#include <util/delay.h>
#include <stdio.h>
#include <string.h>
#define NUMBER_OF_STRING 70
#define MAX_STRING_SIZE 10
int length = 70;
char notes[][MAX_STRING_SIZE] = {"G4","G4", "G4", "D#4/Eb4", "A#4/Bb4", "G4", "D#4/Eb4","A#4/Bb4", "G4", 
				  "D5", "D5", "D5", "D#5/Eb5", "A#4/Bb4", "F#4/Gb4", "D#4/Eb4","A#4/Bb4", 
				  "G4", "G5","G4","G4","G5","F#5/Gb5", "F5","E5","D#5/Eb5","E5", "rest", 
				  "G4", "rest","C#5/Db5","C5","B4","A#4/Bb4","A4","A#4/Bb4", "rest", 
				  "D#4/Eb4", "rest", "F#4/Gb4", "D#4/Eb4","A#4/Bb4", "G4" ,"D#4/Eb4","A#4/Bb4", "G4"}; 
int beats[] = { 8, 8, 8, 6, 2, 8, 6 , 2 ,16 , 8, 8, 8, 6, 2, 8, 6, 2, 16,8,6,2,8,6,2,2, 2, 2,6,2,2,8,6,2,2,2,2,6,2,2,9,6,2,8,6,2,16  };
int tempo = 40;

 void delay_us(uint8_t n) {
    for(; n; n--) {
        _delay_us(1);
    }
}
 void delay_ms(uint8_t n) {
    for(; n; n--) {
        _delay_ms(1);
    }
}

void playTone(int tone, int duration) {
    for (long i = 0; i < duration * 1000L; i += tone * 2) {
      PORTD |= (1<<PORTD3);
      delay_us(tone);
      PORTD &= ~(1<<PORTD3);
      delay_us(tone);     
    }
};

void playNote(char note[MAX_STRING_SIZE], int duration) {
    char noteNames[][MAX_STRING_SIZE] = { "D#4/Eb4", "E4", "F4", "F#4/Gb4", "G4", "G#4/Ab4", "A4", "A#4/Bb4", "B4", "C5", "C#5/Db5", "D5", "D#5/Eb5", "E5", "F5", "F#5/Gb5", "G5", "G#5/Ab5", "A5", "A#5/Bb5", "B5", "C6", "C#6/Db6", "D6", "D#6/Eb6", "E6", "F6", "F#6/Gb6", "G6" };
    int tones[] = { 1607, 1516, 1431, 1351, 1275, 1203, 1136, 1072, 1012, 955, 901, 851, 803, 758, 715, 675, 637, 601, 568, 536, 506, 477, 450, 425, 401, 379, 357, 337, 318 };
    for (int i = 0; i < 29; i++) {
      if (strcmp(noteNames[i],note) == 1) {
            playTone(tones[i], duration);
      }
    }
};

void setup() {
    DDRD |= (1<<DDD3);
};

void loop() {
    for (int i = 0; i < length; i++) {
      if (strcmp(notes[i], "rest")== 1) {
            delay_ms(beats[i] * tempo);
      } else {
            playNote(notes[i], beats[i] * tempo);      
      }
      delay_ms(tempo / 2);
    } 
}

int main(){

// Pin ansteuern
// DDRD --> data direct register von Port DDRD, 8 bit --> 00011010 --> 0 ist input, 1 output
setup();
while(1) {
    
      loop();
   }
}
	

btw: If I do it that way, the melody plays much much faster than in my previous version. S.th. might still not be correct with the delays.

I don't have any other hardware to simulate an input and neither do I have a debugger :(

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Before I spent to much time on this solving the problem,

After using delays I want to use timers for conting and playing/stopping the notes.

 

I know I have to set some register in TCCR1A, TCCR1B,OCR1A, OCR1B. But how does this work in general?

Does anyone have an example code how to transform my code above using the timer functionality?

 

Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

michac1995 wrote:
Does anyone have an example code how to transform my code above using the timer functionality?

Take a look in the tutorial forum on using timers, also look at using soft timers where one h/w timer can do the work of many.

 

Jim

 

Click Link: Get Free Stock: Retire early! PM for strategy

share.robinhood.com/jamesc3274
get $5 free gold/silver https://www.onegold.com/join/713...

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Check your usage of strcmp.

You are doing

if (strcmp(s1, s2) == 1)

I am guessing you really want

if (strcmp(s1, s2) == 0)

 

The  strcmp() function compares the two strings s1 and s2.  It returns an integer less than, equal to, or greater than zero if s1 is found, respectively, to be less than, to match,  or be greater than s2.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You need to be careful with things like these:

 

 void delay_us(uint8_t n) {
    for(; n; n--) {
        _delay_us(1);
    }
}
 void delay_ms(uint8_t n) {
    for(; n; n--) {
        _delay_ms(1);
    }
}

 

The for loop has a certain overhead, that probably is negligible for the ms version, but not for the us version.

 

In fact, as you already suspect, the only way to obtain correct timings is to use a timer.