Permalink
Browse files

Initial commit.

  • Loading branch information...
0 parents commit 3e5b371a7522be33a6dbfa369ab86a325df9876b @rcaputo committed Feb 14, 2011
Showing with 1,434 additions and 0 deletions.
  1. +150 −0 Firmata.pm
  2. +14 −0 README
  3. +439 −0 UNO-Firmata.cpp
  4. +600 −0 UNO-Firmata.h
  5. +203 −0 firmata-midi-protocol.txt
  6. +28 −0 test.pl
150 Firmata.pm
@@ -0,0 +1,150 @@
+package Firmata;
+
+use warnings;
+use strict;
+
+use Moose;
+extends 'Reflex::Base';
+
+has handle => ( isa => 'FileHandle', is => 'rw' );
+
+with 'Reflex::Role::Streaming' => { handle => 'handle' };
+
+has buffer => ( isa => 'Str', is => 'rw', default => '' );
+
+sub on_handle_data {
+ my ($self, $args) = @_;
+
+ my $data = $args->{data};
+ my $buffer = $self->buffer() . $data;
+
+ # TODO - Cheezy, slow. Do better.
+
+ while (1) {
+
+ if ($buffer =~ s/^\xF9(..)//s) {
+ my ($maj, $min) = unpack("CC", $1);
+ print "<-- version $maj.$min\n";
+ next;
+ }
+
+ # SysEx? Ogods!
+
+ if ($buffer =~ s/^\xF0\x79(..)(.*?)\xF7//s) {
+ my ($maj, $min) = unpack("CC", $1);
+
+ # TODO - See String SysEx discussion.
+ my $string = $2;
+ $string =~ tr[\x00][]d;
+ print "<-- version $maj.$min ($string)\n";
+
+ # Capabilities.
+ $self->put_handle("\xF0\x6B\xF7");
+
+ next;
+ }
+
+ # String SysEx.
+
+ if ($buffer =~ s/^\xF0\x71(.*?)\xF7//s) {
+ my $string = $1;
+
+ # TODO - Actually the MSB of each string octet is in one of the
+ # bytes. We should convert these 16bit characters to 14bit
+ # before displaying. Ripping out the \x00s is just an expedient
+ # hack.
+
+ $string =~ tr[\x00][]d;
+ print "<-- string: '$string'\n";
+ next;
+ }
+
+ # Capability SysEx.
+
+ if ($buffer =~ s/^\xF0\x6C(.*?)\xF7//s) {
+ my $raw = $1;
+ my @pins = ($raw =~ m/([^\x7F]*)\x7F/g);
+
+ my $pin = 0;
+ foreach my $modes (@pins) {
+ foreach my $mode ($modes =~ m/(..)/sg) {
+ my ($m, $r) = unpack "CC", $mode;
+
+ my $text = [
+ qw(
+ input
+ output
+ analog
+ pwm
+ servo
+ shift
+ i2c
+ )
+ ]->[$m];
+
+ print "<-- pin $pin can $text ($r)\n";
+ }
+
+ $pin++;
+ }
+
+ next;
+ }
+
+ # Unknown SysEx.
+
+ if ($buffer =~ s/^\xF0(.)(.*?)\xF7//s) {
+ my $cmd = ord($1);
+
+ my $hex = $2;
+ $hex =~ s/([^ -~])/sprintf("<%02.2x>", ord($1))/seg;
+
+ printf "<-- sysex %02.2X '%s'\n", $cmd, $hex;
+ next;
+ }
+
+ if ($buffer =~ s/^([\xE0-\xEF])(..)//s) {
+ my $port = ord($1) & 0x0F;
+ my ($lsb, $msb) = unpack "CC", $2;
+ my $value = (($msb & 0x7F) << 7) | ($lsb & 0x7F);
+ print "<-- a($port) = $value\n";
+ next;
+ }
+
+ if ($buffer =~ s/^([\x90-\x9F])(..)//s) {
+ my $port = ord($1) & 0x0F;
+ my ($lsb, $msb) = unpack "CC", $2;
+ my $value = (($msb & 0x7F) << 7) | ($lsb & 0x7F);
+ print "<-- d($port) = $value\n";
+ next;
+ }
+
+ if ($buffer =~ s/^([\xC0-\xCF])(..)//s) {
+ my $port = ord($1) & 0x0F;
+ my ($lsb, $msb) = unpack "CC", $2;
+ my $value = $lsb & 0x01;
+ print "<-- a($port) set $value\n";
+ next;
+ }
+
+ if ($buffer =~ s/^([\xD0-\xDF])(..)//s) {
+ my $port = ord($1) & 0x0F;
+ my ($lsb, $msb) = unpack "CC", $2;
+ my $value = $lsb & 0x01;
+ print "<-- d($port) set $value\n";
+ next;
+ }
+
+ last;
+ }
+
+ $self->buffer($buffer);
+
+ if (length $buffer) {
+ my $hex = $buffer;
+ $hex =~ s/(.)/sprintf("<%02.2x>", ord($1))/seg;
+ print "<-- raw: $hex\n";
+ }
+}
+
+1;
14 README
@@ -0,0 +1,14 @@
+Reflexive::Firmata - an Asynchronous OO interface to serial Firmata devices
+
+Current status: Work in progress. Committed to github and Gitorious
+to facilitate collaborative hacking.
+
+Community: irc.perl.org #reflex (for now)
+
+License: Unless otherwise specified, Same Terms as Perl Itself. This
+may change.
+
+The repository contains some other-copyright files that are being used
+for reference. Their code won't be incorporated in the released
+distribution, and the files will be removed from the repository once
+the Perl implementation is reasonably complete.
439 UNO-Firmata.cpp
@@ -0,0 +1,439 @@
+/*
+ Firmata.cpp - Firmata library
+ Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See file LICENSE.txt for further informations on licensing terms.
+*/
+
+//******************************************************************************
+//* Includes
+//******************************************************************************
+
+#include "WProgram.h"
+#include "HardwareSerial.h"
+#include "Firmata.h"
+
+extern "C" {
+#include <string.h>
+#include <stdlib.h>
+}
+
+//******************************************************************************
+//* Support Functions
+//******************************************************************************
+
+void sendValueAsTwo7bitBytes(int value)
+{
+ Serial.print(value & B01111111, BYTE); // LSB
+ Serial.print(value >> 7 & B01111111, BYTE); // MSB
+}
+
+void startSysex(void)
+{
+ Serial.print(START_SYSEX, BYTE);
+}
+
+void endSysex(void)
+{
+ Serial.print(END_SYSEX, BYTE);
+}
+
+//******************************************************************************
+//* Constructors
+//******************************************************************************
+
+FirmataClass::FirmataClass(void)
+{
+ firmwareVersionCount = 0;
+ systemReset();
+}
+
+//******************************************************************************
+//* Public Methods
+//******************************************************************************
+
+/* begin method for overriding default serial bitrate */
+void FirmataClass::begin(void)
+{
+ begin(57600);
+}
+
+/* begin method for overriding default serial bitrate */
+void FirmataClass::begin(long speed)
+{
+#if defined(__AVR_ATmega128__) // Wiring
+ Serial.begin((uint32_t)speed);
+#else
+ Serial.begin(speed);
+#endif
+ blinkVersion();
+ delay(300);
+ printVersion();
+ printFirmwareVersion();
+}
+
+// output the protocol version message to the serial port
+void FirmataClass::printVersion(void) {
+ Serial.print(REPORT_VERSION, BYTE);
+ Serial.print(FIRMATA_MAJOR_VERSION, BYTE);
+ Serial.print(FIRMATA_MINOR_VERSION, BYTE);
+}
+
+void FirmataClass::blinkVersion(void)
+{
+ // flash the pin with the protocol version
+ pinMode(VERSION_BLINK_PIN,OUTPUT);
+ pin13strobe(FIRMATA_MAJOR_VERSION, 200, 400);
+ delay(300);
+ pin13strobe(2,1,4); // separator, a quick burst
+ delay(300);
+ pin13strobe(FIRMATA_MINOR_VERSION, 200, 400);
+}
+
+void FirmataClass::printFirmwareVersion(void)
+{
+ byte i;
+
+ if(firmwareVersionCount) { // make sure that the name has been set before reporting
+ startSysex();
+ Serial.print(REPORT_FIRMWARE, BYTE);
+ Serial.print(firmwareVersionVector[0]); // major version number
+ Serial.print(firmwareVersionVector[1]); // minor version number
+ for(i=2; i<firmwareVersionCount; ++i) {
+ sendValueAsTwo7bitBytes(firmwareVersionVector[i]);
+ }
+ endSysex();
+ }
+}
+
+void FirmataClass::setFirmwareNameAndVersion(const char *name, byte major, byte minor)
+{
+ const char *filename;
+ char *extension;
+
+ // parse out ".cpp" and "applet/" that comes from using __FILE__
+ extension = strstr(name, ".cpp");
+ filename = strrchr(name, '/') + 1; //points to slash, +1 gets to start of filename
+ // add two bytes for version numbers
+ if(extension && filename) {
+ firmwareVersionCount = extension - filename + 2;
+ } else {
+ firmwareVersionCount = strlen(name) + 2;
+ filename = name;
+ }
+ firmwareVersionVector = (byte *) malloc(firmwareVersionCount);
+ firmwareVersionVector[firmwareVersionCount] = 0;
+ firmwareVersionVector[0] = major;
+ firmwareVersionVector[1] = minor;
+ strncpy((char*)firmwareVersionVector + 2, filename, firmwareVersionCount - 2);
+ // alas, no snprintf on Arduino
+ // snprintf(firmwareVersionVector, MAX_DATA_BYTES, "%c%c%s",
+ // (char)major, (char)minor, firmwareVersionVector);
+}
+
+//------------------------------------------------------------------------------
+// Serial Receive Handling
+
+int FirmataClass::available(void)
+{
+ return Serial.available();
+}
+
+
+void FirmataClass::processSysexMessage(void)
+{
+ switch(storedInputData[0]) { //first byte in buffer is command
+ case REPORT_FIRMWARE:
+ printFirmwareVersion();
+ break;
+ case STRING_DATA:
+ if(currentStringCallback) {
+ byte bufferLength = (sysexBytesRead - 1) / 2;
+ char *buffer = (char*)malloc(bufferLength * sizeof(char));
+ byte i = 1;
+ byte j = 0;
+ while(j < bufferLength) {
+ buffer[j] = (char)storedInputData[i];
+ i++;
+ buffer[j] += (char)(storedInputData[i] << 7);
+ i++;
+ j++;
+ }
+ (*currentStringCallback)(buffer);
+ }
+ break;
+ default:
+ if(currentSysexCallback)
+ (*currentSysexCallback)(storedInputData[0], sysexBytesRead - 1, storedInputData + 1);
+ }
+}
+
+void FirmataClass::processInput(void)
+{
+ int inputData = Serial.read(); // this is 'int' to handle -1 when no data
+ int command;
+
+ // TODO make sure it handles -1 properly
+
+ if (parsingSysex) {
+ if(inputData == END_SYSEX) {
+ //stop sysex byte
+ parsingSysex = false;
+ //fire off handler function
+ processSysexMessage();
+ } else {
+ //normal data byte - add to buffer
+ storedInputData[sysexBytesRead] = inputData;
+ sysexBytesRead++;
+ }
+ } else if( (waitForData > 0) && (inputData < 128) ) {
+ waitForData--;
+ storedInputData[waitForData] = inputData;
+ if( (waitForData==0) && executeMultiByteCommand ) { // got the whole message
+ switch(executeMultiByteCommand) {
+ case ANALOG_MESSAGE:
+ if(currentAnalogCallback) {
+ (*currentAnalogCallback)(multiByteChannel,
+ (storedInputData[0] << 7)
+ + storedInputData[1]);
+ }
+ break;
+ case DIGITAL_MESSAGE:
+ if(currentDigitalCallback) {
+ (*currentDigitalCallback)(multiByteChannel,
+ (storedInputData[0] << 7)
+ + storedInputData[1]);
+ }
+ break;
+ case SET_PIN_MODE:
+ if(currentPinModeCallback)
+ (*currentPinModeCallback)(storedInputData[1], storedInputData[0]);
+ break;
+ case REPORT_ANALOG:
+ if(currentReportAnalogCallback)
+ (*currentReportAnalogCallback)(multiByteChannel,storedInputData[0]);
+ break;
+ case REPORT_DIGITAL:
+ if(currentReportDigitalCallback)
+ (*currentReportDigitalCallback)(multiByteChannel,storedInputData[0]);
+ break;
+ }
+ executeMultiByteCommand = 0;
+ }
+ } else {
+ // remove channel info from command byte if less than 0xF0
+ if(inputData < 0xF0) {
+ command = inputData & 0xF0;
+ multiByteChannel = inputData & 0x0F;
+ } else {
+ command = inputData;
+ // commands in the 0xF* range don't use channel data
+ }
+ switch (command) {
+ case ANALOG_MESSAGE:
+ case DIGITAL_MESSAGE:
+ case SET_PIN_MODE:
+ waitForData = 2; // two data bytes needed
+ executeMultiByteCommand = command;
+ break;
+ case REPORT_ANALOG:
+ case REPORT_DIGITAL:
+ waitForData = 1; // two data bytes needed
+ executeMultiByteCommand = command;
+ break;
+ case START_SYSEX:
+ parsingSysex = true;
+ sysexBytesRead = 0;
+ break;
+ case SYSTEM_RESET:
+ systemReset();
+ break;
+ case REPORT_VERSION:
+ Firmata.printVersion();
+ break;
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// Serial Send Handling
+
+// send an analog message
+void FirmataClass::sendAnalog(byte pin, int value)
+{
+ // pin can only be 0-15, so chop higher bits
+ Serial.print(ANALOG_MESSAGE | (pin & 0xF), BYTE);
+ sendValueAsTwo7bitBytes(value);
+}
+
+// send a single digital pin in a digital message
+void FirmataClass::sendDigital(byte pin, int value)
+{
+ /* TODO add single pin digital messages to the protocol, this needs to
+ * track the last digital data sent so that it can be sure to change just
+ * one bit in the packet. This is complicated by the fact that the
+ * numbering of the pins will probably differ on Arduino, Wiring, and
+ * other boards. The DIGITAL_MESSAGE sends 14 bits at a time, but it is
+ * probably easier to send 8 bit ports for any board with more than 14
+ * digital pins.
+ */
+
+ // TODO: the digital message should not be sent on the serial port every
+ // time sendDigital() is called. Instead, it should add it to an int
+ // which will be sent on a schedule. If a pin changes more than once
+ // before the digital message is sent on the serial port, it should send a
+ // digital message for each change.
+
+ // if(value == 0)
+ // sendDigitalPortPair();
+}
+
+
+// send 14-bits in a single digital message (protocol v1)
+// send an 8-bit port in a single digital message (protocol v2)
+void FirmataClass::sendDigitalPort(byte portNumber, int portData)
+{
+ Serial.print(DIGITAL_MESSAGE | (portNumber & 0xF),BYTE);
+ Serial.print((byte)portData % 128, BYTE); // Tx bits 0-6
+ Serial.print(portData >> 7, BYTE); // Tx bits 7-13
+}
+
+
+void FirmataClass::sendSysex(byte command, byte bytec, byte* bytev)
+{
+ byte i;
+ startSysex();
+ Serial.print(command, BYTE);
+ for(i=0; i<bytec; i++) {
+ sendValueAsTwo7bitBytes(bytev[i]);
+ }
+ endSysex();
+}
+
+void FirmataClass::sendString(byte command, const char* string)
+{
+ sendSysex(command, strlen(string), (byte *)string);
+}
+
+
+// send a string as the protocol string type
+void FirmataClass::sendString(const char* string)
+{
+ sendString(STRING_DATA, string);
+}
+
+
+// Internal Actions/////////////////////////////////////////////////////////////
+
+// generic callbacks
+void FirmataClass::attach(byte command, callbackFunction newFunction)
+{
+ switch(command) {
+ case ANALOG_MESSAGE: currentAnalogCallback = newFunction; break;
+ case DIGITAL_MESSAGE: currentDigitalCallback = newFunction; break;
+ case REPORT_ANALOG: currentReportAnalogCallback = newFunction; break;
+ case REPORT_DIGITAL: currentReportDigitalCallback = newFunction; break;
+ case SET_PIN_MODE: currentPinModeCallback = newFunction; break;
+ }
+}
+
+void FirmataClass::attach(byte command, systemResetCallbackFunction newFunction)
+{
+ switch(command) {
+ case SYSTEM_RESET: currentSystemResetCallback = newFunction; break;
+ }
+}
+
+void FirmataClass::attach(byte command, stringCallbackFunction newFunction)
+{
+ switch(command) {
+ case STRING_DATA: currentStringCallback = newFunction; break;
+ }
+}
+
+void FirmataClass::attach(byte command, sysexCallbackFunction newFunction)
+{
+ currentSysexCallback = newFunction;
+}
+
+void FirmataClass::detach(byte command)
+{
+ switch(command) {
+ case SYSTEM_RESET: currentSystemResetCallback = NULL; break;
+ case STRING_DATA: currentStringCallback = NULL; break;
+ case START_SYSEX: currentSysexCallback = NULL; break;
+ default:
+ attach(command, (callbackFunction)NULL);
+ }
+}
+
+// sysex callbacks
+/*
+ * this is too complicated for analogReceive, but maybe for Sysex?
+ void FirmataClass::attachSysex(sysexFunction newFunction)
+ {
+ byte i;
+ byte tmpCount = analogReceiveFunctionCount;
+ analogReceiveFunction* tmpArray = analogReceiveFunctionArray;
+ analogReceiveFunctionCount++;
+ analogReceiveFunctionArray = (analogReceiveFunction*) calloc(analogReceiveFunctionCount, sizeof(analogReceiveFunction));
+ for(i = 0; i < tmpCount; i++) {
+ analogReceiveFunctionArray[i] = tmpArray[i];
+ }
+ analogReceiveFunctionArray[tmpCount] = newFunction;
+ free(tmpArray);
+ }
+*/
+
+//******************************************************************************
+//* Private Methods
+//******************************************************************************
+
+
+
+// resets the system state upon a SYSTEM_RESET message from the host software
+void FirmataClass::systemReset(void)
+{
+ byte i;
+
+ waitForData = 0; // this flag says the next serial input will be data
+ executeMultiByteCommand = 0; // execute this after getting multi-byte data
+ multiByteChannel = 0; // channel data for multiByteCommands
+
+
+ for(i=0; i<MAX_DATA_BYTES; i++) {
+ storedInputData[i] = 0;
+ }
+
+ parsingSysex = false;
+ sysexBytesRead = 0;
+
+ if(currentSystemResetCallback)
+ (*currentSystemResetCallback)();
+
+ //flush(); //TODO uncomment when Firmata is a subclass of HardwareSerial
+}
+
+
+
+// =============================================================================
+// used for flashing the pin for the version number
+void FirmataClass::pin13strobe(int count, int onInterval, int offInterval)
+{
+ byte i;
+ pinMode(VERSION_BLINK_PIN, OUTPUT);
+ for(i=0; i<count; i++) {
+ delay(offInterval);
+ digitalWrite(VERSION_BLINK_PIN, HIGH);
+ delay(onInterval);
+ digitalWrite(VERSION_BLINK_PIN, LOW);
+ }
+}
+
+// make one instance for the user to use
+FirmataClass Firmata;
600 UNO-Firmata.h
@@ -0,0 +1,600 @@
+/*
+ This introduces modifications on the normal Firmata made for Arduino so that the LED
+ blinks until receiving the first command over serial.
+
+ Copyright (C) 2010 David Cuartielles. All rights reserved.
+
+ based at 99.9% on Firmata by HC Steiner according to the following license terms:
+
+ Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See file LICENSE.txt for further informations on licensing terms.
+
+ formatted using the GNU C formatting and indenting
+*/
+
+/*
+ * TODO: use Program Control to load stored profiles from EEPROM
+ */
+
+#include <Servo.h>
+#include <Firmata.h>
+
+/*==============================================================================
+ * GLOBAL VARIABLES
+ *============================================================================*/
+
+/* has the command arrived? */
+boolean firstCommand = false;
+int dataOnSerial = 0;
+boolean statusLed = false;
+
+/* analog inputs */
+int analogInputsToReport = 0; // bitwise array to store pin reporting
+
+/* digital input ports */
+byte reportPINs[TOTAL_PORTS]; // 1 = report this port, 0 = silence
+byte previousPINs[TOTAL_PORTS]; // previous 8 bits sent
+
+/* pins configuration */
+byte pinConfig[TOTAL_PINS]; // configuration of every pin
+byte portConfigInputs[TOTAL_PORTS]; // each bit: 1 = pin in INPUT, 0 = anything else
+int pinState[TOTAL_PINS]; // any value that has been written
+
+/* timer variables */
+unsigned long currentMillis; // store the current value from millis()
+unsigned long previousMillis; // for comparison with currentMillis
+int samplingInterval = 19; // how often to run the main loop (in ms)
+unsigned long toggleMillis;
+
+Servo servos[MAX_SERVOS];
+
+/*==============================================================================
+ * FUNCTIONS
+ *============================================================================*/
+
+void toggleLed()
+{
+ if (millis() - toggleMillis > 500) {
+ statusLed = !statusLed;
+ digitalWrite(13, statusLed);
+ toggleMillis = millis();
+ }
+}
+
+void outputPort(byte portNumber, byte portValue, byte forceSend)
+{
+ // pins not configured as INPUT are cleared to zeros
+ portValue = portValue & portConfigInputs[portNumber];
+ // only send if the value is different than previously sent
+ if(forceSend || previousPINs[portNumber] != portValue) {
+ Firmata.sendDigitalPort(portNumber, portValue);
+ previousPINs[portNumber] = portValue;
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * check all the active digital inputs for change of state, then add any events
+ * to the Serial output queue using Serial.print() */
+void checkDigitalInputs(void)
+{
+ /* Using non-looping code allows constants to be given to readPort().
+ * The compiler will apply substantial optimizations if the inputs
+ * to readPort() are compile-time constants. */
+ if (TOTAL_PORTS > 0 && reportPINs[0]) outputPort(0, readPort(0, portConfigInputs[0]), false);
+ if (TOTAL_PORTS > 1 && reportPINs[1]) outputPort(1, readPort(1, portConfigInputs[1]), false);
+ if (TOTAL_PORTS > 2 && reportPINs[2]) outputPort(2, readPort(2, portConfigInputs[2]), false);
+ if (TOTAL_PORTS > 3 && reportPINs[3]) outputPort(3, readPort(3, portConfigInputs[3]), false);
+ if (TOTAL_PORTS > 4 && reportPINs[4]) outputPort(4, readPort(4, portConfigInputs[4]), false);
+ if (TOTAL_PORTS > 5 && reportPINs[5]) outputPort(5, readPort(5, portConfigInputs[5]), false);
+ if (TOTAL_PORTS > 6 && reportPINs[6]) outputPort(6, readPort(6, portConfigInputs[6]), false);
+ if (TOTAL_PORTS > 7 && reportPINs[7]) outputPort(7, readPort(7, portConfigInputs[7]), false);
+ if (TOTAL_PORTS > 8 && reportPINs[8]) outputPort(8, readPort(8, portConfigInputs[8]), false);
+ if (TOTAL_PORTS > 9 && reportPINs[9]) outputPort(9, readPort(9, portConfigInputs[9]), false);
+ if (TOTAL_PORTS > 10 && reportPINs[10]) outputPort(10, readPort(10, portConfigInputs[10]), false);
+ if (TOTAL_PORTS > 11 && reportPINs[11]) outputPort(11, readPort(11, portConfigInputs[11]), false);
+ if (TOTAL_PORTS > 12 && reportPINs[12]) outputPort(12, readPort(12, portConfigInputs[12]), false);
+ if (TOTAL_PORTS > 13 && reportPINs[13]) outputPort(13, readPort(13, portConfigInputs[13]), false);
+ if (TOTAL_PORTS > 14 && reportPINs[14]) outputPort(14, readPort(14, portConfigInputs[14]), false);
+ if (TOTAL_PORTS > 15 && reportPINs[15]) outputPort(15, readPort(15, portConfigInputs[15]), false);
+}
+
+// -----------------------------------------------------------------------------
+/* sets the pin mode to the correct state and sets the relevant bits in the
+ * two bit-arrays that track Digital I/O and PWM status
+ */
+void setPinModeCallback(byte pin, int mode)
+{
+ if (IS_PIN_SERVO(pin) && mode != SERVO && servos[PIN_TO_SERVO(pin)].attached()) {
+ servos[PIN_TO_SERVO(pin)].detach();
+ }
+ if (IS_PIN_ANALOG(pin)) {
+ reportAnalogCallback(PIN_TO_ANALOG(pin), mode == ANALOG ? 1 : 0); // turn on/off reporting
+ }
+ if (IS_PIN_DIGITAL(pin)) {
+ if (mode == INPUT) {
+ portConfigInputs[pin/8] |= (1 << (pin & 7));
+ } else {
+ portConfigInputs[pin/8] &= ~(1 << (pin & 7));
+ }
+ }
+ pinState[pin] = 0;
+ switch(mode) {
+ case ANALOG:
+ if (IS_PIN_ANALOG(pin)) {
+ if (IS_PIN_DIGITAL(pin)) {
+ pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver
+ digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
+ }
+ pinConfig[pin] = ANALOG;
+ }
+ break;
+ case INPUT:
+ if (IS_PIN_DIGITAL(pin)) {
+ pinMode(PIN_TO_DIGITAL(pin), INPUT); // disable output driver
+ digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable internal pull-ups
+ pinConfig[pin] = INPUT;
+ }
+ break;
+ case OUTPUT:
+ if (IS_PIN_DIGITAL(pin)) {
+ digitalWrite(PIN_TO_DIGITAL(pin), LOW); // disable PWM
+ pinMode(PIN_TO_DIGITAL(pin), OUTPUT);
+ pinConfig[pin] = OUTPUT;
+ }
+ break;
+ case PWM:
+ if (IS_PIN_PWM(pin)) {
+ pinMode(PIN_TO_PWM(pin), OUTPUT);
+ analogWrite(PIN_TO_PWM(pin), 0);
+ pinConfig[pin] = PWM;
+ }
+ break;
+ case SERVO:
+ if (IS_PIN_SERVO(pin)) {
+ pinConfig[pin] = SERVO;
+ if (!servos[PIN_TO_SERVO(pin)].attached()) {
+ servos[PIN_TO_SERVO(pin)].attach(PIN_TO_DIGITAL(pin));
+ } else {
+ Firmata.sendString("Servo only on pins from 2 to 13");
+ }
+ }
+ break;
+ case I2C:
+ pinConfig[pin] = mode;
+ Firmata.sendString("I2C mode not yet supported");
+ break;
+ default:
+ Firmata.sendString("Unknown pin mode"); // TODO: put error msgs in EEPROM
+ }
+ // TODO: save status to EEPROM here, if changed
+}
+
+void analogWriteCallback(byte pin, int value)
+{
+ if (pin < TOTAL_PINS) {
+ switch(pinConfig[pin]) {
+ case SERVO:
+ if (IS_PIN_SERVO(pin))
+ servos[PIN_TO_SERVO(pin)].write(value);
+ pinState[pin] = value;
+ break;
+ case PWM:
+ if (IS_PIN_PWM(pin))
+ analogWrite(PIN_TO_PWM(pin), value);
+ pinState[pin] = value;
+ break;
+ }
+ }
+}
+
+void digitalWriteCallback(byte port, int value)
+{
+ byte pin, lastPin, mask=1, pinWriteMask=0;
+
+ if (port < TOTAL_PORTS) {
+ // create a mask of the pins on this port that are writable.
+ lastPin = port*8+8;
+ if (lastPin > TOTAL_PINS) lastPin = TOTAL_PINS;
+ for (pin=port*8; pin < lastPin; pin++) {
+ // do not disturb non-digital pins (eg, Rx & Tx)
+ if (IS_PIN_DIGITAL(pin)) {
+ // only write to OUTPUT and INPUT (enables pullup)
+ // do not touch pins in PWM, ANALOG, SERVO or other modes
+ if (pinConfig[pin] == OUTPUT || pinConfig[pin] == INPUT) {
+ pinWriteMask |= mask;
+ pinState[pin] = ((byte)value & mask) ? 1 : 0;
+ }
+ }
+ mask = mask << 1;
+ }
+ writePort(port, (byte)value, pinWriteMask);
+ }
+}
+
+
+// -----------------------------------------------------------------------------
+/* sets bits in a bit array (int) to toggle the reporting of the analogIns
+ */
+//void FirmataClass::setAnalogPinReporting(byte pin, byte state) {
+//}
+void reportAnalogCallback(byte analogPin, int value)
+{
+ if (analogPin < TOTAL_ANALOG_PINS) {
+ if(value == 0) {
+ analogInputsToReport = analogInputsToReport &~ (1 << analogPin);
+ } else {
+ analogInputsToReport = analogInputsToReport | (1 << analogPin);
+ }
+ }
+ // TODO: save status to EEPROM here, if changed
+}
+
+void reportDigitalCallback(byte port, int value)
+{
+ if (port < TOTAL_PORTS) {
+ reportPINs[port] = (byte)value;
+ }
+ // do not disable analog reporting on these 8 pins, to allow some
+ // pins used for digital, others analog. Instead, allow both types
+ // of reporting to be enabled, but check if the pin is configured
+ // as analog when sampling the analog inputs. Likewise, while
+ // scanning digital pins, portConfigInputs will mask off values from any
+ // pins configured as analog
+}
+
+/*==============================================================================
+ * SYSEX-BASED commands
+ *============================================================================*/
+
+void sysexCallback(byte command, byte argc, byte *argv)
+{
+ switch(command) {
+ case SERVO_CONFIG:
+ if(argc > 4) {
+ // these vars are here for clarity, they'll optimized away by the compiler
+ byte pin = argv[0];
+ int minPulse = argv[1] + (argv[2] << 7);
+ int maxPulse = argv[3] + (argv[4] << 7);
+
+ if (IS_PIN_SERVO(pin)) {
+ // servos are pins from 2 to 13, so offset for array
+ if (servos[PIN_TO_SERVO(pin)].attached())
+ servos[PIN_TO_SERVO(pin)].detach();
+ servos[PIN_TO_SERVO(pin)].attach(PIN_TO_DIGITAL(pin), minPulse, maxPulse);
+ setPinModeCallback(pin, SERVO);
+ }
+ }
+ break;
+ case SAMPLING_INTERVAL:
+ if (argc > 1)
+ samplingInterval = argv[0] + (argv[1] << 7);
+ else
+ Firmata.sendString("Not enough data");
+ break;
+ case EXTENDED_ANALOG:
+ if (argc > 1) {
+ int val = argv[1];
+ if (argc > 2) val |= (argv[2] << 7);
+ if (argc > 3) val |= (argv[3] << 14);
+ analogWriteCallback(argv[0], val);
+ }
+ break;
+ case CAPABILITY_QUERY:
+ Serial.write(START_SYSEX);
+ Serial.write(CAPABILITY_RESPONSE);
+ for (byte pin=0; pin < TOTAL_PINS; pin++) {
+ if (IS_PIN_DIGITAL(pin)) {
+ Serial.write((byte)INPUT);
+ Serial.write(1);
+ Serial.write((byte)OUTPUT);
+ Serial.write(1);
+ }
+ if (IS_PIN_ANALOG(pin)) {
+ Serial.write(ANALOG);
+ Serial.write(10);
+ }
+ if (IS_PIN_PWM(pin)) {
+ Serial.write(PWM);
+ Serial.write(8);
+ }
+ if (IS_PIN_SERVO(pin)) {
+ Serial.write(SERVO);
+ Serial.write(14);
+ }
+ Serial.write(127);
+ }
+ Serial.write(END_SYSEX);
+ break;
+ case PIN_STATE_QUERY:
+ if (argc > 0) {
+ byte pin=argv[0];
+ Serial.write(START_SYSEX);
+ Serial.write(PIN_STATE_RESPONSE);
+ Serial.write(pin);
+ if (pin < TOTAL_PINS) {
+ Serial.write((byte)pinConfig[pin]);
+ Serial.write((byte)pinState[pin] & 0x7F);
+ if (pinState[pin] & 0xFF80) Serial.write((byte)(pinState[pin] >> 7) & 0x7F);
+ if (pinState[pin] & 0xC000) Serial.write((byte)(pinState[pin] >> 14) & 0x7F);
+ }
+ Serial.write(END_SYSEX);
+ }
+ break;
+ case ANALOG_MAPPING_QUERY:
+ Serial.write(START_SYSEX);
+ Serial.write(ANALOG_MAPPING_RESPONSE);
+ for (byte pin=0; pin < TOTAL_PINS; pin++) {
+ Serial.write(IS_PIN_ANALOG(pin) ? PIN_TO_ANALOG(pin) : 127);
+ }
+ Serial.write(END_SYSEX);
+ break;
+ }
+}
+
+/*==============================================================================
+ * SETUP()
+ *============================================================================*/
+void setup()
+{
+ byte i;
+
+ Firmata.setFirmwareVersion(2, 2);
+
+ Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
+ Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback);
+ Firmata.attach(REPORT_ANALOG, reportAnalogCallback);
+ Firmata.attach(REPORT_DIGITAL, reportDigitalCallback);
+ Firmata.attach(SET_PIN_MODE, setPinModeCallback);
+ Firmata.attach(START_SYSEX, sysexCallback);
+
+ // TODO: load state from EEPROM here
+
+ /* these are initialized to zero by the compiler startup code
+ for (i=0; i < TOTAL_PORTS; i++) {
+ reportPINs[i] = false;
+ portConfigInputs[i] = 0;
+ previousPINs[i] = 0;
+ }
+ */
+ for (i=0; i < TOTAL_PINS; i++) {
+ if (IS_PIN_ANALOG(i)) {
+ // turns off pullup, configures everything
+ setPinModeCallback(i, ANALOG);
+ } else {
+ // sets the output to 0, configures portConfigInputs
+ setPinModeCallback(i, OUTPUT);
+ }
+ }
+ // by defult, do not report any analog inputs
+ analogInputsToReport = 0;
+
+ Firmata.begin(57600);
+
+ /* send digital inputs to set the initial state on the host computer,
+ * since once in the loop(), this firmware will only send on change */
+ for (i=0; i < TOTAL_PORTS; i++) {
+ outputPort(i, readPort(i, portConfigInputs[i]), true);
+ }
+
+ /* init the toggleLed counter */
+ toggleMillis = millis();
+ pinMode(13, OUTPUT);
+}
+
+/*==============================================================================
+ * LOOP()
+ *============================================================================*/
+void loop()
+{
+ byte pin, analogPin;
+
+ /* DIGITALREAD - as fast as possible, check for changes and output them to the
+ * FTDI buffer using Serial.print() */
+ checkDigitalInputs();
+
+ //XXX: hack Firmata to blink until serial command arrives
+ dataOnSerial = Firmata.available();
+ if (dataOnSerial > 0 && !firstCommand) {
+ firstCommand = true;
+ }
+ //XXX: do the blink if the first command hasn't arrived yet
+ // configures pin 13 as output and then back as input
+ if (!firstCommand) {
+ toggleLed();
+ }
+
+ /* SERIALREAD - processing incoming messagse as soon as possible, while still
+ * checking digital inputs. */
+ while(dataOnSerial) {
+ Firmata.processInput();
+ dataOnSerial = Firmata.available();
+ }
+
+ /* SEND FTDI WRITE BUFFER - make sure that the FTDI buffer doesn't go over
+ * 60 bytes. use a timer to sending an event character every 4 ms to
+ * trigger the buffer to dump. */
+
+ currentMillis = millis();
+ if (currentMillis - previousMillis > samplingInterval) {
+ previousMillis += samplingInterval;
+ /* ANALOGREAD - do all analogReads() at the configured sampling interval */
+ for(pin=0; pin<TOTAL_PINS; pin++) {
+ if (IS_PIN_ANALOG(pin) && pinConfig[pin] == ANALOG) {
+ analogPin = PIN_TO_ANALOG(pin);
+ if (analogInputsToReport & (1 << analogPin)) {
+ Firmata.sendAnalog(analogPin, analogRead(analogPin));
+ }
+ }
+ }
+ }
+}
+
+====================================================================
+
+/*
+ Firmata.h - Firmata library
+ Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ See file LICENSE.txt for further informations on licensing terms.
+*/
+
+#ifndef Firmata_h
+#define Firmata_h
+
+#include <WProgram.h>
+#include <inttypes.h>
+
+
+/* Version numbers for the protocol. The protocol is still changing, so these
+ * version numbers are important. This number can be queried so that host
+ * software can test whether it will be compatible with the currently
+ * installed firmware. */
+#define FIRMATA_MAJOR_VERSION 2 // for non-compatible changes
+#define FIRMATA_MINOR_VERSION 2 // for backwards compatible changes
+
+#define MAX_DATA_BYTES 32 // max number of data bytes in non-Sysex messages
+
+// message command bytes (128-255/0x80-0xFF)
+#define DIGITAL_MESSAGE 0x90 // send data for a digital pin
+#define ANALOG_MESSAGE 0xE0 // send data for an analog pin (or PWM)
+#define REPORT_ANALOG 0xC0 // enable analog input by pin #
+#define REPORT_DIGITAL 0xD0 // enable digital input by port pair
+//
+#define SET_PIN_MODE 0xF4 // set a pin to INPUT/OUTPUT/PWM/etc
+//
+#define REPORT_VERSION 0xF9 // report protocol version
+#define SYSTEM_RESET 0xFF // reset from MIDI
+//
+#define START_SYSEX 0xF0 // start a MIDI Sysex message
+#define END_SYSEX 0xF7 // end a MIDI Sysex message
+
+// extended command set using sysex (0-127/0x00-0x7F)
+/* 0x00-0x0F reserved for user-defined commands */
+#define SERVO_CONFIG 0x70 // set max angle, minPulse, maxPulse, freq
+#define STRING_DATA 0x71 // a string message with 14-bits per char
+#define SHIFT_DATA 0x75 // a bitstream to/from a shift register
+#define I2C_REQUEST 0x76 // send an I2C read/write request
+#define I2C_REPLY 0x77 // a reply to an I2C read request
+#define I2C_CONFIG 0x78 // config I2C settings such as delay times and power pins
+#define EXTENDED_ANALOG 0x6F // analog write (PWM, Servo, etc) to any pin
+#define PIN_STATE_QUERY 0x6D // ask for a pin's current mode and value
+#define PIN_STATE_RESPONSE 0x6E // reply with pin's current mode and value
+#define CAPABILITY_QUERY 0x6B // ask for supported modes and resolution of all pins
+#define CAPABILITY_RESPONSE 0x6C // reply with supported modes and resolution
+#define ANALOG_MAPPING_QUERY 0x69 // ask for mapping of analog to pin numbers
+#define ANALOG_MAPPING_RESPONSE 0x6A // reply with mapping info
+#define REPORT_FIRMWARE 0x79 // report name and version of the firmware
+#define SAMPLING_INTERVAL 0x7A // set the poll rate of the main loop
+#define SYSEX_NON_REALTIME 0x7E // MIDI Reserved for non-realtime messages
+#define SYSEX_REALTIME 0x7F // MIDI Reserved for realtime messages
+// these are DEPRECATED to make the naming more consistent
+#define FIRMATA_STRING 0x71 // same as STRING_DATA
+#define SYSEX_I2C_REQUEST 0x76 // same as I2C_REQUEST
+#define SYSEX_I2C_REPLY 0x77 // same as I2C_REPLY
+#define SYSEX_SAMPLING_INTERVAL 0x7A // same as SAMPLING_INTERVAL
+
+// pin modes
+//#define INPUT 0x00 // defined in wiring.h
+//#define OUTPUT 0x01 // defined in wiring.h
+#define ANALOG 0x02 // analog pin in analogInput mode
+#define PWM 0x03 // digital pin in PWM output mode
+#define SERVO 0x04 // digital pin in Servo output mode
+#define SHIFT 0x05 // shiftIn/shiftOut mode
+#define I2C 0x06 // pin included in I2C setup
+#define TOTAL_PIN_MODES 7
+
+extern "C" {
+// callback function types
+ typedef void (*callbackFunction)(byte, int);
+ typedef void (*systemResetCallbackFunction)(void);
+ typedef void (*stringCallbackFunction)(char*);
+ typedef void (*sysexCallbackFunction)(byte command, byte argc, byte*argv);
+}
+
+
+// TODO make it a subclass of a generic Serial/Stream base class
+class FirmataClass
+{
+public:
+ FirmataClass();
+/* Arduino constructors */
+ void begin();
+ void begin(long);
+/* querying functions */
+ void printVersion(void);
+ void blinkVersion(void);
+ void printFirmwareVersion(void);
+ //void setFirmwareVersion(byte major, byte minor); // see macro below
+ void setFirmwareNameAndVersion(const char *name, byte major, byte minor);
+/* serial receive handling */
+ int available(void);
+ void processInput(void);
+/* serial send handling */
+ void sendAnalog(byte pin, int value);
+ void sendDigital(byte pin, int value); // TODO implement this
+ void sendDigitalPort(byte portNumber, int portData);
+ void sendString(const char* string);
+ void sendString(byte command, const char* string);
+ void sendSysex(byte command, byte bytec, byte* bytev);
+/* attach & detach callback functions to messages */
+ void attach(byte command, callbackFunction newFunction);
+ void attach(byte command, systemResetCallbackFunction newFunction);
+ void attach(byte command, stringCallbackFunction newFunction);
+ void attach(byte command, sysexCallbackFunction newFunction);
+ void detach(byte command);
+
+private:
+/* firmware name and version */
+ byte firmwareVersionCount;
+ byte *firmwareVersionVector;
+/* input message handling */
+ byte waitForData; // this flag says the next serial input will be data
+ byte executeMultiByteCommand; // execute this after getting multi-byte data
+ byte multiByteChannel; // channel data for multiByteCommands
+ byte storedInputData[MAX_DATA_BYTES]; // multi-byte data
+/* sysex */
+ boolean parsingSysex;
+ int sysexBytesRead;
+/* callback functions */
+ callbackFunction currentAnalogCallback;
+ callbackFunction currentDigitalCallback;
+ callbackFunction currentReportAnalogCallback;
+ callbackFunction currentReportDigitalCallback;
+ callbackFunction currentPinModeCallback;
+ systemResetCallbackFunction currentSystemResetCallback;
+ stringCallbackFunction currentStringCallback;
+ sysexCallbackFunction currentSysexCallback;
+
+/* private methods ------------------------------ */
+ void processSysexMessage(void);
+ void systemReset(void);
+ void pin13strobe(int count, int onInterval, int offInterval);
+};
+
+extern FirmataClass Firmata;
+
+/*==============================================================================
+ * MACROS
+ *============================================================================*/
+
+/* shortcut for setFirmwareNameAndVersion() that uses __FILE__ to set the
+ * firmware name. It needs to be a macro so that __FILE__ is included in the
+ * firmware source file rather than the library source file.
+ */
+#define setFirmwareVersion(x, y) setFirmwareNameAndVersion(__FILE__, x, y)
+
+/* Hardware Abstraction Layer */
+#include "Boards.h"
+
+#endif /* Firmata_h */
203 firmata-midi-protocol.txt
@@ -0,0 +1,203 @@
+Notes on the Firmata 2.2 wire protocol.
+Retrieved from "http://firmata.org/wiki/V2.2ProtocolDetails"
+
+/* This protocol uses the MIDI message format, but does not use the whole
+ * protocol. Most of the command mappings here will not be directly usable in
+ * terms of MIDI controllers and synths. It should co-exist with MIDI without
+ * trouble and can be parsed by standard MIDI interpreters. Just some of the
+ * message data is used differently.
+ *
+ * MIDI format: http://www.harmony-central.com/MIDI/Doc/table1.html
+ *
+ * MIDI
+ * type command channel first byte second byte
+ *----------------------------------------------------------------------------
+ * analog I/O message 0xE0 pin # LSB(bits 0-6) MSB(bits 7-13)
+ * digital I/O message 0x90 port LSB(bits 0-6) MSB(bits 7-13)
+ * report analog pin 0xC0 pin # disable/enable(0/1) - n/a -
+ * report digital port 0xD0 port disable/enable(0/1) - n/a -
+ *
+ * sysex start 0xF0
+ * set pin mode(I/O) 0xF4 pin # (0-127) pin state(0=in)
+ * sysex end 0xF7
+ * protocol version 0xF9 major version minor version
+ * system reset 0xFF
+ *
+ */
+/* SysEx-based commands (0x00-0x7F) are used for an extended command set.
+ *
+ * type command first byte second byte ...
+ *-----------------------------------------------------------------------------
+ * string 0x71 char *string ...
+ * firmware name/version 0x79 major version minor version char *name...
+ */
+
+[edit] Data Message Expansion
+
+/* two byte digital data format
+ * 0 digital data, 0x90-0x9F, (MIDI NoteOn, but different data format)
+ * 1 digital pins 0-6 bitmask
+ * 2 digital pins 7-13 bitmask
+ */
+
+/* analog 14-bit data format
+ * 0 analog pin, 0xE0-0xEF, (MIDI Pitch Wheel)
+ * 1 analog least significant 7 bits
+ * 2 analog most significant 7 bits
+ */
+
+/* version report format
+ * -------------------------------------------------
+ * 0 version report header (0xF9) (MIDI Undefined)
+ * 1 major version (0-127)
+ * 2 minor version (0-127)
+ */
+
+[edit] Control Messages Expansion
+
+/* set pin mode
+ * 1 set digital pin mode (0xF4) (MIDI Undefined)
+ * 2 pin number (0-127)
+ * 3 state (INPUT/OUTPUT/ANALOG/PWM/SERVO, 0/1/2/3/4)
+ */
+
+/* toggle analogIn reporting by pin
+ * 0 toggle digitalIn reporting (0xC0-0xCF) (MIDI Program Change)
+ * 1 disable(0)/enable(non-zero)
+ */
+
+/* toggle digital port reporting by port
+ * 0 toggle digital port reporting (0xD0-0xDF) (MIDI Aftertouch)
+ * 1 disable(0)/enable(non-zero)
+ */
+
+/* request version report
+ * 0 request version report (0xF9) (MIDI Undefined)
+ */
+
+[edit] Sysex Message Format
+
+The idea for SysEx is to have a second command space using the first byte after the SysEx Start byte. The key difference is that the data can be of any size, rather than just one or two bytes for standard MIDI messages.
+
+/* Generic Sysex Message
+ * 0 START_SYSEX (0xF0)
+ * 1 sysex command (0x00-0x7F)
+ * x between 0 and MAX_DATA_BYTES 7-bit bytes of arbitrary data
+ * last END_SYSEX (0xF7)
+ */
+
+
+[edit] Query Firmware Name and Version
+
+The firmware name to be reported should be exactly the same as the name of the Arduino file, minus the .pde. So for Standard_Firmata.pde, the firmware name is: Standard_Firmata.
+
+/* Query Firmware Name and Version
+ * 0 START_SYSEX (0xF0)
+ * 1 queryFirmware (0x79)
+ * 2 END_SYSEX (0xF7)
+ */
+
+
+/* Receive Firmware Name and Version (after query)
+ * 0 START_SYSEX (0xF0)
+ * 1 queryFirmware (0x79)
+ * 2 major version (0-127)
+ * 3 minor version (0-127)
+ * 4 first 7-bits of firmware name
+ * 5 second 7-bits of firmware name
+ * x ...for as many bytes as it needs)
+ * 6 END_SYSEX (0xF7)
+ */
+
+[edit] I2C
+
+/* I2C read/write request
+ * -------------------------------
+ * 0 START_SYSEX (0xF0) (MIDI System Exclusive)
+ * 1 I2C_REQUEST (0x76)
+ * 2 slave address (LSB)
+ * 3 slave address (MSB) + read/write and address mode bits
+ {7: always 0} + {6: reserved} + {5: address mode, 1 means 10-bit mode} +
+ {4-3: read/write, 00 => write, 01 => read once, 10 => read continuously, 11 => stop reading} +
+ {2-0: slave address MSB in 10-bit mode, not used in 7-bit mode}
+ * 4 data 0 (LSB)
+ * 5 data 0 (MSB)
+ * 6 data 1 (LSB)
+ * 7 data 1 (MSB)
+ * ...
+ * n END_SYSEX (0xF7)
+ */
+
+/* I2C reply
+ * -------------------------------
+ * 0 START_SYSEX (0xF0) (MIDI System Exclusive)
+ * 1 I2C_REPLY (0x77)
+ * 2 slave address (LSB)
+ * 3 slave address (MSB)
+ * 4 register (LSB)
+ * 5 register (MSB)
+ * 6 data 0 LSB
+ * 7 data 0 MSB
+ * ...
+ * n END_SYSEX (0xF7)
+ */
+
+/* I2C config
+ * -------------------------------
+ * 0 START_SYSEX (0xF0) (MIDI System Exclusive)
+ * 1 I2C_CONFIG (0x78)
+ * 2 Power pin settings (0:off or 1:on)
+ * 3 Delay in microseconds (LSB)
+ * 4 Delay in microseconds (MSB)
+ * ... user defined for special cases, etc
+ * n END_SYSEX (0xF7)
+ */
+
+[edit] Sampling Interval
+
+/* Set sampling interval
+ * -------------------------------
+ * 0 START_SYSEX (0xF0) (MIDI System Exclusive)
+ * 1 SAMPLING_INTERVAL (0x7A)
+ * 2 sampling interval on the millisecond time scale (LSB)
+ * 3 sampling interval on the millisecond time scale (MSB)
+ * 4 END_SYSEX (0xF7)
+ */
+
+[edit] Servos
+
+This is the current proposal for adding Servo support based on feedback from Bjoern Hartmann, Shigeru Kobayashi, and Erik Sjodin. The core idea is to just add a "config" message, then use the SET_PIN_MODE message to attach/detach Servo support to a pin. This is how hardware PWM is currently handled. This would save space in the protocol by reusing the SET_PIN_MODE message, but the host software implementation could have a different interface, e.g. Arduino's attach() and detach().
+
+minPulse, maxPulse, and angle are all 14-bit unsigned integers. Angle is in degrees. The SERVO_CONFIG can be sent at any time to change the settings.
+
+/* servo config
+ * --------------------
+ * 0 START_SYSEX (0xF0)
+ * 1 SERVO_CONFIG (0x70)
+ * 2 pin number (0-127)
+ * 3 minPulse LSB (0-6)
+ * 4 minPulse MSB (7-13)
+ * 5 maxPulse LSB (0-6)
+ * 6 maxPulse MSB (7-13)
+ * 7 angle LSB (0-6)
+ * 8 angle MSB (7-13)
+ * 9 END_SYSEX (0xF7)
+ */
+
+This is just the standard SET_PIN_MODE message:
+
+/* set digital pin mode
+ * --------------------
+ * 1 set digital pin mode (0xF4) (MIDI Undefined)
+ * 2 pin number (0-127)
+ * 3 state (INPUT/OUTPUT/ANALOG/PWM/SERVO, 0/1/2/3/4)
+ */
+
+Then the normal ANALOG_MESSAGE data format is used to send data.
+
+/* write to servo, servo write is performed if the pins mode is SERVO
+ * ------------------------------
+ * 0 ANALOG_MESSAGE (0xE0-0xEF)
+ * 1 value lsb
+ * 2 value msb
+ */
28 test.pl
@@ -0,0 +1,28 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+use Firmata;
+use Device::SerialPort;
+use Symbol qw(gensym);
+
+my $handle = gensym();
+my $port = tie(*$handle, "Device::SerialPort", "/dev/cu.usbmodem641") or die(
+ "can't open port: $!"
+);
+
+$port->datatype("raw");
+$port->baudrate(57600);
+$port->databits(8);
+$port->parity("none");
+$port->stopbits(1);
+$port->write_settings();
+
+$|=1;
+
+my $f = Firmata->new(handle => $handle);
+
+#$f->put_handle("\x9C\x01\x00");
+
+$f->run_all();

0 comments on commit 3e5b371

Please sign in to comment.