Project showcase
Spooky Messages

Spooky Messages © GPL3+

Show spooky messages (inspired by the Stranger Things Ouija board) using a pan and tilt servo controlled by a BASIC program.

  • 1,811 views
  • 0 comments
  • 2 respects

Components and supplies

Apps and online services

About this project

It's Halloween, and I just got an Arduino board that can control a pan-and-tilt display. And up on the dank meme sites are the scary Ouija board setup from Stranger Things. Time to write some code!

Spooky messages, programmed using BASIC, Arduino and more

In this project, I use BC BASIC to control both an Ardunio running the Ardudroid protocol and controlling the pan-and-tilt device. The same BC BASIC program also controls a beLight CC2540T Bluetooth light kit that I picked up from TI a couple of years ago. The result: a beam of light that I can move and position.

By setting up a table that translates characters (A to Z) into positions, I can even spell out words, or in my case, pick out different spooky images.

Code

Scary Message package for Best Calculator BASICMarkdown
## Scary Alphabet Ouija Board

Controls an Arduino-based pan-and-tilt on which is a TI Bluetooth light. 
The Training program lets you point at an area and give it a name (like "A").  Then save the resulting training file
The Display program will read in a training file and, given a message, point to each spot in turn
The Demo program controls the device manually

### Display

Displays messages using a Pan&Tilt controlled light

```BASIC
REM Display a message using the Pan & Tilt
REM The pan and tilt is controlled by an Arduino running
REM the enhanced Ardudroid program with Servo support.

IMPORT FUNCTIONS FROM "TableFunctions"

CLS GREEN
PRINT "Display messages using the Pan and Tilt"


REM Get the pan and tilt device
list = Bluetooth.DevicesRfcommName("HC-06")
PRINT list.Count
IF (list.Count = 0)
    PAPER RED
    PRINT "ERROR: unable to find HC-06 Ardudroid"
    STOP 
END IF
ptraw = list.Get (1)
PRINT ptraw
pt = ptraw.As ("Ardudroid")
pt.ServoAttach (0, 2)
pt.ServoAttach (1, 3)
CONSOLE pt.Name

list = Bluetooth.DevicesName ("beLight*")
lightraw = list.Get(1)
light = lightraw.As ("beLight")
light.SetColor (255, 0, 0, 0)

PRINT "READY!"

tbl = ReadTable()
10 REM Loop Top
message = INPUT DEFAULT "Hello" PROMPT "Enter a message"
message  = String.ToUpper (message)
FOR i  = 1 TO LEN(message)
    ch = MID(message, i, 1)
    pan = GetPan (tbl, ch, -1)
    tilt = GetTilt (tbl, ch, 1)
    IF (pan = -1 OR tilt = -1)
        PRINT "No pan or tilt for " + ch
    ELSE
        light.SetColor (0, 0, 30, 30)
        pt.ServoMove (0, pan)
        pt.ServoMove (1, tilt)
        PAUSE 10
        light.SetColor (255, 0, 0, 0)
        PAUSE 50
        PRINT ch, pan, tilt
    END IF
NEXT i
GOTO 10
```

### Pan Demo

Shows how to use the Slider and Button to manually point the pan and tilt device

```BASIC
CLS BLUE
PRINT "Pan and Tilt"
list = Bluetooth.DevicesRfcommName("HC-06")
PRINT list.Count
IF (list.Count = 0)
    PAPER RED
    PRINT "ERROR: unable to find HC-06 Ardudroid"
    STOP 
END IF
ptraw = list.Get (1)
PRINT ptraw
pt = ptraw.As ("Ardudroid")
pt.ServoAttach (0, 2)
pt.ServoAttach (1, 3)
CONSOLE pt.Name

list = Bluetooth.DevicesName ("beLight*")
lightraw = list.Get(1)
light = lightraw.As ("beLight")

W=400
g = Screen.Graphics (250, 50, 200, W)
g.Background = YELLOW

pan = g.Slider(0, 50, W, 110, "Pan", "pan")
pan.Min = 0
pan.Max = 180
tilt = g.Slider(0, 120, W, 180, "Tilt",  "tilt")
tilt.Min = 0
tilt.Max = 90

BW = 80
stopb = g.Button (0, 0, 70, 30, "STOP", "button")
redb = g.Button (1*BW, 20, 2*BW-5, 60, "RED", "red")
greenb = g.Button (2*BW, 20, 3*BW-5, 60, "GREEN", "green")
blueb = g.Button (3*BW, 20, 4*BW-5, 60, "BLUE", "blue")
offb = g.Button (4*BW, 20, 5*BW-5, 60, "OFF", "off")

FOREVER

FUNCTION button(b)
    FOREVER STOP
END

FUNCTION red(b)
    GLOBAL light
    light.SetColor (255, 0, 0, 0)
END

FUNCTION green(b)
    GLOBAL light
    light.SetColor (0, 255, 0, 0)
END

FUNCTION blue(b)
    GLOBAL light
    light.SetColor (0, 0, 255, 0)
END

FUNCTION off(b)
    GLOBAL light
    light.SetColor (0, 0, 255, 0)
END

FUNCTION pan(s, value)
    Screen.ClearLine (2)
    PRINT "Pan", value
    GLOBAL pt
    pt.ServoMove (0, value)
END
FUNCTION tilt(s, value)
    Screen.ClearLine (2)
    PRINT "Tilt", value
    GLOBAL pt
    pt.ServoMove (1, value)
END
```

### TableFunctions

A set of table functions to create, save, restore and get data from tables.
ReadTable () and SaveTable (tbl)

SetData (tbl, name, pan, tilt)  GetPan (tbl, name, default)  GetTilt (tbl, name, default)


```BASIC
REM Functions to make, use, save, restore tables
REM MakeEmptyTable
REM ...SetData (tbl, name, pan, tilt)
REM ...GetPan (tbl, name, default)
REM ...GetTilt (tbl, name, default)
REM
REM ReadTable ()
REM SaveTable (tbl)
REM
REM ...FindRow --> Index or -1
REM ...
CLS BLUE
PRINT "Test TableFunctions"
TEST()


FUNCTION MakeEmptyTable ()
    DIM tbl()
    tbl.AddRow ("Name", "Pan", "Tilt")
    RETURN tbl
END

FUNCTION FindRow (tbl, name)
    maxrow = tbl.Count
    retval = -1
    FOR r = 2 TO maxrow
        row = tbl[r]
        rowname = row[1]
        IF (rowname = name) THEN RETURN r
    NEXT r
    RETURN retval
END

FUNCTION GetPan (tbl, name, default)
    r = FindRow (tbl, name)
    retval = default
    IF (r <> -1)
        row = tbl[r]
        retval = row[2]
    END IF
    RETURN  retval
END

FUNCTION GetTilt (tbl, name, default)
    r = FindRow (tbl, name)
    IF (r = -1)
        RETURN default
    ELSE
        row = tbl[r]
        RETURN row[3]
    END IF
RETURN


FUNCTION SetData (tbl, name, pan, tilt)
    r = FindRow (tbl, name)
    IF (r = -1)
        tbl.AddRow (name, pan, tilt)
    ELSE
        row = tbl[r]
        row[2] = pan
        row[3] = tilt
    END IF
RETURN

FUNCTION ReadTable ()
    str = String.Escape ("csv", tbl)
    file = File.ReadPicker(.csv)
    IF (file.IsError)
        REM file will have a error message
        PRINT "Cannot open filee", file
        RETURN
    END IF
    str = file.ReadAll()
    tbl = String.Parse ("csv", str)
RETURN tbl

FUNCTION SaveTable (tbl)
    str = String.Escape ("csv", tbl)
    file = File.WritePicker("CSV file", .csv, "test.csv")
    IF (file.IsError)
        REM file will have a error message
        PRINT "Cannot open filee", file
        RETURN
    END IF
    file.WriteText (str)
RETURN


FUNCTION Test_Add_Row_Twice()
    nerror = 0
    tbl = MakeEmptyTable()

    pan = GetPan (tbl, "A", -1)
    nerror = nerror + ASSERT(pan, -1, "no rows result is default")

    SetData (tbl, "A", 10, 200)
    pan = GetPan (tbl, "A", -1)
    nerror = nerror + ASSERT(pan, 10, "first row pan is 10")

    SetData (tbl, "A", 11, 201)
    pan = GetPan (tbl, "A", -1)
    nerror = nerror + ASSERT(pan, 11, "pan should be 11")

    SetData (tbl, "B", 20, 120)
    SetData (tbl, "C", 30, 130)
    SaveTable (tbl)
    tbl2 = ReadTable ()
    pan = GetPan (tbl2, "B", -1)
    nerror = nerror + ASSERT(pan, 20, "tbl2 pan B")
    pan = GetPan (tbl2, "C", -1)
    nerror = nerror + ASSERT(pan, 30, "tbl2 pan C")

    RETURN nerror
END

FUNCTION TEST()
    nerror = 0
    nerror = nerror + Test_Add_Row_Twice()
    RETURN nerror
END

FUNCTION ASSERT (actual, expected, str)
    IF (actual = expected) 
        CONSOLE "OK: " + str
        RETURN 0
    END IF
    CONSOLE "ERROR: "+str
    CONSOLE "Variable is " + actual + " but should be " + expected
    RETURN 1
END
```

### Training

Trains the Pan and Tilt device, giving names (like "A") to different positions

```BASIC
REM Train the Pan & Tilt
REM The pan and tilt is controlled by an Arduino running
REM the enhanced Ardudroid program with Servo support.

IMPORT FUNCTIONS FROM "TableFunctions"

CLS GREEN
PRINT "Train the Pan and Tilt"
tbl = MakeEmptyTable()
Pan= 0
Tilt = 0

H = 300
W = 400
g = Screen.Graphics(50, 50, 300, 400)
g.Background = YELLOW
pan = g.Slider (0, 220, W, 280, "pan", "OnPan")
pan.Max  = 180
tilt = g.Slider(0, 150, W, 210, "tilt", "OnTilt")
tilt.Max = 90
readb = g.Button(0, 0, 70, 40, "Read", "OnRead")
trainb = g.Button(80, 0, 150, 40, "Train", "OnTrain")
saveb = g.Button(160, 0, 230, 40, "Save", "OnSave")
debugb = g.Button(240, 0, 310, 40, "debug", "OnDebug")


REM Get the pan and tilt device
list = Bluetooth.DevicesRfcommName("HC-06")
PRINT list.Count
IF (list.Count = 0)
    PAPER RED
    PRINT "ERROR: unable to find HC-06 Ardudroid"
    STOP 
END IF
ptraw = list.Get (1)
PRINT ptraw
pt = ptraw.As ("Ardudroid")
pt.ServoAttach (0, 2)
pt.ServoAttach (1, 3)
CONSOLE pt.Name

list = Bluetooth.DevicesName ("beLight*")
lightraw = list.Get(1)
light = lightraw.As ("beLight")
light.SetColor (255, 0, 0, 0)

PRINT "READY!"

FOREVER

FUNCTION OnRead(b)
    GLOBAL tbl
    tbl = ReadTable()
    CONSOLE "Read table " + tbl
RETURN

FUNCTION OnDebug(b)
    GLOBAL tbl
    CONSOLE "Current table " + tbl
RETURN

FUNCTION OnSave(b)
    GLOBAL tbl
    SaveTable (tbl)
RETURN

FUNCTION OnTrain(b)
    GLOBAL tbl
    GLOBAL Pan
    GLOBAL Tilt
    label = INPUT DEFAULT "A" PROMPT "What letter?"
    SetData (tbl, label, Pan, Tilt)
    CONSOLE "Current table " + tbl
RETURN


FUNCTION OnPan(s, value)
    GLOBAL Pan
    Pan = value
    Screen.ClearLine (2)
    PRINT "Pan", Pan
    GLOBAL pt
    pt.ServoMove (0, Pan)
END

FUNCTION OnTilt(s, value)
    GLOBAL Tilt
    Tilt = value
    Screen.ClearLine (2)
    PRINT "Tilt", Tilt
    GLOBAL pt
    pt.ServoMove (1, Tilt)
END
```
Ardudroid system with support for Servo motorsArduino
/*
 PROJECT: ArduDroid 
 PROGRAMMER: Hazim Bitar (techbitar at gmail dot com)
 DATE: Oct 31, 2013
 FILE: ardudroid.ino
 LICENSE: Public domain
*/
#include <Servo.h>

#define START_CMD_CHAR '*'
#define END_CMD_CHAR '#'
#define DIV_CMD_CHAR '|'
#define CMD_DIGITALWRITE 10
#define CMD_ANALOGWRITE 11
#define CMD_TEXT 12
#define CMD_READ_ARDUDROID 13
#define MAX_COMMAND 20  // max command number code. used for error checking.
#define MIN_COMMAND 10  // minimum command number code. used for error checking. 
#define IN_STRING_LENGHT 40
#define MAX_ANALOGWRITE 255
#define PIN_HIGH 3
#define PIN_LOW 2

// Additions to support Servo
Servo Servos[10];
#define CMD_SERVO_ATTACH 14
#define CMD_SERVO_WRITE 15

// Additions to support Ultrasonic!
//+++++++++++++++ULTRASONIC VARIABLES++++++++++++++++++++++++++++

#define echoPin A2 // Echo Pin
#define trigPin A3 // Trigger Pin
#define buzzerPin A0 // Pin for the buzzer
int maximumRange = 200; // Maximum range needed
int minimumRange = 0; // Minimum range needed
long readDistance; // the output distance from the sensor
int ultraSensor(int theEchoPin, int theTrigPin);
#define CMD_ULTRA 16

String inText;

void setup() {
  Serial.begin(9600);
  Serial.println("ArduDroid 0.12 Alpha by TechBitar (2013)");
  Serial.flush();
}

void loop()
{
  Serial.flush();
  int ard_command = 0;
  int pin_num = 0;
  int pin_value = 0;

  char get_char = ' ';  //read serial

  // wait for incoming data
  if (Serial.available() < 1) return; // if serial empty, return to loop().

  // parse incoming command start flag 
  get_char = Serial.read();
  if (get_char != START_CMD_CHAR) return; // if no command start flag, return to loop().

  // parse incoming command type
  ard_command = Serial.parseInt(); // read the command
  
  // parse incoming pin# and value  
  pin_num = Serial.parseInt(); // read the pin
  pin_value = Serial.parseInt();  // read the value

  // 1) GET TEXT COMMAND FROM ARDUDROID
  if (ard_command == CMD_TEXT){   
    inText =""; //clears variable for new input   
    while (Serial.available())  {
      char c = Serial.read();  //gets one byte from serial buffer
      delay(5);
      if (c == END_CMD_CHAR) { // if we the complete string has been read
        // add your code here
        break;
      }              
      else {
        if (c !=  DIV_CMD_CHAR) {
          inText += c; 
          delay(5);
        }
      }
    }
  }

  // 2) GET digitalWrite DATA FROM ARDUDROID
  if (ard_command == CMD_DIGITALWRITE){  
    if (pin_value == PIN_LOW) pin_value = LOW;
    else if (pin_value == PIN_HIGH) pin_value = HIGH;
    else return; // error in pin value. return. 
    set_digitalwrite( pin_num,  pin_value);  // Uncomment this function if you wish to use 
    return;  // return from start of loop()
  }

  // 3) GET analogWrite DATA FROM ARDUDROID
  if (ard_command == CMD_ANALOGWRITE) {  
    analogWrite(  pin_num, pin_value ); 
    // add your code here
    return;  // Done. return to loop();
  }

  // 4) SEND DATA TO ARDUDROID
  if (ard_command == CMD_READ_ARDUDROID) { 
    // char send_to_android[] = "Place your text here." ;
    // Serial.println(send_to_android);   // Example: Sending text
    Serial.print(" Analog 0 = "); 
    Serial.println(analogRead(A0));  // Example: Read and send Analog pin value to Arduino
    return;  // Done. return to loop();
  }

  if (ard_command == CMD_SERVO_ATTACH){
    Servos[pin_num].attach(pin_value); // the 'pin' is the servo number
  }

  // 'pin' is the servo number and 'value' is the value.
  // often values are 0..180
  if (ard_command == CMD_SERVO_WRITE) {
    Servos[pin_num].write (pin_value);
  }
  
  if (ard_command == CMD_ULTRA) {
   int singleRead = 0;
   int allReads = 0;
   for (int i = 0; i< 25; i++){
      singleRead = ultraSensor(echoPin, trigPin); //read the distance
      allReads += singleRead;
    }
    
    // final average
    readDistance = allReads/25;
    Serial.print("Distance="); 
    Serial.println(readDistance); 
  }
  
}

// 2a) select the requested pin# for DigitalWrite action
void set_digitalwrite(int pin_num, int pin_value)
{
  switch (pin_num) {
  case 13:
    pinMode(13, OUTPUT);
    digitalWrite(13, pin_value);  
    // add your code here      
    break;
  case 12:
    pinMode(12, OUTPUT);
    digitalWrite(12, pin_value);   
    // add your code here       
    break;
  case 11:
    pinMode(11, OUTPUT);
    digitalWrite(11, pin_value);         
    // add your code here 
    break;
  case 10:
    pinMode(10, OUTPUT);
    digitalWrite(10, pin_value);         
    // add your code here 
    break;
  case 9:
    pinMode(9, OUTPUT);
    digitalWrite(9, pin_value);         
    // add your code here 
    break;
  case 8:
    pinMode(8, OUTPUT);
    digitalWrite(8, pin_value);         
    // add your code here 
    break;
  case 7:
    pinMode(7, OUTPUT);
    digitalWrite(7, pin_value);         
    // add your code here 
    break;
  case 6:
    pinMode(6, OUTPUT);
    digitalWrite(6, pin_value);         
    // add your code here 
    break;
  case 5:
    pinMode(5, OUTPUT);
    digitalWrite(5, pin_value); 
    // add your code here       
    break;
  case 4:
    pinMode(4, OUTPUT);
    digitalWrite(4, pin_value);         
    // add your code here 
    break;
  case 3:
    pinMode(3, OUTPUT);
    digitalWrite(3, pin_value);         
    // add your code here 
    break;
  case 2:
    pinMode(2, OUTPUT);
    digitalWrite(2, pin_value); 
    // add your code here       
    break;      
    // default: 
    // if nothing else matches, do the default
    // default is optional
  } 
}


// From walteros_05_2.ino from their Slant Robot
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

int ultraSensor(int theEchoPin, int theTrigPin){
   //this fucntion caluclates and returns the distance in cm
    
   long duration, distance; // Duration used to calculate distance
   /* The following trigPin/echoPin cycle is used to determine the
   distance of the nearest object by bouncing soundwaves off of it. */ 
   digitalWrite(theTrigPin, LOW); 
   delayMicroseconds(2); 
  
   digitalWrite(theTrigPin, HIGH);
   delayMicroseconds(10); 
   
   digitalWrite(theTrigPin, LOW);
   duration = pulseIn(theEchoPin, HIGH);
   
   //Calculate the distance (in cm) based on the speed of sound.
   distance = duration/58.2;  
   return distance;
  
}

Comments

Similar projects you might like

Stranger Blinks: Send Messages from the Upside-Down

Project tutorial by Luigi Francesco Cerfeda

  • 3,424 views
  • 1 comment
  • 10 respects

Using SMS messages to control LED color

Project tutorial by SteiniBrodda

  • 6,974 views
  • 0 comments
  • 6 respects

Add Lights and Spooky Music to Your Jack-O-Lantern

Project tutorial by aams86

  • 5,034 views
  • 0 comments
  • 19 respects

Send messages to the Arduino Yun with your internet browser

Project tutorial by Arduino_Scuola

  • 2,309 views
  • 2 comments
  • 5 respects

Spooky Candy Box

Project tutorial by Shoeb Ahmed

  • 907 views
  • 0 comments
  • 5 respects

Programmable Stranger Things Light Wall Costume

Project showcase by Michael Barretta

  • 3,703 views
  • 6 comments
  • 18 respects
Add projectSign up / Login