Вы находитесь на странице: 1из 39

Bluetooth Myocardial

Temperature Monitor
FINAL REPORT

APRIL 21,2017

Bicong Li, Fanghong Shen, Hehaoyu Zou

BME 401B, Group 23

Instructor: Dan Moran

TA: Nathan Kennedy Birenbaum


Contents
I. Design Specification....2

II. Parts List...2

III. Schematic Diagrams...5

IV. Design Safe & Web Page...7

V. Conclusions..8

VI. References.11

VII. Appendix A---Arduino IDE Code.12

VIII. Appendix B---Adafruit Bluefruit LE Connect Code...24

IX. Appendix C--Data sheets of parts...34

1
I. Design Specification

Feature Specification

Accuracy 0.2

Range 0~40

Wireless transmission distance 6~10m

Needle stability The needle is expected to stand on its own


without falling off during surgery

Automatic alert-triggering Customizable desired monitoring range

Alarming Consecutive until back to safe range

Needle length 8mm/15mm/30mm

Table1. Design specifications of myocardial temperature sensor

All design specifications were met except for the wireless transmission distance. The

bluetooth connection is stable up until 8 meters. However, as the distance goes farther, the

signal becomes oscillating and is not reliable enough to provide accurate myocardial

temperature readings. This is due mainly to the limitation of bluetooth module. A longer

transmission distance can be acquired with a more robust bluetooth emitting device.

II. Parts List

1. Arduino Uno Microcontroller Board

Figure 1. Arduino Uno Microcontroller Board.


Source: http://www.arduino.org/products/boards/arduino-uno

This board was borrowed from the Physiology Lab by courtesy of Professor Widder.

2
2. Analog Technology ATH10KR8BT65 Thermistor

Figure 2. ATH10KR8BT65 Thermistor


Source: http://shop.analogtechnologies.com/ATH10KR8BT65-ATH10KR8BT65.htm
3. Hollowed surgical needle

Figure 3. Hollow surgical needle


Source: https://www.amazon.com/Gauge-Sterilized-Piercing-Needles-Pack/dp/B002ZUDYH4

4. Adafruit nrf 8001 breakout Bluetooth Low Energy Module

Figure 4. nrf8001 Bluetooth module


Source: https://www.adafruit.com/product/1697

3
5. Miscellaneous Resistors

Figure 5. Miscellaneous resistors (left: 220 ohm, right: 10000 ohm)


Source: https://www.adafruit.com/product/2780

6. Piezo Speaker

Figure 6. Piezo Buzzer


Source: https://solarbotics.com/product/17855/

4
Part Name Part Number Price Source Lead Time

Arduino Uno Board A000066 $23.38 Arduino 3 weeks

BLE Module nrf8001 $19.95 Adafruit 16 weeks

Thermistor ATH10KR8BT65 $3.63 Analog Technologies 4 weeks

Surgical Needle B00HSXLEWK $0.40 Amazon N/A

Resistors CF14JT220R $0.10 Stackpole Electronics 14 weeks

Piezo Speaker 7BB-12-9 $0.51 Murata Electronics 7 weeks

Table 2. Part list, part number, price, source, and lead time

III. Schematic Diagrams

1. Circuit Drawing

Figure 7. Circuit diagram generated with Autocad Eagle


nrf8001 schematic downloaded from: https://github.com/adafruit/Adafruit-Bluefruit-LE-nRF8001-PCB
Arduino library schematic downloaded from:
https://www.element14.com/community/community/arduino/blog/2013/11/05/finally-an-arduino-library-for-eagle

5
2. Wiring Diagram

Figure 8. Wiring diagram generated with Fritzing

3. Mechanical Drawing

Figure 9. Mechanical drawing generated with Autocad. Units are in milimeter (mm).

6
IV. Design Safe & Webpage

Figure 10. Design Safe first half

Figure 11. Design Safe second half

Webpage: http://myocardial-temperature-monitor.weebly.com/

V. Conclusion

Problem Solved?

After seven months of hard work, our project finally comes into full shape. According

to validation results, the wireless needle was able to stand on its own even with moderate

maneuvers in the heart. The data is transmitted wirelessly to a computer and displayed

accurately on a computer screen. The alarm goes off when the temperature goes out of the

safe range and stops once it comes back. Therefore, we have completed all the three

features that we promised. To further improve our product, a microcontroller board could

help reduce the size of the current product employed on Arduino board. This product can be

employed in real open-heart surgeries and offer greater flexibility and stability, which is

exactly what our customer asked for in the first place.

7
Design Specification Met?

We have met the majority of the design specifications. The only shortcoming is that

our reliable transmission range did not go all the way up to 10 meters. The bluetooth

connection drops off at around 8 meters. That being said, a range of 8 meters shall probably

suffice for most of the operation rooms. We proposed 10 meters out of a bold intention of

covering beyond what is actually needed. Moreover, according to our clients input, a

surgeon is unlikely to place the monitor screen that far.

Improvements could have been made

In hindsight, there are several issues that we could have better dealt with. First, we

did not look as carefully as we should into the differences between various bluetooth

modules. Not until halfway into our programming did we realize nrf8001 may not be the best

fit for our purposes. There are better options such as the Feather product line which would

have enabled more robust wireless connection and more useful libraries. We were blinded

by the price at first and ended up simply purchasing the cheapest. We should have

compared the datasheet and available libraries across different product lines before coming

to a decision.

Second, our choice of the bluetooth receiving program is debatable. For novices to

OSX programming like we were when we started off, a ready-to-use app by courtesy of the

vendor seems definitely a first-choice. But after we have spent days and nights trying to

understand the source code without sufficient comments and figure out which lines to

modify, we realized a tradeoff between an easy start and a hard progress. We could have

paced ourselves and tried to learn some basics of the SWIFT language to begin with. Since

we were only reaching for 2 or 3 simple functions, building an app of our own should not be

too complex. More importantly, we would have been alleviated of the drudgery of having to

sift through an excess of irrelevant functions.

Third, one team member later learned from an elective course that designing a

microcontroller board and soldering other electronic would be a viable method. We used to

8
think it was impossible to downsize the microcontroller board with our current knowledge.

Though we are most likely to have a hard time making it work, we might be able to deliver a

ready-to-use product instead of a prototype.

Future Directions

Both the hardware and the software have space for improvement. As mentioned

above, the microcontroller board needs to be further downsized. 3D-printing alone might not

be able to make it small enough. We need to look for electronics manufacturer and get quote

for integration services. Ideally, all parts should fit into the needle tube in a fashion that

keeps the centroid close to the penetrating end. Otherwise, the needle would easily fall off.

Our software did work, but the user-interface is still in its crudest form. By far, we have not

put any efforts in designing the UI. In the future, we will make it more visually pleasant as

well as more user-friendly.

What did we learn?

We honed our programming skills in two common languages, both Arduino and

SWIFT, such as the ability to grasp the data structure of a given program and quickly locate

target functions among hundreds of lines. This skill is especially useful when we start off

from a pre-built program and want to make modifications according to our own needs.

We also apply the knowledge that we have learned in ESE 444 Sensors and actuators,

including Steinhart-Hart equation. We gained some important soft skills, too. We became

familiarized with project management. We learned how to plan ahead and make adjustments

down the way. This precious experience prepared us in advance for industry product

development in the future.

9
Ethical Consideration

Our product is mostly free of ethical concerns since we did not involve any living

tissues throughout the process except the final validation phase. To test performance on

myocardium, we bought a pig heart tissue from a supermarket. Since the butchery is

licensed and the experiment did not produce anything harmful, we are confident that our

procedure is ethical.

Intellectual Property

Intellectual property refers to creations of the mind: inventions; literary and artistic

works; and symbols, names and images used in commerce. There are two categories:

industrial property and copyright.1 Our product belongs to industrial property. According to

World Intellectual Property Organization(WIPO), a patent is an exclusive right granted for an

invention a product or process that provides a new way of doing something, or that offers a

new technical solution to a problem. Our design of the wireless temperature probe is indeed

substantially different from existing products, be its usage or mechanism. No Bluetooth

myocardial temperature monitor ever existed to date. In terms of definitions, our product is

the first one of its kind and we are supposed to apply for patent. However, unless we

succeed in integrating the Bluetooth module with the surgical needle so that it looks much

less grumpy, our product would not be of real value to surgeons. In a word, though we came

up with the first ever wireless myocardium temperature monitoring, we are not confident

enough of the feasibility and utility of our product. Instead of applying for patent, we prefer to

keep it open sourced so that anyone else interested can contribute to its improvement.

10
V. References

1. http://www.wipo.int/edocs/pubdocs/en/intproperty/450/wipo_pub_450.pdf

2. https://learn.adafruit.com/getting-started-with-the-nrf8001-bluefruit-le-breakout/pinout

3. https://learn.adafruit.com/getting-started-with-the-nrf8001-bluefruit-le-

breakout/hooking-everything-up

4. https://www.uspto.gov/web/offices/com/iip/pdf/brochure_05.pdf

5. https://github.com/adafruit/Adafruit_nRF8001

11
Appendix A. Arduino Firmata Protocol

#include <Servo.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_BLE_Firmata.h>
#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) {

12
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) {


// not used
// 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;

13
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_PORTS; i++) {
if (reportPINs[i]) {
// FIRMATADEBUG.print("Reporting on port "); FIRMATADEBUG.print(i); FIRMATADEBUG.print(" mask 0x");
FIRMATADEBUG.println(portConfigInputs[i], HEX);
uint8_t x = BLE_Firmata.readPort(i, portConfigInputs[i]);
//FIRMATADEBUG.print("Read 0x"); FIRMATADEBUG.println(x, HEX);
outputPort(i, x, forceSend);
}
}
}

// -----------------------------------------------------------------------------
/* 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)
{
//FIRMATADEBUG.print("Setting pin #"); FIRMATADEBUG.print(pin); FIRMATADEBUG.print(" to ");
FIRMATADEBUG.println(mode);
if ((pinConfig[pin] == I2C) && (isI2CEnabled) && (mode != I2C)) {
// disable i2c so pins can be used for other functions
// the following if statements should reconfigure the pins properly
disableI2CPins();
}
if (BLE_Firmata.IS_PIN_SERVO(pin) && mode != SERVO && servos[BLE_Firmata.PIN_TO_SERVO(pin)].attached()) {
servos[BLE_Firmata.PIN_TO_SERVO(pin)].detach();
}
if (BLE_Firmata.IS_PIN_ANALOG(pin)) {
reportAnalogCallback(BLE_Firmata.PIN_TO_ANALOG(pin), mode == ANALOG ? 1 : 0); // turn on/off reporting
}
if (BLE_Firmata.IS_PIN_DIGITAL(pin)) {
if (mode == INPUT) {
portConfigInputs[pin / 8] |= (1 << (pin & 7));
} else {

14
portConfigInputs[pin / 8] &= ~(1 << (pin & 7));
}
// FIRMATADEBUG.print(F("Setting pin #")); FIRMATADEBUG.print(pin); FIRMATADEBUG.print(F(" port config mask to =
0x"));
// FIRMATADEBUG.println(portConfigInputs[pin/8], HEX);
}
pinState[pin] = 0;
switch (mode) {
case ANALOG:
if (BLE_Firmata.IS_PIN_ANALOG(pin)) {
//FIRMATADEBUG.print(F("Set pin #")); FIRMATADEBUG.print(pin); FIRMATADEBUG.println(F(" to analog"));
if (BLE_Firmata.IS_PIN_DIGITAL(pin)) {
pinMode(BLE_Firmata.PIN_TO_DIGITAL(pin), INPUT); // disable output driver
}
pinConfig[pin] = ANALOG;
lastAnalogReads[BLE_Firmata.PIN_TO_ANALOG(pin)] = -1;
}
break;
case INPUT:
if (BLE_Firmata.IS_PIN_DIGITAL(pin)) {
//FIRMATADEBUG.print(F("Set pin #")); FIRMATADEBUG.print(pin); FIRMATADEBUG.println(F(" to input"));

if (AUTO_INPUT_PULLUPS) {
pinMode(BLE_Firmata.PIN_TO_DIGITAL(pin), INPUT_PULLUP); // disable output driver
} else {
pinMode(BLE_Firmata.PIN_TO_DIGITAL(pin), INPUT); // disable output driver
}
pinConfig[pin] = INPUT;

// force sending state immediately


//delay(10);
//checkDigitalInputs(true);
}
break;
case OUTPUT:
if (BLE_Firmata.IS_PIN_DIGITAL(pin)) {
//FIRMATADEBUG.print(F("Set pin #")); FIRMATADEBUG.print(pin); FIRMATADEBUG.println(F(" to output"));
digitalWrite(BLE_Firmata.PIN_TO_DIGITAL(pin), LOW); // disable PWM
pinMode(BLE_Firmata.PIN_TO_DIGITAL(pin), OUTPUT);
pinConfig[pin] = OUTPUT;
}
break;
case PWM:
if (BLE_Firmata.IS_PIN_PWM(pin)) {
//FIRMATADEBUG.print(F("Set pin #")); FIRMATADEBUG.print(pin); FIRMATADEBUG.println(F(" to PWM"));
pinMode(BLE_Firmata.PIN_TO_PWM(pin), OUTPUT);
analogWrite(BLE_Firmata.PIN_TO_PWM(pin), 0);
pinConfig[pin] = PWM;
}
break;
case SERVO:
if (BLE_Firmata.IS_PIN_SERVO(pin)) {
pinConfig[pin] = SERVO;
if (!servos[BLE_Firmata.PIN_TO_SERVO(pin)].attached()) {
servos[BLE_Firmata.PIN_TO_SERVO(pin)].attach(BLE_Firmata.PIN_TO_DIGITAL(pin));
}
}
break;
case I2C:
if (BLE_Firmata.IS_PIN_I2C(pin)) {
// mark the pin as i2c
// the user must call I2C_CONFIG to enable I2C for a device
pinConfig[pin] = I2C;
}
break;
default:
FIRMATADEBUG.print(F("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]) {

15
case SERVO:
if (BLE_Firmata.IS_PIN_SERVO(pin))
servos[BLE_Firmata.PIN_TO_SERVO(pin)].write(value);
pinState[pin] = value;
break;
case PWM:
if (BLE_Firmata.IS_PIN_PWM(pin))
analogWrite(BLE_Firmata.PIN_TO_PWM(pin), value);
//FIRMATADEBUG.print("pwm("); FIRMATADEBUG.print(BLE_Firmata.PIN_TO_PWM(pin)); FIRMATADEBUG.print(",");
FIRMATADEBUG.print(value); FIRMATADEBUG.println(")");
pinState[pin] = value;
break;
}
}
}

void digitalWriteCallback(byte port, int value)


{
//FIRMATADEBUG.print("DWCx"); FIRMATADEBUG.print(port, HEX); FIRMATADEBUG.print(" ");
FIRMATADEBUG.println(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 (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

16
// 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;

17
} 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 < queryIndex + 1; i++) {


if (i < MAX_QUERIES) {
query[i].addr = query[i + 1].addr;
query[i].reg = query[i + 1].addr;
query[i].bytes = query[i + 1].bytes;
}
}
queryIndex--;
}
break;
default:
break;
}
break;
case I2C_CONFIG:
delayTime = (argv[0] + (argv[1] << 7));

if (delayTime > 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);

18
//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:

19
//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()
{
FIRMATADEBUG.println("check check check systemResetCallback used");
// 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

20
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<sizeof(boards_analogiopins); i++) {
// FIRMATADEBUG.println(boards_analogiopins[i]);
//}

BLE_Firmata.setUsablePins(boards_digitaliopins, sizeof(boards_digitaliopins),
boards_analogiopins, sizeof(boards_analogiopins),
boards_pwmpins, sizeof(boards_pwmpins),
boards_servopins, sizeof(boards_servopins), SDA, SCL);

/* Initialise the module */


FIRMATADEBUG.print(F("Init nRF8001: "));
if (! bluefruit.begin()) {
error(F("Failed"));
}
bluefruit.setDeviceName("ADA_BLE");
FIRMATADEBUG.println(F("Done"));
BTLEstatus = lastBTLEstatus = ACI_EVT_DISCONNECTED;
}

void firmataInit() {
FIRMATADEBUG.println(F("Init firmata"));
//BLE_Firmata.setFirmwareVersion(FIRMATA_MAJOR_VERSION, FIRMATA_MINOR_VERSION);
//FIRMATADEBUG.println(F("firmata analog"));
BLE_Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
//FIRMATADEBUG.println(F("firmata digital"));
BLE_Firmata.attach(DIGITAL_MESSAGE, digitalWriteCallback);
//FIRMATADEBUG.println(F("firmata analog report"));
BLE_Firmata.attach(REPORT_ANALOG, reportAnalogCallback);
//FIRMATADEBUG.println(F("firmata digital report"));
BLE_Firmata.attach(REPORT_DIGITAL, reportDigitalCallback);
//FIRMATADEBUG.println(F("firmata pinmode"));
BLE_Firmata.attach(SET_PIN_MODE, setPinModeCallback);
//FIRMATADEBUG.println(F("firmata sysex"));
BLE_Firmata.attach(START_SYSEX, sysexCallback);
//FIRMATADEBUG.println(F("firmata reset"));
BLE_Firmata.attach(SYSTEM_RESET, systemResetCallback);

FIRMATADEBUG.println(F("Begin firmata"));
BLE_Firmata.begin();
systemResetCallback(); // reset to default config
}
/*==============================================================================
LOOP()
============================================================================*/

void loop()
{
// Check the BTLE link, how're we doing?
bluefruit.pollACI();
// Link status check
BTLEstatus = bluefruit.getState();

// Check if something has changed


if (BTLEstatus != lastBTLEstatus) {
// print it out!
if (BTLEstatus == ACI_EVT_DEVICE_STARTED) {
FIRMATADEBUG.println(F("* Advertising"));
}

21
if (BTLEstatus == ACI_EVT_CONNECTED) {
FIRMATADEBUG.println(F("* Connected!"));
// initialize Firmata cleanly
firmataInit();
}
if (BTLEstatus == ACI_EVT_DISCONNECTED) {
FIRMATADEBUG.println(F("* Disconnected"));
}
// OK set the last status change to this one
lastBTLEstatus = BTLEstatus;
}
// if not connected... bail
if (BTLEstatus != ACI_EVT_CONNECTED) {
delay(100);

return;
}

// For debugging, see if there's data on the serial console, we would forwad it to BTLE
if (FIRMATADEBUG.available()) {
bluefruit.write(FIRMATADEBUG.read());
}

// Onto the Firmata main loop

byte pin, analogPin;


int upperRange = 30;
int lowerRange = 15;

/* DIGITALREAD - as fast as possible, check for changes and output them to the
BTLE buffer using FIRMATADEBUG.print() */
checkDigitalInputs();

/* SERIALREAD - processing incoming messagse as soon as possible, while still


checking digital inputs. */
while (BLE_Firmata.available()) {
//FIRMATADEBUG.println(F("*data available*"));
BLE_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. */

// make the sampling interval longer if we have more analog inputs!


uint8_t analogreportnums = 0;
for (uint8_t a = 0; a < 8; a++) {
if (analogInputsToReport & (1 << a)) {
analogreportnums++;
}
}

samplingInterval = (uint16_t)MINIMUM_SAMPLE_DELAY + (uint16_t)ANALOG_SAMPLE_DELAY * (1 + analogreportnums);

currentMillis = millis();
if (currentMillis - previousMillis > samplingInterval) {
previousMillis += samplingInterval;
/* ANALOGREAD - do all analogReads() at the configured sampling interval */

for (pin = 0; pin < TOTAL_PINS; pin++) {


// FIRMATADEBUG.print("pin #"); FIRMATADEBUG.print(pin); FIRMATADEBUG.print(" config = ");
FIRMATADEBUG.println(pinConfig[pin]);
if (BLE_Firmata.IS_PIN_ANALOG(pin) && (pinConfig[pin] == ANALOG)) {
analogPin = BLE_Firmata.PIN_TO_ANALOG(pin);

if (analogInputsToReport & (1 << analogPin)) {


// float x = analogRead(analogPin);
// FIRMATADEBUG.print(F("x=")); FIRMATADEBUG.println(x);
// float n = 3450/(-log(1023/x-1)+3450/298.15)-273.15;
// FIRMATADEBUG.print(F("n=")); FIRMATADEBUG.println(n);
// int currentRead = analogRead(analogPin);

//if ((lastAnalogReads[analogPin] == -1) || (lastAnalogReads[analogPin] != currentRead)) {


// if ((lastAnalogReads[analogPin] == -1) || (lastAnalogReads[analogPin] != n)) {
if (analogPin == byte(5)) {
float x = analogRead(analogPin);

22
FIRMATADEBUG.print(F("x=")); FIRMATADEBUG.println(x);
float n = 3450 / (-log(1023 / x - 1) + 3450 / d298.15) - 273.15;
if (n > upperRange||n < lowerRange ){
tone(6,500);
delay(100);
noTone(6);
delay(1);
}
FIRMATADEBUG.print(F("n=")); FIRMATADEBUG.println(n);
int currentRead = analogRead(analogPin);
FIRMATADEBUG.print(F("Analog")); FIRMATADEBUG.print(analogPin); FIRMATADEBUG.print(F(" = "));
FIRMATADEBUG.println(currentRead);
BLE_Firmata.sendAnalog(analogPin, n);
BLE_Firmata.sendAnalog(byte(4), n * 100);
//lastAnalogReads[analogPin] = currentRead;
}
//}
}
}
// report i2c data for all device with read continuous mode enabled
if (queryIndex > -1) {
for (byte i = 0; i < queryIndex + 1; i++) {
readAndReportData(query[i].addr, query[i].reg, query[i].bytes);
}
}
Appendix B: Adafruit Bluefruit LE Connect Code

Source code downloaded from: https://github.com/adafruit/Bluefruit_LE_Connect_v2

import Foundation

protocol PinIOModuleManagerDelegate: class {


func onPinIODidEndPinQuery(isDefaultConfigurationAssumed: Bool)
func onPinIODidReceivePinState()
}

class PinIOModuleManager: NSObject {


// Config
private let CAPABILITY_QUERY_TIMEOUT = 5.0 // in seconds

// Constants
private let SYSEX_START: UInt8 = 0xF0
private let SYSEX_END: UInt8 = 0xF7

private let DEFAULT_PINS_COUNT = 20


private let FIRST_DIGITAL_PIN = 3
private let LAST_DIGITAL_PIN = 8
private let FIRST_ANALOG_PIN = 14
private let LAST_ANALOG_PIN = 19

// Types
enum UartStatus {
case InputOutput // Default mode (sending and receiving pin data)
case QueryCapabilities
case QueryAnalogMapping
}

class PinData {
enum Mode: UInt8 {
case Unknown = 255
case Input = 0 // Don't chage the values (these are the bytes defined by firmata spec)
case Output = 1

case Analog = 2
case PWM = 3
case Servo = 4
}

enum DigitalValue: Int{


case Low = 0
case High = 1

23
}

var digitalPinId: Int = -1


var analogPinId: Int = -1

var isDigital: Bool


var isAnalog: Bool
var isPWM: Bool

var mode = Mode.Input


var digitalValue = DigitalValue.Low
var analogValue: Int = 0

init(digitalPinId: Int, isDigital: Bool, isAnalog: Bool, isPWM: Bool) {


self.digitalPinId = digitalPinId
self.isDigital = isDigital
self.isAnalog = isAnalog
self.isPWM = isPWM
}
}

// Data
private var uartStatus = UartStatus.InputOutput
private var queryCapabilitiesTimer : NSTimer?

var pins = [PinData]()

weak var delegate: PinIOModuleManagerDelegate?

var digitalPinCount: Int {


return pins.filter{$0.isDigital}.count
}

var analogPinCount: Int {


return pins.filter{$0.isAnalog}.count
}

override init() {
super.init()
}

deinit {
cancelQueryCapabilitiesTimer()
}

func isQueryingCapabilities() -> Bool {


return uartStatus != .InputOutput
}

func start() {
DLog("pinio start");
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: #selector(PinIOModuleManager.didReceiveData(_:)), name:
UartManager.UartNotifications.DidReceiveData.rawValue, object: nil)
}

func stop() {
DLog("pinio stop");
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.removeObserver(self, name: UartManager.UartNotifications.DidReceiveData.rawValue,
object: nil)

// Cancel pending queries

24
cancelQueryCapabilitiesTimer()
}

// MARK: Notifications
func didReceiveData(notification: NSNotification) {
if let dataChunk = notification.userInfo?["dataChunk"] as? UartDataChunk {
// DLog("pin io received: \(hexString(dataChunk.data))")
switch uartStatus {
case .QueryCapabilities:
receivedQueryCapabilities(dataChunk.data)
case .QueryAnalogMapping:
receivedAnalogMapping(dataChunk.data)
default:
receivedPinState(dataChunk.data)
break
}
}
}

// MARK: - Query Capabilities


func reset() {
uartStatus == .InputOutput
pins = []

// Reset Firmata
let bytes:[UInt8] = [0xff]
let data = NSData(bytes: bytes, length: bytes.count)
UartManager.sharedInstance.sendData(data)
}

private var queryCapabilitiesDataBuffer = [UInt8]()


func queryCapabilities() {
DLog("queryCapabilities")

// Set status
pins = []
self.uartStatus = .QueryCapabilities
self.queryCapabilitiesDataBuffer.removeAll()

// Query Capabilities
let bytes:[UInt8] = [SYSEX_START, 0x6B, SYSEX_END]
let data = NSData(bytes: bytes, length: bytes.count)
UartManager.sharedInstance.sendData(data)

self.queryCapabilitiesTimer = NSTimer.scheduledTimerWithTimeInterval(self.CAPABILITY_QUERY_TIMEOUT,
target: self, selector: #selector(PinIOModuleManager.cancelQueryCapabilities), userInfo: nil, repeats: false)
}

private func receivedQueryCapabilities(data: NSData) {

// Read received packet


var dataBytes = [UInt8](count: data.length, repeatedValue: 0)
data.getBytes(&dataBytes, length: data.length)

for byte in dataBytes {


queryCapabilitiesDataBuffer.append(byte)
if byte == SYSEX_END {
DLog("Finished receiving Capabilities")
queryAnalogMapping()
break
}
}
}

private func cancelQueryCapabilitiesTimer() {


queryCapabilitiesTimer?.invalidate()
queryCapabilitiesTimer = nil

25
}

// MARK: - Query AnalogMapping


private var queryAnalogMappingDataBuffer = [UInt8]()

private func queryAnalogMapping() {


DLog("queryAnalogMapping")

// Set status
self.uartStatus = .QueryAnalogMapping
self.queryAnalogMappingDataBuffer.removeAll()

// Query Analog Mapping


let bytes:[UInt8] = [self.SYSEX_START, 0x69, self.SYSEX_END]
let data = NSData(bytes: bytes, length: bytes.count)
UartManager.sharedInstance.sendData(data)
}

private func receivedAnalogMapping(data: NSData) {


cancelQueryCapabilitiesTimer()

// Read received packet


var dataBytes = [UInt8](count: data.length, repeatedValue: 0)
data.getBytes(&dataBytes, length: data.length)

for byte in dataBytes {


queryAnalogMappingDataBuffer.append(byte)
if byte == SYSEX_END {
DLog("Finished receiving Analog Mapping")
endPinQuery(false)
break
}
}
}

func cancelQueryCapabilities() {
DLog("timeout: cancelQueryCapabilities")
endPinQuery(true)
}

// MARK: - Process Capabilities


func endPinQuery(abortQuery: Bool) {
cancelQueryCapabilitiesTimer()
uartStatus = .InputOutput

var capabilitiesParsed = false


var mappingDataParsed = false
if !abortQuery && queryCapabilitiesDataBuffer.count > 0 && queryAnalogMappingDataBuffer.count > 0 {
capabilitiesParsed = parseCapabilities(queryCapabilitiesDataBuffer)
mappingDataParsed = parseAnalogMappingData(queryAnalogMappingDataBuffer)
}

let isDefaultConfigurationAssumed = abortQuery || !capabilitiesParsed || !mappingDataParsed


if isDefaultConfigurationAssumed {
initializeDefaultPins()
}
enableReadReports()

// Clean received data


queryCapabilitiesDataBuffer.removeAll()
queryAnalogMappingDataBuffer.removeAll()

// Refresh
delegate?.onPinIODidEndPinQuery(isDefaultConfigurationAssumed)
}

private func parseCapabilities(cababilitiesData : [UInt8]) -> Bool {


let endIndex = cababilitiesData.indexOf(SYSEX_END)
guard cababilitiesData.count > 2 && cababilitiesData[0] == SYSEX_START && cababilitiesData[1] == 0x6C &&
endIndex != nil else {
DLog("invalid capabilities received")
return false

26
}

// Separate pin data


var pinsBytes = [[UInt8]]()
var currentPin = [UInt8]()
for i in 2..<endIndex! { // Skip 2 header bytes and end byte
let dataByte = cababilitiesData[i]
if dataByte != 0x7f {
currentPin.append(dataByte)
}
else { // Finished current pin
pinsBytes.append(currentPin)
currentPin = []
}
}

// Extract pin info


self.pins = []
var pinNumber = 0
for pinBytes in pinsBytes {
var isInput = false, isOutput = false, isAnalog = false, isPWM = false

if pinBytes.count > 0 { // if is available


var i = 0
while i<pinBytes.count {
let byte = pinBytes[i]
switch byte {
case 0x00:
isInput = true
i += 1 // skip resolution byte
case 0x01:
isOutput = true
i += 1 // skip resolution byte
case 0x02:
isAnalog = true
i += 1 // skip resolution byte
case 0x03:
isPWM = true
i += 1 // skip resolution byte
case 0x04:
// Servo
i += 1 //skip resolution byte
case 0x06:
// I2C
i += 1 // skip resolution byte
default:
break
}
i += 1
}

let pinData = PinData(digitalPinId: pinNumber, isDigital: isInput && isOutput, isAnalog: isAnalog, isPWM:
isPWM)
DLog("pin id: \(pinNumber) digital: \(pinData.isDigital) analog: \(pinData.isAnalog)")
self.pins.append(pinData)
}

pinNumber += 1
}

return true
}

private func parseAnalogMappingData(analogData : [UInt8]) -> Bool {


let endIndex = analogData.indexOf(SYSEX_END)
guard analogData.count > 2 && analogData[0] == SYSEX_START && analogData[1] == 0x6A && endIndex !=
nil else {
DLog("invalid analog mapping received")
return false
}

var pinNumber = 0
for i in 2..<endIndex! { // Skip 2 header bytes and end byte
let dataByte = analogData[i]
if dataByte != 0x7f {

27
if let indexOfPinNumber = indexOfPinWithDigitalId(pinNumber) {
pins[indexOfPinNumber].analogPinId = Int(dataByte)
DLog("pin id: \(pinNumber) analog id: \(Int(dataByte))")
}
else {
DLog("warning: trying to set analog id: \(Int(dataByte)) for pin id: \(pinNumber)");
}
}
pinNumber += 1
}

return true
}

private func indexOfPinWithDigitalId(digitalPinId: Int) -> Int? {


return pins.indexOf { (pin) -> Bool in
pin.digitalPinId == digitalPinId
}
}

private func indexOfPinWithAnalogId(analogPinId: Int) -> Int? {


return pins.indexOf { (pin) -> Bool in
pin.analogPinId == analogPinId
}
}

// MARK: - Pin Management


private func initializeDefaultPins() {
pins.removeAll()

for i in 0..<DEFAULT_PINS_COUNT {
var pin: PinData!
if (i == 3 || i == 5 || i == 6) { // PWM pins
pin = PinData(digitalPinId: i,isDigital: true, isAnalog: false, isPWM: false)
}
else if (i >= FIRST_DIGITAL_PIN && i <= LAST_DIGITAL_PIN) { // Digital pin
pin = PinData(digitalPinId: i, isDigital: true, isAnalog: false, isPWM: false)
}
else if (i >= FIRST_ANALOG_PIN && i <= LAST_ANALOG_PIN) { // Analog pin
pin = PinData(digitalPinId: i, isDigital: true, isAnalog: true, isPWM: false)
pin.analogPinId = i-FIRST_ANALOG_PIN
}

if let pin = pin {


pins.append(pin)
}
}
}

private func enableReadReports() {

//Enable Read Reports by port


let ports:[UInt8] = [0,1,2]
for port in ports {
let data0:UInt8 = 0xD0 + port // start port 0 digital reporting (0xD0 + port#)
let data1:UInt8 = 1 // enable
let bytes:[UInt8] = [data0, data1]
let data = NSData(bytes: bytes, length: 2)
UartManager.sharedInstance.sendData(data)
}

//Set all pin modes active


for pin in pins {
// Write pin mode
setControlMode(pin, mode: pin.mode)
}
}

func setControlMode(pin: PinData, mode: PinData.Mode) {


let previousMode = pin.mode

// Store
pin.mode = mode
pin.digitalValue = .Low // Reset dialog value when chaning mode

28
pin.analogValue = 0 // Reset analog value when chaging mode

//DLog("pin \(pin.digitalPinId): mode: \(pin.mode.rawValue)")

// Write pin mode


let bytes:[UInt8] = [0xf4, UInt8(pin.digitalPinId), mode.rawValue]
let data = NSData(bytes: bytes, length: bytes.count)
UartManager.sharedInstance.sendData(data)

// Update reporting for Analog pins


if mode == .Analog {
setAnalogValueReporting(pin, enabled: true)
}
else if previousMode == .Analog {
setAnalogValueReporting(pin, enabled: false)
}
}

func setAnalogValueReporting(pin: PinData, enabled: Bool) {


// Write pin mode
let bytes:[UInt8] = [0xC0 + UInt8(pin.analogPinId), UInt8(enabled ?1:0)]
let data = NSData(bytes: bytes, length: bytes.count)
UartManager.sharedInstance.sendData(data)
}

func setDigitalValue(pin: PinData, value: PinData.DigitalValue) {


// Store
pin.digitalValue = value
DLog("setDigitalValue: \(value) for pin id: \(pin.digitalPinId)")

// Write value
let port = UInt8(pin.digitalPinId / 8)
let data0 = 0x90 + port

let offset = 8 * Int(port)


var state: Int = 0
for i in 0...7 {
if let pinIndex = indexOfPinWithDigitalId(offset + i) {
let pinValue = pins[pinIndex].digitalValue.rawValue & 0x1
let pinMask = pinValue << i
state |= pinMask
}
}

let data1 = UInt8(state & 0x7f) // only 7 bottom bits


let data2 = UInt8(state >> 7) // top bit in second byte

let bytes:[UInt8] = [data0, data1, data2]


let data = NSData(bytes: bytes, length: bytes.count)
UartManager.sharedInstance.sendData(data)
}

private var lastSentAnalogValueTime : NSTimeInterval = 0


func setPMWValue(pin: PinData, value: Int) -> Bool {

// Limit the amount of messages sent over Uart


let currentTime = CACurrentMediaTime()
guard currentTime - lastSentAnalogValueTime >= 0.05 else {
DLog("Won't send: Too many slider messages")
return false
}
lastSentAnalogValueTime = currentTime

// Store
pin.analogValue = value

// Send
let data0 = 0xe0 + UInt8(pin.digitalPinId)
let data1 = UInt8(value & 0x7f) //only 7 bottom bits
let data2 = UInt8(value >> 7) //top bit in second byte

let bytes:[UInt8] = [data0, data1, data2]

29
let data = NSData(bytes: bytes, length: bytes.count)
UartManager.sharedInstance.sendData(data)

return true
}

private var receivedPinStateDataBuffer = [UInt8]()

private func receivedPinState(data: NSData) {

// Append received bytes to buffer


var receivedDataBytes = [UInt8](count: data.length, repeatedValue: 0)
data.getBytes(&receivedDataBytes, length: data.length)
for byte in receivedDataBytes {
receivedPinStateDataBuffer.append(byte)
}

// Check if we received a pin state response


let endIndex = receivedPinStateDataBuffer.indexOf(SYSEX_END)
if receivedPinStateDataBuffer.count >= 5 && receivedPinStateDataBuffer[0] == SYSEX_START &&
receivedPinStateDataBuffer[1] == 0x6e && endIndex != nil {
/* pin state response
* -------------------------------
* 0 START_SYSEX (0xF0) (MIDI System Exclusive)
* 1 pin state response (0x6E)
* 2 pin (0 to 127)
* 3 pin mode (the currently configured mode)
* 4 pin state, bits 0-6
* 5 (optional) pin state, bits 7-13
* 6 (optional) pin state, bits 14-20
... additional optional bytes, as many as needed
* N END_SYSEX (0xF7)
*/

let pinDigitalId = Int(receivedPinStateDataBuffer[2])


let pinMode = PinData.Mode(rawValue: receivedPinStateDataBuffer[3])
let pinState = Int(receivedPinStateDataBuffer[4])

if let index = indexOfPinWithDigitalId(pinDigitalId), pinMode = pinMode {


let pin = pins[index]
pin.mode = pinMode
if (pinMode == .Analog || pinMode == .PWM || pinMode == .Servo) {
if receivedPinStateDataBuffer.count >= 6 {
let analogValue = pinState + (Int(receivedPinStateDataBuffer[5])<<7)
pin.analogValue = analogValue
}
else {
DLog("Warning: received pinstate for analog pin without analogValue");
}
}
else {
if let digitalValue = PinData.DigitalValue(rawValue: pinState) {
pin.digitalValue = digitalValue
}
else {
DLog("Warning: received pinstate with unknown digital value. Valid (0,1). Received: \(pinState)")
}
}
}
else {
DLog("Warning: received pinstate for unknown digital pin id: \(pinDigitalId)")
}

// Remove from the buffer the bytes parsed


if let endIndex = endIndex {
receivedPinStateDataBuffer.removeFirst(endIndex)
}
}
else {
// Each pin state message is 3 bytes long
var isDigitalReportingMessage = (receivedPinStateDataBuffer[0] >= 0x90) && (receivedPinStateDataBuffer[0]
<= 0x9F)
var isAnalogReportingMessage = (receivedPinStateDataBuffer[0] >= 0xE0) &&

30
(receivedPinStateDataBuffer[0] <= 0xEF)

while receivedPinStateDataBuffer.count >= 3 && (isDigitalReportingMessage || isAnalogReportingMessage)


// Check that current message length is at least 3 bytes
{
if isDigitalReportingMessage { // Digital Reporting (per port)
/* two byte digital data format, second nibble of byte 0 gives the port number (e.g. 0x92 is the third port,
port 2)
* 0 digital data, 0x90-0x9F, (MIDI NoteOn, but different data format)
* 1 digital pins 0-6 bitmask
* 2 digital pin 7 bitmask
*/

let port = Int(receivedPinStateDataBuffer[0]) - 0x90


var pinStates = Int(receivedPinStateDataBuffer[1])
pinStates |= Int(receivedPinStateDataBuffer[2]) << 7 // PORT 0: use LSB of third byte for pin7,
PORT 1: pins 14 & 15
updatePinsForReceivedStates(pinStates, port: port)
}
else if isAnalogReportingMessage { // Analog Reporting (per pin)

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

let analogPinId = Int(receivedPinStateDataBuffer[0]) - 0xE0


let value = Int(receivedPinStateDataBuffer[1]) + (Int(receivedPinStateDataBuffer[2])<<7)

if let index = indexOfPinWithAnalogId(analogPinId) {


let pin = pins[index]
pin.analogValue = value
}
else {
DLog("Warning: received pinstate for unknown analog pin id: \(index)")
}
}

// Remove from the buffer the bytes parsed


receivedPinStateDataBuffer.removeFirst(3)

// Setup vars for next message


if receivedPinStateDataBuffer.count >= 3 {
isDigitalReportingMessage = (receivedPinStateDataBuffer[0] >= 0x90) &&
(receivedPinStateDataBuffer[0] <= 0x9F)
isAnalogReportingMessage = (receivedPinStateDataBuffer[0] >= 0xE0) &&
(receivedPinStateDataBuffer[0] <= 0xEF)
}
else {
isDigitalReportingMessage = false
isAnalogReportingMessage = false
}
}

// Refresh UI
delegate?.onPinIODidReceivePinState()
}

private func updatePinsForReceivedStates(pinStates:Int, port:Int) {


let offset = 8 * port

// Iterate through all pins


for i in 0...7 {
let mask = 1 << i
let state = (pinStates & mask) >> i

let digitalId = offset + i

if let index = indexOfPinWithDigitalId(digitalId), digitalValue = PinData.DigitalValue(rawValue: state) {


let pin = pins[index]

31
pin.digitalValue = digitalValue
DLog("update pinid: \(digitalId) digitalValue: \(digitalValue)")
}
}
}

// MARK: - Utils
static func stringForPinMode(mode: PinIOModuleManager.PinData.Mode)-> String {
var modeString: String

switch mode {
case .Input:
modeString = "Input"
case .Output:
modeString = "Output"
case .Analog:
modeString = "Analog"
case .PWM:
modeString = "PWM"
case .Servo:
modeString = "Servo"
default:
modeString = "Unkwnown"
}

return modeString
}

static func stringForPinDigitalValue(digitalValue: PinIOModuleManager.PinData.DigitalValue)-> String {


var valueString: String

switch digitalValue {
case .Low:
valueString = "Low"
case .High:
valueString = "High"
}
return valueString
}
}

32
High Stability Miniature Thermistor
Analog Technologies ATH10KR8B
Note: This thermistor ATH10KR8B is a replacement for
ATH10KR8.
The ATH10KR8B is a high precision glass encapsulated
thermistor. Comparing with conventional epoxy encapsulated
thermistors, ATH10KR8B presents higher long term stability
and wider temperature range. In addition, it has a small size
and short response time. In addition, there are two insulation
versions available, one of which comes with leads covered by
plastic tubing, the ATH10KR8BT65, and the other one, the
ATH10KR8BT65S, is sealed between the head and the
tubing. They can work under up to 140C temperature and
the latter is of liquid resistant.
The ATH10KR8B series can be used to measure the
temperatures for laser diodes, optical components, etc., with
high accuracy and long term stability.
Figure1.1. The physical photo of ATH10KR8B

Figure 2. Side View of ATH10KR8B


SPECIFICATIONS
Value
Parameters
ATH10KR8B ATH10KR8
Nominal Resistance @ 25C 10K 1% 10K 1%
B Value @ 25C /85C 3478K 1% 3480K 1%
Figure 1.2 The physical photo of ATH10KR8BT65
B Value @ 0C /100C 3450K 1% 3450K 1%
MAIN FEATURES
B Value @ 25C /100C 3492K 1% 3497K 1%
Glass Encapsulated for Long Term Stability & Reliability
Thermistor Diameter 0.8 0.1mm 0.8 0.1mm
High Stability: <0.1C/Y
Thermistor Length 1.4 0.4mm 1.4 0.4mm
Small Size: 0.8mm1.4mm Lead Diameter 0.15mm 0.15mm
High Resistance Accuracy: 1% Lead Length 65 3mm 63 3mm
Short Response Time Dissipation Factor 0.4mW/K 0.4mW/K
Wide Temp. Range: 55C to 250C Heat Capacity 1.3mJ/K 1.3mJ/K
100 % Lead (Pb)-free and RoHS Compliant Maximum Power @ 25C 18mW 18mW
APPLICATIONS 0.14s (in water) 0.14s (in water)
Temperature sensing for laser diodes, optical components, etc. Time Constant 2~2.2s (in still 2~2.2s (in still
air @5~25C) air @5~25C)
DESCRIPTION
The ATH10KR8B series thermistor is consisted of three APPLICATION
versions, ATH10KR8B as shown in Figure 1.1, Drill a hole on the object for which the temperature needs to
ATH10KR8BT65 shown in Figure 1.2 and be measured and use thermally conductive epoxy to pot the
ATH10KR8BT65S. The ATH10KR8B has bear leads coated thermistor inside the hole. The hole diameter should be
with copper, the ATH10KR8BT65S has the leads covered by between 1.2 to 1.4mm and the depth should between 2 to
high temperature plastic tubing and sealed by epoxy, while 2.5mm. When a deeper hole is needed, drill a 2 stage hole to
the ATH10KR8BT65 is the non-sealed version. prevent mounting epoxy bobbles trapped inside which would

2352 Walsh Ave., Santa Clara, CA 95051, U. S. A. Tel.: (408) 748-9100, Fax: (408) 748-9111 www.analogtechnologies.com
Copyrights 2000-2016, Analog Technologies, Inc. All Rights Reserved. Updated on 5/4/2016 www.analogti.com 1
High Stability Miniature Thermistor
Analog Technologies ATH10KR8B
cause temperature measurement errors. Figure 3 shows the errors. To avoid the bubbles, use thin epoxy, vibrate the
section view of the 2 stage hole. assembly before curing, and cure the epoxy inside the
mounting hole at high temperature, 80C to 150C,
Thermal Conductive Epoxy depending on the epoxy used and the maximum temperature
3 5 mm assembly components allow.
The thermistor lead wires are made of plain copper and there
is no insulation coating on them, please make sure that they
2 2.5 mm do not touch each other after mounting the thermistor.
Some thermal conductive epoxies are also electrically
1.2 1.5 mm conductive and such epoxies should not be used for mounting
the thermistors, since the lead wires are conductive.
Figure 3. Section View of the 2 Stage Hole Notice: Glass encapsulated cannot be used in water or other
The worst mounting result is that there are air bubbles liquid directly.
trapped inside the thermistor mounting hole. These bubbles
cause thermal sensing time delay and sensing temperature
Resistance Temperature Characteristics
Table 1. ATH10KR8B vs. ATH10KR8

T R_nom () T R_nom ()
(C) ATH10KR8B ATH10KR8 (C) ATH10KR8B ATH10KR8
-55 526240 519911 50 4100 4103
-50 384520 379894 55 3479.8 3482
-45 284010 280697 60 2966.3 2967
-40 211940 209603 65 2539.2 2539
-35 159720 158088 70 2182.4 2182
-30 121490 120372 75 1883 1882
-25 93246 92484 80 1630.7 1629
-20 72181 71668 85 1417.4 1415
-15 56332 55993 90 1236.2 1234
-10 44308 44087 95 1081.8 1079
-5 35112 34971 100 949.73 946.6
0 28024 27936 105 836.4 833.1
5 22520 22468 110 738.81 735.5
10 18216 18187 115 654.5 651.1
15 14827 14813 120 581.44 578.1
20 12142 12136 125 517.94 514.6
25 10000 10000 130 462.59 459.4
30 8281.8 8284 135 414.2 411.1
35 6895.4 6899 140 371.79 368.8
40 5770.3 5774 145 334.51 331.6
45 4852.5 4856 150 301.66 298.9

2352 Walsh Ave., Santa Clara, CA 95051, U. S. A. Tel.: (408) 748-9100, Fax: (408) 748-9111 www.analogtechnologies.com
Copyrights 2000-2016, Analog Technologies, Inc. All Rights Reserved. Updated on 5/4/2016 www.analogti.com 2
High Stability Miniature Thermistor
Analog Technologies ATH10KR8B
T R_nom () T R_nom ()
(C) ATH10KR8B ATH10KR8 (C) ATH10KR8B ATH10KR8
155 272.64 270.0 205 110.51 109.1
160 246.94 244.4 210 101.94 100.7
165 224.14 221.7 215 94.181 93.01
170 203.85 201.6 220 87.144 86.08
175 185.77 183.6 225 807.51 79.78
180 169.61 167.6 230 74.933 74.05
185 155.14 153.3 235 69.631 68.83
190 142.16 140.4 240 64.791 64.08
195 130.49 128.9 245 60.366 59.73
200 119.99 118.5 250 56.316 55.75

Table 2. (Temperature coefficient at T in %/K)ATH10KR8B


T T T
(%/K) (%/K) (%/K)
(C) (C) (C)
-55 6.4 50 3.3 155 2.0
-50 6.2 55 3.2 160 2.0
-45 6.0 60 3.2 165 1.9
-40 5.8 65 3.1 170 1.9
-35 5.6 70 3.0 175 1.8
-30 5.4 75 2.9 180 1.8
-25 5.2 80 2.8 185 1.8
-20 5.0 85 2.8 190 1.7
-15 4.9 90 2.7 195 1.7
-10 4.7 95 2.6 200 1.7
-5 4.6 100 2.6 205 1.6
0 4.4 105 2.5 210 1.6
5 4.3 110 2.5 215 1.6
10 4.2 115 2.4 220 1.5
15 4.1 120 2.3 225 1.5
20 3.9 125 2.3 230 1.5
25 3.8 130 2.2 235 1.5
30 3.7 135 2.2 240 1.4
35 3.6 140 2.1 245 1.4
40 3.5 145 2.1 250 1.4
45 3.4 150 2.0

2352 Walsh Ave., Santa Clara, CA 95051, U. S. A. Tel.: (408) 748-9100, Fax: (408) 748-9111 www.analogtechnologies.com
Copyrights 2000-2016, Analog Technologies, Inc. All Rights Reserved. Updated on 5/4/2016 www.analogti.com 3
High Stability Miniature Thermistor
Analog Technologies ATH10KR8B
ORDERING INFORMATIONS
Table 2. Part Number of the Thermistors

Part # Description

ATH10KR8B High stability miniature thermistor with bare leads

ATH10KR8BT65 High stability miniature thermistor with leads covered by high temperature plastic tubing

High stability miniature thermistor with leads covered by high temperature plastic tubing and
ATH10KR8BT65S
sealed by epoxy

Table 3. Unit Price

Quantity 1-9 10 - 49 50 - 199 200 - 499 500


ATH10KR8B $3.43 $3.23 $3.08 $2.93 $2.78
ATH10KR8BT65 $3.63 $3.43 $3.28 $3.13 $2.98
ATH10KR8BT65S $3.83 $3.63 $3.48 $3.32 $3.18

NOTICE
1. ATI reserves the right to make changes to its products or to discontinue any product or service without notice, and advise
customers to obtain the latest version of relevant information to verify, before placing orders, that information being relied
on is current and complete.
2. All products are sold subject to the terms and conditions of sale supplied at the time of order acknowledgment, including
those pertaining to warranty, patent infringement, and limitation of liability. Testing and other quality control techniques are
utilized to the extent ATI deems necessary to support this warranty. Specific testing of all parameters of each device is not
necessarily performed, except those mandated by government requirements.
3. Customers are responsible for their applications using ATI components. In order to minimize risks associated with the
customers applications, adequate design and operating safeguards must be provided by the customers to minimize inherent
or procedural hazards. ATI assumes no liability for applications assistance or customer product design.
4. ATI does not warrant or represent that any license, either express or implied, is granted under any patent right, copyright,
mask work right, or other intellectual property right of ATI covering or relating to any combination, machine, or process in
which such products or services might be or are used. ATIs publication of information regarding any third partys products
or services does not constitute ATIs approval, warranty or endorsement thereof.
5. IP (Intellectual Property) Ownership: ATI retains the ownership of full rights for special technologies and/or techniques
embedded in its products, the designs for mechanics, optics, plus all modifications, improvements, and inventions made by
ATI for its products and/or projects.

2352 Walsh Ave., Santa Clara, CA 95051, U. S. A. Tel.: (408) 748-9100, Fax: (408) 748-9111 www.analogtechnologies.com
Copyrights 2000-2016, Analog Technologies, Inc. All Rights Reserved. Updated on 5/4/2016 www.analogti.com 4
...

The Arduino Uno is a microcontroller board based on the ATmega328 (datasheet). It has 14 digital
input/output pins (of which 6 can be used as PWM outputs), 6 analog inputs, a 16 MHz crystal oscillator, a
USB connection, a power jack, an ICSP header, and a reset button. It contains everything needed to support
the microcontroller; simply connect it to a computer with a USB cable or power it with a AC-to-DC adapter or
battery to get started.

The Uno differs from all preceding boards in that it does not use the FTDI USB-to-serial driver chip. Instead,
it features the Atmega8U2 programmed as a USB-to-serial converter.

"Uno" means "One" in Italian and is named to mark the upcoming release of Arduino 1.0. The Uno and
version 1.0 will be the reference versions of Arduino, moving forward. The Uno is the latest in a series of
USB Arduino boards, and the reference model for the Arduino platform; for a comparison with previous
versions, see the index of Arduino boards.

Techinal Specifications

Microcontroller ATmega328
Operating Voltage 5V
Supply Voltage (recommended) 7-12V
Maximum supply voltage (not recommended) 20V
Digital I/O Pins 14 (of which 6 provide PWM output)
Analog Input Pins 6
DC Current per I/O Pin 40 mA
DC Current for 3.3V Pin 50 mA
Flash Memory 32 KB (ATmega328) of which 0.5 KB used by bootloader
SRAM 2 KB (ATmega328)
EEPROM 1 KB (ATmega328)
Clock Speed 16 MHz
If you want to give a closer look to this board we advice you to visit the official Arduino UNO page in the
Hardware Section.

1
2