Serial Tutorial

Serial tutorial

More than you ever wanted to know about serial communications with the Particle Photon and Electron.

Updates to this document are here: [GitHub - rickkas7/serial_tutorial: Particle Photon and Electron serial tutorial] (GitHub - rickkas7/serial_tutorial: Particle Photon and Electron serial tutorial)

USB serial

The USB serial provides a way for the Photon/Electron to send data to the computer across the USB connection. Often this is used for debugging messages.

For example:

int counter = 0;

void setup() {
	Serial.begin(9600);
}

void loop() {
	Serial.printlnf("testing %d", ++counter);
	delay(1000);
}

The Serial.begin(9600); call initializes the serial port. When you're using the USB serial, the value doesn't actually matter. Sometimes you'll see Serial.begin(115200); but it really runs at the same fast speed regardless.

The Serial.printlnf prints a formatted string to the debugging USB serial.

The [documentation for Serial is here] (https://docs.particle.io/reference/firmware/photon/#serial).

Particle CLI

A common way to view debug serial messages is the [Particle CLI] (https://docs.particle.io/guide/tools-and-features/cli/photon/). Note that the CLI only reads serial messages, it is strictly a serial monitor, and you can't type things to the Photon/Electron. Still, it's quick and easy, and very handy.

$ particle serial monitor
Opening serial monitor for com port: "/dev/cu.usbmodemFD1161"
testing 1
testing 2
testing 3
testing 4
testing 5
testing 6

Particle Dev (Atom IDE)

Select Show Serial Monitor in the Particle menu if the Serial Monitor is not showing. Make sure you have the correct port select and click Connect.

Note that you can write stuff to the serial port in Particle Dev, but you must do so in the Enter string to send box, it's not like a regular terminal emulator where you type in the same place where stuff is being printed out.

Arduino IDE

Select the port from the Port hierarchical menu in the Tools menu.

Then select Serial Monitor from the Tools menu.

You can send data via serial with the Ardiuno IDE as well, but you need to enter text to send in the box at the top of the window and press Return or click Send.

Windows - using PuTTY or CoolTerm

For Windows, you can also use a program like [PuTTY] (Download PuTTY: latest release (0.79)) or [CoolTerm] (http://freeware.the-meiers.org).

It's hard to say what COM port your Photon or Electron will use, but if you open the Windows Device Manager and expand Ports (COM & LPT) it should show the device.

This is the configuration screen for PuTTY:

Click Serial (1) then enter the COM port number (2) then click Open (3).

Mac - using screen

Find the serial port that is being used using the Terminal program command line:

ls /dev/cu.usb*

It should return something like /dev/cu.usbmodemFD1161.

Then issue the command:

screen /dev/cu.usbmodemFD1161

Screen allows you you both send characters to the Photon or Electron as well as receive them from the USB serial device.

Linux - using screen

Find the serial port that is being used using the Terminal program command line:

ls /dev/ttyACM*

It should return something like /dev/ttyACM0.

Then issue the command:

screen /dev/ttyACM0

Screen allows you you both send characters to the Photon or Electron as well as receive them from the USB serial device.

Android phone or tablet with USB OTG

If your Android phone supports USB OTG ("on the go") and you have an OTG adapter cable, you may be able to use it for debugging serial! One caveat is that your phone probably won't power up a Photon, so this will probably only work if you have an external power source, like an Electron or Photon with a battery.

Install the "Android USB Serial Monitor Lite" application from the Google Play store.

Connect the device to your phone using a USB cable and a USB OTG adapter.

Open the serial monitor app and it should ask if you want to connect to the device. Tap yes and you should see a screen in the picture above.

Configuration using USB Serial

If the Photon is in listening mode (blinking dark blue), configuration can also be done using the USB Serial port. Each of these commands only requires that you type the command letter (case-sensitive):

  • i - Prints the device ID (24 character hexadecimal string)
  • f - Firmware update (using ymodem)
  • x - Exit listening mode
  • s - Print system_module_info
  • v - System firmware version
  • L - Safe listen mode (does not run user code concurrently with listening mode)
  • w - Configure Wi-Fi
  • m - Print MAC Address for the Wi-Fi adapter

Listening mode is the default when you plug in a Photon the first time. You can also get into listening mode by holding down SETUP for about 3 seconds until the status LED blinks blue.

The commands other then the last two Wi-Fi related commands are also available on the Electron.

Changing operating modes with USB Serial

Normally you press buttons to enter listening or DFU mode on the Photon or Electron. You can also trigger it by making a USB Serial connection at a specific baud rate.

For example, on the Mac you can use this command to enter DFU mode:

stty -f /dev/cu.usbmodemFD1141 14400

(The device name, cu.usbmodemFD1141 in this example, may be different on your computer.)

The special baud rates are:

  • 14400 DFU mode (blinking yellow)
  • 28800 Listening mode (blinking dark blue)

UART Serial

When connecting to an actual serial device, you'll be using one of the UART hardware serial ports. The Photon has oneā€  and the Electron has three UART serial ports.

All of the devices have the Serial1 object, the main UART serial port, on the RX and TX pins.

  • RX means serial data received into the device
  • TX means serial data transmitted from the device

When you connect a Photon another device, say an Arduino, the RX pin on the Photon always gets connected to the TX pin of the Arduino and vice versa. This is always the case, even if you connect two Photons by serial.

ā€  The Photon actually has two and the Electron four UART serial ports. The catch is that Serial2 is on the same pins as the RGB status LED. Using it requires soldering and disabling the status LED, which will make troubleshooting your device very difficult. It's a complicated enough topic that it has its own tutorial.

Additional ports on the Electron

The Electron has two additional UART serial ports that you can use, Serial4 and Serial5.

  • C0 Serial5 RX
  • C1 Serial5 TX
  • C2 Serial4 RX
  • C3 Serial4 TX

If you need Serial4 or Serial5 you'll need to enable the port by adding one or both of these includes near the top of your main source file:

#include "Serial4/Serial4.h"
#include "Serial5/Serial5.h"

Serial logic levels

The Photon and Electron are 3.3V serial devices that are 5V tolerant. When transmitting data, logic 1 values are 3.3V and logic 0 values are 0V, so we list the port as being 3.3V.

Many 5V serial devices will correctly respond to 3.3V values as logic 1 even though it is out-of-spec. Likewise, the Photon doesn't mind having 5V levels for logic 1 on the RX pins. So you often can connect a Photon directly to a 5V serial device, like an Arduino. This is often referred to as "TTL serial" as it uses the 5V logic levels used by TTL (transistor-transistor logic) devices.

One thing that you absolutely must never do is connect a Photon directly to a computer or other device using an actual RS232 interface. A converter is required and is described in the next section.

Interfacing to RS232 devices

Actual RS232 devices, such as old computers, newer computers with an adapter, and various external hardware devices likely use "real" RS232 signal levels, which can range between +15V and -15V. This will cause immediate, permanent damage to the Photon or Electron if connected directly.

A TTL serial to RS232 adapter board is typically used in these cases. They are available from places like [SparkFun] (SparkFun RS232 Shifter - SMD - PRT-00449 - SparkFun Electronics). You can also find them on eBay, search for "TTL RS232 breakout".

Make the following connections:

  • Converter VCC to Photon 3V3 (red)
  • Converter GND to Photon GND (black)
  • Converter TX-O to Photon TX (green)
  • Converter TX-I to Photon RX (blue)

Note that TX and RX don't cross here, between the Photon and the converter, because they're crossed in the RS232 serial cable DCE to DTE connection.

Connectors

Two different connectors are used for RS232 serial, the DB9 and the DB25. The DB25, a "D" shaped 25-pin connector was the original connector but IBM PC compatible computers mostly settled on the smaller DB9 connector. The DB9 is also referred to as a DE9 connector.

This is a close-up of the DB9 female connector on the [SparkFun] (SparkFun RS232 Shifter - SMD - PRT-00449 - SparkFun Electronics) converter board.

DTE/DCE

Serial devices are either DTE (data terminal equipment) or DCE (data communication equipment). These names come from the early days of dumb terminals (DTE) connected to modems (DCE). It doesn't entirely matter which device is which, but if you have to connect two like items (say DTE to DTE) you need a null modem adapter that crosses the TX and RX lines (among others).

Typically DTE devices have a male connector. Pin 2 is an input and pin 3 is an output.

Likewise, DCE devices typically have a female connector. Pin 2 is an output and pin 3 is an input.

The Sparkfun board has a female DB9 and makes the Photon a DCE. This makes sense because most computer serial ports are DTE.

Baud rate, bits, parity, and stop bits

There are four configuration parameters for serial, and you must make sure they're all set correctly, otherwise communication will often fail, either with no data or garbage characters received.

The baud rate is the speed that data is sent. A common value is 9600. The valid values for the Photon and Electron are 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, and 115200. Neither device can use speeds under 1200 (such as 300 or 600).

The number of bits per byte is typically 8. It's occasionally 7 or 9. There is limited support for 9 bit in system firmware 0.5.0 and later, and full support for 7 and 9 bit in 0.6.0 and later.

The parity is a method of detecting errors in the data. It can be none ("N"), odd ("O") or even ("E"). Support for parity is included in 0.5.0 and later.

The number of stop bits is 1 or 2. Support for stop bit setting is included in 0.5.0 and later.

The last three things are typically combined into a single string, for example "8N1" means 8 bits, no parity, 1 stop bit. "7E1" means 7 bits, even parity, 1 stop bit. And so on.

The available values are:

  • SERIAL_7E1
  • SERIAL_7E2
  • SERIAL_7O1
  • SERIAL_7O2
  • SERIAL_8N1
  • SERIAL_8N2
  • SERIAL_8E1
  • SERIAL_8E2
  • SERIAL_8O1
  • SERIAL_8O2
  • SERIAL_9N1
  • SERIAL_9N2

You use these with the Serial.begin call, for example:

Serial1.begin(9600, SERIAL_9N1);

Flow control

There are two types of flow control in serial: hardware (RTS/CTS) and software (XON/XOFF).

The Photon does not support hardware flow control (RTS/CTS). The Electron does not currently support hardware flow control.

Neither the Photon or Electron support software (XON/XOFF) flow control, either. In some limited cases, you could note when you receive XOFF (Ctrl-S) in your received data and stop sending, however there is currently no way to stop the send FIFO from sending, so this will only work when you don't have any data waiting to be sent.

Communicating with an Arduino

Here's an example of using serial to communicate between an Arduino (Uno, in this case) and a Photon.

  • Photon TX connects to Arduino RX (0) (green wire)
  • Photon RX connects to Arduino RX (1) (blue wire)
  • Photon GND connects to Arduino GND (black wire)

Remember: RX and TX always cross, and you must have a common GND connection.

This is a rather silly example: Every 2 seconds the Photon sends a number to the Arduino by UART serial. The Arduino parses this number, increments it, and sends it back. The Photon prints it out via the debugging serial.

Remember that serial is a byte-oriented protocol, so we keep reading bytes until we find a character that marks the end of the transmission. I selected the new line character ("\n").

Arduino code:

// Constants
const size_t READ_BUF_SIZE = 64;

// Forward declarations
void processBuffer();

// Global variables
char readBuf[READ_BUF_SIZE];
size_t readBufOffset = 0;

void setup() {
  // Serial TX (1) is connected to Photon RX
  // Serial RX (0) is connected to Photon TX
  // Ardiuno GND is connected to Photon GND
  Serial.begin(9600);
}

void loop() {
  // Read data from serial
  while(Serial.available()) {
    if (readBufOffset < READ_BUF_SIZE) {
      char c = Serial.read();
      if (c != '\n') {
        // Add character to buffer
        readBuf[readBufOffset++] = c;
      }
      else {
        // End of line character found, process line
        readBuf[readBufOffset] = 0;
        processBuffer();
        readBufOffset = 0;
      }
    }
    else {
      readBufOffset = 0;
    }
  }

}

void processBuffer() {
  int receivedValue = atoi(readBuf);

  // This program just increments the value sent by the Photon and returns it
  Serial.print(receivedValue + 1, DEC);
  Serial.print('\n');
}

Photon code:

#include "Particle.h"

// Constants
const unsigned long SEND_INTERVAL_MS = 2000;
const size_t READ_BUF_SIZE = 64;

// Forward declarations
void processBuffer();

// Global variables
int counter = 0;
unsigned long lastSend = 0;

char readBuf[READ_BUF_SIZE];
size_t readBufOffset = 0;

void setup() {
	Serial.begin(9600);

	// Serial1 RX is connected to Arduino TX (1)
	// Serial2 TX is connected to Arduino RX (0)
	// Photon GND is connected to Arduino GND
	Serial1.begin(9600);
}

void loop() {
	if (millis() - lastSend >= SEND_INTERVAL_MS) {
		lastSend = millis();

		Serial1.printlnf("%d", ++counter);
		Serial.printlnf("Sent to Arduiuno: %d", counter);
	}

	// Read data from serial
	while(Serial1.available()) {
		if (readBufOffset < READ_BUF_SIZE) {
			char c = Serial1.read();
			if (c != '\n') {
				// Add character to buffer
				readBuf[readBufOffset++] = c;
			}
			else {
				// End of line character found, process line
				readBuf[readBufOffset] = 0;
				processBuffer();
				readBufOffset = 0;
			}
		}
		else {
			Serial.println("readBuf overflow, emptying buffer");
			readBufOffset = 0;
		}
	}

}

void processBuffer() {
	Serial.printlnf("Received from Arduino: %s", readBuf);
}

Sample output:

$ particle serial monitor
Opening serial monitor for com port: "/dev/cu.usbmodemFD1161"
Sent to Arduiuno: 1
Received from Arduino: 2
Sent to Arduiuno: 2
Received from Arduino: 3
Sent to Arduiuno: 3
Received from Arduino: 4
Sent to Arduiuno: 4
Received from Arduino: 5

More Code Tips

Some additional helpful tips:

Process all available characters

You should process all available serial characters in each call to loop. In other words, don't do this, using an if statement. Use a while, instead.

void setup() {
	Serial.begin(9600);
	Serial1.begin(9600);
}

void loop() {
	// Don't do this! Use a while loop instead of an if to make sure all
	// of the data is processed in each call to loop
	if (Serial1.available()) {
		Serial.write(Serial1.read());
	}
}

The reason is that loop may be called at most 1000 times per second, and often fewer, so it's possible to fall behind faster serial speeds unless you handle all of the outstanding bytes at once.

Avoid delay in loop

When processing serial data, make sure you don't block the loop. One obvious way to do this is using delay, but there are other more subtle ways.

For example, if you wanted to do something once per second while handling serial data, you should not do this:

#include "Particle.h"

void setup() {
	Serial.begin(9600);
	Serial1.begin(9600);
}

void loop() {
	while (Serial1.available()) {
		Serial.write(Serial1.read());
	}

	// Don't do this!
	delay(1000);
	Serial.println("called once per second");
}

The problem is that serial data won't be handled during the 1 second long delay, which could cause data to be lost.

Instead you could do this:

unsigned long lastTime = 0;

void setup() {
	Serial.begin(9600);
	Serial1.begin(9600);
}

void loop() {
	while (Serial1.available()) {
		Serial.write(Serial1.read());
	}

	if (millis() - lastTime >= 1000) {
		lastTime = millis();
		Serial.println("called once per second");
	}
}

This allows loop to run freely, and still only execute some code once per second.

Reading lines of data

Serial is a byte or character-oriented method for transferring data. The hardware assures that you will always receive a character at a time; you'll never get half a character, for example.

For convenience, data may be grouped into lines of characters. The Ardiuno example, above, read a line at a time. Even if you're expecting a line of data, there is no guaranteed that all of your data will arrive at once. In fact, it's quite likely that it won't and you will have to write your code to handle that.

One easy way to do this is to use readStringUntil. For example:

void setup() {
	Serial.begin(9600);

	// Wait up to 10 seconds for a line to arrive
	Serial.setTimeout(10000);
}

void loop() {
	String s = Serial.readStringUntil('\n');
	Serial.printlnf("got %s", s.c_str());
}

This is great only if you have nothing to else to do in your loop. The call may block for up to 10 seconds (as configured here), which may be unacceptable for some applications. In that case, you may prefer to do this manually, so the loop runs freely:

// Constants
const size_t READ_BUF_SIZE = 64;
const unsigned long CHAR_TIMEOUT = 10000;

// Global variables
char readBuf[READ_BUF_SIZE];
size_t readBufOffset = 0;
unsigned long lastCharTime = 0;

void setup() {
	Serial.begin(9600);
}

void loop() {
	// Read data from serial
	while(Serial.available()) {
		if (readBufOffset < READ_BUF_SIZE) {
			char c = Serial.read();
			if (c != '\n') {
				// Add character to buffer
				readBuf[readBufOffset++] = c;
				lastCharTime = millis();
			}
			else {
				// End of line character found, process line
				readBuf[readBufOffset] = 0;
				Serial.printlnf("got: %s", readBuf);
				readBufOffset = 0;
			}
		}
		else {
			Serial.println("readBuf overflow, emptying buffer");
			readBufOffset = 0;
		}
	}
	if (millis() - lastCharTime >= CHAR_TIMEOUT) {
		lastCharTime = millis();
		readBuf[readBufOffset] = 0;
		Serial.printlnf("got timeout: %s", readBuf);
		readBufOffset = 0;
	}
}
16 Likes

A sidenote: Since USB Serial doesnā€™t care what baudrate you pass - as you already mentioned -, newer system versions do allow to leave that parameter out.

How do you get your application to wait until the Serial port has opened before continuing? I have to this point simply been waiting for serial input before proceeding with the setup() function, but I would like to use a method whereby the user only has to open the serial port (not type anything into it). That way it will work with Serial terminals that you canā€™t type into.

Hereā€™s what I have been using:

   void waitForSerialInput(){
      while(!Serial.available()){
      }
      while(Serial.available()){
        char useLessChar = Serial.read();
      }
   }
   void setup(){
      waitForSerialInput();
   }

Iā€™ve used this technique. What it does it if D6 is jumped to ground, then it will wait 10 seconds for a keypress before continuing.

If D6 is unconnected, setup proceeds normally.

void setup() {
	Serial.begin(9600);
	pinMode(D6, INPUT_PULLUP);
	if (digitalRead(D6) == LOW) {
		// Only check for waiting for serial when D6 is pulled low
		unsigned long waitStart = millis();

		// Wait for 10 seconds or until a key is pressed
		while(!Serial.available() && ((millis() - waitStart) < 10000)) {
			Particle.process();
		}
	}
	Serial.println("exiting setup");
}

void loop() {

}
1 Like

@jaza_tom, I never noticed this and have not tested it!

https://docs.particle.io/reference/firmware/photon/#isconnected-

Iā€™m kind of new to using the photon board and Iā€™m having some problens when I try to apply the Arduino and Photon communication. I canā€™t seem to see the output from the comunication (like the sample output), do I have to do something other than the indicated in the example code? Any help would be great, thanks!

Great tutorial, but Iā€™m finding one thing missing. Iā€™m trying to push relatively high bandwidth data over the USB serial channel to a photon. Iā€™m asking for a baud rate of 115200 in my Python code on the PC and in the serial.Begin(115200) on the photon. I believe the photon one will definitely be ignored, but it also appears that regardless of what baud rate I set in my Python code, the photon only ever talks at 9600 baud over that serial link. Is there a method by which I can set a higher baud rate on the photonā€™s USB Serial and actually have that setting actioned?

@DKW_Engineering, USB is ā€œbaud-lessā€. However, you CAN set the baudrate on the Photon and it works. Perhaps you can share your code so we can see if there is anything amiss.

1 Like

Iā€™ll build a minimum-test-case when Iā€™m back at my desk. Apologies for the double post, I was going to spin up a new thread rather than comment on this old one, I mustā€™ve posted from both browser tabsā€¦ :confounded:

1 Like

but even the opening post in this thread clearly states

For that reason USB Serial even supports a parameterless overload Serial.begin() that should make it absolutely clear that baudrate is irrelevant on the device side.
And from the host side, the set baudrate should also not impact the transfer speed as USB reports are usually sent with the default USB 2.0 speed (unless your drivers "interfere").

How did you "measure" this?

Hey Scruff,

I wrote a python script using pySerial that blatted data at the photon in packet sizes of 1, 10 or 50 bytes, then timed how long it took me to spit a couple of K down the link at each of those packet sizes. The serial port was opened at 115200 baud, and I think I was testing using a total transmit of 2050 bytes. The photon had a callback firing on each RXd byte using the SerialEvent callback, but wasn't actually doing anything with the data. When I did deeper testing I was going to go in and look at inter-callback times and dropped bytes as the photon saw them.

I was getting about 1K bytes/second over the link regardless of packet size. That led me to believe that:

  1. Since the packet size seemed to be irrelevant it looked like a throttling issue with the link itself rather than my Python code.
  2. That number was was near enough to a 9600 baud asynch serial link that I figured there was something somewhere that was using that as a value.

Since I don't have RX and TX lines to stick my silly-scope onto that was about the most detailed testing I could come up with.

These are no real event callbacks but merely synchronous functions that are called between iterations of loop(). This in combination with the misleading naming is an Arduino legacy which has caused some confusion with others before.
Since loop() gets called about every 1ms when cloud connected and you only reading one byte at a time, that might explain the 1K you got, but that's not the max speed data gets transmitted.

To do some "more precise" testing, you can disable the could connection and run a tight loop that permanently reads and counts bytes received over a given time.

@ScruffR - What Iā€™m measuring is the out-time from the Python, not the in-time on the Photon. Should be the same thing, possibly even slightly faster since there will be some buffering in the PC driver layer. Iā€™m not using the photon for the timing at all.

Iā€™ve been doing some more digging here, it may be possible that itā€™s a Win10 thing as-well, but I saw a bunch of references to 9600 baud on the serial.begin() so I thought it was a possible candidate.

Check your out settings whether flow control is enabled or not. The Photon doesn't support flow control but if your host would expect it, that might incur some delay.

Serial<id=0x508d6d0, open=True>(port=ā€˜COM3ā€™, baudrate=115200, bytesize=8, parity=ā€˜Nā€™, stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)

Thatā€™s what Pythonā€™s giving me - All looks good. I just did a couple of minimal tests, and I have to admit Iā€™m now more suspicious of Python than I am of the Photon. I think the next step will be to dust off a usb-3V3uart converter and try the same tests against the photonā€™s Serial1 at 115200 so I can get a scope on the TX and RX lines and get a better sense for exactly whatā€™s going on.

ISSUE SOLVED!

TL;DR
Thereā€™s an interesting interaction between pySerial (and possibly other serial interface implementations) and the Photonā€™s USB serial port that means flow control is effectively always on. If your read rate is limited by the frequency of your loop() function, then it can look like a low-baud serial link when itā€™s actually a firmware bug.

Long Version

OK all who are interested, hereā€™s the deal. There are a few factors at play here, which combine together to give some really strange false-positive results when testing:

  1. I donā€™t know about other interfaces, but when interfacing to pySerial, regardless of the flow control settings, the python code will block if the 128 byte RX buffer on the photon is full until the photon has cleared some of that data out. Occasionally the code will timeout, but I havenā€™t been able to find rhyme or reason on that one. Therefore at the Python end, it appears as if thereā€™s a local TX buffer thatā€™s being overfilled because the pipeline size isnā€™t sufficient for the data youā€™re trying to send.
  2. As mentioned by Scruff, the SerialEvent() ā€œcallbackā€ does not behave like a traditional ISR. I was expecting it to be called repeatedly while there was serial data to be processed. Instead, it will be called only once at the end of loop() if there is serial data in the RX buffer.
  3. One wierd result of this - If your loop() function has a 10 mSec delay in it, and your SerialEvent() handler is only reading one byte at a time, you get an apparent data rate of 100 bytes/second regardless of the baud rate setting of the serial link.

It was an issue in my Photon firmware, nothing to do with the Python or Win10 issues I had been chasing for a while. Hereā€™s the summarry:

BROKEN CODE:

void loop(void){
  // Do some assorted data processing.
  MySerialHandler.processData(); // Process any data in my buffer as appropriate
  delay(10); // I want a roughly 100 Hz core loop
}

// Because loop() is only running at 100Hz, this will get called at a _FASTEST_ speed of 100 Hz
SerialEvent(void)
{
  mySerialHandler.RXData(Serial.read());
}

WORKING CODE:

void loop(void){
  // Do some assorted data processing.
  while(Serial.available())  // Make sure I'm reading out ALL the available data
    mySerialHandler.RXData(Serial.read());
  MySerialHandler.processData(); // Process any data in my buffer as appropriate
  delay(10); // I want a roughly 100 Hz core loop
}

I now have the capability to push ~20 byte packets to the photon at about 100 Hz. Not a crazy-high-rate test, but sustained for 20 minutes seems to be stable.

Thanks as always for your help @ScruffR!

1 Like

There are better ways to achieve that and service the interface as fast as possible
e.g.

void loop() {
  static uint32_t ms = 0;
  // should be done on every call of loop()
  while(Serial.available())  // Make sure I'm reading out ALL the available data
    mySerialHandler.RXData(Serial.read());

  if (millis() - ms < 10) return;
  ms = millis();
  // should only be executed once every 10ms
  MySerialHandler.processData(); // Process any data in my buffer as appropriate
}

Additionally decoupling cloud and application process via SYSTEM_THREAD(ENABLED) might also increase max transfer rate.

2 Likes

Hi,

I am attempting to send and receive messages via Serial1 and display these messages on Serial for debugging with as little delay as possible. I would like to avoid using delay(ā€¦) or any functions which could potentially block for more than 200 ms. The options I have looked into are:

  1. Counting the exact number of letters I expect to receive from Serial1 and using something like this, the exact number of times for the exact number of letters I expect to receive. The issue here is if I receive an unexpected message, the function will wait forever. If I set a timeout below 500 ms for the while loop, the message does not read properly. However, the timeout of 500 ms can stack up and cause a large delay.
      void readResponse(void) {
         while (Serial1.available() == 0) {
           ;
          }
         while (Serial1.available()) {
           Serial.write(Serial1.read());
         }
      }
  1. I can use this, as suggested by the main post. However, this is quite slow and can block for potentially 10 seconds.
     String s = Serial1.readStringUntil('\n');
	 Serial.printlnf("Received: %s", s.c_str());
  1. Using delays :frowning:
     delay (1000);
     while (Serial1.available()) {Serial.write(Serial1.read()); }

Any ideas?

For 1.

You are not servicing the cloud tasks while there is no data in buffer, which will lead to connection loss after ~10seconds. You could call Particle.process() in there, but I'd rather just fall through the function if there is no data to be read.

For 2.
I'd advise against the use of String, as it may lead to heap framentation and unexpected behaviour over time.

For 3.
Don't do that either.

I'd rather use a global or local static buffer, which is incrementally filled (by use of a global/local static index) when data is present otherwise the function just falls through to repeat the same process on next visit. Once a complete message has been received, process the message, clear the buffer and start over.
This could also be implemented as FSM.

1 Like

Thereā€™s an example after the readStringUntil example that accomplishes the same thing without blocking. The test looks for \n but you could change it to test for other things, like a specific number of characters, instead.

Thatā€™s the model you should use.

1 Like