To begin, I've made a video of the problem I'm experiencing:
https://www.youtube.com/watch?v=xHl_tgrwg6A
I've attached a crude schematic, as well.
Issue: When applying power and data to my Neopixels, I sometimes get the wrong color or frozen animation only on startup.
Setup:
- I am running a Python app I wrote which sends commands over USB serial to an Arduino Mega (2560). The Mega is powered off USB.
- The Mega has its hardware Serial1 Tx/Rx pins hooked up to a USB jack. I have an 8V DC regulator (fed by a 12V, 8A wall wart, the regulator is capable of 12A) that shares ground with the Mega. Its Vcc (8V) and Gnd go to the USB jack, too.
- After a 3ft USB cable (which is really only being used as a generic 4 conductor cable), I connect the Tx/Rx to the Serial of an Arduino Micro, which is powered with the 8V and Gnd via it's Vin and Gnd pins. The 8V also goes into a standard LM317T circuit configured with two resistors to output 5V. It shares Gnd with the USB (thus also the 8V and Mega, and the Micro).
- The 5V goes to power two short (60 pixels per meter, black PCB) RGBW Neopixel strips, each 9 pixels long. I have a 1000 uf polarized cap across the Power / Gnd to the Neopixels. The data pin of the pixel strips is tied together and connected to pin 10 of the Micro via a 370 ohm resistor, as I want the strips to be identical. I seem to have the issue even if I only use one strip.
- In this application, the whole strips are the same color, but the number of lights lit up and brightness varies. So, the commands go from the Mac to the Mega, which forwards them over USB to the Micro to tell to set the R, G, B, or W channel, and I have another command for "latching" that into the lights, which actually causes the Micro to call color sets and show on the strip object.
The reason for the forwarding is these lights will live in a peripheral connected to the main enclosure that contains the Mega. I'm trying to make my application impervious to the USB connection being broken and unbroken, so the lights will always work when plugged in.
THEORIES
* I had an idea that maybe the order the pins are contacting the jack is varying depending on how I plug it in, and that is causing variation.
* I wondered if there is a timing issue going on related to how long it takes after I connect USB for the LM317T to start outputting a solid 5V, and how that relates to when the code starts controlling the lights (if this seems suspicious to anyone, I suppose I could sample this on an analog pin of the micro and not init the lights until it hits around 5V). I tried delaying the setup function on the Micro by 2 seconds just to see if that stabilized things, but that didn't seem to reliably fix it.
* I saw in the Uber guide something about trying to always connect ground to the pixels first, then power, but I don't have much control over that because all these connections are made when the USB is plugged in.
I know it's an awful lot, but in case there's something you want to check, I'm including the code below for the Python app, the Mega, and the Micro.
Python
- Code: Select all | TOGGLE FULL SIZE
# Commands for talking to the lab and syringe
# Calling the construct commands adds the command to the serial write queue, which
# can be polled and written at a speed of your choice from the main app
import Queue
import time
import serial
from lib.utils import get_all_from_queue, get_item_from_queue, clamp
serial_write_queue = Queue.Queue()
serial_read_queue = Queue.Queue()
serial_port = None
def SetSerialPort(portReference):
global serial_port
serial_port = portReference
def GetWriteQueueItems():
return list(get_all_from_queue(serial_write_queue))
def GetReadQueueItems():
return list(get_all_from_queue(serial_read_queue))
def WriteSerialQueue():
if serial_port == None:
return
qdata = GetWriteQueueItems()
for command in qdata:
try:
# Can try writing command directly (byte array)
serial_port.write( command )
except serial.serialutil.SerialException:
# Fail silently if port not available. Could close
# port and stop serial here, but since we have nothing
# to auto reopen port, we just fail
return
# Module level state of the most recent values read in
# from serial. Each command read makes a copy of this
# and adds it to the serial queue to be read by check_server_data_queues in Engine
sensors = { "he1" : 0,
"he2" : 0,
"he3" : 0,
"he4" : 0,
"he5" : 0,
"he6" : 0,
"he7" : 0,
"he8" : 0,
"he9" : 0,
"mbut1" : 0,
"mbut2" : 0,
"mbut3" : 0,
"mbut4" : 0,
"mbut5" : 0,
"mbut6" : 0,
"mbut7" : 0,
"mbut8" : 0,
"mbut9" : 0,
"syrpot" : 0,
"enc1but" : 0,
"enc1" : 0
}
channel_to_sensor = {
0 : "he9",
1 : "he8",
2 : "he5",
3 : "he3",
4 : "he6",
5 : "he7",
6 : "he1",
7 : "he2",
8 : "he4",
9 : "mbut1",
10 : "mbut2",
11 : "mbut3",
12 : "mbut7",
13 : "mbut8",
14 : "mbut9",
15 : "enc1but",
16 : "enc1",
17 : "mbut6",
18 : "mbut5",
19 : "mbut4",
20 : "syrpot"
}
# 9 lights * 2 strips * 4 channels (RGBW) = 72 channels on syringe
# 9 wells * 10 lights * 2 strips * 4 channels = 720 channels on lab
# 792 total channels, addressable with 10 bit channels
# I considered using the command topology:
# 3 bit commands = 8 commands
# 10 bit channels = 1024 channels
# 8 bit values = 255 possible values
# So that each channel of each light could be set.
#
# 792 channels would take 792 commands, plus two latch commands to set all the colors
# that's 794 * 3 bytes * 8 bits = 19056 bits/symbols
# I'm running at 57600 the fastest reliable rate. That means I could only send
# 3 full updates of all channels per second this way, which just isn't fast enough.
# For that reason, I'm changing in favor of a larger command space, so I can do things
# in software to handle colors, and play only with the level/height from the Engine side.
#
# If I wanted to go back to the idea of addressing every channel, I suppose this could
# be made more efficient by usign an indexed color palette
#
# Instead going with:
# 3 byte commands
#
# Bit #
# | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
# Byte 1 | CMD1 CMD2 CMD3 CMD4 CMD5 CMD6 CHAN1 STATUS
# Byte 2 | CHAN2 CHAN3 CHAN4 CHAN5 VAL1 VAL2 VAL3 STATUS
# Byte 3 | VAL4 VAL5 VAL6 VAL7 VAL8 VAL9 VAL10 STATUS
# 6 bit commands - 64 commands
#Commands 0 - 32 reserved for messages going from Engine to Mega/Micro
#Commands 33 - 63 reserves for messages going from Mega/Micro to here
# 5 bit channels - 32 channels
# 10 bit values - 1024 resolution
#Channels:
#0 - Syringe
#1 - Well 1
#2 - Well 2
#3 - Well 3
#4 - Well 4
#5 - Well 5
#6 - Well 6
#7 - Well 7
#8 - Well 8
#9 - Well 9
#0 - Set R - Command #, Target a well or syringe channel, red PWM intensity
def ConstructSetRCommand(channel, value):
if channel > 31 or value > 1023:
print "Channel or value send to Arduino out of bounds, things might get wacky."
cmd = ""
# Bitwise OR Cmd #1 with the status bit set to 1 with
# the two MSB of channel shifted over
cmd += chr( 0b00000001 | (channel & 0b10000) >> 3 )
cmd += chr( ((channel & 0b01111) << 4) | ((value & 0b1110000000) >> 6 ) )
cmd += chr( (value & 0b0001111111) << 1 )
serial_write_queue.put(cmd)
#1 - Set G - Command #, Target a well or syringe channel, green PWM intensity
def ConstructSetGCommand(channel, value):
if channel > 31 or value > 1023:
print "Channel or value send to Arduino out of bounds, things might get wacky."
cmd = ""
# Bitwise OR Cmd #1 with the status bit set to 1 with
# the two MSB of channel shifted over
cmd += chr( 0b00000101 | (channel & 0b10000) >> 3 )
cmd += chr( ((channel & 0b01111) << 4) | ((value & 0b1110000000) >> 6 ) )
cmd += chr( (value & 0b0001111111) << 1 )
serial_write_queue.put(cmd)
#2 - Set B - Command #, Target a well or syringe channel, blue PWM intensity
def ConstructSetBCommand(channel, value):
if channel > 31 or value > 1023:
print "Channel or value send to Arduino out of bounds, things might get wacky."
cmd = ""
# Bitwise OR Cmd #1 with the status bit set to 1 with
# the two MSB of channel shifted over
cmd += chr( 0b00001001 | (channel & 0b10000) >> 3 )
cmd += chr( ((channel & 0b01111) << 4) | ((value & 0b1110000000) >> 6 ) )
cmd += chr( (value & 0b0001111111) << 1 )
serial_write_queue.put(cmd)
#3 - Set W - Command #, Target a well or syringe channel, white PWM intensity
def ConstructSetWCommand(channel, value):
if channel > 31 or value > 1023:
print "Channel or value send to Arduino out of bounds, things might get wacky."
cmd = ""
# Bitwise OR Cmd #1 with the status bit set to 1 with
# the two MSB of channel shifted over
cmd += chr( 0b00001101 | (channel & 0b10000) >> 3 )
cmd += chr( ((channel & 0b01111) << 4) | ((value & 0b1110000000) >> 6 ) )
cmd += chr( (value & 0b0001111111) << 1 )
serial_write_queue.put(cmd)
#4 - Latch Lab and Syringe - Command #, no op channel, no op value
def ConstructLatchCommand():
serial_write_queue.put('\x11\x00\x00')
#5 - Latch Syringe - Command #, no op channel, no op value
def ConstructSetHeightCommand(channel, value):
if channel > 31 or value > 1023:
print "Channel or value send to Arduino out of bounds, things might get wacky."
cmd = ""
# Bitwise OR Cmd #1 with the status bit set to 1 with
# the two MSB of channel shifted over
cmd += chr( 0b00010101 | (channel & 0b10000) >> 3 )
cmd += chr( ((channel & 0b01111) << 4) | ((value & 0b1110000000) >> 6 ) )
cmd += chr( (value & 0b0001111111) << 1 )
serial_write_queue.put(cmd)
def serialDataAvailable():
try:
checkPort = serial_port.inWaiting()
except IOError:
return False
return checkPort > 0
# Regarding this code being called by a timer, QTimer docs say:
# "If Qt is unable to deliver the requested number of timer clicks,
# it will silently discard some."
# Not entirely clear if this means because it can't schedule them because
# the main event loop or some other system process is going, or because
# it's trying to only run
def ReadSerialPort():
if serial_port == None:
return
while serialDataAvailable():
# A command is 3 bytes.
# Start trying to parse command if more than 2 bytes are
# available.
if serial_port.inWaiting() > 2:
#XXX for some reason when I sent no data, it found a str
# of length zero? try this and catch it?
# XXX REMOVE ALL THESE TRIES WHAT IS GOING ON?????
# SERIOUSLY WHAT
try:
byte1 = ord(serial_port.read())
except TypeError:
return
# If we're not to a byte with the status bit (rightmost)
# set to 1 keep reading bytes and essentially throwing
# them away. If this means we run out of bytes in the buffer,
# exit the loop
while (not (byte1 & 1)) and serial_port.inWaiting() > 0:
try:
byte1 = ord(serial_port.read())
except TypeError:
return
# Make sure we exited the previous loop because the
# status bit (rightmost) of the first byte was 1, and
# not just because we ran out of bytes in the serial buffer.
if byte1 & 1:
try:
byte2 = ord(serial_port.read())
byte3 = ord(serial_port.read())
except TypeError:
return
# At this point, we have a 3 byte command: start parsing
command = ( 0b11111100 & byte1 ) >> 2
channel = ( ( 0b00000010 & byte1 ) << 3 ) | ( (byte2 & 0b11110000) >> 4 )
value = ( ( 0b00001110 & byte2 ) << 6) | ( (byte3 & 0b11111110) >> 1 )
# Command 33, from Mega: Channel and sensor value
if command == 33:
print channel, value
# Normalize analog syringe pot
if channel == 20:
value /= 1023.
value = 1.0 - value
value = min(1.0, value)
value = max(0.0, value)
try:
sensor_key = channel_to_sensor[channel]
global sensors
sensors[sensor_key] = value
entry = [time.clock(), sensors.copy()]
serial_read_queue.put( entry )
# XXX DELETE ME #XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
f = open("/Users/mtf/Desktop/sensors", "w")
f.write( str(pprint.pformat(entry)) )
f.close()
except KeyError:
print "Bogus channel"
print channel
# Command 34, from Mega, Syringe connection state (0/1), channel 0
if command == 34:
print "Syringe connection message"
print channel, value
print
### XXX XXX XXX DELETE ME
def enumerate_serial_ports():
from serial.tools import list_ports
return [x[0] for x in list_ports.comports()]
ports = enumerate_serial_ports()
portname = None
for x in ports:
if x.find("usbmodem14241") != -1:
portname = x
ssss = serial.Serial( port = portname,
baudrate = 57600,
bytesize = serial.EIGHTBITS,
parity = serial.PARITY_NONE,
stopbits = serial.STOPBITS_ONE,
# timeout 0 means non-blocking mode (return immediately on read)
# If we don't get data, the timer function immediately returns to be
# called again. Since we're polling at an interval, a timeout isn't important.
# If we were in an infinite loop where the blocking timeout helped us wait
# the interval before trying to read again, we might use a timeout.
# Note that timeout = None will block / wait forever: the function would not
# return until data came in
timeout = 0.0)
SetSerialPort(ssss)
import time
import math
import LabColor
import os
import pprint
smess = []
# Arduino resets after connected, so we
# give it time to to just chill -- THIS WOULD BE MORE RELIABLE
# IF WE ACTUALLY WAITED ON A MESSAGE TO CONFIRM CONNECTION AND READINESS
time.sleep(1.5)
# Flush any data out of the Arduino
serial_port.flush()
def setSyringeToColorAndHeight(r, g, b, w, h):
ConstructSetRCommand(0, r)
ConstructSetGCommand(0, g)
ConstructSetBCommand(0, b)
ConstructSetWCommand(0, w)
WriteSerialQueue()
ConstructSetHeightCommand(0, h)
WriteSerialQueue()
ConstructLatchCommand()
WriteSerialQueue()
c1 = LabColor.Color(1.0, 0.0, 0.0)
c2 = LabColor.Color(0, 0.0, 1.0)
def WriteToMega():
n = LabColor.hsl_color_lerp(c1, c2, (math.sin(5*time.time())+1)*.5)
# Set the syringe light to yellow and half height
height = int(((math.sin(time.time()*2.0)+1)*.5) * 1000.0)
setSyringeToColorAndHeight(int(n.r*255.0), int(n.g*255.0), int(n.b*255.0), 0, height)
def ReadFromMega():
ReadSerialPort()
while True:
# Sleep a bit so we don't flood the buffers with commands
# Timer takes care of this in the real app
time.sleep(.01)
WriteToMega()
ReadFromMega()
# READ
#if serial_port != None:
# while serialDataAvailable():
# if serial_port.inWaiting() > 0:
# smess.append( serial_port.read() )
# if len(smess) > 0:
# print "".join(smess)
# smess = []
#### XXX XXX XXX DELETE ME
Mega
- Code: Select all | TOGGLE FULL SIZE
// Notes
//
// analogReference(EXTERNAL); can be used when an external ground
// is applied to the Arduino's AREF pin
//
// Serial.write can be used to directly write bytes, which
// may be better than using ASCII values with print
//
// baud is 'symbols per second'
// There can be a distinction between BIT rate and BAUD rate, but for
// asynchronous communication they are effectively the same thing.
//
// If we want to, say, send 2 byte readings of 10 bit values with sensor channel
// every 5000 microseconds (5 ms)
// That's 200 sends per second * 2 bytes * 8 bits = 3200 baud,
// so we'd select 4800 baud for the Arduino, smallest available rate >= 3200 baud
// Some inspiration from:
// http://balau82.wordpress.com/2011/03/26/capturing-an-analog-signal-with-arduino-and-python/
// ********************************************************
// LIGHTING
// ********************************************************
// MegaBrite LEDs
// ints are 16 bit (2-byte) on ATMega
// unsigned int LEDChannels[NumLEDs][3] = {0};
// Maps sensible well light indices to the way they are actually wired
// in the daisy chain:
// Sensible
// 0 1 2
// 3 4 5
// 6 7 8
//
// As wired
// 6 7 8
// 5 4 3
// 0 1 2
//
// Since we have to push through a chain,
// the last light must come first, so
// think of the indices like this and remap
// to 'sensible':
// 2 1 0
// 3 4 5
// 8 7 6
byte LEDRemap[9] = { 2, 1, 0, 3, 4, 5, 8, 7, 6 };
byte SyringeCommand[3];
int byte1, byte2, byte3, forward;
int command, channel, value;
bool syringe_connected = 0;
// ********************************************************
// END LIGHTING
// ********************************************************
// ********************************************************
// Wells and Buttons
// ********************************************************
#define SENSOR_CHANGE_THRESHOLD 0
// 9 wells, 6 arcade buttons
#define NUM_CHANNELS 15
#define WELL1_PIN 22
#define WELL1_CHANNEL 0
#define WELL2_PIN 23
#define WELL2_CHANNEL 1
#define WELL3_PIN 24
#define WELL3_CHANNEL 2
#define WELL4_PIN 25
#define WELL4_CHANNEL 3
#define WELL5_PIN 26
#define WELL5_CHANNEL 4
#define WELL6_PIN 27
#define WELL6_CHANNEL 5
#define WELL7_PIN 28
#define WELL7_CHANNEL 6
#define WELL8_PIN 29
#define WELL8_CHANNEL 7
#define WELL9_PIN 30
#define WELL9_CHANNEL 8
#define ARCADE1_PIN 31
#define ARCADE1_CHANNEL 9
#define ARCADE2_PIN 32
#define ARCADE2_CHANNEL 10
#define ARCADE3_PIN 33
#define ARCADE3_CHANNEL 11
#define ARCADE4_PIN 34
#define ARCADE4_CHANNEL 12
#define ARCADE5_PIN 35
#define ARCADE5_CHANNEL 13
#define ARCADE6_PIN 37
#define ARCADE6_CHANNEL 14
// ********************************************************
// TEMPORARY OLD SYRINGE
// ********************************************************
//#define SYRINGE_BUTTON1_PIN 8
//#define SYRINGE_BUTTON1_CHANNEL 17
//#define SYRINGE_BUTTON2_PIN 9
//#define SYRINGE_BUTTON2_CHANNEL 18
//#define SYRINGE_BUTTON3_PIN 10
//#define SYRINGE_BUTTON3_CHANNEL 19
//#define SYRINGE_POT_PIN 0
//#define SYRINGE_POT_CHANNEL 20
// ********************************************************
// END TEMPORARY OLD SYRINGE
// ********************************************************
// How often to read and send sensor values in
// microseconds
#define SENSOR_SEND_MICROS 5000
unsigned long previousMicros = 0;
unsigned long elapsedTime = 0;
// Hold previous sensor values such that we only
// send on change
int sensorValues[NUM_CHANNELS];
int sensorValue = 0;
// ********************************************************
// End Wells and Buttons
// ********************************************************
byte SensorCommand[3];
#define BAUD_RATE 57600
#define SYRINGE_SERIAL_BAUD_RATE 57600
void setup() {
// ********************************************************
// LIGHTING
// ********************************************************
// TODO REPLACE WITH NEOPIXEL SETUP
// ********************************************************
// END LIGHTING
// ********************************************************
// ********************************************************
// SENSORS
// ********************************************************
// init sensor values to bogus value
for(int i=0; i < NUM_CHANNELS; i++){
sensorValues[i] = -1;
}
// Serial1 Tx / Rx pullup to pull lines up to 5V,
// the idle state
pinMode(18, INPUT_PULLUP);
pinMode(19, INPUT_PULLUP);
// Well Reed Switches
pinMode(WELL1_PIN, INPUT_PULLUP);
pinMode(WELL2_PIN, INPUT_PULLUP);
pinMode(WELL3_PIN, INPUT_PULLUP);
pinMode(WELL4_PIN, INPUT_PULLUP);
pinMode(WELL5_PIN, INPUT_PULLUP);
pinMode(WELL6_PIN, INPUT_PULLUP);
pinMode(WELL7_PIN, INPUT_PULLUP);
pinMode(WELL8_PIN, INPUT_PULLUP);
pinMode(WELL9_PIN, INPUT_PULLUP);
// Arcade Buttons
pinMode(ARCADE1_PIN, INPUT_PULLUP);
pinMode(ARCADE2_PIN, INPUT_PULLUP);
pinMode(ARCADE3_PIN, INPUT_PULLUP);
pinMode(ARCADE4_PIN, INPUT_PULLUP);
pinMode(ARCADE5_PIN, INPUT_PULLUP);
pinMode(ARCADE6_PIN, INPUT_PULLUP);
// ********************************************************
// TEMPORARY OLD SYRINGE
// ********************************************************
//pinMode(SYRINGE_BUTTON1_PIN, INPUT_PULLUP);
//pinMode(SYRINGE_BUTTON2_PIN, INPUT_PULLUP);
//pinMode(SYRINGE_BUTTON3_PIN, INPUT_PULLUP);
// ********************************************************
// END TEMPORARY OLD SYRINGE
// ********************************************************
// ********************************************************
// END SENSORS
// ********************************************************
// ********************************************************
// SERIAL
// ********************************************************
// Serial connection to the Mac
Serial.begin(BAUD_RATE);
// Trying this blocking delay to wait for serial
// to be ready, rather than the old delay(100), based on:
// https://www.arduino.cc/en/Guide/ArduinoLeonardoMicro
while(!Serial);
// Serial conneciton to the Syringe / Arduino Micro
Serial1.begin(SYRINGE_SERIAL_BAUD_RATE);
// Trying this blocking delay to wait for serial
// to be ready, rather than the old delay(100), based on:
// https://www.arduino.cc/en/Guide/ArduinoLeonardoMicro
while(!Serial1);
//Serial.println("Started...");
// delay(100);
// ********************************************************
// END SERIAL
// ********************************************************
}
void loop() {
// Read all sensors and send out values
readSensors();
// Check for commands from Mac
checkSerialPortForCommand();
// Any commands coming in for the syringe are
// intended to broadcast sensor values, and can
// be forwarded to the Mac
forwardSerialBytesFromSyringe();
}
void readSensors() {
// Elapsed time calculation done with difference and unsigned long to avoid
// issues when micros overflows back to 0. See this tip:
// https://arduino.stackexchange.com/questions/22994/resetting-millis-and-micros
elapsedTime = micros() - previousMicros;
if( elapsedTime >= SENSOR_SEND_MICROS) {
// Arcade Buttons
digitalSensorRead(ARCADE1_PIN, ARCADE1_CHANNEL, 1);
digitalSensorRead(ARCADE2_PIN, ARCADE2_CHANNEL, 1);
digitalSensorRead(ARCADE3_PIN, ARCADE3_CHANNEL, 1);
digitalSensorRead(ARCADE4_PIN, ARCADE4_CHANNEL, 1);
digitalSensorRead(ARCADE5_PIN, ARCADE5_CHANNEL, 1);
digitalSensorRead(ARCADE6_PIN, ARCADE6_CHANNEL, 1);
// Well Reed Switches
digitalSensorRead(WELL1_PIN, WELL1_CHANNEL, 1);
digitalSensorRead(WELL2_PIN, WELL2_CHANNEL, 1);
digitalSensorRead(WELL3_PIN, WELL3_CHANNEL, 1);
digitalSensorRead(WELL4_PIN, WELL4_CHANNEL, 1);
digitalSensorRead(WELL5_PIN, WELL5_CHANNEL, 1);
digitalSensorRead(WELL6_PIN, WELL6_CHANNEL, 1);
digitalSensorRead(WELL7_PIN, WELL7_CHANNEL, 1);
digitalSensorRead(WELL8_PIN, WELL8_CHANNEL, 1);
digitalSensorRead(WELL9_PIN, WELL9_CHANNEL, 1);
// ********************************************************
// TEMPORARY OLD SYRINGE
// ********************************************************
//digitalSensorRead(SYRINGE_BUTTON1_PIN, SYRINGE_BUTTON1_CHANNEL, 1);
//digitalSensorRead(SYRINGE_BUTTON2_PIN, SYRINGE_BUTTON2_CHANNEL, 1);
//digitalSensorRead(SYRINGE_BUTTON3_PIN, SYRINGE_BUTTON3_CHANNEL, 1);
//analogSensorRead(SYRINGE_POT_PIN, SYRINGE_POT_CHANNEL);
// ********************************************************
// END TEMPORARY OLD SYRINGE
// ********************************************************
previousMicros = micros();
}
}
// Construct CMD 33, to send a channel and value to the Mac
void sendChannelAndValue(int channel, int value) {
SensorCommand[0] = 0b10000101 | (channel & 0b10000) >> 3;
SensorCommand[1] = ((channel & 0b001111) << 4) | ((value & 0b1110000000) >> 6 );
SensorCommand[2] = (value & 0b0001111111) << 1;
Serial.write(SensorCommand, 3);
}
// Construct CMD 34, which lets the Engine know the syringe is CONNECTED or DISCONNECTED
void sendSyringeConnectedMessageToEngine(int value) {
SensorCommand[0] = 0b10001001;
SensorCommand[1] = (value & 0b1110000000) >> 6 ;
SensorCommand[2] = (value & 0b0001111111) << 1;
Serial.write(SensorCommand, 3);
}
void digitalSensorRead(int pin, int channel, byte invert) {
sensorValue = digitalRead(pin);
// The second condition, checking that the old value is >= 0, makes sure we don't
// send the initial state when all of sensorValues = -1 at init
if( abs(sensorValue - sensorValues[channel]) > SENSOR_CHANGE_THRESHOLD && sensorValues[channel] >= 0 ){
if(invert == 1){
sendChannelAndValue(channel, 1-sensorValue);
}
else {
sendChannelAndValue(channel, sensorValue);
}
}
sensorValues[channel] = sensorValue;
}
void analogSensorRead(int pin, int channel) {
sensorValue = analogRead(pin);
//if( abs(sensorValue - sensorValues[channel]) > 2 && sensorValues[channel] >= 0 ){
sendChannelAndValue(channel, sensorValue);
//}
sensorValues[channel] = sensorValue;
}
void forwardSerialBytesFromSyringe() {
//if ( syringe_connected ) {
while ( Serial1.available() > 0 ) {
forward = Serial1.read();
Serial.write(forward);
}
//}
}
// Commands go a little something like this:
// Bit #
// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
// Byte 1 | CMD1 CMD2 CMD3 CMD4 CMD5 CMD6 CHAN1 STATUS
// Byte 2 | CHAN2 CHAN3 CHAN4 CHAN5 VAL1 VAL2 VAL3 STATUS
// Byte 3 | VAL4 VAL5 VAL6 VAL7 VAL8 VAL9 VAL10 STATUS
void checkSerialPortForCommand() {
while ( Serial.available() > 0 ) {
// Serial receive buffer is 64 bytes. A command is 3 bytes.
// Start trying to parse command if more than 2 bytes are
// available.
if ( Serial.available() > 2 ) {
byte1 = Serial.read();
// If we're not to a byte with the status bit (rightmost)
// set to 1 keep reading bytes and essentially throwing
// them away. If this means we run out of bytes in the buffer,
// exit the loop
while( !(byte1 & 1) && Serial.available() > 0 ) {
byte1 = Serial.read();
}
// Make sure we exited the previous loop because the
// status bit (rightmost) of the first byte was 1, and
// not just because we ran out of bytes in the serial buffer.
if ( byte1 & 1 ) {
byte2 = Serial.read();
byte3 = Serial.read();
// At this point, we have a 3 byte command: start parsing
command = ( 0b11111100 & byte1 ) >> 2;
channel = ( ( 0b00000010 & byte1 ) << 3 ) | ( (byte2 & 0b11110000) >> 4 );
value = ( ( 0b00001110 & byte2 ) << 6) | ( (byte3 & 0b11111110) >> 1 );
// Set R
if ( command == 0 ) {
// Channel 0 is syringe. Forward the command
if( channel == 0 ){
SyringeCommand[0] = byte1;
SyringeCommand[1] = byte2;
SyringeCommand[2] = byte3;
//Serial.println("Forwarding command 0 to syringe");
Serial1.write(SyringeCommand, 3);
}
}
// Set G
else if ( command == 1 ) {
// Channel 0 is syringe. Forward the command
if( channel == 0 ){
SyringeCommand[0] = byte1;
SyringeCommand[1] = byte2;
SyringeCommand[2] = byte3;
//Serial.println("Forwarding command 1 to syringe");
Serial1.write(SyringeCommand, 3);
}
}
// Set B
else if ( command == 2 ) {
// Channel 0 is syringe. Forward the command
if( channel == 0 ){
SyringeCommand[0] = byte1;
SyringeCommand[1] = byte2;
SyringeCommand[2] = byte3;
//Serial.println("Forwarding command 2 to syringe");
Serial1.write(SyringeCommand, 3);
}
}
// Set W
else if ( command == 3 ) {
// Channel 0 is syringe. Forward the command
if( channel == 0 ){
SyringeCommand[0] = byte1;
SyringeCommand[1] = byte2;
SyringeCommand[2] = byte3;
//Serial.println("Forwarding command 3 to syringe");
Serial1.write(SyringeCommand, 3);
}
}
// Latch
else if ( command == 4 ) {
// Channel 0 is syringe. Forward the command
if( channel == 0 ){
SyringeCommand[0] = byte1;
SyringeCommand[1] = byte2;
SyringeCommand[2] = byte3;
//Serial.println("Forwarding command 4 to syringe");
Serial1.write(SyringeCommand, 3);
}
}
// Set Height
else if ( command == 5 ) {
// Channel 0 is syringe. Forward the command
if( channel == 0 ){
SyringeCommand[0] = byte1;
SyringeCommand[1] = byte2;
SyringeCommand[2] = byte3;
//Serial.println("Forwarding command 5 to syringe");
Serial1.write(SyringeCommand, 3);
}
}
}
}
}
}
Micro
- Code: Select all | TOGGLE FULL SIZE
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
// Firmware for the Arduino Micro in syringe with ATmega 32U4
// If IDE upload with autoreset isn't working, try:
// https://www.arduino.cc/en/Guide/ArduinoLeonardoMicro
// These differences affect the way you use the physical reset button to perform
// an upload if the auto-reset isn't working. Press and hold the reset button
// on the Leonardo or Micro, then hit the upload button in the Arduino software.
// Only release the reset button after you see the message "Uploading..." appear
// in the software's status bar. When you do so, the bootloader will start,
// creating a new virtual (CDC) serial port on the computer. The software
// will see that port appear and perform the upload using it.
#define NUMBER_OF_SYRINGE_LEDS 9
#define LINEAR_POT A3 // Analog A3
#define SYRINGE_BUTTON_1 4 // Digital 4
#define SYRINGE_BUTTON_2 7
#define SYRINGE_BUTTON_3 12
#define SYRINGE_SERIAL_BAUD_RATE 57600
#define SENSOR_SEND_MICROS 5000
#define NEOPIXEL_PIN 10
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMBER_OF_SYRINGE_LEDS,
NEOPIXEL_PIN,
NEO_GRBW + NEO_KHZ800);
// Internal values set by forwarded commands
int R = 0;
int G = 0;
int B = 0;
int W = 0;
int height = 0;
float modr = 0;
int divr = 0;
int SYRINGE_VALUE = 0;
int SYRINGE_BUTTON_1_VALUE = 0;
int SYRINGE_BUTTON_2_VALUE = 0;
int SYRINGE_BUTTON_3_VALUE = 0;
byte Command[3];
byte clr;
int byte1, byte2, byte3;
int command, channel, value;
int sensorValue;
unsigned long previousMicros = 0;
unsigned long elapsedTime = 0;
void setup() {
// Syringe Buttons
pinMode(SYRINGE_BUTTON_1, INPUT_PULLUP);
pinMode(SYRINGE_BUTTON_2, INPUT_PULLUP);
pinMode(SYRINGE_BUTTON_3, INPUT_PULLUP);
// On the Micro, Serial1 is the hardware serial port, Serial is the virtual
// serial driver.
Serial1.begin(SYRINGE_SERIAL_BAUD_RATE);
// Trying this blocking delay to wait for serial
// to be ready, rather than the old delay(100), based on:
// https://www.arduino.cc/en/Guide/ArduinoLeonardoMicro
while(!Serial1);
// BEGIN LIGHTING
strip.begin();
// Initialize all pixels to 'off'
strip.show();
}
void loop() {
//readSensors();
//delay(100);
checkSerialPortForCommand();
}
void readSensors() {
// Elapsed time calculation done with difference and unsigned long to avoid
// issues when micros overflows back to 0. See this tip:
// https://arduino.stackexchange.com/questions/22994/resetting-millis-and-micros
elapsedTime = micros() - previousMicros;
if( elapsedTime >= SENSOR_SEND_MICROS) {
// See OXullo Intersecans' answer on Quora
// http://www.quora.com/Why-is-a-little-delay-needed-after-analogRead-in-Arduino
// For explanation of why we're calling analogRead twice and ignoring the
// first result. Effectively, the ADC is connceted to the analog pins via a MUX
// with with a cap. If the cap isn't given time to discharge, it can influence
// the ADC readings
// SLIDE LINEAR POT
analogRead(LINEAR_POT);
sensorValue = analogRead(LINEAR_POT);
SYRINGE_VALUE = sensorValue;
//sendChannelAndValue(22, sensorValue);
// SYRINGE BUTTONS
sensorValue = digitalRead( SYRINGE_BUTTON_1 );
SYRINGE_BUTTON_1_VALUE = sensorValue;
//sendChannelAndValue(19, !sensorValue);
sensorValue = digitalRead( SYRINGE_BUTTON_2 );
SYRINGE_BUTTON_2_VALUE = sensorValue;
//sendChannelAndValue(20, !sensorValue);
sensorValue = digitalRead( SYRINGE_BUTTON_3 );
SYRINGE_BUTTON_3_VALUE = sensorValue;
//sendChannelAndValue(21, !sensorValue);
previousMicros = micros();
}
}
void latchLight() {
// Height is 10 bit int (0 - 1023)
// This modr / divr business is used to fade the intensity
// of the leading edge light to reduce stepping. It's effectively
// a 1 neopixel wide filter
modr = (height % 102) / 102.0f;
divr = height / 102;
for(uint16_t i=0; i<strip.numPixels(); i++) {
if( i < divr ) {
strip.setPixelColor(i, strip.Color(R,G,B,W));
}
else {
if ( i == divr ){
strip.setPixelColor(i, strip.Color(R*modr, G*modr, B*modr, W*modr));
}
else {
strip.setPixelColor(i, strip.Color(0,0,0,0));
}
}
}
strip.show();
}
// Construct CMD 4, to send a channel and value to the lab, then the Mac
void sendChannelAndValue(int channel, int value) {
Command[0] = 0b00100001 | (channel & 0b110000) >> 3;
Command[1] = ((channel & 0b001111) << 4) | ((value & 0b1110000000) >> 6 );
Command[2] = (value & 0b0001111111) << 1;
//Serial1.write(Command, 3);
}
// Commands go a little something like this:
// Bit #
// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
// Byte 1 | CMD1 CMD2 CMD3 CMD4 CMD5 CMD6 CHAN1 STATUS
// Byte 2 | CHAN2 CHAN3 CHAN4 CHAN5 VAL1 VAL2 VAL3 STATUS
// Byte 3 | VAL4 VAL5 VAL6 VAL7 VAL8 VAL9 VAL10 STATUS
void checkSerialPortForCommand() {
while ( Serial1.available() > 0 ) {
// Serial receive buffer is 64 bytes. A command is 3 bytes.
// Start trying to parse command if more than 2 bytes are
// available.
if ( Serial1.available() > 2 ) {
byte1 = Serial1.read();
// If we're not to a byte with the status bit (rightmost)
// set to 1 keep reading bytes and essentially throwing
// them away. If this means we run out of bytes in the buffer,
// exit the loop
while( !(byte1 & 1) && Serial1.available() > 0 ) {
byte1 = Serial1.read();
}
// Make sure we exited the previous loop because the
// status bit (rightmost) of the first byte was 1, and
// not just because we ran out of bytes in the serial buffer.
if ( byte1 & 1 ) {
byte2 = Serial1.read();
byte3 = Serial1.read();
// At this point, we have a 3 byte command: start parsing
command = ( 0b11111100 & byte1 ) >> 2;
channel = ( ( 0b00000010 & byte1 ) << 3 ) | ( (byte2 & 0b11110000) >> 4 );
value = ( ( 0b00001110 & byte2 ) << 6) | ( (byte3 & 0b11111110) >> 1 );
// Set R channel
if ( command == 0 ) {
// Channel should always be 0 if this was forwarded here, but check anyway
if( channel == 0) {
R = value;
}
}
// Set G channel
else if ( command == 1 ) {
// Channel should always be 0 if this was forwarded here, but check anyway
if( channel == 0) {
G = value;
}
}
// Set B channel
else if ( command == 2 ) {
// Channel should always be 0 if this was forwarded here, but check anyway
if( channel == 0) {
B = value;
}
}
// Set W channel
else if ( command == 3 ) {
// Channel should always be 0 if this was forwarded here, but check anyway
if( channel == 0) {
W = value;
}
}
// Latch
else if ( command == 4 ) {
latchLight();
}
// Set height
else if ( command == 5 ) {
height = value;
}
}
}
}
}
Here are photos of the setup as wired for now:





free image cdn