SDI-12 compatibility

I have some sensors that only have SDI-12 output. Is there an easy way to interface them with an Arduino?

I assume you mean this:

You should be able to communicate with such devices easily using the Arduino, as the serial data stream is just HIGH/LOW pulses of (approximately) 0 and 5 VDC, where a LOW logic level (0V) is equivalent to a binary state of "1" and a HIGH logic level (5V) is equivalent to a binary state of "0"; in other words, inverse logic.

I don't know of a library or anything that makes it simple to communicate using an Arduino, but you should be able to adapt one of the "2-wire" serial libraries with only a few changes (mainly inverting the logic if needed, and adjusting for 1200 baud).

I'm sure there are other methods (ie, using a level or protocol converter IC or similar), but in theory, based on my (admittedly) limited quick reading of the spec of the protocol, it should be something you can directly interface to with the Arduino using a single digital pin, and appropriate driver/interface software...

:slight_smile:

Yep, that's the protocol. It's rather common in the environmental sensor world. It looks like the Arduino could handle it, I just dont' have the experience or time to brute force my way through designing an interface, so I'm hoping someone else has done it, or has something that could be easily adapted to work with Arduino.

I guess I am curious why you would want to interface an SDI-12 sensor to an Arduino.

SDI-12 sensors have their own MCU, most pre-process measurements already (e.g. smp, avg, min, max, etc.) which can be called using the SDI protocol based on how the manufacturer has it configured and typically support other output types e.g. RS232/485. SDI-12 is great for low powered applications and supports daisy chaining of sensors as the protocol supports sensor addressing. It's a slick protocol developed by Eric Campbell of Campbell Scientific, worked for the company, and have used it frequently (sensor & data-logger development).

If your an intermediate programmer I would suggest polling the sensor via RS232. Check the sensor because some support TTL, if thats the case, use the SoftSerial library and two IO ports.

Just wondering whether anyone had made progress with this topic: SDI-12 on Arduino?
There are plenty of SDI-12 compatible data loggers in the market place and on the other hand, plenty of good, simple sensor ideas around for Arduino.
What would be good would be to bring them together: that is using Arduino as a simple front end for some SDI-12 smart sensors.

Hi all,

I don't suppose anybody has found a sdi12.h library? I have a very nice anemometer that uses SDI-12 communication.

Thx

Here is a newbie thought. Please tell me if this is a dumb one:

If I tie a couple of Rx and Tx pins together, and wrap all Tx serial commands in toggling the interrupt register OFF then ON.

For example for pins 14 & 15:

PCICR |= (0<<PCIE2); // disables interrupts on pins 8-15
serial.print("0");
serial.print(13, BYTE);
serial.print(10, BYTE);
PCICR |= (1<<PCIE2); // enables interrupts on pins 8-15

Would this allow me to communicate over the same SDI-12 line using standard serial commands? If yes; here comes the next question:

How can I apply this to say pin 30 and 31 using the NewSoftSerial.h library. I can't even figure out what the pin-change-register is for pins 30 and 31. Besides that, it would be even better to figure out how to modify the NewSoftSerial library to pull this trick.

Cheers,
Terke

I've just received one of these devices. I've yet to hook it up and try it yet, but I thought I would provide the link. I work for the National Weather Service and we use quite a bit of SDI-12. I wanted to see if I could interface with our SDI-12 sensors effectively.

http://www.vegetronix.com/Products/SDI-12.phtml

Thanks,

Had a look at that board. Nice, but the mega should simply be able to talk SDI-12. It is just a matter of finding the trick.

Cheers,
terke

I modified the software serial code to work for some sdi-12 sensor I was working with. The documentation for sdi-12 isn't the best but it wasn't to hard. Just invert the digitalRead part of the code and reduce it for a 7 bit string. Let me know and I'll post the modified softwareSerial.

started some work on a SDI-12 lib (anemometer) a few weeks ago, never finished it :blush: no sensor => could not test

It contains several shortcuts, but it could get someone started

//
//    FILE: SDI-12.001.pde
//  AUTHOR: Rob Tillaart
//    DATE: 2012-mar-18 
//
// PUPROSE: talk to SDI-12 anemometer
//

void setup()
{
  // communication with PC
  Serial.begin(115200);
  Serial.println("\nSDI-12 0.01");

  // hardware serial1 for the sensor  [MEGA, optional replace with softSerial]
  Serial1.begin(1200);
}

void loop()
{
  char *answer;
  
  answer = addressQuery(); // char, no string
  Serial.print("\n addressQuery: ");
  Serial.println(answer);

  AcknowledgeActive('0'); // char, no string
  Serial.print("\n Address: ");
  Serial.println(answer);

  addressChange('0', 'Z');
  Serial.println(answer);
  addressChange('Z', '0');
  Serial.println(answer);

  sendIdentification('0'); // char, no string
  Serial.print("\n id: ");
  Serial.println(answer);

  int t = 0;
  int n = 0;

  startMeasurement('0', &t, &n);
  Serial.print("\n startMeasurement: ");
  Serial.println(answer);  
  Serial.println(t, DEC);  
  Serial.println(n, DEC);

  delay(t*1000UL); // wait for the measurement to be ready 

  sendData('0');
  Serial.print("\n sendData: ");
  Serial.println(answer);

  delay(20000UL);
}

/////////////////////////////////////////////////////////
//
// Proto  SDI-12 commands
//
// names of functions, Table 5, page 8, SDI-12 1.3 specification
//


/////////////////////////////////////////////////////////
//
// Private members
//
char response[80];    // at least 35/75 according to 1.3 spec

/////////////////////////////////////////////////////////
//
// Public part I (admin commands)
//
char* AcknowledgeActive(char address)
{
  char command[] = "a!";
  command[0] = address;
  request(2, command, response);
  return response;
}

char* addressQuery()
{
  char command[] = "?!";
  request(2, command, response);
  return response;
}

char* addressChange(char oldAddress, char newAddress)
{
  char command[] = "aAb!";
  command[0] = oldAddress;
  command[2] = newAddress;
  request(4, command, response);
  return response;
}

char* sendIdentification(char address)
{
  char command[] = "aI!";
  command[0] = address;
  request(3, command, response);
  return response;
}

void printIdentification(char *idStr)
{
  // TODO
}

/////////////////////////////////////////////////////////
//
// Public part II (measurements)
//
char* startMeasurement(char address, int *t, int *n)
{
  char command[] = "aI!";
  command[0] = address;
  request(3, command, response);

  int len = strlen(response);
  *n = (int) (response[len-1] - '\0');
  *t = atoi(&response[1]) / 10;

  return response;
}

char* sendData(char address)
{
  char command[] = "aD0!";
  command[0] = address;
  request(4, command, response);
  return response;
}


/////////////////////////////////////////////////////////
//
// Private part I
//
void request(uint8_t length, char *command, char *response)
{
  // send command
  for (int idx=0; idx<length; idx++)
  {
    Serial.write(command[idx]);
  }

  // blocking until answer comes in
  char c = ' ';
  int idx = 0;
  while (c != 10)  // answer ends with LineFeed = char 10
  {
    if (Serial.available() > 0)
    {
      c = Serial.read();
      response[idx] = c & 0x7F;  // strip bit 8
      idx++;
      if (idx > 75) break; // prevent overflow answer;
    }
  }
  response[idx-2] = 0; // remove the CRLF and end string correctly
}

// --- END OF FILE ---

@4A_5E_33 Would you mind posting that modified library for some of us to take a look at? I've been trying to get a good interface method for SDI-12 sensors for a while and would be very interested in seeing your method.

Hello All;

I'm making a little progress in talking with my SDI-12 sensor. I'm using Rob Tillaart's code as a starting point with a few changes:
First off I switched to SoftwareSerial.h and then set the UART to inverted logic. Before I had tried with Rob's code but that kept the Tx (pin 10) port high all the time which won't work with SDI-12. I added a 13ms HIGH pulse, followed by 9ms of LOW on the Tx pin before sending the command. You can see the SDI-12 answer but at slightly lower voltage. The voltage is lower for the reply because the Tx (pin 10) and Rx (pin 11) pins are tied together. At first they were simply tied together, but then the reply voltage was only about 1.8V because of the open Tx pin. Now the Tx pin talks through a 2.2kOhm resistor and far more signal is available for the Rx pin. If I could figure out how to pull the internal resistor on the Tx pin high I would give that a try.

I have attached two pics. One of the scope showing the 13ms High pulse, 9ms Low, the request from the Arduino, and finally the reply from the sensor.

The second pic is a screen grab of the terminal output. I send the configuration commands to the sensor, and then try to ask for the sensor to send back what it has for configuration. For some reason, the I can only read a small part of the settings back when requested. I can't figure out why.

And here is the code:

//
//    FILE: SDI-12.001.pde
//  AUTHOR: Rob Tillaart
//    DATE: 2012-mar-18
//
// PUPROSE: talk to SDI-12 anemometer
//

#include <SoftwareSerial.h>

#define txPin 10
#define rxPin 11
#define INVERTED true          //SDI-12 uses inverted logic

SoftwareSerial sdiAnem(rxPin, txPin, INVERTED);

void setup()
{
 // communication with PC
 Serial.begin(115200);
 Serial.println("\nSDI-12 0.01");

 // software serial for the sensor
 sdiAnem.begin(1200);
 
 delay(9);
 digitalWrite(txPin, HIGH);
 delay(13);
 digitalWrite(txPin, LOW);
 delay(9); 
sdiAnem.write("0XXU,A=0,M=R,C=1,B=1200,D=7,P=E,S=1!"); 
Serial.println("0XXU,A=0,M=R,C=1,B=1200,D7,P=E,S=1!");

 delay(9);
 digitalWrite(txPin, HIGH);
 delay(13);
 digitalWrite(txPin, LOW);
 delay(9);
sdiAnem.write("0XWU,R=01001100&01001100,I=10,A=60,G=3,U=N,D=0,N=W,F=4!");
Serial.println("0XWU,R=01001100&01001100,I=10,A=60,G=3,U=N,D=0,N=W,F=4!");

char incomingAnswer[80];
char a;
int idy = 0;
 delay(9);
 digitalWrite(txPin, HIGH);
 delay(13);
 digitalWrite(txPin, LOW);
 delay(9); 
sdiAnem.write("0SU!");
   if (sdiAnem.available() > 0)
   {
     a = sdiAnem.read();
     incomingAnswer[idy] = a & 0x7F;  // strip bit 8
     idy++;
   }
Serial.print("Reported settings are: ");   
Serial.println(incomingAnswer);
   

}

void loop()
{
 char *answer;

 answer = addressQuery(); // char, no string
 Serial.print("\n addressQuery: ");
 Serial.println(answer);

 AcknowledgeActive('0'); // char, no string
 Serial.print("\n Address: ");
 Serial.println(answer);

 sendIdentification('0'); // char, no string
 Serial.print("\n id: ");
 Serial.println(answer);

 int t = 0;
 int n = 0;

/* startMeasurement('0', &t, &n);
 Serial.print("\n startMeasurement: ");
 Serial.println(answer);
 Serial.println(t, DEC);
 Serial.println(n, DEC);

 delay(t*1000UL); // wait for the measurement to be ready
*/
 sendData('0');
 Serial.print("\n sendData: ");
 Serial.println(answer);

 delay(20000UL);
}

/////////////////////////////////////////////////////////
//
// Proto  SDI-12 commands
//
// names of functions, Table 5, page 8, SDI-12 1.3 specification
//


/////////////////////////////////////////////////////////
//
// Private members
//
char response[80];    // at least 35/75 according to 1.3 spec

/////////////////////////////////////////////////////////
//
// Public part I (admin commands)
//
char* AcknowledgeActive(char address)
{
 char command[] = "a!";
 command[0] = address;
 request(2, command, response);
 return response;
}

char* addressQuery()
{
 char command[] = "?!";
 request(2, command, response);
 return response;
}

char* addressChange(char oldAddress, char newAddress)
{
 char command[] = "aAb!";
 command[0] = oldAddress;
 command[2] = newAddress;
 request(4, command, response);
 return response;
}

char* sendIdentification(char address)
{
 char command[] = "aI!";
 command[0] = address;
 request(3, command, response);
 return response;
}

void printIdentification(char *idStr)
{
 // TODO
}

/////////////////////////////////////////////////////////
//
// Public part II (measurements)
//
char* startMeasurement(char address, int *t, int *n)
{
 char command[] = "aI!";
 command[0] = address;
 request(3, command, response);

 int len = strlen(response);
 *n = (int) (response[len-1] - '\0');
 *t = atoi(&response[1]) / 10;

 return response;
}

char* sendData(char address)
{
 char command[] = "0R1!";
 request(4, command, response);
 return response;
}


/////////////////////////////////////////////////////////
//
// Private part I
//
void request(uint8_t length, char *command, char *response)
{
 // send command
 delay(9);
 digitalWrite(txPin, HIGH);
 delay(13);
 digitalWrite(txPin, LOW);
 delay(9);
 for (int idx=0; idx<length; idx++)
 {
   sdiAnem.write(command[idx]);
 }

 // blocking until answer comes in
 char c = ' ';
 int idx = 0;
 while (c != 10)  // answer ends with LineFeed = char 10
 {
   if (sdiAnem.available() > 0)
   {
     c = sdiAnem.read();
     response[idx] = c & 0x7F;  // strip bit 8
     idx++;
   }
 }
 response[idx-2] = 0; // remove the CRLF and end string correctly
}

// --- END OF FILE ---

noderator added code tags

Screen Shot 2012-06-19 at 10.49.28 PM.png

Hi, just letting you know I'm also interested in your work here. Thanks for taking the time to post your code, and I'd love to know if you've made more progress.

I have just created this exact library :slight_smile:

Thanks for sharing!

As the SDISerial only works on 1200 baud, one could consider to "hard code" the timing reducing footprints, would strip off a few bytes in memory and initialization. iso the progmem struct one can use two #defines. 1187 and 2378 (half bit && whole bit)

// saw one typo in the lib - SDISerrial connection(DATA_PIN); - // README.md
// demo sketch might give an error

made issues for that

thanks Rob :slight_smile:

there are alot of things I would still like to implement... but it seems to work pretty dang good on the sensors I tested it with :slight_smile:

Welcome,
good to see a working lib, that is the best basis to expand further!