Software Serial for half duplex

Hi,

I am trying to use the Software Serial library to control a AX-12 servo, that use a half duplex uart protocol.

To implement the half duplex protocol without additionnal hardware, i simply connect the tx and rx ports together, and disable the tx port when receiving, and vice-versa (see code below).

My problem is that, at 9600bd, when i switch from tx mode to rx mode the first byte is not received correctly.
My first idea was that the switch to rx mode was simply not fast enough (i don’t know how fast the servo starts to send the status package).

However, when i try at 57600bd, it works correctly, so i really don’t know what is happening here.

Here is my code, it sends a ping packet to device 1, read the answer, and send it on the serial console for debugging.

At 57600bd, i get : “ff ff 01 02 00 fc” (the expected response), but at 9600bd i get : “ff 01 02 00 fc”

#include "SoftwareSerial.h"

//extends SoftwareSerial to be able to disable Tx port
class MySoftwareSerial:public SoftwareSerial
{
  public:
  MySoftwareSerial(uint8_t aRxPin, uint8_t aTxPin):
    SoftwareSerial(aRxPin, aTxPin), mTxPin(aTxPin)
  {}
  void enableTx(){pinMode(mTxPin, OUTPUT);}
  void disableTx(){pinMode(mTxPin, INPUT);}
  private:
  uint8_t mTxPin;
};

void setup()
{
  MySoftwareSerial serial(2,3);
  serial.begin(9600);
  delay(100);

  unsigned char buffer[6];

//switch to tx mode
  serial.stopListening();
  serial.enableTx();

//send a ping instruction packet
  serial.write(0xFF);
  serial.write(0xFF);
  serial.write(0x01);
  serial.write(0x02);
  serial.write(0x01);
  serial.write(0xFB);
  serial.flush();

//switch to rx mode
  serial.disableTx();
  serial.listen();

//receive status packet
  int n=serial.readBytes(buffer, 6);


//write received packed to serial console
  Serial.begin(9600);
  for(int i=0; i<n; ++i)
  {
    Serial.write(buffer[i]);
  }
  Serial.flush();
}

void loop() {}

Thanks for your help!

One possible explanation for why the baud rate matters is if there is any activity on the line immediately after the TX to RX switch over. Even a short glitch could be seen as a start bit by SoftwareSerial and that would initiate a character read. At 9600 baud that would leave your code blind to any real character for about 1ms. At 57.6K it would only be about 170us.

The best thing would be to actually monitor the line in order to see if my suspicion is true or not. You could even use another Arduino to capture transitions and time stamps.

Another way to test this theory would be to add a delay (maybe 100-200us?) between disabling TX and calling listen().

Thanks for your suggestions, jboyton.

I tried to add a delay, like you suggested,, but with no better result. In fact, by trying higher values, i noticed that the threshold to receive the second character is around 1010 microseconds. Given that a character takes about 1041 microseconds at that speed, maybe the switch to rx mode is really too slow, but then i have no idea why it works at 57600bd.

Also, when i invert the disableTx() and listen() lines, the code works, but that doesn't sound like a good solution to me.

I do not have a oscilloscope, nor a second arduino right now, but i will try to use xoscope to monitor the line.

How soon after you transmit the last byte is the servo supposed to respond?

If the timing is really tight – and the fact that switching disableTx and listen seems to fix the problem suggests that it is – one thing you might try is to access the hardware directly. Instead of using pinMode, listen and stopListening you could write directly to the registers. Your code wouldn’t be as portable to other hardware platforms but it would be a lot faster.

One other thing I noticed is that you’re calling serial.flush() (lower case “s”). Unlike HardwareSerial, SoftwareSerial implements flush() as a way to clear the RX buffer (something that listen() also does). There is no TX buffer to clear since each character is sent synchronously (with interrupts disabled except for during the stop bit). So the serial.flush() call isn’t doing anything except adding a little to the delay of switching back to receive mode.

Ok, i found the problem when investigating the return time of the servo. The servo has a "return delay time" option, that is by default at 500us, but was set to zero in mine, probably during previous experimentations. It does exactly what it says, it adds a delay between the packet reception and the answer, and now it works perfectly.

About the flush function, good point, i forgot the (big) difference between the flush in SoftwareSerial and in HardwareSerial. If i remember correctly, the old behaviour of flush in HardwareSerial was the same than the current one in SoftwareSerial, so i guess this is the historical reason, but i find it really confusing..

Anyway, problem solved, thank you very much for your help!