Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
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!
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.
## 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
```
/*
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;
}
2 projects • 3 followers
C# Programmer for many years, and a lover of all things IOT and Bluetooth.
Comments
Please log in or sign up to comment.