Bleeding edge: "multithreading" on the Spark Core

@satishgn has built a new feature for the Spark Core that allows the Wi-Fi connectivity stuff to run in parallel to the user application, so that your code is not blocked when the Core isn’t connected to the internet.

This has a few important wonderful implications:

  • Your code will run without a Wi-Fi connection
  • Your code won’t stop when the network connection dies

But it’s not without its downsides:

  • This “multithreading” is accomplished by switching back and forth between the user application and the Spark background tasks at a microsecond level. This may affect high speed timing-sensitive code (I’m looking at you @BDub)
  • This takes WAY more RAM than our firmware did previously, leaving less available RAM for your application.

This feature isn’t yet ready for primetime; it hasn’t been tested sufficiently, and we need to optimize memory use to provide more RAM for the user application. But if you want to live on the bleeding edge, and are building code locally, try it out!

To enable multithreading, check out the context-switching branches of all three firmware repositories. Basically, once you’ve got your local core-firmware toolchain, do this:

cd core-firmware
git checkout context-switching
cd ../core-communication-lib
git checkout context-switching
cd ../core-common-lib
git checkout context-switching
cd ../core-firmware/build
make
dfu-util -d 1d50:607f -a 0 -s 0x08005000:leave -D core-firmware.bin

Feedback and pull requests welcome!

:spark:

9 Likes

This is very encouraging!

Nice work

:eyes: hides

Haha!

Will this be a “feature” that can be enabled and disabled at compile time?

Can this be disabled temporarily at run time without much problem to the Wifi connection (minus maybe some dropped packets)?

Does this help to create a micros() function as well?

Is there a planned time slot that each thread will run in, or will the background tasks just be interrupting a lot?

1 Like

Making this a ‘feature’ that can be activated and deactivated is definitely something that we’re discussing.

Your other questions I am not in the weeds enough to answer. @satishgn, can you share a bit more information for how context switching works in this implementation? Great opportunity to get a little feedback, if there are implementation details still to be decided.

Brief explanation on working of Spark’s context-switching code:

Instead of the earlier single main loop, we now have 2 independent loops managed by 2 tasks:
1)Spark_Process_Task: This does the job of initializing the WLAN, connecting to cloud server and executing the spark event loop.
2)Wiring_Process_Task: This does all Arduino/Wiring related stuff i.e. setup() and loop() as well as executing the User functions that has been registered using Spark.function…

Both the above 2 task is managed by PSP(Process stack pointer) with independent stack setup for each. For Task1, stack size is 0x800 bytes and for Task2, it is 0x500 bytes. This has been carefully selected. Please do not modify this values.

All the Interrupt handlers execute within MSP(Main stack pointer) context.

The first switch to Task1 occurs within SVC_Handler then the PendSV bit is set i.e schedule context-switch within Systick_Handler which is now configured for 100us ticks instead of older 1ms.

Both the PSP based tasks switches alternately getting 100us of time-slice each and this switching occurs within PendSV_Handler() which is in assembly code.

Most of the RAM is utilized by heap since the communication’s handshake, encryption/decryption uses a lot of heap space via the malloc/free calls. Highly recommended not to use stdlib’s atol(), atof() calls. A local implementation for this has been provided within String class.

2 Likes

I do like the idea of “multithreaded” loops and I’m looking forward to have a test with it once it runs stable.

But depending on the final RAM impact of this approach - and other side effects like breaking atol(), atof() and who knows what else - I’d like to suggest not to drop the current approach completely but parallel to the multithreading still think about a way to temporarily disable the cloud fn (as suggested here https://community.spark.io/t/spark-core-execution-speed/884/2) in the current version - especially since temp deactivating cloud fn still is on the wish list even with MT (see BDub’s earlier post here) - this might temporarily provide even more RAM for user task, since en/decryption does not require any heap space, till actively demanded, which can be controlled and cared for in user task.

If you then provide an option in Sparkulator to opt for the more RAM/ol’ school version or for the more comortable/MT version, we are all good :wink:

At this point I think you guys should look into something like FreeRTOS to better handle the multitasking. :-/

1 Like

I’m all for multithreading, but for what i’d like to use my spark cores for, I would like a bit of both. I have to say that I was a bit disappointed that the core has to stay connected to the cloud. For me, I want a core acting as a sensor that takes readings every hour or something which would push values out and wouldn’t want to be connected all the time. On the other hand I also want a core to monitor something continuously and for me to be able to access through the internet when I want.

Could we handle the connection and disconnection to the cloud (which will turn off/disable the wifi when not needed)? Maybe something like Cloud.Activate(); etc?

Sorry if this is slightly leading away from what is been said, but I think a non-blocking scheme that can be activated (at compile time or other methods) would be good. I also think that more user control in general would also be of great benefit.

3 Likes

We did - the memory footprint for FreeRTOS was too much for our micro-controller when added to the memory requirements for our encryption.

Hi Phil12d3 - sounds like what you’re looking for is Spark.sleep()

http://docs.spark.io/#/firmware/functions-sleep

@zach thanks, yes that would do it.

It would still be good to be able to control the connection to the cloud though.

I agree with @Phil12d3, but in my case to have full µC speed for the user loop, which would still not be achieved with the MT approach - better than at the moment, but still only appox 50%.

I was thinking about this earlier and although Spark.sleep() would do what I want for the specific task I mentioned, I can also think of scenarios when i’d want to disconnect from the cloud and still run code (that may include a small sleep, but i’d still want to run code rather than just turn wi-fi off and wait).

Out of interest, does anyone know what the power consumption is with wi-fi on and off?

I’m curious how you’ve come to the conclusion that FreeRTOS uses too much resources for this microcontroller. The flash space requirement is typically around 4K and ram overhead is minimal, except for allocation to the task stacks. FreeRTOS on smaller micro’s is common.

If time is shared between both tasks and there is nothing to do in the WLAN task, what happens? It sounds like there is no scheduler and no synchronization primitives… so the user task is always limited to < 50% of the CPU time - even when there is nothing to do in the WLAN task. Context switching every 100usec (10 kHz !!) is wasteful, what drives the decision to run context switches that fast?

Sorry to be the negative guy at the party but this approach doesn’t make sense to me. Sounds like more work needs to be done in getting the memory and program space utilization into line before rolling your own tasking instead of using something off the shelf.

2 Likes

Totally agree that FreeRTOS is not resource hungry but the SSL libraries that we use does need more Heap. We tried porting at first - it works great till we reach the Handshake part with the Cloud where it runs out of memory(tried all the heap_x MemMang provided by RTOS). So if we replace the current encryption/decryption scenario with some lightweight approach, FreeRTOS would have been a good off the shelf choice given the 20KB RAM onchip. We initially tried with 1ms context-switching but handshake didn’t work till it was dropped to 100us. You are not at all negative but inputs/recommendations from you and the community always helps.

1 Like

Do we really need multithreading? Others are experiencing problems because of delays in loop(), but its easy enough to write code that doesn’t depend on delay().

That’s a good point, there are probably lots of ways we could let people avoid that issue. Even just something like subtracting away how long loop takes to run from the cloud check, or making the connection keepalive check configurable might be good enough for most.

Exactly what I would have thought to be the quickest and easiest tweak in the standard FW.

User callable function to deactivate/reactivate the connection and keepalive while still running user loop (meaning #undef SPARK_WLAN_ENABLE or Spark.sleep() will not do - neither the route to hack the FW wit local toolchain :wink: - been there, got the T-shirt)

Here’s an odd but cool thought… how about hijacking the delay() function and processing the b/g wifi tasks at a timed interval for the duration of the delay?

Sure it’s easy enough to write code that doesn’t depend on delay(), but if you want to create a timing loop that fires off precisely every 1ms, you can’t do it with the current setup because loop() is blocked for 5-6ms every time through. Even creating a real time exec that fires every 10ms will result in times of 10, 11 and 12ms due to the randomness of the 5-6ms blocking.

2 Likes

Really interesting idea, definitely something to consider…