TOP

Get on the CodeShield Train!

We travelled to Makerfaire Detroit as part of our marketing push for the Diyode CodeShield crowdfunding campaign. It seemed like a great excuse to play with some Lego. Not that I really need an excuse.

This year I dusted off some vintage train parts (Set #7722, for those playing along at home). The train is motorized, and pulls it’s own battery pack around the track.

Wouldn’t it be fun if it stopped at stations on it’s own? Let’s add an Arduino and a CodeShield and see if we can make Lego even more interactive.

If we want to control our train with a CodeShield, the first thing we need to be able to do is to turn the motor on and off. Fortunately the CodeShield comes equipped with a relay and terminal block for just such an emergency.

CodeShield Train

Using a relay

A relay is essentially an electronically controlled switch. All we need to do is send it a HIGH or LOW value, and it will turn on or off.

If you look closely at the terminal block on the CodeShield, you’ll see that the pins are labelled ‘NC’, ‘C’, and ‘NO’. These translate from engineer speak as ‘Normally Closed’, ‘Common’ and ‘Normally Open’. This means that when the relay is turned off, the ‘NC’ and ‘C’ pins are connected. When the relay is turned on, the ‘C’ and ‘NO’ pins are connected.

This gives us a basic on/off switch.  All we have to do is wire this switch — our relay — in between the battery and the motor.

Given that my version of this set is thirty years old, the cables are long since gone. I’ve modified this circuit just a little to use a single battery pack for both the Arduino and the train motor.

The beginnings of a sketch

#define ENCODER_A 14
#define ENCODER_B 15
#define ENCODER_PORT PINC
#define SWITCH 13
#define BUTTON 12
#define RGB_RED 11
#define RGB_GREEN 10
#define RGB_BLUE 9
#define LED 6
#define SERVO 5
#define PIEZO 3
#define RELAY 2
#define POT 2
#define HALL 3
#define THERMISTOR 4
#define PHOTOCELL 5

void start_driving() {
  // Turn on the relay.
  digitalWrite(RELAY,HIGH);
}

void stop_driving() {
  // Turn off the relay.
  digitalWrite(RELAY,LOW);
}

void keep_driving() {
  // Do nothing for a short time.
  delay(10);
}

void setup() {
  // Make sure we can turn our relay on.
  pinMode(RELAY, OUTPUT);
  start_driving();
}

void loop() {
  keep_driving();
}
									

Driving blind

If we want to stop at a given station, we might be able to do it with some careful timing. I timed how long it takes the train to run this track, and averaged five laps. This sketch will run the train for that amount of time, and then stop and wait for a moment.

#define ENCODER_A 14
#define ENCODER_B 15
#define ENCODER_PORT PINC
#define SWITCH 13
#define BUTTON 12
#define RGB_RED 11
#define RGB_GREEN 10
#define RGB_BLUE 9
#define LED 6
#define SERVO 5
#define PIEZO 3
#define RELAY 2
#define POT 2
#define HALL 3
#define THERMISTOR 4
#define PHOTOCELL 5

void start_driving() {
  // Turn on the relay.
  digitalWrite(RELAY,HIGH);
}

void stop_driving() {
  // Turn off the relay.
  digitalWrite(RELAY,LOW);
}

void keep_driving(int time) {
  // Do nothing for a short time.
  delay(time);
}

void setup() {
  // Make sure we can turn our relay on.
  pinMode(RELAY, OUTPUT);
}

void loop() {
  // Drive forwards for 8.1 seconds.
  // I got that number by timing the train
  // with a stopwatch.
  start_driving();
  keep_driving(8100);
  stop_driving();

  // Stay put for a second.
  delay(1000);
}
									

Sorry for the noise. I did mention this set is from 1985, right? Big coarse metal gears instead of fine toothed nylon gears. You should have heard it before WD-40.

Not only does our train not stop at our station, it doesn’t even stop at the same place each time. We could trim this program to be a little more accurate, but it will never be completely, 100% true. As the batteries wear down, our train will move slower and our timing will be off again.

This is the robot equivalent of walking forwards blindfolded. This works just fine for some simple cases, but one wrong move and you’ll never figure figure out where you are. This is called ‘Open Loop Control’.

Watching for the station

So we can’t rely on just timing to make sure our train always gets to the station. We need to be able to tell when the train reaches the station, and turn the engine off.

The CodeShield has quite a few sensors and switches on it. For this example, we’ll use lego magnets, like the one one this post:

CodeShield and Magnet

These are out of some old M-Tron sets. The CodeShield has an on-board Hall Effect sensor which can measure magnetic fields.

This strategy is called ‘Closed Loop Control’ — instead of blindly stepping forwards, our train will keep looking for the station, and stop when we see it.

Using a Hall effect sensor

The Hall effect sensor we’ve used on the codeshield is an analog sensor. Digital sensors return a true or false value. Analog sensors return pretty much any number, and we need to be able to interpret that number in software. On the Arduino, that number can vary between 0 and 1023, which is quite a range. For our purposes, we need to know if a magnet is present.

Let’s not break out the textbooks just yet. Instead, we’ll measure how strong our little lego magnets are.

To do this, we’ll use a custom sketch that will show us exactly what the Arduino sees when we bring our magnet near.

#define ENCODER_A 14
#define ENCODER_B 15
#define ENCODER_PORT PINC
#define SWITCH 13
#define BUTTON 12
#define RGB_RED 11
#define RGB_GREEN 10
#define RGB_BLUE 9
#define LED 6
#define SERVO 5
#define PIEZO 3
#define RELAY 2
#define POT 2
#define HALL 3
#define THERMISTOR 4
#define PHOTOCELL 5

void setup() {
  // Initialize serial communications at 9600 bps:
  Serial.begin(9600); 
}

void loop() {
  // Measure the hall effect sensor.
  int hall = analogRead(HALL);            

  // Print the results to the serial monitor.
  Serial.print("hall sensor = ");                       
  Serial.println(hall);      

  // Wait a little bit before the next measurement
  
  delay(100);                     
}
									

Once you’ve got this sketch loaded and running, click the Serial Monitor icon at the top right of the Arduino IDE. Here are the numbers I got when I tried it:

hall sensor = 520
hall sensor = 519
hall sensor = 519
hall sensor = 517
hall sensor = 512
hall sensor = 502
hall sensor = 500
hall sensor = 501
hall sensor = 499
hall sensor = 499
hall sensor = 499
hall sensor = 499
hall sensor = 499
hall sensor = 499
hall sensor = 499
hall sensor = 500
hall sensor = 502
hall sensor = 508
hall sensor = 516
hall sensor = 519
hall sensor = 520
									

After a little bit of experimentation, we can see that the average measurement on our Hall sensor is about 519. When we put our magnet near it, it can vary down to about 500. If I flipped the magnet over, it’d go about the same amount the other way. So let’s call that about 540. Just to be safe, let’s assume a magnet is present if the reading from the hall effect sensor varies by more than 10.

Let’s build a function that tells us if a our sensor sees a magnet:

#define ENCODER_A 14
#define ENCODER_B 15
#define ENCODER_PORT PINC
#define SWITCH 13
#define BUTTON 12
#define RGB_RED 11
#define RGB_GREEN 10
#define RGB_BLUE 9
#define LED 6
#define SERVO 5
#define PIEZO 3
#define RELAY 2
#define POT 2
#define HALL 3
#define THERMISTOR 4
#define PHOTOCELL 5

// Take some measurements
const int hall_normal = 519;
const int hall_sensitivity = 10;

boolean magnet_present() {
  // Take a reading.
  int hall_reading = analogRead(HALL);

  // Test to see if the reading is further away from normal
  // then our sensitivity value.  If it is, return true.
  // otherwise return false.  
  return(abs(hall_reading - hall_normal) > hall_sensitivity);
}

void setup() {
  // Initialize serial communications at 9600 bps:
  Serial.begin(9600); 
}

void loop() {
  if( magnet_present() ) {
    Serial.println("I see a magnet!");    
  } else {
    Serial.println("I don't see a magnet!");    
  }

  // Wait a little bit before the next measurement
  delay(100);                     
}
									

And here’s the result:

I don't see a magnet!
I don't see a magnet!
I don't see a magnet!
I don't see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I see a magnet!
I don't see a magnet!
I don't see a magnet!
I don't see a magnet!
									

Instead of a number, now we have a yes-or-no answer.

Loops

So now we can measure our magnet. That’s a good thing! We’re almost done! We just need to figure out what our train should do when it sees our magnet. This means we have to build the loop in our closed loop control system.

A while loop is just the thing. It will keep repeating all of the instructions within it’s curly brackets, as long as it’s condition is true. In this case, we want it to keep looping as long as we don’t see the magnet. The ‘!’ in our condition reverses is — basically it says “Not!”.

Here’s the loop:

void loop() {
  // Start Driving.
  start_driving();
  
  // If we can't see a magnet, keep driving.
  while(!magnet_present()) {
    keep_driving();
  }
  
  // If we're out of the loop, we must have seen a magnet.
  stop_driving();
  
  // Wait a little bit before starting again.
  delay(1000);
}
									

And here’s the whole program:

#define ENCODER_A 14
#define ENCODER_B 15
#define ENCODER_PORT PINC
#define SWITCH 13
#define BUTTON 12
#define RGB_RED 11
#define RGB_GREEN 10
#define RGB_BLUE 9
#define LED 6
#define SERVO 5
#define PIEZO 3
#define RELAY 2
#define POT 2
#define HALL 3
#define THERMISTOR 4
#define PHOTOCELL 5

// Take some measurements
const int hall_normal = 519;
const int hall_sensitivity = 10;

boolean magnet_present() {
  // Take a reading.
  int hall_reading = analogRead(HALL);

  // Test to see if the reading is further away from normal
  // then our sensitivity value.  If it is, return true.
  // otherwise return false.  
  return(abs(hall_reading - hall_normal) > hall_sensitivity);
}

void start_driving() {
  // Turn on the relay.
  digitalWrite(RELAY,HIGH);
}

void stop_driving() {
  // Turn off the relay.
  digitalWrite(RELAY,LOW);
}

void keep_driving() {
  // Do nothing for a short time.
  delay(10);
}

void setup() {  
  // Make sure we can turn our relay on.
  pinMode(RELAY, OUTPUT);
}

void loop() {
  // Start Driving.
  start_driving();
  
  // If we can't see a magnet, keep driving.
  while(!magnet_present()) {
    keep_driving();
  }
  
  // If we're out of the loop, we must have seen a magnet.
  stop_driving();
  
  // Wait a little bit before starting again.
  delay(1000);
}
									


All the bells and whistles!

Our train works! It starts and stops! But we can do a little better. Here’s the final sketch that we used at MakerFaire Detroit, with some additional refinements.

#define ENCODER_A 14
#define ENCODER_B 15
#define ENCODER_PORT PINC
#define SWITCH 13
#define BUTTON 12
#define RGB_RED 11
#define RGB_GREEN 10
#define RGB_BLUE 9
#define LED 6
#define SERVO 5
#define PIEZO 3
#define RELAY 2
#define POT 2
#define HALL 3
#define THERMISTOR 4
#define PHOTOCELL 5

// Take some measurements
const int hall_normal = 519;
const int hall_sensitivity = 10;

boolean magnet_present() {
  // Take a reading.
  int hall_reading = analogRead(HALL);

  // Test to see if the reading is further away from normal
  // then our sensitivity value.  If it is, return true.
  // otherwise return false.  
  return(abs(hall_reading - hall_normal) > hall_sensitivity);
}

void start_driving() {
  // Turn on the relay.
  digitalWrite(RELAY,HIGH);

  // Turn on the RGB LED.
  digitalWrite(RGB_GREEN, HIGH);
}

void stop_driving() {
  // Turn off the relay.
  digitalWrite(RELAY,LOW);

  // Turn off the RGB LED.
  digitalWrite(RGB_GREEN, LOW);
}

void keep_driving() {
  // Do nothing for a short time.
  delay(10);
}

void chirp() {
  for( int i = 0; i < 5; i++ ) {
    tone(PIEZO, 1000, 10);
    delay( 90 );
  }
}

void countdown() {
  for( int i = 0; i < 5; i++ ) {
    digitalWrite( RGB_RED, HIGH );
    delay( 100 );
    digitalWrite( RGB_RED, LOW );
    delay( 900 );
  }
}

void setup() {  
  // Make sure we can turn our relay on.
  pinMode(RELAY, OUTPUT);

  // Set up the RGB LED.
  pinMode( RGB_RED, OUTPUT );
  pinMode( RGB_GREEN, OUTPUT );
  pinMode( RGB_BLUE, OUTPUT );

  // Also, set up the piezo.
  pinMode(PIEZO, OUTPUT);
}

void loop() {
  // Start Driving.
  chirp();
  start_driving();
  
  // If we can't see a magnet, keep driving.
  while(!magnet_present()) {
    keep_driving();
  }
  
  // If we're out of the loop, we must have seen a magnet.
  stop_driving();
  chirp();
  
  // Wait a little bit before starting again.
  countdown();
}
									

The next project

Control loops are the foundation of many types of software. In fact, most of my Arduino projects have been based around a single control loop just like this one — watch for a sensor to change, and then do something when it does.

The CodeShield has many different sensors, and many different outputs. Next time, we’ll follow up with a laser tripwire that uses a very similar kind of loop.

Read More
TOP

The CodeShield gets a IndieGoGo Campaign!

We have now started a crowd sourced funding campaign on IndieGoGo!

The Diyode Codeshield – A better way to teach Arduino  campaign’s goal is to provide pre-assembled Codeshields to the masses!
The Codeshield IndieGoGo

Read More
TOP

April 1st on the Diyode IRC

So, probably like many hackerspaces, Diyode spends a lot of time on our IRC channel. We also have a good few members who compulsively check the webcam whenever someone enters the shop. So when April first rolled around on a Monday this year, I figured it was time to put the CodeShield to work. I present: the IRC triggered, arduino controlled, floating head! This is the feed from the classroom webcam:

The face rising is triggered by a specific phrase on the Diyode IRC channel. We have a server in the shop that runs our access control system, including a bot that hangs out on the IRC channel so that it can post workshop events to the IRC, and collect to-do items and meeting topics from the chat room. The bot can also speak Text-to-Speech phrases with an IRC command.

The prank was to attract attention to the Diyode webcam on IRC (“Hey, someone just entered the shop without the doorbot noticing”), and then trigger a face to appear right in front of the cam while everyone was watching. For the face, I chose the disturbingly brilliant Jackson Blankenship. A specific phrase spoken on the IRC channel would trigger IRC python script on the linux box to send a message over USB to the arduino above the camera. Then the servo on the codeshield would make the face rise slowly in front of the webcam, then drop back down, freaking the crap out of anyone watching.

The Setup

prank

 

The photo above shows the arduino/codeshield on a wooden block above the webcam. Three laser-cut arms control the face rising (I tweaked the design a bit after I took the photo).

Here’s the .ai file for laser cutting the arms. Particularly useful for the cut-out of the servo setting, if you’re doing something similar with sg90 servos.

The Code:

The Access Control box is running linux, with the responsibilities for the the IRC being handling by a python script. Some bits, obviously, have been changed for security reasons…

#!/usr/bin/python

import sys 
import socket 
import string 
import os
import time
import conf
import datetime
import httplib
import urllib2
import MySQLdb
import calendar

import serial
from serial.tools import list_ports

if conf.platform == 'linux':  
  from festival import say

HOST='irc.freenode.net' #The server we want to connect to 
PORT=6667 #The connection port which is usually 6667 
NICK='DiyodeDoorBot' #The bot's nickname 
IDENT='pybot' 
REALNAME='Diyode HQ Door Bot' 
OWNER='diyode' #The bot owner's nick 
CHANNELINIT='#diyode' #The default channel for the bot 
readbuffer='' #Here we store all the messages from server 

s=socket.socket( ) #Create the socket 
s.settimeout(5)
s.connect((HOST, PORT)) #Connect to server 
s.send('NICK '+NICK+'n') #Send the nick to server 
s.send('USER '+IDENT+' '+HOST+' bla :'+REALNAME+'n') #Identify to server

def doSpeak(msg):
  if conf.platform == 'linux':  
    say(msg)
  else:
    os.system('say "'+msg.replace('"','')+'"') 

def saveMessage(message, sender):
  d = datetime.datetime.utcnow()
  timestamp = calendar.timegm(d.utctimetuple())
  conn = MySQLdb.connect(host= "localhost", user="-------", passwd="----------", db="-------")
  x = conn.cursor()
  sql = "INSERT INTO irc_events (message,timestamp,sender) VALUES ('%s','%s','%s')" % (message,timestamp,sender)
  x.execute("INSERT INTO irc_events (message,timestamp,sender) VALUES (%s, %s, %s)", (message,timestamp,sender))
  conn.commit()
  conn.close()  

def parsemsg(msg): 
  global CHANNELINIT
  complete=msg[1:].split(':',1) #Parse the message into useful data 
  info=complete[0].split(' ') 
  msgpart=complete[1] 
  msgpartLower = msgpart.lower()
  sender=info[0].split('!') 
  saveIt = 1
  if msgpart[0]=='`' and sender[0]==OWNER: #Treat all messages starting with '`' as command 
    saveIt = 0;
    cmd=msgpart[1:].split(' ') 
    if cmd[0]=='op': 
      s.send('MODE '+info[2]+' +o '+cmd[1]+'n') 
    if cmd[0]=='deop': 
      s.send('MODE '+info[2]+' -o '+cmd[1]+'n') 
    if cmd[0]=='voice': 
      s.send('MODE '+info[2]+' +v '+cmd[1]+'n') 
    if cmd[0]=='devoice': 
      s.send('MODE '+info[2]+' -v '+cmd[1]+'n') 
    if cmd[0]=='sys': 
      syscmd(msgpart[1:],info[2]) 
  elif msgpartLower.find('!speak') == 0:
    doSpeak(msgpart[6:])
    s.send('PRIVMSG '+CHANNELINIT+' :Message spoken.n') 
    if msgpartLower.find('!speak: hello?') == 0:
      doPrank()
  elif msgpartLower.find('!newtopic") == 0:
    doSaveTopic(msgpart[13:], sender[0])
  elif msgpartLower.find('!todo') == 0:
    doSaveTask(msgpart[5:], sender[0])
  if msgpart[0]=='-' and sender[0]==OWNER : #Treat msgs with - as explicit command to send to server 
    saveIt = 0;
    cmd=msgpart[1:] 
    s.send(cmd+'n') 
    if conf.debug:
      print 'cmd='+cmd
  if saveIt and msgpart.find('Welcome to the freenode Internet Relay Chat Network') != 0:
    saveMessage(msgpart.strip(), sender[0])

def syscmd(commandline,channel): 
  cmd=commandline.replace('sys ','') 
  cmd=cmd.rstrip() 
  os.system(cmd+' >temp.txt') 
  a=open('temp.txt') 
  ot=a.read() 
  ot.replace('n','|') 
  a.close() 
  s.send('PRIVMSG '+channel+' :'+ot+'n') 
  return 0

def checkFile():
  h = open(conf.ircFlagFile,'r')
  contents = h.read()
  h.close()
  return contents    

def clearFile():
  h = open(conf.ircFlagFile,'w')
  h.write('-')
  h.close()
  
def doSaveTopic(topic, sender):
  h = open(conf.topicFile,'a')
  now = str(datetime.datetime.utcnow())
  topic = "%s:::%s:::%sn" % (now,topic.strip(),sender.strip());
  print topic
  h.write(topic)
  h.close()
  s.send('PRIVMSG '+CHANNELINIT+' :' + 'Topic Saved: http://diyode.com/topics.phpn') 
  getURL('http://www.diyode.com/interface/UpdateTopics.php')

def doSaveTask(task, sender):
  h = open(conf.taskFile,'a')
  now = str(datetime.datetime.utcnow())
  task = "%s:::%s:::%sn" % (now,task.strip(),sender.strip());
  print task
  h.write(task)
  h.close()
  s.send('PRIVMSG '+CHANNELINIT+' :' + 'To Do Item Saved: http://diyode.com/tasks.phpn') 
  getURL('http://www.diyode.com/interface/updateTasks.php')

def getURL(urlStr):
  #print "getting URL: "+urlStr
  try:
    fileHandle = urllib2.urlopen(urlStr,timeout=3)
    str1 = fileHandle.read()
    fileHandle.close()
    #print 'getUrl Result: '+str1
    return str1
  except IOError:
    print 'Cannot open URL %s for reading' % urlStr
    return False


def doPrank():
  if arduino1:
    arduino1.write("P");


port1 = ""
ports = list_ports.comports()
for port in ports:
    if (port[0].find("usb") != -1):
        if port1 == "":
            port1 = port[0]
if (port1 == ""):
    print "there are no arduinos attached"
else:
  arduino1 = serial.Serial(port1, 115200)
  print("arduino1 connected to "+port1)


clearFile()
pongCount = 0;

while pongCount < 600: 
  pongCount = pongCount + 1
  if pongCount % 10:
    if not conf.checkRun():
      print "Stopping irc"
      exit()
  try:
    line = s.recv(1000) #recieve server messages 
    time.sleep(0.25)
  except:
    line = ''
  if len(line):
    if conf.debug:
      print line #server message is output 
    if line.find(' :+i')!=-1: 
      time.sleep(0.5)
      print 'joining #diyode'
      s.send('JOIN '+CHANNELINIT+'n') #Join a channel 
      pongCount = 0
    if line.find('PRIVMSG')!=-1: #Call a parsing function 
      parsemsg(line) 
    line=line.rstrip() #remove trailing 'rn' 
    linebits=line.split() 
    if(linebits[0]=='PING'): #If server pings then pong 
      if conf.debug:
        print "pongCount %d" % (pongCount)
      s.send('PONG '+linebits[1]+'n')
      pongCount = 0
  sendMsg = checkFile()
  if sendMsg != '-':
    if conf.debug:
      print sendMsg
    s.send('PRIVMSG '+CHANNELINIT+' :'+sendMsg+'n') 
    clearFile()

conn.close()
									

Plugged into the linux box is an arduino with CodeShield attached, and the face hanging on a laser cut arm attached to the codeshield’s servo. This is the sketch running on the arduino

#include <Servo.h>

#define ENCODER_A 14
#define ENCODER_B 15
#define ENCODER_PORT PINC
#define SWITCH 13
#define BUTTON 12
#define RGB_RED 11
#define RGB_GREEN 10
#define RGB_BLUE 9
#define LED 6
#define SERVO 5
#define PIEZO 3
#define RELAY 2
#define POT 2
#define HALL 3
#define THERMISTOR 4
#define PHOTOCELL 5

Servo aServo;
char incomingByte;
char mode = '-';
char val[7];
int col = 1;
int a = 0;

void setup() {
  Serial.begin (115200);
  aServo.attach(SERVO);
}

void loop() {
  if (Serial.available() > 0) {
    incomingByte = Serial.read();
    if (incomingByte == 'p' ||incomingByte == 'P') {
      if (a == 0) {
        a = 1;
      }
    }
  }
  if (a) {
    int angle = 45 + 90 * sin(a * 0.00174532922);
    aServo.write(angle);
    if (a++ > 1800) {
      a = 0;
    }
  }
  
  delay(10);
}

									

 

Read More
TOP

CodeShield is heading to the Bay Area Maker Faire!

We’re terribly excited this year to be attending the Bay Area Maker Faire in San Mateo, California.  The Maker Faire will be on May 18th and 19th. Come and find us if you’re there. We’ll be convincing everyone how terrific the Codeshield is, with plenty of demonstrations.

Read More
TOP

New site launched!

We’ve spent  some time improving the online presence of the Diyode Codeshield. Hopefully, everything you need is far easier to find here.

If there was information you were looking for in the old site, you can still reach it at http://www.diyode.com/codeshield/index.php

Read More