logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie

p.kaczmarek2 05 Lip 2025 13:10 1407 2

TL;DR

  • Pokazuje uruchomienie zewnętrznej pamięci SPI Flash W25Q32CSIG z dowolnym mikrokontrolerem do zapisu pomiarów, z wykorzystaniem programowego SPI i GPIO.
  • Opisuje minimalny zestaw komend 0x06, 0x20, 0x05, 0x02 i 0x03 oraz bit-banging: SPI_Send, SPI_Read, SPI_Begin, SPI_End i SPI_Setup.
  • Test z układem W25Q32 dał odczyt JEDEC EF 40 16, czyli 32 Mbit i 4 MB pojemności.
  • Komunikacja działała poprawnie, a procedura testowa kasowała sektor, weryfikowała 0xFF, programowała bajt wzorcowy i odczytem potwierdzała zapis.
  • Zapis wymaga wcześniejszego kasowania sektorów, a programowanie może tylko zerować bity; przy zmianie 0 na 1 trzeba odczytać, zmodyfikować RAM i wgrać ponownie.
Wygenerowane przez model językowy.
REKLAMA
📢 Słuchaj (AI):
  • Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Pokażę tutaj jak można łatwo uruchomić zewnętrzną pamięć Flash z dowolnym mikrokontrolerem w oparciu o programowe SPI. Omówię tu działanie i znaczenie poszczególnych operacji (odczyt, zapis, kasowanie) oraz zaimplementuję je w kodzie.

    Temat użycia takich pamięci wydaje mi się co najmniej interesujący, bo bardzo łatwo jest dostać kilka takich kości za darmo. Są one dostępne w wielu urządzaniach, spotykam je w starych monitorach, telewizorach, odbiornikach telewizji satelitarnej, a nawet w komputerach czy tam laptopach (to ta słynna kość BIOS). Zazwyczaj przechowuje ona program dla mikrokontrolera, ale ja chcę użyć jej ogólnie do zapisu pomiarów i nie tylko.

    Pamięć Flash to pamięć nieulotna, więc przechowywane w niej dane nie są tracone po odłączeniu zasilania, można tam więc przechowywać konfiguracje, pomiary historyczne a nawet pliki.

    Prezentowane pamięci można najczęściej spotkać w rozmiarze 1 Mb do 8 Mb, przykładowo W25Q80 czy W25Q64. Należy pamiętać o różnicy pomiędzy 1 Mb (megabit) a 1 MB (megabajt), gdzie 1 MB = 8 Mb. Oznacza to, że pamięć oznaczona jako 8 Mb ma 1 MB pojemności.

    Prezentację dokonam w oparciu o OBK, ale nie powinno to mieć tu znaczenia.

    Zasadniczo zaimplementujemy to, co robi programator oparty o CH341:



    I zrealizujemy to na dowolnym MCU poprzez podstawowe operacje na cyfrowym GPIO.

    Rozważmy przykładową pamięć odzyskaną z elektrośmieci - 25Q32CSIG. Już sama nazwa nieco nam o niej mówi, bo „25” oznacza interfejs SPI, "Q" sugeruje obsługę trybu Quad SPI, "32" to jej pojemność – 32 megabity, czyli 4 megabajty, a "CSIG" to oznaczenia producenta i rodzaju obudowy. Taka pamięć ma 8 wyprowadzeń i komunikuje się w pełni poprzez SPI, co czyni ją dobrym kandydatem do naszych eksperymentów.
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Przejrzyjmy najważniejsze informacje z noty katalogowej. Napięcie pracy - tutaj do 3.6V, ale to nie problem, gdyż chcę ją uruchomić z MCU działającym na 3.3V. Dodatkowo mamy tu informacje o szybkości i wytrzymałości pamięci - minimum 100 000 cykli Program/Erase, później to wytłumaczę. Dane wytrzymają typowo 20 lat (!), a producent również podaje oczekiwane czasy operacji programowania i nie tylko. Warto zwrócić uwagę na informacje o stronach i sektorach, o tym też za chwilę.

    Zacznijmy jednak od tego, co widać na pierwszy rzut oka. Wyprowadzenia.
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Taka pamięć ma tylko osiem nóżek, a tak naprawdę do MCU będziemy podłączać tylko cztery. Mamy tutaj:
    - zasilanie i masa - wiadomo, tylko warto sprawdzić napięcie pracy
    - SO - Serial Out (wyjście danych)
    - SI - Serial In (wejście danych)
    - SCLK - zegar
    - CS - chip select, pozwala wybrać jedno z wielu urządzeń na wspólnej magistrali SPI (wspólne SO, SI, SCLK)
    - WP - write protect, ochrona przed zapisem. Dla uproszczenia podciągniemy tam stan wysoki, dzięki temu wyłączymy ochronę i umożliwimy zapis do pamięci.
    - HOLD - zatrzymanie transmisji, gdy ustawimy ten pin w stan niski, pamięć "zamraża" transmisję SPI bez utraty synchronizacji.
    Czyli finalnie do działania potrzebujemy tylko czterech linii: CS, SCLK, SI i SO. Resztę można zostawić na stałych stanach logicznych (Vcc lub GND, zgodnie z datasheetem).

    Co do fizycznego podłączenia - można użyć płytek konwerterów SMD na THT lub coś samodzielnie zlutować -> SMD na wierconej płytce prototypowej - co zrobić, gdy nie ma adaptera pod ręką

    Teraz pojawia się pytanie co musimy z tymi pinami robić. Tu trzeba wiedzieć dwie rzeczy.

    Po pierwsze, mamy tu zasadniczo trzy operacje:
    - kasowanie (erase) - ustawia komórki pamięci na 1 (bajty na 0xFF)
    - programowanie (program) - ustawia wybrane bity pamięci na 0
    - odczyt - po prostu odczytuje wybrane komórki pamięci
    Tu już pojawia się ważna obserwacja, a mianowicie, jeśli chcemy zapisać coś do pamięci, to trzeba najpierw wykasować daną sekcję (o tym zaraz), a potem zaprogramować ("zgasić") wybrane bity. To może być niespodzianką dla osób, które myślą że po prostu jest osobno zapis i odczyt i tyle starcza. Kasowanie można pominąć tylko w jednym przypadku - jeśli chcemy tylko gasić bity i nie musimy ich zapalać.

    Po drugie, pamięć zorganizowana jest w sekcje zwane stronami/sektorami i to często właśnie na nich się operuje. Mówi o tym więcej sekcja Memory Organization:
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie

    Wyposażeni w tę wiedzę możemy zapoznać się z tabelą komend. To są rozkazy, które wysyłamy przez SPI. Tylko część z nich będzie nam potrzebna:
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Komend jest jednak trochę dużo, więc przygotowałem dla Was wybór niezbędnego minimum, który moim zdaniem jest potrzebny do operacji na takiej pamięci.
    1. Ustawienie bitu Write Enable (0x06, zezwolenie na zapis):
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Ustawienie bitu WEL (Write Enable Latch) jest niezbędne przed każdą operacją programowania strony oraz przed operacjami kasowania.
    2. Kasowanie sektora pamięci (0x20):
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Komenda wymaga podania 3 bajtów adresujących sektor. Kasowanie rozpoczyna się po podniesieniu CS. Nie można wykonywać wtedy kolejnych operacji, trzeba sprawdzać bit Write In Progress (trwa zapis) z rejestru Status by dowiedzieć się kiedy operacja się zakończy.
    3. Odczyt rejestru status (0x05):
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    To jest wspomniane powyżej sprawdzanie rejestru Status.
    4. Programowanie strony (0x02):
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Programowanie strony pozwala na gaszenie bitów w obrębie jednej strony, choć sam adres może wskazywać w jej dowolnym miejscu. Trzeba jedynie dzielić komendy Program tak, by dotyczyły jednej strony, nie jest wspierane "przekroczenie" granicy na drugą stronę. Te programowanie nie jest w stanie "zapalać" bitów, jedynie może je "gasić", dlatego zazwyczaj poprzedza je cykl erase.
    5. Odczyt (0x03):
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Odczyt jest najprostszą komendą. Całą pamięć można wczytać jednym odczytem, nie trzeba go rozdzielać.

    Pora przejść do implementacji.
    Zakładam tu podstawową znajomość SPI, ale i tak pokażę implementację z mojego środowiska. Całość wykonywana jest programowo w oparciu o bit-banging. Najpierw struktura - indeksy GPIO:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Implementacja:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Mamy tu kolejno:
    - SPI_Send() – funkcję wysyłającą bajt przez SPI, bit po bicie od najbardziej znaczącego. Po każdej zmianie linii MOSI generowany jest impuls zegarowy
    - SPI_Read() – funkcję do odbioru bajtu przez SPI. Każdy bit odczytywany jest po podniesieniu zegara i składany do pełnego bajtu
    - SPI_Begin() i SPI_End() – proste funkcje zarządzające linią CS (chip select), odpowiednio aktywując i dezaktywując pamięć flash
    - SPI_Setup() – przygotowanie pinów GPIO: ustawienie odpowiednich kierunków i stanów początkowych

    Warto zauważyć, że HAL_PIN_... to abstrakcja sprzętowa – w moim przypadku wiąże się z GPIO konkretnego mikrokontrolera. W Waszym przypadku może to być inna biblioteka lub bezpośrednia manipulacja rejestrami. W Arduino można tam po prostu dać słynne digitalRead i digitalWrite.

    Na bazie tego interfejsu możemy już pisać funkcje wyższego poziomu – np. flash_write_enable(), flash_erase_sector(address), flash_read(address, buffer, length), itd. Ale zanim do tego przejdziemy, dobrze jest jeszcze sprawdzić, czy pamięć działa – czyli odczytać jej ID.
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Tym właśnie zajmiemy się w kolejnym kroku – odczytem JEDEC ID (komenda 0x9F), co pozwala potwierdzić, że układ działa, że linie są dobrze podłączone, a komunikacja SPI przebiega poprawnie. Dzięki temu łatwo też wykryć błędy w podłączeniach lub konfiguracji GPIO.

    Mamy już określone funkcje SPI i mamy już przebieg komunikacji pokazany na rysunku z noty katalogowej. Wystarczy to wpisać w kod:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    U mnie wyszło to tak jak powyżej. Jeszcze dodałem komendę na wywołanie tego z panelu kontrolnego:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Moje stanowisko testowe - pamięć jest przylutowana do płytki konwertera na rozmiar DIP:
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Rezultaty testu:
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Odczyt ID działa - otrzymaliśmy EF 40 16. Można to teraz zinterpretować.
    - EF – Winbond Electronics (producent)
    - 40 – rodzina układów (tutaj oznacza standardową serię SPI NOR Flash)
    - 16 – pojemność układu, w tym przypadku 0x16, co odpowiada 32 Mbit, czyli 4 MB
    Ten wynik potwierdza, że mamy do czynienia z układem W25Q32 - tak jak pisałem. Oznacza to, że połączenia są poprawne, SPI działa prawidłowo, a układ odpowiada na polecenia.

    Teraz możemy wdrożyć pozostałe komendy. Oczekiwanie na ukończenie operacji:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Powyższy kod odczytuje w pętli rejestr statusu i sprawdza czy dany jego bit jest zgaszony. Jeśli nie jest zgaszony, to odczekuje krótki moment (dłuższy z każdą iteracją) i ponownie wykonuje odczyt. Na ten moment nie dałem tam zabezpieczenia przed ewentualną pętlą bez wyjścia.

    Kasowanie sektora:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Powyższy kod po prostu wysyła omawianą komendę kasowania sektora wraz z adresem a potem oczekuje na jej wykonanie.

    Zapis (z uwzględnieniem podziału na strony):
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Powyższy kod działa zgodnie z dokumentacją operacji Page Program 02H, czyli respektuje granice stron i odpowiednio dzieli wysyłane dane. Dzięki temu możemy wysyłać dowolny ciąg bajtów a on i tak zostanie poprawnie zaprogramowany.

    Odczyt pamięci:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Tu nie ma większego problemu. Odczytywać można wszystko jednym blokiem.

    W oparciu o te funkcje przygotowałem krótką metodę testującą pamięć w wybranym zakresie:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Metoda pobiera za argument adres, ilość bajtów oraz bajt wzorcowy. Na początku kasowana jest wymagana ilość stron, potem skasowanie jest weryfikowane, a dalej pamięć jest programowana podanym bajtem. Na koniec odczyt weryfikuje to, co zaprogramowaliśmy.
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Taka metoda pozwala w pełni sprawdzić poprawność komunikacji wraz z uwzględnieniem podziału zapisu na strony.

    Na koniec zostaje tylko jedno pytanie - czy można jakoś ustawić na daną wartość tylko i wyłącznie wybrany przez nas bajt?
    Zasadniczo nie ma tu takiej bezpośredniej opcji. Komenda Page Program pozwala nam gasić dowolne bity, ale nie daje nam ich zapalać. W sytuacji gdy chcemy zapalić jeden z bitów, musimy odczytać cały sektor (4KB), zapamiętać go w RAM, wykonać na nim erase, a potem zmodyfikować go w RAM i wgrać ponownie.

    Czy to jednak jest aż tak źle? To zależy, można to wykorzystać też do optymalizacji zużycia pamięci. Pamięć zużywają cykle kasowania, a nikt nie mówi, że każdy zapis kilku bajtów musi oznaczać kasowanie... można wydzielić np. strukturę 64 bajtów i zapisywać ją na stronie o rozmiarze 4096 całe 64 razy, na kolejnych komórkach pamięci, a przy odczycie jedynie sprytnie sprawdzać, która jest aktualna. To może przydać się do zapisu małych ilości danych które są często zmieniane.

    Podsumowując, pamięć Flash z interfejsem SPI jest bardzo prosta w uruchomieniu. Wcale nie trzeba do tego dużo kodu, ani też nie potrzeba zewnętrznych bibliotek. Wystarczy zaimplementować kilka podstawowych operacji oraz mieć na uwadze ograniczenia takiej pamięci (wymóg kasowania sektorami) by móc wykorzystywać ją do zapisywania danych z własnego programu.
    Zostaje tylko pytanie jakich danych i co w praktyce z kasowaniem - postaram się to pokazać w kolejnej części. Osobno również spróbuję zoptymalizować programowy SPI tak, by każda transakcja trwała znacząco krócej.
    Czy korzystacie z dodatkowych kości pamięci Flash w swoich projektach?

    PS: To nie koniec przygody z pamięciami Flash - planowane są co najmniej takie kolejne części:
    - Ręczny zapis wyników pomiarów w pamięci Flash (stacja pogodowa?)
    - Optymalizacja (redukcja) cykli kasowania (jeden cykl kasowania na wiele zapisów małej struktury danych)
    - Optymalizacja bit-bang poprzez pominięcie HAL i bezpośredni zapis do rejestrów na BK7231
    - Uruchomienie miniaturowego systemu plików LittleFS na zewnętrznej kości Flash 8 Mb

    Fajne? Ranking DIY
    Pomogłem? Kup mi kawę.
    O autorze
    p.kaczmarek2
    Moderator Smart Home
    Offline 
    Inżynier programista z wieloletnim doświadczeniem embedded i full stack developer.
    Specjalizuje się w: embedded, Full-Stack Developer
    p.kaczmarek2 napisał 14615 postów o ocenie 12633, pomógł 655 razy. Jest z nami od 2014 roku.
  • REKLAMA
  • #3 21598443
    p.kaczmarek2
    Moderator Smart Home
    Posty: 14615
    Pomógł: 655
    Ocena: 12633
    To zależy od wielu czynników.
    Sprawdziłem to na razie dla takiej sytuacji:
    - nowsza wersja kodu obsługi programowej SPI (bit-banging z bezpośrednim pisaniem po rejestrach, ale rozwinięte pętle, itd)
    - początkowy rozmiar zawiera funkcję testującą zapis/odczyt
    - ta funkcja zawiera komunikaty na uART
    Różnica wyniosła 1-1.5 kilobajtów:
    Dodatkowa pamięć Flash na pomiary za darmo? Protokół komunikacji, zapis, odczyt, kasowanie
    Zaraz zrobię drugi test, sprawdzę ile pamięci to używało w wersji z normalnym HAL

    Added after 14 [minutes]:

    EDIT: Użycie "szybkiego" programowego SPI (zamiast prostego HAL i pętli) to kwestia 200-300 bajtów więcej.
    Pomogłem? Kup mi kawę.
📢 Słuchaj (AI):
REKLAMA