/* EEPROM emulation over flash with reduced wear * * We will use 2 contiguous groups of pages as main and alternate. * We want an structure that allows to read as fast as possible, * without the need of scanning the whole FLASH memory. * * FLASH bits default erased state is 1, and can be set to 0 * on a per bit basis. To reset them to 1, a full page erase * is needed. * * Values are stored as differences that should be applied to a * completely erased EEPROM (filled with 0xFFs). We just encode * the starting address of the values to change, the length of * the block of new values, and the values themselves. All diffs * are accumulated into a RAM buffer, compacted into the least * amount of non overlapping diffs possible and sorted by starting * address before being saved into the next available page of FLASH * of the current group. * Once the current group is completely full, we compact it and save * it into the other group, then erase the current group and switch * to that new group and set it as current. * * The FLASH endurance is about 1/10 ... 1/100 of an EEPROM * endurance, but EEPROM endurance is specified per byte, not * per page. We can't emulate EE endurance with FLASH for all * bytes, but we can emulate endurance for a given percent of * bytes. * */ #ifdef ARDUINO_ARCH_SAM #include "../shared/persistent_store_api.h" #include "../../inc/MarlinConfig.h" #if ENABLED(EEPROM_SETTINGS) && DISABLED(I2C_EEPROM) && DISABLED(SPI_EEPROM) #include #define EEPROMSize 4096 #define PagesPerGroup 128 #define GroupCount 2 #define PageSize 256u /* Flash storage */ typedef struct FLASH_SECTOR { uint8_t page[PageSize]; } FLASH_SECTOR_T; #define PAGE_FILL \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, \ 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF #define FLASH_INIT_FILL \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL, \ PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL,PAGE_FILL /* This is the FLASH area used to emulate a 2Kbyte EEPROM -- We need this buffer aligned to a 256 byte boundary. */ static const uint8_t flashStorage[PagesPerGroup * GroupCount * PageSize] __attribute__ ((aligned (PageSize))) = { FLASH_INIT_FILL }; /* Get the address of an specific page */ static const FLASH_SECTOR_T* getFlashStorage(int page) { return (const FLASH_SECTOR_T*)&flashStorage[page*PageSize]; } static uint8_t buffer[256] = {0}, // The RAM buffer to accumulate writes curPage = 0, // Current FLASH page inside the group curGroup = 0xFF; // Current FLASH group //#define EE_EMU_DEBUG #ifdef EE_EMU_DEBUG static void ee_Dump(int page,const void* data) { const uint8_t* c = (const uint8_t*) data; char buffer[80]; sprintf(buffer, "Page: %d (0x%04x)\n", page, page); SERIAL_ECHO(buffer); char* p = &buffer[0]; for (int i = 0; i< PageSize; ++i) { if ((i & 0xF) == 0) p += sprintf(p,"%04x] ", i); p += sprintf(p," %02x", c[i]); if ((i & 0xF) == 0xF) { *p++ = '\n'; *p = 0; SERIAL_ECHO(buffer); p = &buffer[0]; } } } #endif /* Flash Writing Protection Key */ #define FWP_KEY 0x5Au #if SAM4S_SERIES #define EEFC_FCR_FCMD(value) \ ((EEFC_FCR_FCMD_Msk & ((value) << EEFC_FCR_FCMD_Pos))) #define EEFC_ERROR_FLAGS (EEFC_FSR_FLOCKE | EEFC_FSR_FCMDE | EEFC_FSR_FLERR) #else #define EEFC_ERROR_FLAGS (EEFC_FSR_FLOCKE | EEFC_FSR_FCMDE) #endif /** * Writes the contents of the specified page (no previous erase) * @param page (page #) * @param data (pointer to the data buffer) */ __attribute__ ((long_call, section (".ramfunc"))) static bool ee_PageWrite(uint16_t page,const void* data) { uint16_t i; uint32_t addrflash = ((uint32_t)getFlashStorage(page)); // Read the flash contents uint32_t pageContents[PageSize>>2]; memcpy(pageContents, (void*)addrflash, PageSize); // We ONLY want to toggle bits that have changed, and that have changed to 0. // SAM3X8E tends to destroy contiguous bits if reprogrammed without erasing, so // we try by all means to avoid this. That is why it says: "The Partial // Programming mode works only with 128-bit (or higher) boundaries. It cannot // be used with boundaries lower than 128 bits (8, 16 or 32-bit for example)." // All bits that did not change, set them to 1. for (i = 0; i > 2; i++) pageContents[i] = (((uint32_t*)data)[i]) | (~(pageContents[i] ^ ((uint32_t*)data)[i])); #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM PageWrite ", page); SERIAL_ECHOLNPAIR(" in FLASH address ", (uint32_t)addrflash); SERIAL_ECHOLNPAIR(" base address ", (uint32_t)getFlashStorage(0)); SERIAL_FLUSH(); #endif // Get the page relative to the start of the EFC controller, and the EFC controller to use Efc *efc; uint16_t fpage; if (addrflash >= IFLASH1_ADDR) { efc = EFC1; fpage = (addrflash - IFLASH1_ADDR) / IFLASH1_PAGE_SIZE; } else { efc = EFC0; fpage = (addrflash - IFLASH0_ADDR) / IFLASH0_PAGE_SIZE; } // Get the page that must be unlocked, then locked uint16_t lpage = fpage & (~((IFLASH0_LOCK_REGION_SIZE / IFLASH0_PAGE_SIZE) - 1)); // Disable all interrupts __disable_irq(); // Get the FLASH wait states uint32_t orgWS = (efc->EEFC_FMR & EEFC_FMR_FWS_Msk) >> EEFC_FMR_FWS_Pos; // Set wait states to 6 (SAM errata) efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(6); // Unlock the flash page uint32_t status; efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(lpage) | EEFC_FCR_FCMD(EFC_FCMD_CLB); while (((status = efc->EEFC_FSR) & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) { // force compiler to not optimize this -- NOPs don't work! __asm__ __volatile__(""); }; if ((status & EEFC_ERROR_FLAGS) != 0) { // Restore original wait states efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); // Reenable interrupts __enable_irq(); #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM Unlock failure for page ", page); #endif return false; } // Write page and lock: Writing 8-bit and 16-bit data is not allowed and may lead to unpredictable data corruption. const uint32_t * aligned_src = (const uint32_t *) &pageContents[0]; /*data;*/ uint32_t * p_aligned_dest = (uint32_t *) addrflash; for (i = 0; i < (IFLASH0_PAGE_SIZE / sizeof(uint32_t)); ++i) { *p_aligned_dest++ = *aligned_src++; } efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(fpage) | EEFC_FCR_FCMD(EFC_FCMD_WPL); while (((status = efc->EEFC_FSR) & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) { // force compiler to not optimize this -- NOPs don't work! __asm__ __volatile__(""); }; if ((status & EEFC_ERROR_FLAGS) != 0) { // Restore original wait states efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); // Reenable interrupts __enable_irq(); #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM Write failure for page ", page); #endif return false; } // Restore original wait states efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); // Reenable interrupts __enable_irq(); // Compare contents if (memcmp(getFlashStorage(page),data,PageSize)) { #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM Verify Write failure for page ", page); ee_Dump( page,(uint32_t *) addrflash); ee_Dump(-page,data); // Calculate count of changed bits uint32_t* p1 = (uint32_t*)addrflash; uint32_t* p2 = (uint32_t*)data; int count = 0; for (i =0; i> 2; i++) { if (p1[i] != p2[i]) { uint32_t delta = p1[i] ^ p2[i]; while (delta) { if ((delta&1) != 0) count++; delta >>= 1; } } } SERIAL_ECHOLNPAIR("--> Differing bits: ", count); #endif return false; } return true; } /** * Erases the contents of the specified page * @param page (page #) */ __attribute__ ((long_call, section (".ramfunc"))) static bool ee_PageErase(uint16_t page) { uint16_t i; uint32_t addrflash = ((uint32_t)getFlashStorage(page)); #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM PageErase ", page); SERIAL_ECHOLNPAIR(" in FLASH address ", (uint32_t)addrflash); SERIAL_ECHOLNPAIR(" base address ", (uint32_t)getFlashStorage(0)); SERIAL_FLUSH(); #endif // Get the page relative to the start of the EFC controller, and the EFC controller to use Efc *efc; uint16_t fpage; if (addrflash >= IFLASH1_ADDR) { efc = EFC1; fpage = (addrflash - IFLASH1_ADDR) / IFLASH1_PAGE_SIZE; } else { efc = EFC0; fpage = (addrflash - IFLASH0_ADDR) / IFLASH0_PAGE_SIZE; } // Get the page that must be unlocked, then locked uint16_t lpage = fpage & (~((IFLASH0_LOCK_REGION_SIZE / IFLASH0_PAGE_SIZE) - 1)); // Disable all interrupts __disable_irq(); // Get the FLASH wait states uint32_t orgWS = (efc->EEFC_FMR & EEFC_FMR_FWS_Msk) >> EEFC_FMR_FWS_Pos; // Set wait states to 6 (SAM errata) efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(6); // Unlock the flash page uint32_t status; efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(lpage) | EEFC_FCR_FCMD(EFC_FCMD_CLB); while (((status = efc->EEFC_FSR) & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) { // force compiler to not optimize this -- NOPs don't work! __asm__ __volatile__(""); }; if ((status & EEFC_ERROR_FLAGS) != 0) { // Restore original wait states efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); // Reenable interrupts __enable_irq(); #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM Unlock failure for page ",page); #endif return false; } // Erase Write page and lock: Writing 8-bit and 16-bit data is not allowed and may lead to unpredictable data corruption. uint32_t * p_aligned_dest = (uint32_t *) addrflash; for (i = 0; i < (IFLASH0_PAGE_SIZE / sizeof(uint32_t)); ++i) { *p_aligned_dest++ = 0xFFFFFFFF; } efc->EEFC_FCR = EEFC_FCR_FKEY(FWP_KEY) | EEFC_FCR_FARG(fpage) | EEFC_FCR_FCMD(EFC_FCMD_EWPL); while (((status = efc->EEFC_FSR) & EEFC_FSR_FRDY) != EEFC_FSR_FRDY) { // force compiler to not optimize this -- NOPs don't work! __asm__ __volatile__(""); }; if ((status & EEFC_ERROR_FLAGS) != 0) { // Restore original wait states efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); // Reenable interrupts __enable_irq(); #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM Erase failure for page ",page); #endif return false; } // Restore original wait states efc->EEFC_FMR = (efc->EEFC_FMR & (~EEFC_FMR_FWS_Msk)) | EEFC_FMR_FWS(orgWS); // Reenable interrupts __enable_irq(); // Check erase uint32_t * aligned_src = (uint32_t *) addrflash; for (i = 0; i < PageSize >> 2; i++) { if (*aligned_src++ != 0xFFFFFFFF) { #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM Verify Erase failure for page ",page); ee_Dump( page,(uint32_t *) addrflash); #endif return false; } } return true; } static uint8_t ee_Read(uint32_t address, bool excludeRAMBuffer = false) { uint32_t baddr; uint32_t blen; // If we were requested an address outside of the emulated range, fail now if (address >= EEPROMSize) return false; // Check that the value is not contained in the RAM buffer if (!excludeRAMBuffer) { uint16_t i = 0; while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ // Get the address of the block baddr = buffer[i] | (buffer[i + 1] << 8); // Get the length of the block blen = buffer[i + 2]; // If we reach the end of the list, break loop if (blen == 0xFF) break; // Check if data is contained in this block if (address >= baddr && address < (baddr + blen)) { // Yes, it is contained. Return it! return buffer[i + 3 + address - baddr]; } // As blocks are always sorted, if the starting address of this block is higher // than the address we are looking for, break loop now - We wont find the value // associated to the address if (baddr > address) break; // Jump to the next block i += 3 + blen; } } // It is NOT on the RAM buffer. It could be stored in FLASH. We are // ensured on a given FLASH page, address contents are never repeated // but on different pages, there is no such warranty, so we must go // backwards from the last written FLASH page to the first one. for (int page = curPage - 1; page >= 0; --page) { // Get a pointer to the flash page uint8_t* pflash = (uint8_t*)getFlashStorage(page + curGroup * PagesPerGroup); uint16_t i = 0; while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ // Get the address of the block baddr = pflash[i] | (pflash[i + 1] << 8); // Get the length of the block blen = pflash[i + 2]; // If we reach the end of the list, break loop if (blen == 0xFF) break; // Check if data is contained in this block if (address >= baddr && address < (baddr + blen)) return pflash[i + 3 + address - baddr]; // Yes, it is contained. Return it! // As blocks are always sorted, if the starting address of this block is higher // than the address we are looking for, break loop now - We wont find the value // associated to the address if (baddr > address) break; // Jump to the next block i += 3 + blen; } } // If reached here, value is not stored, so return its default value return 0xFF; } static uint32_t ee_GetAddrRange(uint32_t address, bool excludeRAMBuffer = false) { uint32_t baddr, blen, nextAddr = 0xFFFF, nextRange = 0; // Check that the value is not contained in the RAM buffer if (!excludeRAMBuffer) { uint16_t i = 0; while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ // Get the address of the block baddr = buffer[i] | (buffer[i + 1] << 8); // Get the length of the block blen = buffer[i + 2]; // If we reach the end of the list, break loop if (blen == 0xFF) break; // Check if address and address + 1 is contained in this block if (address >= baddr && address < (baddr + blen)) return address | ((blen - address + baddr) << 16); // Yes, it is contained. Return it! // Otherwise, check if we can use it as a limit if (baddr > address && baddr < nextAddr) { nextAddr = baddr; nextRange = blen; } // As blocks are always sorted, if the starting address of this block is higher // than the address we are looking for, break loop now - We wont find the value // associated to the address if (baddr > address) break; // Jump to the next block i += 3 + blen; } } // It is NOT on the RAM buffer. It could be stored in FLASH. We are // ensured on a given FLASH page, address contents are never repeated // but on different pages, there is no such warranty, so we must go // backwards from the last written FLASH page to the first one. for (int page = curPage - 1; page >= 0; --page) { // Get a pointer to the flash page uint8_t* pflash = (uint8_t*)getFlashStorage(page + curGroup * PagesPerGroup); uint16_t i = 0; while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ // Get the address of the block baddr = pflash[i] | (pflash[i + 1] << 8); // Get the length of the block blen = pflash[i + 2]; // If we reach the end of the list, break loop if (blen == 0xFF) break; // Check if data is contained in this block if (address >= baddr && address < (baddr + blen)) return address | ((blen - address + baddr) << 16); // Yes, it is contained. Return it! // Otherwise, check if we can use it as a limit if (baddr > address && baddr < nextAddr) { nextAddr = baddr; nextRange = blen; } // As blocks are always sorted, if the starting address of this block is higher // than the address we are looking for, break loop now - We wont find the value // associated to the address if (baddr > address) break; // Jump to the next block i += 3 + blen; } } // If reached here, we will return the next valid address return nextAddr | (nextRange << 16); } static bool ee_IsPageClean(int page) { uint32_t* pflash = (uint32_t*) getFlashStorage(page); for (uint16_t i = 0; i < (PageSize >> 2); ++i) if (*pflash++ != 0xFFFFFFFF) return false; return true; } static bool ee_Flush(uint32_t overrideAddress = 0xFFFFFFFF, uint8_t overrideData = 0xFF) { // Check if RAM buffer has something to be written bool isEmpty = true; uint32_t* p = (uint32_t*) &buffer[0]; for (uint16_t j = 0; j < (PageSize >> 2); j++) { if (*p++ != 0xFFFFFFFF) { isEmpty = false; break; } } // If something has to be written, do so! if (!isEmpty) { // Write the current ram buffer into FLASH ee_PageWrite(curPage + curGroup * PagesPerGroup, buffer); // Clear the RAM buffer memset(buffer, 0xFF, sizeof(buffer)); // Increment the page to use the next time ++curPage; } // Did we reach the maximum count of available pages per group for storage ? if (curPage < PagesPerGroup) { // Do we have an override address ? if (overrideAddress < EEPROMSize) { // Yes, just store the value into the RAM buffer buffer[0] = overrideAddress & 0xFF; buffer[0 + 1] = (overrideAddress >> 8) & 0xFF; buffer[0 + 2] = 1; buffer[0 + 3] = overrideData; } // Done! return true; } // We have no space left on the current group - We must compact the values uint16_t i = 0; // Compute the next group to use int curwPage = 0, curwGroup = curGroup + 1; if (curwGroup >= GroupCount) curwGroup = 0; uint32_t rdAddr = 0; do { // Get the next valid range uint32_t addrRange = ee_GetAddrRange(rdAddr, true); // Make sure not to skip the override address, if specified int rdRange; if (overrideAddress < EEPROMSize && rdAddr <= overrideAddress && (addrRange & 0xFFFF) > overrideAddress) { rdAddr = overrideAddress; rdRange = 1; } else { rdAddr = addrRange & 0xFFFF; rdRange = addrRange >> 16; } // If no range, break loop if (rdRange == 0) break; do { // Get the value uint8_t rdValue = overrideAddress == rdAddr ? overrideData : ee_Read(rdAddr, true); // Do not bother storing default values if (rdValue != 0xFF) { // If we have room, add it to the buffer if (buffer[i + 2] == 0xFF) { // Uninitialized buffer, just add it! buffer[i] = rdAddr & 0xFF; buffer[i + 1] = (rdAddr >> 8) & 0xFF; buffer[i + 2] = 1; buffer[i + 3] = rdValue; } else { // Buffer already has contents. Check if we can extend it // Get the address of the block uint32_t baddr = buffer[i] | (buffer[i + 1] << 8); // Get the length of the block uint32_t blen = buffer[i + 2]; // Can we expand it ? if (rdAddr == (baddr + blen) && i < (PageSize - 4) && /* This block has a chance to contain data AND */ buffer[i + 2] < (PageSize - i - 3)) {/* There is room for this block to be expanded */ // Yes, do it ++buffer[i + 2]; // And store the value buffer[i + 3 + rdAddr - baddr] = rdValue; } else { // No, we can't expand it - Skip the existing block i += 3 + blen; // Can we create a new slot ? if (i > (PageSize - 4)) { // Not enough space - Write the current buffer to FLASH ee_PageWrite(curwPage + curwGroup * PagesPerGroup, buffer); // Advance write page (as we are compacting, should never overflow!) ++curwPage; // Clear RAM buffer memset(buffer, 0xFF, sizeof(buffer)); // Start fresh */ i = 0; } // Enough space, add the new block buffer[i] = rdAddr & 0xFF; buffer[i + 1] = (rdAddr >> 8) & 0xFF; buffer[i + 2] = 1; buffer[i + 3] = rdValue; } } } // Go to the next address ++rdAddr; // Repeat for bytes of this range } while (--rdRange); // Repeat until we run out of ranges } while (rdAddr < EEPROMSize); // We must erase the previous group, in preparation for the next swap for (int page = 0; page < curPage; page++) { ee_PageErase(page + curGroup * PagesPerGroup); } // Finally, Now the active group is the created new group curGroup = curwGroup; curPage = curwPage; // Done! return true; } static bool ee_Write(uint32_t address, uint8_t data) { // If we were requested an address outside of the emulated range, fail now if (address >= EEPROMSize) return false; // Lets check if we have a block with that data previously defined. Block // start addresses are always sorted in ascending order uint16_t i = 0; while (i <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ // Get the address of the block uint32_t baddr = buffer[i] | (buffer[i + 1] << 8); // Get the length of the block uint32_t blen = buffer[i + 2]; // If we reach the end of the list, break loop if (blen == 0xFF) break; // Check if data is contained in this block if (address >= baddr && address < (baddr + blen)) { // Yes, it is contained. Just modify it buffer[i + 3 + address - baddr] = data; // Done! return true; } // Maybe we could add it to the front or to the back // of this block ? if ((address + 1) == baddr || address == (baddr + blen)) { // Potentially, it could be done. But we must ensure there is room // so we can expand the block. Lets find how much free space remains uint32_t iend = i; do { uint32_t ln = buffer[iend + 2]; if (ln == 0xFF) break; iend += 3 + ln; } while (iend <= (PageSize - 4)); /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ // Here, inxt points to the first free address in the buffer. Do we have room ? if (iend < PageSize) { // Yes, at least a byte is free - We can expand the block // Do we have to insert at the beginning ? if ((address + 1) == baddr) { // Insert at the beginning // Make room at the beginning for our byte memmove(&buffer[i + 3 + 1], &buffer[i + 3], iend - i - 3); // Adjust the header and store the data buffer[i] = address & 0xFF; buffer[i + 1] = (address >> 8) & 0xFF; buffer[i + 2]++; buffer[i + 3] = data; } else { // Insert at the end - There is a very interesting thing that could happen here: // Maybe we could coalesce the next block with this block. Let's try to do it! uint16_t inext = i + 3 + blen; if (inext <= (PageSize - 4) && (buffer[inext] | uint16_t(buffer[inext + 1] << 8)) == (baddr + blen + 1)) { // YES! ... we can coalesce blocks! . Do it! // Adjust this block header to include the next one buffer[i + 2] += buffer[inext + 2] + 1; // Store data at the right place buffer[i + 3 + blen] = data; // Remove the next block header and append its data memmove(&buffer[inext + 1], &buffer[inext + 3], iend - inext - 3); // Finally, as we have saved 2 bytes at the end, make sure to clean them buffer[iend - 2] = 0xFF; buffer[iend - 1] = 0xFF; } else { // NO ... No coalescing possible yet // Make room at the end for our byte memmove(&buffer[i + 3 + blen + 1], &buffer[i + 3 + blen], iend - i - 3 - blen); // And add the data to the block buffer[i + 2]++; buffer[i + 3 + blen] = data; } } // Done! return true; } } // As blocks are always sorted, if the starting address of this block is higher // than the address we are looking for, break loop now - We wont find the value // associated to the address if (baddr > address) break; // Jump to the next block i += 3 + blen; } // Value is not stored AND we can't expand previous block to contain it. We must create a new block // First, lets find how much free space remains uint32_t iend = i; while (iend <= (PageSize - 4)) { /* (PageSize - 4) because otherwise, there is not enough room for data and headers */ uint32_t ln = buffer[iend + 2]; if (ln == 0xFF) break; iend += 3 + ln; } // If there is room for a new block, insert it at the proper place if (iend <= (PageSize - 4)) { // We have room to create a new block. Do so --- But add // the block at the proper position, sorted by starting // address, so it will be possible to compact it with other blocks. // Make space memmove(&buffer[i + 4], &buffer[i], iend - i); // And add the block buffer[i] = address & 0xFF; buffer[i + 1] = (address >> 8) & 0xFF; buffer[i + 2] = 1; buffer[i + 3] = data; // Done! return true; } // Not enough room to store this information on this FLASH page - Perform a // flush and override the address with the specified contents return ee_Flush(address, data); } static void ee_Init() { // Just init once! if (curGroup != 0xFF) return; // Clean up the SRAM buffer memset(buffer, 0xFF, sizeof(buffer)); // Now, we must find out the group where settings are stored for (curGroup = 0; curGroup < GroupCount; curGroup++) if (!ee_IsPageClean(curGroup * PagesPerGroup)) break; // If all groups seem to be used, default to first group if (curGroup >= GroupCount) curGroup = 0; #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM Current Group: ",curGroup); SERIAL_FLUSH(); #endif // Now, validate that all the other group pages are empty for (int grp = 0; grp < GroupCount; grp++) { if (grp == curGroup) continue; for (int page = 0; page < PagesPerGroup; page++) { if (!ee_IsPageClean(grp * PagesPerGroup + page)) { #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOPAIR("EEPROM Page ",page); SERIAL_ECHOLNPAIR(" not clean on group ",grp); SERIAL_FLUSH(); #endif ee_PageErase(grp * PagesPerGroup + page); } } } // Finally, for the active group, determine the first unused page // and also validate that all the other ones are clean for (curPage = 0; curPage < PagesPerGroup; curPage++) { if (ee_IsPageClean(curGroup * PagesPerGroup + curPage)) { #ifdef EE_EMU_DEBUG ee_Dump(curGroup * PagesPerGroup + curPage, getFlashStorage(curGroup * PagesPerGroup + curPage)); #endif break; } } #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOLNPAIR("EEPROM Active page: ",curPage); SERIAL_FLUSH(); #endif // Make sure the pages following the first clean one are also clean for (int page = curPage + 1; page < PagesPerGroup; page++) { if (!ee_IsPageClean(curGroup * PagesPerGroup + page)) { #ifdef EE_EMU_DEBUG SERIAL_ECHO_START(); SERIAL_ECHOPAIR("EEPROM Page ",page); SERIAL_ECHOLNPAIR(" not clean on active group ",curGroup); SERIAL_FLUSH(); ee_Dump(curGroup * PagesPerGroup + page, getFlashStorage(curGroup * PagesPerGroup + page)); #endif ee_PageErase(curGroup * PagesPerGroup + page); } } } uint8_t eeprom_read_byte(uint8_t* addr) { ee_Init(); return ee_Read((uint32_t)addr); } void eeprom_write_byte(uint8_t* addr, uint8_t value) { ee_Init(); ee_Write((uint32_t)addr, value); } void eeprom_update_block(const void* __src, void* __dst, size_t __n) { uint8_t* dst = (uint8_t*)__dst; const uint8_t* src = (const uint8_t*)__src; while (__n--) { eeprom_write_byte(dst, *src); ++dst; ++src; } } void eeprom_read_block(void* __dst, const void* __src, size_t __n) { uint8_t* dst = (uint8_t*)__dst; uint8_t* src = (uint8_t*)__src; while (__n--) { *dst = eeprom_read_byte(src); ++dst; ++src; } } void eeprom_flush(void) { ee_Flush(); } #endif // ENABLED(EEPROM_SETTINGS) && DISABLED(I2C_EEPROM) && DISABLED(SPI_EEPROM) #endif // ARDUINO_ARCH_AVR