Maximising cellular data streaming rates on Boron

I have an application that requires upload data streaming at approx. 10-20kbps (kilobits rather than kilobytes). I had mostly achieved this successfully on the Electron, although far from optimised, and have achieved similar performance on the Boron 2G/3G but not on the Boron LTE.

I find that, in situations where it cannot handle the data rate, after a few seconds of sending the modem locks up for 30+ seconds.

I am not particularly fussed about latency (<5 seconds would be great) or whether the data is delivered via UDP or TCP. The aim is to get all of the data being generated locally smoothly to the cloud. The data source is continuous (and arriving via serial - a lot of work was done to handle that connection without loss) so there aren’t really any pauses to take advantage of.

There have been some comments in topics 2+ years ago on a few tips and tricks but no definitive answer and nothing yet that takes into account Boron LTE performance.

Is what I am attempting to do possible? If so, what would be the best way to go about it, either using UDP or TCPClient?

1 Like

Boron LTE is not the high performance LTE you’d know from your mobile phone but rather the opposite - it uses LTE Cat M1/NB1 which are optimised for M2M applications which typically don’t need high performance but high reliability, high penetration and low power.

You can have a quick overview here

Sure, although as one of those links says:

…and provides average upload speeds between 200kpbs and 400 kpbs

I’m hoping that I’m not asking for the world with 20kbps.

strange that one link states 1Mbit up/down peak for LTE Cat M1 and the other states, as mentioned, 200-400kbps average up/down. i guess the peak is never seen much.

Also don’t forget the NB1 part of it. Narrow band reduce the bandwith some more and there you also have the multi/single tone distinction which reduces it even more (NB1 single tone 20kbps uplink peak ;-))

i think i’m not fully up to speed on the module because i thought LTE Cat M1 was a separate protocol from the NB1 and the module was capable of both but that each was fairly distinct from the other. i vaguely remember reading that NB1 is not available right now so only Cat M1 is in play. but, ScruffR, i get what you are saying about NB1 itself.


There are a few things:

I’m pretty sure TCP doesn’t work right on the Boron on 0.8.0-rc.26. In my test it would only send the first buffer of data then stall.

However, I ran the same code on an E Series LTE (running 0.8.0-rc.11, same SARA-R410M-02-B modem). I get a consistent 1792 bytes/sec. So 10 Kbits/sec would appear to be possible.

0000016410 [app] INFO: sent 8960 bytes in 5 sec (1792 bytes/sec), 0 % complete
0000021416 [app] INFO: sent 17920 bytes in 10 sec (1792 bytes/sec), 1 % complete
0000026422 [app] INFO: sent 26880 bytes in 15 sec (1792 bytes/sec), 2 % complete
0000031428 [app] INFO: sent 35840 bytes in 20 sec (1792 bytes/sec), 3 % complete
0000036434 [app] INFO: sent 44800 bytes in 25 sec (1792 bytes/sec), 4 % complete
0000041440 [app] INFO: sent 53760 bytes in 30 sec (1792 bytes/sec), 5 % complete

The Nordic nRF52840 MCU on the Boron is a little slower than the STM32F205 on the E Series, but I think you’ll be bound by the network speed, not CPU speed.

Here’s the code:

#include "Particle.h"

SerialLogHandler logHandler;


uint16_t START_PIN = D2;

enum {

const IPAddress remoteHost = IPAddress(65, 19, 178, 42);
const uint16_t remotePort = 7123;
const size_t transmissionSize = 1024 * 1024; // must be a multiple of writeSize
const size_t writeSize = 256;
const unsigned long reportTimePeriodMs = 5000;

int state = WAIT_STATE;
unsigned long stateTime = 0;
unsigned long reportTime = 0;
uint8_t buffer[writeSize];
size_t transmissionOffset = 0;

TCPClient client;

void reportProgress();

void setup() {

	char c = 'a';
	for(size_t ii = 0; ii < writeSize; ii++) {
		buffer[ii] = c++;
		if (c > 'z') {
			c = 'a';

void loop() {
	if (digitalRead(START_PIN) == 0) {
		if (state == WAIT_STATE && Network.ready()) {
			state = START_STATE;

	switch(state) {

		if (client.connect(remoteHost, remotePort)) {"Connected to %s:%d!", remoteHost.toString().c_str(), remotePort);
			state = SEND_STATE;

			stateTime = reportTime = millis();
		else {"failed to connect to %s:%d", remoteHost.toString().c_str(), remotePort);
			state = WAIT_STATE;

		// Prepare a buffer
		snprintf((char *)buffer, writeSize, "|%08x", transmissionOffset);

		// Send it
		//"writing %u", writeSize);
			int res = client.write(buffer, writeSize);
			if (res > 0) {
				//"write complete %u res=%d", writeSize, res);

				transmissionOffset += writeSize;
				if (transmissionOffset >= transmissionSize) {
					state = WAIT_STATE;
			else {"write failure transmissionOffset=%u res=%d", transmissionOffset, res);
				state = WAIT_STATE;
		if (millis() - reportTime >= reportTimePeriodMs) {
			reportTime = millis();



void reportProgress() {
	int pctComplete = transmissionOffset * 100 / transmissionSize;

	size_t elapsedSec = (millis() - stateTime) / 1000;"sent %u bytes in %u sec (%u bytes/sec), %d %% complete",
			transmissionOffset, elapsedSec, transmissionOffset / elapsedSec, pctComplete);


As mentioned by @dkryder confirming that we are talking about Cat M1 speeds and not NB-IoT.

@rickkas7 thank you for those insights! Really promising that you could get those speeds out of the same modem. I will need to use the Boron LTE and don’t have access to the E Series LTE as I am outside of the US and using an external sim.

I believe I had the Boron 2G/3G working using TCPClient however I wasn’t using SYSTEM_THREAD(ENABLED). I will see what kind of performance I get with your code with and without that flag on the different Boron types when I can test in a day or two.

I will post here when I have an update.

1 Like

@rickkas7 I just did some preliminary testing and was able to replicate some of your results. One thing of note is that for the Boron LTE, the tcp client does behave quite differently.

Some of the lower level logs:

0000044284 [app] INFO: Connected to [redacted]!
0000044285 [app] INFO: writing 256
0000044286 [app] INFO: write complete 256 res=256
0000044287 [app] INFO: writing 256
0000044288 [app] INFO: write complete 256 res=256
0000044289 [app] INFO: writing 256
0000044289 [app] INFO: write complete 256 res=256
0000044290 [app] INFO: writing 256
0000044291 [app] INFO: write complete 256 res=256
0000044292 [app] INFO: writing 256
0000044292 [app] INFO: write complete 256 res=256
0000044293 [app] INFO: writing 256
0000044294 [app] INFO: write complete 256 res=256
0000044295 [app] INFO: writing 256
0000044298 [app] INFO: write complete 256 res=256
0000044299 [app] INFO: writing 256
0000044299 [app] INFO: write complete 256 res=256
0000044300 [app] INFO: writing 256
0000044301 [app] INFO: write complete 256 res=256
0000044301 [app] INFO: writing 256
0000044302 [app] INFO: write complete 256 res=256
0000044303 [app] INFO: writing 256
0000044304 [app] INFO: write complete 256 res=256
0000044305 [app] INFO: writing 256
0000044693 [app] INFO: write complete 256 res=256
0000044694 [app] INFO: writing 256

Unlike on the other modules, judging by the speed of the serial output and the loop numbers client.write() intially seems to return immediately without blocking and as such it appears the buffer gets rapidly flooded with over 3KB of messages until the program hangs completely.

Adding an arbitrary waiting period in the loop of 300-350ms after each client.write() resolves this issue, which helps me achieve speeds topping out at 870 bytes per second for an extended duration. However, occasionally the modem still seizes up after a period of time.

0000054244 [app] INFO: write complete 256 res=256
0000054245 [app] INFO: writing 256
0000054596 [app] INFO: write complete 256 res=256
0000054597 [app] INFO: writing 256
0000054948 [app] INFO: write complete 256 res=256
0000054949 [app] INFO: writing 256
0000055300 [app] INFO: write complete 256 res=256
0000055301 [app] INFO: writing 256
0000055652 [app] INFO: write complete 256 res=256
0000055653 [app] INFO: writing 256
0000056004 [app] INFO: write complete 256 res=256
0000056005 [app] INFO: writing 256
0000056356 [app] INFO: write complete 256 res=256
0000056357 [app] INFO: writing 256
0000056708 [app] INFO: write complete 256 res=256
0000056709 [app] INFO: sent 19200 bytes in 26 sec (738 bytes/sec), 1 % complete

It seems to be a very fine balance, and unfortunately this kind of rate is slightly too slow for me.

I will look to try UDP and see what difference it makes, but is there any other optimisations I could make with packet sizes, buffers or other settings that could make a noticeable difference? I am ideally looking for closer double these speeds to be really comfortable.

I figured a delay would help on the Boron but really the underlying problem is a bug. I’ll take a closer look when I’m back at the office on Wednesday.


Hello. Is there any updates to this thread?


I’d also like to know If there is any developement with this issue. I am able to send 100Kb images at a consistent rate of about 2.5 kbytes/s. That is with packets size of 512 bytes and 200 ms delay. My application is for an autonomous solar camera so maximizing speed would mean maximizing power usage.


Well I meant minimizing power usage of course.

That’s about the maximum data transfer speed on the Boron. There’s no trick to make it go any faster at this time, unfortunately.

Oh I see, Thank you for your fast reply. Maybe a Boron 3G would be better suited for this kind of application, I might try it.

The data transfer speed is approximately the same on all cellular devices.

If I may ask, does this limitation comes from the U-blox SARA Series chip?

There are a number of reasons. There are bottlenecks in the serial interface to the cellular modem, in how the data is processed coming out of the modem and dispatched to user code. The LTE Cat M1 modem itself does limit the speed as well.

Since the cellular devices are not intended to be used in high data rate applications, it’s unlikely that this will be optimized in the near future.

I see, at least there is some room for optimizations. Thanks for the info.

Sounds interesting.

Is there any thing you can share about your project that sends camera images to the web over cellular?