This example is for Wiring version 1.0 build 0100+. If you have a previous version, use the examples included with your software. If you see any errors or have comments, please let us know.
WIRING_OSC 0004
Firmware to send OSC messages from a Wiring board to a PC and to receive OSC messages sent from the PC. * Right now, only messages with a single integer argument are supported in either direction. * Uses the following serial OSC format: 0xBE MSG_LENGTH OSC_MESSAGE CHECKSUM ---- ---------- ---/.../-- -------- 1b 1byte 4*n bytes 1byte * PROTOCOL DETAILS Digital pins 0..39, analog inputs 0..7 are supported. PWM output on PWM pins 0..5 is supported as well. Below, the notation [0..39] means: any number from 0 to 39. The notation [0|1] means: either 0 or 1. Pin numbers are always part of the OSC address. The single integer argument for each OSC message represents either HIGH/LOW, or an 8bit analog value. * PC->WIRING MESSAGE FORMAT /pinmode/[0..39] [0|1] - set a pin to input or output mode * /report/adc [0|1] - turn all analog pin reporting on/off (Default: off) /report/adc/[0..7] [0|1] - set analog pin reporting for one pin on/off /report/in [0|1] - turn digital pin reporting on/off (default: on) * /out/[0..39] [0|1] - set a digital pin to [low|high] /pwm/[0..5] [0..1023] - set duty on a pwm-enabled pin * ARDUINO->PC PROTOCOL /in/[0..39] [0|1] - a digital input pin changed to [high|low] /adc/[0..7] [0..255] - analog input value changed to [0..255] NOTE: input pins use pull-up resistors and are HIGH by default. Therefore, 0 means HIGH, 1 means LOW (pulled to ground). * EXAMPLES: PC->ARDUINO /pinmode/5 0 - set pin 5 to INPUT /pinmode/9 1 - set pin 9 to OUTPUT /out/9 0 - set pin 9 to LOW /out/12 1 - set pin 12 to HIGH /pwm/4 512 - set PWM duty on PWM pin 4 to 512 (50%) /report/in 1 - turn digital input pin reporting on /report/adc/4 0 - turn reporting of analog input 4 off * EXAMPLES: WIRING->PC /in/4 1 - digital input pin 4 pulled to ground /adc/2 512 - analog input pin2 read 512 (=2.5V) * DEFAULT STARTUP CONFIGURATION - Pins 0-39 are all set to input, digital reporting enabled (change variable reportDigital to False to disable by default) - Analog reporting is disabled (change variable reportAnalog to 0xFF to enable by default) * NOTES: - Pins 32,33 cannot be used (needed for Rx/Tx) - Resolution on analog in and out is 10 bit. - So far, not much error checking for bounds done - In testing with pd and [serialIO], message bytes tend to get "stuck" in a buffer either on PC or on Wiring board side when sending to hardware, with the effect that an OSC message doesn't get processed on the board until the *next* message arrives, or until a few 0x00 characters are sent. This does not appear to happen wir Arduino_Osc. - On the tested board, pin Analog In 0 corresponds to analogRead(7) and pins 1..7 correspond to analogRead(0)..analogRead(6). * MIT License: Copyright (c) 2008 Bjoern Hartmann, Stanford HCI Group Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * bjoern@cs.stanford.edu 11/23/2008
Firmware to send OSC messages from a Wiring board to a PC and to receive OSC messages sent from the PC. * Right now, only messages with a single integer argument are supported in either direction. * Uses the following serial OSC format: 0xBE MSG_LENGTH OSC_MESSAGE CHECKSUM ---- ---------- ---/.../-- -------- 1b 1byte 4*n bytes 1byte * PROTOCOL DETAILS Digital pins 0..39, analog inputs 0..7 are supported. PWM output on PWM pins 0..5 is supported as well. Below, the notation [0..39] means: any number from 0 to 39. The notation [0|1] means: either 0 or 1. Pin numbers are always part of the OSC address. The single integer argument for each OSC message represents either HIGH/LOW, or an 8bit analog value. * PC->WIRING MESSAGE FORMAT /pinmode/[0..39] [0|1] - set a pin to input or output mode * /report/adc [0|1] - turn all analog pin reporting on/off (Default: off) /report/adc/[0..7] [0|1] - set analog pin reporting for one pin on/off /report/in [0|1] - turn digital pin reporting on/off (default: on) * /out/[0..39] [0|1] - set a digital pin to [low|high] /pwm/[0..5] [0..1023] - set duty on a pwm-enabled pin * ARDUINO->PC PROTOCOL /in/[0..39] [0|1] - a digital input pin changed to [high|low] /adc/[0..7] [0..255] - analog input value changed to [0..255] NOTE: input pins use pull-up resistors and are HIGH by default. Therefore, 0 means HIGH, 1 means LOW (pulled to ground). * EXAMPLES: PC->ARDUINO /pinmode/5 0 - set pin 5 to INPUT /pinmode/9 1 - set pin 9 to OUTPUT /out/9 0 - set pin 9 to LOW /out/12 1 - set pin 12 to HIGH /pwm/4 512 - set PWM duty on PWM pin 4 to 512 (50%) /report/in 1 - turn digital input pin reporting on /report/adc/4 0 - turn reporting of analog input 4 off * EXAMPLES: WIRING->PC /in/4 1 - digital input pin 4 pulled to ground /adc/2 512 - analog input pin2 read 512 (=2.5V) * DEFAULT STARTUP CONFIGURATION - Pins 0-39 are all set to input, digital reporting enabled (change variable reportDigital to False to disable by default) - Analog reporting is disabled (change variable reportAnalog to 0xFF to enable by default) * NOTES: - Pins 32,33 cannot be used (needed for Rx/Tx) - Resolution on analog in and out is 10 bit. - So far, not much error checking for bounds done - In testing with pd and [serialIO], message bytes tend to get "stuck" in a buffer either on PC or on Wiring board side when sending to hardware, with the effect that an OSC message doesn't get processed on the board until the *next* message arrives, or until a few 0x00 characters are sent. This does not appear to happen wir Arduino_Osc. - On the tested board, pin Analog In 0 corresponds to analogRead(7) and pins 1..7 correspond to analogRead(0)..analogRead(6). * MIT License: Copyright (c) 2008 Bjoern Hartmann, Stanford HCI Group Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * bjoern@cs.stanford.edu 11/23/2008
#define VERSION 4 #define MIN_A2D_DIFF 4 // threshold for reporting a2d changes #define MAX_LENGTH 24 // size of buffer for building OSC msgs #define SERIAL_SPEED 38400 #define FIRST_DIGITAL_PIN 0 #define LAST_DIGITAL_PIN 39 #define NUM_DIGITAL_PINS 40 #define NUM_PORTS 5 #define FIRST_ANALOG_PIN 0 #define LAST_ANALOG_PIN 7 #define NUM_ANALOG_PINS 8 #define RX_PIN 32 #define TX_PIN 33 // define state constants for parsing FSM #define OSC_RXOP_WAITFORSTART 0 #define OSC_RXOP_READSIZE 1 #define OSC_RXOP_READADDR 2 #define OSC_RXOP_READTAG 3 #define OSC_RXOP_READARG 4 #define OSC_RXOP_READCHECKSUM 5 //removed READMSG #define OSC_RXOP_SKIPMSG 7 #define OSC_RXOP_READARG1BYTE1 8 #define OSC_RXOP_READARG1BYTE2 9 #define OSC_RXOP_READARG1BYTE3 10 #define OSC_RXOP_READARG1BYTE4 11 #define OSC_RXOP_READTAGBYTE1 12 #define OSC_RXOP_READTAGBYTE2 13 #define OSC_RXOP_READTAGBYTE3 14 #define OSC_RXOP_READTAGBYTE4 15 #define OSC_MAX_RX_MSG_SIZE 32 int incomingByte = 0; // for incoming serial data int k = FIRST_ANALOG_PIN; byte inputBuffer[NUM_PORTS] = {0xFF}; // holds previous values of PORTB and PORTD (pins 0..7); start all high because of pull-ups int a2dBuffer[NUM_ANALOG_PINS] = {0x00}; // holds previous A2D conversion values char oscBuffer[MAX_LENGTH] = {0x00}; // holds outgoing OSC message byte pinDir[NUM_PORTS] = {0x00}; //buffer that saves pin directions 0=input; 1=output; default: all in char prefixReport[] = "/report/"; char prefixPinmode[] = "/pinmode/"; char prefixOut[] = "/out/"; char prefixPwm[] = "/pwm/"; char prefixIn[] = "/in/"; char prefixA2d[] = "/adc/"; char prefixReset[] = "/reset"; //TODO: implement char oscOutAddress[10] = {0x00}; //string that holds outgoing osc message address char* numbers[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39" }; byte pwmPinMap[6] = {37, 36, 35, 31, 30, 29}; // which values should be reported? byte reportAnalog = 0x00; //bitmask - 0=off, 1=on - default:all off boolean reportDigital = true; //no per-pin reporting for analog //////parser variables//////// static byte oscRxNextOp = OSC_RXOP_WAITFORSTART; //keeps track of current state // space for buffer in RAM static char oscRxData[OSC_MAX_RX_MSG_SIZE]; byte oscRxMsgSize; // size of incoming msg byte oscRxReadBytes; //number of bytes read byte *oscRxAddr; unsigned long oscRxIntArg1; //int argument of message byte oscRxChecksum; /*********************************************** * SETUP - open serial comm and initialize pins ***********************************************/ void setup() { int i; reportAnalog = 0x00; reportDigital = true; // set all pins as inputs for (i = FIRST_DIGITAL_PIN; i <= LAST_DIGITAL_PIN; i++) { if ((i != RX_PIN) && (i != TX_PIN)) { pinMode(i, INPUT); digitalWrite(i, HIGH); // use pull-ups } } Serial.begin(SERIAL_SPEED); } /*********************************************** * LOOP - poll pin values and read incoming * serial communication ***********************************************/ void loop() { // check all digital inputs if (reportDigital) { checkDiscreteInputs(); } // check one analog input per loop if (reportAnalog & (1 << k)) { checkAnalogInput(k); } k = (k + 1) % NUM_ANALOG_PINS; // handle all received serial bytes while (Serial.available() > 0) { incomingByte = Serial.read() & 0xFF;// read byte - truncate to 8bits to be safe oscRxHandler(incomingByte); // hand to message parser } } /*********************************************** * Check all digital inputs and call * oscSendMessageInt() if values has changed ***********************************************/ void checkDiscreteInputs() { int i; byte state[NUM_PORTS] = {0x00}; //WIRING: read all ports into buffer for (i = 0; i < NUM_PORTS; i++) { state[i] = portRead(i); } // if the state of a pin has changed since last time, // and that pin is an input pin, send a message for (i = FIRST_DIGITAL_PIN; i <= LAST_DIGITAL_PIN; i++) { if (!(pinDir[i / 8] & (1 << (i % 8))) && (i != RX_PIN) && (i != TX_PIN)) //if pin is input and not RX/TX { if ((state[i / 8] & (1 << (i % 8))) != (inputBuffer[i / 8] & (1 << (i % 8)))) //does this shift work? { strcpy(oscOutAddress, prefixIn); strcat(oscOutAddress, numbers[i]); oscSendMessageInt(oscOutAddress, !(state[i / 8] & (1 << (i % 8)))); } } } //save current state to buffer for (i = 0; i < NUM_PORTS; i++) { inputBuffer[i] = state[i]; } } /*********************************************** * Check one analog input channel and call * oscSendMessageInt() if its value has changed ***********************************************/ void checkAnalogInput(byte channel) { int result; int diff; // read a2d result = analogRead(channel); // >>2 on arduino // compare to last reading - if delta big enough, // send message //diff = result - a2dBuffer[channel]; //if (diff>MIN_A2D_DIFF || diff<(int)((-1)*MIN_A2D_DIFF)) { if (result != a2dBuffer[channel]) { a2dBuffer[channel] = result; strcpy(oscOutAddress, prefixA2d); strcat(oscOutAddress, numbers[channel]); oscSendMessageInt(oscOutAddress, result); } } /*********************************************** * Send an OSC message with the passed in * address and a single integer argument ***********************************************/ void oscSendMessageInt(char * address, unsigned long value) { byte offset = 0; byte i = 0; // clear buffer for (i = 0; i < MAX_LENGTH; i++) { oscBuffer[i] = 0x00; } //write packet header oscBuffer[offset++] = 0xBE; //compute message length //first compute address string length and padd if necessary byte addrlen = strlen(address); if (addrlen & 0x03) addrlen += 4 - (addrlen & 0x03); //then add type-tag length and arg length (both 4 for a simple int message) byte typetaglen = 4; byte arglen = 4; //final length is sum of the three byte len = addrlen + typetaglen + arglen; //write message length oscBuffer[offset++] = (unsigned char)len; //write address strcpy(oscBuffer + offset, address); offset += addrlen; //write typetag oscBuffer[offset++] = ','; oscBuffer[offset++] = 'i'; oscBuffer[offset++] = 0x00; oscBuffer[offset++] = 0x00; //write argument oscBuffer[offset++] = *(((unsigned char *)(&value)) + 3); oscBuffer[offset++] = *(((unsigned char *)(&value)) + 2); oscBuffer[offset++] = *(((unsigned char *)(&value)) + 1); oscBuffer[offset++] = *(((unsigned char *)(&value)) + 0); //compute + write checksum byte checksum = 0; for (i = 2; i < offset; i++) { checksum += oscBuffer[i]; } oscBuffer[offset++] = checksum; //send message byte-by-byte for (i = 0; i < offset; i++) { Serial.print(oscBuffer[i], BYTE); } } /*********************************************** * Handle a received OSC message ***********************************************/ void oscReceiveMessageInt(char * msg, unsigned long value) { int i; int outPin; //uncomment to echo message back for debugging oscSendMessageInt(msg, value); // check if this is an output message, i.e., starts with "/out/" if (strncmp(msg, prefixOut, strlen(prefixOut)) == 0) { //if so, find which pin outPin = atoi(msg + strlen(prefixOut)); if (outPin >= FIRST_DIGITAL_PIN && outPin <= LAST_DIGITAL_PIN && outPin != RX_PIN && outPin != TX_PIN) //sanity check { //change its value //note: pin can be set to input - this enables/disables pullups digitalWrite(outPin, (byte)(value & 0x01)); } return; } // check if this is a pwm message, i.e., starts with "/pwm/" if (strncmp(msg, prefixPwm, strlen(prefixPwm)) == 0) { outPin = atoi(msg + strlen(prefixPwm)); if (outPin >= FIRST_DIGITAL_PIN && outPin <= LAST_DIGITAL_PIN && outPin != RX_PIN && outPin != TX_PIN) //sanity check { //make sure we turn pin into output in our pinDir first //so we don't generate lots of extraneous messages //may need to map from 0..5 to 29..37 range as well if (0 <= outPin && 5 >= outPin) { pinDir[pwmPinMap[outPin] / 8] = pinDir[pwmPinMap[outPin] / 8] | (1 << (pwmPinMap[outPin] % 8)); } else { pinDir[outPin / 8] = pinDir[outPin / 8] | (1 << (outPin % 8)); } //set pwm analogWrite(outPin, value & 1023); } return; } // check if this is a "report" message which starts with "/report/" // changes which pins get reported if (strncmp(msg, prefixReport, strlen(prefixReport)) == 0) { //if it continues with "/in" if (strncmp(msg + strlen(prefixReport) - 1, prefixIn, strlen(prefixIn) - 1) == 0) { reportDigital = (value != 0); return; } //else if it continues with "/adc/" else if (strncmp(msg + strlen(prefixReport) - 1, prefixA2d, strlen(prefixA2d)) == 0) { //extract which analog pin we're talking about outPin = atoi(msg + strlen(prefixReport) - 1 + strlen(prefixA2d)); //flip the bit in reportAnalog: if (outPin >= FIRST_ANALOG_PIN && outPin <= LAST_ANALOG_PIN) //sanity check { if (value == 0) { reportAnalog = reportAnalog & ~(1 << outPin); } else { reportAnalog = reportAnalog | (1 << outPin); } } return; } //else if it continues with "/adc" (no final slash) else if (strncmp(msg + strlen(prefixReport) - 1, prefixA2d, strlen(prefixA2d) - 1) == 0) { //turn reporting for all analog pins on or off if (value == 0) { reportAnalog = 0x00; } else { reportAnalog = 0xFF; } return; } } //finally, this could be a "/pinmode/ message" if (strncmp(msg, prefixPinmode, strlen(prefixPinmode)) == 0) { outPin = atoi(msg + strlen(prefixPinmode)); if (outPin >= FIRST_DIGITAL_PIN && outPin <= LAST_DIGITAL_PIN) //sanity check { if (value == 0) { pinDir[outPin / 8] = pinDir[outPin / 8] & ~(1 << (outPin % 8)); //turn bit in our own direction buffer to off = input pinMode(outPin, INPUT); //set DDR register bit to input digitalWrite(outPin, HIGH); //reenable pull-up } else { pinDir[outPin / 8] = pinDir[outPin / 8] | (1 << (outPin % 8)); //turn bit on pinMode(outPin, OUTPUT); // turn DDR bit to output } } return; } //is this a reset message? if so, reinitialize. if (strncmp(msg, prefixReset, strlen(prefixReset)) == 0) { oscRxNextOp = OSC_RXOP_WAITFORSTART; setup(); } } /*********************************** * PARSING OF MESSAGES BELOW ***********************************/ /*********************************** * PARSER ***********************************/ void oscRxHandler(unsigned char c) { byte i; switch (oscRxNextOp) { case OSC_RXOP_WAITFORSTART: if (c == 0xBE) //0xBE is the magic start byte { oscRxNextOp = OSC_RXOP_READSIZE; } break; case OSC_RXOP_READSIZE: oscRxMsgSize = c; // read message size oscRxReadBytes = 0; //reset index into message buffer if (oscRxMsgSize < (OSC_MAX_RX_MSG_SIZE - 1)) { oscRxNextOp = OSC_RXOP_READADDR; } else { oscRxNextOp = OSC_RXOP_SKIPMSG; //Msg is too long } break; case OSC_RXOP_READADDR: if (c != 0 && c != ',') { oscRxData[oscRxReadBytes++] = c; //copy normal addr byte } else if (c == ',') { //if we went straight from addr to to tag string, convert "," of tag string to 0x00 // so our address string is properly zero-terminated //and jump ahead in the state machine to read second tag byte next oscRxData[oscRxReadBytes++] = 0x00; oscRxNextOp = OSC_RXOP_READTAGBYTE2; } else { oscRxData[oscRxReadBytes++] = 0; if (!((oscRxReadBytes) & 0x03)) //skip 0s until we hit byte boundary { oscRxNextOp = OSC_RXOP_READTAGBYTE1; } } break; // read type tag string bytes 1-4 case OSC_RXOP_READTAGBYTE1: oscRxData[oscRxReadBytes++] = c; if (c == ',') { // valid type tag start character found oscRxNextOp = OSC_RXOP_READTAGBYTE2; } else { // no type tag start char present // assume one int arg and read its first byte oscRxIntArg1 = 0; oscRxIntArg1 |= ((unsigned long)(c) << 0x18); oscRxNextOp = OSC_RXOP_READARG1BYTE2; } break; case OSC_RXOP_READTAGBYTE2: oscRxData[oscRxReadBytes++] = c; switch (c) { case 'i': //int32 argument oscRxNextOp = OSC_RXOP_READTAGBYTE3; break; // all other types besides int are not supported yet // so skip remainder of message case 'f': //float arg case 's': //string arg default: oscRxNextOp = OSC_RXOP_SKIPMSG; } break; case OSC_RXOP_READTAGBYTE3: oscRxData[oscRxReadBytes++] = c; switch (c) { // null - message has only one arg - we're ok case 0: oscRxNextOp = OSC_RXOP_READTAGBYTE4; break; // all others: not yet supported; skip rest of message default: oscRxNextOp = OSC_RXOP_SKIPMSG; break; } break; case OSC_RXOP_READTAGBYTE4: oscRxData[oscRxReadBytes++] = c; switch (c) { // null - message has only one arg - we're ok case 0: oscRxNextOp = OSC_RXOP_READARG1BYTE1; break; // all others: not yet supported; skip rest of message default: oscRxNextOp = OSC_RXOP_SKIPMSG; break; } break; // read argument bytes 1-4 case OSC_RXOP_READARG1BYTE1: oscRxData[oscRxReadBytes++] = c; oscRxIntArg1 = 0; oscRxIntArg1 |= ((unsigned long)(c) << 0x18); oscRxNextOp = OSC_RXOP_READARG1BYTE2; break; case OSC_RXOP_READARG1BYTE2: oscRxData[oscRxReadBytes++] = c; oscRxIntArg1 |= ((unsigned long)(c) << 0x10); oscRxNextOp = OSC_RXOP_READARG1BYTE3; break; case OSC_RXOP_READARG1BYTE3: oscRxData[oscRxReadBytes++] = c; oscRxIntArg1 |= ((unsigned long)(c) << 0x08); oscRxNextOp = OSC_RXOP_READARG1BYTE4; break; case OSC_RXOP_READARG1BYTE4: oscRxData[oscRxReadBytes++] = c; oscRxIntArg1 |= (unsigned long)(c); oscRxNextOp = OSC_RXOP_READCHECKSUM; break; // read checksum byte; check msg integrity; fire off user function case OSC_RXOP_READCHECKSUM: // check that we read the right number of bytes if (oscRxReadBytes != oscRxMsgSize) { oscSendMessageInt("/error/msgsize/", oscRxReadBytes); } else { oscRxChecksum = 0; for (i = 0; i < oscRxMsgSize; i++) { oscRxChecksum += oscRxData[i]; } if (oscRxChecksum == c) { // checksum matched and we're done with this msg // -> fire off user function oscReceiveMessageInt(oscRxData, oscRxIntArg1); } else { // mismatch - throw this message away oscSendMessageInt("/error/checksum", oscRxChecksum); } } // wait for next message header oscRxNextOp = OSC_RXOP_WAITFORSTART; break; //skip rest of message - called if an error was detected in the current //incoming message case OSC_RXOP_SKIPMSG: if (++oscRxReadBytes == (oscRxMsgSize + 1)) { oscRxNextOp = OSC_RXOP_WAITFORSTART; } break; default: oscRxNextOp = OSC_RXOP_WAITFORSTART; } }