Skip to content

fix Rx edge sensitivity to recover from framing error / BREAK#526

Open
gicking wants to merge 1 commit intoarduino:mainfrom
gicking:fix_SoftwareSerial_framing-error
Open

fix Rx edge sensitivity to recover from framing error / BREAK#526
gicking wants to merge 1 commit intoarduino:mainfrom
gicking:fix_SoftwareSerial_framing-error

Conversation

@gicking
Copy link

@gicking gicking commented Feb 4, 2026

I maintain an Arduino library for LIN master emulation. A LIN frame starts with a BREAK signal (>13b low).

With the original SoftwareSerial implementation I was unable to receive anything after BREAK. On deeper analysis I found that the selected Rx-IRQ edge sensitivity (CHANGING) interprets the rising edge of the BREAK as the start bit for the next byte (see screenshot), which leads to loss of sync.

grafik

However, when I change the Rx edge sensitivity to only falling (or rising for inverted logic), the issue disappears and I can receive the complete frame without hassle :-)

To verify this fix I checked the Rx reception after a BREAK (aka FE) using the below sketch. Here are the logs for original (all fail) and fix (all ok)

/*
  check SoftwareSerial bugfix for framing errors

  Setup: 
    - connect Tx1 with Rx
    - check console output
*/

#include <SoftwareSerial.h>

SoftwareSerial SoftSerial(2, 7); // Rx, Tx

void setup() {
  Serial1.begin(19200);       // HW-Serial / sender
  SoftSerial.begin(19200);    // SW-Serial / receiver
  Serial.begin(115200);       // console output
  delay(5000);                // allow terminal to connect to VCP
}

void loop() {

  static uint8_t Tx=0x00, Rx[2];

  // flush Rx buffer, just to be sure
  while (SoftSerial.available())
    SoftSerial.read();

  // send BREAK / provoke Rx framing error
  Serial1.begin(9600);
  Serial1.write((uint8_t) 0x00);
  Serial1.begin(19200);
  
  // send byte
  Serial1.write((uint8_t) Tx);

  // read loop-back (BREAK + byte)
  while (SoftSerial.available() < 2);
  SoftSerial.readBytes(Rx, 2);

  // check received bytes
  Serial.print("Tx: 0x00 0x");
  Serial.print(Tx, HEX);
  Serial.print(", Rx: 0x");
  Serial.print((int) Rx[0], HEX);
  Serial.print(", 0x");
  Serial.print((int) Rx[1], HEX);

  if ((Rx[0] == 0x00) && (Rx[1] == Tx))
    Serial.println(" -> ok");
  else
    Serial.println(" -> FAIL");

  // test 0x00..0xFF
  Tx++;
  if (Tx == 0x00)
    while(1);

  delay(50);
}

@pennam pennam requested a review from maidnl February 5, 2026 16:09
Copy link
Contributor

@maidnl maidnl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a "quick and dirty" way to fix the problem.
It misses something: the fact that the pin Irq is set to CHANGE allows the timer to be restarted at every front of the bit reception assuring the greatest possible accuracy with timing.
Besides the PR is issued for LIN SYNC BREAK field. I must say I did not check the LIN library implementation for lack time, so forgive me if a am wrong. I suppose that if you are using Serial Software to handle LIN communication you are treating the SYNC BREAK field as something special (a received 0x00 with a missing stop bit, I suppose). But this is not a "true" SYNC. It is again something quick and dirty (again sorry if this is not the case).
In my opinion it would be better to have a more comprehensive PR that takes care of the particularity of the SYNC BREAK field (line kept low for more than 13 bytes): a function that "suspends" Serial Software and checks for a true SYNC and then restarts the Serial Software when a SYNC BREAK field is received. This would be beneficial for 2 reasons: it will not reduce the sync capabilities of this library and it will truly check for a SYNC LIN frame, compliant with LIN specification.
Saying that this PR should not harm Serial Software, but only reduce its own re-sync capacities.

@gicking
Copy link
Author

gicking commented Feb 6, 2026 via email

@gicking
Copy link
Author

gicking commented Feb 6, 2026

After thinking about this a bit more, I no longer understand why my PR works - but I have a hunch... Maybe you can enlighten me. If I understand the concept correctly(?), SoftwareSerial reception works like this

  1. at the first edge of the start bit, the pin-ISR starts a timer with an overflow period of 1 byte duration
  2. for following edges the respective timer count value is stored. I assume that's what the DMA does...?
  3. on timer overflow (i.e. byte is complete) the SW restores the received byte from the stored edge times

If this understanding is correct(?), there is no way that only FALLING (or RISING) edge can work. Because if e.g. only falling edges are stored, the SW cannot distinguish between bit patterns 10110 and 10010.

But since my empirical tests did work (see here), I suspect that that only above step 1 is impacted by the edge sensitivity which I changed, and that the DMA triggers for step 2 have their own sensitivity (which I did not change). And step 3 is anyway triggered by the time overflow.

Is this understanding correct or is my thinking completely off...? For your feedback thanks a lot in advance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants