nrf52840 S140 Central DFU Bluetooth Zephyr 2.0.0 nrf52840 peripheral

We have nrf52840 S140 BLE Central device that currently connects to TI based sensors. I have implemented code that can upgrade those TI sensors' firmware using the TI BLE protocol.

Now we are developing our own BLE peripheral sensors also based on the nrf52840 but now using the newly supported Zephyr 2.0.0. RF connect stuff. I can update the sensors using the Nordic Android program (unauthenticated for now) and now need to implement upgrading BLE DFU using our central device which connects to a cellular gateway.

I do not want to reinvent the wheel, writing existing code, like I had to do with the TI sensors.

Could you point me to code that allows a Central device to update a peripheral device BLE DFU?

If not where can I look to reinvent another wheel?

Thanks David

Parents
  • Have a look at the sample Bluetooth: Central SMP Client

    Then use the SMP sever Sample on the Bluetooth Peripheral device

    Currently it is only possible to send the echo command from the Central SMP Client, so you would have to implement the other commands yourself, like upload, listtest and so on.. 

    Best regards,

    Simon

  • Now I have gotten back to this issue with our custom sensor hardware to arrive next week.

    I have used the Android nRF Toolbox app successfully to update an unsigned image whereas the Nordic DFU tool did not work.

    I understand that the Android apps use the SMP protocol that ties in with the img_mgmt libraries.

    Where can I find documentation on what the Android app does so I can reproduce it in c in my central device?

    Without any documentation it will be very hard for me to reverse engineer the Java somehow.

    I do not want to have to add more code in the sensor to handle a custom DFU if possible since it already exists and the Zephyr junk makes a giant image anyway.

    I saw a post with my exact request,  but I understand that it is not a priority issue.

     BLE DFU, MCU to MCU (Zephyr), GATT DFU SMP Service Clinet 

    I also saw a SoftDevice (not Zephyr) example post of what I need but uses the old DFU stuff I think and SoftDevice APIs

     Mesh DFU: Serial transfer of DFU archive 

    I need much more information to implement upload,list & test SMP commands that evidently exist in my sensor using the SMP server.

    Could it be that the Android app used some legacy DFU?

    Thanks David

  • Our hardware is supposed to arrive by the end of the day.

    Our Gateway, uses a host microcontroller with a cellular modem, connected by a uart serial channel to a MS88SF2 module with the nRF52840 chip as a Central device.
    The nRF52840 has no external storage.
    When updating TI sensors, the image information is first sent, which is used to start the TI OAD.
    Then, I stream the new image bytes to a 64K ram buffer on the nRF52840 while it writes TI BLE OAD packets.
    I must keep the ram buffer filled or the TI OAD process will fail with a timeout.

    It does look very daunting to reconstruct the Kotlin code using the SMP protocol in my Central code.
    I am still exploring my different options but I was thinking of trying something based on the multi-NUS-master example.

    Very roughly something like this:
    1) The Central will receive an image update message with the new image information (size,crc,etc) and which Peripherals to update from its host.
    2) The Central will connect to all of its peripherals that need the image update.
    Every message below will be broadcast to all connected periperals.
    3) The Central will send a message to erase its slot#1 image
    Is Slot#1 correct?
    What API should I call?
    4) The Central will send image block packets to fill up a 64K ram buffer.
    5) The Central will send a message to flash the 64K to Slot#1 at the specified offset.
    What API should I call?
    6) The Central will continue with steps 4) & 5) until the whole image has been written.
    7) The Central will send a message to get the image's hash.
    What API should I call?
    8) The Central will send a message to "test" the image
    What API should I call?
    9) The Central will send a message to reset the peripheral.
    What API should I call?
    10) Upon reconnection, the Central will send a message to "confirm" the image.

    If the nRF Toolbox app is able to upgrade the peripheral, do I need any new prog.confg defines for the suggested API?

    Another quick question that is related.
    If I design the project with a mode that leaves the Peripherals always connected,
    I understand that I may be able to get an even lower Peripheral current drain.
    If a Peripheral is at the edge of the BLE signal range or there is a lot of interference ,
    will I end up wasting a lot for current?

    Thanks David

  • Hello and sorry for the delay. I've been gone on vacation the last two weeks.

    Could you update me on the current state of this ticket? Then I will help you accordingly.

    Best regards,

    Simon

  • I have been working on other things in this project and on other more urgent projects changes and fixes. 

    Now I am back on this and still need to get this working somehow.

    If you could just write the correct APIs in every step, I may be able to advance this. quicker.

    One of my peripheral sensors stopped working when trying to measure its current.

    It is not detected by the SDK. Is there any way to brick a nRF52840 or have I just damaged a pin?

    Thanks David

  • DavidKaplan said:
    3) The Central will send a message to erase its slot#1 image
    Is Slot#1 correct?
    What API should I call?

    You should put the update into the secondary slot, so if you refer to the primary slot as slot#0 and the secondary slot as slot#1, then slot#1 is correct yes.

    Regarding what API to call to erase the secondary slot, you can take a look at the function img_mgmt_upload()-->img_mgmt_impl_erase_image_data() (called in response to an mcumgr upload command), as you can see, it uses the function flash_area_erase() to erase the secondary slot.

    DavidKaplan said:
    5) The Central will send a message to flash the 64K to Slot#1 at the specified offset.
    What API should I call?

    Take a look at the same function img_mgmt_impl_erase_image_data(), as mentioned above. It will write to the secondary slot using img_mgmt_impl_write_image_data()-->flash_img_buffered_write()-->stream_flash_buffered_write()

    If using fota_download+dfu_target_mcuboot (used by nRF91 to update appl.) the same function is used: dfu_target_write-->dfu_target_mcuboot_write()-->

    stream_flash_buffered_write().
    I guess it could work to just use flash_map.h-->flash_area_write() or flash.h-->flash_write, but as mentioned in the documentation, there are some advantages with using the Stream Flash API:
    "One typical use of a stream write operation is when receiving a new firmware image to be used in a DFU operation.

    There are several reasons why one might want to use buffered writes instead of writing the data directly as it is made available. Some devices have hardware limitations which does not allow flash writes to be performed in parallel with other operations, such as radio RX and TX. Also, fewer write operations result in faster response times seen from the application."

    DavidKaplan said:
    7) The Central will send a message to get the image's hash.
    What API should I call?

    Check how it's done in the function \zephyr\subsys\mgmt\mcumgr\lib\cmd\img_mgmt\src\img_mgmt.c-->img_mgmt_read_info(), which will read the version and build hash from the specified image slot.

    DavidKaplan said:
    8) The Central will send a message to "test" the image
    What API should I call?

    Check out how how the image management library does it: \zephyr\subsys\mgmt\mcumgr\lib\cmd\img_mgmt\include\img_mgmt\img_mgmt.h-->img_mgmt_state_set_pending()-->img_mgmt_impl_write_pending()-->boot_request_upgrade_multi()

    So you can set the image to a "test" (run once and then revert) state by running boot_request_upgrade_multi(int image_index, int permanent) with permanent=0, or confirm the image by setting permanent=1 (run image forever).

    DavidKaplan said:
    9) The Central will send a message to reset the peripheral.
    What API should I call?

    You can use \zephyr\lib\os\reboot.c-->sys_reboot() for this.

    DavidKaplan said:
    10) Upon reconnection, the Central will send a message to "confirm" the image.

    You can do this by running boot_write_img_confirmed() from within the app itself, like it's done in the http_update sample: https://github.com/nrfconnect/sdk-nrf/blob/v2.0.2/samples/nrf9160/http_update/application_update/src/main.c#L60 

    DavidKaplan said:
    If the nRF Toolbox app is able to upgrade the peripheral, do I need any new prog.confg defines for the suggested API?

    If you wan't to perform a DFU app from the phone using custom BLE commands and the APIs I suggested above, you can use the nRF Connect for Mobile app: https://www.nordicsemi.com/Products/Development-tools/nrf-connect-for-mobile 

    If you want to perfom a DFU using mcumgr protocol, I suggest using the nRF Connect Device Manager app and follow the DFU guide.

    DavidKaplan said:
    Another quick question that is related.
    If I design the project with a mode that leaves the Peripherals always connected,
    I understand that I may be able to get an even lower Peripheral current drain.
    If a Peripheral is at the edge of the BLE signal range or there is a lot of interference ,
    will I end up wasting a lot for current?

    This is not my area of expertise. Could you open a new ticket and ask about it there. I think you will get better help then.

    Best regards,

    Simon

  • I successfully upgraded a unit once but did not saved immediately the code and evidently changed something and it now fails when calling img_mgmt_impl_write_pending().

    I guess I could build a small project for the nRF52840-DK  using the serial uart interface.

    It is hard to debug break with the BLE running.

    In my main program when running the debugger it always says the image is not confirmed.

    I expected this to occur just once after DFU.

      //---------------------------------------------------------------------------
      // DFU upgraded and not confirmed, confirm
      //---------------------------------------------------------------------------
      if (!boot_is_img_confirmed()){
        err = boot_write_img_confirmed();
    	  if (err) {
    		  d_printf(LINE_INFO,kDbg_Error|kDbg_Display,"BootConfirmErr:%d",err);
        }else{
              d_printf(LINE_INFO,kDbg_Error|kDbg_Display,"BootConfirmed");
        }
      }
    

    Here is my DFU code if you see anything wrong.

    I could not find a way to set the global area_id except for setting it myself.

    I would think that some function should set it.

    g_img_mgmt_state.area_id = UPLOAD_FLASH_AREA_ID;

    #include <stddef.h>
    #include <string.h>
    #include <stdio.h>
    #include <errno.h>
    #include <kernel.h>
    #include <zephyr.h>
    
    #include <sys/printk.h>
    
    #include <zcbor_encode.h>
    #include <zcbor_decode.h>
    #include <zcbor_common.h>
    
    #include <zephyr/bluetooth/bluetooth.h>
    #include <zephyr/bluetooth/hci.h>
    #include <zephyr/bluetooth/hci_vs.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/uuid.h>
    #include <zephyr/bluetooth/gatt.h>
    #include <bluetooth/gatt_dm.h>
    #include <zephyr/sys/byteorder.h>
    #include <bluetooth/services/dfu_smp.h>
    #include <sys/reboot.h>
    #include <zephyr/sys/ring_buffer.h>
    
    #include <dfu/mcuboot.h>
    #include <zephyr/dfu/mcuboot.h>
    #include <dfu/dfu_target.h>
    #include <dfu/dfu_target_stream.h>
    #include <dfu/dfu_target_mcuboot.h>
    
    #include <img_mgmt/img_mgmt_impl.h>
    #include <img_mgmt/img_mgmt.h>
    #include <img_mgmt/image.h>
    #include <storage/flash_map.h>
    #include <dfu/flash_img.h>
    #include <devicetree.h>
    
    #if FLASH_AREA_LABEL_EXISTS(image_1)
    	#define UPLOAD_FLASH_AREA_ID FLASH_AREA_ID(image_1)
    #endif
    
    
    #include "aqua_def.h"
    #include "d_printf.h"
    #include "ami_ble.h"
    #include "ami_main_serv.h"
    #include "ami_setup_serv.h"
    #include "ami_learn_serv.h"
    #include "ami_transfer_serv.h"
    #include "ami_timer.h"
    #include "ami_dfu.h"
    #include "ami_watchdog.h"
    
    //-----------------------------------------------------------------------------
    // define Ring buffers
    //-----------------------------------------------------------------------------
    RING_BUF_ITEM_DECLARE_SIZE(dfu_ring_buf, AMI_DFU_RING_BUF_SIZE);
    
    //-----------------------------------------------------------------------------
    // Variables
    //-----------------------------------------------------------------------------
    ami_dfu_state_struct    *dfu_state = NULL;
    static struct	flash_img_context flash_img;
    
    //-----------------------------------------------------------------------------
    // ami_dfu_HandlePacket
    //  Description
    //   Parses an incoming BLE image packet
    //  Parameters
    //  Returns
    //   0 = OK
    //-----------------------------------------------------------------------------
    int ami_dfu_HandlePacket(uint8_t *buf,uint16_t buf_pos,uint16_t len)
    {
      //---------------------------------------------------------------------------
      // transfer_receive_cb
      //---------------------------------------------------------------------------
      int       err = 0;
      uint16_t  data_len;
      //---------------------------------------------------------------------------
      // FileRxOffset
      //---------------------------------------------------------------------------
      dfu_state->FileRxOffset.bytes[LSB_LO_BYTE] = buf[buf_pos++];
      dfu_state->FileRxOffset.bytes[LSB_HI_BYTE] = buf[buf_pos++];
      dfu_state->FileRxOffset.bytes[MSB_LO_BYTE] = buf[buf_pos++];
      dfu_state->FileRxOffset.bytes[MSB_HI_BYTE] = buf[buf_pos++];
      //---------------------------------------------------------------------------
      // If FileRxOffset==0 then FileSize sent
      //---------------------------------------------------------------------------
      if (!dfu_state->FileRxOffset.ulong){
        dfu_state->FileSize.bytes[LSB_LO_BYTE] = buf[buf_pos++];
        dfu_state->FileSize.bytes[LSB_HI_BYTE] = buf[buf_pos++];
        dfu_state->FileSize.bytes[MSB_LO_BYTE] = buf[buf_pos++];
        dfu_state->FileSize.bytes[MSB_HI_BYTE] = buf[buf_pos++];
      }
      data_len = (len-buf_pos);
      //---------------------------------------------------------------------------
      // Something to write to Flash 
      //---------------------------------------------------------------------------
      if (data_len){
        //-------------------------------------------------------------------------
        // Store image data 
        //-------------------------------------------------------------------------
        if (dfu_state->FileRingWrOffset==dfu_state->FileRxOffset.ulong){
          uint16_t num_put = ring_buf_put(&dfu_ring_buf,&buf[buf_pos],data_len);
          if (num_put!=data_len) {
            dfu_state->err = AMI_DFU_CENTRAL_ERR_RING_OVERFLOW;
            d_printf(LINE_INFO,kDbg_Error|kDbg_General,"RingPut:%u Rx:%u Off:%ld",num_put,data_len,dfu_state->FileRxOffset.ulong);
            return err;
          }else{
                dfu_state->FileRingWrOffset += data_len;
          }
        }else{
              // This was a retry that the Central did not received an acknowledgemnet
        }
      }
      return err;
    } // ami_dfu_HandlePacket
    
    //-----------------------------------------------------------------------------
    // ami_dfu_HandleBlock
    //  Description
    //   Handles the incoming DFU image block
    //  Parameters
    //  Returns
    //  the offset to send back o the Central
    //-----------------------------------------------------------------------------
    uint32_t ami_dfu_HandleBlock(void)
    {
      static   uint8_t wr_buf[CONFIG_IMG_BLOCK_BUF_SIZE];
      bool     bLast      = false;
      //---------------------------------------------------------------------------
      // If there was a fatal error, just return the last error
      //---------------------------------------------------------------------------
      if (dfu_state->err){
        return dfu_state->err;
      }
      //---------------------------------------------------------------------------
      // All bytes were received?
      //---------------------------------------------------------------------------
      bool bRxFinished = (dfu_state->FileRingWrOffset>=dfu_state->FileSize.ulong);
      do{
          //-----------------------------------------------------------------------
          // Peek and see how many bytes accumulated exist
          //-----------------------------------------------------------------------
          uint32_t accum_bytes = ring_buf_size_get(&dfu_ring_buf);
          //-----------------------------------------------------------------------
          // Wait to write until we have CONFIG_IMG_BLOCK_BUF_SIZE bytes or last packet 
          //-----------------------------------------------------------------------
          if (accum_bytes<CONFIG_IMG_BLOCK_BUF_SIZE && !bRxFinished){
            break;
          }
          //-----------------------------------------------------------------------
          // Don't write more than CONFIG_IMG_BLOCK_BUF_SIZE bytes at a time
          //-----------------------------------------------------------------------
          if (accum_bytes>CONFIG_IMG_BLOCK_BUF_SIZE){
            accum_bytes = CONFIG_IMG_BLOCK_BUF_SIZE;
          }
          //-----------------------------------------------------------------------
          // Calculate the number of bytes left to write to flash
          //-----------------------------------------------------------------------
          uint32_t bytes_to_wr = (dfu_state->FileSize.ulong-dfu_state->FileWrOffset);
          //-----------------------------------------------------------------------
          // Last block to write?
          //-----------------------------------------------------------------------
          if (accum_bytes>=bytes_to_wr){
            accum_bytes = bytes_to_wr;
            bLast       = true;
          }
          //-----------------------------------------------------------------------
          // Get the bytes from the ring buffer
          //-----------------------------------------------------------------------
          uint32_t data_len   = ring_buf_get(&dfu_ring_buf,wr_buf,accum_bytes);
          //-----------------------------------------------------------------------
          // Nothing to write?
          //-----------------------------------------------------------------------
          if (!data_len){        
            break;
          }
          //-----------------------------------------------------------------------
          // Write the data to the flash
          //-----------------------------------------------------------------------
          int err = img_mgmt_impl_write_image_data(dfu_state->FileWrOffset,wr_buf,data_len,bLast);
          if (err!=0) {
            dfu_state->err = AMI_DFU_CENTRAL_ERR_WR_BLOCK;
            d_printf(LINE_INFO,kDbg_Error|kDbg_General,"ImageWrErr:%d",err);
            return dfu_state->err;
          }else{
                //-----------------------------------------------------------------
                // Update last offset written
                //-----------------------------------------------------------------
                dfu_state->FileWrOffset += data_len;
                //-----------------------------------------------------------------
                // All of the image was written?
                //-----------------------------------------------------------------
                if (dfu_state->FileWrOffset>=dfu_state->FileSize.ulong){
                  int image_slot = 1;
                  struct image_version ver;
                  uint8_t  hash;
                  uint32_t flags;
                  bool permanent = false;
                  
                  err = img_mgmt_read_info(image_slot,&ver,&hash,&flags);
                  if (err==0){
                    err = img_mgmt_impl_write_pending(image_slot,permanent);
                    if (err==0){
                      d_printf(LINE_INFO,kDbg_Error|kDbg_General,"FinishedDFU");
                    }else{
                          dfu_state->err = AMI_DFU_CENTRAL_ERR_SET_PENDING;                
                    }
                  }else{
                        dfu_state->err = AMI_DFU_CENTRAL_ERR_RD_INFO;
                        d_printf(LINE_INFO,kDbg_Error|kDbg_General,"ImageInfoErr:%d",err);
                  }
                  //---------------------------------------------------------------
                  // Reset after reply
                  //---------------------------------------------------------------
                  ami_state->reset_secs = 2;
                }
          }
      }while (bRxFinished);
      return dfu_state->FileRxOffset.ulong;
    } // ami_dfu_HandleBlock
    
    //-----------------------------------------------------------------------------
    // ami_dfu_Start
    //  Description
    //   Inits the dfu state and sets the dfu streaming buffer
    //  Parameters
    //  Returns
    //   0 = OK
    //-----------------------------------------------------------------------------
    int ami_dfu_Start(void)
    {
      //---------------------------------------------------------------------------
      // Clear dfu state
      //---------------------------------------------------------------------------
      memset(dfu_state,0,sizeof(ami_dfu_state_struct));
      //---------------------------------------------------------------------------
      // Reset dfu ring buffer
      //---------------------------------------------------------------------------
      ring_buf_reset(&dfu_ring_buf);
      //---------------------------------------------------------------------------
      // Erase upload slot
      //---------------------------------------------------------------------------
      int err = img_mgmt_impl_erase_slot();
      if (err!=0){
        //-------------------------------------------------------------------------
        // Reset after reply
        //-------------------------------------------------------------------------
        ami_state->reset_secs = 2;
        dfu_state->err = AMI_DFU_CENTRAL_ERR_ERASE;
        d_printf(LINE_INFO,kDbg_Error|kDbg_General,"ImageEraseErr:%d", err);
        return err;
      }      
      //---------------------------------------------------------------------------
      // Init Flash stream writer (CONFIG_IMG_BLOCK_BUF_SIZE)
      //---------------------------------------------------------------------------
      g_img_mgmt_state.area_id = UPLOAD_FLASH_AREA_ID;
      err = flash_img_init(&flash_img);
      if (err!=0){
        d_printf(LINE_INFO,kDbg_Error|kDbg_General,"FlashInitErr:%d", err);
        dfu_state->err = AMI_DFU_CENTRAL_ERR_IMAGE_INIT;
        //-------------------------------------------------------------------------
        // Reset after reply
        //-------------------------------------------------------------------------
        ami_state->reset_secs = 2;
        return err;
      }
      g_img_mgmt_state.area_id = UPLOAD_FLASH_AREA_ID;
      return err;
    } // ami_dfu_Start
    
    //-----------------------------------------------------------------------------
    // ami_dfu_Init
    //  Description
    //   Alolocates memory
    //  Parameters
    //  Returns
    //   0 = OK
    //-----------------------------------------------------------------------------
    int ami_dfu_Init(void)
    {
      int err = 0;
    
      //---------------------------------------------------------------------------
      // Allocate memory
      //---------------------------------------------------------------------------
      dfu_state  = k_malloc(sizeof(ami_dfu_state_struct));
      while (!dfu_state);
      memset(dfu_state,0,sizeof(ami_dfu_state_struct));
      return err;
    } // ami_dfu_Init
    
    

    Thanks David

  • Reply
    • I successfully upgraded a unit once but did not saved immediately the code and evidently changed something and it now fails when calling img_mgmt_impl_write_pending().

      I guess I could build a small project for the nRF52840-DK  using the serial uart interface.

      It is hard to debug break with the BLE running.

      In my main program when running the debugger it always says the image is not confirmed.

      I expected this to occur just once after DFU.

        //---------------------------------------------------------------------------
        // DFU upgraded and not confirmed, confirm
        //---------------------------------------------------------------------------
        if (!boot_is_img_confirmed()){
          err = boot_write_img_confirmed();
      	  if (err) {
      		  d_printf(LINE_INFO,kDbg_Error|kDbg_Display,"BootConfirmErr:%d",err);
          }else{
                d_printf(LINE_INFO,kDbg_Error|kDbg_Display,"BootConfirmed");
          }
        }
      

      Here is my DFU code if you see anything wrong.

      I could not find a way to set the global area_id except for setting it myself.

      I would think that some function should set it.

      g_img_mgmt_state.area_id = UPLOAD_FLASH_AREA_ID;

      #include <stddef.h>
      #include <string.h>
      #include <stdio.h>
      #include <errno.h>
      #include <kernel.h>
      #include <zephyr.h>
      
      #include <sys/printk.h>
      
      #include <zcbor_encode.h>
      #include <zcbor_decode.h>
      #include <zcbor_common.h>
      
      #include <zephyr/bluetooth/bluetooth.h>
      #include <zephyr/bluetooth/hci.h>
      #include <zephyr/bluetooth/hci_vs.h>
      #include <zephyr/bluetooth/conn.h>
      #include <zephyr/bluetooth/uuid.h>
      #include <zephyr/bluetooth/gatt.h>
      #include <bluetooth/gatt_dm.h>
      #include <zephyr/sys/byteorder.h>
      #include <bluetooth/services/dfu_smp.h>
      #include <sys/reboot.h>
      #include <zephyr/sys/ring_buffer.h>
      
      #include <dfu/mcuboot.h>
      #include <zephyr/dfu/mcuboot.h>
      #include <dfu/dfu_target.h>
      #include <dfu/dfu_target_stream.h>
      #include <dfu/dfu_target_mcuboot.h>
      
      #include <img_mgmt/img_mgmt_impl.h>
      #include <img_mgmt/img_mgmt.h>
      #include <img_mgmt/image.h>
      #include <storage/flash_map.h>
      #include <dfu/flash_img.h>
      #include <devicetree.h>
      
      #if FLASH_AREA_LABEL_EXISTS(image_1)
      	#define UPLOAD_FLASH_AREA_ID FLASH_AREA_ID(image_1)
      #endif
      
      
      #include "aqua_def.h"
      #include "d_printf.h"
      #include "ami_ble.h"
      #include "ami_main_serv.h"
      #include "ami_setup_serv.h"
      #include "ami_learn_serv.h"
      #include "ami_transfer_serv.h"
      #include "ami_timer.h"
      #include "ami_dfu.h"
      #include "ami_watchdog.h"
      
      //-----------------------------------------------------------------------------
      // define Ring buffers
      //-----------------------------------------------------------------------------
      RING_BUF_ITEM_DECLARE_SIZE(dfu_ring_buf, AMI_DFU_RING_BUF_SIZE);
      
      //-----------------------------------------------------------------------------
      // Variables
      //-----------------------------------------------------------------------------
      ami_dfu_state_struct    *dfu_state = NULL;
      static struct	flash_img_context flash_img;
      
      //-----------------------------------------------------------------------------
      // ami_dfu_HandlePacket
      //  Description
      //   Parses an incoming BLE image packet
      //  Parameters
      //  Returns
      //   0 = OK
      //-----------------------------------------------------------------------------
      int ami_dfu_HandlePacket(uint8_t *buf,uint16_t buf_pos,uint16_t len)
      {
        //---------------------------------------------------------------------------
        // transfer_receive_cb
        //---------------------------------------------------------------------------
        int       err = 0;
        uint16_t  data_len;
        //---------------------------------------------------------------------------
        // FileRxOffset
        //---------------------------------------------------------------------------
        dfu_state->FileRxOffset.bytes[LSB_LO_BYTE] = buf[buf_pos++];
        dfu_state->FileRxOffset.bytes[LSB_HI_BYTE] = buf[buf_pos++];
        dfu_state->FileRxOffset.bytes[MSB_LO_BYTE] = buf[buf_pos++];
        dfu_state->FileRxOffset.bytes[MSB_HI_BYTE] = buf[buf_pos++];
        //---------------------------------------------------------------------------
        // If FileRxOffset==0 then FileSize sent
        //---------------------------------------------------------------------------
        if (!dfu_state->FileRxOffset.ulong){
          dfu_state->FileSize.bytes[LSB_LO_BYTE] = buf[buf_pos++];
          dfu_state->FileSize.bytes[LSB_HI_BYTE] = buf[buf_pos++];
          dfu_state->FileSize.bytes[MSB_LO_BYTE] = buf[buf_pos++];
          dfu_state->FileSize.bytes[MSB_HI_BYTE] = buf[buf_pos++];
        }
        data_len = (len-buf_pos);
        //---------------------------------------------------------------------------
        // Something to write to Flash 
        //---------------------------------------------------------------------------
        if (data_len){
          //-------------------------------------------------------------------------
          // Store image data 
          //-------------------------------------------------------------------------
          if (dfu_state->FileRingWrOffset==dfu_state->FileRxOffset.ulong){
            uint16_t num_put = ring_buf_put(&dfu_ring_buf,&buf[buf_pos],data_len);
            if (num_put!=data_len) {
              dfu_state->err = AMI_DFU_CENTRAL_ERR_RING_OVERFLOW;
              d_printf(LINE_INFO,kDbg_Error|kDbg_General,"RingPut:%u Rx:%u Off:%ld",num_put,data_len,dfu_state->FileRxOffset.ulong);
              return err;
            }else{
                  dfu_state->FileRingWrOffset += data_len;
            }
          }else{
                // This was a retry that the Central did not received an acknowledgemnet
          }
        }
        return err;
      } // ami_dfu_HandlePacket
      
      //-----------------------------------------------------------------------------
      // ami_dfu_HandleBlock
      //  Description
      //   Handles the incoming DFU image block
      //  Parameters
      //  Returns
      //  the offset to send back o the Central
      //-----------------------------------------------------------------------------
      uint32_t ami_dfu_HandleBlock(void)
      {
        static   uint8_t wr_buf[CONFIG_IMG_BLOCK_BUF_SIZE];
        bool     bLast      = false;
        //---------------------------------------------------------------------------
        // If there was a fatal error, just return the last error
        //---------------------------------------------------------------------------
        if (dfu_state->err){
          return dfu_state->err;
        }
        //---------------------------------------------------------------------------
        // All bytes were received?
        //---------------------------------------------------------------------------
        bool bRxFinished = (dfu_state->FileRingWrOffset>=dfu_state->FileSize.ulong);
        do{
            //-----------------------------------------------------------------------
            // Peek and see how many bytes accumulated exist
            //-----------------------------------------------------------------------
            uint32_t accum_bytes = ring_buf_size_get(&dfu_ring_buf);
            //-----------------------------------------------------------------------
            // Wait to write until we have CONFIG_IMG_BLOCK_BUF_SIZE bytes or last packet 
            //-----------------------------------------------------------------------
            if (accum_bytes<CONFIG_IMG_BLOCK_BUF_SIZE && !bRxFinished){
              break;
            }
            //-----------------------------------------------------------------------
            // Don't write more than CONFIG_IMG_BLOCK_BUF_SIZE bytes at a time
            //-----------------------------------------------------------------------
            if (accum_bytes>CONFIG_IMG_BLOCK_BUF_SIZE){
              accum_bytes = CONFIG_IMG_BLOCK_BUF_SIZE;
            }
            //-----------------------------------------------------------------------
            // Calculate the number of bytes left to write to flash
            //-----------------------------------------------------------------------
            uint32_t bytes_to_wr = (dfu_state->FileSize.ulong-dfu_state->FileWrOffset);
            //-----------------------------------------------------------------------
            // Last block to write?
            //-----------------------------------------------------------------------
            if (accum_bytes>=bytes_to_wr){
              accum_bytes = bytes_to_wr;
              bLast       = true;
            }
            //-----------------------------------------------------------------------
            // Get the bytes from the ring buffer
            //-----------------------------------------------------------------------
            uint32_t data_len   = ring_buf_get(&dfu_ring_buf,wr_buf,accum_bytes);
            //-----------------------------------------------------------------------
            // Nothing to write?
            //-----------------------------------------------------------------------
            if (!data_len){        
              break;
            }
            //-----------------------------------------------------------------------
            // Write the data to the flash
            //-----------------------------------------------------------------------
            int err = img_mgmt_impl_write_image_data(dfu_state->FileWrOffset,wr_buf,data_len,bLast);
            if (err!=0) {
              dfu_state->err = AMI_DFU_CENTRAL_ERR_WR_BLOCK;
              d_printf(LINE_INFO,kDbg_Error|kDbg_General,"ImageWrErr:%d",err);
              return dfu_state->err;
            }else{
                  //-----------------------------------------------------------------
                  // Update last offset written
                  //-----------------------------------------------------------------
                  dfu_state->FileWrOffset += data_len;
                  //-----------------------------------------------------------------
                  // All of the image was written?
                  //-----------------------------------------------------------------
                  if (dfu_state->FileWrOffset>=dfu_state->FileSize.ulong){
                    int image_slot = 1;
                    struct image_version ver;
                    uint8_t  hash;
                    uint32_t flags;
                    bool permanent = false;
                    
                    err = img_mgmt_read_info(image_slot,&ver,&hash,&flags);
                    if (err==0){
                      err = img_mgmt_impl_write_pending(image_slot,permanent);
                      if (err==0){
                        d_printf(LINE_INFO,kDbg_Error|kDbg_General,"FinishedDFU");
                      }else{
                            dfu_state->err = AMI_DFU_CENTRAL_ERR_SET_PENDING;                
                      }
                    }else{
                          dfu_state->err = AMI_DFU_CENTRAL_ERR_RD_INFO;
                          d_printf(LINE_INFO,kDbg_Error|kDbg_General,"ImageInfoErr:%d",err);
                    }
                    //---------------------------------------------------------------
                    // Reset after reply
                    //---------------------------------------------------------------
                    ami_state->reset_secs = 2;
                  }
            }
        }while (bRxFinished);
        return dfu_state->FileRxOffset.ulong;
      } // ami_dfu_HandleBlock
      
      //-----------------------------------------------------------------------------
      // ami_dfu_Start
      //  Description
      //   Inits the dfu state and sets the dfu streaming buffer
      //  Parameters
      //  Returns
      //   0 = OK
      //-----------------------------------------------------------------------------
      int ami_dfu_Start(void)
      {
        //---------------------------------------------------------------------------
        // Clear dfu state
        //---------------------------------------------------------------------------
        memset(dfu_state,0,sizeof(ami_dfu_state_struct));
        //---------------------------------------------------------------------------
        // Reset dfu ring buffer
        //---------------------------------------------------------------------------
        ring_buf_reset(&dfu_ring_buf);
        //---------------------------------------------------------------------------
        // Erase upload slot
        //---------------------------------------------------------------------------
        int err = img_mgmt_impl_erase_slot();
        if (err!=0){
          //-------------------------------------------------------------------------
          // Reset after reply
          //-------------------------------------------------------------------------
          ami_state->reset_secs = 2;
          dfu_state->err = AMI_DFU_CENTRAL_ERR_ERASE;
          d_printf(LINE_INFO,kDbg_Error|kDbg_General,"ImageEraseErr:%d", err);
          return err;
        }      
        //---------------------------------------------------------------------------
        // Init Flash stream writer (CONFIG_IMG_BLOCK_BUF_SIZE)
        //---------------------------------------------------------------------------
        g_img_mgmt_state.area_id = UPLOAD_FLASH_AREA_ID;
        err = flash_img_init(&flash_img);
        if (err!=0){
          d_printf(LINE_INFO,kDbg_Error|kDbg_General,"FlashInitErr:%d", err);
          dfu_state->err = AMI_DFU_CENTRAL_ERR_IMAGE_INIT;
          //-------------------------------------------------------------------------
          // Reset after reply
          //-------------------------------------------------------------------------
          ami_state->reset_secs = 2;
          return err;
        }
        g_img_mgmt_state.area_id = UPLOAD_FLASH_AREA_ID;
        return err;
      } // ami_dfu_Start
      
      //-----------------------------------------------------------------------------
      // ami_dfu_Init
      //  Description
      //   Alolocates memory
      //  Parameters
      //  Returns
      //   0 = OK
      //-----------------------------------------------------------------------------
      int ami_dfu_Init(void)
      {
        int err = 0;
      
        //---------------------------------------------------------------------------
        // Allocate memory
        //---------------------------------------------------------------------------
        dfu_state  = k_malloc(sizeof(ami_dfu_state_struct));
        while (!dfu_state);
        memset(dfu_state,0,sizeof(ami_dfu_state_struct));
        return err;
      } // ami_dfu_Init
      
      

      Thanks David

    Children
    • Hello David, 

      I would just like to point you to this solution I just completed: https://github.com/simon-iversen/ncs_samples/tree/master/central_smp_client_dfu 

      With this sample you will be able to perform a DFU from one nRF52840DK to another nRF52840DK

      This sample seems to address the exact issue this post is about.

      Let me know if you still want me to look into your last questions.

      Best regards,

      Simon

    • After trying to implement the nRF52840 BLE DFU without SMP & ZCBOR from my nRF52840  central device which worked 2/3 of the time, I am back to trying to get your code to work on my central.

      I am using Zephyr ncs v.2.2.0 and have made the two changes in the zcbor_decode.c file and have added the

      CONFIG_ZCBOR=y
      CONFIG_ZCBOR_STOP_ON_ERROR=y

      defines in both my central an my sensors.

      At first I was getting an error after the second packet where I got -122 which seems to be a too large of a packet. Maybe the sensor just disconnected.

      01-05 10:47 05/01/2023 08:47:49 |  05/01/2023 10:48:04[.ami_vibe.c:1148] #1 DFUpos:129000 len:1000
      01-05 10:47 05/01/2023 08:47:49 |  05/01/2023 10:48:04[.ami_vibe.c:1148] #1 DFUpos:130000 len:1000
      01-05 10:47 05/01/2023 08:47:49 |  05/01/2023 10:48:04[.ami_vibe.c:1962] ...[ami_ble.c:1703] StartScanConnect#1 FD:2C:12:F9:D6:03
      01-05 10:47 05/01/2023 08:47:52 |  05/01/2023 10:48:07[.ami_vibe.c:1962] ...[ami_ble.c:1733] DiscoveryStarted
      01-05 10:47 05/01/2023 08:47:57 |  05/01/2023 10:48:12[.ami_vibe.c:1962] ...[ami_ble.c: 609] MainHandlesOK
      01-05 10:48 05/01/2023 08:48:00 |  05/01/2023 10:48:15[.ami_vibe.c:1962] ...[ami_ble.c: 636] SetupHandlesOK
      01-05 10:48 05/01/2023 08:48:00 |  05/01/2023 10:48:15[.ami_vibe.c:1962] ...[ami_ble.c: 725] DISHandlesOK
      01-05 10:48 05/01/2023 08:48:01 |  05/01/2023 10:48:16[.ami_vibe.c:1962] ...[ami_ble.c: 670] TransferHandlesOK
      01-05 10:48 05/01/2023 08:48:02 |  05/01/2023 10:48:16[.ami_vibe.c:1962] ...[ami_ble.c: 739] SMPHandlesOK
      01-05 10:48 05/01/2023 08:48:12 |  05/01/2023 10:48:26[.ami_vibe.c:1962] ...[ami_smp.c: 671] DFU:0/373928
      01-05 10:48 05/01/2023 08:48:22 |  05/01/2023 10:48:36[.ami_vibe.c:1962] ...[ami_smp.c: 671] DFU:50/373928
      01-05 10:48 05/01/2023 08:48:22 |  05/01/2023 10:48:37[.ami_vibe.c:1962] ...[ami_smp.c: 780] bt_dfu_smp_command failed with -122
      01-05 10:48 05/01/2023 08:48:24 |  05/01/2023 10:48:38[.ami_vibe.c:1962] ...[ami_ble.c:1394] DisconnectTimeout
      01-05 10:48 05/01/2023 08:48:24 |  05/01/2023 10:48:39[.ami_vibe.c:1962] ...[ami_ble.c:2492] DFUConnectFinishedErr:5
      

      Now, in my latest code, I must have changed something, since the smp_upload_rsp_proc() is not being called but I do not get an error when calling bt_dfu_smp_command().

      01-05 15:43 05/01/2023 13:43:00 |  05/01/2023 15:43:14[.ami_vibe.c:1148] #1 DFUpos:130000 len:1000
      01-05 15:43 05/01/2023 13:43:00 |  05/01/2023 15:43:14[.ami_vibe.c:1941] ...[ami_ble.c:1703] StartScanConnect#1 FD:2C:12:F9:D6:03
      01-05 15:43 05/01/2023 13:43:03 |  05/01/2023 15:43:17[.ami_vibe.c:1941] ...[ami_ble.c:1733] DiscoveryStarted
      01-05 15:43 05/01/2023 13:43:08 |  05/01/2023 15:43:22[.ami_vibe.c:1941] ...[ami_ble.c: 609] MainHandlesOK
      01-05 15:43 05/01/2023 13:43:11 |  05/01/2023 15:43:25[.ami_vibe.c:1941] ...[ami_ble.c: 636] SetupHandlesOK
      01-05 15:43 05/01/2023 13:43:11 |  05/01/2023 15:43:26[.ami_vibe.c:1941] ...[ami_ble.c: 725] DISHandlesOK
      01-05 15:43 05/01/2023 13:43:12 |  05/01/2023 15:43:26[.ami_vibe.c:1941] ...[ami_ble.c: 670] TransferHandlesOK
      01-05 15:43 05/01/2023 13:43:12 |  05/01/2023 15:43:27[.ami_vibe.c:1941] ...[ami_ble.c: 739] SMPHandlesOK
      01-05 15:43 05/01/2023 13:43:13 |  05/01/2023 15:43:27[.ami_vibe.c:1941] ...[ami_smp.c: 659] DFU:0/373928
      

      Can you think of any reason that the callback is never called?

      Is there any reason that your code would not work with the zcbor_decode.c file changes on v2.2.0 ?

      I will try to continue next week and compare my code to see what has happened.

      I can DFU from my phone with the nRFConnect app on Android.

      Thanks David

    • This morning I found a few problems:

       a) I am still using v2.0.2 for this project but applied the patch correctly in zcbor_decode.c only in v2.2.0 which I am using for newer projects.

       b) I fixed a problem keeping my ring buffer full. I use a ring buffer with the image streamed from the host to the central instead of from the central's flash.

       c) My custom printf function was evidently causing a problem in smp_upload_rsp_proc() so instead of printing, I just update an error parameter

      I was able to finish uploading the send_upload2() function or at least it does not show any error.

      Now I understand that I need to call send_smp_test() to set the pending flag but it gives an error.

      I then saw that it uses the hash_value_secondary_slot variable as a parameter so I try to call send_smp_list() first so it will be updated.

      When I call send_smp_list() the central resets.

      I tried to call bt_disable() in send_smp_list() so I could single step to where the problem is, but this did not help as the ble crashes. How can I find the problem?

      My sensor boards have the standard nRF52840 image setup.

      So, I understand that I need to list,test & reset the sensor board?

      Thanks David

    • As happens all too often, I awoke in the middle of the night and thought that I could debug the smp_list_rsp_proc() function by disconnecting the sensor 's BLE at the start of the function.

      I found the reason for the smp_list_rsp_proc() function's problem in my case.

      I found that my sensors are returning what is called the version_key with a length of 7 bytes whereas there are only 5 allocated. I just changed this to 16 for now and no crashes after removing the sensor disconnect lines which I inserted for debugging.

      			//Decoding version key 
      			char version_key[5]; // my key is 7 ?!?
      			ok = zcbor_tstr_decode(zsd, &value);
      			if (!ok) {
      				printk("Decoding error, version key (err: %d)\n", zcbor_pop_error(zsd));
      				return;
      			}  /*else if (value.len != 6) {
      

      I am testing my code now, but I have a couple of new questions.

      1) I have a MTU of about 491 when uploading. I set the  UPLOAD_CHUNK to 400 in send_upload2().

      I would like to calculate the payload at runtime according to the current MTU.

      I am a little stuck on figuring out the whole payload size.

      send_upload2()
      ...
        while (!update_complete){
                 uint16_t mtu = bt_gatt_get_mtu(dfu_smp.conn);
                 uint16_t user_payload_len = ?!?
                   ...
      			(zse->payload - smp_cmd.payload);
                   ...			
                 if (user_payload_len>(mtu-header_size)){
                   user_payload_len = (mtu-header_size);
                 }
      

      2) My sensor code is currently quite large 365 KB (373,888 bytes).

      I saw a broken link that might help in the release version to safely cut its size.

       Best practice advice for Zephyr debug and release configurations. 

      I will get back on my DFU testing.

      Thank you David

    • Hi David,

      I will continue helping you with this case.

      There are two ways to mark an DFU image as "test".
      One is to use the SMP command.
      The other is to tag the image before you send it.

      For debugging purposes, can you try the second one as well?

      I'm thinking that if you upload a "pre-tagged" image to the SMP Server, it should automatically update on its next reboot.

      To sign an image manually you use imgtool.
      To mark it as "test" you use the "--pad" option.

      The command will look something like this:

      <NRF_CONNECT_PATH>/bootloader/mcuboot/scripts/imgtool.py sign --header-size 0x200 --align 4 --slot-size 0x79e00  --version 1.0.0 --pad-header --pad --key <SIGNING_KEY_PATH> build/zephyr/app_to_sign.bin app_manually_signed.bin

      To find the other variables used for this command (to generate app_update.bin), you can use "west -vvv build -p -b <BOARD_NAME>" and search the log for "imgtool".

      Does the image swap if you mark the image as "test" before uploading it?

      EDIT: Did not see your previous message before I sent this one.
      I will read through it now.

      Regards,
      Sigurd Hellesvik

    Related