This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Secondary Bootloader IAP Flash Write

Hi everyone, I hope someone can help me with my problem.

I'm writing a secondary bootloader for an lpc2388 that reads the new firmware from external I2C EEPROM (separated into 128byte pages) and writes it to the internal flash using IAP commands. The bootloader starts at 0x0000 in flash and the main application (RTX) starts at 0x2000.

Currently the program flow is:
1. Check if user code doesn't exist or a flag has been set (set at a flash address by the main application). If not it executes the code. If so it continues.
2. Inits the system IO (UART for debugging and I2C for EEPROM access)
3. Goes through the eeprom and calculates a checksum and compares to the checksum received from the main application
4. Erases the current firmware using IAP commands
5. using IAP commands the new firmware is written by preparing, writing, and validating the flash in 512byte chunks
6. Once the new firmware is written I do another pass through comparing what is in the EEPROM with what is in the Flash
7. I then use the internal WDT to reset and run the new firmware

I'm seeing a couple problems.
1. Sometimes the IAP validate fails after writing a 512byte chunk. When this happens I retry the chunk and it usually works the second try, but I can't explain why it fails the first time.
2. Sometimes the new firmware get written without any errors generated by the IAP commands, but when I do the final pass to validate the new firmware in flash, sometimes I get a byte mismatch where only a single bit is flipped. Could this have something to do with ECC?
3. The third problem I'm having is that when the new firmware is flashed without any problems and we start running the new firmware, sometimes I will get some strange errors related to RTX timing. It seems the os_time_get command is returning very large values.

I don't really know how to explain the above errors. I played around doing IAP writes with different chunk sizes (4096) but to no avail. Does anyone have any ideas?

  • Would suggest you do a CRC (not a checksum) of each block of data as you read it from I2C. Display this via your diagnostic UART, and compare it to a similar list on the PC that you computed before you wrote the image to the I2C device.

    Verify each block as you write. In your final pass verify generate and output a CRC from both data blocks (the one from flash, and one from I2C)

    Try to make a determination about where the source of the error coming from, is the data read from the I2C consistent/correct. Try to see if there is any commonality to the problem, specific bits, addresses, or binary patterns.

    If the problem is with the CPU Flash, check carefully the speed settings and the supply voltages.

  • I think the problem is coming from the flash. I did a test where I flash the firmware to the chip and then run the bootloader with the same version of the firmware in the eeprom. I then run a comparison (byte-by-byte) between the flash and eeprom and they end up being the same (what I expect). This means the file in the eeprom is correct, but my bootloader doesn't seem to be programming it properly.

    Here's the code I'm using to flash the chip. The code first erases all the required sectors, then goes through chunk-by-chunk (I've since changed the chunk size back to 4096bytes) and prepares, writes, and validates each chunk.

    Could the problem be that when I get to the first 32kb sector, I erase the whole sector but write to it with ~8-9 4096byte writes? I put in some 100ms delays inbetween each step, but do I need to wait more time to ensure the flash has been written properly?

    CHUNK_SIZE is 4096
    EEPROM_PAGE_LENGTH is 128
    USER_CODE_ADDRESS is 0x2000
    USER_CODE_SIZE is 0x70000

    BOOL flash_firmware(void) {
        BOOL    f_error         = __FALSE;
        U32     its             = 0;
        U32     ind             = 0;
        U16     num_pages       = 0;
        U16     num_chunks      = 0;
        U32     length          = 0;
        U8      start_sect      = 0;
        U8      end_sect        = 0;
        U8      sector          = 0;
        U32     address         = 0;
        U32     offset          = 0;
        U32     tries           = 0;
        BOOL    write_success   = __FALSE;
        U8      data[CHUNK_SIZE];
    
        // Get firmware length
        length      = get_firmware_length();
        num_pages   = ((length-1)/(EEPROM_PAGE_LENGTH))+1;
        num_chunks  = ((num_pages-1)/(CHUNK_SIZE/EEPROM_PAGE_LENGTH))+1;
        if (length == 0) goto close1;
    
        // Ensure bootlaoder is not overwritten
        if (USER_CODE_ADDRESS < 0x2000) {
            DPRINT("\tERROR: Write location is invalid (0x%x)\n", USER_CODE_ADDRESS);
            f_error = __TRUE; goto close1;
        }
    
        // Erase current firmware
        DPRINT("\tErasing current firmware...\n");
        start_sect = get_sect_num(USER_CODE_ADDRESS);
        end_sect = get_sect_num(USER_CODE_ADDRESS+USER_CODE_SIZE);
        f_error = flash_erase(start_sect,end_sect);
        if (f_error) goto close1;
        delay_ms(100);
    
        // Write new firmware to flash
        DPRINT("\tFlashing new firmware...\n");
        for (its=0; its<num_chunks; its++) {
            // Set write address
            address = USER_CODE_ADDRESS+(its*CHUNK_SIZE);
    
            // Zero data array
            for (ind=0; ind<CHUNK_SIZE; ind++) {
                data[ind] = 0xFF;
            }
    
            // Grab pages from eeprom
            offset = (its*CHUNK_SIZE)/EEPROM_PAGE_LENGTH;
            for (ind=0; ind<CHUNK_SIZE/EEPROM_PAGE_LENGTH; ind++) {
                f_error = eeprom_read_page(offset+ind,&data[ind*EEPROM_PAGE_LENGTH]);
                if (f_error) goto close1;
            }
    
            write_success = __FALSE;
            tries = 0;
            while (!write_success) {
                DPRINT("\t%d: 0x%x\n",its,address);
                write_success = __TRUE;
                tries++;
                if (tries>5) {
                    DPRINT("\tfailed\n");
                    f_error = __TRUE;
                    goto close1;
                }
                delay_ms(100);
    
                // Prepare flash sector for write
                sector = get_sect_num(address);
                f_error = flash_prepare(sector,sector);
                if (f_error) {
                    DPRINT("\tretrying... (%d)\n",tries);
                    write_success = __FALSE;
                    continue;
                }
                delay_ms(100);
    
                // Write page to flash
                f_error = flash_write(address,data,CHUNK_SIZE);
                if (f_error) {
                    DPRINT("\tretrying... (%d)\n",tries);
                    write_success = __FALSE;
                    continue;
                }
                delay_ms(100);
    
                // Validate write
                f_error = flash_validate(address,data,CHUNK_SIZE);
                if (f_error) {
                    DPRINT("\tretrying... (%d)\n",tries);
                    write_success = __FALSE;
                    continue;
                }
                delay_ms(100);
            }
        }
    
    close1:
        return f_error;
    }
    
    BOOL flash_erase(U8 start_sect, U8 end_sect) {
        BOOL    f_error             = __FALSE;
        U32     enabled_interrupts  = 0;
        U32     command_iap[5], result_iap[3];
    
        // Copy interrupts and disable
        enabled_interrupts = VICIntEnable;
        VICIntEnClr = enabled_interrupts;
    
        // Prepare memory for erasing
        command_iap[0] = 50;
        command_iap[1] = start_sect;
        command_iap[2] = end_sect;
        run_iap_command(command_iap,result_iap);
        if (result_iap[0] != 0) {
            DPRINT("\tERROR: flash_erase prepare (%d)\n", result_iap[0]);
            f_error = __TRUE;
        }
    
        // Erase memory
        command_iap[0] = 52;
        command_iap[1] = start_sect;
        command_iap[2] = end_sect;
        command_iap[3] = CCLK_KHZ;
        run_iap_command(command_iap,result_iap);
        if (result_iap[0] != 0) {
            DPRINT("\tERROR: flash_erase erase (%d)\n", result_iap[0]);
            f_error = __TRUE;
        }
    
        // Re-enable interrupts
        VICIntEnable = enabled_interrupts;
    
        return f_error;
    }
    
    BOOL flash_prepare(U8 start_sect, U8 end_sector) {
        BOOL    f_error             = __FALSE;
        U32     enabled_interrupts  = 0;
        U32     command_iap[5], result_iap[3];
    
        // Prepare memory for writing
        command_iap[0] = 50;
        command_iap[1] = start_sect;
        command_iap[2] = end_sector;
        run_iap_command(command_iap,result_iap);
        if (result_iap[0] != 0) {
            DPRINT("\tERROR: flash_prepare (%d)\n", result_iap[0]);
            f_error = __TRUE;
        }
    
        // Re-enable interrupts
        VICIntEnable = enabled_interrupts;
    
        return f_error;
    }
    
    BOOL flash_write(U32 address, U8* data, U32 size) {
        BOOL    f_error             = __FALSE;
        U32     enabled_interrupts  = 0;
        U32     command_iap[5], result_iap[3];
    
        // Write data to memory
        command_iap[0] = 51;
        command_iap[1] = address;
        command_iap[2] = (U32)data;
        command_iap[3] = size;
        command_iap[4] = CCLK_KHZ;
        run_iap_command(command_iap,result_iap);
        if (result_iap[0] != 0) {
            DPRINT("\tERROR: flash_write (%d)\n", result_iap[0]);
            f_error = __TRUE;
        }
    
        // Re-enable interrupts
        VICIntEnable = enabled_interrupts;
    
        return f_error;
    }
    
    BOOL flash_validate(U32 address, U8* data, U32 size) {
        BOOL    f_error             = __FALSE;
        U32     enabled_interrupts  = 0;
        U32     command_iap[5], result_iap[3];
    
        // Validate flash
        command_iap[0] = 56;
        command_iap[1] = (U32)data;
        command_iap[2] = address;
        command_iap[3] = size;
        run_iap_command(command_iap,result_iap);
        if (result_iap[0] != 0) {
            DPRINT("\tERROR: flash_validate (%d)\n", result_iap[0]);
            f_error = __TRUE;
        }
    
        // Re-enable interrupts
        VICIntEnable = enabled_interrupts;
    
        return f_error;
    }
    

  • Can't say I've much experience with the NXP LPC2388 flash, but had my share with ATMEL, and SoC

    Most devices have the flash controller "self time" for programming/erase, the programming voltages are generated by a charge pump, and the exact time will depend on the supply. There may be additional timing setting related to wait states tied to the primary clock.

    You might want to check the flash performance aside from the I2C, for example by programming test patterns.

    Check also the amount of bulk capacitance you have parked near the CPU, say 2u2 or 4u7 in addition to 100n decoupling caps there for a different job. Keil's MCB2300 has 10uF parked out by the regulator, confirm your design has enough to meet the requirements of the regulator and CPU.

  • I think I may have found my problem. I was inputting a clock speed of 15KHz to the erase and write IAP functions, where my bootloader is using 60KHz. I made the change and so far I haven't seen any problems.