1

I am having problems while writing custom bootloader, so that it uploads code from EEPROM (for now internal, as I have no external memory in my hands) and writes into flash. After ~2 weeks of struggling I encountered these question and answers. After that, I realized that I made a mistake not checking the possibility of it. However, I am still confused about several things:

  1. If Harvard Architecture limits what I want, how come optiboot can flash the memory from incoming UART data, which is not even a memory?

  2. If UART reads and flashes memory accordingly, cant I do the same with EEPROM? Read bytes, erase flash, write bytes.

  3. If it is not possible, given two answers in above link, then how does this work? I guess, it is the same logic as I want, using external memory and rewriting flash. Does it mean Harvard Architecture limits only internal memory exchange?

UPDATE: Here is what I have done for now:

In separate sketch, EEPROM is filled with binary code. Bootloader (edited optiboot v8):

  // Set up watchdog to trigger after desired timeout
  watchdogConfig(WDTPERIOD);
#if BIGBOOT

if ((eeprom_read(0x000) == 0x0C) && (eeprom_read(0x001) == 0x94)) { //while (EECR & (1 << EEPE)); //wait if previous data is still being written uint8_t i, j;

for (i = 0; i &lt; 8; ++i) // 8 pages total
{
  uint8_t *code;
  code = buff.bptr;
  address.word = (i * 128);

  for (j = 0; j &lt; 128; ++j)  // 128 words at a time
  {     
    if (j % 64 == 0)     
      LED_PORT ^= _BV(LED);

    *code++ = eeprom_read(address.word + j);
  }

#ifdef VIRTUAL_BOOT_PARTITION virtualBootPartition(buff, address); #endif

  writebuffer(0x46, buff, address, 128);
}

while(1); // wait for WDT

} #endif

#if (LED_START_FLASHES > 0) || LED_DATA_FLASH || LED_START_ON /* Set LED pin as output */ LED_DDR |= _BV(LED); #endif

while my function looks like this:

#if BIGBOOT 
static uint8_t eeprom_read(uint16_t addr)
{
  EEAR = addr; //read from that address
  EECR |= (1 << EERE); //enable reading

return EEDR; } #endif

I use make atmega328 BIGBOOT=1 to generate hex file. It does not work + UART uploading also fails. If I set BIGBOOT to 0 and generate, UART uploading works fine.

P.S. virtualBootPartition is function which is called in STK_PROG_PAGE case. In order to not repeat the same piece of code, I collected that part into the function.

#ifdef VIRTUAL_BOOT_PARTITION
/*
 *              How the Virtual Boot Partition works:
 * At the beginning of a normal AVR program are a set of vectors that
 * implement the interrupt mechanism.  Each vector is usually a single
 * instruction that dispatches to the appropriate ISR.
 * The instruction is normally an rjmp (on AVRs with 8k or less of flash)
 * or jmp instruction, and the 0th vector is executed on reset and jumps
 * to the start of the user program:
 * vectors: jmp startup
 *          jmp ISR1
 *          jmp ISR2
 *             :      ;; etc
 *          jmp lastvector
 * To implement the "Virtual Boot Partition", Optiboot detects when the
 * flash page containing the vectors is being programmed, and replaces the
 * startup vector with a jump to te beginning of Optiboot.  Then it saves
 * the applications's startup vector in another (must be unused by the
 * application), and finally programs the page with the changed vectors.
 * Thereafter, on reset, the vector will dispatch to the beginning of
 * Optiboot.  When Optiboot decides that it will run the user application,
 * it fetches the saved start address from the unused vector, and jumps
 * there.
 * The logic is dependent on size of flash, and whether the reset vector is
 * on the same flash page as the saved start address.
 */

#if FLASHEND > 8192 /*

  • AVR with 4-byte ISR Vectors and "jmp"
  • WARNING: this works only up to 128KB flash!

/ #if FLASHEND > (1281024) #error "Can't use VIRTUAL_BOOT_PARTITION with more than 128k of Flash" #endif if (address.word == RSTVEC_ADDRESS) { // This is the reset vector page. We need to live-patch the // code so the bootloader runs first. // // Save jmp targets (for "Verify") rstVect0_sav = buff.bptr[rstVect0]; rstVect1_sav = buff.bptr[rstVect1];

    // Add &quot;jump to Optiboot&quot; at RESET vector
    // WARNING: this works as long as 'main' is in first section
buff.bptr[rstVect0] = ((uint16_t)pre_main) &amp; 0xFF;
buff.bptr[rstVect1] = ((uint16_t)pre_main) &gt;&gt; 8;

#if (SAVVEC_ADDRESS != RSTVEC_ADDRESS) // the save_vector is not necessarilly on the same flash page as the reset // vector. If it isn't, we've waiting to actually write it. } else if (address.word == SAVVEC_ADDRESS) { // Save old values for Verify saveVect0_sav = buff.bptr[saveVect0 - SAVVEC_ADDRESS]; saveVect1_sav = buff.bptr[saveVect1 - SAVVEC_ADDRESS];

  // Move RESET jmp target to 'save' vector
  buff.bptr[saveVect0 - SAVVEC_ADDRESS] = rstVect0_sav;
  buff.bptr[saveVect1 - SAVVEC_ADDRESS] = rstVect1_sav;

} #else // Save old values for Verify saveVect0_sav = buff.bptr[saveVect0]; saveVect1_sav = buff.bptr[saveVect1];

    // Move RESET jmp target to 'save' vector
    buff.bptr[saveVect0] = rstVect0_sav;
    buff.bptr[saveVect1] = rstVect1_sav;

} #endif

#else /*

  • AVR with 2-byte ISR Vectors and rjmp

*/ if (address.word == rstVect0) { // This is the reset vector page. We need to live-patch // the code so the bootloader runs first. // // Move RESET vector to 'save' vector // Save jmp targets (for "Verify") rstVect0_sav = buff.bptr[rstVect0]; rstVect1_sav = buff.bptr[rstVect1]; addr16_t vect; vect.word = ((uint16_t)pre_main-1); // Instruction is a relative jump (rjmp), so recalculate. // an RJMP instruction is 0b1100xxxx xxxxxxxx, so we should be able to // do math on the offsets without masking it off first. // Note that rjmp is relative to the already incremented PC, so the // offset is one less than you might expect. buff.bptr[0] = vect.bytes[0]; // rjmp to start of bootloader buff.bptr[1] = vect.bytes[1] | 0xC0; // make an "rjmp" #if (SAVVEC_ADDRESS != RSTVEC_ADDRESS) } else if (address.word == SAVVEC_ADDRESS) { addr16_t vect; vect.bytes[0] = rstVect0_sav; vect.bytes[1] = rstVect1_sav; // Save old values for Verify saveVect0_sav = buff.bptr[saveVect0 - SAVVEC_ADDRESS]; saveVect1_sav = buff.bptr[saveVect1 - SAVVEC_ADDRESS];

vect.word = (vect.word-save_vect_num); //substract 'save' interrupt position

// Move RESET jmp target to 'save' vector buff.bptr[saveVect0 - SAVVEC_ADDRESS] = vect.bytes[0]; buff.bptr[saveVect1 - SAVVEC_ADDRESS] = (vect.bytes[1] & 0x0F)| 0xC0; // make an "rjmp" } #else

// Save old values for Verify saveVect0_sav = buff.bptr[saveVect0]; saveVect1_sav = buff.bptr[saveVect1];

vect.bytes[0] = rstVect0_sav; vect.bytes[1] = rstVect1_sav; vect.word = (vect.word-save_vect_num); //substract 'save' interrupt position // Move RESET jmp target to 'save' vector buff.bptr[saveVect0] = vect.bytes[0]; buff.bptr[saveVect1] = (vect.bytes[1] & 0x0F)| 0xC0; // make an "rjmp" // Add rjmp to bootloader at RESET vector vect.word = ((uint16_t)pre_main-1); // (main) is always <= 0x0FFF; no masking needed. buff.bptr[0] = vect.bytes[0]; // rjmp 0x1c00 instruction }

#endif #endif // FLASHEND #endif // VBP

  • You can only run code from flash memory. Only the bootloader can write to flash memory. When the bootloader "exits", it will run the code on the "regular" section of flash memory. The bootloader can get the code from input it want, UART, IR, I2C, SPI. It just has to implement it, and probably some protocol on top of it, so it knows when program data start and stops. It could read from EEPROM, but that would mean your code can only be as large as the EEPROM, which is 1k, if I remember correctly, for the 328, while flash memory is 32k (minus the size of your bootloader). – Gerben Oct 19 '20 at 14:26
  • 2
    I found this bootloaders: https://github.com/mysensors/DualOptiboot. It get it's program from SPI-EEPROM (where the main program downloads it from somewhere and puts it in EEPROM. Then, after a "reboot", the bootloader transfers it from EEPROM to Flash memory). – Gerben Oct 19 '20 at 14:29
  • Thanks for the answer. I have thoroughly analyzed serial communication between PC and Arduino upon uploading. The code size for atmega328p (I dont know for other MCUs) is exactly 1K. I wrote EEPROM in another sketch for once, which is binary file. Then, I edited bootloader, so that, if EEPROM has anything, read and upload it, as it does when receiving UART sequence. But, unfortunately it was unsuccessful. I guess, external memory will solve this problem. – Miradil Zeynalli Oct 19 '20 at 15:13
  • you can store that 1 kB in flash too. see the do_spm function in Optiboot 7 https://github.com/Optiboot/optiboot/blob/master/optiboot/examples/demo_dospm/demo_dospm.ino – Juraj Oct 19 '20 at 17:37
  • I sound like your on the right track. Not sure why it was unsuccessful. Though your emphasis on 'serial communication' and 'upload' makes me a bit confused. I don't see how it would all of a sudden work with external EEPROM. – Gerben Oct 19 '20 at 19:40
  • @Juraj I don't think that works if you want to rewrite the entire sketch itself. But maybe it can work for OP. – Gerben Oct 19 '20 at 19:43
  • 2
    @Gerben, to rewrite the sketch there is my copy_flash_pages function in bootloader to bootload from upper half of the flash. https://github.com/jandrassy/ArduinoOTA/blob/master/src/InternalStorageAVR.cpp https://github.com/Optiboot/optiboot/pull/269 – Juraj Oct 20 '20 at 04:55
  • What you're trying to do may require editing the linker script (not even sure where that's located for Arduino at the moment). – Gabriel Staples Oct 20 '20 at 07:33
  • Looks like the linker script files are located in, for example: arduino-1.8.13/hardware/tools/avr/avr/lib/ldscripts/avr1.x. I'm grasping at straws here though--not sure how to tell which one is for the ATmega328. Anyone know? Also, here's the optiboot code. It's worth studying I think to see exactly how it works, to try and see how it works, what its limitations are, and go from there: https://github.com/arduino/ArduinoCore-avr/blob/master/bootloaders/optiboot/optiboot.c. It will probably have clues to track down in datasheets and Atmel/Microchip white papers too. – Gabriel Staples Oct 20 '20 at 07:43
  • 1
    I edited my post and include more explaination and pieces of code. – Miradil Zeynalli Oct 20 '20 at 08:55
  • @GabrielStaples why would I need linker script? – Miradil Zeynalli Oct 20 '20 at 08:58
  • 1
    to use BIGBOOT with 328p, you must change the fuses https://github.com/jandrassy/my_boards/blob/14f363387bbe70bfe66e1b6c2b7a398338d92533/avr/boards.txt#L89 while burning the bootloader – Juraj Oct 20 '20 at 11:17
  • 1
    And I was thinking, why BIGBOOT without my code is not working). Thanks! – Miradil Zeynalli Oct 20 '20 at 11:47
  • read my answer below for more know-how – Juraj Oct 20 '20 at 19:02

1 Answers1

1

If you have a sketch which can write its updated version into EEPROM of ATmega328p and then boootload from the EEPROM, then you can use the flash for the same purpose. The ATmega328p has 32kB of flash and only 1kB EEPROM.

For my ArduinoOTA library I developed a way to store the uploaded binary in upper half of the flash and then let the bootloader copy the binary to address 0 and reset the MCU.

The Optiboot 8 has do_spm() function. This function can be called from the sketch to write to flash memory.

To copy the binary stored in upper half of the flash to address 0, I wrote a new function for the Optiboot. It is called copy_flash_pages and has an extra boolean parameter to request a reset of MCU after copying the pages.

For network upload the sketch size is larger then half of the flash of 328p because of the networking library. So ArduinoOTA is only for ATmega with more then 64kB flash.

If you have a way how to get the binary of size less then half of the 328p flash, then you can write it to the upper half of the flash and then use my copy_flash_pages function to bootload it.

Resources:

  • header file to access the Optiboot functions from sketch
  • boards definition and bootloader for 328p with Optiboot with copy_flash_pages function in my_boards (note the changed fuses in boards.txt for Uno/Nano/Mini)
  • the InternalStorageAVR class in ArduinoOTA library as example how to store and apply the binary

Example of InternalStorage use:

  if (!InternalStorage.open(length)) {
    Serial.println("There is not enough space to store the update. Can't continue with update.");
    return;
  }
  byte b;
  while (length > 0) {
    if (!source.readBytes(&b, 1)) // reading a byte with timeout
      break;
    InternalStorage.write(b);
    length--;
  }
  InternalStorage.close();
  if (length > 0) {
    Serial.print("Timeout downloading update file at ");
    Serial.print(length);
    Serial.println(" bytes. Can't continue with update.");
    return;
  }
  Serial.println("Sketch update apply and reset.");
  InternalStorage.apply(); // this doesn't return
Juraj
  • 18,037
  • 4
  • 29
  • 49
  • This almost solves my problem. However, I did not understand this: "If you have a way how to get the binary of size less then half of the 328p flash, then you can write it to the upper half of the flash and then use my copy_flash_pages function to bootload it.". ATmega328p has 32k flash and binary size is 1k. – Miradil Zeynalli Oct 21 '20 at 08:04
  • @MiradilZeynalli, to make the next upload the uploaded code must support the upload. can you make this code so small? – Juraj Oct 21 '20 at 08:13
  • Moreover, I mostly would like to solve this with bootloader as my question implies. Because, having binary in flash + program itself + bootloader can have limitations for bigger programs (without using your network library). In my case, I would like to use bootloader max of 1k (I am even planning to delete STK500 support, so I can reduce it to 0.5k). – Miradil Zeynalli Oct 21 '20 at 08:13
  • then why do you want to go over EEPROM? the bootloader can write directly to flash. I don't understand the process you try to achieve. how is the binary transferred? – Juraj Oct 21 '20 at 08:16
  • I just have to test it, to be honest :). But, I think, from your experience, you have failed to do so. Then, please correct me if I got you wrong. Even if I have already array of values (let's say I got it from bluetooth) of binary file, I can flash memory and upload code with your InternalStorageAVR class and its functions – Miradil Zeynalli Oct 21 '20 at 08:17
  • 1
    yes. similar to this example https://github.com/jandrassy/ArduinoOTA/blob/master/examples/Advanced/OTASketchDownload/OTASketchDownload.ino – Juraj Oct 21 '20 at 08:18
  • I am using internal EEPROM for now, as I don't have external one right now. What I want to achieve, is to read from eeprom and rewrite flash DURING bootloader. In application code, read binary - store binary to EEPROM - reset yourself. – Miradil Zeynalli Oct 21 '20 at 08:20
  • I analyzed your code and as you said, it is good for small programs or larger flash MCUs. If I understand your code correctly, you are using upper half of flash as temporary storage for code and then copy it to actual program section, which is why, you need your code to be less than the size of the flash. Then my question is: is it possible to store binary not in flash but in EEPROM (or in other external memory device) and then copy it to flash using your functions? – Miradil Zeynalli Oct 21 '20 at 09:38
  • the code to write to EEPROM is simple. then in bootloader you check if EEPROM contains a new binary and copy it to flash. and as I understand you have the code, but the new binary doesn't start? – Juraj Oct 21 '20 at 10:03
  • the problem with external device is that you must code the communication over SPI or similar and it makes the code larger – Juraj Oct 21 '20 at 10:04
  • Yes, I am trying to read from eeprom during bootloader and write to flash (as optiboot does). But, it seems to fail. I have included all the needed code pieces in the question. – Miradil Zeynalli Oct 21 '20 at 11:09
  • 1
    Everything was working correctly, I was just testing it wrong. As you said, with your library I can store heavier code than EEPROM. I will still use external EEPROM, but still your answer is correct, so I marked it (despite the fact that it has been 6 months) – Miradil Zeynalli Apr 21 '21 at 13:11