18 Jan 2020
I got a robot kit for Christmas (thanks to you know who) and a few of the things it came with were an Arduino, batteries, and an ultrasonic. I never really used it until I did this project where I wanted a plant to speak to you if you walked past it.
So like I said, the robot kit I got had batteries and a nice little battery connector; otherwise, I would have had to tape my own battery pack together.
Each battery was about 3.7V each, for a total of 7.4V of power to the Arduino. For voltages between 7 to 12 volts, you're supposed to use the Vin pin on the Power side of the Arduino. Using male to male cables (the ones with two pointy ends), connect the red cable to Vin and black cable to ground (GND).
Yay, Arduino is on!
An ultrasonic is a little device that can do motion detection using sonar. Basically how it works is:
The wires ended up being short so I used more male to male cables to extend the connection.
I also used the Arduino docs and this Youtube video
to learn how to set this up.
Side note: The pin numbers I chose for the ultrasonic are
trivial, you can use any (digital) pin without consquence.
LESSON LEARNED 1: Don't connect a speaker straight to an Arduino. I'll mention why later on in this post. For documentation's sake, I am continuing, but that's just my word to the wise.
Although I'm not actually doing this at this point, I will write down the pin connections:
Now for some code! So once I plugged in my Arduino to the computer, we'll have to write a little bit of code to make things work.
Ultrasonic outputs time between transmission and recieving (in μs)
All Arduino code have two main sections: the setup()
function
and the loop()
function. setup() will run once and loop()
is what the Arduino will keep doing when you upload code to it.
So setting up the code looks like this:
const int trig = 8;
const int echo = 7;
const int speakerpin = 11;
// put your setup code here, to run once:
void setup() {
Serial.begin(9600); #opens up serial port
pinMode(trig, OUTPUT);
pinMode(echo, INPUT);
}
// put your main code here, to run repeatedly:
void loop() {
//write voltage to selcted pin
digitalWrite(trig, LOW);
delayMicroseconds(5);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);
int time = pulseIn(echo, HIGH);
Serial.println(time);
delay(1000)
}
Here we set the pin numbers from the connections on the Arduino as variables
in the code.Serial.begin()
is always
in the setup function, and usually set to 9600bps. The pinMode()
function configures Trig as output and Echo as input.
In loop()
, the digitalWrite function
will send a voltage to the Trig pin with digitalWrite(trig, HIGH);
This is transmitting out the sound frequency. It's recommended to write LOW (0V) to the Trig pin
before you send out HIGH. Finally, pulseIn(echo, HIGH)
is measuring the time for Echo to go from LOW to HIGH. This will return time in microseconds.
To convert time into distance, I used this formula that I Googled somewhere:
//...
long secondsToInches(int time) {
long distance = time / 83.3;
distance = distance / 2;
Serial.print("The inches is ");
Serial.println(distance);
return distance;
}
//...other code from above...
void loop() {
//...
int time = pulseIn(echo, HIGH);
long distance = secondsToInches(time)
Serial.println(time)
Serial.println(distance)
}
Now we can print distance!
In the project, I wanted when someone got in range of the plant (let's say 36 inches away) the speaker would speak. In theory, that means I should do this:
if (distance <= 36 ) {
//make the plant talk
}
Ultrasonic outputs time between transmission and recieving (in μs)
Unfortunately, the ultrasonic produces noisy code, and since the distance is being measured like every 30 microseconds or so, I don't want the speaker to play after one bad measurement.
This meant I needed to take a sliding average of distances. When the average was 36 inches, then I would sound the speaker. Basically, we'll just set up a queue and keep popping the least recent value, inserting the most recent value, and then taking the average of everything inside the queue to see if it is less than the desired distance.
This is generally what that would look like:
void setup() {
//...
queue = [] //create empty queue
queue.capacity = 3 //only three values in queue at a time
}
void loop() {
//...
if(queue == full) {
pop(queue) //take out oldest value
}
push(queue) //put new value
if average(queue) <= 36 {
//make the plant talk
}
}
Arduino uses a C-like language, so this is what the sliding average code looked like:
#include <assert.h>
typedef struct Queue {
long *arr;
int index;
int capacity;
int len;
} queue;
int returnLen (queue* q) { return q->len; }
int returnCapacity (queue* q) { return q->capacity; }
void pop(queue* q) {
assert(q->len != 0);
for (int i = 0; i < q->index - 1; i++) {
q->arr[i] = q->arr[i+1];
}
q->index -= 1;
q->len -= 1;
}
void push(queue* q, long val) {
assert(q->len < q->capacity);
q->arr[q->index] = val;
q->index++;
q->len++;
}
long avg(queue* q) {
assert(q->len != 0);
int i;
int val;
for (i = 0; i < q->len; i++) { val += q->arr[i]; }
return val / q->len;
}
void insert(queue* q, long val) {
if (q->len == q->capacity) { pop(q); }
push(q, val);
}
queue Q; //make queue object called Q
void setup() {
//...
Q.len = 0;
Q.capacity = 3;
Q.arr = (long*)malloc(Q.capacity * sizeof(long));
Q.index = 0;
}
void loop() {
//...
insert(&Q, inches);
if ( returnCapacity(&Q) == returnLen(&Q) ) {
if ((avg(&Q)) <= 36) {
//make the plant speak
}
}
delay(5000);
}
LESSON LEARNED 2: If you want to use sound in a project, use a sound card. Don't just do everything straight from the Arduino.
Ok, now for the sound part. I'll first mention why it's not a good idea to rely on the Arduino to play sound:
Luckily, there is a PCM library that encodes MP3 data. However, I found out that for my Mac, the library used an incompatible version of Java(?) so I ended up using a PC to use the library. It looked like this:
#include <PCM.h>
//it is a lot more numbers than this...
const unsigned char sample[] PROGMEM = {
130, 133, 135, 134, 131, 131, 132, 131, 130, 129, 129, 12... 6, 133, 133, 132, 125, 126, 129, 128, 127, 126,
};
//...
void loop() {
//...
if ( returnCapacity(&Q) == returnLen(&Q) ) {
if ((avg(&Q)) <= 36) {
startPlayback(sample, sizeof(sample)); //plays the encoded sound
}
}
}
Testing out everything together, looked like this:
It speaks!
The speaker was very tiny. That and a lack of an amplfier meant you could barely hear anything as you walked past. I quickly learned Arduinos are not made to play sound.
The ultrasonic was also not mounted firmly onto anything, so it was subject to moving around, and generating more noise for the sliding average.
In the end, the plant could only say, "seeking human kindness", due to the time constraints and lack of better equipment. This was my first project related to robotics, so I learned a lot in the process of making this. But I also learned a couple of things after:
Read up. I, not knowing what all these gadgets really do, bought an AdaFruit Sound Board thinking that it would be a amp. Quickly realized it wasn't a amp and never used it. By the end of the project, I found out I could have used it, along with an amplfier to provide louder sound and it would have worked as a sound card too.
Using amplifiers. Amplifiers make things loud. Use them.
Using resistors. Although no damage happened to any product, resistors should be used to connect the Arduino to the speaker, especially ones more powerful than a 3W, 4Ω one.
All in all, I had a lot of fun throughout the whole thing; I think that's it. :)