SoftwareSerial stop bit and parity

I started here:

I've tried these steps and have had no luck. It's been over a year since the last post regarding this matter. It would be really nice if softwareserial supported changing the parity and stop bits. Can someone with more coding experience walk me through the changes needed to make softwareserial work with parity changes and different stop bits? Thanks in advance.

The stop bits should be quite easy do add (just insert a few tunedDelays in write() and recv()). Parity handling is a bit more complex but could be implemented in these two locations too. It’s probably not an option because all possible combinations would require quite a bit additional code (uses flash) and almost nobody uses anything but 8N1 today. If you have other requirements I would make a special version of SoftwareSerial for exactly that purpose (let’s say a 7E2 version of SoftwareSerial) and not produce a version that fulfills all possible needs. Which specific version do you need?

8E2 with inverse logic would make my life so much easier.

Change these two routines:

//
// The receive routine called by the interrupt handler
//
void SoftwareSerial::recv()
{

#if GCC_VERSION < 40302
// Work-around for avr-gcc 4.3.0 OSX version bug
// Preserve the registers that the compiler misses
// (courtesy of Arduino forum user *etracer*)
  asm volatile(
    "push r18 \n\t"
    "push r19 \n\t"
    "push r20 \n\t"
    "push r21 \n\t"
    "push r22 \n\t"
    "push r23 \n\t"
    "push r26 \n\t"
    "push r27 \n\t"
    ::);
#endif  

  uint8_t d = 0;

  // If RX line is high, then we don't see any start bit
  // so interrupt is probably not for us
  if (_inverse_logic ? rx_pin_read() : !rx_pin_read())
  {
    // Wait approximately 1/2 of a bit width to "center" the sample
    tunedDelay(_rx_delay_centering);
    DebugPulse(_DEBUG_PIN2, 1);

    // Read each of the 8 bits
    for (uint8_t i=0x1; i; i <<= 1)
    {
      tunedDelay(_rx_delay_intrabit);
      DebugPulse(_DEBUG_PIN2, 1);
      uint8_t noti = ~i;
      if (rx_pin_read())
        d |= i;
      else // else clause added to ensure function timing is ~balanced
        d &= noti;
    }
    // skip the parity bit
    tunedDelay(_rx_delay_stopbit);
    DebugPulse(_DEBUG_PIN2, 1);

    // skip the 2 stop bits
    tunedDelay(_rx_delay_stopbit);
    DebugPulse(_DEBUG_PIN2, 1);
    tunedDelay(_rx_delay_stopbit);
    DebugPulse(_DEBUG_PIN2, 1);

    if (_inverse_logic)
      d = ~d;

    // if buffer full, set the overflow flag and return
    if ((_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF != _receive_buffer_head) 
    {
      // save new data in buffer: tail points to where byte goes
      _receive_buffer[_receive_buffer_tail] = d; // save new byte
      _receive_buffer_tail = (_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF;
    } 
    else 
    {
#if _DEBUG // for scope: pulse pin as overflow indictator
      DebugPulse(_DEBUG_PIN1, 1);
#endif
      _buffer_overflow = true;
    }
  }

#if GCC_VERSION < 40302
// Work-around for avr-gcc 4.3.0 OSX version bug
// Restore the registers that the compiler misses
  asm volatile(
    "pop r27 \n\t"
    "pop r26 \n\t"
    "pop r23 \n\t"
    "pop r22 \n\t"
    "pop r21 \n\t"
    "pop r20 \n\t"
    "pop r19 \n\t"
    "pop r18 \n\t"
    ::);
#endif
}

and

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

  uint8_t oldSREG = SREG;
  cli();  // turn off interrupts for a clean txmit

  // Write the start bit
  tx_pin_write(_inverse_logic ? HIGH : LOW);
  tunedDelay(_tx_delay + XMIT_START_ADJUSTMENT);
  uint8_t p = 0;
  uint8_t t;
  for (t = 0x80; t; t >>= 1)
    if (b & t) p++;
  

  // Write each of the 8 bits
  if (_inverse_logic)
  {
    for (byte mask = 0x01; mask; mask <<= 1)
    {
      if (b & mask) // choose bit
        tx_pin_write(LOW); // send 1
      else
        tx_pin_write(HIGH); // send 0
    
      tunedDelay(_tx_delay);
    }
    // parity
    if (p & 0x01)
      tx_pin_write(HIGH); // send 0
    else 
      tx_pin_write(LOW); // send 1
    tunedDelay(_tx_delay);

    tx_pin_write(LOW); // restore pin to natural state
  }
  else
  {
    for (byte mask = 0x01; mask; mask <<= 1)
    {
      if (b & mask) // choose bit
        tx_pin_write(HIGH); // send 1
      else
        tx_pin_write(LOW); // send 0
    
      tunedDelay(_tx_delay);
    }
    // parity
    if (p & 0x01)
      tx_pin_write(LOW); // send 0
    else 
      tx_pin_write(HIGH); // send 1
    tunedDelay(_tx_delay);

    tx_pin_write(HIGH); // restore pin to natural state
  }

  SREG = oldSREG; // turn interrupts back on
  tunedDelay(_tx_delay << 1);
  
  return 1;
}

and check if it works. It’s just a quick hack, no testing done.

@mikesbaker: Did it work for you?

I face a similar problem communicating with a device which talks also 8E2...

Best regards,
Carsten

How about 5N1.5? Radio-teletype uses Baudot code 5 bits data and 1.5 stop bits at 45.45 baud no less.. I would like to solve this one.

Are you sending or receiving? If sending you could send 8N1 and make sure the last 3 bits (the high order ones) are set to 1. The receiving end won't be able to tell the difference.

Nick,

I have same problem. It is supposed I have configured Hyperterminal 9600,N,8,1 and Arduino UNO using 9600 but I have only garbash frow what I transmit from Arduino.

I am using RS-485 and using a MAX483...Ideas?

Thanks,

Post your code? It's too brief a description to tell.

CarlosAlbertoEstrada:
I have configured Hyperterminal 9600,N,8,1 and Arduino UNO using 9600 but I have only garbash frow what I transmit from Arduino.

But 8N1 should be the standard, we discuss here more the non standard case :wink: I would say concentrate on the physical connection...

Carsten

@pylon:

I tried it now and get these errors during compile:

C:\DOKUME~1\cw\LOKALE~1\Temp/ccFZO1Cg.s: Assembler messages:
C:\DOKUME~1\cw\LOKALE~1\Temp/ccFZO1Cg.s:243: Error: register r24, r26, r28 or r30 required
C:\DOKUME~1\cw\LOKALE~1\Temp/ccFZO1Cg.s:263: Error: register r24, r26, r28 or r30 required

Of course I have no idea what this means :%

EDIT: After a wild guess I inserted the #ifdef with the pushing and poping of the registers also for the write function. Now it compiles.

Single chars (using Putty.exe) are transmitted ok (even typing very fast) but using the serial monitor from the IDE (sends a whole line) only transmits 2 chars ok, then some garbage appears:

Send    : QWERT
Receive: QW¢©?@

(the last @ is a square, non-printable)

Carsten

Hmm. It works now after some hacking in SoftSerial.cpp

I probably (I am more a hacker than a programmer) found a bug in SoftSerial.

The size_t SoftwareSerial::write(uint8_t b) function is not sending a a stop bit.

Guess: when there are more than 2 chars (not sure why 2..) next chars comming too fast and the logical 1 on the pin is takes as new start bit, then the next bit as first etc. and so you get shifted garbage.

I added 2 stop bits (remember I need to communicate 8E2) like this (both for normal and inverse_logic):

   // STOP Bits
      tx_pin_write(LOW); // send 1
    tunedDelay(_tx_delay);
       tx_pin_write(LOW); // send 1
    tunedDelay(_tx_delay);

I encountered the same with the original example files for SoftSerial (see my topic: Arduino Forum). This was still with the unmodified SoftSerial.

Carsten

@calli: Are you sure you use the current IDE (1.0.1)? The push and pop stuff isn’t made by me but was there already. If you don’t use the current version you may get a lot of problems with my patch.

The size_t SoftwareSerial::write(uint8_t b) function is not sending a a stop bit.

I guess you haven’t understood the code.

This:

    tx_pin_write(HIGH); // restore pin to natural state

and

  tunedDelay(_tx_delay << 1);

is sending 2 stop bits. The TX line goes HIGH (logical 1) and stays there for twice a bit’s length.

I havn't seen that in your code. My fault.

And of course you are right, I am not understanding the code completely. However I understood it so much that I added the Stop bits again :wink: Well in my hacky way.

What me really bugs (and make me again feeling like a fool) is that NOW the normal SoftareSerial transmits "suddenly" all correct. And I have no clue why it did not work before (same hardware, IDE, Sketch, connections). That sucks :frowning:

And yes I am using 1.0.1.

Carsten

calli:
Guess: when there are more than 2 chars (not sure why 2..) next chars comming too fast and the logical 1 on the pin is takes as new start bit, then the next bit as first etc. and so you get shifted garbage.

Just to clarify, a start bit is a 0, and stop bit is a 1. It is impossible for a stop bit to be mistaken for a start bit. In fact, that is why they are opposite of each other.

Yes, please don’t put your finger on my wound :wink: :roll_eyes:

Carstem

Now.

Receiving from the device works. But sending not.

I tested very much now, and have now the following test scenario:

PC1 USB <->FTDI#1 <-> FTDI#2 <-> PC2

I tested it with RealTerm (very nice) and it worked in all configurations. Realterm can show HEX, and also (important) shows Errors. I could force an error when setting wrong Parity on PC1 and sending. So far so good.

Now:

PC1 USB <->FTDI#1 <-> HWSerial → Arduino → SoftSerial (8E2 Hacked) HW Pins to RX/TX of an FTDI#2 → PC2

With an unhacked SoftSerial it works without errors (8N1).

With the hacked one I always get “Error” (Serial params set correct). My device I like to talk to of course doesn’t like this and is not reacting (HW ok, works with direct terminal control).

I am (even more) clueless now. I am so close but…

@pylon: do you have an idea here?

Thanks,
Carsten

I gave up!

And switched to HardwareSerial, which means that I need to disconnect the ftdi and two wires every time I need to upload a sketch. BUT, now it works (was quite easy to get HW Serial to talk 8E2). My dialog is now via Softserial, which works now in 8N1 Mode ofcourse. Later I will use a Ethernet Module.

Thanks,
Carsten

I know this is an old thread, but I just found it now and figured someone else might be trying to do the same thing.

Although my understanding of the code is very weak, I tracked down the compile errors in pylon’s code to his copy-paste of these lines in the receive routine:

    tunedDelay(_rx_delay_stopbit);
    DebugPulse(_DEBUG_PIN2, 1);

For reasons I don’t understand, running this once (as in the original code) is fine, but running it several times in a row as pylon does causes the compile errors mentioned above. To get around this, I replaced the three instances in pylon’s code with this:

    tunedDelay(_rx_delay_stopbit * 3);
    DebugPulse(_DEBUG_PIN2, 1);

I didn’t verify the timing with an oscilloscope (extra stop bits should appear as more “space” between bytes), but I did feed the output into a device expecting two stop bits and was able to communicate with it.

Thanks Pylon for the original code!

Hi all..

I can also confirm maser228 that the proposed code modification compiles and works. I ve noticed one strange thing however that I cant explain:
I am using the softserial port with 9600, 8E1 and at the same time I am transmitting all data on the hardware serial port for monitoring on the serial port monitor included in the arduino IDE. I can receive all data sent by the remote device to the softserial port and they are monitored on the IDE''s serial port monitor. However all or most data sent by the softserial port to the remote device seem to somehow get lost. I have noticed that for some reason the problem disappears if I close the serial port monitor on the PC. Any ideas?