Firmwire over the air

Hello

I am using a SODAQ Sara 412 AFF board. The ublox chip can be updatedd over the air, using proper AT commands.

However, Id like to know, if there are ways to implemented my software, that runs on the SAMD21 chip, (takes gps updates and saves in an SD card) over the air as well

Thank you.

Hi , i think you can use the arduinoota library as written in this post:https://forum.arduino.cc/index.php?topic=664694.0
is in italian but there is a sample sketch that show how it work.it’s the same samd chip so it should work.
if it work it will be usefull an update because i’m doing the same with arduino hardware but i will switch to sodaq for industrial version of my concept

Thank you.
i have tried to use the SDU library.

But the SDU libray fails with unknown chip error.
So I tried to force

  #include "boot/mzero.h"

and

  #include "boot/zero.h"

Neither works - what can be done in this case?

So, here is the update.

I have tested and tested with all possible combinations in the SDU.cpp file.

I had success with MKR1010.h - but only 2 times out of 26 tests.

What can be done.

How does the Community do OTA Updates?

Hi

You can use the WiFi101OTA library with some modifications to implement OTA for SAMD21.

in order to do that, i need to use the SDU library.
The SDU library complains :

“error unsupported board”

I rebuild the boot/xxxxxx.h files, using the build.sh file under extras. Unfortunately, I still dont see any update. Forcing the SDU library to choose ARDUINO_SAMD_ZERO , for example does not work.

Here is my Sketch :

// define Battery Sensor
#define ADC_AREF 3.3f
#define BATVOLT_R1 4.7f
#define BATVOLT_R2 10.0f
#define BATVOLT_PIN BAT_VOLT


// ##############################################
// define outputs
// ----------------------------------------------
// define SDCARD
//#include "FS.h"
#include "SD.h"
#include <SPI.h>

#define SD_CS 10
#define SD_SCLK 13
#define SD_MISO 12
#define SD_MOSI 11

// define Server

// define EEPROM
#include "FlashStorage.h"
FlashStorage(wakeup, int);
int wakeUpCnt = 0;


// ----------------------------------------------


// ##############################################
// define bridge / conns
// ----------------------------------------------
// define LTE
#include <Arduino.h>
#include "Sodaq_R4X.h"
#include "Sodaq_wdt.h"

#define powerPin SARA_ENABLE
#define enablePin SARA_TX_ENABLE

static Sodaq_R4X r4x;
static Sodaq_SARA_R4XX_OnOff saraR4xxOnOff;

bool modemstat = false;
uint32_t getNow();
const int urat = 7;                                                    // LTE Cat 1 dual mode not possible
const int cid = 1 ;                                                   // cell id set to 1
const int mnoProfile = 31;                                                // Deutsche Telekom
const int bandmask = 524420;
const int bandmaskRATind = 0;
const char * FOperator = "20404";

const char* APN = "";
const char* gprsUser = "";
const char* gprsPass = "";

#define MODEMBAUD 115200

#define R4XX // Uncomment when you use the ublox R4XX module

/* SODAQ SARA */
#define DEBUG_STREAM SerialUSB
#define MODEM_STREAM Serial1
#define powerPin SARA_ENABLE
#define enablePin SARA_TX_ENABLE

#define NETWORK_STATUS_GPIO_ID     16



#define TINY_GSM_MODEM_SARAR4                                             // Modem is SARA R4
#define TINY_GSM_RX_BUFFER   2048                                           // Set RX buffer to 1Kb
#include <TinyGsmClient.h>

#define TINY_GSM_DEBUG
#define TINY_GSM_TEST_GPRS    true
#define TINY_GSM_TEST_GPS     true
#define TINY_GSM_POWERDOWN    true
TinyGsm modemGSM(MODEM_STREAM);

TinyGsmClient client(modemGSM);
int uploadedFlag = 0;


// define server
const char server[] = "78.47.186.254";                                          // domain name: example.com, maker.ifttt.com, etc
const char authURL[] = "/auth";                                             // resource path, for example: /post-data.php
const char sodaqUploadPath[] = "/upload";                                         // new function for sodaq upload, does not touch the other one
const int  port = 5504;


// define fallback
// ----------------------------------------------

#define ARDUINO_SAMD_MKR1000

#include "SDU.h"

#if defined(ARDUINO_SAMD_ZERO)
  #define BOARD "arduino_zero_edbg"
#elif defined(ARDUINO_SAMD_MKR1000)
  #define BOARD "mkr1000"
#elif defined(ARDUINO_SAMD_MKRZERO)
  #define BOARD "mkrzero"
#else
  #error "Unsupported board!"
#endif

#define BOARD_LENGTH (sizeof(BOARD) - 1)


const uint32_t RESPONSE_TIMEOUT_MS = 5000;
const int updatePort = 4402;

// ##############################################
// define controls


#include "LedColor.h"
int wakeupCause = 0;
int debuglevel = 0;
#define CONSOLE_STREAM SerialUSB

#define CONSOLE_BAUD 115200

static uint8_t lastResetCause;


#define PROJECT_NAME "SODAQ - GPS Tracker Webaro"
#define VERSION "1.8"
#define STARTUP_DELAY 5000

bool uploadAborted;

int wpCounter = 1;

/*
 * 
 *  debuglevel = 0, say nothing
 *  debuglevel = 1, draw
 *  debuglevel = 2, text
 */
// ----------------------------------------------
// define dev configs

String devID = "GPS320024";
String fileID = "";
String authKEY = "uaVZHB67r55BfgjkikhFUZ64489533g6311GRDI908740HDWWlaykdtw2446jdsg";




// ##############################################
// define GSM functions




// ----------------------------------------------


// ##############################################


// ----------------------------------------------


// ##############################################
// define SERVER-CLIENT operation functions



// ----------------------------------------------



// ##############################################
// define EEPROM functions


void activate_EEPROM() {
  
  wakeUpCnt = wakeup.read();
  ++wakeUpCnt ;                                                   SerialUSB.print("Wake up counter at: ");SerialUSB.println(wakeUpCnt);
  
  wakeup.write(wakeUpCnt);
  
  
}

// ----------------------------------------------


// ##############################################
// define SDC  functions


void init_SDC() {
  
  Sd2Card card;
  SdVolume volume;
  SdFile root;
  
  SerialUSB.print("\nInitializing SD card...");
  // we'll use the initialization code from the utility libraries
  // since we're just testing if the card is working!
  
  if (!card.init(SPI_HALF_SPEED, SD_CS)) {                                      SerialUSB.println("initialization failed. Things to check:");
    SerialUSB.println("* is a card inserted?");
    SerialUSB.println("* is your wiring correct?");
    SerialUSB.println("* did you change the chipSelect pin to match your shield or module?");
    
    while (1);
  } else {                                                      SerialUSB.println("Wiring is correct and a card is present.");
  }
  
  // print the type of card
  SerialUSB.println();
  SerialUSB.print("Card type:         ");
  
  switch (card.type()) {
    case SD_CARD_TYPE_SD1:                                              SerialUSB.println("SD1");
    break;
    case SD_CARD_TYPE_SD2:                                              SerialUSB.println("SD2");
    break;
    case SD_CARD_TYPE_SDHC:                                             SerialUSB.println("SDHC");
    break;
    default:                                                    SerialUSB.println("Unknown");
  }
  
  // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
  
  while (!volume.init(card)) {                                              SerialUSB.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
    
  }
  
  SerialUSB.print("Clusters:          ");
  SerialUSB.println(volume.clusterCount());
  SerialUSB.print("Blocks x Cluster:  ");
  SerialUSB.println(volume.blocksPerCluster());
  SerialUSB.print("Total Blocks:      ");
  SerialUSB.println(volume.blocksPerCluster() * volume.clusterCount());
  SerialUSB.println();
  
  // print the type and size of the first FAT-type volume
  
  uint32_t volumesize;                                                SerialUSB.print("Volume type is:    FAT");
  SerialUSB.println(volume.fatType(), DEC);
  
  volumesize = volume.blocksPerCluster();                                         // clusters are collections of blocks
  volumesize *= volume.clusterCount();                                            // we'll have a lot of clusters
  volumesize /= 2;                                                                // SD card blocks are always 512 bytes (2 blocks are 1KB)
  
  SerialUSB.print("Volume size (Kb):  ");
  SerialUSB.println(volumesize);
  
  
  
}


// ----------------------------------------------

// ##############################################
// define ACCEL  functions



bool initialize_wakeUp() { return true;}

// ----------------------------------------------


// ##############################################
// define SENSOR  functions



// ----------------------------------------------



// ##############################################
// define GPS  functions



// ----------------------------------------------


// ##############################################
// define timer  functions

long lastTickTime;
long sleepTime = 600;

// ----------------------------------------------


void setup() {
  
  CONSOLE_STREAM.begin(CONSOLE_BAUD);
  
  
  
  lastResetCause = PM->RCAUSE.reg;                                          debug2(String(lastResetCause));
  
  // In case of reset (this is probably unnecessary)
  sodaq_wdt_disable();
  
  // Setup the BOD33
  //setupBOD33();
  
  //sodaq_wdt_enable(WDT_PERIOD_8X);
  //sodaq_wdt_reset();
  
  
  
  sodaq_wdt_safe_delay(STARTUP_DELAY);
  printBootUpMessage(CONSOLE_STREAM);
  
  
  //Serial.begin(115200);                                               /** set serial output **/
  //                                                          CONSOLE_STREAM.println("waiting...");
  //while(!CONSOLE_STREAM.available());                                         // wait until get debug level
  debuglevel = 2;//CONSOLE_STREAM.parseInt();                                       // parse as integer
  CONSOLE_STREAM.println(debuglevel);
  
  // get_wakeupReason()                                               // already set via callbacks
  
  switch(wakeupCause) {                                               // branch wakeup reason
    
    case 0:                                                     // FIRST POWER UP
      activate_initialProtocol();
      break;
    case 1:                                                     // ACCELEROMETER trigger
      //activate_standardProtocol();
      break;
    case 2:                                                     // timer expired
      //activate_sporadicProtocol();
      break;
  }
  
  // shutdown_Peripherals();                                              // send unnecessary components to sleep
  // activate_sensors();
  
  
  lastTickTime = millis();
  
}


void activate_initialProtocol() {
  
 
  bool initialized_wakeUP = initialize_wakeUp();
  
  if (! initialized_wakeUP) return;
  
  setLedColor(YELLOW);
  sodaq_wdt_safe_delay(650);
  
  
  
  init_SDC();
  setLedColor(CYAN);
  sodaq_wdt_safe_delay(650);
  
  
  
  
  
  
  debug2("handling update ...");
  sodaq_wdt_safe_delay(5000);
  
}


void handleUpdate() {
  /*
  bool isValidContentType = false;
  
  const char* PATH = "http://78.47.186.254:4402/update.bin";
  const unsigned long CHECK_INTERVAL = 5000;
  
  
  
  
  if (!modemGSM.gprsConnect(APN, gprsUser, gprsPass)) {                             debug2(" fail apn");
  } else {
    if (!client.connect(server, updatePort))  {                               debug2("Cannot connect to " + String(server));
      return;
    }
  }
  
  client.print(String("GET ") + PATH + " HTTP/1.1\r\n");
  client.print(String("Host: ") + server + "\r\n");
  client.print("Connection: close\r\n\r\n");
  
  unsigned long timeout = millis();
  while (client.available() == 0) {
    if (millis() - timeout > RESPONSE_TIMEOUT_MS) {                             debug2("Client Timeout !");
      client.stop();
      return;
    }
  }
  
  if(!SD.begin(SD_CS)) {                                              debug2("SD FAILURE ::: ");
    return;
  }
  
  File file = SD.open(BIN_FILENAME, O_CREAT | O_WRITE);
  if (!file) {
    client.stop();                                              debug2("Could not create bin file. Can't continue with update.");
    return;
  }
  
  
  const uint32_t clientReadTimeout = 5000;
  uint32_t clientReadStartTime = millis();
  String headerBuffer;
  bool finishedHeader = false;
  uint32_t contentLength = 0;
  uint32_t knownFileSize = 1024; 
  uint32_t timeElapsed = millis();
  
  while (!finishedHeader) {
    int nlPos;
    
    if (client.available()) {
      clientReadStartTime = millis();
      while (client.available()) {
        char c = client.read();
        headerBuffer += c;
        
        // Uncomment the lines below to see the data coming into the buffer
        if (c < 16)
          //SerialUSB.print('0');
          //SerialUSB.print(c, HEX);
          //SerialUSB.print(' ');
          if (isprint(c))
            SerialUSB.print(reinterpret_cast<char> (c));
          else
            SerialUSB.print('*');
          SerialUSB.print(' ');
        
        // Let's exit and process if we find a new line
        if (headerBuffer.indexOf(F("\r\n")) >= 0)
          break;
      }
    }
    else {
      if (millis() - clientReadStartTime > clientReadTimeout) {
        // Time-out waiting for data from client
        SerialUSB.println(F(">>> Client Timeout !"));
        break;
      }
    }
    
    // See if we have a new line.
    nlPos = headerBuffer.indexOf(F("\r\n"));
    
    if (nlPos > 0) {
      headerBuffer.toLowerCase();
      // Check if line contains content-length
      if (headerBuffer.startsWith(F("content-length:"))) {
        contentLength = headerBuffer.substring(headerBuffer.indexOf(':') + 1).toInt();
        SerialUSB.print(F("Got Content Length: "));  // uncomment for
        SerialUSB.println(contentLength);            // confirmation
      }
      
      headerBuffer.remove(0, nlPos + 2);  // remove the line
    }
    else if (nlPos == 0) {
      // if the new line is empty (i.e. "\r\n" is at the beginning of the line), we are done with the header.
      finishedHeader = true;
    }
  }
  
  // The two cases which are not managed properly are as follows:
  // 1. The client doesn't provide data quickly enough to keep up with this loop.
  // 2. If the client data is segmented in the middle of the 'Content-Length: ' header,
  //    then that header may be missed/damaged.
  //
  
  uint32_t readLength = 0;
  
  
  if (finishedHeader && contentLength == knownFileSize) {
    SerialUSB.println(F("Reading response data"));
    clientReadStartTime = millis();
    
    printPercent(readLength, contentLength);
    while (readLength < contentLength && client.connected() && millis() - clientReadStartTime < clientReadTimeout) {
      while (client.available()) {
        uint8_t c = client.read();
        SerialUSB.print((c));  // Uncomment this to show data
        readLength++;
        if (readLength % (contentLength / 13) == 0) {
          printPercent(readLength, contentLength);
        }
        clientReadStartTime = millis();
      }
    }
    printPercent(readLength, contentLength);
  }
  
  timeElapsed = millis() - timeElapsed;
  SerialUSB.println();
  
  
  file.close();
  client.stop();
*/  
  
  #ifdef __AVR__
  wdt_enable(WDTO_15MS);
  while (true);
  #else
  NVIC_SystemReset();
  #endif
  
  
  
  //  if (contentLength && isValidContentType)  {
  // 
  //    
  //  }
  
}



void loop() {
  
  for(int i = 0; i < 10; i ++) { debug2(String(i) + "/10");
  setLedColor(GREEN);
  sodaq_wdt_safe_delay(1000);
  setLedColor(RED);
  sodaq_wdt_safe_delay(1000);
  }         debug2("handling update ");
  handleUpdate();
  
}








void setLONG_LedColor_(LedColor color) {
  
  setLedColor(color);
  sodaq_wdt_safe_delay(3000);
  
}

void debug2(String s) {
  if (debuglevel == 2){ CONSOLE_STREAM.println(s);}
}
void debug3(String f) {
  if (debuglevel == 3){ CONSOLE_STREAM.println(f);}
}


static void printBootUpMessage(Stream& stream) {
  stream.println("** " PROJECT_NAME " - " VERSION " **");
  
  stream.println();
  
  stream.print(" -> ");
  printCpuResetCause(stream);
  
  stream.println();
}


static void printCpuResetCause(Stream& stream){
  stream.print("CPU reset by");
  
  if (PM->RCAUSE.bit.SYST) {
    stream.print(" System reset request was performed, e.g. by rebooting....");
  }
  
  // Syntax error due to #define WDT in CMSIS/4.0.0-atmel/Device/ATMEL/samd21/include/samd21j18a.h
  // if (PM->RCAUSE.bit.WDT) {
  if ((PM->RCAUSE.reg & PM_RCAUSE_WDT) != 0) {
    stream.print(" Watchdog");
  }
  
  if (PM->RCAUSE.bit.EXT) {
    stream.print(" External - expect this for the accelerometer ");
  }
  
  if (PM->RCAUSE.bit.BOD33) {
    stream.print(" BOD33");
  }
  
  if (PM->RCAUSE.bit.BOD12) {
    stream.print(" BOD12");
  }
  
  if (PM->RCAUSE.bit.POR) {
    stream.print(" Power On Reset");
  }
  
  stream.print(" [");
  stream.print(PM->RCAUSE.reg);
  stream.println("]");
}




// OTHER FUNCTIONS

String split(String s, char parser, int index) {
  String rs = "";
  int parserIndex = index;
  int parserCnt = 0;
  int rFromIndex = 0, rToIndex = -1;
  while (index >= parserCnt) {
    rFromIndex = rToIndex + 1;
    rToIndex = s.indexOf(parser, rFromIndex);
    if (index == parserCnt) {
      if (rToIndex == 0 || rToIndex == -1) return "";
      return s.substring(rFromIndex, rToIndex);
    } else parserCnt++;
  }
  return rs;
}


inline String getHeaderValue(String header, String headerName)
{
  return header.substring(strlen(headerName.c_str()));
}

void printPercent(uint32_t readLength, uint32_t contentLength) {
  // If we know the total length
  if (contentLength != (uint32_t)-1) {
    SerialUSB.print("\r ");
    SerialUSB.print((100.0 * readLength) / contentLength);
    SerialUSB.print('%');
  } else {
    SerialUSB.println(readLength);
  }
}

The UPDATE.BIN file is in the SD card. The server communication is still in the sketch - but not implemented currently. The arduino compiler produced one file with bootloader, and one without. I tested both. The Chip select pin in the SDUBoot.ino has been updated.

But, the system does NOT pick up the new firmware. Please help.

1 Like

Hi,

If you want SODAQ to implement the OTA service for you, please contact sales@sodaq.com
We can do projects to implement OTA services. We will then also provide a dashboard to manage all your devices.

We cannot share the source code of our OTA implementation.

Best regards,
Jan

@Sean_Con have you found a working solution? I want to do the same thing. We need to focus on the SDU and how the bootloader can load new firmware from SD card. How to store new firmware to the SD card, is another issue. :laughing:

This is a working example, it writes the software to the internal flash memory. Change the APN, host and path. Works only for small files due to some buffer limits.

#define NO_OTA_PORT
#include <ArduinoOTA.h> // only for InternalStorage
#include <Sodaq_R4X.h>

#define CONSOLE_STREAM   SerialUSB
#define MODEM_STREAM     Serial1

#define CURRENT_APN       "" //Change this
#define CURRENT_OPERATOR AUTOMATIC_OPERATOR
#define CURRENT_URAT     SODAQ_R4X_LTEM_URAT
#define CURRENT_MNO_PROFILE MNOProfiles::STANDARD_EUROPE

//Location of new firmware
#define HTTP_HOST        "" //Change this
#define HTTP_PORT        80
#define HTTP_QUERY       "/fota/fw1.bin" //Change this

static Sodaq_R4X r4x;
static Sodaq_SARA_R4XX_OnOff saraR4xxOnOff;
static bool isReady;

#ifndef NBIOT_BANDMASK
#define NBIOT_BANDMASK BAND_MASK_UNCHANGED
#endif

void setup()
{
  while ((!CONSOLE_STREAM) && (millis() < 10000)) {
    // Wait max 10 sec for the CONSOLE_STREAM to open
  }

  CONSOLE_STREAM.begin(115200);
  MODEM_STREAM.begin(r4x.getDefaultBaudrate());

  r4x.setDiag(CONSOLE_STREAM);
  r4x.init(&saraR4xxOnOff, MODEM_STREAM);

  isReady = r4x.connect(CURRENT_APN, CURRENT_URAT, CURRENT_MNO_PROFILE, CURRENT_OPERATOR, BAND_MASK_UNCHANGED, NBIOT_BANDMASK);
  CONSOLE_STREAM.println(isReady ? "Network connected" : "Network connection failed");

  CONSOLE_STREAM.println("Setup done");

  if (isReady) {
    downloadFile();
  }
}

void loop()
{
  if (CONSOLE_STREAM.available()) {
    int i = CONSOLE_STREAM.read();
    CONSOLE_STREAM.write(i);
    MODEM_STREAM.write(i);
  }

  if (MODEM_STREAM.available()) {
    CONSOLE_STREAM.write(MODEM_STREAM.read());
  }
}

void downloadFile()
{
  char buffer[2048]; //Nice big buffer
  uint32_t messageLength = r4x.httpGet(HTTP_HOST, HTTP_PORT, HTTP_QUERY, buffer, sizeof(buffer));

  CONSOLE_STREAM.print("Read bytes: ");
  CONSOLE_STREAM.println(messageLength);
  //Check if there is enough space
  if (!InternalStorage.open(messageLength)) {
    CONSOLE_STREAM.println("There is not enough space to store the update. Can't continue with update.");
    return;
  }
  CONSOLE_STREAM.println("Size is ok, copying to internal storage");
  //Copy downloaded file to storage with small delay
  for (int i = 0; i <= messageLength; i++) {
    CONSOLE_STREAM.println(i);
    InternalStorage.write(buffer[i]);
    //delay(1);
  }
  CONSOLE_STREAM.println("Copy done");
  InternalStorage.close();
  CONSOLE_STREAM.println("Sketch update apply and reset.");
  CONSOLE_STREAM.flush();
  InternalStorage.apply();
}

@thijsfranssen thank you! I have found this code. I have an SD card reader attached over SPI that works perfectly with an FAT32 formatted SD card.

SDUBoot: SodaqCore-samd/SDUBoot.ino at v1.8.6-sodaq · SodaqMoja/SodaqCore-samd · GitHub

/*
  Copyright (c) 2017 Arduino LLC.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <SD.h>
#include <FlashStorage.h>

#ifdef ARDUINO_SAM_ZERO
#define SDU_START    0x4000
#else
#define SDU_START    0x2000
#endif
#define SDU_SIZE     0x4000

#define SKETCH_START (uint32_t*)(SDU_START + SDU_SIZE)

#ifndef SDCARD_SS_PIN
#define SDCARD_SS_PIN 4
#endif

#define UPDATE_FILE "UPDATE.BIN"

FlashClass flash;

// Initialize C library
extern "C" void __libc_init_array(void);

int main() {
  init();

  __libc_init_array();

  delay(1);

  if (SD.begin(SDCARD_SS_PIN) && SD.exists(UPDATE_FILE)) {
    File updateFile = SD.open(UPDATE_FILE);
    uint32_t updateSize = updateFile.size();
    bool updateFlashed = false;

    if (updateSize > SDU_SIZE) {
      // skip the SDU section
      updateFile.seek(SDU_SIZE);
      updateSize -= SDU_SIZE;

      uint32_t flashAddress = (uint32_t)SKETCH_START;

      // erase the pages
      flash.erase((void*)flashAddress, updateSize);

      uint8_t buffer[512];

      // write the pages
      for (uint32_t i = 0; i < updateSize; i += sizeof(buffer)) {
        updateFile.read(buffer, sizeof(buffer));

        flash.write((void*)flashAddress, buffer, sizeof(buffer));

        flashAddress += sizeof(buffer);
      }

      updateFlashed = true;
    }

    updateFile.close();

    if (updateFlashed) {
      SD.remove(UPDATE_FILE);
    }
  }

  // jump to the sketch
  __set_MSP(*SKETCH_START);

  //Reset vector table address
  SCB->VTOR = ((uint32_t)(SKETCH_START) & SCB_VTOR_TBLOFF_Msk);

  // address of Reset_Handler is written by the linker at the beginning of the .text section (see linker script)
  uint32_t resetHandlerAddress = (uint32_t) * (SKETCH_START + 1);
  // jump to reset handler
  asm("bx %0"::"r"(resetHandlerAddress));
}

So, this is my sketch with SDU using this bootloader: https://github.com/SodaqMoja/SodaqCore-samd/tree/v1.8.6-sodaq/libraries/SDU/src/boot/zero.h

/*
 Usage
 This example demonstrates how to use the SAMD SDU library to update a 
 sketch on an Arduino/Genuino Zero, MKRZero or MKR1000 board using an
 SD card. It prints out the date and time the sketch was compiled at 
 to both Serial and Serial1.

 Circuit:
 * Arduino MKRZero board with SD card
 OR
 * Arduino/Genuino Zero or MKR1000 board
 * SD shield or breakout connected with CS pin of 4
 * SD card

 Non-Arduino/Genuino Zero, MKRZero or MKR1000 board are NOT supported.

 Steps to update sketch via SD card:

 1) Upload this sketch or another sketch that includes the SDU library
    via #include <SDU.h>

 2) Update the sketch as desired. For this example the sketch prints out
    the compiled date and time so no updates are needed.

 3) In the IDE select: Sketch -> Export compiled Binary

 4) Copy the .bin file from the sketch's folder to the SD card and rename
    the file to UPDATE.bin. Eject the SD card from your PC.

 5) Insert the SD card into the board, shield or breakout and press the
    reset button or power cycle the board. The SDU library will then update
    the sketch on the board with the contents of UPDATE.bin

 created 23 March 2017
 by Sandeep Mistry
*/
#include <Arduino.h>

__attribute__ ((section(".sketch_boot")))
unsigned char sduBoot[0x4000] = {
  // 0x00, 0x80, ... content of libraries/SDU/src/boot/zero.h or if boot folder is in sketch folder use
  // #include "boot/zero.h"
};

String message;

void setup() {
  SerialUSB.begin(9600);
  delay(1000);

  message += "Sketch compile date and time: ";
  message += __DATE__;
  message += " ";
  message += __TIME__;

  SerialUSB.println(message);
}

void loop() {
  // ...
}

What I’m doing wrong? Should I store sdu.ino.sodaq_sara.bin (renamed to UPDATE.BIN) to the SD card or sdu.ino.with_bootloader.sodaq_sara.bin (renamed to UPDATE.BIN)?

@Jan any idea?

!!! SOLUTION !!!

I have compiled my own SDUBoot.ino with fixed SDU_START 0x2000 and for SODAQ Sara (boot/sodaq_sara.h). My sketch with #include "SDU.h" compiles. The board package is also installed in Arduino (http://downloads.sodaq.net/package_sodaq_samd_index.json). Here my code with changes (base is SodaqCore-samd-1.8.6-sodaq from Github):

File libraries/SDU/extras/SDUBoot/SDUBoot.ino:

#include <SPI.h>
#include <SD.h>
#include <FlashStorage.h>

#define SDU_START    0x2000
#define SDU_SIZE     0x4000

#define SKETCH_START (uint32_t*)(SDU_START + SDU_SIZE)

#ifndef SDCARD_SS_PIN
#define SDCARD_SS_PIN 10
#endif

#define UPDATE_FILE "UPDATE.BIN"

FlashClass flash;

// Initialize C library
extern "C" void __libc_init_array(void);

int main() {
  init();

  __libc_init_array();

  delay(1);

  if (SD.begin(SDCARD_SS_PIN) && SD.exists(UPDATE_FILE)) {
    File updateFile = SD.open(UPDATE_FILE);
    uint32_t updateSize = updateFile.size();
    bool updateFlashed = false;

    if (updateSize > SDU_SIZE) {
      // skip the SDU section
      updateFile.seek(SDU_SIZE);
      updateSize -= SDU_SIZE;

      uint32_t flashAddress = (uint32_t)SKETCH_START;

      // erase the pages
      flash.erase((void*)flashAddress, updateSize);

      uint8_t buffer[512];

      // write the pages
      for (uint32_t i = 0; i < updateSize; i += sizeof(buffer)) {
        updateFile.read(buffer, sizeof(buffer));

        flash.write((void*)flashAddress, buffer, sizeof(buffer));

        flashAddress += sizeof(buffer);
      }

      updateFlashed = true;
    }

    updateFile.close();

    if (updateFlashed) {
      SD.remove(UPDATE_FILE);
    }
  }

  // jump to the sketch
  __set_MSP(*SKETCH_START);

  //Reset vector table address
  SCB->VTOR = ((uint32_t)(SKETCH_START) & SCB_VTOR_TBLOFF_Msk);

  // address of Reset_Handler is written by the linker at the beginning of the .text section (see linker script)
  uint32_t resetHandlerAddress = (uint32_t) * (SKETCH_START + 1);
  // jump to reset handler
  asm("bx %0"::"r"(resetHandlerAddress));
}

File libraries/SDU/extras/SDUBoot/build.sh:

$ cd libraries/SDU/extras/SDUBoot
$ ./build.sh

#!/bin/sh -x

ARDUINO=arduino
SKETCH_NAME="SDUBoot.ino"
SKETCH="$PWD/$SKETCH_NAME"
BUILD_PATH="$PWD/build"
OUTPUT_PATH="../../src/boot"

if [[ "$OSTYPE" == "darwin"* ]]; then
	ARDUINO="/Applications/Arduino.app/Contents/MacOS/Arduino"
fi

buildSDUBootSketch() {
	BOARD=$1
	DESTINATION=$2

	$ARDUINO --verify --board $BOARD --preserve-temp-files --pref build.path="$BUILD_PATH" $SKETCH
	cat "$BUILD_PATH/$SKETCH_NAME.bin" | xxd -i > $DESTINATION
	rm -rf "$BUILD_PATH"
}

mkdir -p "$OUTPUT_PATH"

buildSDUBootSketch "SODAQ:samd:sodaq_sara" "$OUTPUT_PATH/sodaq_sara.h"

File libraries/SDU/src/SDU.cpp:

#include <Arduino.h>

#include "SDU.h"

__attribute__ ((section(".sketch_boot")))
unsigned char sduBoot[0x4000] = {
  #include "boot/sodaq_sara.h"
};

Sketch file sdu_test.ino:

#include <Arduino.h>
#include "SDU.h"

#if defined(ARDUINO_SODAQ_SARA)
#define DEBUG_STREAM SerialUSB
#else
#error "Please select the SODAQ SARA as your board"
#endif

#define DEBUG_STREAM_BAUD 115200

String message;

void setup() {
  // Start communication.
  DEBUG_STREAM.begin(DEBUG_STREAM_BAUD);

  while (!DEBUG_STREAM) {
    // Wait for Serial Monitor to be opened.
  }

  message += "Sketch compile date and time: ";
  message += __DATE__;
  message += " ";
  message += __TIME__;

  // print out the sketch compile date and time on the serial port
  DEBUG_STREAM.println(message);
}

void loop() {
  // add you own code here
}

File arduino/50/.arduino15/packages/SODAQ/hardware/samd/1.8.6/variants/sodaq_sara/linker_scripts/gcc/flash_with_bootloader.ld add these lines:

KEEP(*(.sketch_boot))

. = ALIGN(0x2000);

here:

...
SECTIONS
{
	PROVIDE(__sodaq_firmware_state = ORIGIN(FLASH) - 0x100);

	.text :
	{
		__text_start__ = .;

		KEEP(*(.sketch_boot))

		. = ALIGN(0x2000);
		KEEP(*(.isr_vector))
		...

On SD card (formatted as FAT32) I have stored the renamed file UPDATE.bin without bootloader of the compiled sketch file. Pin D10 is connected and used as chip select CS.

Build boot hex files:

$ cd libraries/SDU/extras/SDUBoot
$ ./build.sh

Version 1.8.6-sodaq has these missing lines on arduino_zero variant but not on sodaq_sara variant.

@Sean_Con see my solution above. Can you test the solution if it works for you?