// WhiteBox Labs -- Tentacle Shield -- UART asynchronous example
// https://www.whiteboxes.ch/tentacle
//
//
// This code is intended to work on all Arduinos. If using the Arduino Yun, connect
// to it's serial port. If you want to work with the Yun wirelessly, check out the respective
// Yun version of this example.
// It will allow you to control up to 8 Atlas Scientific devices through the I2C bus.
//
// This example shows how to take readings from the sensors in an asynchronous way, completely
// without using any delays. This allows to do other things while waiting for the sensor data.
// To demonstrate the behaviour, we will blink a simple led in parallel to taking the readings.
// Notice how the led blinks at the desired frequency, not disturbed by the other tasks the
// Arduino is doing.
//
//
// USAGE:
//---------------------------------------------------------------------------------------------
// - Set all your EZO circuits to UART, 38400 baud before using this sketch.
// - You can use the "tentacle-steup.ino" sketch to do so)
// - Non-EZO (legacy) circuits are supported
// - Change the variables NUM_CIRCUITS, channel_ids and channel_names to reflect your setup
// - Set host serial terminal to 9600 baud
//
//---------------------------------------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
//---------------------------------------------------------------------------------------------
#include //Include the software serial library
SoftwareSerial sSerial(11, 10); // RX, TX - Name the software serial library sftSerial (this cannot be omitted)
// assigned to pins 10 and 11 for maximum compatibility
#define NUM_CIRCUITS 4 // <-- CHANGE THIS | set how many UART circuits are attached to the Tentacle
#define baud_host 9600 // set baud rate for host serial monitor(pc/mac/other)
const unsigned int send_readings_every = 5000; // set at what intervals the readings are sent to the computer (NOTE: this is not the frequency of taking the readings!)
unsigned long next_serial_time;
#define baud_circuits 38400 // NOTE: older circuit versions have a fixed baudrate (e.g. 38400. pick a baudrate that all your circuits understand and configure them accordingly)
int s0 = 7; // Tentacle uses pin 7 for multiplexer control S0
int s1 = 6; // Tentacle uses pin 6 for multiplexer control S1
int enable_1 = 5; // Tentacle uses pin 5 to control pin E on shield 1
int enable_2 = 4; // Tentacle uses pin 4 to control pin E on shield 2
char sensordata[30]; // A 30 byte character array to hold incoming data from the sensors
byte sensor_bytes_received = 0; // We need to know how many characters bytes have been received
byte code = 0; // used to hold the I2C response code.
byte in_char = 0; // used as a 1 byte buffer to store in bound bytes from the I2C Circuit.
char *channel_names[] = {"DO", "ORP", "PH", "EC"}; // <-- CHANGE THIS. A list of channel names (this list should have TOTAL_CIRCUITS entries)
// only used to designate the readings in serial communications
// position in array defines the serial channel. e.g. channel_names[0] is channel 0 on the shield; "PH" in this case.
String readings[NUM_CIRCUITS]; // an array of strings to hold the readings of each channel
int channel = 0; // INT pointer to hold the current position in the channel_ids/channel_names array
const unsigned int reading_delay = 100; // delay between each reading.
// low values give fast reading updates, <1 sec per circuit.
// high values give your Ardino more time for other stuff
unsigned long next_reading_time; // holds the time when the next reading should be ready from the circuit
boolean request_pending = false; // wether or not we're waiting for a reading
const unsigned int blink_frequency = 250; // the frequency of the led blinking, in milliseconds
unsigned long next_blink_time; // holds the next time the led should change state
boolean led_state = LOW; // keeps track of the current led state
void setup() {
pinMode(13, OUTPUT); // set the led output pin
pinMode(s0, OUTPUT); // set the digital output pins for the serial multiplexer
pinMode(s1, OUTPUT);
pinMode(enable_1, OUTPUT);
pinMode(enable_2, OUTPUT);
Serial.begin(baud_host); // Set the hardware serial port to 9600
sSerial.begin(baud_circuits); // Set the soft serial port to 9600 (change if all your devices use another baudrate)
next_serial_time = millis() + send_readings_every; // calculate the next point in time we should do serial communications
next_reading_time = millis() + reading_delay;
Serial.println("-----");
}
void loop() {
do_sensor_readings();
do_serial();
// Do other stuff here (Blink Leds, update a display, etc)
blink_led();
}
// blinks a led on pin 13 asynchronously
void blink_led() {
if (millis() >= next_blink_time) { // is it time for the blink already?
led_state = !led_state; // toggle led state on/off
digitalWrite(13, led_state); // write the led state to the led pin
next_blink_time = millis() + blink_frequency; // calculate the next time a blink is due
}
}
// do serial communication in a "asynchronous" way
void do_serial() {
if (millis() >= next_serial_time) { // is it time for the next serial communication?
Serial.println("-------------");
for (int i = 0; i < NUM_CIRCUITS; i++) { // loop through all the sensors
Serial.print(channel_names[i]); // print channel name
Serial.print(":\t");
Serial.println(readings[i]); // print the actual reading
}
Serial.println("-");
next_serial_time = millis() + send_readings_every;
}
}
// take sensor readings in a "asynchronous" way
void do_sensor_readings() {
if (request_pending) { // is a request pending?
while (sSerial.available()) { // while there is data available from the circuit
char c = sSerial.read(); // read the next available byte from the circuit
if (c=='\r') { // in case it's a character, we reached the end of a message
sensordata[sensor_bytes_received] = 0; // terminate the string with a \0 character
readings[channel] = sensordata; // update the readings array with this circuits data
// un-comment to see the real update frequency of the readings / debug
//Serial.print(channel_names[channel]);
//Serial.print(" update:\t");
//Serial.println(readings[channel]);
sensor_bytes_received = 0; // reset data counter
memset(sensordata, 0, sizeof(sensordata)); // clear sensordata array;
request_pending = false; // toggle request_pending
next_reading_time = millis()+reading_delay; // schedule the reading of the next sensor
break; // get out of this while loop, we have our data and don't care about the rest in the buffer
} else {
sensordata[sensor_bytes_received] = c;
sensor_bytes_received++;
}
} // end while
} else { // no request is pending,
if (millis()>next_reading_time) {
switch_channel(); // switch to the next channel
request_reading(); // do the actual UART communication
}
}
}
void switch_channel() {
channel = (channel + 1) % NUM_CIRCUITS; // switch to the next channel (increase current channel by 1, and roll over if we're at the last channel using the % modulo operator)
open_channel(); // configure the multiplexer for the new channel - we "hot swap" the circuit connected to the softSerial pins
sSerial.flush(); // clear out everything that is in the buffer already
}
// Request a reading from the current channel
void request_reading() {
request_pending = true;
sSerial.print("r\r"); // carriage return to terminate message
}
// Open a channel via the Tentacle serial multiplexer
void open_channel() {
switch (channel) {
case 0: // if channel==0 then we open channel 0
digitalWrite(enable_1, LOW); // setting enable_1 to low activates primary channels: 0,1,2,3
digitalWrite(enable_2, HIGH); // setting enable_2 to high deactivates secondary channels: 4,5,6,7
digitalWrite(s0, LOW); // S0 and S1 control what channel opens
digitalWrite(s1, LOW); // S0 and S1 control what channel opens
break;
case 1:
digitalWrite(enable_1, LOW);
digitalWrite(enable_2, HIGH);
digitalWrite(s0, HIGH);
digitalWrite(s1, LOW);
break;
case 2:
digitalWrite(enable_1, LOW);
digitalWrite(enable_2, HIGH);
digitalWrite(s0, LOW);
digitalWrite(s1, HIGH);
break;
case 3:
digitalWrite(enable_1, LOW);
digitalWrite(enable_2, HIGH);
digitalWrite(s0, HIGH);
digitalWrite(s1, HIGH);
break;
case 4:
digitalWrite(enable_1, HIGH);
digitalWrite(enable_2, LOW);
digitalWrite(s0, LOW);
digitalWrite(s1, LOW);
break;
case 5:
digitalWrite(enable_1, HIGH);
digitalWrite(enable_2, LOW);
digitalWrite(s0, HIGH);
digitalWrite(s1, LOW);
break;
case '6':
digitalWrite(enable_1, HIGH);
digitalWrite(enable_2, LOW);
digitalWrite(s0, LOW);
digitalWrite(s1, HIGH);
break;
case 7:
digitalWrite(enable_1, HIGH);
digitalWrite(enable_2, LOW);
digitalWrite(s0, HIGH);
digitalWrite(s1, HIGH);
break;
}
}