Pages

Thursday 1 May 2014

Practice - Melody Selector Using XBee

Description

On this section we are going to configure a system in which a buzz is going to be activated by a remote button. For achieve this purpose we are going to establish communication between two Xbee modules connected to different Arduino.

We are also showing how we implemented a system in which a melody is displayed in one side according to the number of times a button is pressed on the other side.

Our invented project (explained in more detail in Part 2) consists in a melody player using two Arduinos and two XBee's (along with XBee Explorer to connect to the breadboard). In one of the nodes we will select the melody to be played and in the second one the order will be received and the melody will be played; moreover some information will be return from the player to the order node.

The items we are going to need for this build are [1]:
  • Hookup wires.
  • Two Arduino boards.
  • USB A-to-B cable for the Arduinos.
  • Two 10K resistors.
  • Two momentary switch or push button for input.
  • One buzzer for output.
  • One XBee radio configured as ZigBee Coordinator AT.
  • One XBee radio configured as ZigBee Router AT.
  • Two breakout boards.
  • USB cable for the XBee breakout board.
  • Two LEDs (one green and one yellow).
  • 1 display.
The software used to complete the task is the same as in the previous practice exercise:
Remember you can download the code from the code section.

Part 1: Wireless Doorbell

First of all we must build the circuits shown on the WSN Course Guide (Section 8) in order to configure the Switch and the Buzz of the system, i.e., the button and the buzz. Here are the images of how the cuircuits must be done:

Figure 1: Button circuit building [2]

Figure 2: Buzz circuit building [3]


Besides the building of the circuit, we must configure the Xbee modules as we saw on the previous practice, obtaining a Coordinator AT and a Router AT.

On the figures shown below we can see pictures of both circuits. Note that we have added two LEDs in the button circuit: one is used for checking that current passes through the button and the other is used to check that buzz is buzzing.

Figure 3: Button Circuit

Figure 4: Buzzer Circuit

Now we show the code that we have used in order to implement the button and the buzz functionalities. This modified code is attached to the code section of this blog.

Button Code


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int BUTTON = 2;
int ACK_led = 12;

void setup()
{
  pinMode(BUTTON, INPUT);
  // initialize the digital pin as an output.
  pinMode(ACK_led, OUTPUT);     
  Serial.begin(9600);
}

void loop(){
  // SEND
  // send a capital D over the serial port if the button is pressed
  if(digitalRead(BUTTON)==HIGH)
  {
    Serial.print("D");
    delay(300); // prevents overwhelming the serial port
  }
  
  // RECEIVE
  if (Serial.available()>0)
  {
    if (Serial.read() == 'A')
    {
      digitalWrite(ACK_led, HIGH);
      delay(500);
    } 
    digitalWrite(ACK_led, LOW);
  }
}

Buzzer Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Pin where the buzzer is connected
const int BELL = 5;

void setup()
{
  pinMode(BELL, OUTPUT);
  Serial.begin(9600); 
}

void loop()
{
  if (Serial.available() > 0)
  {
    if (Serial.read() == 'D')
    { 
      // Send 'A' to tell the other node that
      //  it is playing a beep in the buzzer
      Serial.print("A");
      
      analogWrite(BELL, 240);
      delay(10);
      analogWrite(BELL, 0); 
    }
  } 
}

Part 2: Melody Player

In this extra part, as explained before, we have two nodes:
  • One node has an array of melodies that it can play thanks to a buzzer. It receives the melody identifier to play through an XBee connected to the Arduino. Moreover, it sends back the title of the song (it includes an end identifier which in our case is '\n') being played through the XBee too (in case the song does not exist, an error message is sent). When the song has been totally played, an end character is sent.
  • One node forms sums one to a variable each time a button is pressed; once we have pushed as many times as we wanted, we push another button to send the number through the XBee to the first node described. Then, it receives the title of the song played or an error message if the song did not exist. This title (or error) will be displayed through an LCD screen along with the previously sent number until the character indicating the end of the song is received; in that case, a message that tells the songs has ended is displayed.
Some difficulties appeared during the elaboration of the lab. One of the main problems we faced was the fact that the character that tells the song has ended was never being displayed individually; by doing some experiments we observed that this character was only sent if some other characters were sent previously through the serial connection. Therefore, as we did not find a good solution, we modified the code of Brett Hagman such that each time the value of the pointer *p is different from '\0' we send a blank through the serial and when it is '\0' we send the desired character indicating the end of song. It is a quite dirty way to do it but any more way to do it was found.

What we also notice is that we tried to incorporate all songs Brett Hagman had on his code in the array of songs we introduced. However, some problems were experimented: songs were reproduced in kind of random way (even some songs seemed to have two id's). But after reducing the number of songs to 7 from 15, everything worked fine. Therefore, we think it was something related to memory issues, which is very important to take into account.

Another problem we faced was the following: from the buzzer we send a string with the song name. Therefore, we thought that in the button part we could read a string as well using readString() but it did not work well since there was a lot of delay (or even it was not printed). Consequently, we tried to read char by char and putting them into a string; as a result, the performance was much better this time.

Circuit schemas

Figure 5: Button circuit

Figure 6: Buzzer circuit

Images

Figure 7: Global setup

Figure 8: Button part setup

Figure 9: Buzzer part setup

Video



Button Part Code

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
 * Button Part
 *  While a song is not being played, the user can
 *  introduce a song number using a button and sending
 *  it to the buzzer part. Once it receives the song
 *  name from the buzzer part, it prints it in the lcd
 *  screen along with its songId until it has been
 *  completely playedd.
 */

#include <LiquidCrystal_I2C.h>
#include <Wire.h> 

// Set the LCD address to 0x27 for a 20 chars 4 line display
// Set the pins on the I2C chip used for LCD connections:
//                    addr, en,rw,rs,d4,d5,d6,d7,bl,blpol
// Set the LCD I2C address
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

/**
 * Constants
 */
// Button that increases the songIndex
const int BUTTON = 2;

// Button that sends the song to the other XBee
const int ENTER = 3;

// Char used to indicate the end of the song
const char END_CHAR = 'E';

// String used to indicate the song id is not in the allowed range
const String SONG_NOT_FOUND = "Song not found";

// Char used to indicate that the song title has been completely sent
const char END_SONG_TITLE_CHAR = '\n';

/**
 * Global variables
 */
int songIndex = 0;
String songName = "";
boolean isPlayingSong = false;

/**
 * Helper functions
 */
// Prints information of a song in LCD screen
//  First line: songIndex - Second line: songName
void printCurrentInformation()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(songIndex);
  lcd.setCursor(0, 1);
  lcd.print(songName); 
}

/**
 * Main functions
 */
void setup()
{
  pinMode(BUTTON, INPUT);
  pinMode(ENTER, INPUT);

  lcd.begin(16,2);
  lcd.backlight(); 
  printCurrentInformation();

  Serial.begin(9600);
}

void loop()
{
  // If a song is being played, not allow to
  // modify or send songIndex
  if (!isPlayingSong)
  {
    // Increments the songIndex very time is pressed
    if (digitalRead(BUTTON) == HIGH)
    {
      ++songIndex;
      printCurrentInformation();
      delay(300);
    }
  
    // Sends the current selected song
    if (digitalRead(ENTER) == HIGH)
    {
      Serial.print(songIndex);
      delay(300);
    }
  }

  // If we are receiving sth through the serial connection...
  if (Serial.available() > 0)
  {
    // It would be easier if we did 'readString()' but
    // some delay problems appeared. It is much faster
    // it we receive the song name char by char
    char c = Serial.read();
    
    if (isPlayingSong)
    {
      // Reset the lcd screen showing that the song has ended
      if (c == END_CHAR)
      {
        songIndex = 0; 
        songName = "End of the song";
        isPlayingSong = false;
        printCurrentInformation();
        songName = ""; // restore song name
      }
    }
    else
    {
      // Is the symbol that tells that the songName has been
      // completely introduced
      if (c == END_SONG_TITLE_CHAR)
      {
        // If the songIndex did not exist, 
        if (songName == SONG_NOT_FOUND)
        {
          songIndex = 0;
          printCurrentInformation();
          songName = "";
        }
        else
        {
          isPlayingSong = true;
          printCurrentInformation();
        }
      }
      else
      {
        // Keep forming the name of the song
        songName += c;  
      }
    }
  }
}

Buzzer Part Code


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/**
 * Buzzer Part
 *  Plays the specified melody by the sender and
 *  sends a text to the other node specyfing which
 *  song is being played.
 *
 * Based on Brett Hagman code (www.roguerobotics.com)
 *   http://code.google.com/p/rogue-code/wiki/ToneLibraryDocumentation
 */

/**
 * Macros defining the notes features (from Brett Hagman code)
 */
#define OCTAVE_OFFSET 0
#define TONE_PIN 5

#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951

/**
 * Constants
 */
// Notes array (from Brett Hagman code)
const int notes[] = { 0,
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7
};

// Number of songs that can be played
const int N_SONGS = 7;

// Char used to indicate the end of the song
const char END_CHAR = 'E';

// String used to indicate the song id is not in the allowed range
const String SONG_NOT_FOUND = "Song not found";

// Char used to indicate that the song title has been completely sent
const char END_SONG_TITLE_CHAR = '\n';

/**
 * Global variables
 */
// Array of songs to select them easily
char* songArray[N_SONGS];

/**
 * Helper functions
 */
// Tells if a char is representing a digit or not
boolean isDigit(char c)
{
  return (c >= '0' && c <= '9'); 
}

// Play the specified song (from Brett Hagman code)
void play_rtttl(char *p)
{
  // Absolutely no error checking in here

  byte default_dur = 4;
  byte default_oct = 6;
  int bpm = 63;
  int num;
  long wholenote;
  long duration;
  byte note;
  byte scale;

  // format: d=N,o=N,b=NNN:
  // find the start (skip name, etc)

  while(*p != ':') p++;    // ignore name
  p++;                     // skip ':'

  // get default duration
  if(*p == 'd')
  {
    p++; p++;              // skip "d="
    num = 0;
    while(isDigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }
    if(num > 0) default_dur = num;
    p++;                   // skip comma
  }

  //Serial.print("ddur: "); Serial.println(default_dur, 10);

  // get default octave
  if(*p == 'o')
  {
    p++; p++;              // skip "o="
    num = *p++ - '0';
    if(num >= 3 && num <=7) default_oct = num;
    p++;                   // skip comma
  }

  //Serial.print("doct: "); Serial.println(default_oct, 10);

  // get BPM
  if(*p == 'b')
  {
    p++; p++;              // skip "b="
    num = 0;
    while(isDigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }
    bpm = num;
    p++;                   // skip colon
  }

  //Serial.print("bpm: "); Serial.println(bpm, 10);

  // BPM usually expresses the number of quarter notes per minute
  wholenote = (60 * 1000L / bpm) * 4;  // this is the time for whole note (in milliseconds)

  //Serial.print("wn: "); Serial.println(wholenote, 10);

  // now begin note loop
  while(*p)
  {
    // first, get note duration, if available
    num = 0;
    while(isDigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }
    
    if(num) duration = wholenote / num;
    else duration = wholenote / default_dur;  // we will need to check if we are a dotted note after

    // now get the note
    note = 0;

    switch(*p)
    {
      case 'c':
        note = 1;
        break;
      case 'd':
        note = 3;
        break;
      case 'e':
        note = 5;
        break;
      case 'f':
        note = 6;
        break;
      case 'g':
        note = 8;
        break;
      case 'a':
        note = 10;
        break;
      case 'b':
        note = 12;
        break;
      case 'p':
      default:
        note = 0;
    }
    p++;

    // now, get optional '#' sharp
    if(*p == '#')
    {
      note++;
      p++;
    }

    // now, get optional '.' dotted note
    if(*p == '.')
    {
      duration += duration/2;
      p++;
    }
  
    // now, get scale
    if(isDigit(*p))
    {
      scale = *p - '0';
      p++;
    }
    else
    {
      scale = default_oct;
    }

    scale += OCTAVE_OFFSET;

    if (*p == ',')
      p++;       // skip comma for next note (or we may be at the end)

    // From line 248 to 256
 // Added to code so end of the song signal is transmitted
    //  It is a very dirty way to send the END_CHAR but it was
    //  impossible to make it work by sending just the END_CHAR
    char songPlaying = *p;
    if (songPlaying)
    {
      Serial.print(" ");
    }
    else
    {
      Serial.print(END_CHAR);
    }

    // now play the note
    if(note)
    {
      //Serial.print("Playing: ");
      //Serial.print(scale, 10); Serial.print(' ');
      //Serial.print(note, 10); Serial.print(" (");
      //Serial.print(notes[(scale - 4) * 12 + note], 10);
      //Serial.print(") ");
      //Serial.println(duration, 10);
      tone(TONE_PIN, notes[(scale - 4) * 12 + note]);
      delay(duration);
      noTone(TONE_PIN);
    }
    else
    {
      //Serial.print("Pausing: ");
      //Serial.println(duration, 10);
      delay(duration);
    }
  }
}

// Returns the name of a song given an input of the type
//  "Name Song : Notes Features"
String getSongName(char* p)
{
  String songName = "";
  
  while (*p != ':')
  {
    songName += *p;
    ++p;
  }
  
  return songName;
}

/**
 * Main functions
 */
void setup(void)
{
  Serial.begin(9600);
  
  // Put all desired songs in the array of songs
  songArray[0] = "The Simpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c6,8c6,c6";
  songArray[1] = "Indiana:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6";
  songArray[2] = "Entertainer:d=4,o=5,b=140:8d,8d#,8e,c6,8e,c6,8e,2c.6,8c6,8d6,8d#6,8e6,8c6,8d6,e6,8b,d6,2c6,p,8d,8d#,8e,c6,8e,c6,8e,2c.6,8p,8a,8g,8f#,8a,8c6,e6,8d6,8c6,8a,2d6";
  songArray[3] = "Looney:d=4,o=5,b=140:32p,c6,8f6,8e6,8d6,8c6,a.,8c6,8f6,8e6,8d6,8d#6,e.6,8e6,8e6,8c6,8d6,8c6,8e6,8c6,8d6,8a,8c6,8g,8a#,8a,8f";
  songArray[4] = "StarWars:d=4,o=5,b=45:32p,32f#,32f#,32f#,8b.,8f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32e6,8c#.6,32f#,32f#,32f#,8b.,8f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32c#6,8b.6,16f#.6,32e6,32d#6,32e6,8c#6";
  songArray[5] = "GoodBad:d=4,o=5,b=56:32p,32a#,32d#6,32a#,32d#6,8a#.,16f#.,16g#.,d#,32a#,32d#6,32a#,32d#6,8a#.,16f#.,16g#.,c#6,32a#,32d#6,32a#,32d#6,8a#.,16f#.,32f.,32d#.,c#,32a#,32d#6,32a#,32d#6,8a#.,16g#.,d#";
  songArray[6] = "MissionImp:d=16,o=6,b=95:32d,32d#,32d,32d#,32d,32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,a#,g,2d,32p,a#,g,2c#,32p,a#,g,2c,a#5,8c,2p,32p,a#5,g5,2f#,32p,a#5,g5,2f,32p,a#5,g5,2e,d#,8d";
}

void loop(void)
{
  if (Serial.available() > 0)
  {
    // Read the songNumber sent by the other node
    int songNumber = Serial.parseInt();
        
    // Check that the songNumber is inside the range of allowed values
    if (songNumber >= 0 && songNumber < N_SONGS)
    {
        // Print song title
        Serial.print(getSongName(songArray[songNumber]) + END_SONG_TITLE_CHAR);
        delay(300);        
        
        // Play the song
        play_rtttl(songArray[songNumber]);
    }
    else
    {
      // Send error message to the other node
      Serial.print(SONG_NOT_FOUND + END_SONG_TITLE_CHAR);
      delay(300);
    }
  }
}

References

[1] Elements list for the system build. Retrieved from WSN course guide on 25/04/2014.
[2] Image "8.1 Wireless doorbell: switch layout". Retrieved from WSN Course Guide on 25/04/2014.
[3] Image "8.2 Wireless doorbell: buzzer layout". Retrieved from WSN Course Guide on 25/04/2014.
[4] Arduino Forums - Need help with a melody (link). Retrieved on 25/04/2014.
[5] Arduino Star Wars Song for Piezo (link). Retrieved on 25/04/2014.

1 comment: