Firmware OTA and remote control on NXP FRDM-RW612

This article shows how to add over-the-air (OTA) firmware updates and remote device control to an NXP FRDM-RW612 board using the Mongoose library and mDash, a device management cloud. You enable mDash with three lines in mongoose_config.h, push new firmware with one curl command, and call your own C functions on the device through a REST API. Nothing here is RW612-specific: the same steps work on STM32, ESP32, NXP i.MX RT, RP2350 and other microcontrollers supported by Mongoose.

Why bother with a device management backend

Once you have more than a handful of devices in the field, you end up solving the same three problems again and again:

  1. Remote firmware update - ship a fix without anyone touching the device
  2. Remote device control - read state, flip a pin, change a setting
  3. Health monitoring - know when a device crashed, and why

You can build all of that yourself. You'd need a secure connection from device to cloud, an authentication scheme, a transport for firmware chunks, an RPC layer, and a server side to tie it all together. Or you can use mDash, which is designed to sit next to Mongoose and does exactly this. It is available as a managed service at https://mdash.net or as a private on-premises installation.

mDash is not meant to replace your product's own cloud. It takes care of the boring fleet plumbing, and your backend talks to it over a REST API.

This article covers OTA and remote control. Health monitoring will get its own video.

What you need

Step 1 - build and flash the minimal example

The RW612 example lives in tutorials/nxp/frdm-rw612. There are two projects: mcuxpresso and minimal. I'm using minimal, which is a plain Makefile project with no IDE involved. Everything below works the same in the MCUXpresso project.

cd mongoose/tutorials/nxp/frdm-rw612/minimal
make flash

On the first run, the Makefile pulls the CMSIS headers for ARM and RW612, then builds and flashes the firmware. Open a serial monitor before you flash (115200 baud) to see the boot log. The board brings up Ethernet and gets an IP address over DHCP.

The firmware runs a tiny web server, so you can check it right away:

curl http://BOARD_IP/
Hi from Mongoose, tick 12345

curl http://BOARD_IP/api/tick
{"tick":12346}

That's the whole starting point: a board on the network with Mongoose running. Now let's hook it up to mDash.

Step 2 - connect the board to mDash

Log in to https://mdash.net and click "Add device". A new device shows up in the list. Click the lock icon next to it to copy the device password to your clipboard.

Then open mongoose_config.h. The example already has the mDash lines there, just disabled. Turn them on and paste the password:

#define MG_ENABLE_MDASH 1
#define MG_MDASH_KEY "..."           // Paste the device password here
#define MG_FIRMWARE_VERSION "1.0.0"
#define MG_TLS MG_TLS_BUILTIN

MG_TLS_BUILTIN is Mongoose's own TLS implementation, so you don't need mbedTLS or wolfSSL to talk to the cloud securely. MG_FIRMWARE_VERSION is reported to mDash as fw_version, which helps you see whether an update actually landed.

Rebuild and reflash:

make flash

In the serial log you'll see the device connect to mDash and exchange a few messages with it (more on those below). In the mDash UI the device turns green - it's online.

Tip: keep the device password out of version control. It's a credential, not a config option.

Step 3 - OTA firmware update

To have something visible to update, I changed the LED blink interval in main.c from 100 ms to 1 second:

static void blink_task(void) {
  static uint64_t blink_timer = 0;
  if (hal_timer_expired(&blink_timer, 1000, hal_get_tick())) {
    hal_gpio_toggle(LED1);
  }
}

This time, don't flash it. Just build:

make build

That produces firmware.bin. You have two ways to push it to the device.

Option 1: Web UI. In the mDash device list, click the download button on your device and pick firmware.bin. The serial log shows the update going through. The device reboots, comes back online, and the LED now blinks once a second.

Option 2: command line. Get an API key from https://mdash.net/#/keys and run:

curl -su :MDASH_API_KEY -F file=@firmware.bin \
  https://mdash.net/api/v2/devices/DEVICE_ID/ota

The CLI option is the one you'll actually use day to day, because you can drop it into a CI pipeline or a script that updates a group of devices.

What happens on the device during OTA

There is no special OTA client in your firmware. mDash drives the update with plain RPC calls that the Mongoose mDash connector registers for you:

On the RW612 those calls end up in Mongoose's RW612 flash driver (MG_OTA MG_OTA_RW612 in mongoose_config.h), which writes to external FlexSPI NOR flash. If you want the details of the flash layout and the staging + copy approach, see NXP RW612 OTA firmware update.

Step 4 - how remote control works

The device keeps a secure WebSocket connection to mDash. Over that link they exchange JSON-RPC frames. When the board connects, mDash asks it for basic info. In the serial log it looks roughly like this.

The cloud sends a request:

{"id": 1, "method": "Sys.GetInfo", "params": {}}

The device answers with a matching id:

{"id": 1, "result": {"fw_version": "1.0.0", "uptime": 3, "reboot_reason": "...", "arch": "...", "report": {"valid": false}}}

That's the whole protocol: the request has an id, a method and optional params, and the response has the same id plus either a result or an error.

The useful part is that mDash bridges this to HTTP REST. Any RPC function on the device can be called with an ordinary HTTP request:

https://mdash.net/api/v2/devices/DEVICE_ID/rpc/METHOD_NAME

mDash turns the HTTP request into a JSON-RPC frame, sends it down the WebSocket, waits for the reply, and returns the result as the HTTP response. Your device never needs to accept an inbound connection, so NAT and firewalls are not a problem.

Try it:

curl -su :MDASH_API_KEY https://mdash.net/api/v2/devices/DEVICE_ID/rpc/Sys.GetInfo
curl -su :MDASH_API_KEY https://mdash.net/api/v2/devices/DEVICE_ID/rpc/RPC.List

RPC.List returns every function registered on the device. Out of the box you get the built-in ones from the mDash connector: RPC.List, Sys.GetInfo, OTA.Begin, OTA.Write and OTA.End.

Step 5 - add your own RPC function

Built-ins are fine, but the point is to expose your own functionality. Let's add Sys.Pin, a function that reads or writes any GPIO pin:

Add this handler to main.c:

static void mg_rpc_sys_pin(struct mg_rpc_req *r) {
  int pin = (int) mg_json_get_long(r->frame, "$.params.pin", -1);
  int val = (int) mg_json_get_long(r->frame, "$.params.val", -1);
  if (pin >= 0 && val >= 0) {
    hal_gpio_write(pin, val);  // Both pin and val are set: write pin
    mg_rpc_ok(r, "true");
  } else if (pin >= 0) {
    mg_rpc_ok(r, "%d", hal_gpio_read(pin));  // Only pin is set: read pin
  } else {
    mg_rpc_err(r, 500, "%m", MG_ESC("set pin and val"));
  }
}

And register it next to the built-ins, after mg_mgr_init():

mg_rpc_add(&mgr.rpcs, mg_str("Sys.Pin"), mg_rpc_sys_pin, NULL);

mg_json_get_long() pulls integers out of the request params and falls back to -1 when a field is missing, which is how the handler tells the three cases apart. mg_rpc_ok() and mg_rpc_err() build the response frame for you.

Build and push it over the air - no cable needed this time:

make build
curl -su :MDASH_API_KEY -F file=@firmware.bin \
  https://mdash.net/api/v2/devices/DEVICE_ID/ota

Call RPC.List again and Sys.Pin is in the list.

Playing with the RGB LED

Call it with no params first, to check the error path:

curl -su :MDASH_API_KEY https://mdash.net/api/v2/devices/DEVICE_ID/rpc/Sys.Pin

You get an RPC error with the message set pin and val, exactly as written in the handler.

The FRDM-RW612 has an RGB LED wired to three pins, defined in main.c:

#define LED1 PIN(0, 12)  // Green
#define LED2 PIN(0, 0)   // Blue
#define LED3 PIN(0, 1)   // Red

So pin 0 is blue, pin 1 is red and pin 12 is green. To avoid fighting with the blink task, I commented out the hal_gpio_toggle(LED1) line and reflashed. Now read the blue pin:

curl -su :MDASH_API_KEY https://mdash.net/api/v2/devices/DEVICE_ID/rpc/Sys.Pin -d '{"pin": 0}'

It returns 1, and the LED is off. That's not a bug: the LEDs on this board are active-low, so 1 means off and 0 means on. Turn blue on:

curl -su :MDASH_API_KEY https://mdash.net/api/v2/devices/DEVICE_ID/rpc/Sys.Pin -d '{"pin": 0, "val": 0}'

Set pins 0, 1 and 12 all to 0 and you get white.

Blinking an LED from a curl command is a toy, of course. But the same pattern works for anything you'd want to do remotely: read a sensor, change a config value, trigger a relay, dump a log. Write a handler, register it, push via OTA, and it's callable from your backend over REST.

FAQ

Does this work on boards other than the RW612? Yes. The mDash connector is part of Mongoose and doesn't depend on the chip. Anything Mongoose runs on with networking and an OTA driver works the same way: STM32, ESP32, NXP i.MX RT, RP2350 and so on. Only the board-specific hal.c and the MG_OTA setting change.

Do I need MCUXpresso or an RTOS? No. The minimal example is a bare-metal Makefile project. Mongoose has its own TCP/IP stack and TLS, so there's no lwIP or mbedTLS to integrate either.

How does the device authenticate to mDash? Each device has its own password (MG_MDASH_KEY). The device opens a TLS WebSocket connection to mDash and sends the password as a bearer token. The connection is outbound only, so the device needs no open ports.

Can my own backend call functions on the device? Yes, that's what the REST bridge is for. Your server calls /api/v2/devices/DEVICE_ID/rpc/METHOD with an API key and gets the device's JSON-RPC result back as the HTTP response.

Can I run mDash on my own servers? Yes. Besides the managed service at https://mdash.net, mDash is available as a private on-premises installation.