Trying to perform OTA to a nRF52840 device via nRF9160 LTE connection

Dear All,

What I need is to be able to perform a DFU on a nRF52840 device that has a UART connection to nRF9160 device that can download the bin file for the nRF52840 device.

I have seen that there are 3 main threads on the topic:

https://devzone.nordicsemi.com/f/nordic-q-a/90110/send-dfu-image-between-two-nrf5340
https://devzone.nordicsemi.com/f/nordic-q-a/79779/firmware-update-ota-of-nrf9160-by-ble-nrf52840-and-update-nrf52840-by-fota-of-nrf9160/330275

 RE: Perform NRF52840 OTA over the NRF9160 and UART

From what I gather from the posts above, there was a pull-request at some point that is now closed that was doing exactly what I am looking for.
I have tried to implement the changes described on this pull request and tried to compile initially a very basic project for the nRF52840.

At the very start of the build process, I am getting this error:


 Found assembler: C:/zephyr-sdk-0.15.2/arm-zephyr-eabi/bin/arm-zephyr-eabi-gcc.exe
usage: zcbor.py [-h] [--version] -c CDDL [--default-max-qty DEFAULT_MAX_QTY]
                [--no-prelude] [-v]
                {code,convert} ...
zcbor.py: error: the following arguments are required: -c/--cddl
CMake Error at C:/ncs/nrf/subsys/dfu/dfu_target/src/dfu_target_uart_cddl/CMakeLists.txt:19 (execute_process):
  execute_process failed command indexes:

    1: "Child return code: 2"



-- Configuring incomplete, errors occurred!
←[91mFATAL ERROR: command exited with status 1: 'C:\Program Files\CMake\bin\cmake.EXE' '-DWEST_PYTHON=C:\Python311\python.exe' '-BC:\Users\Desktop\nRF52\build' -GNinja -DBOARD=nrf52840dk_nrf52840 -DSIGNED=Signed '-SC:\Users\Desktop\nRF52'
←[0m


Any ideas what do I need to do in order to fix this and move on with allowing the application to compile?

I am using NCS v2.1.0 running on Windows

Parents
  • Hi,

    First, I just want to check:

    Do you run separate applications for both the nRF52840 and the nRF9160?
    The alternatives would be something like Serial LTE Modem or HCI Uart.

    Regards,
    Sigurd Hellesvik

  • Not sure what you mean. What I am initially trying to achieve is to download a FOTA file, via LTE on the nRF9160 and then set the nRF52840 in DFU mode and send it the new image via UART and then have it running the new image.

    From what I understand the PR above downloads the bin file in segments and sends each segment directly via UART to the nRF52840, which is also acceptable.

    Currently, I am stuck with building the nRF52840 application that does what is shown in the sample for the nRF53 (although there is nothing special to the nRF53, from what I can understand in the prj.conf).

    The only thing that I am also not sure of is

    CONFIG_UPDATEABLE_IMAGE_NUMBER=2


    Is this needed for the nRF52840?

  • Giannis Anastasopoulos said:
    Is this needed for the nRF52840?

    It is not.

    Let's take a birds eyes view over what you are trying to do:

    You want to perform DFU between two DKs.
    This is done using firmware called mcumgr.
    mcumgr use the SMP Protocol for updates. In this case, a SMP Client sends the update to a SMP Server.

    We have a working SMP Server, and this comes in two types, either SMP Server in the application or Serial Recovery. I explain both these in my unofficial bootloader samples.
    If you want to update the nRF52840, you will need a SMP Server on the nRF52840. Serial Recovery uses less memory and is more robust, so I would recommend that for this purpose.

    Then on your nRF9160, you will need an SMP Client.
    As you say, we are working on an SMP Client in the nRF Connect SDK. This has not been released yet, but will be part of an nRF Connect SDK tag in the future. (Ask sales for timeline)
    In the meantime, you have some alternatives:

    • Use the Pull requests implementing this into your nRF Connect SDK
      • This will be easier if you use the newest version of the nRF Connect SDK.
        The mcumgr code has changed paths since v2.1.0, so implementing the changes will require you to patch most of the mcumgr drivers.
      • Here is an Pull request for our official implementation. https://github.com/zephyrproject-rtos/zephyr/pull/56934
    • Alternatively, you can use an unofficial sample which implements an SMP Client as a sample, without read driver support yet. I have an unofficial sample at my github repo.
      • This code/configuration is not thoroughly tested or qualified and should be considered provided “as-is”. Please test it with your application and let me know if you find any issues.

    Here are a couple of cases where I have discussed similar stuff before:

     BLE FOTA example for Central device 

     Information on how to perform a FOTA update on nRF52811

    From what I gather from the posts above, there was a pull-request at some point that is now closed that was doing exactly what I am looking for.

    My options above explain how to do an SMP Client.
    Still, some code is needed to first download the update from FOTA and then send it using the SMP Client.
    This is what the PR you refer to has been trying to fix.
    So it is up to you if you want to implement such a feature yourself, if you want to try to use the PR or if you want to wait for us to implement such functionality officially in the nRF Connect SDK. Ask sales if you want to know about timelines)

    Any ideas what do I need to do in order to fix this and move on with allowing the application to compile?

    Which command did you run when you got this error?

Reply
  • Giannis Anastasopoulos said:
    Is this needed for the nRF52840?

    It is not.

    Let's take a birds eyes view over what you are trying to do:

    You want to perform DFU between two DKs.
    This is done using firmware called mcumgr.
    mcumgr use the SMP Protocol for updates. In this case, a SMP Client sends the update to a SMP Server.

    We have a working SMP Server, and this comes in two types, either SMP Server in the application or Serial Recovery. I explain both these in my unofficial bootloader samples.
    If you want to update the nRF52840, you will need a SMP Server on the nRF52840. Serial Recovery uses less memory and is more robust, so I would recommend that for this purpose.

    Then on your nRF9160, you will need an SMP Client.
    As you say, we are working on an SMP Client in the nRF Connect SDK. This has not been released yet, but will be part of an nRF Connect SDK tag in the future. (Ask sales for timeline)
    In the meantime, you have some alternatives:

    • Use the Pull requests implementing this into your nRF Connect SDK
      • This will be easier if you use the newest version of the nRF Connect SDK.
        The mcumgr code has changed paths since v2.1.0, so implementing the changes will require you to patch most of the mcumgr drivers.
      • Here is an Pull request for our official implementation. https://github.com/zephyrproject-rtos/zephyr/pull/56934
    • Alternatively, you can use an unofficial sample which implements an SMP Client as a sample, without read driver support yet. I have an unofficial sample at my github repo.
      • This code/configuration is not thoroughly tested or qualified and should be considered provided “as-is”. Please test it with your application and let me know if you find any issues.

    Here are a couple of cases where I have discussed similar stuff before:

     BLE FOTA example for Central device 

     Information on how to perform a FOTA update on nRF52811

    From what I gather from the posts above, there was a pull-request at some point that is now closed that was doing exactly what I am looking for.

    My options above explain how to do an SMP Client.
    Still, some code is needed to first download the update from FOTA and then send it using the SMP Client.
    This is what the PR you refer to has been trying to fix.
    So it is up to you if you want to implement such a feature yourself, if you want to try to use the PR or if you want to wait for us to implement such functionality officially in the nRF Connect SDK. Ask sales if you want to know about timelines)

    Any ideas what do I need to do in order to fix this and move on with allowing the application to compile?

    Which command did you run when you got this error?

Children
  •   

    I am trying to use your samples on my custom nRF52840 board.

    For the most part I am successful so far with getting the device to DFU mode.

    There is just one issue that I have encountered:

    Since my board is a custom one, in order to get it to work, I am applying an overlay file.

    From what I see when the project compiles, the overlay is picked and applied on the board of the main application, but this is not the case for the MCUboot build:

    For the normal application:

    -- Found BOARD.dts: C:/ncs/zephyr/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts
    -- Found devicetree overlay: C:/Users/np080/Desktop/GIT_Projects/mcuboot_serial_recovery_uart/nrf52840dk_nrf52840.overlay
    -- Generated zephyr.dts: C:/Users/np080/Desktop/GIT_Projects/mcuboot_serial_recovery_uart/build/zephyr/zephyr.dts
    -- Generated devicetree_unfixed.h: C:/Users/np080/Desktop/GIT_Projects/mcuboot_serial_recovery_uart/build/zephyr/include/generated/devicetree_unfixed.h


    For the MCUboot:

    === child image mcuboot -  begin ===
    loading initial cache file C:/Users/np080/Desktop/GIT_Projects/mcuboot_serial_recovery_uart/build/mcuboot/child_image_preload.cmake
    Loading Zephyr default modules (Zephyr base).
    -- Application: C:/ncs/bootloader/mcuboot/boot/zephyr
    -- Found Python3: C:/Python311/python.exe (found suitable exact version "3.11.4") found components: Interpreter 
    -- Cache files will be written to: C:/ncs/zephyr/.cache
    -- Zephyr version: 3.1.99 (C:/ncs/zephyr)
    -- Found west (found suitable version "1.1.0", minimum required is "0.7.1")
    -- Board: nrf52840dk_nrf52840
    -- Found host-tools: zephyr 0.15.2 (C:/Users/np080/zephyr-sdk-0.15.2)
    -- Found dtc: C:/ProgramData/chocolatey/bin/dtc.exe (found suitable version "1.5.0", minimum required is "1.4.6")
    -- Found toolchain: zephyr 0.15.2 (C:/Users/np080/zephyr-sdk-0.15.2)
    -- Found BOARD.dts: C:/ncs/zephyr/boards/arm/nrf52840dk_nrf52840/nrf52840dk_nrf52840.dts
    -- Found devicetree overlay: C:/ncs/nrf/modules/mcuboot/usb.overlay
    -- Found devicetree overlay: C:/ncs/bootloader/mcuboot/boot/zephyr/dts.overlay
    -- Generated zephyr.dts: C:/Users/np080/Desktop/GIT_Projects/mcuboot_serial_recovery_uart/build/mcuboot/zephyr/zephyr.dts
    -- Generated devicetree_unfixed.h: C:/Users/np080/Desktop/GIT_Projects/mcuboot_serial_recovery_uart/build/mcuboot/zephyr/include/generated/devicetree_unfixed.h
    -- Generated device_extern.h: C:/Users/np080/Desktop/GIT_Projects/mcuboot_serial_recovery_uart/build/mcuboot/zephyr/include/generated/device_extern.h
    -- Including generated dts.cmake file: C:/Users/np080/Desktop/GIT_Projects/mcuboot_serial_recovery_uart/build/mcuboot/zephyr/dts.cmake


    As a result I cannot reset the device to DFU because the MCUboot uses a different pin.

    My overlay file looks like this:

    &uart0 {
    	compatible = "nordic,nrf-uarte";
    	status = "okay";
    	current-speed = <115200>;
    	pinctrl-0 = <&uart0_default>;
    	pinctrl-1 = <&uart0_sleep>;
    	pinctrl-names = "default", "sleep";
    };
    
    &uart1 {
    	status = "disabled";
    };
    
    &pinctrl {
    	uart0_default: uart0_default {
    		group1 {
    			psels = <NRF_PSEL(UART_TX, 1, 14)>;
    		};
    		group2 {
    			psels = <NRF_PSEL(UART_RX, 1, 15)>;
    			bias-pull-up;
    		};
    	};
    
    	uart0_sleep: uart0_sleep {
    		group1 {
    			psels = <NRF_PSEL(UART_TX, 1, 14)>,
    					<NRF_PSEL(UART_RX, 1, 15)>;
    			low-power-enable;
    		};
    	};
    };
    
    / {
    	buttons {
    		compatible = "gpio-keys";
    
    		button0: button_0 {
    			gpios = <&gpio0 6 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
    			label = "Push button switch 0";
    		};
    	};
    	leds {
    		compatible = "gpio-leds";
    		led0: led_0 {
    			gpios = <&gpio1 2 GPIO_ACTIVE_LOW>;
    			label = "Green LED 0";
    		};
    	};
    
    	aliases {
    		inputpin1 = &button0;
    		customled0 = &led0;
    		bootloader-led0 = &led0;
    		mcuboot-button0 = &button0;
    		mcuboot-led0 = &led0;
    	};
    };
    
    &qspi {
    	status = "disabled";
    };


    What I found out is that if I copy the nRF52840dk_nRF52840 into my project, manually change the dts and dtsi to match the stuff I have on my overlay and compile then the whole thing works.

    So what I wonder is if there is anyway to apply my overlay to the MCUboot board as well without touching the SDK files.

  • Giannis Anastasopoulos said:
    What I found out is that if I copy the nRF52840dk_nRF52840 into my project, manually change the dts and dtsi to match the stuff I have on my overlay and compile then the whole thing works.

    So what I wonder is if there is anyway to apply my overlay to the MCUboot board as well without touching the SDK files.

    Yes.

    Indeed, you have found one of the important features of Zephyr: Board files.

    We provide board files for our Developement kits, with names such as "nrf52840dk_nrf52840".
    You can overlay these, meaning that you add extra configurations to them, but keep the rest.

    However, if you design your own board, I recommend that you create custom board files to fit this board.
    You can build your project with "-- -DBOARD_ROOT=<PATH_TO_BOARD_FILES>" to point the build to your custom board files, and thus add them without changing the SDK.

    Here are some useful docs and tools:

    https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/config_and_build/board_support.html#custom-boards

    https://nrfconnect.github.io/vscode-nrf-connect/guides/bd_boards_devices.html?h=board

    https://nrfconnect.github.io/vscode-nrf-connect/reference/ui_sidebar_welcome.html?h=new+board#create-a-new-board

    https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/hardware/porting/board_porting.html#board-porting-guide

    Does this make sense?

  • Dear Sigurd,

    Thanks a lot for your response and assistance.

    I figured out the issue I had with editing your sample for the serial recovery dfu.

    In order to have the MCUboot use the same pinout as the overlay for the main application, I had to copy this overlay inside the child_image/mcuboot/boards directory and then that part worked like a charm.


    What I did next is to create a .bin file and use it to DFU the nRF52840. For now I am using the default MCUboot key, to test with.


    The next step towards my goal is to download the .bin file. Using the sample found in ncs\nrf\samples\nrf9160\download it becomes straight forward to download my file.

    In the sample there is the download callback that I have simplified to make it look like this:

    static int callback(const struct download_client_evt* event)
    {
        static size_t downloaded;
        static size_t file_size;
        uint32_t speed;
        int64_t ms_elapsed;
    
        if (downloaded == 0) {
            download_client_file_size_get(&downloader, &file_size);
            downloaded += STARTING_OFFSET;
        }
    
        switch (event->id) {
            case DOWNLOAD_CLIENT_EVT_FRAGMENT:
                downloaded += event->fragment.len;
                smp_frag_upload(event->fragment.buf, event->fragment.len);
                return 0;
            case DOWNLOAD_CLIENT_EVT_DONE:
                ms_elapsed = k_uptime_delta(&ref_time);
                speed = ((float)file_size / ms_elapsed) * MSEC_PER_SEC;
                printk("\nDownload completed in %lld ms @ %d bytes per sec, total %d bytes\n", ms_elapsed, speed, downloaded);
                lte_lc_power_off();
                printk("Bye\n");
                return 0;
            case DOWNLOAD_CLIENT_EVT_ERROR:
                if ((socket_retries_left)) {
                    printk("Download socket error: %d. %d retries left...\r\n", event->error, socket_retries_left);
                    socket_retries_left--;
                    /* Fall through and return 0 below to tell
                     * download_client to retry
                     */
                    return 0;
                }
                else {
                    printk("Error %d during download\n", event->error);
                    lte_lc_power_off();
                    /* Stop download */}
    
                    return -1;
        }
    
        return 0;
    }


    In there, as you can see there is the smp_frag_upload that I am trying to implement in order to send the data received directly via uart to the nRF52 device:

    static void smp_frag_upload(void* buf, size_t len)
    {
        struct mgmt_hdr image_list_header;
        struct net_buf* nb;
    
        uint8_t data[2048 + 1]; // One more byte, to store '/0'
        memcpy(data, buf, MIN((sizeof(data) - 1), len));
        data[MIN((sizeof(data) - 1), len) + 1] = '\0';
    
        static int offset = 0x0000C000; // Start of slot0 partition of the nRF52840
    
        nb = smp_packet_alloc();
    
        zcbor_state_t zs[CBOR_ENCODER_STATE_NUM];
        size_t tailroom = net_buf_tailroom(nb);
        printk("NUM of bytes: %u\r\n", tailroom);
        zcbor_new_encode_state(zs, ARRAY_SIZE(zs), nb->data + sizeof(struct mgmt_hdr), tailroom, 0);
    
        zs->constant_state->stop_on_error = true;
        zcbor_map_start_encode(zs, 20);
        zcbor_tstr_put_lit(zs, "image");
        zcbor_int64_put(zs, 0);
        zcbor_tstr_put_lit(zs, "data");
        zcbor_bstr_put_lit(zs, data);
        zcbor_tstr_put_lit(zs, "len");
        zcbor_uint64_put(zs, (uint64_t)len);
        zcbor_tstr_put_lit(zs, "off");
        zcbor_uint64_put(zs, offset);
        zcbor_tstr_put_lit(zs, "sha");
        zcbor_bstr_put_lit(zs, "12345");
        zcbor_tstr_put_lit(zs, "upgrade");
        zcbor_bool_put(zs, false);
        zcbor_map_end_encode(zs, 20);
    
        if (!zcbor_check_error(zs)) {
            printk("Failed to encode SMP test packet, err: %d\n", zcbor_pop_error(zs));
            goto end;
        }
    
        offset += len;
    
        image_list_header.nh_op = MGMT_OP_WRITE;
        image_list_header.nh_flags = 0;
        image_list_header.nh_len = zs->payload_mut - nb->data - MGMT_HDR_SIZE;
        image_list_header.nh_group = MGMT_GROUP_ID_IMAGE;
        image_list_header.nh_seq = 0;
        image_list_header.nh_id = IMG_MGMT_ID_UPLOAD;
        mgmt_hton_hdr(&image_list_header);
    
        nb->len = zs->payload_mut - nb->data;
        memcpy(nb->data, &image_list_header, sizeof(image_list_header));
        int err = uart_mcumgr_send(nb->data, nb->len);
        if (err) {
            printk("upload2: uart_mcumgr_send failed with %d\n", err);
            goto end;
        }
    end:
        net_buf_unref(nb);
    }


    In my case I am receiving the data in chunks of 2048 bytes, so, following the idea in your sample I am declaring a buffer at the size of the chunk plus 1 that is set to '0'.

    In the board files, I see that the slot0 particion starts at 0xC000 for the nRF52840, so I am sending that as the offset of the first chunk and for each subsequent chunk I am incrementing the value with the number of bytes already received.
    In my prj.conf I have set this configuration to make sure that the buffer is sufficiently large to contain the data of the fragment.

    CONFIG_MCUMGR_BUF_SIZE=2200


    In my custom board configuration I have set this:
    chosen {
    		zephyr,console = &uart0;
    		zephyr,shell-uart = &uart0;
    		zephyr,uart-mcumgr = &uart1;
    	};

    In the dtsi this is what I have:

    &pinctrl {
    ...
        uart1_default: uart1_default {
    		group1 {
    			psels = <NRF_PSEL(UART_TX, 0, 1)>;
    		};
    		group2 {
    			psels = <NRF_PSEL(UART_RX, 0, 0)>;
    			bias-pull-up;
    		};
    	};
    
    	uart1_sleep: uart1_sleep {
    		group1 {
    			psels = <NRF_PSEL(UART_TX, 0, 1)>,
    				<NRF_PSEL(UART_RX, 0, 0)>;
    			low-power-enable;
    		};
    	};
    ...
    };

    So bringing this sample together, I do see the data downloading properly, but the application run by the nRF52840 does not change.

    Is there any step that I am missing here?

  • Giannis Anastasopoulos said:

    What I did next is to create a .bin file and use it to DFU the nRF52840. For now I am using the default MCUboot key, to test with.

    When you build with MCUboot enabled, these binaries are automatically generated for you and lives inside build/zephyr.
    The one for this is called build/zephyr/app_update.bin. See MCUboot output build files for more info.

    Giannis Anastasopoulos said:
    that part worked like a charm.

    Were you able to perform Serial Recovery DFU from a computer to test this?

    Giannis Anastasopoulos said:
    The next step towards my goal is to download the .bin file. Using the sample found in ncs\nrf\samples\nrf9160\download it becomes straight forward to download my file.

    Makes sense to me.

    Giannis Anastasopoulos said:

    So bringing this sample together, I do see the data downloading properly, but the application run by the nRF52840 does not change.

    Is there any step that I am missing here?

    Steps seem logical to me.

    Do you get any errors from the mcumgr code?

    Is it possible for you to probe the UART lines using either a analyzator or oscilloscope to verify if data is being transferred?

    If the file was being transferred but wrong, we would not see the old app still on the nRF52840, instead it would fail at boot. So we can assume that something went wrong so that the file was never transferred.

  •   

    I have finally manage to update my nRF52 device using your code as a starting point, so I would like to thank you for your assistance.

    I have one more question regarding the process.

    In the case of the having the DFU running through the application and not in the bootloader of the nRF52, it seems that the mcumgr uart is occupied and it is not capable of receiving UART data apart from the DFU data. Is that correct? Is there anyway around it, like registering the uart for MCUMGR when needed and un-registering it later on?

    On the serial recovery mode, what I am observing is that if I send a new image signed with the wrong key, the image gets applied on the nRF52, but it will not run leaving the device in the bootloader. Is there a way to check that the image is valid before applying it?

    Again, thanks a lot for your assistance.

Related