Weird UC3 Reset behavior after user page NVRAM usage

0

I recently need to use in build NVRAM/EEPROM of AT32UC3L0256 to store some configuration data. I finally managed to use the user page NVRAM of the MCU (after days of trial and error and cursing on GCC ignoring noinit directives and fixing and workarounding bugs in ASF as usual) to something like this:

typedef struct
    {
    int writes;                 // write cycles counter
    int irc_pot;                // IRC_POT_IN position state
    } _cfg;
volatile static int *nvram_adr=(int*)(void*)0x80800000;     // user page NVRAM
volatile static _cfg    ram_cfg;                            // RAM copy of cfg

void cfg_load() // nvram_cfg -> ram_cfg
    {
    ram_cfg.writes =nvram_adr[8];
    ram_cfg.irc_pot=nvram_adr[9];
    }
void cfg_save() // nvram_cfg <- ram_cfg
    {
    int i;
    U32 buf[128];
    // blank
    for (i=0;i<128;i++) buf[i]=0xFFFFFFFF;
    // cfg
    buf[8]=ram_cfg.writes;
    buf[9]=ram_cfg.irc_pot;
    // Bootloader default cfg
    buf[126]=0x929E0B79;
    buf[127]=0xE11EFFD7;
    flashcdw_memcpy(nvram_adr   ,buf   ,256,true);  // write data -> nvram_cfg with erase
    flashcdw_memcpy(nvram_adr+64,buf+64,256,false); // write data -> nvram_cfg without erase (fucking ASF cant write more than 256Bytes at once but erases whole page !!!)
    }

I had to update flashcdw.c,flashcdw.h from ASF 3.48.0.98 in order to be able to write the full 512 Bytes as old ASF did program just up to 256 BYTES but erases whole page making a mess. I also had to store the full page (instead of just 8 bytes due to erase) and as usual due ASF bugs I needed to do it as is instead of just calling flashcdw_memcpy just once...

Its working now but I found out that some addresses are causing weird behavior. When 0xFF is not on some address the device will no longer RESET normally (but still until reset runs OK). On non Bootloader RESET it start the firmware code but after few [ms] it resets again and this goes on forever. To be clear the RESET occurs in this part of code (in my case):

for (U8 i=0;i<4;i++)
    {
    gpio_tgl_gpio_pin(_LED);
    wait_ms(200);
    }

its simple blink of LED after the system is configured (PLL CPU clock, configured timers and ISRs but interrupts still disabled). The LED blinks as should few times (PLL works on correct speed) but before loop is finished reset occurs. The wait is simple:

//------------------------------------------------------------------------------------------------
#define clk_cpu 50000000
#define RDTSC_mask 0x0FFFFFFF
void wait_ms(U32 dt)
    {
    U32 t0,t1;
    t0=Get_system_register(AVR32_COUNT);
    static const U32 ms=(clk_cpu+999)/1000;
    t0&=RDTSC_mask;
    for (;dt>0;)
        {
        t1=Get_system_register(AVR32_COUNT);
        t1&=RDTSC_mask;
        if (t0>t1)  t1+=RDTSC_mask+1;
        if ((t1-t0)>=ms)
            {
            dt--;
            t0+=ms;
            t0&=RDTSC_mask;
            continue;
            }
        }
    }
//------------------------------------------------------------------------------------------------

Even weirder is that if I boot into Bootloader and then reset normally again device is resetting properly and firmware works again (without any erasing/programing) however if I reset normally again the resetting loop occurs again ...

If I reprogram the .userpage NVRAM back to original state using BatchISP (flip) the chip works again normally.

So finally the questions:

  1. What addresses in userpage of NVRAM are causing this or should be reserved/avoided to change?

    I know the last 8 Bytes are Bootloader configuration. I suspect the problematic addresses are first 16 bytes. The .userpage should be for user data and does not contain fuses.

  2. What is happening?

    its some kind of watchdog or something? I thought those are inside fuses which are stored elsewhere. I do not see anything in datasheet.

Here hex of original .userpage:

:020000048080FA
:10000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
:10008000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80
:10009000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF70
:1000A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60
:1000B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF50
:1000C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40
:1000D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF30
:1000E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF20
:1000F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF10
:10010000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
:10011000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF
:10012000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF
:10013000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF
:10014000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF
:10015000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAF
:10016000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F
:10017000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8F
:10018000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
:10019000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6F
:1001A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5F
:1001B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4F
:1001C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F
:1001D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F
:1001E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1F
:1001F000FFFFFFFFFFFFFFFF929E0B79E11EFFD77E
:00000001FF

I use these (Flip commands) to obtain and restore it:

Batchisp -device AT32UC3L0256 -hardware RS232 -port COM1 -baudrate 115200 -operation memory user read savebuffer cfg_userpage.hex hex386 start reset 0
Batchisp -device AT32UC3L0256 -hardware RS232 -port COM1 -baudrate 115200 -operation onfail abort memory user loadbuffer cfg_userpage.hex program start reset 0

The Bootloader in question is USART version: 1.0.2 And the firmware with this behavior uses PLL,TC,GPIO,PWMA,ADC modules however reset occurs before any ISR and or ADC,PWMA,TC usage.

[Edit1] Watchdog

according to this the first word in .userpage of NVRAM is a fuse for the watchdog which explains the reset after few ms once repairing the data to original values and disabling the WDT the reseting stops. However now instead of booting program the Bootloader is started instead so there is still something fishy. The Bootloader pin selection is in last 8 Bytes

Also I looked into USART Bootloader ver: 1.0.2 source and found out they are using FLASHC instead of FLASHCDW and forcing boot with watchdog (which might reset its state and enable my program to run again somehow).

[Edit2] bug isolated

I finally found out that the problem is caused by writing to last 32bit word of the 512 Byte .userpage:

U32 btldr[2]={0x929E0B79,0xE11EFFD7};
flashcdw_memcpy(&nvram_adr[127],(U32*)&btldr[1]       ,4,false);

which is a huge problem as in order to store data correctly I must use erase which erases whole page no matter what and in order to still be able to boot correctly to Bootloader or My firmware I have to restore the Bootloader configuration data:

U32 btldr[2]={0x929E0B79,0xE11EFFD7};
flashcdw_memcpy(&nvram_adr[126],(U32*)&btldr[0]       ,4,false);
flashcdw_memcpy(&nvram_adr[127],(U32*)&btldr[1]       ,4,false);

I need to find a workaround how to restore the chip to functional state. Maybe duplicate the watchdog reset from Bootloader (but that would be very problematic and even risky in my application) as it restores the chip even without any flashing...

so map for now:

:020000048080FA
:10000000---WDT--FFFFFFFFFFFFFFFFFFFFFFFF00
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
:10008000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80
:10009000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF70
:1000A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF60
:1000B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF50
:1000C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF40
:1000D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF30
:1000E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF20
:1000F000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF10
:10010000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
:10011000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF
:10012000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDF
:10013000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCF
:10014000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBF
:10015000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFAF
:10016000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9F
:10017000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF8F
:10018000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F
:10019000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6F
:1001A000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5F
:1001B000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF4F
:1001C000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F
:1001D000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF2F
:1001E000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1F
:1001F000FFFFFFFFFFFFFFFF------BTLDR-----7E
:00000001FF

[Edit3] workaround

I managed to successfully write/read program memory FLASH (checked with BatchISP and MCU itself) it also boots OK.

Here code I test this with:

// **** test ****
volatile static U32 *flash_adr=(U32*)(void*)0x00000000;
const U32 buf[4]=
    {
    0xDEADBEEF,0x00112233,
    0xDEADBEEF,0x44556677,
    };
volatile static U32 *adr=(U32*)(void*)0x80030000;
flashcdw_memcpy(&adr[0],(U32*)buf,4*4,true ); // erase

flash_adr=(U32*)(void*)0x80000000;
for (U32 i=0;i<0x08000000;i++,flash_adr++)
    {
    if (flash_adr!=buf)
     if (flash_adr[0]==buf[0])
      if (flash_adr[1]==buf[1])
       if (flash_adr[2]==buf[2])
        if (flash_adr[3]==buf[3])
         { break; }
    if ((i&0xFFF)==0) gpio_tgl_gpio_pin(_LED);
    }

where flash_adr is the found address which contents matches the buf[] signature... (I print it on LCD so I see if it matches what I expect) and it finally does :). So I will use this instead of .userpage.

However the .userpage booting problem fix is still open question

c++
reset
bootloader
avr32
atmel-uc3
asked on Stack Overflow May 26, 2020 by Spektre • edited Jun 20, 2020 by Community

2 Answers

1

disable wdt early in main function

wdt_disable();

Also I think you not need to write the full page each time. flashc_memcpy takes bytes length to write while preserving other data unchanged.

unsigned char buf[AVR32_FLASHCDW_PAGE_SIZE];
void* flash_addr = AVR32_FLASHCDW_USER_PAGE;
memcpy(buf, flash_addr, 512);
// modify data in buf
flashcdw_memcpy(flash_addr, buf,AVR32_FLASHCDW_PAGE_SIZE,TRUE);

or just use

int mydata = 123;
int *nvmydata=AVR32_FLASHCDW_USER_PAGE + 16 // offset
flashcdw_memcyp(nvmydata,&mydata,sizeof(mydata),TRUE);

flashcdw_memcpy

 volatile void* flashcdw_memcpy(volatile void* dst, const void* src, size_t nbytes, Bool erase)
{
  // Use aggregated pointers to have several alignments available for a same address.
  UnionCVPtr flash_array_end;
  UnionVPtr dest;
  UnionCPtr source;
  StructCVPtr dest_end;
  UnionCVPtr flash_page_source_end;
  Bool incomplete_flash_page_end;
  Union64 flash_dword;
  Bool flash_dword_pending = FALSE;
  UnionVPtr tmp;
  unsigned int error_status = 0;
  unsigned int i, j;

  // Reformat arguments.
  flash_array_end.u8ptr = AVR32_FLASH + flashcdw_get_flash_size();
  dest.u8ptr = dst;
  source.u8ptr = src;
  dest_end.u8ptr = dest.u8ptr + nbytes;

  // If destination is outside flash, go to next flash page if any.
  if (dest.u8ptr < AVR32_FLASH)
  {
    source.u8ptr += AVR32_FLASH - dest.u8ptr;
    dest.u8ptr = AVR32_FLASH;
  }
  else if (flash_array_end.u8ptr <= dest.u8ptr && dest.u8ptr < AVR32_FLASHCDW_USER_PAGE)
  {
    source.u8ptr += AVR32_FLASHCDW_USER_PAGE - dest.u8ptr;
    dest.u8ptr = AVR32_FLASHCDW_USER_PAGE;
  }

  // If end of destination is outside flash, move it to the end of the previous flash page if any.
  if (dest_end.u8ptr > AVR32_FLASHCDW_USER_PAGE + AVR32_FLASHCDW_USER_PAGE_SIZE)
  {
    dest_end.u8ptr = AVR32_FLASHCDW_USER_PAGE + AVR32_FLASHCDW_USER_PAGE_SIZE;
  }
  else if (AVR32_FLASHCDW_USER_PAGE >= dest_end.u8ptr && dest_end.u8ptr > flash_array_end.u8ptr)
  {
    dest_end.u8ptr = flash_array_end.u8ptr;
  }

  // Align each end of destination pointer with its natural boundary.
  dest_end.u16ptr = (U16*)Align_down((U32)dest_end.u8ptr, sizeof(U16));
  dest_end.u32ptr = (U32*)Align_down((U32)dest_end.u16ptr, sizeof(U32));
  dest_end.u64ptr = (U64*)Align_down((U32)dest_end.u32ptr, sizeof(U64));

  // While end of destination is not reached...
  while (dest.u8ptr < dest_end.u8ptr)
  {
    // Clear the page buffer in order to prepare data for a flash page write.
    flashcdw_clear_page_buffer();
    error_status |= flashcdw_error_status;

    // Determine where the source data will end in the current flash page.
    flash_page_source_end.u64ptr =
      (U64*)min((U32)dest_end.u64ptr,
                 Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE) + AVR32_FLASHCDW_PAGE_SIZE);

    // Determine if the current destination page has an incomplete end.
    incomplete_flash_page_end = (Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE) >=
                                 Align_down((U32)dest_end.u8ptr, AVR32_FLASHCDW_PAGE_SIZE));

    // If destination does not point to the beginning of the current flash page...
    if (!Test_align((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE))
    {
      // Fill the beginning of the page buffer with the current flash page data.
      // This is required by the hardware, even if page erase is not requested,
      // in order to be able to write successfully to erased parts of flash
      // pages that have already been written to.
      for (tmp.u8ptr = (U8*)Align_down((U32)dest.u8ptr, AVR32_FLASHCDW_PAGE_SIZE);
           tmp.u64ptr < (U64*)Align_down((U32)dest.u8ptr, sizeof(U64));
           tmp.u64ptr++)
      {
        * tmp.u32ptr = *tmp.u32ptr;
        * (tmp.u32ptr + 1) = *(tmp.u32ptr + 1);
      }

      // If destination is not 64-bit aligned...
      if (!Test_align((U32)dest.u8ptr, sizeof(U64)))
      {
        // Fill the beginning of the flash double-word buffer with the current
        // flash page data.
        // This is required by the hardware, even if page erase is not
        // requested, in order to be able to write successfully to erased parts
        // of flash pages that have already been written to.
        for (i = 0; i < Get_align((U32)dest.u8ptr, sizeof(U64)); i++)
          flash_dword.u8[i] = *tmp.u8ptr++;

        // Fill the end of the flash double-word buffer with the source data.
        for (; i < sizeof(U64); i++)
          flash_dword.u8[i] = *source.u8ptr++;

        // Align the destination pointer with its 64-bit boundary.
        dest.u64ptr = (U64*)Align_down((U32)dest.u8ptr, sizeof(U64));

        // If the current destination double-word is not the last one...
        if (dest.u64ptr < dest_end.u64ptr)
        {
          // Write the flash double-word buffer to the page buffer.
            *dest.u32ptr++ = flash_dword.u32[0];
            *dest.u32ptr++ = flash_dword.u32[1];
        }
        // If the current destination double-word is the last one, the flash
        // double-word buffer must be kept for later.
        else flash_dword_pending = TRUE;
      }
    }

    // Read the source data with the maximal possible alignment and write it to
    // the page buffer with 64-bit alignment.
    switch (Get_align((U32)source.u8ptr, sizeof(U32)))
    {
    case 0:
      for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
      {
        *dest.u32ptr++ = *source.u32ptr++;
        *dest.u32ptr++ = *source.u32ptr++;
      }
      break;

    case sizeof(U16) :
      for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
      {
        for (j = 0; j < sizeof(U64) / sizeof(U16); j++) flash_dword.u16[j] = *source.u16ptr++;
* dest.u32ptr++ = flash_dword.u32[0];
* dest.u32ptr++ = flash_dword.u32[1];
      }
      break;

    default:
      for (i = flash_page_source_end.u64ptr - dest.u64ptr; i; i--)
      {
        for (j = 0; j < sizeof(U64); j++) flash_dword.u8[j] = *source.u8ptr++;
 dest.u32ptr++ = flash_dword.u32[0];
 dest.u32ptr++ = flash_dword.u32[1];
      }
    }

    // If the current destination page has an incomplete end...
    if (incomplete_flash_page_end)
    {
      // If the flash double-word buffer is in use, do not initialize it.
      if (flash_dword_pending) i = Get_align((U32)dest_end.u8ptr, sizeof(U64));
      // If the flash double-word buffer is free...
      else
      {
        // Fill the beginning of the flash double-word buffer with the source data.
        for (i = 0; i < Get_align((U32)dest_end.u8ptr, sizeof(U64)); i++)
          flash_dword.u8[i] = *source.u8ptr++;
      }

      // This is required by the hardware, even if page erase is not requested,
      // in order to be able to write successfully to erased parts of flash
      // pages that have already been written to.
      {
        tmp.u8ptr = (volatile U8*)dest_end.u8ptr;

        // If end of destination is not 64-bit aligned...
        if (!Test_align((U32)dest_end.u8ptr, sizeof(U64)))
        {
          // Fill the end of the flash double-word buffer with the current flash page data.
          for (; i < sizeof(U64); i++)
            flash_dword.u8[i] = *tmp.u8ptr++;

          // Write the flash double-word buffer to the page buffer.
* dest.u32ptr++ = flash_dword.u32[0];
* dest.u32ptr++ = flash_dword.u32[1];
        }

        // Fill the end of the page buffer with the current flash page data.
        for (; !Test_align((U32)tmp.u64ptr, AVR32_FLASHCDW_PAGE_SIZE); tmp.u64ptr++)
        {
 tmp.u32ptr = *tmp.u32ptr;
* (tmp.u32ptr + 1) = *(tmp.u32ptr + 1);
        }
      }
    }

    // If the current flash page is in the flash array...
    if (dest.u8ptr <= AVR32_FLASHCDW_USER_PAGE)
    {
      // Erase the current page if requested and write it from the page buffer.
      if (erase)
      {
        flashcdw_erase_page(-1, FALSE);
        error_status |= flashcdw_error_status;
      }
      flashcdw_write_page(-1);
      error_status |= flashcdw_error_status;

      // If the end of the flash array is reached, go to the User page.
      if (dest.u8ptr >= flash_array_end.u8ptr)
      {
        source.u8ptr += AVR32_FLASHCDW_USER_PAGE - dest.u8ptr;
        dest.u8ptr = AVR32_FLASHCDW_USER_PAGE;
      }
    }
    // If the current flash page is the User page...
    else
    {
      // Erase the User page if requested and write it from the page buffer.
      if (erase)
      {
        flashcdw_erase_user_page(FALSE);
        error_status |= flashcdw_error_status;
      }
      flashcdw_write_user_page();
      error_status |= flashcdw_error_status;
    }
  }

  // Update the FLASHC error status.
  flashcdw_error_status = error_status;

  // Return the initial destination pointer as the standard memcpy function does.
  return dst;
}
answered on Stack Overflow May 26, 2020 by Ahmed Anter • edited May 28, 2020 by Ahmed Anter
0

OK here is official resolution from Atmel/Microchip:

Proposed Resolution:
Looks like the DFU word being restored doesn't have the boot flag + CRC change. For the customer's application where userpage data is not constant, it's preferable to flash instead for non-volatile storage.

So my understanding its a HW bug (on the newer chips like AT32UC3L0256) and can not be work-around-ed ... other than using different non volatile memory like Flash for program (just like I ended up doing in the first place).

answered on Stack Overflow May 13, 2021 by Spektre

User contributions licensed under CC BY-SA 3.0