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?
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.