Skocz do zawartości

NOR FLASH za duże sektory MT28EW128ABA 128KB


Gieneq

Pomocna odpowiedź

Dalej w temacie pamięci FLASH NOR MT28EW128ABA, mam problem bo biblioteka zwraca błąd - za duże sektory.

Korzystam z biblioteki LevelX do obsługi wear levelingu i dalej jako driver dla FileX (system plików dla Azure RTOS). Testowałem na symulacji na kawałku RAMu - działało super.

Tu jest problem w funkcji z pliku fx_media_format formatującej pamięć:

    /* Validate bytes per sector value: greater than zero and no more than 4096.  */
    if((bytes_per_sector == 0) || (bytes_per_sector > 4096))
        return(FX_SECTOR_INVALID);

    /* Validate sectors per cluster value: greater than zero and no more than 128.  */
    if((sectors_per_cluster == 0) || (sectors_per_cluster > 128))
        return(FX_SECTOR_INVALID);

Z dokumentacji rozumiem że blok ma rozmiar 128KB. W pamięci 128Mb => 16MB jest więc 128 bloków. Kasowanie następuje blokami - całe 128KB dostaje jedynki (1). Informacja o stronach (pages) tyczy się zapisu (wstawienia (0) stronami w pamięci - tu minimalnie mogę zapisać 64B.

 FileX formatuje medium do FAT32. FAT operuje na sektorach czyli jednostkach które są optymalizowane względem cykli czyszczenia i logicznych jednostek zapisu klaster "cluster".  Nazewnictwo nie jest tu do końca szczęśliwe ale rozumiem że:

  • sektor FAT to block NOR = 128KB,
  • cluster FAT to np. 1 block.

Wszędzie widzę że bloki FLASH są od 512 do 4096B, więc chyba robię coś źle. A klastry są tak dobrane żeby nie przekroczyć adresowania systemu. Dla mnie minimum 128KB to bezsens. Oczywiście mam cache w RAMie, ale to tylko pogłębia bezsens marnowaniem pamięci.

image.png

Link do komentarza
Share on other sites

Może dodam kolejną porcję przemyśleń. Oby to w czymś pomogło 🦆

Wychodzę z dokumentacji LevelX (wear leveling dla NOR i NAND): 

Cytat

NOR flash memory is composed of blocks that are typically evenly divisible by 512 bytes. There are no concept of a flash page in NOR flash memory.

LevelX divides each NOR flash block into 512-byte logical sectors. Furthermore, LevelX uses the first "n" sectors of each NOR flash block to store management information.

W dokumentacji MT28EW128ABA:

  • Rozmiar FLASH: 128Mib = 16KiB,
  • Rozmiar bloku: 128KiB Blok to jednostka którą można wyczyścić czyli zapisać samymi (1)
  • Liczba bloków: 16MiB / 128KiB = 128 bloków
  • Strona: 32B do zapisu/odczytu
  • Możliwość dostępu swobodnego zajmującego kilkanaście razy dłużej.

Dalej, czy blok 128KiB jest podzielny przez 512 - tak. 

Dokumentacja LevelX twierdzi że NOR nie ma stron bo dostęp jest swobodny. I podobnie funkcja HAL używa half-word w zapisie:

HAL_StatusTypeDef HAL_NOR_Program(NOR_HandleTypeDef *hnor, uint32_t *pAddress, uint16_t *pData)
  ...

  /* Write the data */
  NOR_WRITE(pAddress, *pData);

Istnieje funkcja zapisu bufora ale ma dopisany komentarz:

/**
  * @brief  Writes a half-word buffer to the NOR memory. This function must be used
            only with S29GL128P NOR memory.*/
HAL_StatusTypeDef HAL_NOR_ProgramBuffer(NOR_HandleTypeDef *hnor, uint32_t uwAddress, uint16_t *pData,
                                        uint32_t uwBufferSize)

Pewnie da się napisać własny HAL do page access. Ale to później.

Otwieram teraz przykład symulatora NOR na RAMie i zaczynam od LevelX:

#define LX_NOR_SIMULATOR_FLASH_SIZE          9192
#define LX_NOR_SIMULATOR_SECTOR_SIZE         512
#define LX_NOR_SIMULATOR_SECTORS_PER_BLOCK   16

9192B to liczba z czapy niepodzielna przez nic sensownie. Sector size domyślam się, że to nie tylko 512B ale każda inna dozwolona liczba dla FAT 512, 1024...4096, która nie spowoduje przekroczenia adresowania danego FAT. 512 może być. Z dokumentacji FileX:

Cytat

With reference to the specification, the bytes per sector may take on only the following values: 512, 1024, 2048 or 4096.

Sectors per block = 16. Z dokumentacji Blok musi być równo podzielny przez rozmiar Sektora. Oznacza to że u mnie jest to 128KiB / 512B = 256 sektorów / blok.

Teraz trochę terminologi FileX (FAT)

  • Sektor - jednostka alokacji. Jak rozumiem to ten sam sektor co w LevelX.
  • Klaster - kilka sektórów, klastarmi operuje FAT przy przydzielaniu pamięci. W przykładzie jest sectors_per_cluster = 8, czyli liczba ta nie pokrywa się z rozmiarem bloku.

Jak rozumiem idea jest następująca. Czyszczę FLASH do samych (1). Wyznaczam sektory, które dalej tworzą klastry. Gdy programuję/zapisuję pamięć to ustawiam (0). Mam tu dostęp swobodny, który zajmuje wieki lub dostęp stronami po 32B. 

OK. To teraz chcę wyczyścić jeden sektor, bo potrzebuję ustawić zamiast (0) jakąś (1). Muszę skasować cały blok 128KiB. Czyli muszę odczytać cały blok i zapisać go do RAMu. Następnie w tym cachu zapisuję. Kasuję blok. Zapisuję cache z RAMu.

Wydaje mi się to szalone, ale możliwe że można zrobić inaczej. Np. biorę inny blok i sektorami go zapisuję. I to może być to, bo z sekcji LevelX Driver Initialization:

Cytat

Specifying the base address of the flash.

Specifying the total number of blocks and the number of words per block.

A RAM buffer for reading one sector of flash (512 bytes) and aligned for ULONG access.

To teraz patrzę na przykład symulatora na RAMie i mam tu funkcje LevelX:

static ULONG  nor_sector_memory[LX_NOR_SIMULATOR_SECTOR_SIZE]; // 512
static ULONG words_per_block = (LX_NOR_SIMULATOR_SECTOR_SIZE * LX_NOR_SIMULATOR_SECTORS_PER_BLOCK) / sizeof(ULONG); //2048

UINT  lx_stm32_nor_simulator_initialize(LX_NOR_FLASH *nor_flash)
{
    /* Setup the base address of the flash memory.  */
    nor_flash->lx_nor_flash_base_address = (ULONG *) LX_NOR_SIMULATOR_FLASH_BASE_ADDRESS;

    /* Setup geometry of the flash.  */
    nor_flash->lx_nor_flash_total_blocks = (LX_NOR_SIMULATOR_FLASH_SIZE / 
      (LX_NOR_SIMULATOR_SECTOR_SIZE * LX_NOR_SIMULATOR_SECTORS_PER_BLOCK)); //okolo 1.1 czyli 1
    nor_flash->lx_nor_flash_words_per_block = words_per_block;

    /* Setup function pointers for the NOR flash services.  */
    nor_flash->lx_nor_flash_driver_read = lx_nor_simulator_read;
    nor_flash->lx_nor_flash_driver_write = lx_nor_simulator_write;

    nor_flash->lx_nor_flash_driver_block_erase = lx_nor_simulator_block_erase;
    nor_flash->lx_nor_flash_driver_block_erased_verify = lx_nor_simulator_block_erased_verify;

    /* Setup local buffer for NOR flash operation. This buffer must be the sector size of the NOR flash memory.  */
    nor_flash->lx_nor_flash_sector_buffer =  &nor_sector_memory[0];

    /* Return success.  */
    return(LX_SUCCESS);
}

oraz interfejs:

static UINT  lx_nor_simulator_read(ULONG *flash_address, ULONG *destination, ULONG words){
    memcpy((VOID *)destination, (VOID *)flash_address, words * sizeof(ULONG));
    return(LX_SUCCESS);
}


static UINT  lx_nor_simulator_write(ULONG *flash_address, ULONG *source, ULONG words){
    memcpy((VOID *)flash_address, (VOID *)source, words * sizeof(ULONG));
    return(LX_SUCCESS);
}

static UINT  lx_nor_simulator_block_erase(ULONG block, ULONG erase_count){
    ULONG   *pointer;
    LX_PARAMETER_NOT_USED(erase_count);
    /* Setup pointer.  */
    pointer = (ULONG *) (LX_NOR_SIMULATOR_FLASH_BASE_ADDRESS + block * (LX_NOR_SIMULATOR_SECTOR_SIZE * LX_NOR_SIMULATOR_SECTORS_PER_BLOCK));
    /* Loop to erase block.  */
    mem_set((VOID *) pointer, 0xff, words_per_block * sizeof(ULONG));
    return(LX_SUCCESS);
}

Czyli podsumowując moje rozważania:

NOR składa się z bloków, które są istotne dla wearlevelingu (LevelX). W praktyce operuję na wirtualnie podzielonych sektorach, które tworzą klastry, które służą do zapisu plików w FAT. Nie potrzebuję cahować cały block, tylko sektor, bo jest możliwość poprzez wear leveling przepisywać dane pomiędzy blokami.

Ciekawe czy jest przewidziany przypadek w którym jest 1 blok. Wydaje mi się, że jest on nierealny, bo przy kasowaniu poleci też sektor z tablicą FAT i ustawieniami.

  • Lubię! 2
Link do komentarza
Share on other sites

Dobra działa 🔥 forum zadziałało jak piękna żółta kaczuszka 🦆 i pomogło zebrać myśli.

Po uporządkowaniu adresów oraz poszczególnych wielkości: rozmiar bloku, rozmiar sektora, rozmiar klastra, zabrałem się za napsianie drivera dla funkcji LevelX. Pojawił się odwieczny dylemat: little/big endian. W tym przypadku mam little endian.

Dlaczego jest to problem? Bo biblioteka operuje na słowach uint32_t 4B, pamięć operuje na półowach słów (halfword) uint16_t (2B), a generalnie adresujemy po bajtach uint8_t (1B).

Dla przykładu liczba:

uint32_t var = 0x12345678;
/* niech &var -> 0x24000000 */

W little endian zapis to: 78 56 34 12. Wiec kolejno rzutując uzyskam:

uint16_t* var_16ptr_0 = (uint16_t*)var;       // adres 0x24000000, zapis 78 56, wartość: 5678
uint16_t* var_16ptr_1 = ((uint16_t*)var) + 1; // adres 0x24000002, zapis 34 12, wartość: 1234

W HALu da NOR są 2 funckje, które mają inne argumenty:

  • zapis - 1 halfword pod dany adres,
  • odczyt - bufor halfwordów od adresu.

Zatem zapis to:

static UINT lx_nor_driver_write(ULONG *flash_address, ULONG *source, ULONG words)
{
    HAL_StatusTypeDef nor_ret = HAL_OK;
    const uint32_t start_address = (uint32_t)flash_address;

    for(ULONG word_idx=0; word_idx < words; ++word_idx) {
        const uint32_t address_half_words = start_address + 4 * word_idx;
        uint16_t* half_words = (uint16_t*)(source + word_idx);

        for(ULONG halfword_idx=0; halfword_idx < 2; ++halfword_idx) {
			nor_ret = HAL_NOR_Program(&hnor1, (uint32_t *)(address_half_words + 2*halfword_idx), half_words + halfword_idx);
			if(nor_ret != HAL_OK) {
				return LX_ERROR;
			}

			if(HAL_NOR_GetStatus(&hnor1, NOR_CUSTOM_START_ADDRESS, NOR_CUSTOM_PROGRAM_TIMEOUT) != HAL_NOR_STATUS_SUCCESS) {
				return LX_ERROR;
			}
        }
    }
    return LX_SUCCESS;
}

Tu przesuwam wskaźnik liczby uint32_t o index (adres co 4) i rzutuję na wskaźnik uint16_t*. Następnie wyciągam 2 części przesuwając adres o 2.

Do odczytu korzystam z tego samego mechanizmu, żeby zachować poprawność w kolejności bajtów:

static UINT lx_nor_driver_read(ULONG *flash_address, ULONG *destination, ULONG words)
{
    HAL_StatusTypeDef nor_ret = HAL_OK;

    const uint32_t start_address = (uint32_t)flash_address;

    for(ULONG word_idx=0; word_idx < words; ++word_idx) {
        const uint32_t address_half_words = start_address + 4 * word_idx;
        uint16_t half_words[2];

        nor_ret = HAL_NOR_ReadBuffer(&hnor1, address_half_words, half_words, 2);
    	if(nor_ret != HAL_OK) {
    		return LX_ERROR;
    	}

    	const uint32_t single_word = *((uint32_t*)(half_words));
    	*(destination + word_idx) = single_word;
    }

    return LX_SUCCESS;
}

Tu mogę odczytać bufor danych. Pobieram 2 halfwordy, konwertuje je przez rzutowanie wskaźnika i dereferencję na 4B i zapisuję pod adres. Można zrobić to w jednej dość magicznej lini:

*(destination + word_idx) = *((uint32_t*)(half_words));

Myślę, że potomni czytelnicy tego kodu byliby bardzo uraczeni taką poprawą nieczytelności 😄 

Trzecia funkcja block erase nie wymaga komentarza, nic ciekawego.

Miałem dylemat, że takie rozwiązanie działało, ale po ponownym podłączeniu zasilania pamięć uciekała. Okazało się, że zapomniałem wyrzucić tymczasowy kod czyszczący pamięć.

    nor_ret = HAL_NOR_Erase_Chip(&hnor1, NOR_CUSTOM_START_ADDRESS);

 

  • Lubię! 2
Link do komentarza
Share on other sites

Testuję właśnie wear-leveling i wygląda na to że działa. W sumie powinno, ale warto sprawdzić.

Oto testowy kod:

  /* Update file */
  nor_sim_status = fx_file_create(&nor_custom_flash_disk, "forbot/more_hello.txt");
  if (nor_sim_status != FX_SUCCESS && nor_sim_status != FX_ALREADY_CREATED) { while(1) {HAL_GPIO_TogglePin(FAN_GPIO_Port, FAN_Pin); tx_thread_sleep(50);}}

  nor_sim_status = fx_file_open(&nor_custom_flash_disk, &hello_file, "forbot/more_hello.txt", FX_OPEN_FOR_WRITE);
  if (nor_sim_status != FX_SUCCESS) { while(1) {HAL_GPIO_TogglePin(FAN_GPIO_Port, FAN_Pin); tx_thread_sleep(50);} }

  static const char* some_text = "SOME MORE HELLO FOR FORBOT HERE!";
  nor_sim_status = fx_file_write(&hello_file, (VOID*)some_text, sizeof(some_text));
  if (nor_sim_status != FX_SUCCESS) { while(1) {HAL_GPIO_TogglePin(FAN_GPIO_Port, FAN_Pin); tx_thread_sleep(50);} }

  nor_sim_status = fx_file_close(&hello_file);
  if (nor_sim_status != FX_SUCCESS) { while(1) {HAL_GPIO_TogglePin(FAN_GPIO_Port, FAN_Pin); tx_thread_sleep(50);} }

  /* Finish */
  nor_sim_status = fx_media_close(&nor_custom_flash_disk);
  if (nor_sim_status != FX_SUCCESS) { while(1) {HAL_GPIO_TogglePin(FAN_GPIO_Port, FAN_Pin); tx_thread_sleep(50);} }

		HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
		tx_thread_sleep(50);
	}

Mam ustawione pułapki na funkcjach read/write z drivera.

Można zauważyć, że gdy tworzę nieistniejący folder to nie następuje zapis, ale już przy utworzeniu w nim pliku natychmiast wchodzą 2 cykle zapisu po 1 słowie: jedno w adresie na początku 0x14 - domyślam się, że adres ten oznacza liczbę pozostałych klastrów - na początek była tam wartość 4093 i przy każdym wywołaniu funkcji flush lub pośrednio media_close wartość pod adresem 0x14 spada. Trzeba by to sprawdzić, ale dokumentacja jakoś inaczej wyjaśnia te początkowe adresy. W późniejszych cyklach modyfikacja pliku nie działa od razu, a dopiero przy flush.

Na etapie wgrywania cachu do pamięci (wspomniany flush) widać wyraźnie jak zmieniany jest adres w tablicy FAT i uzupełniany jest kolejny klaster (512B -> 0x200). Ciekawe, że modyfikacja 1 pliku powoduje zmianę w 2 klastrach. Narazie nie jest mi potrzeba głębsza wiedza, ale już widać, że działa wear-leveling. Ani razu nie wykonano czyszczenia pamięci, jedynie 1 zamieniają się na 0.

Ciekawy mechanizm widać po zapisaniu klastra. Następują 4 cykle zapisu modyfikujące 2 adresy. Są one bardzo powtarzalne. Ciekawe do czego to służy.

Załączam 2 cykle:

  • 1 to początkowy z utworzeniem pliku,
  • 2 to kolejne obejście, bez restartu całego urządzenia.

image.thumb.png.c33ed76259aa74aa1180eccb558a74ab.png

Ciekawe, że przy pierwszym obejściu nastąpił zapis do jednego z adresów.

IMG_6721.thumb.jpg.50ae7a93bdee57eef0092d4e2a1939da.jpg

  • Lubię! 1
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Mając system plików można przygotować dość sprawny magazyn. W ESP32 jest coś takiego jak NVM (Non volatile memory) Czyli kawałek FLASHa wygospodarowany dla użytkownika. Można dodawać wartości na zasadzie klucz-wartość, gdzie klucz to niezadługi string a wartość to liczba, napis, lub blob z bytów.

Najłatwiej wystartować od funkcji zapisującej bufor bytów. Można binarkę, można jako znaki. Ja wybrałem znaki. Klucz od liczby oddzielam ':', a linię kończę '\n'. Uwzględniając różne warianty dodawania liczby do końca, do środka, aktualizacji, operacji na kopii, zabezpieczenia mutexem udało się przygotować 2 zgrabne funkcje, które o dziwo są też całkiem odporne na nagły zanik napięcia:

UINT MX_FileX_nvm_store_uint32(const char* label, ULONG value);
UINT MX_FileX_nvm_load_uint32(const char* label, ULONG* value);

Dość szalony przypadek to aktualizacja wartości w istniejącym kluczu w środku pliku nvm. Jakby liczba bajtów (albo bardziej słów) się nie zmieniła to nie problem, ale przy przesunięciu choćby o 1 bajt trzeba przekopiować cały ogon pliku od miejsca w którym jest zmiana. Wiąże się to oczywiście z relokacją całych klastrów. Trzeba skorzystać z tymczasowego pliku kopii zapasowej. Warto skorzystać z opcji wstępnej alokacji i dealokacji, gdy rozmiar pliku oscyluje na granicy klastra to może to uchronić trochę zasobów.

Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.