How to use flash for mega2560 update sketch (update user APP)

Dear all, I am doing a project on my filed experiments, with about 100 mega2560 used in the project. Each mega2560 control a tank (turn on/off heating, reading temperature and so on). The sketch (user APP) in the mega2560 is the same for all of them. Each mega2560 communicate with the server (a PC with self-written software by VB.net) by ESP32 S3. (Do not ask me why two chips used :smiley: )

Here is what I used: Serial port 3 of mega2560 was connected with Serial port 2 of ESP32. The pin 18 of ESP32 was connected to RESET pin of mega2560, then ESP32 can reset mega2560 if needed. Both of the Serial port 0 for mega2560 and ESP32 were retained for sketch downloading by USB cable.

What I am going to do is that: I will update user APP for mega2560 since the project is still going on, and wish to update all of them wireless (something like by OTA), not by a USB cable which I used now.

There was a idea in my mind which I think that I do not need to change anything physically, but not know how to make it become true.
First, I will update my code in Arduino IDE, and export hex file.

Second, may be I can read the hex file line by line, and send it to ESP32 by virtual serial port line by line. (It seems that hex file was consist with many lines begin with ":", so I think I can read it by the software developed by VB.net, and send it to ESP32 by wifi)

When the ESP32 got data from server, and know I am going to update the user APP (may be I can add some code before each line), the data will be send to mega2560 directly.

When mega2560 got data from ESP32, and know I am going to update the user APP, then the data will be write to flash. (How can I write a hex file in flash in mega2560?). Only can find examples for writing data, not a file.

When the whole hex file was transferred, I can use CRC to make they are write. The server can let ESP32 to reset the mega2560. Then mega2560 will find new sketch was loaded and updated it. I think here I need to modify the default BOOTLOADER of mega2560, right?

The bold sentences are my major questions, thanks in advance! Any help and comments are welcome, and if there is some codes are even more great, since I am not a coding boy, but just doing control experiments and like doing some coding :laughing:

Take a look:

The hex file is in Intel Hex Format.

just to brag

Thanks Juraj, a very impressive way. This can also use IDE as the update tool, WiFi just works as a "line".

But I should find each IP address, which might be 100, and might changed. Also, the ESP32 should connect to Rx0/Tx0, which is not connected at this moment. I need to change all the boards

esp32 doesn't have to be on RX0/TX0. You can use any transport way for the binary if you use the updater class (InternalStorage) directly. even with WiFiEspAT library as network support, the esp32 can be on any Serial

Dear Juraj, thanks for your answer. I just read your codes more deeply, I think that the branch SD2Flash2BootAVRHex might work fine to my project. If understand correctly, the HEX file could be read from SD or Serial, and then write to flash of MEGA2560 in my case by InternalStorage.write(b);. When everything is fine, the code can be updated.

Thanks again, I will try this maybe few days later, since a little busy with my manuscript :frowning:

great, thanks for your reply :grinning:

Thanks for your answer. Just checked, maybe Jural's suggestion will be easy for the current project :grinning:

1 Like

I recommend you to use a binary update file. Then you can skip the conversion from the hex file to binary on MCU.
https://github.com/JAndrassy/ArduinoOTA#atmega-support

Dear Juraj, just tried your package ArduinoOTA these days, not sure why there is something wrong on the InternalStorage.write(ihex->data[i]);. I think that the bootloader of Optiboot was upload correctly, but no idea how to test it :joy: It would be great if any comments from you. Here is my code:
What I have done is that:

  1. use Serial to receive the lines of HEX file, and deal it line by line; (not know how to read .bin file by pc and send it to mega2560 by Serial, maybe HEX file is also easy to handle by your package)
  2. copy the functions from kk_ihex_read.c file into the main .ino file, Then I can use Serial to output some messages.
  3. use mega2560 as the chip, the flash should be large enough?

Just checked the function in InternalStorageAVR.cpp, it seems that optiboot_page_erase does not work in my case. When it comes to the code, none response, and after a few seconds, without any response, the mega2560 will restart again.

/*
 This example reads HEX file from SD card and writes it
 to InternalStorage to store and apply it as update.

 To read Intel HEX file format ihex library
 by Kimmo Kulovesi is used as source files.
 https://github.com/arkku/ihex

 Created for ArduinoOTA library in May 2022
 by Juraj Andrassy
 */

#include <ArduinoOTA.h>
#include "kk_ihex_read.h"

#define IHEX_START ':'
#define ADDRESS_HIGH_MASK ((ihex_address_t)0xFFFF0000U)

enum ihex_read_state {
  READ_WAIT_FOR_START = 0,
  READ_COUNT_HIGH = 1,
  READ_COUNT_LOW,
  READ_ADDRESS_MSB_HIGH,
  READ_ADDRESS_MSB_LOW,
  READ_ADDRESS_LSB_HIGH,
  READ_ADDRESS_LSB_LOW,
  READ_RECORD_TYPE_HIGH,
  READ_RECORD_TYPE_LOW,
  READ_DATA_HIGH,
  READ_DATA_LOW
};

#define IHEX_READ_RECORD_TYPE_MASK 0x07
#define IHEX_READ_STATE_MASK 0x78
#define IHEX_READ_STATE_OFFSET 3

void ihex_begin_read(struct ihex_state *const ihex) {
  ihex->address = 0;
#ifndef IHEX_DISABLE_SEGMENTS
  ihex->segment = 0;
#endif
  ihex->flags = 0;
  ihex->line_length = 0;
  ihex->length = 0;
}

void ihex_read_at_address(struct ihex_state *const ihex, ihex_address_t address) {
  ihex_begin_read(ihex);
  ihex->address = address;
}

#ifndef IHEX_DISABLE_SEGMENTS
void ihex_read_at_segment(struct ihex_state *const ihex, ihex_segment_t segment) {
  ihex_begin_read(ihex);
  ihex->segment = segment;
}
#endif


int ihex2binError = 0;
int lineNumber = 0;
int writeHexStatus = 0;

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.println("I am here!");
}

void loop() {
  /*
  digitalWrite(LED_BUILTIN, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay(1000);                      // wait for a second
  digitalWrite(LED_BUILTIN, LOW);   // turn the LED off by making the voltage LOW
  delay(1000);                      // wait for a second
  */
}

void serialEvent() {
  ihex_state ihex;
  ihex_begin_read(&ihex);
  if (writeHexStatus == 0) {
    InternalStorage.open(0);  // InternalStorageAVR doesn't use the length parameter
    writeHexStatus = 1;
  }

  char buffer[64];
  memset(buffer, NULL, sizeof(buffer));
  int length = Serial.readBytesUntil('\n', buffer, sizeof(buffer));  // 读取每一行中 \n这个结束符的位置

  // 清空缓存
  while (Serial.read() >= 0) {}

  if (buffer[0] == IHEX_START) {
    lineNumber++;
    ihex_read_bytes(&ihex, buffer, length);  //
    delay(100);
  } else if (buffer[0] == 'E' & buffer[1] == 'N' & buffer[2] == 'D') {
    ihex_end_read(&ihex);
    switch (ihex2binError) {
      case 0:  // no error
        Serial.print("apply and reset...");
        Serial.flush();
        InternalStorage.apply();
        Serial.print("Ready");
        break;
      case 1:
        Serial.print("Checksum error on line ");
        Serial.print(lineNumber);
        break;
      case 2:
        Serial.print("Line length error on line  ");
        Serial.print(lineNumber);
        break;
      case 3:
        Serial.print("Not continues address on line ");
        Serial.print(lineNumber);
        break;
      case 4:
        Serial.print("Sketch is larger than half of the flash.");
        break;
    }

  } else {
    Serial.print("Not right code");
  }
}



void ihex_read_byte(struct ihex_state *const ihex, const char byte) {
  uint_fast8_t b = (uint_fast8_t)byte;
  uint_fast8_t len = ihex->length;
  uint_fast8_t state = (ihex->flags & IHEX_READ_STATE_MASK);

  ihex->flags ^= state;  // turn off the old state
  state >>= IHEX_READ_STATE_OFFSET;

  if (b >= '0' && b <= '9') {
    b -= '0';
  } else if (b >= 'A' && b <= 'F') {
    b -= 'A' - 10;
  } else if (b >= 'a' && b <= 'f') {
    b -= 'a' - 10;
  } else if (b == IHEX_START) {
    // sync to a new record at any state
    state = READ_COUNT_HIGH;
    goto end_read;
  } else {
    // ignore unknown characters (e.g., extra whitespace)
    goto save_read_state;
  }

  if (!(++state & 1)) {
    // high nybble, store temporarily at end of data:
    b <<= 4;
    ihex->data[len] = b;
  } else {
    // low nybble, combine with stored high nybble:
    b = (ihex->data[len] |= b);
    // We already know the lowest bit of `state`, dropping it may produce
    // smaller code, hence the `>> 1` in switch and its cases.
    switch (state >> 1) {
      default:
        // remain in initial state while waiting for :
        return;
      case (READ_COUNT_LOW >> 1):
        // data length
        ihex->line_length = b;

#if IHEX_LINE_MAX_LENGTH < 255
        if (b > IHEX_LINE_MAX_LENGTH) {
          ihex_end_read(ihex);
          return;
        }
#endif
        break;
      case (READ_ADDRESS_MSB_LOW >> 1):
        // high byte of 16-bit address
        ihex->address &= ADDRESS_HIGH_MASK;  // clear the 16-bit address
        ihex->address |= ((ihex_address_t)b) << 8U;
        break;

      case (READ_ADDRESS_LSB_LOW >> 1):
        // low byte of 16-bit address
        ihex->address |= (ihex_address_t)b;
        break;

      case (READ_RECORD_TYPE_LOW >> 1):
        // record type
        if (b & ~IHEX_READ_RECORD_TYPE_MASK) {
          // skip unknown record types silently
          return;
        }

        ihex->flags = (ihex->flags & ~IHEX_READ_RECORD_TYPE_MASK) | b;
        break;

      case (READ_DATA_LOW >> 1):
        if (len < ihex->line_length) {
          // data byte
          ihex->length = len + 1;
          state = READ_DATA_HIGH;
          goto save_read_state;
        }
        // end of line (last "data" byte is checksum)
        state = READ_WAIT_FOR_START;
end_read:
        ihex_end_read(ihex);
    }
  }
save_read_state:
  ihex->flags |= state << IHEX_READ_STATE_OFFSET;
}


// ihex_read_bytes(&ihex, buffer, length);
void ihex_read_bytes(struct ihex_state *restrict ihex, const char *restrict data, ihex_count_t count) {
  while (count > 0) {
    ihex_read_byte(ihex, *data++);
    --count;
  }
  //Serial.print("OK"); // tell the PC to send the next row
}

ihex_bool_t ihex_data_read(ihex_state *ihex, ihex_record_type_t type, ihex_bool_t checkSumError) {
  static uint32_t bytesWritten = 0;

  if (checkSumError) {
    ihex2binError = 1;
  } else if (ihex->length < ihex->line_length) {
    ihex2binError = 2;
  } else if (type == IHEX_DATA_RECORD) {

    if (ihex->address != bytesWritten) {
      ihex2binError = 3;
    } else if (ihex->address > InternalStorage.maxSize()) {
      ihex2binError = 4;
    } else {
      for (int i = 0; i < ihex->length; i++) {
        Serial.print(i);
        Serial.print("_");
        delay(10);  //delay for a few time
        InternalStorage.write(ihex->data[i]);
        delay(10);
        Serial.print(ihex->data[i]); // will not reach here!!!!
        bytesWritten++;
      }
      Serial.println("");
      Serial.println(bytesWritten);
    }
  } else if (type == IHEX_END_OF_FILE_RECORD) { 
    Serial.print(bytesWritten);
    Serial.print("OK");
    InternalStorage.close();
    writeHexStatus = 1;
  }
  return true;  // 
}


void ihex_end_read(struct ihex_state *const ihex) {
  uint_fast8_t type = ihex->flags & IHEX_READ_RECORD_TYPE_MASK;
  uint_fast8_t sum;  

  if ((sum = ihex->length) == 0 && type == IHEX_DATA_RECORD) {
    return;
  }
  {
    // compute and validate checksum
    const uint8_t *const eptr = ihex->data + sum;
    const uint8_t *r = ihex->data;
    sum += type + (ihex->address & 0xFFU) + ((ihex->address >> 8) & 0xFFU);
    while (r != eptr) {
      sum += *r++;
    }
    sum = (~sum + 1U) ^ *eptr;  // *eptr is the received checksum
  }

  delay(10);
  
  if (ihex_data_read(ihex, type, (uint8_t)sum)) {
    if (type == IHEX_EXTENDED_LINEAR_ADDRESS_RECORD) {
      ihex->address &= 0xFFFFU;
      ihex->address |= (((ihex_address_t)ihex->data[0]) << 24) | (((ihex_address_t)ihex->data[1]) << 16);

#ifndef IHEX_DISABLE_SEGMENTS
    } else if (type == IHEX_EXTENDED_SEGMENT_ADDRESS_RECORD) {
      ihex->segment = (ihex_segment_t)((ihex->data[0] << 8) | ihex->data[1]);
#endif
    }
  }
  ihex->length = 0;
  ihex->flags = 0;
}

can't you first try the original example with SD card? or basic ArduinoOTA examples with Ethernet or WiFi?

Thanks Juraj, I just find the problem with my code. And it should be the bootloader problem. I used your mega2560.hex and reinstall, then it works:)
Thank your for your contribution of the great ArduinoOTA :grin:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.