0

I am trying to create a stable communication channel without any mis-synchronization. In the SoftwareSerial library, the write function transmits one byte at a time. The mis-synchronization occurs when the receiver misjudges which bit is the start bit. Why is this a problem? Because the internal delay between the start bit and the first message bit is different from the internal delay between each message bit.

One of the really good suggestions I have heard is to use Manchester encoding on the Arduino. While I am testing out this option, I also wanted advice on altering the software serial library in the following way. If I modify the write function to transmit 128 bytes instead of 1, then this can avoid the problem I explained above, I assume.

Here is how I have altered the write function of SoftwareSerial. I haven't thought of any issues with this approach, but does anyone else see any problems? Here is the whole altered function. (I changed the parameter, and added 2 new loops - one to invert all the bytes if(inv) and another loop outside the loop that transmits each bit of a byte. I have attached the original function, further below.

size_t SoftwareSerial::write(byte* b)
{
  if (_tx_delay == 0) {
    setWriteError();
    return 0;
  }

// By declaring these as local variables, the compiler will put them
// in registers _before_ disabling interrupts and entering the
// critical timing sections below, which makes it a lot easier to
// verify the cycle timings
volatile uint8_t *reg = _transmitPortRegister;
uint8_t reg_mask = _transmitBitMask;
uint8_t inv_mask = ~_transmitBitMask;
uint8_t oldSREG = SREG;
bool inv = _inverse_logic;
uint16_t delay = _tx_delay;

//Doubtful of this loop ------------------
for(int i = 0; i < 128; i++)
{
  if (inv)
    b[i] = ~(b[i]); 
}

cli();  // turn off interrupts for a clean txmit

// Write the start bit
if (inv)
  *reg |= reg_mask;
else
  *reg &= inv_mask;

tunedDelay(delay);
for(int j = 0; j < 128; j++)
{
  // Write each of the 8 bits
  for (long i = 8; i > 0; --i)
  {
    if (b[j] & 1) // choose bit
      *reg |= reg_mask; // send 1
    else
      *reg &= inv_mask; // send 0

    tunedDelay(delay);
    b[j] >>= 1;
  }
}
// restore pin to natural state
if (inv)
  *reg &= inv_mask;
else
  *reg |= reg_mask;

SREG = oldSREG; // turn interrupts back on
tunedDelay(_tx_delay);

return 1;
}

Here is the original function.

size_t SoftwareSerial::write(uint8_t b)
{
if (_tx_delay == 0) {
  setWriteError();
  return 0;
}

// By declaring these as local variables, the compiler will put them
// in registers _before_ disabling interrupts and entering the
// critical timing sections below, which makes it a lot easier to
// verify the cycle timings
volatile uint8_t *reg = _transmitPortRegister;
uint8_t reg_mask = _transmitBitMask;
uint8_t inv_mask = ~_transmitBitMask;
uint8_t oldSREG = SREG;
bool inv = _inverse_logic;
uint16_t delay = _tx_delay;

if (inv)
  b = ~b;

cli();  // turn off interrupts for a clean txmit

// Write the start bit
if (inv)
  *reg |= reg_mask;
else
  *reg &= inv_mask;

tunedDelay(delay);

// Write each of the 8 bits
for (uint8_t i = 8; i > 0; --i)
{
  if (b & 1) // choose bit
    *reg |= reg_mask; // send 1
  else
    *reg &= inv_mask; // send 0

  tunedDelay(delay);
  b >>= 1;
}

// restore pin to natural state
if (inv)
  *reg &= inv_mask;
else
  *reg |= reg_mask;

SREG = oldSREG; // turn interrupts back on
tunedDelay(_tx_delay);

return 1;
}

The serial buffer can only hold 64 bytes at a time, but I am using the following code, courtesy of Nick Gammon to read the data from serial buffer without blocking. So it shouldn't be a problem on the receiving end either, I hope.

2
  • You've asked this before - in what way is this question different to your previous one? As I point out in your other question, by trying to send 1024 bits rather than 8 bits, you are substantially reducing the margin for error, if the difference between the sender and receiver clock is at all significant.
    – Nick Gammon
    Mar 31, 2016 at 23:18
  • The mis-synchronization occurs when the receiver misjudges which bit is the start bit. - that would happen. You would need to allow a really big gap (greater than 1024 bits) to stop this happening. This gap would wipe out any savings by not just sending 8 bits at a time as is normal. I don't see what this technique achieves.
    – Nick Gammon
    Mar 31, 2016 at 23:20

2 Answers 2

0

Basically your code has changed the size of the RS-232 packet from 10 bits to 1026 bits. There is nothing "wrong" with that, but it doesn't really solve the problem of a missed start bit - if anything it makes it worse since instead of losing 8 bits you lose 1024 bits.

Synchronisation is best done separately to the transmission of packets, and for that you need your packet to be nice and short.

A packet consists of 10 bits - the first is a LOW and the last is a HIGH; everything else in between is data. The trick is to provide a forced idle period before sending of at least the amount of time it takes to transfer one packet. That way if the receiver is mistakenly in a receive loop for whatever reason the 10 (or more) bits worth of HIGH will effectively cause the receiver to finish whatever it is doing and get ready to receive your real byte.

If you have a hugely long packet (1024 + 2 bits) flushing the receiver before transmitting will take a very long time - rather than just one byte's worth of time it will be your entire packet's transmit time.

On top of that you then have the problem of byte order and data integrity checking. Just sending two bytes (or 128 bytes) is not reliable. You need to wrap those bytes in a protocol including a start-of-packet indicator (0x55 0xAA is common, as is the special ASCII character 0x01: SOH), packet or payload length, and checksums. Options for things like sequence numbers, acknowledgements, packet retransmission, etc can also be built in to increase the reliability of the protocol.

Rather than trying to reinvent the wheel by changing the underlying data layer transmission you should learn the OSI 7 layer model and implement your protocol at a higher level than the data layer.

3
  • Just a quick clarification, isn't the start bit a one, which is a high?
    – Jonathan
    Mar 31, 2016 at 22:49
  • 1
    The start bit is always a zero. See <gammon.com.au/forum/?id=10894>. The "idle state" is continuous 1 bits.
    – Nick Gammon
    Mar 31, 2016 at 23:11
  • Coincidentally, you should learn the OSI 7 layer model is what I recommended, too.
    – sekdiy
    Apr 1, 2016 at 9:26
1

So far you have asked a lot of questions about your project which uses SoftwareSerial to send a lot of bits:

And now we have another one (this question).

I think you are flogging a dead horse with this. For one thing, what are you trying to achieve, that can't be achieved by just sending the bytes "normally" as 8-bit bytes? The only thing I can think of is a slight speed increase. By trying to make a 1024-bit packet, you are saving some start and stop bits in the middle, that's all.

In some of your other questions you mention 9600 baud, so let's run with that. At 9600 baud one bit is 1/9600 of a second, that is 104.17 µs.

To transmit 1024 bits "normally" you would need 128 bytes, each of which needs a start and stop bit, so the total is 128 * 10 * 104.17 µs = 133.338 ms.

To use your system you need 1024 bits plus one start and stop bit, so that is 1026 * 104.17 µs = 106.878 ms.

So far you have only saved 26.46 ms.

You could save a lot more by simply doubling the baud rate. Or quadrupling it. However SoftwareSerial is less reliable at higher baud rates. Why not use HardwareSerial?

Your next problem is that SoftwareSerial turns interrupts off during reception of one byte, so that it gets the timing right. For an 8-bit byte this means they are off for 10 * 104.17 µs = 1.0417 ms. That's about the limit for turning interrupts off if you want the internal timer to work, the one which gives you millis and micros. If you turn them off for 128 times as long, you can say "goodbye" to knowing how much time has elapsed.

As I mentioned in a comment, too, since serial comms relies on external clocks (that is, both ends have to get the clocking right) you can only afford around a ±2% error in the receiving clock to ensure that we are sampling at the correct place (see the datasheet) for an 8-bit byte. The error tolerance will drop to 1/128th of that if you try to send 128 bytes in a single stream. The crystal/resonator in the Arduino just won't be that good.

Finally, to avoid false detection of the start bit (which you have asked about elsewhere) you need a much longer gap between packets, to ensure that we get the correct start bit. Effectively you need a one-byte length (ie. the incoming data is continually HIGH) for 8-bit bytes. For 1024-bit packets you will need to introduce a gap of 1024 * 104.17 µs = 106.670 ms between packets. This required gap totally wipes out any saving by not sending those extra start and stop bits.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.