SoftwareSerial stops working after a while...

I’m interfacing with a soil moisture sensor that returns sensor values via serial a few milliseconds after being powered up. I have been using software serial to read the ASCII string after powering the sensor from a digital pin. Here is some basic code I have been using for testing.

#include <SoftwareSerial.h>

SoftwareSerial mps6 = SoftwareSerial(2, 3);
int pwrpin = 6;

void setup() {
  mps6.begin(1200);
  Serial.begin(9600);
  pinMode(pwrpin, OUTPUT);
  digitalWrite(pwrpin, LOW);
  Serial.println("Started...");
  delay(3000);
}

void loop() {
  String message;
  digitalWrite(pwrpin, HIGH);
  delay(200);
  while (mps6.available() > 0){
    message.concat(char(mps6.read()));
  }
  Serial.println(message);
  delay(5000);
}

This works perfectly for about 20 to 30 minutes at which point I no longer receive any serial data.
Using the scope I can see the serial data when everything is working normally (ScopeNormal.png), but after a time, I can see why I’m not receiving anything (ScopeNotworking.png).
Were leaving the Arduino running, I’ve completely disconnected the sensor, and reconnected it, but I see the same behaviour. Conversely, when I reset the Arduino, immediately everything works. I’m therefore guessing that is the problem with the software serial library.

Any thoughts?

Do you need to set the pwrpin LOW in loop? Otherwise, it’s always on.

How much data does it send in 5 seconds? You could be using up all the RAM. Don’t use String. Instead, try this, which doesn’t use any RAM:

#include <SoftwareSerial.h>

SoftwareSerial mps6 = SoftwareSerial(2, 3);
int pwrpin = 6;

void setup() {
  mps6.begin(1200);
  Serial.begin(9600);
  pinMode(pwrpin, OUTPUT);
  digitalWrite(pwrpin, LOW);
  Serial.println("Started...");
  delay(3000);
}

void loop() {
  digitalWrite(pwrpin, HIGH);
  delay(200);

  uint32_t start = millis();
  while (millis() - start < 5000) {
    while (mps6.available()) {
      // Just echo the data
      Serial.print( mps6.read() );
    }
  }
  Serial.println();

  digitalWrite(pwrpin, LOW); // Need this?
  delay( 3000 );
}

Which soil sensor are you using. I don’t see any that use a SoftwareSerial interface. They all seem to use AnalogRead to get a value from then sensor.

Cheers,
/dev

The sensor I’m using is:
http://www.decagon.com/products/soils/water-potential/mps-6-calibrated-water-potential-sensor/

It only sends one string of about 16 characters after being powered on, thats why it’s powered off and then on again. The sensor is not sending any data during the 5 second delay. The data is received during the 200ms delay (this not only allows the transmission time, but the time from power up to transmission.

It only sends one string of about 16 characters after being powered on

OK, so you will need to power it off during loop.

The data is received during the 200ms delay (this not only allows the transmission time, but the time from power up to transmission.

There’s no reason to do the delay, then. Instead, start reading data after it’s powered on, and quit after 200ms.

But this is an SDI-12 device. According to the spec, it sends 1 start bit, 7 data bits, 1 even parity bit and 1 stop bit. Because the TX and RX lines are shared, it’s half duplex instead of the typical full duplex. This would be H7E1, but it should work with the default F8N1 if you ignore the last bit and disable the transmit pin when you’re not transmitting. SoftwareSerial only supports F8N1. The real problem is that these bits are inverted:

SDI-12 3.1 Serial Data Line.jpg

Fortunately, someone else has tackled this problem. Here is a library for communicating with SDI-12 devices. You will have to use it instead of SoftwareSerial

Cheers,
/dev

Oh, I see this device will send an F8N1 stream after power-up. Something like this should work:

#include <SoftwareSerial.h>

SoftwareSerial mps6 = SoftwareSerial(2, 3);
int pwrpin = 6;

void setup() {
  mps6.begin(1200);
  Serial.begin(9600);
  pinMode(pwrpin, OUTPUT);
  digitalWrite(pwrpin, LOW);
  Serial.println("Started...");
  delay(3000);
}

void loop() {
  digitalWrite(pwrpin, HIGH);

  char     buf[64];
  uint8_t  i = 0;
  uint32_t start = millis();

  while (millis() - start < 200) {
    while (mps6.available()) {
      char c = mps6.read();
      if (i < sizeof(buf)-2)
        buf[i++] = c;
    }
  }

  digitalWrite(pwrpin, LOW);

  // Just echo the data
  buf[i] = '\0'; // NUL-terminate
  Serial.println( buf );

  delay( 3000 );
}

This will reduce the Serial interrupts that happen during the first 200ms, which can affect SoftwareSerial reads.

It also saves ~1600 bytes of program space because it doesn’t use String. I would suspect memory fragmentation caused by String before suspecting SoftwareSerial. Your use of String seems ok, but I’d have to dig a little deeper to know for sure.

Cheers,
/dev

Hi /dev, thanks for your thoughts.

I tried to use your code suggestion to see if the use of String was the root of the problem. Unfortunately I can’t get it working, and I can’t figure out why:

The first loop outputs the string as expected, and then nothing. I played around and tried to figure out what was happening. I modified the code like this:

  while (millis() - start < 200) {
    while (mps6.available()) {
      char c = mps6.read();
      Serial.print(c);
      if (i < sizeof(buf)-2)
        buf[i++] = c;
    }
  }

I wanted to know if the while loop was being correctly executed, and the reads were working. It turns out they are, as the characters are printed as they’re read, but they’re not making it into buf. I did a lot of fiddling around but couldn’t fill buf after the first iteration.

Any ideas?

Hmm, I'll need to see the entire program. The one I posted compiles and runs on my Mega. I substituted a GPS for your sensor -- it outputs a lot of data. And I had to use different pins, but it seems to work.

Adding the Serial.print is ok for testing, but it could affect SoftwareSerial a little bit. In this case, it's probably ok.

Well, if the buffer index i does not get reset to 0, it won't be able to save any more characters. I'm just guessing, though, without the entire program.

Cheers, /dev

There is another possibility. There could still be characters left in the input buffer from the last cycle. Add this to the top of loop:

void loop() {

  // Flush old characters
  while (mps6.available())
    mps6.read();

Then when you turn the power on, the input buffer is empty.

Cheers, /dev

Hi again,

Apologies for not posting the entire program, but it was just yours with the added Serial.print().

I tested that i was going back to 0, it was.

So even though the data wasn’t getting into buf using your program, I left it running and monitored data transmission with the scope. Sure enough, after a while transmission abruptly stopped and I saw what looked like a cap discharge on the scope (see attached). I wanted to verify that the sensor was still trying to send data, so I disconnected it from the Arduino while keeping the scope probe attached, and saw the serial data being sent. Instantly on reconnecting to the arduino, data started coming through normally. It seems the arduino pin is doing something strange which is causing the problem? It’s certainly puzzling.

I’ll try the flushing now, but I’m not hugely hopeful.

So it turns out that flushing solved the problem of not getting data into buf, but didn’t have any effect on my original problem.

but didn't have any effect on my original problem.

To confirm, you're saying after 20-30 minutes, you don't get any data.

The last scope image seems to show the power pin 6 on top (blue) and the TX pin 2 on bottom (green). The green trace seems to jump from 0 to 1.5V, and then decay back to 0. The voltage levels seem odd, as the MPS6 output is supposed to be 3.6V.

The decay is what I would expect if the MPS6 is driving HIGH and then is powered off. But I can't think of a reason for the Arduino input pin to cause the MPS6 output pin to "latch up" like that, and then the MPS6 starts working after they are disconnected. Let's go down the hardware path for a bit...

The MPS6 draws 10mA during measurement, which is within the limits of an Arduino output pin (40mA max). That seems ok.

Which Arduino are you using, and how is everything connected?

If you are using any discrete components, besides the Soil sensor, please draw a schematic. Are you using any other modules?

How long are the wires?

How is the Arduino being powered?

Cheers, /dev

Thanks for your continued help!

You’ve got the traces correct on the scope image. To clarify I’ve uploaded two new scope images, the first shows normal behaviour, and I receive sensible data. The second shows what happens after a period of time, and, as you’d expect, i get no data. The time taken to change doesn’t seem consistent, occasionally seems to be as short as 5 minutes, but usually between 20-30. The change is abrupt, one loop all is well, the next I see the odd behaviour. The arduino is still running at this point as I get newlines in the serial terminal as it’s printing the empty buffer. If I disconnect the data line from the arduino but leave the scope attached I see that the MPS-6 is still trying to send data (Scope3) and as soon as I reconnect the data line, I start receiving data normally again (for a time, until the same problem occurs).

I’m using an Uno, standard board as this is a test setup. I’ve got a breakout board which adapts all the uno pins to screw terminals for easy testing. The Uno is being powered from a Macbook Pro USB port. The MPS-6 is connected to digital 6 (power), GND and digital 2 for data over sodftware serial. The MPS-6 comes with a 5m cable, that’s all I’m using.

Cheers,

The MPS-6 comes with a 5m cable

That’s quite long, but the signals look very clean. Everything else seems ok.

There is one thing you should do… change the declaration of the mps6 instance to this:

SoftwareSerial mps6(2, 3);

If that doesn’t work, I’m gonna have to make up a story to fit what’s happening. Let’s take a look at the signals when it works:

MPS6.jpg

I am guessing at the sequence of events. Because this sensor should peacefully coexist with other sensors on the same wire, I believe it tests the data line before sending the ASCII data. Wouldn’t be polite to just barge in to an existing data conversation, you know. After about 20mS, it pulls the line low. It probably reads it to see if it was successful. If it doesn’t go low, something else is driving the line (a bus, really).

After seeing that it can transmit, I think it begins a measurement cycle. You can see the power line droop as it begins the process. After the process completes, the power rises back to normal, and the data begins transmitting.

In the traces where it doesn’t work, I don’t see a “sense” LOW pulse. Maybe it tried, but the line doesn’t go low. So it doesn’t begin a measurement process, which is why the power line doesn’t dip.

But why didn’t the line go low? The only thing I can think of is that the Arduino RX pin is in OUTPUT mode, not INPUT. Maybe the Arduino is driving the line HIGH?

A way to test this theory is to reset the pinMode before each cycle:

void loop() {

  // Flush old characters
  while (mps6.available())
    mps6.read();

  pinMode( 2, INPUT ); // force it?
  digitalWrite(pwrpin, HIGH);

  char     buf[64];

I have no explanation as to why the RX pin would get set to OUTPUT mode.

BTW, SoftwareSerial is very inefficient, so it my be worth trying an alternative library. Could you use pin 8 for the RX line instead? AltSoftSerial is a much better choice. It’s a fairly simple change, too:

#include <AltSoftSerial.h>

AltSoftSerial mps6; // always uses 8 for RX, 9 for TX
int pwrpin = 6;

What kind of data do you see when it works? I’d like to correlate that with the traces, too.

Cheers,
/dev

I’m working on some of your suggestions now, and will report back. For now, I’ve attached an image of a data transfer.

The packets I received were (decimal):

255 248 9 45 55 54 50 51 46 49 32 50 49 46 56 13 108 41 13 10 224

And translated to ASCII:

ÿø	-7623.1 21.8
l)

à

The -7623.1 and 21.8 are the two values we expect and are interested in. I think the leading characters are noise from the MPS-6’s initial pull-down of the serial line, and I’m unsure about the trailing characters. Both the leading and trailing chars are inconsistent, but the water potential and temperature readings always come through fine (well, except when I get nothing :).

Good news, the pinmode trick seems to have fixed it! Take a look at the scope image attached, note that we’re now getting the 3.6v signal we expected. Also less noise, I’m not getting those random leading bytes now.

It’s been running successfully now for several hours, but I’ll leave it longer to be sure.

Then I’ll check to see if we need to reset the mode each loop or just once.

Thanks again for your help and I’ll post an update later today or maybe tomorrow.

Cheers!

Just to make sure the data was ok, I annotated your data trace:

15 - data.jpg

It looks good. Note that the bits are lsb first, and there’s always a zero start bit and a one stop bit.

Cheers,
/dev

That trace looks really good.

7db606b0cfa226e6afcc234cd2e0e6f19f05757a.png

I expected the data line to be 0 when it first powered up, so you shouldn’t see the sense pulse. Also, the power line rises a little after it’s done transmitting.

As for why this works… ¯_(ツ)_/¯

Has anybody else seen the pinMode change unexpectedly? This points to an unsafe set/clear of the port data direction bits, but this sketch seems so simple. I can only suspect SoftwareSerial. WTH?

Cheers,
/dev

The only other thing I can suggest is that powering it from an output pin might be causing a problem. Brown out? Stress of some sort? The voltage drops to about 2V, so the MPS-6 has a significant inrush current. It might be better to use an SSR to switch the 5V supply to the MPS-6.

Here's one of many possibilities, in a 4-pin DIP you can breadboard (datasheet here). Just connect Arduino pin 6 to the +Control with a current-limiting resistor of 470 to 1K ohms, ground the -Control pin, connect 5V to one of the Load pins and the MPS-6 VCC to the other load pin and voila! It will drop the voltage to the MPS-6 to about 4.5V, but it only needs 3.6. With a peak load of 1A and continuous load of 90mA, it seems more than sufficient for one MPS-6 at 10mA continuous.

Cheers, /dev

So it turns out that a single:

  pinMode( 2, INPUT );

in setup() is all that’s needed to keep it working.

Thanks for your help. Re the SSR suggestion, it will be powered from a 5v regulator IC, or via an SSR on the final custom board. However I’ll dig around in my parts and see if I’ve got something I can breadboard now, to see if it makes a difference.

Thanks again!

Upon further reflection, I think you might be seeing Latch-up on the Soil Sensor IO pin. SoftwareSerial enables the PULLUP resistor on the RX pin (before setup), which means the Soil Sensor IO pin sees 5V when the power line is low. Naughty.

By setting the pinMode to INPUT, the PULLUP is disabled, and the data line floats LOW with the turned-off Soil Sensor.

That's my story, and I'm sticking to it. :)

Cheers, /dev