Pages

Friday, 9 May 2014

Practice - Light Based Alarm (Sunset Project)

Description

This time we are going to show you how we can interact with ambient light through sensors. As usually, we will propose you two main parts for this lab exercise: the first one shows how we implemented the WSN MOOC proposal, while the second part goes beyond that, so we will also show you how to activate a LED and a buzzer on the sensor side in order to prevent thefts. The stuff we are using this time is showed below [1]:
  • Breadboard (two better than one).
  • Jumper wire Arduino UNO (and USB-A-to-B cable).
  • 2 XBee.
  • 2 XBee explorer (and at least one USB-to-mini-USB cable).
  • Four LEDs (white, red, blue, green).
  • A buzzer.
  • A photoresistor (or Light Dependant Resistance).
  • 10Kohm in the dark, 1Kohm in bright light 20Kohm resistor .
Remember you can download the code in the code section.

Part 1

The idea is to implement a voltage divider with a fixed resistance and a variable one in order to take the value between them. This will allow us to detect the voltage variations, which will let knowing the state of the ambient light. When the photoresistor detects bright light, its resistance value decreases, so the output voltage decreases. The opposite behavior happens when it detects a dark environment. The following schema shows you the logical circuit that we are implementing:

Figure 1: Voltage divider
The value that we are going to find at the point of interest is:


Once we know how the photoresistor works and how we must take benefit of it, we can build the circuit that will turn on a LED depending on the ambient light received on the sensor’s side. The following picture shows the connections that have to be done for this part:

Figure 2: Sunset connections [2]
Before implementing the code we have to configure the Xbee modules by following these steps:

1. The processing part must implement coordinator tasks, so we set a PAN ID (BFB7 in our case):

Figure 3: Coordinator node

2. The sensor side is the router part, so we set the same PAN ID as for the coordinator and we enable the “JV Channel Verification” field:

Figure 4: Router Node 1

3. We also configure the I/O settings that allow us to convert the received values from the circuit into digital characters for transmission: ADC (Analog-Digital Conversion) option.

Figure 5: Router Node 2

4. The last step consists in changing the IO Sampling Rate into 255 ms (0xFF) in order to avoid some communication troubles:

Figure 6: Router Node 3

Circuit Photography

  

Processing Side Code (Part 1)


 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
int LED_NIGHT = 10;
int LED_SUNSET = 11;
int LED_DAY = 12;
int debugLED = 13;
int analogValue = 0;
// Upper boundaries
int DAYup = 750;
int SUNSETup = 950;

void setup() {
  pinMode(LED_DAY,OUTPUT);
  pinMode(LED_SUNSET,OUTPUT);
  pinMode(LED_NIGHT,OUTPUT);
  pinMode(debugLED,OUTPUT);
  Serial.begin(9600);
}

void loop() {
  digitalWrite(LED_NIGHT, LOW);
  digitalWrite(LED_SUNSET, LOW);
  digitalWrite(LED_DAY, LOW);
  // make sure everything we need is in the buffer
  if (Serial.available() >= 21) {
    // look for the start byte
    if (Serial.read() == 0x7E) {
      //blink debug LED to indicate when data is received
      digitalWrite(debugLED, HIGH);
      delay(10);
      digitalWrite(debugLED, LOW);
      // read the variables that we're not using out of the buffer
      for (int i = 0; i<18; i++) {
        byte discard = Serial.read();
      }
      int analogHigh = Serial.read();
      int analogLow = Serial.read();
      analogValue = analogLow + (analogHigh * 256);
      Serial.print("Analog value ");
      Serial.println(analogValue);
    }
  }

  // DAY TIME
  if (analogValue > 0 && analogValue <= DAYup) {
    digitalWrite(LED_DAY, HIGH);
    delay(10);
    digitalWrite(LED_DAY, LOW);
  }

  // SUNSET TIME
  if (analogValue > DAYup && analogValue <= SUNSETup) {
    digitalWrite(LED_SUNSET, HIGH);
    delay(10);
    digitalWrite(LED_SUNSET, LOW);
  }

  // NIGHT TIME
  if (analogValue > SUNSETup && analogValue <= 1023) {
    digitalWrite(LED_NIGHT, HIGH);
    delay(10);
    digitalWrite(LED_NIGHT, LOW);
  }
}

Part 2

To extend the practical exercise seen on this entry we have deployed a buzzer that makes sounds when the state of the sensor changes. It could be useful for detecting movement (detecting a shadow) and then displaying the alarm to frighten thieves.

So, the behavior of our circuit is going to be the same as shown before, but we are adding communication between the coordinator and the router, in order to transmit a command to activate the port D1 at the sensor side, that will make the buzzer sound.

For this purpose, we have to use the "setRemoteState" function, which sends information through the serial channel with the value (as input of the function) that enables or disables the specific port with we are dealing to.

In our case, we have implemented that the Programmer side checks every 10 seconds if the status of the photoreceptor has changed from day to night. If yes, we call the "setRemoteState" function to send "05". Otherwise, we send "04".

More information about XBee 802.15.4 digital input/output passing can be found on the following link: http://www.digi.com/support/kbase/kbaseresultdetl?id=2188

Circuit Assembly



Circuit Photographies   





Processing Side Code (Part 2)

  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
int LED_NIGHT = 10;
int LED_SUNSET = 11;
int LED_DAY = 12;
int debugLED = 13;
int analogValue = 0;
// Upper boundaries
int DAYup = 750;
int SUNSETup = 950;

int remoteIndicator = false; // keeps track of the desired remote on/off state
int lastRemoteIndicator = false; // record of prior remote state

unsigned long lastSent = 0; // records last time the remote was re-set to keep it in sync

void setup() {
  pinMode(LED_DAY,OUTPUT);
  pinMode(LED_SUNSET,OUTPUT);
  pinMode(LED_NIGHT,OUTPUT);
  pinMode(debugLED,OUTPUT);
  Serial.begin(9600);
}

void loop() {
  digitalWrite(LED_NIGHT, LOW);
  digitalWrite(LED_SUNSET, LOW);
  digitalWrite(LED_DAY, LOW);
  // make sure everything we need is in the buffer
  if (Serial.available() >= 23) {
    // look for the start byte
    if (Serial.read() == 0x7E) {
      //blink debug LED to indicate when data is received
      digitalWrite(debugLED, HIGH);
      delay(10);
      digitalWrite(debugLED, LOW);
      // read the variables that we're not using out of the buffer
      for (int i = 0; i<20; i++) {
        byte discard = Serial.read();
      }
      int analogHigh = Serial.read();
      int analogLow = Serial.read();
      analogValue = analogLow + (analogHigh * 256);
      Serial.print("Analog value ");
      Serial.println(analogValue);
    }
  }

  // DAY TIME
  if (analogValue > 0 && analogValue <= DAYup) {
    digitalWrite(LED_DAY, HIGH);
    delay(10);
    digitalWrite(LED_DAY, LOW);
    remoteIndicator = false;
  }

  // SUNSET TIME
  if (analogValue > DAYup && analogValue <= SUNSETup) {
    digitalWrite(LED_SUNSET, HIGH);
    delay(10);
    digitalWrite(LED_SUNSET, LOW);
    remoteIndicator = false;
  }

  // NIGHT TIME
  if (analogValue > SUNSETup && analogValue <= 1023) {
    digitalWrite(LED_NIGHT, HIGH);
    delay(10);
    digitalWrite(LED_NIGHT, LOW);
    remoteIndicator = true;
  }

  // set the indicator immediately when there's a state change
  if (remoteIndicator != lastRemoteIndicator) {
    if (remoteIndicator==false) setRemoteState(0x4);
    if (remoteIndicator==true) setRemoteState(0x5);
    lastRemoteIndicator = remoteIndicator;
  }
  // re-set the indicator occasionally in case it's out of sync
  if (millis() - lastSent > 10000 ) {
    if (remoteIndicator==false) setRemoteState(0x4);
    if (remoteIndicator==true) setRemoteState(0x5);
    lastSent = millis();
  }

}


void setRemoteState(int value) {
  Serial.write(0x7e);//Start byte
  Serial.write((byte)0x0);//Length
  Serial.write(0x10);//Length High
  Serial.write(0x17);//AT Command Request
  Serial.write((byte)0x0);//Frame ID
  Serial.write((byte)0x0);//Serial Number of Destination
  Serial.write((byte)0x0);
  Serial.write((byte)0x0);
  Serial.write((byte)0x0);
  Serial.write((byte)0x0);
  Serial.write((byte)0x0);
  Serial.write(0xFF);
  Serial.write(0xFF);//End of Serial Number of Destinition

  // 16 bit of recipient or 0xFFFE if unknown
  Serial.write(0xFF);
  Serial.write(0xFE);
  Serial.write(0x02);//Apply changes immediately

  //Command name in ASCII characters
  Serial.write('D');
  Serial.write('1');

  //command data in as many bytes after length bytes
  Serial.write(value);

  //checksum is all bytes after length bytes
  long sum = 0x17 + 0xFF+ 0xFF + 0xFF + 0xFE + 0x02 + 'D' + '1' + value;
  Serial.write (0xFF - ( sum & 0xFF) );
  delay(10); // avoiding overwhelming
} 


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
26
27
28
29
30
31
32
33
// Pin where the buzzer is connected
const int BELL = 5;
const int PIN_STATE = 2;
// Light detector state
int DETstate = 0;
// Tone variables
float sinVal;
int toneVal;
void setup()
{
  pinMode(BELL, OUTPUT);
  Serial.begin(9600); 
}

void loop()
{
  DETstate = digitalRead(PIN_STATE);
  if (DETstate == HIGH){
    for (int x=0; x<180; x++) {
      // convert degrees to radians then obtain sin value
      sinVal = (sin(x*(3.1412/180)));
      // generate a frequency from the sin value
      toneVal = 2000+(int(sinVal*1000));
      tone(BELL, toneVal);
       
    }
    delay(10);
    noTone(BELL);
    //analogWrite(BELL, 0); 
  }


}


Video


References

[1] Material list retrieved from “WSN course guide”, chapter 11 (Sunset Sensor), made by Jaume Barceló and Luis Sanabria 

[2] Image retrieved from “WSN course guide”, chapter 11 (Sunset Sensor), made by Jaume Barceló and Luis Sanabria 

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.

Thursday, 10 April 2014

Practice - Chatting with XBee

Description

In this lab we have experimented with the XBee devices and how they can complement with Arduino. Firstly, we set a chat between two computers using the XBee's without Arduino; secondly, as an extra activity, we decided to establish a chat such as the messages sent from one computer through an XBee, were received by a second XBee connected to the Arduino. Moreover, these messages were shown through a LCD screen.

We used the following components:
  • 2 Digi XBee.
  • 2 Xbee Explorer.
  • 2 Type-A USB to mini-USB cables.
  • 2 Computers.
  • 1 Arduino Uno.
  • 1 (or 2) Breadboard.
  • 1 Display.
  • Several wires to interconnect the breadboard and Arduino.
The needed sofware to complete the task is the following:

Part 1: Simple Chat

The first step consists in achieving communication between two machines connected to the following set:


Figure 1: Xbee Module

We used the software XCTU to configure the devices and to set the PAN ID. We can also implement some stuff like changing device’s name, applying security, changing transmission power, etc.

After connecting the XBee modules to the computer, we must set a coordinator and a router as follows:

Figure 2: Coordinator setting

After setting one of the XBee module as a coordinator, we should see this on the XCTU screen:

Figure 3: Coordinator setting

We do the same for setting the other XBee module as router:

Figure 4: Router setting

We check that the change has been done successfully:

Figure 5: Router checking

Once the configuration has been done and the PAN ID is the same for both devices, we can run the “discover nodes in the same network” functionality:

Figure 6: Device discovered

The last step for chatting is switching to the consoles working mode and opening the serial connection with the radio module. The figure below shows a conversation maintained between two ZigBee modules connected to different computers.

Figure 7: Chatting
For more specific details, see the YouTube video attached at the end of the post.

Part 2: Chat display

In this second part we are going to show how we can display the information transmitted between two XBee in a screen connected to Arduino. To do that, we need to understand how the XBee Explorer works:

Figure 8: XBee Explorer
The previous figure shows the pins we must connect to the Arduino in order to be able to display the information sent by the other XBee module. We note that the input voltage must be 3.3V instead of 5V to avoid potential problems (however, we can use the 5V pin placed at the upper-right side). The XBee Explorer is not only important to connect the XBee to the computer, but also to put it in the breadboard so as connections can be set (later we will show the figure).

The connection between the XBee modules must be implemented as shown on part 1. In this case, however, we have set that one of the XBee's acts as a coordinator and the other one as an end-device (it could be used as a router too).

One of the main aspects we have to take into account when establishing the connection is that if we send broadcast messages, these messages will probably arrived much more delayed than in the case were we have specified the addresses. Moreover, it is very useful to observe that until RSSI leds in each XBee Explorer are not high, messages will not be sent (so be patient).

Once connectivity between the two XBee modules has been established, we can implement the part where we display the information in a screen. The image below also shows the connections done for our purpose and we can observe that we have the same type of pins as for ZigBee Explorer: Voltage input (5V), Ground, TX and RX.

Circuit schema

The image below shows the circuit. We must say that is not exactly the schema we followed because there are some pieces that are not included in the software (Fritzing): XBee Explorer (which is not the same as the one put in the image) and an I2C module we used for the LCD screen (see figure 15). At the link [5] you will find information on how to use I2C module.
Figure 9: Physical Schema

Circuit photographs

Figure 10: General image of the circuit

Figure 11: General image of the circuit

Figure 12: Zoom in the Arduino board

Figure 13: Zoom in the XBee (receiver) module part

Figure 14: Zoom in the LCD screen part

Figure 15: The I2C module

Video


Code

Now we can see two versions that correspond to the second part explained before. In both cases some characters written from the XBee connected from a computer are received in another XBee and shown through the display. However, there are some differences that are explained in the header comments of the code.

You can download the code from the code section.

Version 1

  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
/**
 * Chatting with Xbee - Version 1
 * In this version an Xbee connected to XCTU software sends a
 * message finished by an INTRO (like in a real chat). Because
 * of display length restrictions, just those characters that
 * fit in the screen are displayed.
 */

#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.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); SoftwareSerial xbee(0, 1); // Pin: RX, TX /** * Constants */ // Number of flashes before starting to show the characters
// received
const int N_FLASHES = 3;

// Number of rows in the display
const int N_ROWS = 2;

// Number of characters in each row
const int MAX_CHARS_PER_ROW = 16;

/**
 * Global variables
 */
// String that will be formed from the chars sent by the other xbee
String input = "";

void setup() 
{
  // Initialize the lcd for 16 chars 2 lines, turn on backlight
  lcd.begin(MAX_CHARS_PER_ROW, N_ROWS);

  // Quick 3 flashes of backlight 
  for (int i = 0; i < N_FLASHES; ++i)
  {
    lcd.backlight();
    delay(250);
    lcd.noBacklight();
    delay(250);
  }
  lcd.backlight(); // finish with backlight on  

  // set the data rate for the SoftwareSerial port
  xbee.begin(9600);
}

void loop()
{
  // If the xbee is receiving something...
  if (xbee.available())
  { 
    // Read the character received from the other xbee
    char c = xbee.read();
    
    // If INTRO key is pressed, print the string in the display
    //  Otherwise, keep reading it
    if (c == 13 || c == 10)
    {
      printStringInLCD();
      input = "";
    }
    else
    {
      input += c;
    }
  }
}

// Updates the LCD parameters (row, position in row) depending on
//  the current position
void printStringInLCD()
{
  // Size restriction of the display
  int length = min(input.length(), MAX_CHARS_PER_ROW * N_ROWS);
  
  int row = 0;
  int pos = 0;
  
  // Important to clear the display before writting new text
  lcd.clear();
  
  // Print char by char in order to take advantage from all 
  // the display. The string can also be divided in two parts
  // and print them separately
  for (int i = 0; i < length; ++i)
  {
    pos = i % MAX_CHARS_PER_ROW;
    
    if (i == MAX_CHARS_PER_ROW)
    {
      ++row; 
    }
    
    lcd.setCursor(pos, row);
    lcd.print(input.charAt(i));   
  }
}

Version 2

  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
/**
 * Chatting with Xbee - Version 2
 * In this version an Xbee connected to XCTU software sends an 
 * infinite message to the Xbee connected to the Arduino.
 * The message is displayed character by character while the 
 * message is written in XCTU. If the message is filled with 
 * characters, then the screen is cleared.
 */

#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.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);
SoftwareSerial xbee(0, 1); // Pin: RX, TX

/**
 * Constants
 */
// Number of flashes before starting to show the characters 
// received
const int N_FLASHES = 3;

// Number of rows in the display
const int N_ROWS = 2;

// Number of characters in each row
const int MAX_CHARS_PER_ROW = 16;

/**
 * Global variables
 */
int currentRow = 0;
int nextRow = 0;
int currentPos = 0;
int nextPos = 0;

void setup() 
{
  // Initialize the lcd for 16 chars 2 lines, turn on backlight
  lcd.begin(MAX_CHARS_PER_ROW, N_ROWS);

  // Quick 3 flashes of backlight 
  for (int i = 0; i < N_FLASHES; ++i)
  {
    lcd.backlight();
    delay(250);
    lcd.noBacklight();
    delay(250);
  }
  lcd.backlight(); // finish with backlight on  

  // set the data rate for the SoftwareSerial port
  xbee.begin(9600);
}

void loop()
{
  // If the xbee is receiving something...
  if (xbee.available())
  {
    updateLCDParams();
 
    // Set the cursor position in the display   
    lcd.setCursor(currentPos, currentRow);
    
    // Read the character received from the other xbee
    char c = xbee.read();
    
    // If INTRO key is pressed...
    if (c == 13 || c == 10)
    {
      nextRow = (currentRow+1) % N_ROWS;
      nextPos = 0;
    }
    else
    {
      // Print char throught the display
      lcd.print(c);
 
      // If we reach the end of the current row...
      if (currentPos == MAX_CHARS_PER_ROW-1)
      {
        nextRow = (currentRow+1) % N_ROWS;
        nextPos = 0;
      }
      else
      {
        nextRow = currentRow;
        nextPos = (currentPos+1) % MAX_CHARS_PER_ROW; 
      }
    }
  }
}

// Updates the LCD parameters (row, position in row) depending on
//  the current position
void updateLCDParams()
{
  currentRow = nextRow;
  currentPos = nextPos;
  
  if (currentRow == 0 && currentPos == 0)
  {
    lcd.clear();
  }
}

References