IMG_5605.jpg

Bells & Yells

Bells & Yells

Time Scope
October – November 2016
(4 weeks)

Teammate
Amy Ashida

My Role
Coding
Circuitry
Fabrication
Usability Testing

How might urban cyclists stay safe and calm in areas where pedestrians and drivers are unaware they are impeding bike lanes and paths?

Overview

Biking in a densely populated city like New York can feel like the most difficult level of a video game. Even when there are designated bike lanes, pedestrians often overflow from sidewalks, walking or standing in the bike lane, unaware of cyclists moving rapidly toward them. Bells are often too quiet or ubiquitous for people to hear and pay attention to, and yelling causes stress levels to rise and creates opportunities for conflict.

Our solution is Bells & Yells, a device that does the talking for us. Mounted onto the handlebars of a bicycle, it has two settings: friendly and aggressive. On the friendly setting, sleigh bells play as a pleasant way to alert those around that the cyclist is approaching. On the aggressive setting a kookaburra bird cackle—an unusual sound outside of the jungle—plays to alert those ahead.

Bells & Yells has two settings. On the automatic setting, an infrared sensor detects the distance of obstacles from the cyclist. If someone is within 15 to 20 feet of the cyclist, Bells & Yells plays friendly sleigh bells to try and alert them. Any closer though, and it will play a kookaburra bird call. On the manual setting, the user has control of when to trigger which sound; pressing the green button emits sleigh bells, and pressing the red button plays the kookaburra bird.

Process

Original Sketches

Phase 1: Testing the Logic & Sensors

The first step was to figure out how the logic would work and decide on which sensors we wanted to use. Using Processing, we successfully coded the logic of playing the sounds with different inputs, which helped us understand how users would interact with the sensors and buttons.

We had originally planned to use a toggle button for the sounds, a pressure sensor for the user to manually change the sound, an IR sensor for distance, and an accelerometer as an on/off switch. In the end, we decided to use buttons instead of the pressure sensor because it would be more straight forward for the user.

Phase 2: Run Wirelessly with a Wave Shield

In order to use the device on a bike, it needed to work without being plugged into a computer. We opted for the Adafruit wave shield, which turned out to be a bigger challenge than expected. Every component of the wave shield needed to be soldered into place, the SD card needed to be formatted as FAT16 or FAT 32, and the audio files had to be 22KHz (or less), 16bit (or less) and mono. Any variations from these specifications led to malfunction, as we slowly learned. After many hours of debugging, we were able to play sounds based on button inputs, using Adafruit’s example code.

Once we were able to play sounds using the shield, we needed to move the logic that we had created in Processing into Arduino. We also needed to calibrate the thresholds of the sensor we were using so that the sounds would change in response to how close an obstacle was to the device. This resulted in a lot of trial and error, during which we learned the ultrasonic sensor we were trying to use wouldn’t work with the wave shield. With limited time, we ended up switching to an infrared sensor, and decided not to include the accelerometer.

Phase 3: Prototype 1

After we figured out the wiring, we transitioned to prototyping how the device would look and feel. We bought several boxes from the Container Store to experiment with size and shape. The one we chose had just enough room for all of the components necessary, and was long and skinny—perfect for mounting onto the handlebar of a bicycle.

We simplified our wiring and cut holes into the box to experiment with the placements of the buttons, speaker, and sensor. During this process, we learned that the box was slightly too small for all of its components. In addition to increasing the size of the next prototype, we also planned to use a smaller breadboard, and discovered the user could easily adjust the volume if we cut a slit in the box to expose the knob.

Phase 4: Prototype 2

For our second prototype we prepared a box design using MakerCase.com and Adobe Illustrator and then laser cut it onto chipboard at the Visible Futures Lab. We made a few adjustments to the case, including moving the speaker hole a few centimeters, to make room for the wave shield.

Phase 5: Prototype 3 and 3.1

The third version of our prototype included the acrylic case. Bright neon green seemed appropriate for a project like this, and adds to cyclists’ visibility and safety. Once the case was cut, we reassembled the wiring inside the new case. We hit a few snags here as some of the wires were loose. It worked well when the case was open, but it didn’t consistently work when the case. We realized the speaker protrusion interfered with the security of the wires in the breadboard. In order to address this, we went back to using a perfboard to secure the connections and free up more space for the speaker.

After soldering the wires to the perfboard, we tested Bells & Yells during a short ride in Manhattan from Chelsea to the Meatpacking District, and in Brooklyn’s Prospect Park. In the late afternoon light, Bells & Yells appears to glow, already capturing people’s attention visually. I used Bells & Yells on its manual setting, mostly pressing the green sleigh bells button, feeling too guilty to go aggressive unless it was really necessary. It was when a woman was walking ahead straight down the bike lane. She was completely bewildered by the kookaburra bird call, so it took a moment for her to move out of the way, but it was effective—and a much calmer experience than yelling at people! Reactions to Bells & Yells in and around Prospect Park ranged from laughter and smiles to nothing at all (that’s New York for you).

Challenges

Wave Shield

The biggest challenge during this process was working with the wave shield. It was difficult to find one with adequate documentation and then probably equally, if not more, difficult to get the one we chose to work properly. The documentation and examples that accompanied it were unclear, and it didn’t end up working with the longer-range ultrasonic sensor we had originally planned to use. If we were to do this again, we would try an mp3 shield instead.

Wiring

Despite hours of soldering practice we got from this project, we ran into snags as we transitioned from prototype to prototype and even place to place. The speaker we used took up a lot of room in the acrylic box, sometimes interfering with the connections of the wires. Every time we had an issue we had to walk through the wiring. In the future, we would add LEDs to help indicate what specifically needs debugging.

 
 

Future Opportunities

Bells & Yells was a fun project that could both reduce stress for cyclists and add joy to their experience biking through dense urban areas. Here are a few ideas we have for future iterations:

  • Improving the placement of the buttons so users don’t have to extend their thumbs as far
  • Adding the accelerometer on/off switch
  • Allowing users to customize sounds and determine what “friendly” or “aggressive” means to them
  • Making the product waterproof
  • Improving the sensor to be higher range and quality to increase sensitivity and reduce noise

Final Components

  • Arduino
  • Adafruit wave shield
  • Infrared sensor
  • Speaker
  • 2 buttons
  • 9V Battery
  • Jumper wires
  • Perfboard
  • Lasercut acrylic case
  • Velcro
  • Tape

Arduino Code

 
#include <FatReader.h>
#include <SdReader.h>
#include <avr/pgmspace.h>
#include "WaveUtil.h"
#include "WaveHC.h"

SdReader card;// This object holds the information for the card
FatVolume vol;// This holds the information for the partition on the card
FatReader root; // This holds the information for the filesystem on the card
FatReader f;// This holds the information for the file we're play
WaveHC wave;// This is the only wave (audio) object, since we will only play one at a time

boolean inKooka;// keeps track of what zone sensor is detecting object
boolean inSleigh;
boolean isNoObstacle;

#define DEBOUNCE 5// button debouncer

const int IRPin = A0; // IR Sensor pin
const int numReadings = 10;
int readings[numReadings];// the readings from the analog input
int readIndex = 0;// the index of the current reading
int total = 0;// the running total
int average = 0;// the average
//const int echoPin = 6; // Echo Pin
//const int trigPin = 7; // Trigger Pin
float distance_cm; // calculated in readUltrasonic function

// here is where we define the buttons that we'll use. button "1" is the first, button "6" is the 6th, etc
// OUR TEST ONLY HAS TWO BUTTONS!
// With Adafruit Waveshield pins 6, 7, 8, 9 and the 6 Analog-In pins (also known as digital i/o pins 14-20) are available
byte buttons[] = {8, 9};

// This handy macro lets us determine how big the array up above is, by checking the size
#define NUMBUTTONS sizeof(buttons)

// we will track if a button is 'pressed' (the current state), just pressed, or just released
volatile byte pressed[NUMBUTTONS], justpressed[NUMBUTTONS], justreleased[NUMBUTTONS];

// this handy function will return the number of bytes currently free in RAM, great for debugging!
int freeRam(void)
{
extern int__bss_end;
extern int*__brkval;
int free_memory;
if ((int)__brkval == 0) {
free_memory = ((int)&free_memory) - ((int)&__bss_end);
}
else {
free_memory = ((int)&free_memory) - ((int)__brkval);
}
return free_memory;
}

void sdErrorCheck(void)
{
if (!card.errorCode()) return;
putstring("\n\rSD I/O error: ");
Serial.print(card.errorCode(), HEX);
putstring(", ");
Serial.println(card.errorData(), HEX);
while (1);
}

void setup() {
byte i;

// set up serial port
Serial.begin(9600);
putstring_nl("WaveHC with ");
Serial.print(NUMBUTTONS, DEC);
putstring_nl(" buttons");
putstring("Free RAM: "); // This can help with debugging, running out of RAM is bad
Serial.println(freeRam());// if this is under 150 bytes it may spell trouble!

for (int thisReading = 0; thisReading < numReadings; thisReading++) {
readings[thisReading] = 0;
}

// Set the output pins for the DAC control. This pins are defined in the library
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);

pinMode(13, OUTPUT);// pin13 LED

// Set up button pins as input, and optionally enable pull-up resistors on switch pins
for (i = 0; i < NUMBUTTONS; i++) {
//pinMode(buttons[i], INPUT);
pinMode({i}, INPUT_PULLUP);
digitalWrite(buttons[i], HIGH);
}
/*
// Set up ultrasonic sensor pins
pinMode(trigPin, OUTPUT); // distance sensor
pinMode(echoPin, INPUT);// distance sensor
*/

//if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
if (!card.init()) { //play with 8 MHz spi (default faster!)
putstring_nl("Card init. failed!");// Something went wrong, lets print out why
sdErrorCheck();
while (1); // then 'halt' - do nothing!
}
// enable optimize read - some cards may timeout. Disable if you're having problems
card.partialBlockRead(true);
// Now we will look for a FAT partition!
uint8_t part;
for (part = 0; part < 5; part++) { // we have up to 5 slots to look in
if (vol.init(card, part))
break; // we found one, lets bail
}
if (part == 5) { // if we ended up not finding one:(
putstring_nl("No valid FAT partition!");
sdErrorCheck();// Something went wrong, lets print out why
while (1); // then 'halt' - do nothing!
}
// Lets tell the user about what we found
putstring("Using partition ");
Serial.print(part, DEC);
putstring(", type is FAT");
Serial.println(vol.fatType(), DEC);// FAT16 or FAT32?
// Try to open the root directory
if (!root.openRoot(vol)) {
putstring_nl("Can't open root dir!"); // Something went wrong,
while (1);// then 'halt' - do nothing!
}
// Whew! We got past the tough parts.
putstring_nl("Ready!");
TCCR2A = 0;
TCCR2B = 1 << CS22 | 1 << CS21 | 1 << CS20;
//Timer2 Overflow Interrupt Enable
TIMSK2 |= 1 << TOIE2;
}
SIGNAL(TIMER2_OVF_vect) {
check_switches();
}

void check_switches()
{
static byte previousstate[NUMBUTTONS];
static byte currentstate[NUMBUTTONS];
byte index;
for (index = 0; index < NUMBUTTONS; index++) {
currentstate[index] = digitalRead(buttons[index]); // read the button

Serial.print(index, DEC);
Serial.print(": cstate=");
Serial.print(currentstate[index], DEC);
Serial.print(", pstate=");
Serial.print(previousstate[index], DEC);
Serial.print(", press=");

if (currentstate[index] == previousstate[index]) {
if ((pressed[index] == LOW) && (currentstate[index] == LOW)) {
// just pressed
justpressed[index] = 1;
}
else if ((pressed[index] == HIGH) && (currentstate[index] == HIGH)) {
// just released
justreleased[index] = 1;
}
pressed[index] = !currentstate[index];// remember, digital HIGH means NOT pressed
}
Serial.println(pressed[index], DEC);
if (index == NUMBUTTONS-1) Serial.println();

previousstate[index] = currentstate[index]; // keep a running tally of the buttons
}
}

// ****************************************************

void loop() {
byte i;
int IRValue;

// SENSOR:
// read sensor
// ultrasonic sensor pulseIn does not work with WaveShield
//readUltrasonic();
// temporary fix, use infrared sensor:
//int IRValue;

// SENSOR:
// read sensor
// ultrasonic sensor pulseIn does not work with WaveShield
//readUltrasonic();
// temporary fix, use infrared sensor:

// subtract the last reading:
total = total - readings[readIndex];
// read from the sensor:
readings[readIndex] = analogRead(A0);
// add the reading to the total:
total = total + readings[readIndex];
// advance to the next position in the array:
readIndex = readIndex + 1;

// if we're at the end of the array...
if (readIndex >= numReadings) {
// ...wrap around to the beginning:
readIndex = 0;
}

// calculate the average:
average = total / numReadings;
// send it to the computer as ASCII digits

IRValue = average;
Serial.println(IRValue);

// convert to centimeters:
//distance_cm = 12343.85 * pow(IRValue, -1.15); // centimeters

//Serial.println(round(distance_cm));

if (IRValue >= 150 ) {

if (!inKooka) {
playfile("kooka.wav");
inKooka = true;
inSleigh = false;
isNoObstacle = false;
Serial.println("sensor kooka");
}
}

if (IRValue >= 85 && IRValue < 150) {

if (!inSleigh) {
playfile("sleigh.wav");
inSleigh = true;
inKooka = false;
isNoObstacle = false;
Serial.println("sensor sleigh");
}
}

if (IRValue < 50) {
if (!isNoObstacle) {
// for the future, figure out how to stop ONLY sensor sounds, not button sounds
// for now, stop everything:
wave.stop();
isNoObstacle = true;
inKooka = false;
inSleigh = false;
Serial.println("no obstacle");
}
}

// BUTTONS:
if (pressed[0]) {
Serial.println();
Serial.print("kooka");
playfile("kooka.wav");
while (wave.isplaying && pressed[0]) {
Serial.print(".");
}
wave.stop();
}
if (pressed[1]) {
Serial.println();
Serial.println("sleigh");
playfile("sleigh.wav");
while (wave.isplaying && pressed[1]) {
Serial.print(".");
}
wave.stop();
}
}

// ****************************************************

// Plays a full file from beginning to end with no pause.
void playcomplete(char *name) {
// call our helper to find and play this name
playfile(name);
while (wave.isplaying) {
// do nothing while its playing
}
// now its done playing
}

void playfile(char *name) {
// see if the wave object is currently doing something
if (wave.isplaying) {// already playing something, so stop it!
wave.stop(); // stop it
}
// look in the root directory and open the file
if (!f.open(root, name)) {
putstring("Couldn't open file "); Serial.print(name); return;
}
// OK read the file and turn it into a wave object
if (!wave.create(f)) {
putstring_nl("Not a valid WAV"); return;
}
// ok time to play! start playback
wave.play();
}

/*void readUltrasonic() {
int duration;

// read the analog in value:
// The following trigPin/echoPin cycle is used to determine the
// distance of the nearest object by bouncing soundwaves off of it.

digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

duration = pulseIn(echoPin, HIGH);

//Calculate the distance based on the speed of sound.
distance_cm = (duration / 2) / 29.1;
//distance_inches = (duration/2) / 74;
}*/