I’ve been meaning to do this for a while, and a short gap between projects gave me some time to try.
The Microbit is (yet another) educational SBC, sitting somewhere between the Codebug and a Raspberry Pi. Its processor has plenty enough flash memory and RAM to run a basic tracker (but more on that later), plus it has accelerometer and compass chips.
Importantly, the Microbit has SPI and I2C busses plus a serial port, all brought out to the edge connector on the bottom. Rather than solder directly to the pads, I bought an edge connector and teeny prototyping board:
I also bought a battery holder with cable and plug to suit the micro JST connector on the Microbit.
Balloon Tracker Hardware
To make a balloon tracker, we also need to connect a suitable GPS (by which I mean, one that still sends positions when at high altitudes) and an ISM band radio transmitter. I chose a UBlox module from Uputronics:
Usefully, this design includes an I2C port as well as the usual serial port. Since the Microbit serial port is normally used by the debug connection to a PC, software development becomes more difficult if we use that serial port for the GPS, so I2C makes life much much easier.
Now for the radio. The most popular HAB option is the NTX2B radio transmitter, but that also needs a serial port, so instead I opted for a LoRa transceiver from Uputronics:
This has an SPI interface, so the serial port remains free for debug purposes.
The first job was to get the devices wired together. There’s not much space on this prototyping board, and it can be useful to keep the GPS away from the other devices anyway (less interference), so I put the GPS and radio on wire tails:
There are several options for writing code for the Microbit, and I opted for MicroPython as I’ve been writing a lot pf Python lately, using the Mu editor/downloader. I started with some simple code to grab the NMEA data stream from the GPS, and this took just minutes to get going:
I then ported my Pi Python GPS NMEA parser (which meant, just changing the code to use the Microbit I2C library rather than the Pi serial port). You can see my test program here (but please don’t use that for a flight, as it was written for car use and therefore doesn’t put the GPS into flight mode!).
LoRa Radio Software
I also have LoRa Python code from another project, so after testing that the device was connected OK (a few commands typed into the Microbit REPL interpreter), I ported that over. The changes were for the SPI library, plus I had to remove all the LoRa register/value definitions as they made the program source too large; the source is compiled on the device, so the compiler has a rather limited RAM workspace. You can see the resulting test program here.
To receive LoRa transmissions, you need another LoRa device as a receiver, plus suitable software. I used my C LoRa Gateway code for the receiver:
Balloon Tracker Program
So far so easy, and the end goal seemed close; once you have GPS and radio modules working, then you just need a small amount of extra code to format the GPS data as a string, adding a prefix (“$$” and the payload ID) and suffix (“*” then CRC then a line-feed), and then transmit the result over radio.
However, as soon as I combined the GPS and LoRa code, the result wouldn’t even compile. Remember that compilation happens on the Microbit, and my code was too large for that process:
Fortunately it wasn’t much too larger, so I removed some code that wasn’t strictly necessary (mainly, the code that switches off unused GPS NMEA sentences) and soon the compiler was happy.
The resulting code however was not happy. Once the compiler has finished, the resulting bytecode is loaded into the Microbit’s RAM, which is shares with any data used by the program (variables, stack, temporary work areas). The nature of Python is that memory gets allocated all the time, and freed up when necessary (i.e. when there’s little free memory available), and my program would run for a short while before crashing with an “out of memory” error when it tried to allocate more memory than was available. This it working before it crashed:
So, I had to reduce the memory footprint. I’m used to doing that in C on microcontrollers, but MicroPython needs different techniques. For example, C on a micro usually sits in flash memory, which often is less of a limit than is the working data in RAM, so you can sometimes rewrite the code to use less RAM without worrying that the new code uses more code memory. Not so for MicroPython, where everything shares RAM. So some things I tried actually made the situation (checked by calling gc.free_ram() in the main loop) worse. So, for the most part, I managed to increase free RAM by removing code that I didn’t need. Having done so, the program was stable though free memory went up and down cyclically as memory was allocated each loop and then eventually freed up.
Some easy improvements came from removing the code to display GPS satellite count on the LEDs, and specifically importing only the required modules instead of the whole Microbit library. The most relevant part of the code turned out to be the part that builds up an NMEA sentence. In C you simply allocate enough memory for the longest sentence you need to parse, then place incoming bytes into that memory using a pointer or index, checking of course for buffer overruns. In Python, strings are immutable so you can’t do this, and the temptation then is to do “string = string + new_character”. Of course, the Python interpreter then allocates memory for the resulting string, marking the old string as “no longer in use” so it can be freed up sometime later. It’s pretty easy to end up with lots of unused memory waiting to be freed. For now, my NMEA code explicitly frees up memory as each new byte comes in. I did briefly change the code to using bytearrays, which are close to what I would do in C, but free memory reduced slightly (I assume the source took more space) so I went back to the original code. Longer term, I’ll ditch NMEA and write code to use the UBX binary protocol instead.
The code has been running continuously now for over 12 hours, and the free-memory figure is solid (measured at the same point each time round the main loop). I do need to add the flight-mode code, but that’s small and shouldn’t cause an issue :-). If all is well then I hope to fly this (weather-permitting of course) on Sunday.
Finally, here’s the result of receiving the telemetry on a Python LoRa gateway program that I’ve been working on lately: