microcontrollerbootloaderatmegamicrochip

ATMEGA4809 UART bootloader block gets corrupted randomly


I'm working on a simple UART bootloader based off the example by Microchip here:text

It works as intended, except randomly the device just won't boot up...it becomes a brick - it doesn't boot into the bootloader or the app. We need to reprogram it over the UPDI interface again to get it working again.

We're using a FT232 USB-UART to interface with the ATMEGA.

This issue happens without any particular pattern, but always when the device USB cable has been rebooted

I've read back the program memory after this issue happens, and there is a section of bytes that gets overwritten from address 0x00E0 to 0x00FF - like if a page was erased and written. But how can that happen without me issuing a write command?

Here is the code:

#define F_CPU_RESET (20E6/6)

#include <avr/io.h>
#include <assert.h>
#include <stdbool.h>

/* Baud rate configuration */
#define BOOT_BAUD (115200)

/* Memory configuration
 * BOOTEND_FUSE * 256 must be above Bootloader Program Memory Usage,
 * this is 194 bytes at optimization level -O3, so BOOTEND_FUSE = 0x01
 */
#define BOOTEND_FUSE               (0x02)
#define BOOT_SIZE                  (BOOTEND_FUSE * 0x100)
#define MAPPED_APPLICATION_START   (MAPPED_PROGMEM_START + BOOT_SIZE)
#define MAPPED_APPLICATION_SIZE    (MAPPED_PROGMEM_SIZE - BOOT_SIZE)

/* Fuse configuration
 * BOOTEND sets the size (end) of the boot section in blocks of 256 bytes.
 * APPEND = 0x00 defines the section from BOOTEND*256 to end of Flash as application code.
 * Remaining fuses have default configuration.
 */
FUSES = {
    .OSCCFG = FREQSEL_20MHZ_gc,
    .SYSCFG0 = 0xC8,
    .SYSCFG1 = SUT_0MS_gc,
    .APPEND = 0x00,
    .BOOTEND = BOOTEND_FUSE
};

/* Define application pointer type */
typedef void (*const app_t)(void);

//uint8_t EEMEM user_byte = 0;

/* Interface function prototypes */
static bool is_bootloader_requested(void);
static void init_uart(void);
static uint8_t uart_receive(void);
static void uart_send(uint8_t byte);
static void init_status_led(void);
static void toggle_status_led(void);
static void initPins(void);
static inline void wait_50_ms(void);

/*
 * Main boot function
 * Put in the constructors section (.ctors) to save Flash.
 * Naked attribute used since function prologue and epilogue is unused
 */
__attribute__((naked)) __attribute__((section(".ctors"))) void boot(void)
{       
    /* Initialize system for AVR GCC support, expects r1 = 0 */
    asm volatile("clr r1");
       
    initPins();
    //this is a lot more than 50ms, but that's ok
    wait_50_ms();

    /* Check if entering application or continuing to bootloader */
    if(!is_bootloader_requested()) {
        /* Enable Boot Section Lock */
        NVMCTRL.CTRLB = NVMCTRL_BOOTLOCK_bm;

        /* Go to application, located immediately after boot section */
        app_t app = (app_t)(BOOT_SIZE / sizeof(app_t));
        app();
    }

    /* Initialize communication interface and LED */
    init_uart();
    init_status_led();
    VPORTB.OUT |= PIN5_bm;

    /*
     * Start programming at start for application section
     * Subtract MAPPED_PROGMEM_START in condition to handle overflow on large flash sizes
     */
    uint8_t *app_ptr = (uint8_t *)MAPPED_APPLICATION_START;
    while(app_ptr - MAPPED_PROGMEM_START <= (uint8_t *)PROGMEM_END) {
        /* Receive and echo data before loading to memory */
        uint8_t rx_data = uart_receive();
        uart_send(rx_data);

        /* Incremental load to page buffer before writing to Flash */
        *app_ptr = rx_data;
        app_ptr++;
        if(!((uint16_t)app_ptr % MAPPED_PROGMEM_PAGE_SIZE)) {
            /* Page boundary reached, Commit page to Flash */
            _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_PAGEERASEWRITE_gc);
            while(NVMCTRL.STATUS & NVMCTRL_FBUSY_bm);

            toggle_status_led();
        }
    }

    //change USERROW back to 0xff, so we go into app after upcoming reboot
    USERROW.USERROW31 = 0xff;
    _PROTECTED_WRITE_SPM(NVMCTRL.CTRLA, NVMCTRL_CMD_PAGEERASEWRITE_gc);
    while(NVMCTRL.STATUS & NVMCTRL_EEBUSY_bm);
    
    //turn off led, just in case it's still on after update
    VPORTB.OUT &= ~PIN5_bm;
            
//  /* Issue system reset */
    _PROTECTED_WRITE(RSTCTRL.SWRR, RSTCTRL_SWRE_bm);
}

/*
 * Boot access request function
 */
static bool is_bootloader_requested(void)
{
    if(USERROW.USERROW31 == 0x55) {
          return true;
    }
    return false;
    
}

/*
 * Communication interface functions
 */
static void init_uart(void)
{
    /* Configure UART */
    USART3.CTRLB = USART_RXEN_bm | USART_TXEN_bm;

    /* From datasheet:
     * Baud rate compensated with factory stored frequency error
     * Asynchronous communication without Auto-baud (Sync Field)
     * 20MHz Clock, 3V
     */
    int32_t baud_reg_val  = (F_CPU_RESET * 64) / (BOOT_BAUD * 16);  // ideal BAUD register value
    assert(baud_reg_val >= 0x4A);           // Verify legal min BAUD register value with max neg comp
    int8_t sigrow_val = SIGROW.OSC20ERR5V;  // read signed error
    baud_reg_val *= (1024 + sigrow_val);    // sum resolution + error
    baud_reg_val += 512;                    // compensate for rounding error
    baud_reg_val /= 1024;                   // divide by resolution
    USART3.BAUD = (int16_t) baud_reg_val;   // set adjusted baud rate

    /* Set TxD (PB2) as output */
    VPORTB.DIR |= PIN0_bm;
}

static uint8_t uart_receive(void)
{
    /* Poll for data received */
    while(!(USART3.STATUS & USART_RXCIF_bm));
    return USART3.RXDATAL;
}

static void uart_send(uint8_t byte)
{
    /* Data will be sent when TXDATA is written */
    USART3.TXDATAL = byte;
}

static void init_status_led(void)
{
    /* Set LED0 (PB4) as output */
    VPORTB.DIR |= PIN5_bm;
}

static void toggle_status_led(void)
{
    /* Toggle LED0 (PB4) */
    VPORTB.OUT ^= PIN5_bm;
}

static inline void wait_50_ms(void)
{
  for(uint32_t count = 0; count < 500000; count++){
        asm volatile ("nop");
    }
}

static void initPins(void){
    VPORTA.DIR = 0x00;
    VPORTB.DIR = 0x20;
    VPORTC.DIR = 0x00;
    VPORTD.DIR = 0x00;
    VPORTE.DIR = 0x00;
    VPORTF.DIR = 0x00;
}

I've tried adding delays to the bootloader start up, making all pins (except LED) inputs immediately on bootup, and changing fuse values. None helped. I've also tried moving my NVMCTRL_BOOTLOCK_bm call to right at the beginning, but then the bootloader never gets to the app jump...

I've tried my device with a 3rd party bootloader (optiboot) to see if it's something in my hardware, but that worked fine - so it must be my bootloader code.

Any help would be greatly appreciated.

Thanks!


Solution

  • It appears to have been corruption due to not setting the BOD fuses. So when it powered up, there was nothing stopping the controller from trying to run before the voltage was high enough.

    I've added the following to my original code above, so that BOD is sampled and the controller only boots up once we're in a safe operating voltage:

    .BODCFG = 0xFE
    

    I had found a similar question at Stack Exchange that tipped me off about BOD: Link

    After adding the BOD fuses appropriately, the issue hasn't occurred again.