Last updated: 26 January 2020
First published: 26 January 2020

OTA Firmware Updates with nRF52840 and SIM7600E LTE Module

Continuing with the Connecting to Cloud series, this part will look into how to perform Over-The-Air (OTA) firmware updates over cellular network. Once again we will use AWS and GCP IoT Core services for MQTT communication between field devices and the cloud platform, Nordic Semiconductors nRF52840 as device MCU and SIM7600E LTE/3G/2G GSM module for cellular network communication.

Let's first build a simple but practical use case to understand the rational behind design decisions that follows:

  1. There are fifty thousand (50,000) IoT devices spread across the world.
  2. Updates can only be initiated by a trusted entity and the device should detect corrupted binary images before and after the update process.
  3. Cost of firmware update should be as less as possible.

Irrespective of your application, its remote OTA update requirements will be very similar to the above ones.

Firmware Update over Trusted Connection

In part one, we established a trusted connection by authenticating and encrypting the MQTT communication between device and the cloud service using TLS with client and server certificate verification. Now it might seem obvious to publish the plain firmware image over this trusted connection, however there are two issues with this approach: QoS and Cost.

Maximum MQTT publish message payload size for AWS IoT core is 128KB and for GCP State or Configuration message it is 64KB. Firmware image size for nRF52840 can be upto 1 MBytes so the image will need to be split into fragments, this brings up the QoS parameter. QoS-2 is not supported by either of our selected cloud services. QoS-0 is not acceptable here. QoS-1 will produce duplicates which will need additional programming to handle it. QoS-0/1 both needs some additional request-response layer on top of MQTT to reliably transfer large blob of data. Transferring large blob of data over MQTT is more expensive as compared to HTTP downloads.

Alternatively we could just publish a simple JSON update request message to devices over MQTT. Because the connection is trusted we can simply use symmetric cryptography to encrypt the firmware image and share the key in JSON message. Encrypted firmware image can then be publicly hosted on any CDN or cloud storage services and devices can download it over HTTP(S). Using HTTP is not an additional firmware overhead as almost all GSM module manufacturers include HTTP AT commands. In this case, SIM7600E even provides built-in storage to download files from HTTP(S) URLs into internal storage.

A JSON update request message over trusted connection could be like:

Listing-1: ota.json
    "type": "update_fw",
    "url": "",
    "alt_url": "http://host.any/image.ebin",    
    "fw_info": "0000000000100000b8070100c0070100000102030405060708090a0b0c0d0e0f00112233445566778899aabbccddeeffba40d96777aeaf3901e3d3eb7db8b9939938c981e18286bd61b3350d00ff6816dd10918635ca7bf00c3dafe76f2a280891f107f4569b11b5d732d630819abebf"

"fw_info" contains the hash, size and key information of the encrypted firmware image. Notice the HTTP protocol in "alt_url", we can use it because even if an attacker injects incorrect image, devices have all the required data to catch and reject it.

Firmware Update Cost

Let's assume the size of firmware image to be 512 KB.

Total download size = 50,000 devices x 512 KB download per device = 24.4141 GB 

Size of the JSON update request message above is 379 bytes.

For all the cost computation, prices were taken taken on this article's First Published date. Only costs related firmware update is considered here. Other costs for keep-alive messages, connection, subscribe, publishing telemetry data would be common to over all application. Both services offer Always Free Usage Limits, these are not taken into account because apart from OTA updates your devices will be consuming data for other application specific purposes. All prices are in USD.


  • AWS: Cost = $1.0 per million messages [1]. PUBLISH messages are metered in 5 KB increments. PUBACK is also metered as a single 5KB message[2]. To initiate OTA update some entity has to publish update request message to IoT core which in turn will deliver messages to all subscribed devices.
    Update request publish messages = 1 PUBLISH  + 1 PUBACK = 2 messages.
    Update request delivery messages = 50,000 devices x (1 PUBLISH + 1 PUBACK) per device = 100,000 messages. 
    Total update request cost = 100,002 messages x ($1 / 1000,000 messages) = $0.10
  • GCP: Cost = $0.0045 per MB [3]. Minimum message size = 1KB. Update request would be sent as a configuration message. PUBACK of the device is also metered for configuration messages.
    Update configuration message size = 50,000 devices x (1 KB PUBLISH + 1 KB PUBACK) per device = 97.65625 MB   
    Total update request cost = 97.65625 MB x ($0.0045 / MB) = $0.44


Let's assume firmware image would be available for 7 days to download in case updates are done in batches.
  • AWS S3 Standard[4]: Source Region - US East (Ohio)
    Storage cost for 7 days = 512 KB x 7 days x ($0.023 / 30 days-GB) = $0.00000262 = $0.01 (cost won't be zero)
    GET request cost = 50,000 devices x 1 GET request per device x ($0.0004 / 1000 requests) = $0.02
    Data transfer cost = 24.4141 GB x ($0.09 / GB) = $2.2 (No Transfer Acceleration)
    Total Data Cost = $2.23
  • GCP Cloud Storage Standard[5]: Source Region - US(multi-region)
    Storage cost for 7 days = 512 KB x 7 days x ($0.026 / 30 days-GB) = $0.000002962 = $0.01
    GET request cost = 50,000 devices x 1 GET request per device x ($0.004 / 10,000 class B operations) = $0.02
    GCP network egress charges depends on destination region, average of all region cost is assumed:
    Data transfer cost = 24.4141 GB x ($0.165 / GB) = $4.03
    Total Data Cost = $4.06

Device Side Architecture

The OTA firmware update process is managed by two parts: Application and Bootloader.

  1. Application through its trusted connection will receive MQTT message for update request. It will then call SIM7600E HTTP AT commands to download firmware image in module's internal memory. Whenever the application is ready (or scheduled) it will invoke bootloader.
  2. Bootloader is designed to do just one thing and do it right every time. Its operation times are limited by watch dog timer and it can resume its last failed step on reboot. When bootloader is invoked by application it verifies the integrity of the encrypted firmware image and whether an update is really required. On success it will begin decryption and flashing process. Bootloader also has access to GSM module but the module need not be connected to internet. Bootloader is a custom implementation, it uses nrf_crypto library with CC310 backend for decryption and hash functionality, there is no dependency on soft device. On every boot, bootloader verifies application's hash before transferring control to it. Application can be with or without a soft device.

    This implementation deviates a lot from nRF52 SDK bootloader exmaples:

    • There is no DFU as it is not applicable here.
    • Images are not signed because we already have trusted connection to receive keys.

    Memory layout of the device:

    Device Memory Layout

    Device Memory Layout


OTA firmware updates are crucial part of IoT devices and must be made reliable with thorough testing. The cost of physically accessing a device if something goes wrong in update process is expensive so there must be few more features in a practical device:

  • Automatic roll back to previous version if current update fails in between. SIM7600E has abundant storage and it can be used to store many versions of encrypted images.
  • Update of bootloader itself.
  • The MBR and Bootloader flash regions should be protected from accidental erase.

Sample Project

Like in part one, this project can also be configured with AWS or GCP. Application image is encrypted with AES 128bit in CBC mode with PKCS7 padding and hashing is with SHA256.
Note, once the hash and keys are sent over MQTT they should not be preserved outside field devices.

Project source code and configuration details are available on my GitHub public repository: