Using STM32 USB with Arduino, FreeRTOS and Platformio

FreeRtos real time operating system can be used in conjunction with the Arduino STM32 framework by importing the Stm32duino FreeRtos library, namely if using PlatformIO, by defining the dependency on the platformio.ini file at the project root:

lib_deps =  stm32duino/STM32duino FreeRTOS

From this point on it is possible to use the FreeRTOS capabilities, while being able to leverage the large availability of other Arduino libraries.

Nevertheless while testing some of the examples of STM32duino FreeRTOS, such as the blink example, it just didn’t work. The board after starting just hanged and nothing happened: No blinking led…

One key aspect, and the root for this issue, is that the example uses the Serial.begin() function to serial output some messages to the user, but specifically, in my case, since I’m using platformio, I’ve enabled on the platform.ini file the serial output to be redirected to USB instead of standard serial port:

build_flags =   -D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC
                -D USBCON
                -D USBD_VID=0x0483
                -D USBD_PID=0x5740
                -D USB_MANUFACTURER="PrimalCortex"
                -D USB_PRODUCT="\"DemoBoard\""

These entries are require to enable STM32 USB output from the ARduino framework Serial commands.

Starting a debug session, to see why or where the code is stuck, we get that the code is stopped in the following assert:

Checking the stack trace, we have:

Ok, so it seems that malloc function is getting called from an ISR, specifically the USBD_CDC_Init function:

And that’s it seems to trigger the ASSERT and hence the failure.

But if we remove, from the platform.ini file, the USB related configuration options that enables USB, namely the -D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC, then everything just works fine.

So how we can have Arduino, FreeRTOS and still use USB on STM32 when using Platformio?

The solution is quite simple, and is a bit hidden in the internet since it seems not many people are using this combination of STM32, Arduino, FreeRTOS and USB. We only need to change the Heap memory manager to be used by the code by defining FreeRTOS to use the heap_3 management policy/configuration:

build_flags =   -D PIO_FRAMEWORK_ARDUINO_ENABLE_CDC
                -D USBCON
                -D configMEMMANG_HEAP_NB=3
                -D USBD_VID=0x0483
                -D USBD_PID=0x5740

With this change, now everything works as supposed.

Edit: What this change on the Heap Management means is that FreeRTOS will use now the standard C memory management functions, and not the specific ones that bring the issue, and hence it works as expected. Anyway, for some use cases, the selection of this heap management might not be ideal, but it is a tradeoff to be able to used the USB port as an output and input serial console.

Happy coding.

Building an OpenThread Border Router with the ESP32- Part II

One of my mistakes regarding the previous post building an ESP32 based OpenThread Border Router was not tracking which commit version from the esp-idf repository I was using along on my tests. As I moved along in testing the border router and finding that some functionality was not working as expected, I’ve rebuild the ot_br esp-idf example by updating the esp-idf to the latest commit. This in fact turned into a disaster since the latest commits are not stable for OpenThread use…

So, without further ado, let’s see how we can, more or less build a stable ESP32 OTBR.

The NRF52840 RCP

After searching some of the issues on the esp-idf repository, it seems that EspressIf is using the ot-nrf528xx repository but NOT on the head commit but on a specific commit: 8c508c8b693ce660134e934c967835cb43ffcc31

This was more or less inferred from this esp-idf github issue, so is not from any official documentation… Also in the same post a specific esp-id commit is also referred…

So to build the NRF52840 dongle based OpenThread radio, we need to do the following:

# git clone https://github.com/openthread/ot-nrf528xx.git
# cd ot-nrf528xx
# git checkout 8c508c8b693ce660134e934c967835cb43ffcc3
# git submodule update --init

And from this point we can follow compile the RCP for the NRF52840 which will compile to the RCP API version 5.

We should now edit the transport-config.h file on the nrf52840 directory to define the necessary UART parameters (pins and flow control) to match what the ESP32 OTBR is expecting:

# cd src/nrf52840
# vi transport-config.h               <- Use your preferred editor....

The UART configuration looks like (changes in bold):

# git diff transport-config.h
+++ b/src/nrf52840/transport-config.h
@@ -74,7 +74,7 @@
  *
  */
 #ifndef UART_HWFC_ENABLED
-#define UART_HWFC_ENABLED 1
+#define UART_HWFC_ENABLED 0
 #endif
 
 /**
@@ -142,7 +142,7 @@
  *
  */
 #ifndef UART_PIN_TX
-#define UART_PIN_TX 6
+#define UART_PIN_TX 20
 #endif
 
 /**
@@ -152,7 +152,7 @@
  *
  */
 #ifndef UART_PIN_RX
-#define UART_PIN_RX 8
+#define UART_PIN_RX 24
 #endif
 
 /**
@@ -162,7 +162,7 @@
  *
  */
 #ifndef UART_PIN_CTS
-#define UART_PIN_CTS 7
+#define UART_PIN_CTS 15
 #endif
 
 /**
@@ -172,7 +172,7 @@
  *
  */
 #ifndef UART_PIN_RTS
-#define UART_PIN_RTS 5
+#define UART_PIN_RTS 13
 #endif

Basically Hardware flow control was disabled, the TX and RX pins where changed to a set of pins on the dongle that definitely work, and I’ve moved out also the CTS and RTS pins.

To compile, we go back to the repository root and execute:

# script/build nrf52840 UART_trans -DOT_BOOTLOADER=USB -DOT_THREAD_VERSION=1.2
# arm-none-eabi-objcopy -O ihex build/bin/ot-rcp build/bin/ot-rcp.hex
# nrfutil-linux pkg generate --hw-version 52 --sd-req=0x00 --application build/bin/ot-rcp.hex --application-version 1 build/bin/ot-rcp.zip

Put the dongle in DFU mode (press the lateral reset button) and flash it.

# nrfutil-linux dfu usb-serial -pkg build/bin/ot-rcp.zip -p /dev/ttyACM0

Note that I’m using the nrfutil-linux utility version because the nrfutil version on Arch Linux is broken. If using other Linux versions, like Ubuntu (not tested) nrfutil might work just fine.

And that’s it, the NRF52840 dongle with RCP version 5 is ready and uses the same pins as the previous post.

The ESP32 Border Router

While the RCP itself didn’t gave much trouble by using the Nordic Connect SDK version of it, the Border Router did have some issues.

The major issue was regarding to the Thread Network routing. The issue was that the OMR (off-mesh routing prefix) was not advertised through IPv6 Router Advertisement (RAs) ICMPv6 packets. In fact I had two situations:

  • In specific esp-idf commits, the initial RAs was sent, but only that one. This meant that some host computer did know the OTBR presence on the network when it booted up, but since the OTBR didn’t refreshed the route (it didn’t send any periodic RAs), the hosts, after sometime, removed the expired routes. This also meant if the hosts rebooted after the router has booted, the OTBR and the thread network would be unknown to them.
  • In the actual esp-idf commit ( c2ccc383dae2a47c2c2dc8c7ad78175a3fd11361 – late June 2022) no RAs are sent at all… So no way, without static routing configuration to access the thread network.
  • A serious of issues where solved on the esp-idf commit id https://github.com/espressif/esp-idf/commit/495d35949d50033ebcb89def98f107aa267388c0 which solved the issue with the no Router Advertisements and an overflow timer, so this commits works better than the previous one which after an hour stop advertising the mesh prefix and routes.

The above issues do not happen at least at specific commit recommended by an Espressif employee.

So, to compile the ot_br example to a more or less stable version of it, the following steps must be done (NOTE: Any previous espressif installation needs to be removed! including the .espressif folder where tools are located.)

# echo "Do this if you know what you are doing!"
# rm -rf ~/.espressif
# rm -rf esp-idf
# git clone https://github.com/espressif/esp-idf.git
# cd esp-idf
# git checkout daa950d9ed1cc3cdc85c09b14ddfeb68a2ac6674
# git submodule update --init --recursive
# ./install.sh
# . ./export.sh

We can now compile and flash the ot_br example:

# cd examples/openthread/ot_br
# idf.py build
# idf.py -p /dev/ttyUSB0 flash monitor

After flashing and at the border router prompt we can start the border router, if not configured to start automatically:

> wifi connect -s myAPssid -p myAPpass
> ifconfig up
> thread start

And all should be fine now, at least for basic IPv6 connectivity between hosts and the thread nodes.

We can check the local linux routing table with the route -6n command, and also use ping to check basic connectivity.

Running wireshark on a host, we can see now the periodic IPv6 Router Advertisements and can ping directly any thread node, send coap messages and so on.

Wireshark capture of ESP32 BR Router Advertisements

Some final thoughts

The ESP32 based Border router does indeed provide the basic functionality without incurring on the more expensive and complex based border routers using the RPi, for example. No OS to cater for, no SD cards, log files and so on, so a more simple approach to get Network <-> Thread connectivity.

Still I have some issues with this commit, for example, if the WIFI disconnects and then connects again the Border Router does not recover (regarding the IPv6 network management). It still says it is connect but meanwhile the BR stops any periodic router advertisements.

Also I still haven’t tested any external device commissioning using for example the commissioner-cli tool or the Android Thread App, but the Thread App finds the ESP32 border router without any problem.

And so, that’s it.

Building an OpenThread Border Router with the ESP32 and the NRF52840 dongle

Openthread is a 802.15.4 radio mesh network that uses IPv6 as the network protocol to communicate between other nodes but also to communicate with the outside world. The Border Router (BR) is the network component that allows OpenThread nodes to access the external networks and vice-versa. And yes, due to the native use of IPv6 we can access our node directly from across the world (if we wish so) directly!. So a Border Router in a OpenThread network is much like a standard router that routes packets between the Openthread radio network to our network or the internet. No application layer conversion whatsoever, just standard IPv6 networking.

A common way to build an OpenThread Border Router is to use a Raspberry Pi (3 or 4) and a 802.15.4 radio to connect to the OpenThread network. A common device used for this is the Nordic NRF52840 chip that is capable of BLE and 802.15.4. The NRF5240 PCA10059 dongle due to its small dimensions (unlike the NRF52840 development kit) is a common device used as the radio componente in building the Border Router.

But the RPI/Dongle combo, while easier to build specifically with docker support , has some drawbacks, namely first to be able to buy a RPI now (…), SD card life time (if not using eEMC disks or external disks), power consumption, among others. So it was with interest that on the Espressif announcement of the ESP32-H2 the Thread protocol, part of the OpenThread project is supported. More so, on the the OpenThread official site an ESP32 based Border Router was documented!

So the question was, could an ESP32 module work with an NRF52840 dongle, instead of the ESP32-H2 (that as far that I know are not yet available), work as the radio for building a Border Router? The short answer is yes! It does indeed work, bridging the OpenThread network through the ESP32 Wifi interface to our network and to the internet.

So the following steps explain how to do it (Notice: Further info on also this post: Building an OpenThread Border Router with the ESP32- Part II):

OpenThread coprocessor

The OpenThread coprocessor is the software that runs on the radio enabled chip that connects to the OpenThread network.

There are two types of coprocessors, the NCP: Network Co-Processor, and the RCP: Radio Co-Processor. The difference is explained on the Openthread site with great detail, but the key point is that the ESP32 will only work with an RCP, not an NCP. So we need to program the NRF52840 dongle with a RCP firmware.

Second, RCP offers an API, and this API can be version 4, 5 and so on. At the moment, the ESP32 Border Router only allows the RCP api version 4 or 5, not 6 which is the latest.

Finally, the NRF52840 dongle when used as the RCP on the RPi based Border Router uses the USB connection as the medium for the serial communication with the RCP. With the ESP32 we want to use a standard UART so we can connect the UART pins from the dongle to the ESP32.

With this in mind, let’s see how to build and flash the RCP.

Building and flashing the RCP

There are at least three sources from where we can build and then flash the RCP on the NRF52840 dongle:

  • The Nordic ot-nrf528xx Git repository. This will build RCP (that I use with the RPi) that can use the USB or UART connection. Unfortunately I wasn’t able to make it using UART (but I didn’t investigate much), so I’ll will try in the future, but for now is in standby. (I was able to make the UART to work (it was the pins…), but the RCP API is version6, see below).
  • The Zephyr RTOS coprocessor example.
  • The Nordic RTOS coprocessor example (is the same one as the Zephyr RTOS) but on the Nordic Connect SDK.

The main difference between the Zephyr RTOS version and the Nordic one is that Zephyr, if using the latest commit, will create a RCP using RCP API version 6. Nordic on the other hand, if using version 1.9, that uses Zephyr 2.7 will create RCP that uses RCP API version 5. And this one will work with our ESP32 based border router.

Anyway, attention must be taken to which Zephyr RTOS version is being used, and in my case I ended up using Nordic Connect SDK 1.9.1 which uses Zephyr 2.7.99, because my main Zephyr repository in my PC was already on the latest commit (3.0.99), and I didn’t want to go back…

Anyway, with Zephyr is simple to compile the RCP coprocessor using the UART instead of the USB connection.

Now on the coprocessor sample location ( ???/???/zephyr/samples/net/openthread/coprocessor), we just need to create an overlay file named nrf52840dongle_nrf52840.overlay and put it on the boards directory with the following contents:

/ {
    chosen {
        zephyr,ot-uart = &uart0;
    };
};

/ {
    /*
    * In some default configurations within the nRF Connect SDK,
    * e.g. on nRF52840, the chosen zephyr,entropy node is &cryptocell.
    * This devicetree overlay ensures that default is overridden wherever it
    * is set, as this application uses the RNG node for entropy exclusively.
    */
    chosen {
        zephyr,entropy = &rng;
    };
};

As we can see all that was needed was to define the OpenThread communications UART to use a real UART port, in this case uart0. If we go to the DTS file that specifies the hardware for the dongle we can see that uart0 uses the following pins:

  • TX Pin: 20
  • RX Pin: 24

The ESP32 will use pins D4 for RX and D5 for TX, so we need to connect NRF Pin 20 to ESP32 Pin D4 e NRF Pin 24 to ESP32 pin D5. Also notice that this overlay will NOT use hardware flow control since it is not specified on the DTS and neither on our overlay file.

Now we can just compile (your Nordic Connect SDK must be correctly configured…):

west build -p always -b nrf52840dongle_nrf52840 -- -DOVERLAY_CONFIG="overlay-rcp.conf"  -DCONFIG_OPENTHREAD_THREAD_VERSION_1_2=y

And we can flash it now on the dongle. We need to put it on DFU mode by pressing the lateral reset button and waiting for the breathing red led:

# nrfutil-linux pkg generate --hw-version 52 --sd-req=0x00 \
        --application build/zephyr/zephyr.hex \
        --application-version 1 firmware.zip

# nrfutil-linux dfu usb-serial -pkg firmware.zip -p /dev/ttyACM0

Notice that I use nrfutil-linux because the “normal” nrfutil in Arch Linux doesn’t work due to Python versions.

And that’s is, the RCP is done, all that is missing is to connect it to the ESP32, and power it up.

In my test I’m using a standard off the shelf ESP32 development board, and I provide the dongle +3.3v from the ESP32 3.3v pin to the dongle Vout pin (yes, it is Vout.), which means that by powering up the ESP32 through USB I also power up the dongle.

The ESP32 Border Router

All we need now is to flash the ESP32 with the sample border Router. For that we need the latest ESP-IDF SDK to flash the code.

After everything is installed and after running the . ./export.sh script o the esp-idf root directory we can go to examples/openthread/ot_br and compile the Border Router and flash it.

# cd esp-idf/examples/openthread/ot_br
# idf.py build
# idf.py -p /dev/ttyUSB0 flash monitor.

And that’s it, we should have a working Border Router.

Checking it out

After boot we have the following output from the ESP32:

I (0) cpu_start: App cpu up.
I (621) cpu_start: Pro cpu start user code
I (621) cpu_start: cpu freq: 160000000 Hz
I (621) cpu_start: Application information:
I (626) cpu_start: Project name:     esp_ot_br
I (631) cpu_start: App version:      v5.0-dev-2959-g31b7694551-dirty
I (638) cpu_start: Compile time:     May 21 2022 18:09:20
I (644) cpu_start: ELF file SHA256:  404095e613551c1d...
I (650) cpu_start: ESP-IDF:          v5.0-dev-2959-g31b7694551-dirty
I (658) heap_init: Initializing. RAM available for dynamic allocation:
I (665) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (671) heap_init: At 3FFC0F08 len 0001F0F8 (124 KiB): DRAM
I (677) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (683) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (690) heap_init: At 40095BC8 len 0000A438 (41 KiB): IRAM
I (697) spi_flash: detected chip: generic
I (701) spi_flash: flash io: dio
I (706) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I(808) OPENTHREAD:[I] Platform------: RCP reset: RESET_POWER_ON
I(838) OPENTHREAD:[N] Platform------: RCP API Version: 5
I (958) OPENTHREAD: OpenThread attached to netif

Notice that the RCP version is 5, which means we are able to communicate with the RCP throught the UART.

We are set. So we need now to start things up, by connecting to wifi:

> wifi connect -s my_ssid -p my password
...
...
I (127298) esp_netif_handlers: sta ip: 192.168.1.153, mask: 255.255.255.0, gw: 192.168.1.1
I(127798) OPENTHREAD:[N] BorderRouter--: No valid OMR prefix found in settings, generating new one
I(127818) OPENTHREAD:[N] BorderRouter--: Local OMR prefix: fd55:91ab:2b5a:5186::/64 (generated)
I(127818) OPENTHREAD:[N] BorderRouter--: Local on-link prefix: fdad:ed43:b217:0::/64 (generated)

We have now our OMR prefix (Off mesh prefix), on another words the network address that our mesh network will be known from the outside. So we need to bring the network interface up, and start OpenThread:

> ifconfig up
I (279898) OPENTHREAD: Platform UDP bound to port 49153
Done
I (279898) OPENTHREAD: netif up
> thread start
I(284758) OPENTHREAD:[N] Mle-----------: Role disabled -> detached
Done
> I(285448) OPENTHREAD:[N] Mle-----------: Attempt to attach - attempt 1, any-partition 
I(287488) OPENTHREAD:[N] RouterTable---: Allocate router id 44
I(287488) OPENTHREAD:[N] Mle-----------: RLOC16 fffe -> b000
I(287498) OPENTHREAD:[N] Mle-----------: Role detached -> leader
I(287508) OPENTHREAD:[N] Mle-----------: Leader partition id 0x2c603881
I (287518) OPENTHREAD: Platform UDP bound to port 49154
I (290248) OPENTHREAD: Platform UDP bound to port 53535

Now, I already have my Thread network configured, so not sure if we don’t need to first configure it first or a sample network will be generated (in nodes it happens), but anyway, we all set!

Our IPs can be obtain through the ipaddr command and are:

> ipaddr
fdde:ad00:beef:0:0:ff:fe00:fc10
fd55:91ab:2b5a:5186:cf9b:9e83:e87b:b008
fdde:ad00:beef:0:0:ff:fe00:fc00
fdde:ad00:beef:0:0:ff:fe00:b000
fdde:ad00:beef:0:fff5:e276:90b2:62a2
fe80:0:0:0:34c0:14ad:cd5:1e7a
Done
> 

And we can see our OMR prefix based address, which means that if we ran the route -6 command on our Linux machine (or other Linux machines/Windows):

[pcortex@pcortex:coprocessor|main]$ route -6
Kernel IPv6 routing table
Destination                    Next Hop                   Flag Met Ref  Use If
pcortex/128                    [::]                       U    256 1      0 lo
fd04:fdeb:3df3::/64            [::]                       U    100 1      0 enp4s0
fd55:91ab:2b5a:5186::/64       fe80::2662:abff:fedc:b224  UG   100 1      0 enp4s0
fdad:ed43:b217::/64            [::]                       U    100 1      0 enp4s0

We can see that our mesh network: fd55:91ab:2b5a:5186::/64 is on our machine routing table, and this network can be accessed by sending packets to our ESP32 Border Router IPv6 link local address (fe80::2662:abff:fedc:b224) that is assigned to the wifi interface.

So the first stage is done. We can now add nodes, and establish a fully accessible OpenThread Network.

For further testing, the OpenThread site has a series of tutorials, including one regarding SRP (Service registration protocol) that allows Thread nodes to be found through mDNS.

Using in NodeJs raw ECC public keys

A quick post about ECC Public keys that are in their native form of the raw X and Y curve coordinates, and how to use them in NodeJs. Such raw keys are normally outputted by hardware cryptographic devices such as the Microchip ATECC508 or ATECC608. These devices keep the private keys securely, without ever exposing them, and so all cryptographic operations that need to use the private keys need to go through the device.

Any operation that uses a private key for an operation such as signing or encrypting data,  we need the public key to do the other necessary operation, either for verifying if a signature for the data is valid or to decrypt the data. The public key can be obtained from the device at any time and it is a two 32 byte number, for a total of 64 bytes, where 32 bytes is for the X coordinate and the other 32 bytes for the Y coordinate. The device returns the two coordinates concatenated  as X+Y.

So, for the simplest case, we have a signature and a public key in raw ECC format, how do we verify the signature?

Since the Microchip ATECC508 and ATECC608 uses the ECC curve NIST P-256, we need first to instatiate the necessary libraries to handle such cryptographic material:

And at the beginning of our code:

npm i -save crypto elliptic

Notice that we’ve specifically choose the p256 curve for our ec variable, since it is the curve that we are using

var crypto = require("crypto");

var EC = require('elliptic').ec;
var ec = new EC('p256');

An example of a raw public key, already in hex format is:

var pubk = '29C67C7AC65D9C8E78FE82C2D8673DF03BBF0A04D0BD230FE745F5F2BAE7D368F4A4AA73EBFE11838F7189370BC16C256871428EA36952F61006F99178429ADD';

But we can’t use this directly since we need to convert to OpenSSL format. Since the key is not compressed, it has all the components X and Y, this is easily done by prefixing the public key with the 0x04 byte (check 2.2 on RFC5480), and them we can obtain the public key:

var ecpub = '04' + pubk;
var pkec = ec.keyFromPublic(ecpub, 'hex');

The key is now in DER format not compressed. So now, given a message hash and the public key and signature (ecsig) we can check for its validity:

if ( ec.verify( hash, ecsig, pkec ) ) {
    console.log("\nSignature is ok!");
} else {
    console.log("\nSorry, signature is not valid for the provided message hash");
}

JWT Tokens

These crypto devices also have the capability to create JWT tokens, and sign them (again with a private key that is never exposed. These JWT tokens can then be used to provide authentication and identity to any backend service. As the same with the public keys which are provided in raw X and Y coordinates, signatures are provided in raw R and S format. While NodeJS jsonwebtoken library can handle the JWT token, it needs the public key in PEM format, and not in raw X and Y format or DER format.

So we need to convert the raw Public key to PEM format. PEM format is made of a header with specifies some data about the key, namely what curve it belongs to and the raw X and Y key.

There are several ways to do the conversion, but I choose the easiest that is to prefix the raw X and Y values with the necessary header to obtain the key in PEM format.

To obtain the correct header to use we can generate a random ECC NIST P-256 key in PEM format, and extract the header.

openssl ecparam -name prime256v1 -genkey -noout -out tempkey.pem
openssl ec -in tempkey.pem -pubout -outform pem -out temppub.pem

With these two commands we have now a NIST P-256 public key on temppub.pem. We edit the file and remove the header and footer so it looks like this (x.pem file):

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkeBXnGHQ00vwtmTRdSDpPvFHJ+Fqv+Ean8bDg0qZf9mufgD9rpg+XfwIeaifGCpDX2LRW+A9hlZP9YeDsLJTbQ==

We can now convert it to hex and obtain the header by removing the last 65 bytes ( 0x04 + 64 key bytes):

base64 -d x.pem | xxd -i
  0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
  0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
  0x42, 0x00, 0x04, 0x91, 0xe0, 0x57, 0x9c, 0x61, 0xd0, 0xd3, 0x4b, 0xf0,
  0xb6, 0x64, 0xd1, 0x75, 0x20, 0xe9, 0x3e, 0xf1, 0x47, 0x27, 0xe1, 0x6a,
  0xbf, 0xe1, 0x1a, 0x9f, 0xc6, 0xc3, 0x83, 0x4a, 0x99, 0x7f, 0xd9, 0xae,
  0x7e, 0x00, 0xfd, 0xae, 0x98, 0x3e, 0x5d, 0xfc, 0x08, 0x79, 0xa8, 0x9f,
  0x18, 0x2a, 0x43, 0x5f, 0x62, 0xd1, 0x5b, 0xe0, 0x3d, 0x86, 0x56, 0x4f,
  0xf5, 0x87, 0x83, 0xb0, 0xb2, 0x53, 0x6d

Now in NodeJs:

var pubk_header = ‘3059301306072a8648ce3d020106082a8648ce3d030107034200’;
var key = Buffer.from( pubk_header + ’04’ + pubk, ‘hex’);

var pub_pem = “—–BEGIN PUBLIC KEY—–\n” + key.toString(‘base64’) + “\n—–END PUBLIC KEY—–“;

jwt.verify( jwttoken, pub_pem , function(err, decoded) {
   if (err) {
     console.log(‘Failed to verify token.’ );
     console.log(err);
   } else {
       console.log(“Token is valid!”);
       console.log(“Decoded token:”, decoded);
   }
 });

And that’s it. A bit of hackish, but it works fine. If using other curves other than NIST P-256, the appropriate curve and header must be used so that the validations work as expected.

A Zephyr RTOS based TTN Lorawan node

My previous post about Zephyr RTOS sample project with the STM32 blue pill board and the LMIC related posts such as Setting the SPI interface for Lorawan TTN LMIC and Some quick notes about Lorawan LMIC Library where the necessary stepping stones to enable the prototype creation for a TTN (The Things Network) Lorawan node but this time using the Zephyr RTOS and the LoraMac Lorawan library for the network connection.

Specifically for the LoraMac Lorawan node library, a Zephyr RTOS LoraMac code port exists, so it provides a device driver/API to the Zephyr applications which enables them to have Lora/Lorawan connectivity.

The Arduino framework and LMIC library was used as a comparative solution while debugging and testing the settings and connections for a successful connection from the node to the TTN network, to make sure that all configuration was correct and the radio (a RFM95W module) was functional. So during testing I went back and forth between the two libraries: LMIC using the Arduino framework and LoraMac-Node with Zephyr RTOS during testing.

The boards used for testing where the STM32F103CB BluePill board, the STM32F407VE board and the Blackpill STM32F411CE board. All boards can be used with either the Arduino framework and the Zephyr RTOS but I settled on doing most of all the tests with the Blackpill F411 board.

This choice was made because the main issue with the BluePill is that it doesn’t have enough flash space to have the Zephyr RTOS, the USB driver for console logging through USB and the LoraMac-node code and sample program to be stored. Using LMIC and Arduino framework on Bluepill is possible and leaves some space, but with Zephyr RTOS it is only possible to do anything useful but without USB support for serial logging. Still just note that modifying the Zephyr project configuration to have the USB driver from removed, and therefore no console logging through USB, allows to flash the board, but at the end not too much space is left do anything useful with this board. So at the end I stopping doing any tests with the Bluepull and not used it further.

Both the two other boards, the F407 and F411, have more than enough space to do any testing, but the smaller form factor of the F411Blackpill  is ideal size to do the tests (it is breadboard friendly), so I ended up using the F411 for all tests, which means that all the following steps are for this board, but can be easily replicated to other boards as long there is enough flash space.

Zephyr RTOS and Lorawan support:

The Lorawan Zephyr RTOS support was out more or less in October 2020, and for a single board 96b-wistrios board with no other information regarding any other possible boards. The support is possible by enabling the SX1276 radio driver and the Lorawan stack on the Zephyr project configuration file.

Zephyr RTOS feature support are enabled on the prj.conf file by defining the feature that we want to enable. So for this testing the following features where enabled:

# Enable GPIO pins and the SPI interface for 
# communicating with the RF95W module
CONFIG_GPIO=y
CONFIG_SPI=y

# Config USB support so that we can use a USB 
# to view log and debug information
CONFIG_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr Console"

# Point the console to the USB
CONFIG_USB_UART_CONSOLE=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y
CONFIG_UART_CONSOLE_ON_DEV_NAME="CDC_ACM_0"

# Enable the console, logging and the printk function
CONFIG_CONSOLE=y
CONFIG_LOG=y
CONFIG_PRINTK=y

# Enable the SX12XX radio driver for Lora
CONFIG_LORA=y
CONFIG_LORA_LOG_LEVEL_DBG=y
CONFIG_LORA_SX12XX=y
CONFIG_LORA_SX1276=y

# Enable the Lorawan stack
CONFIG_LORAWAN=y
CONFIG_LORAWAN_LOG_LEVEL_DBG=y
# Define the Lorawan region to be used: CONFIG_LORAMAC_REGION_EU868=y #CONFIG_LORAWAN_SYSTEM_MAX_RX_ERROR=90

All the above configuration enables the necessary components for building the Lorawan node: the SPI bus, GPIO, the SX1276 radio driver and LoraWan stack and finally logging to the USB console, where we can just use a simple terminal program to see what is going on, by can also use the printk function for the old style printf debugging…

Note that at this point we’ve haven’t defined neither the board that we will be using and the hardware interface to the Lora radio module, since the target hardware is defined at build time, not at configuration time.

Connecting the Lora Radio Module:
I’m using a RFM95W radio module that exposes some pins but not all from the SX1276 radio. To correctly work the LoraMac node library requires that each SX1276 radio DIO pins has its own associated GPIO pin (we can not merge DIO pins using diodes for example). For the several SX1276 DIO pins for Lora support, the DIO0 and DIO1 must be connected to the processor GPIO pins. The pins can be connected directly without any pull-downs or pull-ups.

The SX1276 radio module, and also by definition, the RF95W radio module that uses the SX1276 radio, use the SPI interface for communication, and so we must select what SPI bus we will use (if the board supports several SPI buses), and connect the associated SPI pins from the selected SPI bus to the RFM95W SPI pins.

To do this we need to define which SPI bus is used and how the RFM95W module is connected.

The Zephyr RTOS uses device trees to specify the connected hardware in a portable method, and comes “out of the box” with a series of predefined configurations for the hardware such SPI, I2C, UART, PWM, LED’s and so on.

If we want to change or add something to the hardware configuration, we need to create what is called an overlay file which for a specific board instance it defines or reconfigures the hardware, and overlays the new configuration over the default provided one.

In our Lorawan node we will be using the Blackpill STM32F411CE board, and for this board the Zephyr RTOS board name is blackpill_f411ce which means that we need to create a new overlay file for selecting which SPI bus we will be using and what pins the RFM95W radio module pins will be using. This configuration must be set on a file named blackpill_f411ce.overlay, and as we can see the filename must math the board name. This file must reside on the root of the project side by side with the prj.conf file or under a sub-directory named boards.

In my testing the RFM95W module will be connected to the SPI1 bus and for this board the pins are taken from the following map:

Blackpill F411 pinout

For the SPI1 bus we have:

  1. MISO – PA6
  2. MOSI – PA7
  3. SCLK – PA5

And these are the default SPI pins for the SPI1 bus that, if we need can we change to other alternate SPI1 bus pins through the overlay file. For now we just use the default pins.
To connect the RFM95W module, we need to define at least the chip select pin NSS, the DIO0 and DIO1 pins being other pins optional. Unlike the LMIC library where we could use LMIC_UNUSED_PIN, it seems that there isn’t such alternative on Zephyr.

So the pin mapping is now:

  1. NSS – PB12
  2. DIO0 – PA0
  3. DIO1 – PA1
  4. RESET – PA2

With the above settings our overlay file has now the following configuration:

&spi1 {
       status = “okay”;
       cs-gpios = <&gpiob 12 GPIO_ACTIVE_LOW>;

       lora: sx1276@0 {
                compatible = “semtech,sx1276”;
                reg = <0>;
                label = “sx1276”;
               reset-gpios = <&gpioa 3 GPIO_ACTIVE_LOW>;
               dio-gpios = <&gpioa 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                    <&gpioa 1 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                    <&gpioa 4 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                   <&gpioa 4 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                   <&gpioa 4 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                                   <&gpioa 4 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
                rfi-enable-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;
                rfo-enable-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;
                pa-boost-enable-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;
               tcxo-power-gpios = <&gpioa 4 GPIO_ACTIVE_HIGH>;
               tcxo-power-startup-delay-ms = <5>;
              spi-max-frequency = <1000000>;
    };
};

/ {
     aliases {
         lora0 = &lora;
     };
};

But the above file has a lot of unused pins mapped to GPIOA pin 4, that is really not correct or ideal.  If checking the following file: ZEPHYR_BASE/zephyr/dts/bindings/lora/semtech,sx1276.yaml we can see that some definitions are not required, and hence we can simplify our hardware configuration to only the RFM95 module pins that we really use:

&spi1 {
   status = “okay”;
   cs-gpios = <&gpiob 12  GPIO_ACTIVE_LOW>;

   lora: sx1276@0 {
       compatible = “semtech,sx1276”;
       reg = <0>;
       label = “sx1276”;
       reset-gpios = <&gpioa 3 GPIO_ACTIVE_LOW>;
       dio-gpios = <&gpioa 0 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>,
                            <&gpioa 1 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
       spi-max-frequency = <1000000>;
       power-amplifier-output = “pa-boost”;
   };
};

/ {
   aliases {
      lora0 = &lora;
 };
};

And this ends the hardware configuration for Zephyr RTOS with the RFM95W module on SPI1 bus.
We are now able to build our Lorawan TTN node for the Blackpill F411 board.

Building the Lorawan node code:
For building the Lorawan node code we must first installed and configured Zephyr RTOS so that we can use the west tool.

The code for the example is available here, and is derived from the class_a Lorawan Zephyr example, with added led blinking and USB logging so we can see what is going on. Beware that we need to first configure first the TTN keys (see below).

git clone https://github.com/fcgdam/zLorawan_Node
workon zephyr 
cd zLorawan_Node
west build -b blackpill_f411ce -p
west flash --runner openocd && sleep 2 && screen /dev/ttyACM0

For flashing I’m using the STLink connected to the board SWD pins, and since the Zephyr default for this board is DFU, we need to specify that we want to use StLink to flash it through the option –runner openocd.

We might need to press the reset button on the board so that the west flash tool works. A simple workaround to this is to edit the openocd.cfg file at (…)/zephyrproject/zephyr/boards/arm/blackpill_f411ce/support/ and add the reset_config none to the file:

source [find board/stm32f4discovery.cfg]
reset_config none

$_TARGETNAME configure -event gdb-attach {
    echo "Debugger attaching: halting execution"
    reset halt
    gdb_breakpoint_override hard
}

$_TARGETNAME configure -event gdb-detach {
    echo "Debugger detaching: resuming execution"
    resume
}

With this modification, flashing should work now without the need to press the reset button.

TTN Configuration:
Unlike the LMIC library, the key values for the necessary keys are taken directly in LSB format from the TTN console. So no need to convert anything to MSB format.
The Application EUI is called now JOIN_EUI and that is the value that we should put on that #define LORAWAN_JOIN_EUI.

Sample node output:
Reseting and connecting to the usb port provided by the board we now have the following output:

[00:00:00.130,000]  sx1276: SX1276 Version:12 found
[00:00:00.281,000]  lorawan.lorawan_init: LoRaMAC Initialized
[00:00:00.338,000]  usb_cdc_acm: Device suspended
[00:00:00.775,000]  usb_cdc_acm: Device configured
Starting up Lora node...
Starting Lorawan stack...
Joining TTN  network over  OTTA
[00:00:02.838,000]  lorawan.lorawan_join: Network join request sent!
Sending data...
[00:00:12.162,000]  lorawan.MlmeConfirm: Received MlmeConfirm (for MlmeRequest 0)
[00:00:12.162,000]  lorawan: Joined network! DevAddr: 260XXXXX
Data sent!
[00:00:16.131,000]  lorawan.McpsIndication: Received McpsIndication 0
[00:00:16.131,000]  lorawan.McpsConfirm: Received McpsConfirm (for McpsRequest 1)
[00:00:16.131,000]  lorawan.McpsConfirm: McpsRequest success!
[00:00:26.132,000]  lorawan: LoRaWAN Send failed: Duty-cycle restricted

And that’s it. Data should be shown now at the Device Traffic tab at the TTN Applications/Device console.

Some quick notes about Lorawan LMIC Library

I’m doing some testing with https://github.com/mcci-catena/arduino-lmic with several STM32 processors (STM32F103 Bluepill, Black STM32F407VE board and the newly arrived STM32F411CE blackpill board) and the RFM95 SX1276 Lora radio for connecting to the TTN Lorawan network. For the Arduino framework the MCCI LMIC library is now the library to use, since all the other alternatives have reached end of life.

For the RFM95W radio module I’m using either the Hallard RFM95W Wemos Lora shield or the DIYcon_nl Lora shield. Please note that on this board, the thickness of the PCB is a bit greater than used by SMA edge mount pin gap intervals, so we need to bend the pins a bit to be able to solder the SMA connector.

Anyway, for the Hallard shield to work, since it “merges” all Lora DIO# pins through diodes to a single pin, a 10K pull down resistor is required to be able to work with the STM32 boards, since even with GPIO pulldown enabled I had issues with the STM32 detecting the state transition. With the resistor everything works fine. This was done since the ESP8266 Wemos board hasn’t enough pins to connect SPI + CS + DIO# pins. This doesn’t happen on the STM32 Bluepill and Blackpill and of course much less on the F407VE black board where we can use all the RFM95W/SX1276 pins.

As I said initially, this post is just some quick notes of what I’ve found when using the library with the above hardware.

EV_JOIN_TXCOMPLETE: no JoinAccept
This error is good, since it means that at hardware level everything might be 100% functional.
This issue happens when using the TTN OTAA method the APPEUI, now with the latest Lorawan releases called JOINEUI and DEVEUI are not in LSB format.
Double check the EUI format, and change the order if necessary.

Assert error at radio.c:1065
This error comes from the ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); radio.c:1065 line.
This is an hardware error. It could be a problematic RF95W module, but so far every instance of this error was caused by the following issues:

1 – Bad wiring
The most obvious issue is just bad wiring/connection, either to the SPI bus or the NSS (chip select) line. Check and double check that the SPI pins used on the STM32 are the correct ones.
For example for the BluePill and the BlackPill the SPI1 bus pins are MISO1 -> PA6, MOSI1 -> A7, SCLK1 -> A5. The NSS pin can be defined on LMIC pin definitions, which takes us to the other possible error:

2 – Bad LMIC pin definition
For the LMIC stack to work, at least the NSS, DIO0 and DIO1 pins must be defined correctly. Without NSS, the RFM95W is never select and hence will never receive the necessary commands to transmit or receive. The correct DIO# pins definition is critical since these indicate when TX and RX have ended and allow the library to retrieve or send more data.
An example that I’m using on the Blackpill F411ce board is as following:

// Pin mapping
const lmic_pinmap lmic_pins = {
    .nss = PB12,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = PA3,
    .dio = {PA0, PA1, PA2},
    //.spi_freq = 4000000
};

Your settings might not be equal, but NSS, DIO0 and DIO1 are mandatory (DIO0 = PA0, DIO1 = PA1 in my case). I have PA2 defined for DIO2, but it can be LMIC_UNUSED_PIN without any consequences.

3 – Again bad wiring
As we can see bad wiring has a lot of influence in how successful we are using the RFM95W module, but in this case even we checked and double checked that everything looks correctly defined, it still doesn’t work and gives uses the radio.c:1065 line assert error.
In all cases that I had with this error it was again bad wiring including cold solder joints.

  • Bad dupont cable
  • Colder solder joint on the NSS pin
  • Bad solder joint on the GND(!) pin

So check all the wires and solder joints for continuity, and if they look suspicious, remake them again. In one of the cases, it was the GND RFM95 board pin, that despite having a blob of solder it didn’t make a proper contact. Also look for shorts and after soldering clean any flux residues to clear out any shorts possibility.

And that’s it. I’ll probably update this post as I find issues along the way.

Setting the SPI interface for Lorawan TTN LMIC

I’m messing around with the Lorawan library LMIC, specifically the more supported MCCI LoRaWAN LMIC library that is supported on several platforms, including the STM32 platform based boards.

While the library worked out of the box on specifically the STM32F103 blue pill and the STM32F407ve black board when using SPI1 bus without any issue, a question popped up: What if I want to SPI2 for example on the STM32F407ve black board instead of the default SPI1 bus for connecting the RFM95W transceiver?

The solution is easy, and works fine if using the ST STM32 Arduino core: Before initializing the LMIC stack we set the SPI pins for the bus we want to use:

...
    // LMIC init
    SPI.setMOSI(PC3);       // Use SPI2 on the STM32F407ve board.
    SPI.setMISO(PC2);
    SPI.setSCLK(PB10);

    os_init();
    
    // Reset the MAC state. Session and pending data transfers will be discarded.
    LMIC_reset();
...
...

Note that this changes the SPI configuration used by the LMIC library which may impact other libraries that use the same SPI global variable. So we need to take caution with this possible side effect.

With this change, LMIC library works as expected by using the Lora transceiver on the SPI2 bus.

Zephyr RTOS sample project with the STM32 blue pill board.

So this post describes more or less in detail how to build a small Zephyr RTOS project using as a target the famous and cheap STM32 blue pill board that has a ST32F103 ARM processor onboard an it is supported by Zephyr RTOS.
The project is quite simple, but it will show how to:

  1. Create a project from scratch.
  2. Create RTOS tasks
  3. Enable USB console

Creating a Zephyr RTOS project
Has documented in my previous post Zephyr RTOS – Initial setup and some tests with Platformio and the NRF52840 PCA10059 dongle and also on Zephyr documentation, we need to download, install and configure the Zephyr RTOS sources, the west tool and the supporting Zephyr SDK. This is explained on the above post.

We can create our project under the zephyr workspace directory, but then we will have trouble if we want to use git to manage our project since the zephyr workspace directory is already a git repository. So we will create our own directory outside of the zephyr workspace directory and work from there.
To do this we need to set the ZEPHYR_BASE environment variable to point to the zephyr workspace, otherwise the west tool that will compile and flash our project will fail. Since west is a python command and we are using virtual environments, as discussed on the previous post we need to first change to the virtual env that has west installed:

workon zephyr

We can now setup our project:

mkdir zSTM32usb
export ZEPHYR_BASE=/opt/Develop/zephyrproject/zephyr
cd zSTM32usb

Because I don’t want to create all the necessary files from scratch, mainly the CMakeLists.txt file, I just copy from the zephyr samples repository the simplest of the projects, blinky:

cp -R /opt/Develop/zephyrproject/zephyr/samples/basic/blinky/* .

At this point we should be able to compile and flash the STM32 blue pill board, but before that we can change the name of the project on the CMakeLists.txt file just for consistency:

# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(zstm32usb)

target_sources(app PRIVATE src/main.c)

We can now compile the project:

west build -b stm32_min_dev_blue

or if we want to do a clean build we add the -p (pristine) flag:

west build -b stm32_min_dev_blue -p

And it should compile without any issues since this is still the basic blinky project.

...
...
-- Configuring done
-- Generating done
-- Build files have been written to: /opt/Develop/zSTM32usb/build
-- west build: building application
[1/138] Preparing syscall dependency handling

[133/138] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       27064 B        64 KB     41.30%
            SRAM:       12392 B        20 KB     60.51%
        IDT_LIST:         184 B         2 KB      8.98%
[138/138] Linking C executable zephyr/zephyr.elf

If we didn’t set correctly the ZEPHYR_BASE environment variable, we will get some errors. For example for listing out the available target boards, we can do a west boards command:

west boards
usage: west [-h] [-z ZEPHYR_BASE] [-v] [-V]  ...
west: error: argument : invalid choice: 'boards' (choose from 'init', 'update', 'list', 'manifest', 'diff', 'status', 'forall', 'help', 'config', 'topdir', 'selfupdate')

With the variable ZEPHYR_BASE (and virtual environment) correctly set, we get:

west boards | grep stm32
...
...
stm32373c_eval
stm32_min_dev_black
stm32_min_dev_blue
stm32f030_demo
...
...

So make sure the environment is correctly set.

Flashing the board:
Flashing the board is as easy as doing:

west flash

To be able to do this is necessary to have a ST-Link programmer and that it is properly connected to the STM32 blue pill board. Any issues here are probably not related with Zephyr or the west tool, since west only calls openocd to flash the board.

 west flash
-- west flash: rebuilding
[0/1] cd /opt/Develop/zSTM32USB/build/zephyr/cmake/flash && /usr/bin/cmake -E echo

-- west flash: using runner openocd
-- runners.openocd: Flashing file: /opt/Develop/zSTM32USB/build/zephyr/zephyr.hex
Open On-Chip Debugger 0.10.0+dev-01341-g580d06d9d-dirty (2020-05-16-15:41)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "hla_swd". To override use 'transport select '.
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
Info : clock speed 1000 kHz
Info : STLINK V2J36S7 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.212648
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : Listening on port 3333 for gdb connections
    TargetName         Type       Endian TapName            State       
--  ------------------ ---------- ------ ------------------ ------------
 0* stm32f1x.cpu       hla_target little stm32f1x.cpu       running

target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08002378 msp: 0x20002768
Info : device id = 0x20036410
Info : flash size = 64kbytes
auto erase enabled
wrote 27648 bytes from file /opt/Develop/zSTM32USB/build/zephyr/zephyr.hex in 1.769166s (15.261 KiB/s)

target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x08002378 msp: 0x20002768
verified 27064 bytes in 0.404006s (65.419 KiB/s)

shutdown command invoked

And now we should have a blinking led.

Creating a project from scratch – Conclusion:
So we now have a project base that we can use that it’s outside of the zephyr workspace directory, and hence, can have it’s own git repository without clashing with the zephyr workspace repository.

We can now move to add functionality to our project.

Creating the RTOS tasks
The basic blinky sample program uses a simple main() entry point and does not create any tasks so it is as simple as it can get.

A single threaded program, like it is blinky, has a main function, and it might have other tasks, either created dynamically or statically.

In our example, we will create the tasks statically. As we can see in the main.c file for our example at the zSTM32usb Github repository we define tasks by using the predefined macro K_THREAD_DEFINE:

// Task for handling blinking led.
K_THREAD_DEFINE(blink0_id, STACKSIZE, blink0, NULL, NULL, NULL, PRIORITY, 0, 0);    

// Task to initialize the USB CDC ACM virtual COM port used for outputing data.
// It's a separated task since if nothing is connected to the USB port the task will hang...
K_THREAD_DEFINE(console_id, STACKSIZE, usb_console_init, NULL, NULL, NULL, PRIORITY, 0, 0);

According to K_THREAD_DEFINE Zephyr documentation the parameters are as follows:

K_THREAD_DEFINE(name, stack_size, entry, p1, p2, p3, prio, options, delay)
Parameters
        name: Name of the thread.
        stack_size: Stack size in bytes.
        entry: Thread entry function.
        p1: 1st entry point parameter.
        p2: 2nd entry point parameter.
        p3: 3rd entry point parameter.
        prio: Thread priority.
        options: Thread options.
        delay: Scheduling delay (in milliseconds), or K_NO_WAIT (for no delay).

Based on this, we can then fine tune the task parameters, for example the stack size that is globally defined as 1024 bytes (way too much), and produces an image that takes around 12K of SRAM:

[133/138] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       27064 B        64 KB     41.30%
            SRAM:       12392 B        20 KB     60.51%
        IDT_LIST:         184 B         2 KB      8.98%
[138/138] Linking C executable zephyr/zephyr.elf

where if we cut the stacksize to 512 bytes, if frees up SRAM, which is now arounf 11K:

[133/138] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
           FLASH:       27064 B        64 KB     41.30%
            SRAM:       11368 B        20 KB     55.51%
        IDT_LIST:         184 B         2 KB      8.98%
[138/138] Linking C executable zephyr/zephyr.elf

So while in this example the stack size is equal to both tasks, in a reality each task should have it’s stack adjusted to make the most of the available SRAM/RAM.

Also since the tasks are cooperative they need to release the processor to other tasks so they can run, hence instructions that wait for resources, or just a simple sleep are required to let all tasks to run cooperatively.

In our example this is achieved by the sleep instruction k_msleep that sleeps the tasks for the miliseconds that is passed as the parameter.

For example for blinking the Led, we have:

    // Blink for ever.
    while (1) {
	gpio_pin_set(gpio_dev, led->gpio_pin, (int)led_is_on);
	led_is_on = !led_is_on;
	k_msleep(SLEEP_TIME_MS);    // We should sleep, otherwise the task won't release the cpu for other tasks!
    }

Tasks description:
Not too much to say about them, except if they do not enter a infinite loop, like the above led blinking while loop, the task does what it has to do and it ends.

Specifically for our example the led blinking task uses the Zephyr Device Tree to retrieve the onboard led configuration, and then, with that configuration it can start blinking the led. This opens the possibility of handling multiple blinking leds with the same code, just by creating a new task for each led.

The usb console init task, initiates the USB console port and waits for a port connection, after the connection happens, it starts printing to the console using the printk function. If we connect to the USB port of the STM32 blue pill board we get:

miniterm2.py /dev/ttyACM0
Hello from STM32 CDC Virtual COM port!

Hello from STM32 CDC Virtual COM port!

....

By experience console output that isn’t used for debugging purposes and/or while in development should be centralized on a single task because: first it will avoid concurrency issues between multiple tasks, and second if nothing is connected to the USB port to consume the console data, the tasks won’t hang waiting for a USB terminal connection to consume the console output.

USB Console Output configuration:
The end this already long post, we need to configure the console output to goto the USB virtual com port. This USB com port is only used for the console output, not for bidirectional communication such as an user and the device using a terminal program.

The configuration is done on the Zephyr configuration project file prj.conf, and the necessary information to enable USB console output was gathered from a series of different sources….

CONFIG_GPIO=y
CONFIG_USB=y
CONFIG_USB_DEVICE_STACK=y
CONFIG_USB_DEVICE_PRODUCT="Zephyr Console"
CONFIG_USB_UART_CONSOLE=y

CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_UART_LINE_CTRL=y
CONFIG_UART_CONSOLE_ON_DEV_NAME="CDC_ACM_0"

A simple and quick description to the above file is that this file enables a set of modules, and provides some configuration to those modules to be able to use them. An example, to use the Led, the Led is connected to a GPIO pin, so it is necessary to enable the GPIO module: CONFIG_GPIO=y.
The same is true to enable USB. It’s necessary to enable USB support and the USB stack. Some console configuration is needed such as the CONFIG_USB_UART_CONSOLE=y, since the original, it seems, console output is to an UART port.

We can see the USB port connected when reseting the board after flashing:

[30958.705584] usb 1-1.2: new full-speed USB device number 7 using xhci_hcd
[30958.818608] usb 1-1.2: New USB device found, idVendor=2fe3, idProduct=0100, bcdDevice= 2.04
[30958.818610] usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[30958.818611] usb 1-1.2: Product: Zephyr Console
[30958.818611] usb 1-1.2: Manufacturer: ZEPHYR
[30958.818612] usb 1-1.2: SerialNumber: 8701241654528651
[30958.880665] cdc_acm 1-1.2:1.0: ttyACM0: USB ACM device

and on the device list:

....
Bus 001 Device 003: ID 0483:3748 STMicroelectronics ST-LINK/V2
Bus 001 Device 007: ID 2fe3:0100 NordicSemiconductor STM32 STLink
...

where the first entry is indeed the ST-Link programmer, and the the second entry is our USB console port.

As a final note, during compilation, a warning about the device id 2fe3:0100 is given, since for production use we need to change the default:

CMake Warning at /opt/Develop/zephyrproject/zephyr/subsys/usb/CMakeLists.txt:22 (message):
  CONFIG_USB_DEVICE_VID has default value 0x2FE3.

  This value is only for testing and MUST be configured for USB products.

CMake Warning at /opt/Develop/zephyrproject/zephyr/subsys/usb/CMakeLists.txt:28 (message):
  CONFIG_USB_DEVICE_PID has default value 0x100.

  This value is only for testing and MUST be configured for USB products.

As usual we can change this by changing the VID on the prj.conf file.

CONFIG_USB_DEVICE_VID=4440

Conclusion:
And that’s it. We now have a minimal skeleton where we can start build some applications and have some console output either for tracing or general information.
The neat part is while I’ve tested this with a STM32 Blue pill board, the same code works without any modification on other boards such as the NRF52840 dongle, which shows that with the same code base we can target different boards.

Upgrading the Arduino MKRWAN Murata Lora module firmware

The Arduino MKRWan 1300 (there is also an improved version MKRWan 1310 that solves some low power issues), is an Arduino compatible board with a SAMD21 ARM processor and a Murata (CMWX1ZZABZ version 078) Lora module that internally has an STM32L0 processor and the Lora transceiver. The STM32L0 Murata module has it’s own firmware that presents an AT modem command type interface to the SAMD21 processor.

While doing some tests I’ve found out that my modules had different Murata firmware versions: 1.1.2, 1.1.5, and so some of the AT commands failed, such as the command to set FPORT AT+PORT that only existed on the 1.1.5 firmware version (or above).

Upgrading the firmware:
My first approach was to download the latest firmware release from the MKRWAN-fw releases and using the Firmware serial bridge combined with the specific STM32 flasher. With this combination it seemed that it was able to flash the STM32L0 through the serial port but I ended up with a bootable Murata module (Could see the +EVENT messages) but no response from the AT commands, so in fact it seemed that I’ve bricked the Murata modules. Reverting to an older firmware version using the same method also exhibited the same behavior.
An example of such upload is as follows:

./stm32flash -b 115200 -e 0 -w mlm32l07x01.bin /dev/ttyACM0 
stm32flash 0.5

http://stm32flash.sourceforge.net/

Using Parser : Raw BINARY
Interface serial_posix: 115200 8E1
Version      : 0x31
Option 1     : 0x00
Option 2     : 0x00
Device ID    : 0x0447 (STM32L07xxx/08xxx)
- RAM        : Up to 20KiB  (8192b reserved by bootloader)
- Flash      : Up to 192KiB (size first sector: 32x128)
- Option RAM : 32b
- System RAM : 8KiB
Write to memory
Wrote address 0x08012ce4 (100.00%) Done.

I’ve also needed to add the -e 0 to not erase the pages, or otherwise the stm32flash failed with an memory erase error so that the command was able to run (it seemed) successfully.
This is probably the issue why the Firmware flashing while sucessufull still ended up with a non responsive module.

Anyway, after some fiddling, there is no need to do anything above. On the MKRWAN library on the examples folder there is a standalone flashing utility with the firmware embedded on the file fw.h as all in one solution. More, the firmware provided seems to be more recent that the MKRWan FW releases folder, version 1.2.0 where on the releases folder it was 1.1.9 with only 1.1.6 providing the binary file.

So all we need is to compile and upload the standalone firmware upload, and it worked straight away:

 miniterm2.py /dev/ttyACM0 
--- Miniterm on /dev/ttyACM0  9600,8,N,1 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Press a key to start FW update
Version      : 0x31
Option 1     : 0x00
Option 2     : 0x00
Device ID    : 0x0447 (STM32L07xxx/08xxx)
- RAM        : Up to 20KiB  (8192b reserved by bootloader)
- Flash      : Up to 192KiB (size first sector: 32x128)
- Option RAM : 32b
- System RAM : 8KiB
Write to memory
Erasing memory
Wrote and verified address 0x08000100 (0%)
 Wrote and verified address 0x08000200 (0%)
 Wrote and verified address 0x08000300 (1%)
 Wrote and verified address 0x08000400 (1%)
 Wrote and verified address 0x08000500 (1%
...
...
 Wrote and verified address 0x08012c00 (100%)
 Done.

Starting execution at address 0x08000000... done.
Flashing ok :)
ARD-078 1.2.0

The odd thing is that the firmware updating is on the Github project for the client Lorawan project, the MKRWan lib and not on the MKRWAN Firmware project.

Using a small terminal/Murata bridge https://github.com/fcgdam/MKRWAN_LoraConsole:

#include <Arduino.h>

void setup() {   
  // Wait for console
  Serial.begin(115200);
  while (!Serial);

  Serial2.begin(19200);                  // Connect to the Murata module through the Serial2 port at 19200

  pinMode(LORA_BOOT0, OUTPUT);
  digitalWrite(LORA_BOOT0, LOW);
  pinMode(LORA_RESET, OUTPUT);
  digitalWrite(LORA_RESET, HIGH);
  delay(200);
  digitalWrite(LORA_RESET, LOW);
  delay(200);
  digitalWrite(LORA_RESET, HIGH);

  Serial.println("Enter AT commands to talk to the Murata module...");
}

void loop() {
	if ( Serial.available() != 0 ) {
		while ( Serial.available() > 0 ) {
			char c = Serial.read();
                if ( c == '\n' ) c = '\r';
		Serial2.print( c );						
		Serial.print( c );						
		if ( c == '\r' )
			Serial.println("");
		}
	}

	if ( Serial2.available() != 0 ) {
		while ( Serial2.available() > 0 ) {
			char c = Serial2.read();
			Serial.print( c );						
			if ( c == '\r' )
				Serial.println("");
		}
	}
}

With this simple sketch flashed onto the MKRWAN board, we can now talk directly to the Murata Module using AT commands, without any dependency from the MKRWAN lib, and hence do any tests that we might want. In my case was just to test:

AT+PORT=5
+OK

Success!.

Zephyr RTOS – Initial setup and some tests with Platformio and the NRF52840 PCA10059 dongle

This posts shows a quick how to for installing and configuring the Zephyr RTOS project on Arch Linux. In reality this post is a mashup of already a set of instructions and tutorials from the Zephyr project home page and also Adafruits Zephyr instructions:

  1. Zephyr RTOS Generic install instructions: https://docs.zephyrproject.org/latest/getting_started/index.html
  2. Adafruits install instructions with setting up Pythons virtual environments: https://learn.adafruit.com/blinking-led-with-zephyr-rtos/installing-zephyr-linux
  3. Specific instructions from the Zephyr RTOS project documentation for Arch Linux: https://docs.zephyrproject.org/latest/getting_started/installation_linux.html

By mashing up all the collected instructions from the above link, here it is my instructions:

Install some needed packages for Arch Linux:

sudo pacman -S git cmake ninja gperf ccache dfu-util dtc wget python-pip python-setuptools python-wheel tk xz file make

Check Python:
Note that Python2 is discontinued, and so all Python programs and packages are for Python 3 version.

One thing that I also had messed up was that the default Python environment on one of my machines was using Platformio penv directory, instead of the Python3 global environment. Make sure that we are using the global environment and not other non global environment.

A (better) approach as described on the Adafruit tutorial is to use Python virtual environments and so we need to install virtual environment support:

sudo pip3 install virtualenv virtualenvwrapper

and we need to change the .bashrc file at our home directory to add virtual environment support:

# For using Python and Venvs
export PATH=~/.local/bin:$PATH
export WORKON_HOME=$HOME/.virtualenvs
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
source /usr/bin/virtualenvwrapper.sh

Execute now source ~/.bashrc to load the new configuration

Since we will load our firmware on the NRF52840 dongle through DFU we also install the nrfutil:

pip install nrfutil

Installing Zephyr RTOS:
I’ll be installing the Zephyr RTOS files and SDK on /opt/Develop:

mkvirtualenv zephyr

mkdir /opt/Develop
cd /opt/Develop
mkdir zephyrproject

workon zephyr
pip install west nrfutil
west init ./zephyrproject

cd zephyrproject
west update

we also installed the nrfutil utility on this virtual environment.

To end the Zephyr RTOS setup we install the also the latest requirements:

pip install -r zephyr/scripts/requirements.txt

and that’s it.

Installing the SDK:
We can install the SDK on some of the predefined directories or our own directories, just make sure that in the later case some environmental variables are set to allow the Zephyr RTOS find the SDK:

wget https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.11.3/zephyr-sdk-0.11.3-setup.run

(zephyr) [pcortex@pcortex:Develop]$ ./zephyr-sdk-0.11.3-setup.run
Verifying archive integrity... All good.
Uncompressing SDK for Zephyr  100%  
Enter target directory for SDK (default: /home/pcortex/zephyr-sdk/): /opt/Develop/zephyr-sdk-0.11.3

It is recommended to install Zephyr SDK at one of the following locations for automatic discoverability in CMake:
  /opt/zephyr-sdk-0.11.3

Note: The version number '-0.11.3' can be omitted.

Do you want to continue installing to /opt/Develop/zephyr-sdk-0.11.3 (y/n)?
y
md5sum is /usr/bin/md5sum
Do you want to register the Zephyr-sdk at location: /opt/Develop/zephyr-sdk-0.11.3
  in the CMake package registry (y/n)?
y
/opt/Develop/zephyr-sdk-0.11.3 registered in /home/pcortex/.cmake/packages/Zephyr-sdk/847bb3ddf638ff02dce20cf8dc171b02
Installing SDK to /opt/Develop/zephyr-sdk-0.11.3
Creating directory /opt/Develop/zephyr-sdk-0.11.3
Success
 [*] Installing arm tools...
 [*] Installing arm64 tools...
 [*] Installing arc tools...
 [*] Installing nios2 tools...
 [*] Installing riscv64 tools...
 [*] Installing sparc tools...
 [*] Installing x86_64 tools...
 [*] Installing xtensa_sample_controller tools...
 [*] Installing xtensa_intel_apl_adsp tools...
 [*] Installing xtensa_intel_s1000 tools...
 [*] Installing xtensa_intel_bdw_adsp tools...
 [*] Installing xtensa_intel_byt_adsp tools...
 [*] Installing xtensa_nxp_imx_adsp tools...
 [*] Installing xtensa_nxp_imx8m_adsp tools...
 [*] Installing CMake files...
 [*] Installing additional host tools...
Success installing SDK.

You need to setup the following environment variables to use the toolchain:

     export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
     export ZEPHYR_SDK_INSTALL_DIR=/opt/Develop/zephyr-sdk-0.11.3

Update/Create /home/pcortex/.zephyrrc with environment variables setup for you (y/n)?
y
SDK is ready to be used.

and the new .bashrc configuration is now:

# For using Python and Venvs
export PATH=~/.local/bin:$PATH
export WORKON_HOME=$HOME/.virtualenvs
export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3
source /usr/bin/virtualenvwrapper.sh

export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
export ZEPHYR_SDK_INSTALL_DIR=/opt/Develop/zephyr-sdk-0.11.3

If we do not add the lines to the .bashrc file when starting up a project or working on it, we need to execute the zephyr-env.sh script on the Zephyr Rtos project directory.

Flashing the Blink sample program on the NRF52840 dongle:

This is pretty much documented on the NRF52840 Dongle page at NRF52840 Dongle documentation.

In our case is just something like:

cd /opt/Develop/zephyrproject
echo Select the PEnv zephyr
workon zephyr
west build -b nrf52840dongle_nrf52840 zephyr/samples/basic/blinky
nrfutil pkg generate --hw-version 52 --sd-req=0x00 --application build/zephyr/zephyr.hex --application-version 1 blinky.zip

and now we need to plugin and enable the dongle dfu mode to flash the firmware:

nrfutil dfu usb-serial -pkg blinky.zip -p /dev/ttyACM0

and the green led on the board should start to blink.

Using Platformio:
While the NRF52840 development kit from Nordic is supported (PCA10056) in both Zephyr and Platformio, the dongle version (PCA10059) is only supported on Zephyr. Since DFU upload is not supported for these boards, so we need some trickery to be able to do it from the Platformio Upload command.

To use to Platformio to target the dongle board, a project targeting the NRF52840_DK board and the Zephyr framework is created and then modifying the platformio.ini we can also target the dongle. For uploading the firmware a custom upload script is used that uses nrfutil to create a non signed DFU package and upload it.

[env:nrf52840_dongle]
platform = nordicnrf52
board = nrf52840_dk
framework = zephyr
board_build.zephyr.variant = nrf52840dongle_nrf52840
extra_scripts = dfu_upload.py
upload_protocol = custom


[env:nrf52840_dk]
platform = nordicnrf52
board = nrf52840_dk
framework = zephyr

For the NRF52840 dongle we pass to the Platformio build system the board variant used by Zephyr that targets the dongle, which is the nrf52840dongle_nrf52840 (where it was previously nrf52840_pca10059). Since the dongle hasn’t an on board debugger for uploading firmware through JTAG/Stlink, we need to use a custom upload method with an associated python script:

import sys
import os
from os.path import basename
Import("env")

platform = env.PioPlatform()

def dfu_upload(source, target, env):
    firmware_path = str(source[0])
    firmware_name = basename(firmware_path)

    genpkg = "".join(["nrfutil pkg generate --hw-version 52 --sd-req=0x00 --application ", firmware_path, " --application-version 1 firmware.zip"])
    dfupkg = "nrfutil dfu usb-serial -pkg firmware.zip -p /dev/ttyACM0"
    print( genpkg )
    os.system( genpkg )
    os.system( dfupkg )

    print("Uploading done.")


# Custom upload command and program name
env.Replace(PROGNAME="firmware", UPLOADCMD=dfu_upload)

This dfu_upload.py file is put side by side with the platformio.ini file and has some hardcoded values, such as the upload port, but it gets the job done.