Skip to content

SerialPIO with baudrate 19200 receives incorrect data #2928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
GHswitt opened this issue Apr 27, 2025 · 5 comments · Fixed by #2929
Closed

SerialPIO with baudrate 19200 receives incorrect data #2928

GHswitt opened this issue Apr 27, 2025 · 5 comments · Fixed by #2929
Labels
waiting for feedback Requires response from original poster

Comments

@GHswitt
Copy link

GHswitt commented Apr 27, 2025

Hi all,

I'm trying to get SerialPIO working with a Pico. After receiving incomplete data and disabling/removing more and more code I'm down to this test program:

#include <SerialPIO.h>

SerialPIO ser3(-1,15,32);

void setup() {
  Serial.begin(115200);
  ser3.begin(19200);
}

void loop() {
  if (ser3.available()) {
    Serial.write(ser3.read());
  }
}
  • If I'm using a hardware serial (Serial2 etc.), I get the correct data.
  • Checked the signal with a scope, it is good.
  • Tried different IOs for RX: 3, 5, 7, 11
  • Tried different FIFO sizes

I also tried changing the system clock from 200 MHz to 120 MHz or 240 MHz since because of this code:

pio_sm_put_blocking(_rxPIO, _rxSM, clock_get_hz(clk_sys) / (_baud * 2) - 7 /* insns in PIO halfbit loop */);

clock_get_hz(clk_sys) / (_baud * 2) - 7

With 120 MHz and 19200 it gets a nice round number of 3118.

It is not that it never matches the data but is about 50% errors or more, for example:

MON	�������������
H1	-89268
H2	-�����������������C!�S�jRB�%�������5)!�%���ip���p�jljrr���r�9579
H10	7
H11�������������������LS�jRB��%����5)!��%���������������B
PID	0xC038
V	53065
I	-5150
P	-27�����������������BN�SH�Eu�4���jR

Normally everything is ASCII, tabs and newlines.

I would be happy for every idea what I could check.

@earlephilhower
Copy link
Owner

Can you post a pulseview/CSV high resolution capture of the serial data at the Pico pin? Without your specific hardware there's not much that can be done by anyone else.

A simple loopback test, using UART0 and SoftwareSerial runs fine. Hook pin GP0 to GP15 and the output is as expected without corruption so there's nothing immediately obvious:

#include <SoftwareSerial.h>

SoftwareSerial s(15, -1);
void setup() {
  Serial1.setTX(0);
  Serial1.begin(19200);
  s.begin(19200);
}

void loop() {
  delay(1000);
  Serial1.println("Had we but world enough and time");
  uint32_t now = millis();
  while (millis() - now < 1000) {
    while (s.available()) {
      auto c = s.read();
      Serial.printf("%02x '%c'\n", c, c);
    }
  }
}
48 'H'
61 'a'
64 'd'
20 ' '
77 'w'
65 'e'
20 ' '
62 'b'
75 'u'
74 't'
20 ' '
77 'w'
6f 'o'
72 'r'
6c 'l'
64 'd'
20 ' '
65 'e'
6e 'n'
6f 'o'
75 'u'
67 'g'
68 'h'
20 ' '
61 'a'
6e 'n'
64 'd'
20 ' '
74 't'
69 'i'
6d 'm'
65 'e'
0d '
'
0a '
'

The divider in SerialPIO being off by a fraction of a 133MHz clock is immaterial at 19200bps.

pio_sm_put_blocking(_rxPIO, _rxSM, clock_get_hz(clk_sys) / (_baud * 2) - 7 /* insns in PIO halfbit loop */);

The way the SW serial UART is implemented it waits for the 1st transition of the start bit and counts to 1/2 the bit time, then samples every bit time (~2x that calculated delay). If there is noise on the line (or a very slow transition so the pin is indeterminate state) even for 1/133MHz of a second, then the start bit may be detected before the actually intended start bit. I think there were some cheap Chinese GPS units that had this kind of issue, check the closed reports here. They solved it, IIRC, by adjusting the baud rate passed in to work around the noise.

@earlephilhower earlephilhower added the waiting for feedback Requires response from original poster label Apr 27, 2025
@GHswitt
Copy link
Author

GHswitt commented Apr 28, 2025

Thanks for your answer. I'll try to get the capture and provide a picture from the scope.

From my experience local loopback on the same system works most of the time fine as both UARTs are using the same system clock and experience no clock drift between them.

The errors start when using different systems with different clocks.

In my case sometimes the data is correct for a short period (2-3 seconds). Then errors occur for a longer time and the data may be ok for some bytes. The source is a Victron Smartshunt, connected to a PC I did not see errors during half an hour of monitoring, so I assume the TX baud matches good enough standard 19200, but I will compare again.

earlephilhower added a commit that referenced this issue Apr 28, 2025
The SerialPIO(SoftwareSerial) receive path was convoluted and required
a lot of work on the host to get the actual data out.  It also wasn't
always sampling on the proper edge leading to errors whenever clocks
or hold times shifted slightly.

Rewrite the SerialPIO RX path fo explicitily wait for start bit,
pause 1/2 bit time, then idle for a full bit time for all bits.
Takes more PIO instruction memory but works flawlessly even with
mismatched clocks.

Tested with a loopback from HW UART to SW UART with a 5% clock
mismatch @ 19200 baud without reception errors, whereas the
original code would fail with less than a 0.5% variation.

Fixes #2928

````

SoftwareSerial s(15, -1);
void setup() {
  Serial1.setTX(0);
  Serial1.begin(19200 + 1920/2, SERIAL_8N1);
  s.begin(19200, SERIAL_8N1);
}

void loop() {
  Serial.println("---");
  Serial1.write("Had we but world enough and time", 32);
  uint32_t now = millis();
  while (millis() - now < 500) {
    while (s.available()) {
      auto c = s.read();
      Serial.printf("%02x '%c'\n", c, c);
    }
  }
}
````
@earlephilhower
Copy link
Owner

@GHswitt please give #2929 a try. I was able to reproduce failure with a slight baud rate mismatch on the old one, but with the one in the PR it's solid to +/-5% @19200 baud.

@GHswitt
Copy link
Author

GHswitt commented Apr 29, 2025

@earlephilhower Sorry for the delay. That is great, I did a test with the device and it looks very good, I now see correct data even for multiple hours. I also did a test with a USB-Serial converter (cheap CP210x something), which before also failed at 19200. Same here, works fine.

Thank you very much, I can now use the RP2040 to monitor five devices and use a sixth serial port for RS485.
If it would help I can still collect the data and do more tests/measurements.

@earlephilhower
Copy link
Owner

Awesome, thanks for the update. No need to capture more info. I think the new way is simple enough to grok by just reading the code and it's now checking exactly (and only) at the bit center. My original code hurt my head trying to intuit exactly what bits and positions were getting dumped...

earlephilhower added a commit that referenced this issue Apr 29, 2025
The SerialPIO(SoftwareSerial) receive path was convoluted and required
a lot of work on the host to get the actual data out.  It also wasn't
always sampling on the proper edge leading to errors whenever clocks
or hold times shifted slightly.

Rewrite the SerialPIO RX path to explicitily wait for start bit,
pause 1/2 bit time, then idle for a full bit time for all bits.
Takes more PIO instruction memory but works flawlessly even with
mismatched clocks.

Tested with a loopback from HW UART to SW UART with a 5% clock
mismatch @ 19200 baud without reception errors, whereas the
original code would fail with less than a 0.5% variation.

Fixes #2928

````

SoftwareSerial s(15, -1);
void setup() {
  Serial1.setTX(0);
  Serial1.begin(19200 + 1920/2, SERIAL_8N1);
  s.begin(19200, SERIAL_8N1);
}

void loop() {
  Serial.println("---");
  Serial1.write("Had we but world enough and time", 32);
  uint32_t now = millis();
  while (millis() - now < 500) {
    while (s.available()) {
      auto c = s.read();
      Serial.printf("%02x '%c'\n", c, c);
    }
  }
}
````
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
waiting for feedback Requires response from original poster
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants