Search wiring.org.co:

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.

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
     *============================================================================*/

    /* 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)

    Servo servos[MAX_SERVOS];

    /*==============================================================================
     * FUNCTIONS
     *============================================================================*/

    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 digital pins");
          }
        }
        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);
      }
    }

    /*==============================================================================
     * 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();

      /* SERIALREAD - processing incoming messagse as soon as possible, while still
       * checking digital inputs.  */
      while(Firmata.available())
        Firmata.processInput();

      /* 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));
            }
          }
        }
      }
    }