EnviroDIY / Arduino-SDI-12

An Arduino library for SDI-12 communication with a wide variety of environmental sensors. This library provides a general software solution, without requiring any additional hardware.
https://github.com/EnviroDIY/Arduino-SDI-12/wiki
BSD 3-Clause "New" or "Revised" License
159 stars 100 forks source link

8 MHz Arduino and SDI12 LIbrary #12

Closed JustinWhitaker closed 9 years ago

JustinWhitaker commented 9 years ago

I have been using your library for some remote soil moisture sensor stations for our strawberry nursery stock production. I'm using the Arduino Pro Mini 5V/16MHz versions and Decagon GS3 soil moisture sensors and everything is working great. I want to redesign my sensor stations to be a little bit smaller and less power hungry (i.e. smaller solar panel, less battery capacity, etc) so I started testing the Arduino Pro Mini 3.3V/8MHz versions and the SDI12 communications stopped working. I'm guessing it has something to do with the serial timing when moving from a 16 MHz device to an 8 MHz device, but I'm not familiar enough with the serial communication programming to know where to look or how to modify it to work at 8 MHz. Could your library be modified to work at 8 MHz? My initial thought was to double the SPACING constant since the speed is cut in half...am I even on the right track?

Thanks for your help in advance and thank you for making such a wonderful open-source SDI12 library in the first place.

PS - I emailed you also, but this seemed like a better place to ask my question...sorry for the double-post/email.

s-hicks2 commented 9 years ago

I use the SDI-12 library with dozens of Arduino Pro 3.3v/8mhz boards and haven't had any problems with the library. Are you connecting the excitation pin of the GS3 sensor to constant Vcc or are you powering it via one of the digital I/O pins?

JustinWhitaker commented 9 years ago

The sensor is connected to Vcc, so it has 5V. I'm sure the rest of the circuit is working, because it works fine with the 5v/16mHz board. I just unplug that one and plug in the 3.3v/8mHz board with the same sketch on it and it stops reading the sensors. I still get the serial communication from the arduino, it just doesn't have any results from the sensors in it like it does with the 5v version. I'm glad to hear that you have used the 3.3v version with no problems. I think I will just upload the sample SDI-12 sketch to the 3.3v board and try to get it to work like that. It may be a problem in my datalogger sketch that happens to work fine on the 5v board and not the 3.3v board.

Kevin-M-Smith commented 9 years ago

Hi Justin,

Glad to hear the library has been working for you!

The library should work with 8MHz - it uses Arduino base functions (e.g. delayMilliseconds()) that check for processor speed and adjust accordingly.

Officially, the current SDI-12 specification requires a 12 volt power line. Many manufacturers (like Decagon) design their sensors to work at lower voltages, but this is not guaranteed. I'd take a look at your data sheet to make sure the sensor is compatible with 3.3v. For debugging, you might also try to power the sensor from the 5 volt Arduino, and taking readings with the 3.3v. (Just remember to give them a common ground.)

Let me know how it goes!

Regards, Kevin

ref: http://sdi-12.org/specification.php

s-hicks2 commented 9 years ago

Kevin's comment about power supply was going to be my suggestion. The Decagon sensors specify at least 3.6v excitation voltage, however I've been successfully using them at 3.3v excitation. But the GS3 sensor uses at least 25ma during measurements, which is much higher current than any of the other Decagon sensors I've used. So it could be that the combination of the higher current draw and lower-than-normal excitation voltage could be the problem. You could also try connecting it to the Vin line of the Arduino Pro Mini, I assume you're powering it with a 3.7v lipo or something similar.

JustinWhitaker commented 9 years ago

Thanks for the suggestions, guys!

Yeah, the whole circuit is powered with a 3.7 lipo battery, but I have a boost converter to convert to 5V so the sensors had enough voltage. So then I used a 5v pro mini, but I had to use a logic level converter for my 900 mHz radio because it is a 3.3v device.

I was trying to convert the whole circuit to 3.3v to save some power (and make everything simpler with less parts) and try to get to a smaller solar panel, battery, enclosure, etc. Save for the GS3, I was going to run it directly from the battery because the datasheet for that sensor says it will run as low as 3.6v. The current draw from that sensor just may not allow me to do that, but I thought I'd give it a try. Then the sensors stopped reading when the first thing I tried was to switch out the pro mini for the 3.3v one.

I'll try to put the 5v board back in to run the datalogger and just read with the 3.3v one separately to see if it works, and I'll also try to power the sensor separately with 12v and see if it will read that way. Otherwise, I will just breadboard a circuit with the 3.3v board and sensor only with 12v power and nothing else and see if I can get it to read with one of the sample sketches, than add back in my datalogger code until I find what breaks it.

Thanks again for the help. I'll give it a try on Monday when I'm back in the office and let you guys know what I find out.

s-hicks2 commented 9 years ago

It's not necessary to run the GS3 at 12v excitation. It will definitely run just fine with the 3.7v of the battery, but you can try a separate 5v if you think it'll help. But according to the Integrator's Guide on Decagon's website, the voltage range of the TTL data output signal is 0 to 3v, no matter what the excitation voltage is, so using anything other than 3.7 or even 5v won't really help.

The other thing that may be causing a problem is the Arduino sketch code not waiting long enough after issuing the "M!" command. I had a problem with some of their newer sensors not liking that part of the code with some of my Arduino boards, so I changed my sketch to just wait a certain amount of time after the M! command (around 500ms), and then issue the D0! command. I think the examples that come with the library have the measurement time being determined by a value returned by the sensor when you initiate the measurement. But since all of the Decagon sensors return the measurement in about 300ms, I figure 500ms is a safe range, and it hasn't failed me yet. I have another sensor that takes 6 seconds to return data after a measurement, so I just hard-coded it to wait 7 seconds before asking for the data. Are you using the example code from the library to poll the sensor for SDI-12 data?

JustinWhitaker commented 9 years ago

I actually had that problem also when I first put these together this spring...I was getting intermittent results from the sensors. I was using the sample code (or parts of it any way) and it would take the response from the M! command and parse out the wait time, then wait that much time (or the sensor could interrupt the wait at any time) then move on to the D! command. My sensors were sometimes not returning values and when I investigated that one, I saw that the sensors were sending a service request when they weren't supposed to...i.e. even if you wait the entire wait time. That service request would then be in the buffer when you went to read the sensor data and it fouled up the whole data collection routine. So I put a hard-coded wait time of 2 seconds after the M! command and that seemed to fix that issue at least, but maybe I need longer wait time still?

Here is my code in case you see anything that I might be missing:

#include <SDI12.h>

const int LED_PIN = 13;
const int DATA_PIN = 3;
const int RADIO_VCC = 10;
const int BATTERY_VOLTS_PIN = A0;
const int SOLAR_VOLTS_PIN = A1;
const int IRRIGATE_PIN = 2;
const int NUM_SENSORS = 3;
const char SENSOR_ADDR[NUM_SENSORS] = {'1', '2', '3'}; // SDI-12 sensor addresses

int sensorIndex = 0;
String sensorData[NUM_SENSORS][3] = {{0,0,0},{0,0,0},{0,0,0}}; // [Rows][Columns]
unsigned long sendDataTimer;
unsigned long previousSendData;
unsigned long sendDataInterval = 60000;
float solarVolts = 0.0;
float batteryVolts = 0.0;
int irrigate = 0;

SDI12 mySDI12(DATA_PIN);

void setup() {
  // Setup pins
  pinMode(RADIO_VCC, OUTPUT);
  digitalWrite(RADIO_VCC, HIGH); // High side p-channel MOSFET, HIGH is OFF
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  pinMode(BATTERY_VOLTS_PIN, INPUT);
  pinMode(SOLAR_VOLTS_PIN, INPUT);
  pinMode(IRRIGATE_PIN, INPUT_PULLUP);

  // Initialize serial port
  Serial.begin(9600);

  // Initialize SDI12
  mySDI12.begin();

  delay(1000); // allow things to settle

  sendData();
}

void loop() {
  sendDataTimer = millis();
  if (sendDataTimer - previousSendData > sendDataInterval) {
    previousSendData = sendDataTimer;
    sendData();
  }
}

void sendData() {
  updateSoilData();

  digitalWrite(RADIO_VCC, LOW); // Turn on radio
  delay(2000); // Let radio initialize

  // Send data out serial port
  Serial.print("*");
  for (int i = 0; i < NUM_SENSORS; i++) {
    Serial.print(SENSOR_ADDR[i]);
    Serial.print(",");
    for (int j = 0; j < 3; j++) {
      Serial.print(sensorData[i][j]);
      Serial.print(",");
    }
  }
  Serial.print(solarVolts);
  Serial.print(",");
  Serial.print(batteryVolts);
  Serial.print(",");
  Serial.print(irrigate);
  Serial.println('#');

  delay(1000);
  digitalWrite(RADIO_VCC, HIGH); // Turn off radio

  // Zero out sensor data array
  for (int i = 0; i < NUM_SENSORS; i++) {
    for (int j = 0; j < 3; j++) {
      sensorData[i][j] = 0;
    }
  }

  //Zero out voltage and irrigation variables
  solarVolts = 0.0;
  batteryVolts = 0.0;
  irrigate = 0;
}

void updateSoilData() {
  // Update sensor values
  sensorIndex = 0;
  for (int i = 0; i < NUM_SENSORS; i++) {
    if(checkActive(SENSOR_ADDR[i])) takeMeasurement(SENSOR_ADDR[i]);
    sensorIndex++;
  }

  // Find solar panel voltage
  solarVolts = analogRead(SOLAR_VOLTS_PIN);
  solarVolts = (solarVolts/1023) * 6.894; // Input voltage when divided voltage = 5v

  // Find battery voltage
  batteryVolts = analogRead(BATTERY_VOLTS_PIN);
  batteryVolts = (batteryVolts/1023) * 5.0;

  // Read irrigation pressure switch...switched on ground side
  if (digitalRead(IRRIGATE_PIN) == LOW) {
    irrigate = 1;
  }
  else {
    irrigate = 0;
  }
}

void takeMeasurement(char i) {
  // Send command to initialize measurement
  delay(500);
  mySDI12.flush();
  String command = ""; 
  command += i;
  command += "M!"; // SDI-12 measurement command format [address]['M'][!]
  mySDI12.sendCommand(command);

  /*
  I don't think the GS3 sensors conform to SDI-12 exactly and they send a service request even if they wait the entire wait time
   And, subsequently, the service request is in the buffer when we try to read the results
   So, I'm just putting in an arbitrarily long wait time before the next command to get everything in the buffer, then flush it
   i.e. enought wait time for measurement command, any wait time for measurement itself, and/or service request

   while(!mySDI12.available()>5); // wait for acknowlegement with format [address][ttt (3 char, seconds)][number of measurments available, 0-9]

   mySDI12.read(); //consume address

   // find out how long we have to wait (in seconds).
   int wait = 0; 
   wait += 100 * mySDI12.read()-'0';
   wait += 10 * mySDI12.read()-'0';
   wait += 1 * mySDI12.read()-'0';

   mySDI12.read(); // ignore # measurements
   mySDI12.read(); // ignore carriage return
   mySDI12.read(); // ignore line feed

   long timerStart = millis(); 
   while((millis() - timerStart) > (1000 * wait)) {
   if(mySDI12.available()) break; //sensor can interrupt us to let us know it is done early
   }
   */

   delay(2000);

  // Send command to get data
  mySDI12.flush(); 
  command = "";
  command += i;
  command += "D0!"; // SDI-12 command to get data [address][D][dataOption][!]
  mySDI12.sendCommand(command);
  //while(!mySDI12.available()>1); // wait for acknowlegement  
  delay(1000); // let the data transfer

  // Parse and save data
  String buffer = "";
  char c;
  c = mySDI12.read(); // First byte is sensor address, don't need it
  c = mySDI12.read();
  if (c=='-') buffer += '-';
  int dataPos = 0;
  while(mySDI12.available()) {
    c = mySDI12.read();
    if (c == '\r') break;
    if (c == '+' || c == '-') {
      sensorData[sensorIndex][dataPos] = buffer;
      buffer = "";
      dataPos++;
      if (c == '-') buffer += '-';
    }
    else {
      buffer += c;
    }
  }
  sensorData[sensorIndex][dataPos] = buffer;

  mySDI12.flush();
}

boolean checkActive(char i){
  String myCommand = "";
  myCommand = "";
  myCommand += (char) i;                 // sends basic 'acknowledge' command [address][!]
  myCommand += "!";

  for(int j = 0; j < 3; j++){            // goes through three rapid contact attempts
    mySDI12.sendCommand(myCommand);
    if(mySDI12.available()>1) break;
    delay(30); 
  }
  if(mySDI12.available()>2){       // if it hears anything it assumes the address is occupied
    mySDI12.flush(); 
    return true;
  } 
  else {   // otherwise it is vacant. 
    mySDI12.flush(); 
  }
  return false; 
}
s-hicks2 commented 9 years ago

Your code looks okay to me, I don't see anything that would break just from switching from 5v to 3.3v boards.

FWIW, here's the SDI-12 measurement portion of some of my sketches. I use a parsing routine to read the data directly into a variable. The code is for a Decagon CTD sensor, but works just fine on their other sensors, I just change the name of the variables that are used. For most of the sensors, 500ms is enough time for the measurement and for the data return, so I don't think it would help to increase your above 2 seconds. I don't have a GS3 to experiment with, otherwise I'd give it a try.

void CTDMeasurement(char i){   

  String command = ""; 
  command += i;
  command += "M!"; // SDI-12 measurement command format  [address]['M'][!]
  mySDI12.sendCommand(command); 
  delay(500); // wait a while
  mySDI12.flush(); // we don't care about what it sends back

  command = "";
  command += i;
  command += "D0!"; // SDI-12 command to get data [address][D][dataOption][!]
  mySDI12.sendCommand(command);
  delay(500); 
     if(mySDI12.available() > 0){
        float junk = mySDI12.parseFloat();
        CTDdepth = mySDI12.parseInt();
        CTDtemp = mySDI12.parseFloat();
        CTDcond = mySDI12.parseInt();
     }

  mySDI12.flush(); 
 }     
Kevin-M-Smith commented 9 years ago

Justin - I'm curious about the service request that you mention... Do you recall what it looks like?

JustinWhitaker commented 9 years ago

It is the address followed by a carriage return and a line feed...so a. The GS3 was returning a 0 for the wait time but then also sending the service request. The specs (section 4.4.6...the last sentence) says that the sensor isn't supposed to send a service request if the time is 0, but this one was.

JustinWhitaker commented 9 years ago

Just looked at my previous post...I forgot to escape the left angle bracket and markdown took it as code...it was supposed to be a<CR><LF>.

Anyway, I have some more intel and thought you guys might have some suggestions. I've reduced my circuit to just a 3.3v Arduino Pro Mini running on 3.7v (regulated from an AC source so the battery won't run down) and then the sensors powered from the same 3.7v source. I've loaded the check_all_addresses sketch and it still won't see the sensors. They are all addressed seperately ('0', '1', and '2'), but the sketch returns all addresses vacant. If I hook them up one sensor at a time, it reads them each flawlessly every time, but just not on the same bus together. Something to do with the 3.3v logic levels maybe? I've looked at the integrators guide and it says the sensors work on 0-3v logic levels (2.6-3.9 for high and -0.3-0.75 for a low), so they should respond, and they do respond fine individually, just not on a bus. I've also tried two different 3.3v Arduinos and I've tried powering the sensors separately (with the grounds connected) with 5v and 12v with no luck. Any ideas?

I could rewrite my datalogger sketch and redesign my board to attach each sensor separately. Maybe that's a better idea anyway in case one sensor ever goes down it won't take the whole bus down.

s-hicks2 commented 9 years ago

I routinely connect 9 Decagon SDI-12 sensors to the same bus, all with a 3.3v Arduino board, and power them all with 3.3v excitation, and I haven't had any problems, so I'm not sure what else to suggest. The only other thing I can think of is the firmware for their sensors was recently update so that the sensors (when given an SDI-12 address other than 0) don't output the data string when initially powered because it was causing a race issue when multiple sensors were on the same bus. The latest firmware for the GS3 should be 4.02, so check that you don't have an earlier version.

JustinWhitaker commented 9 years ago

Yeah, I've read about that serial data string interfering with some microcontrollers' UART ports. I do have 4.02 version sensors and they are all addresses as something other than 0, specifically, I've had them at 1, 2, and 3.

I think I'll close this issue as it's not an issue with the library. I've played around with them for several days and I believe it has something to do with the logic voltage level and the sensor voltage. If I run everything at 5VDC (bypassing the on-board 3.3VDC regulator so all the I/O pins run at 5VDC) and 8MHz then everything works fine. The sensors will even read fine when powered with as little as 3.0VDC, as long as the VCC of the microcontroller is at 5VDC. I don't get any response from the sensors if I power the micro with 3.3VDC (or similarly feed 5VDC into the regulator so the chip itself and all I/O pins use 3.3VDC), regardless of the sensor supply voltage.

It's almost as if there has to be some headroom in the logic level voltage vs the sensor voltage. If there is .8 volts more at the micro VCC than the sensor supply voltage then it works fine, up until about 5 volts on the micro, then it doesn't matter what you power the sensor with, everything seems to work fine. I guess it's just something in these sensors specifically or it's something in my setup that makes it finicky. Could be my 900 MHz radio dropping the voltage some when it transmits and interfering with the voltages?

Anyway, thanks for your help and suggestions, and for the great library. I was looking at saving about 14mA by going to 3.3V 8MHZ, but even going to 5V 8MHz will still save 6mA...maybe I can reduce the solar panel and battery a little bit still.

Kevin-M-Smith commented 9 years ago

Thanks for bringing this discussion online for others to see. I will close up the issue, but if you come up with any insights or breakthroughs please consider updating the thread so that we can continue to learn together. Good luck!

shmrymbd commented 7 years ago

hi sorry to ask. if my sensor is powered by 12V .. what is the signal output? +12V.. i am using arduino nano + hydra probe.. cant communicate. thanks

Kevin-M-Smith commented 7 years ago

Hi @shmrymbd,

The output from SDI-12 probes is a binary serial output between 0-5v. The output is not an analog signal. Please see the latest specification here: http://sdi-12.org/current%20specification/SDI-12_version-1_4-August-10-2016.pdf

Best, Kevin