#include #include #include #include #include "Adafruit_BLE_UART.h" // Change this to whatever is the Serial console you want, either Serial or SerialUSB #define FIRMATADEBUG Serial // Pause for Serial console before beginning? #define WAITFORSERIAL true // Print all BLE interactions? #define VERBOSE_MODE false // Pullups on all input pins? #define AUTO_INPUT_PULLUPS true /************************ CONFIGURATION SECTION ***********************************/ /* Don't forget to also change the BluefruitConfig.h for the SPI connection and pinout you are using! Then below, you can edit the list of pins that are available. Remove any pins that are used for accessories or for talking to the BLE module! */ /************** For UNO + nRF8001 SPI breakout ************/ uint8_t boards_digitaliopins[] = {3, 4, 5, 6, 7, 8, A0, A1, A2, A3, A4, A5}; #if defined(__AVR_ATmega328P__) // Standard setup for UNO, no need to tweak uint8_t boards_analogiopins[] = {A0, A1, A2, A3, A4, A5}; // A0 == digital 14, etc uint8_t boards_pwmpins[] = {3, 5, 6, 9, 10, 11}; uint8_t boards_servopins[] = {9, 10}; uint8_t boards_i2cpins[] = {SDA, SCL}; #elif defined(__AVR_ATmega32U4__) uint8_t boards_analogiopins[] = {A0, A1, A2, A3, A4, A5}; // A0 == digital 14, etc uint8_t boards_pwmpins[] = {3, 5, 6, 9, 10, 11, 13}; uint8_t boards_servopins[] = {9, 10}; uint8_t boards_i2cpins[] = {SDA, SCL}; #elif defined(__SAMD21G18A__) #define SDA PIN_WIRE_SDA #define SCL PIN_WIRE_SCL uint8_t boards_analogiopins[] = {PIN_A0, PIN_A1, PIN_A2, PIN_A3, PIN_A4, PIN_A5,PIN_A6, PIN_A7}; // A0 == digital 14, etc uint8_t boards_pwmpins[] = {3,4,5,6,8,10,11,12,A0,A1,A2,A3,A4,A5}; uint8_t boards_servopins[] = {9, 10}; uint8_t boards_i2cpins[] = {SDA, SCL}; #define NUM_DIGITAL_PINS 26 #endif #define TOTAL_PINS NUM_DIGITAL_PINS /* highest number in boards_digitaliopins MEMEFIXME:automate */ #define TOTAL_PORTS ((TOTAL_PINS + 7) / 8) /***********************************************************/ #include "Adafruit_BLE_Firmata_Boards.h" #include "BluefruitConfig.h" // Create the bluetooth breakout instance, set the pins in the BluefruitConfig.h file! Adafruit_BLE_UART bluefruit = Adafruit_BLE_UART(ADAFRUITBLE_REQ, ADAFRUITBLE_RDY, ADAFRUITBLE_RST); // our current connection status aci_evt_opcode_t lastBTLEstatus, BTLEstatus; // make one instance for the user to use Adafruit_BLE_FirmataClass BLE_Firmata = Adafruit_BLE_FirmataClass(bluefruit); // A small helper void error(const __FlashStringHelper*err) { FIRMATADEBUG.println(err); while (1); } /*============================================================================== * GLOBAL VARIABLES *============================================================================*/ /* analog inputs */ int analogInputsToReport = 0; // bitwise array to store pin reporting int lastAnalogReads[NUM_ANALOG_INPUTS]; /* 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 = 200; // how often to run the main loop (in ms) #define MINIMUM_SAMPLE_DELAY 150 #define ANALOG_SAMPLE_DELAY 50 /* i2c data */ struct i2c_device_info { byte addr; byte reg; byte bytes; }; /* for i2c read continuous more */ i2c_device_info query[MAX_QUERIES]; byte i2cRxData[32]; boolean isI2CEnabled = false; signed char queryIndex = -1; unsigned int i2cReadDelayTime = 0; // default delay time between i2c read request and Wire.requestFrom() Servo servos[MAX_SERVOS]; /*============================================================================== * FUNCTIONS *============================================================================*/ void readAndReportData(byte address, int theRegister, byte numBytes) { // allow I2C requests that don't require a register read // for example, some devices using an interrupt pin to signify new data available // do not always require the register read so upon interrupt you call Wire.requestFrom() if (theRegister != REGISTER_NOT_SPECIFIED) { Wire.beginTransmission(address); #if ARDUINO >= 100 Wire.write((byte)theRegister); #else Wire.send((byte)theRegister); #endif Wire.endTransmission(); delayMicroseconds(i2cReadDelayTime); // delay is necessary for some devices such as WiiNunchuck } else { theRegister = 0; // fill the register with a dummy value } Wire.requestFrom(address, numBytes); // all bytes are returned in requestFrom // check to be sure correct number of bytes were returned by slave if(numBytes == Wire.available()) { i2cRxData[0] = address; i2cRxData[1] = theRegister; for (int i = 0; i < numBytes; i++) { #if ARDUINO >= 100 i2cRxData[2 + i] = Wire.read(); #else i2cRxData[2 + i] = Wire.receive(); #endif } } else { if(numBytes > Wire.available()) { BLE_Firmata.sendString("I2C Read Error: Too many bytes received"); } else { BLE_Firmata.sendString("I2C Read Error: Too few bytes received"); } } // send slave address, register and received bytes BLE_Firmata.sendSysex(SYSEX_I2C_REPLY, numBytes + 2, i2cRxData); } 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) { //FIRMATADEBUG.print(F("Sending update for port ")); FIRMATADEBUG.print(portNumber); FIRMATADEBUG.print(" = 0x"); FIRMATADEBUG.println(portValue, HEX); BLE_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 () */ void checkDigitalInputs(boolean forceSend = false) { /* 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. */ for (uint8_t i=0; i TOTAL_PINS) lastPin = TOTAL_PINS; for (pin=port*8; pin < lastPin; pin++) { // do not disturb non-digital pins (eg, Rx & Tx) if (BLE_Firmata.IS_PIN_DIGITAL(pin)) { // only write to OUTPUT // do not touch pins in PWM, ANALOG, SERVO or other modes if (pinConfig[pin] == OUTPUT) { pinWriteMask |= mask; pinState[pin] = ((byte)value & mask) ? 1 : 0; } } mask = mask << 1; } //FIRMATADEBUG.print(F("Write digital port #")); FIRMATADEBUG.print(port); //FIRMATADEBUG.print(F(" = 0x")); FIRMATADEBUG.print(value, HEX); //FIRMATADEBUG.print(F(" mask = 0x")); FIRMATADEBUG.println(pinWriteMask, HEX); BLE_Firmata.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 < BLE_Firmata._num_analogiopins) { if(value == 0) { analogInputsToReport = analogInputsToReport &~ (1 << analogPin); //FIRMATADEBUG.print(F("Stop reporting analog pin #")); FIRMATADEBUG.println(analogPin); } else { analogInputsToReport |= (1 << analogPin); //FIRMATADEBUG.print(F("Will report analog pin #")); FIRMATADEBUG.println(analogPin); } } // TODO: save status to EEPROM here, if changed } void reportDigitalCallback(byte port, int value) { if (port < TOTAL_PORTS) { //FIRMATADEBUG.print(F("Will report 0x")); FIRMATADEBUG.print(value, HEX); FIRMATADEBUG.print(F(" digital mask on port ")); FIRMATADEBUG.println(port); 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) { byte mode; byte slaveAddress; byte slaveRegister; byte data; unsigned int delayTime; FIRMATADEBUG.println("********** Sysex callback"); switch(command) { case I2C_REQUEST: mode = argv[1] & I2C_READ_WRITE_MODE_MASK; if (argv[1] & I2C_10BIT_ADDRESS_MODE_MASK) { //BLE_Firmata.sendString("10-bit addressing mode is not yet supported"); //FIRMATADEBUG.println(F("10-bit addressing mode is not yet supported")); return; } else { slaveAddress = argv[0]; } switch(mode) { case I2C_WRITE: Wire.beginTransmission(slaveAddress); for (byte i = 2; i < argc; i += 2) { data = argv[i] + (argv[i + 1] << 7); #if ARDUINO >= 100 Wire.write(data); #else Wire.send(data); #endif } Wire.endTransmission(); delayMicroseconds(70); break; case I2C_READ: if (argc == 6) { // a slave register is specified slaveRegister = argv[2] + (argv[3] << 7); data = argv[4] + (argv[5] << 7); // bytes to read readAndReportData(slaveAddress, (int)slaveRegister, data); } else { // a slave register is NOT specified data = argv[2] + (argv[3] << 7); // bytes to read readAndReportData(slaveAddress, (int)REGISTER_NOT_SPECIFIED, data); } break; case I2C_READ_CONTINUOUSLY: if ((queryIndex + 1) >= MAX_QUERIES) { // too many queries, just ignore BLE_Firmata.sendString("too many queries"); break; } queryIndex++; query[queryIndex].addr = slaveAddress; query[queryIndex].reg = argv[2] + (argv[3] << 7); query[queryIndex].bytes = argv[4] + (argv[5] << 7); break; case I2C_STOP_READING: byte queryIndexToSkip; // if read continuous mode is enabled for only 1 i2c device, disable // read continuous reporting for that device if (queryIndex <= 0) { queryIndex = -1; } else { // if read continuous mode is enabled for multiple devices, // determine which device to stop reading and remove it's data from // the array, shifiting other array data to fill the space for (byte i = 0; i < queryIndex + 1; i++) { if (query[i].addr = slaveAddress) { queryIndexToSkip = i; break; } } for (byte i = queryIndexToSkip; i 0) { i2cReadDelayTime = delayTime; } if (!isI2CEnabled) { enableI2CPins(); } break; 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 (BLE_Firmata.IS_PIN_SERVO(pin)) { if (servos[BLE_Firmata.PIN_TO_SERVO(pin)].attached()) servos[BLE_Firmata.PIN_TO_SERVO(pin)].detach(); servos[BLE_Firmata.PIN_TO_SERVO(pin)].attach(BLE_Firmata.PIN_TO_DIGITAL(pin), minPulse, maxPulse); setPinModeCallback(pin, SERVO); } } break; case SAMPLING_INTERVAL: if (argc > 1) { samplingInterval = argv[0] + (argv[1] << 7); if (samplingInterval < MINIMUM_SAMPLING_INTERVAL) { samplingInterval = MINIMUM_SAMPLING_INTERVAL; } } else { //BLE_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: bluefruit.write(START_SYSEX); bluefruit.write(CAPABILITY_RESPONSE); //FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.print(START_SYSEX, HEX); FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.println(CAPABILITY_RESPONSE, HEX); delay(10); for (byte pin=0; pin < TOTAL_PINS; pin++) { //FIRMATADEBUG.print("\t#"); FIRMATADEBUG.println(pin); if (BLE_Firmata.IS_PIN_DIGITAL(pin)) { bluefruit.write((byte)INPUT); bluefruit.write(1); bluefruit.write((byte)OUTPUT); bluefruit.write(1); /* FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.print(INPUT, HEX); FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.print(1, HEX); FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.print(OUTPUT, HEX); FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.println(1, HEX); */ delay(20); } else { bluefruit.write(127); //FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.println(127, HEX); delay(20); continue; } if (BLE_Firmata.IS_PIN_ANALOG(pin)) { bluefruit.write(ANALOG); bluefruit.write(10); //FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.print(ANALOG, HEX); FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.println(10, HEX); delay(20); } if (BLE_Firmata.IS_PIN_PWM(pin)) { bluefruit.write(PWM); bluefruit.write(8); //FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.print(PWM, HEX); FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.println(8, HEX); delay(20); } if (BLE_Firmata.IS_PIN_SERVO(pin)) { bluefruit.write(SERVO); bluefruit.write(14); //FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.print(SERVO, HEX);FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.println(14, HEX); delay(20); } if (BLE_Firmata.IS_PIN_I2C(pin)) { bluefruit.write(I2C); bluefruit.write(1); // to do: determine appropriate value delay(20); } bluefruit.write(127); //FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.println(127, HEX); } bluefruit.write(END_SYSEX); //FIRMATADEBUG.print(" 0x"); FIRMATADEBUG.println(END_SYSEX, HEX); break; case PIN_STATE_QUERY: if (argc > 0) { byte pin=argv[0]; bluefruit.write(START_SYSEX); bluefruit.write(PIN_STATE_RESPONSE); bluefruit.write(pin); if (pin < TOTAL_PINS) { bluefruit.write((byte)pinConfig[pin]); bluefruit.write((byte)pinState[pin] & 0x7F); if (pinState[pin] & 0xFF80) bluefruit.write((byte)(pinState[pin] >> 7) & 0x7F); if (pinState[pin] & 0xC000) bluefruit.write((byte)(pinState[pin] >> 14) & 0x7F); } bluefruit.write(END_SYSEX); } break; case ANALOG_MAPPING_QUERY: //FIRMATADEBUG.println("Analog mapping query"); bluefruit.write(START_SYSEX); bluefruit.write(ANALOG_MAPPING_RESPONSE); for (byte pin=0; pin < TOTAL_PINS; pin++) { bluefruit.write(BLE_Firmata.IS_PIN_ANALOG(pin) ? BLE_Firmata.PIN_TO_ANALOG(pin) : 127); } bluefruit.write(END_SYSEX); break; } } void enableI2CPins() { byte i; // is there a faster way to do this? would probaby require importing // Arduino.h to get SCL and SDA pins for (i=0; i < TOTAL_PINS; i++) { if(BLE_Firmata.IS_PIN_I2C(i)) { // mark pins as i2c so they are ignore in non i2c data requests setPinModeCallback(i, I2C); } } isI2CEnabled = true; // is there enough time before the first I2C request to call this here? Wire.begin(); } /* disable the i2c pins so they can be used for other functions */ void disableI2CPins() { isI2CEnabled = false; // disable read continuous mode for all devices queryIndex = -1; // uncomment the following if or when the end() method is added to Wire library // Wire.end(); } /*============================================================================== * SETUP() *============================================================================*/ void systemResetCallback() { // initialize a defalt state FIRMATADEBUG.println(F("***RESET***")); // TODO: option to load config from EEPROM instead of default if (isI2CEnabled) { disableI2CPins(); } for (byte i=0; i < TOTAL_PORTS; i++) { reportPINs[i] = false; // by default, reporting off portConfigInputs[i] = 0; // until activated previousPINs[i] = 0; } // pins with analog capability default to analog input // otherwise, pins default to digital output for (byte i=0; i < TOTAL_PINS; i++) { if (BLE_Firmata.IS_PIN_ANALOG(i)) { // turns off pullup, configures everything setPinModeCallback(i, ANALOG); } else { // sets the output to 0, configures portConfigInputs setPinModeCallback(i, INPUT); } } // by default, do not report any analog inputs analogInputsToReport = 0; /* send digital inputs to set the initial state on the host computer, * since once in the loop(), this firmware will only send on change */ /* TODO: this can never execute, since no pins default to digital input but it will be needed when/if we support EEPROM stored config for (byte i=0; i < TOTAL_PORTS; i++) { outputPort(i, readPort(i, portConfigInputs[i]), true); } */ } void setup() { if (WAITFORSERIAL) { while (!FIRMATADEBUG) delay(1); } FIRMATADEBUG.begin(9600); FIRMATADEBUG.println(F("Adafruit Bluefruit nRF8001 Firmata test")); FIRMATADEBUG.print("Total pins: "); FIRMATADEBUG.println(NUM_DIGITAL_PINS); FIRMATADEBUG.print("Analog pins: "); FIRMATADEBUG.println(sizeof(boards_analogiopins)); //for (uint8_t i=0; i samplingInterval) { previousMillis += samplingInterval; /* ANALOGREAD - do all analogReads() at the configured sampling interval */ for(pin=0; pin