Particle Threading Options

Background:

I have a process running in loop() that communicates to a device via Serial1.

I use a timer to trigger a function (every 100ms). If the function finds Serial1.available, it reads bytes, assembles data frames, validates frame format and checksums, processes unsolicited frames that report device events, and writes solicited “response” frames in a circular buffer where loop() can find them.

Question 1: The timer-called function’s first line checks a semiphore and exits if another instance is still running. Is that check necessary, or is it impossible for the timer to trigger multiple instances?
Question 2: Would it be more appropriate to execute the timer-called function in a separate Particle “Thread”?

More Background:

When the Serial1 processing logic mentioned above encounters an unsolicited event frame from the device, it currently calls another functions(aka event-handlers) to process the event. If this event-handler burns too many cycles, Serial1 data will not be handled in a timely manner. I, therefore, need to run the event-handlers in a different thread. I’ve been reviewing the following options:

  • Particle Threads won’t work because they never stop.
  • I’ve tried a one-shot timer … it works, but I can still create extreme situations where the event-handler interferes with with the processing of Serial1 data.
  • I can write the unsolicited event frames in a second buffer and let loop() call the event handlers. Any time delays would be more manageable in loop().

I keep looking for a way to trigger the event handlers from software … the way we trigger Particle Functions from the web.

Question 3: Am I missing any options?

Q1) Software Timers are not executed in parallel but sequentially. There is one thread that services all the individual timer callbacks as they come in. If one callback is still running while another or the same timer expires, the new request will be queued. If one and the same timer overtakes itself, you are in trouble as this would just pile up eventually exhausting the stack for the timer thread.

Q2) No

Q3) What happens inside your main loop() and what baudrate does your Serial1 communication use?
Is it possible to restructure loop() to have serialEvent1() do the reading of Serial1?
If you donate a big enough circular buffer and let your timer thread or serialEvent1() “permanently” drain the RX buffer into the intermediate buffer you may be able to buy yourself enough time to do the heavy lifiting in loop().

What kind of data are your processing? Can you really not accept to lose any of the data?
Maybe reevaluating/reframing the border assumptions/propositions can open additional paths.

This sounds like what 4D Systems does in their Arduino code for their serial displays. They created an FSM for receiving solicited and unsolicited bytes from the serial port. The servicing function is called from loop() as often as possible since there are no threads or interrupts involved. They buffer the unsolicited bytes and processes the expected bytes. The function looks at the buffered messages and do callbacks once decoded. Typically, that would happen on touch events for example.

@ScruffR, serialEvent1() is only called at the end of loop() is it not? I prefer to control the processing of serial data in loop().

(at the end or before the subsequent call of loop() - once the first iteration of loop() has passed there is no distinction between the two anymore)

Yes it is, and having the actual action happen in serialEvent1() or in a "block executed"/"function called" from loop() (i.e. as first or last call in loop()) doesn't look that much different to me.
Granted, you can't have the call in the "middle" of loop(), but again what is the "middle" when you split loop() exactly there and swap top and bottom, the middle gets shifted to the begin/end and there we are again.
One thing - I'd need to check tho' but may be suggested between the lines of the docs - is that with serialEvent1() you may actually not need to check for Serial1.available() due to this

For that I'd understand the contrapositive statement to be: "Whenever there is no data (aka
!Serial1.available()) the function won't be called", and hence a check for Serial1.available() can be omitted.

I’m generally opposed to threads, however the “threads cannot be stopped” isn’t necessarily a disqualification. What you do is leave the thread running, but block it with a mutex so it doesn’t use any processing time. For example, you could process your unsolicited frames in their own thread, with a queue of unsolicited frames that are handled by the thread, one-by-one. The serial reader could queue these up as it encounters the frames.

There’s sort of an example of this in my asynctcpclient - there’s a thread to do async connects that sits idle most of the time that’s fed by a queue and guarded by a mutex.

That being said, there are thorny issues with thread safety and deadlock that you then need to worry about, so I’m not necessarily recommending it, just pointing out that it’s not necessarily impossible.

2 Likes

I would simply call your serial protocol
handler function in loop(), returning immediately when there are no bytes available, otherwise while loop gathering chars until no more left, then return. Set up a callback that is called within the protocol handler when you have a message.

If you need to call this routine every 100 ms for whatever reason, set up a timer to set a gobal flag that is checked in loop(). If set, clear it, then call your routine.

This will work no worries.

I tried to use SerialEvent() in the past, but found that it did not meet expectations.

1 Like

Thank you for your responses. You have given me a lot to think about.

Finding out that timers are processed sequentially gave me a new perspective on how Particle timers should be used, and why running the Serial1 input handler in the “one and only” timer thread is not a design I want to stay with.

I do allow the circular response frame buffer to overwrite unread data. Losing data is not a concern with the device I am currently interfacing with, but may be an issue with some future device. I’m trying to develop a handler that I can quickly adapt to manage any serial device.

Using the FSM approach and using a thread with a mutex are ideas that I MUST explore. I’m also curious about serialEvent1(). I have seen it in the docs, but never taken a serious look at it.

Simply calling the Serial1 input handler from loop() also has merit, but I would still have to figure out how to prevent event handlers from introducing too much delay. Some of the event handlers may be designed to take corrective action, so I need to avoid linear code. This takes me back to a one-shot timer, FSM , a thread with mutex … or ???

I’m puzzled by the overall lack of enthusiasm for threading. Threading is one of the design pillars in the prototyping world that I come from. I totally understand the upside of threading, and I know what to avoid on PC and larger platforms. I’d really like to know more about what to avoid in the IOT world, and why … if anyone can provide or point me to that information.

Thanks again for sharing your perspective on this topic.

MT does come with some overhead (extra cycles for register push/pop, extra RAM demand due to seperated stacks, ...) that is best served on powerful, full blown CPUs preferably multi-core with plenty of RAM, cache, dedicated data/instruction pipelines, ... , but here we are dealing with rather limited single core micros.

2 Likes

I should point out that I’m not opposed to threads as a programming paradigm; I also program in Java and use a lot of threads. It’s just not well-suited on micro controllers for the reasons ScruffR mentioned. Plus, APIs in Java, Windows, etc. where threads are common are MT-safe now. Most of the Wiring APIs are not, and debugging thread-induced bugs is not fun at all.

2 Likes

I drew the following Visio to help me think about using the FSM (Finite State Machine) paradigm to manage devices. If we think of a user with a remote control to control a radio, and we think of the radio as two devices … a tuner, and an amplifier, we can see where clicking “volume-up”, “volume-down”, and “mute” would impact the desired state for one device, while clicking “tuner-search”, “station-preset 1”, or “AM/FM1/FM2”, would impact the desired state of the other device.

With all three FSMs running on the same Photon, and all three FSM’s getting a share of the loop() thread, the application FSM can merely update global “desired state” variables. There is no need to call the device FSMs because they can read the “desired state” variables and initiate their own action when they get their turn to execute.

Likewise, the device FSMs can maintain “device status” information in global memory so the “application FSM” has access to that information when it executes. Events are even more interesting. Let’s say device 1 informs the device1 FSM that it has restarted. The FSM has all of the information it needs to reconfigure the device to the desired state. Some “device events” would need to go back to the application … like if a device reported a low-battery, or a fading-signal. Again, that this information could be exchanged in shared/global memory.

This is really an interesting concept because an FSM could run on a remote device simply by creating a way to exchange the shared variables. That concept and my noob understanding of mesh computing seem to jive very nicely. Hmmm …