
Witajcie moi drodzy
Przedstawię tutaj przykład użycia 32-bitowego mikrokontrolera PIC32MX250F128B w roli hosta USB obsługującego pendrive (zapisującego i odczytującego z niego dane). W temacie opiszę najpierw płytkę którą pod tego PICa zaprojektowałem, a potem przykładowe kody obsługi pendrive w języku C, w środowisku MPLAB X i kompilatorze XC32. Wszystko będzie w załącznikach do pobrania.
Użyty mikrokontroler - PIC32MX250F128B
Do przedstawionego tu projektu użyłem 32-bitowego mikrokontrolera PIC32MX250F128B od Microchipa.

PIC32MX250F128B pracuje na napięciu 2.3V-3.6V, oferuje 128KB pamięci Flash (w wersji PIC32MX270F256B aż 256KB), 32KB SRAM, dwa interfejsy UART, dwa interfejsy SPI, dwa interfejsy I2C, sprzętowe USB które może pracować zarówno w trybie Device jak i Hosta. Użycie wszystkiego ułatwia w nim funkcjonalność PPS (Peripheral Pin Select), która pozwala w pewnym zakresie zremapować różne funkcjonalności na różne piny (np. możemy sobie wybrać gdzie będzie UART RX).
Więcej szczegółów o nim znajdziecie u producenta:
https://www.microchip.com/wwwproducts/en/PIC32MX250F128B
oraz w nocie katalogowej:
Przykładowy hardware - zaprojektowana płytka
Specjalnie pod ten projekt przygotowałem prostą płytkę z PIC32MX250F128B w obudowie DIP28. Użyłem do tego darmowej wersji programu Eagle. Na płytce znajduje się tylko to co niezbędne, dodatkowe wyprowadzone są wszystkie piny mikrokontrolera tak by można było za jej pomocą łatwo robić prototypy.
Cała płytka korzysta z elementów przewlekanych, by każdy mógł ją łatwo złożyć.
Na płytce znajduje się:
- PIC32MX250F128B w DIP28 (najlepiej w podstawce; inne PIC32MX220*/PIC32MX270* tu też przypasują; PIC32MX270F256B tu też pasuje, a oferuje aż 256KB zamiast 128KB flash)
- rezonator kwarcowy 8MHz, on jest niezbędny dla USB (wewnętrzny oscylator PICa bywa zbyt niedokładny)
- regulator LDO TC1264 by zapewnić 3.3V dla PICa
- złącze USB typu B tylko do podpięcia zasilania 5V
- złącze USB żeńskie (to tutaj podłączamy pendrive)
- złącze DC Jack wraz z 7805 też tylko do podpięcia zasilania
- zworka za pomocą której wybieramy źródło zasilania układu (albo z USB typu B albo ze złącza jack)
- dwie diody LED których anody są wyprowadzone na goldpiny (możemy je podłączyć gdzie chcemy i sterować nimi z PICa)
- złącze ICSP by podłączyć PICKIT3 lub inny programator
- przycisk RESET
- dodatkowo w układzie zasilania są dwie diody 1N4001 (lub podobne; warto dać coś o mniejszym spadku napięcia) w celu ochrony układy przed odwrotnym podłączeniem polaryzacji
Poniżej finalna wersja płytki w programie Eagle:


Wymiary płytki wyszły niecałe 40mm na 77mm.
Schemat płytki do pobrania:
Projekt Eagle do pobrania:
Wyeksportowane PCB do gerberów do pobrania:
Lutowanie płytki
Płytki zamówiłem w chińskiej firmie, 5 sztuk. Jakby ktoś był chętny, to mi jeszcze ich kilka zostało.
Tym razem obyło się bez poprawek na płytce, wszystko wyszło bezbłędnie:


Przygotowałem sobie wszystkie elementy:

Lutowanie poszło przyjemnie i sprawnie:

Przy projektowaniu płytki specjalnie zdecydowałem się na THT (montaż przewlekany) by łatwo się prototypowało i by w razie czego ktoś inny początkujący mógł skorzystać z mojego projektu.

Na koniec sprawdziłem czy wszystko jest ok. Najpierw bez PICa, a potem już z PICem za pomocą najprostszego programu blink, ale o tym już w dalszym akapicie.

Użyte IDE i kompilator
Przedstawiony tu przykłady przeznaczone są dla środowiska MPLAB X IDE v5.20 i kompilatora XC32 v2.20. Kompilator tej w darmowej wersji można pobrać ze stron Microchipa.

MPLAB użyłem w połączeniu z programatorem PICKIT3 (MPLAB pozwala bezpośrednio ze swojego IDE programować PICa przez PICKIT3 co jest bardzo wygodne):

Użyta biblioteka
Projekt korzysta z biblioteki opracowanej przez Microchip, opisanej w jego nocie aplikacyjnej AN1045 "Implementing File I/O Functions Using Microchips Memory Disk Drive File System Library", do pobrania tutaj:
https://www.microchip.com/wwwAppNotes/AppNotes.aspx?appnote=en532040
Jako załącznik:
Opisana tam jest szczegółowo biblioteka która obsługuje nośniki pamięci Flash (nie tylko przez USB, ale np. też kartę SD przez SPI).
Biblioteka ta wspiera systemy plików FAT16 i FAT32.
Biblioteka posiada osobne moduły odpowiedzialne za np. wyszukiwanie plików lub obsługę FAT32, moduły te można wyłączyć by oszczędzić zużycie pamięci. Szczegóły:

W załączonym pdfie jest szczegółowy opis funkcji tej biblioteki, a nawet przykłady jej użycia:


Drugim bardzo dobrym źródłem informacji z którego korzystałem jest repozytorium github autorstwa douglaskatz i fkummer, dostępne tutaj:
https://github.com/douglaskatz/PIC32-Mass-Storage
Kopia zapasowa repozytorium:
Znajdują się w nim pełne przykłady Write/Read/Seek wcześniej wspomnianej biblioteki.
Funkcje obsługi plików z biblioteki
Warto tutaj podkreślić, że funkcje obsługi plików (oraz cały koncept obsługi plików) w tej bibliotece jest taki sam jak ogólnie w języku C (zarówno na Windowsie, jak i na innych platformach).
Pliki są reprezentowane jako uchwyt do pliku, można je otwierać w różnych trybach (odczyt, zapis, append czyli dopisywanie), można w nich przesuwać kursor (miejsce skąd czytamy), czytać dane oraz zapisywać.
Kilka podobieństw poniżej:
Na PIC32 | Zazwyczaj w języku C | Opis | Przykład użycia w kodzie |
FSFILE | FILE | Typ zmiennej reprezentującej plik | FSFILE * myFile |
FSfopen | fopen | Funkcja otwierająca plik w danym trybie | myFile = FSfopen("data.bin","r") |
FSfseek | seek | Funkcja ustawiająca kursor odczytu/zapisu na danym bajcie w pliku | FSfseek(myFile, 0, SEEK_END) |
FSftell | ftell | Funkcja zwraca bieżącą pozycję kursora w pliku | fl = FSftell(myFile) |
FSfread | fread | Funkcja wczytuje daną ilość razy daną ilość bajtów do pamięci z pliki i automatycznie przesuwa kursor dalej | FSfread(&ex,sizeof(ex),1,myFile) |
FSfwrite | fwrite | Funkcja zapisuje daną ilość bajtów daną ilość razy z pamięci do pliku | FSfwrite(txt, 1, stringLength, myFile ) |
FSfclose | fclose | Funkcja zamyka plik i zapisuje zmiany | FSfclose(myFile) |
Ogólnie, wszystko działa tak jak normalnie w C, można wzorować się na kodach dla FILE * z sieci, np. stąd:
https://www.tutorialspoint.com/c_standard_library/c_function_fread.htm
http://www.cplusplus.com/reference/cstdio/fread/
https://www.educative.io/edpresso/c-reading-data-from-a-file-using-fread
Przykład 0 - miganie diodą
Na samym początku tylko chciałem sprawdzić czy wszystko z PICem jest okej, więc uruchomiłem na nim klasyczny 'blink led', czyli miganie diodą. Kod blinka bazowałem na przykładzie z forum Microchipa, który jest o tyle ciekawie napisany, że robi wszystko "na piechotę" i nie korzysta z użytecznych funkcji bibliotecznych. Ten przykład (tak jak pozostałe tutaj) kompilowałem w MPLAB X kompilatorze XC32, a nie w Mikro C PRO for PIC32 z którego korzystam z reguły.
Cały kod:
Code: c
Pełny projekt do pobrania wraz z .hex:
Kod wgrałem poprzez PICKIT3 bezpośrednio z MPLAB. Rezultat działania:
Przykład 1 - prosty zapis danych na pendrive
Przykład ten to przykład 'read' z wcześniej wspomnianego PIC32-Mass-Storage. Musiałem go przekonwertować do nowszej wersji MPLAB, dodatkowo zmieniłem pinologię tak by była zgodna z moją płytką i PICem (diody oznaczające stan podłączenia pendrive/zapisu):
Code: c
(Pełna wersja w załącznikach)
Powyższy kod wypisuje też dodatkowe informacje na UART. UART (a dokładniej - UART2, moduł drugi UART) jest zmapowany na piny (RX) i RPB14 (TX) za pomocą PPS, tutaj:
Code: c
W razie potrzeby można zmapować UART na inne piny. Dostępne piny dla danych funkcjonalności są opisane w nocie katalogowej w dwóch miejscach, w TABLE 11-2: OUTPUT PIN SELECTION:

oraz w TABLE 11-1: INPUT PIN SELECTION:

UART podłączyłem poprzez najprostszą przejściówkę na USB:

Po stronie komputera do odbioru komunikacji UART użyłem RealTerm:

Do pierwszego testu działania użyłem pendrive Kingston 32GB:

Przykład ten wymaga odpowiednio sformatowanego pendrive - wspiera system plików FAT32:


Przedstawiony tutaj przykład działa w prosty sposób. Na początku obie diody LED są zgaszone. Po podłączeniu nośnika USB zapala się czerwona dioda LED i rozpoczyna się próba zapisu. Jak zapis się powiedzie to zapala się zielona dioda LED. Potem można odłączyć pendrive i obie diody LED gasną.
Przed podłączeniem pendrive:

Tuż po podłączeniu:

Tuż po pomyślnym zapisie:


Po odłączeniu:

Rezultat na pendrive:

Potem z ciekawości sprawdziłem, czy biblioteka da sobie radę z kartą SD 64GB w przejściówce na USB:

Próbowałem zapisać do niej na różne sposoby. Było troszkę kłopotu, bo systemy plików FAT16/FAT32 nie wspierają aż tak dużych partycji. System Windows nie dawał nawet mi możliwości sformatować tego nośnika pod FAT. Musiałem na karcie wydzielić osobną, mniejszą partycje, specjalnie pod zapis danych z PICa.
Użyłem do tego MiniTool Partition Wizard Free.
Najpierw skasowałem partycje z karty SD (opcja Delete):

Następnie utworzyłem partycję FAT32 o rozmiarze 32GB:

Po tym jeszcze trzeba zapisać zmiany (przycisk Apply).
Tak przygotowany nośnik pamięci podłączyłem do układu i okazało się, że wszystko działa - na karcie SD pojawił się wyczekiwany plik wraz z zawartością:


Czyli ze strony hosta USB nie widać chyba nawet różnicy między pendrive a kartą SD w takiej przejściówce.
Przykład 2 - zapis dużej ilości danych
Następnie chciałem sprawdzić czy biblioteka jest w stanie sobie poradzić z większą ilością danych do zapisu. W tym celu nieco zmodyfikowałem poprzedni przykład. Użyłem pętli for by zapisywać do pliku tekstowego kolejne liczby:
Code: c
Do zamiany liczby całkowitej (typ int) na napis (tablicę charów) w powyższym kodzie użyłem funkcji sprintf.
Czas zapisu po podłączeniu pendrive do płytki nieco się wydłużył. Oczywiście, można by go skrócić optymalizując samą metodę zapisu (wywoływać rzadziej FSfwrite, dla większej ilości danych na raz) ale nie to było celem przykładu. Warto też zwrócić tu uwagę na to, że mimo dużej ilości danych tworzonych przez ten program użycie pamięci jest bardzo małe.
Rezultat na pendrive dla i = 10 000:


Rezultat na pendrive dla i = 100 000:

Utworzony plik ma całe 673KB!
Rezultat na pendrive dla i = 1 000 000:

Plik ma prawie 8MB.
Przykład 3 - listowanie/wyszukiwanie plików
Następnie sprawdziłem listowanie/wyszukiwanie plików wedle wzorca, czyli funkcje FindFirst i FindNext. Działają one analogicznie do FindFirstFile/FindNextFile z WINAPI. Znajdują pliki i katalogi pasujące do wzorca, przy okazji zwracając ich atrybuty, rozmiar, itp.
Przykład poniżej:
Code: c
W ramach testu przygotowałem kilka plików na pendrive:

Następnie sprawdziłem, czy wyszukiwanie działa. Rezultat:

Rozmiary plików wyglądają ok. Widać różne wartosci atrybutów dla folderów i plików. Dodatkowo na liście znalezionych znaduje się nazwa 'NEW', czyli nazwa partycji która też jest widoczna w Windowsie.
Przykład 4 - odczyt pliku linia po linii
Pliki tekstowe wygodnie jest czytać linia po linii. Nie potrzebujemy wtedy dużego bufora, a możemy tak przetworzyć duże ilości danych. W bibliotece brakowało mi odpowiednika getline, więc napisałem go samodzielnie. Skrócony kod:
Code: c
Kod powyżej otwiera plik data.txt, czyta go linia po linii i wypisuje ich zawartości na UART (bez znaków nowych linii z pliku).
Rezultaty kodu jeśli na pendrive nie ma takiego pliku - program wyświetli odpowiedni komunikat:

Natomiast jeśli na pendrive będzie plik:


Wszystko jest okej - plik jest odczytywany poprawnie.
Przykład 5 - test pliku 1GB i funkcji seek
(Uwaga - to nie jest przykład Seek z zalinkowanego wcześniej repozytorium. To jest inny przykład, mój własny, dla pliku binarnego.)
Na koniec chciałem sprawdzić jak biblioteka poradzi sobie z plikiem o rozmiarze ponad 1000MB. Zdecydowałem, że to będzie plik binarny, w którym powtarzać się będzie wielokrotnie jedna struktura danych (tablica znaków i kilka liczb). W ramach przykładu za pomocą funkcji fseek i fread odczytam wartość jednej ze struktur na środku pliku.
Zdecydowałem się na taką strukturę:
Code: c
Struktura ma 76 bajty (na obu użytych platformach typ int jest 32-bitowy, char to jeden bajt; ale warto pamiętać, że rozmiar typów na innych platformach może być inny).
Na Windowsie, też w języku C przygotowałem program generujący losowo przykładowe dane wedle ustalonej struktury:
Code: c
Pełna wersja w załącznikach.
Do pliku w sumie trafiło 14,000,000 struktur, każda po 76 bajtów. W sumie około 1GB danych.
Przy okazji zapisałem co jest w pliku na pozycji 7,000,000-tej struktury:

Wygenerowany plik powstał obok programu na Windowsie:

Plik przerzuciłem na pendrive.
Do odczytu na PIC32 użyłem następującego kodu:
Code: c
Rezultat na UART:

Dane odczytane przez PIC zgadzają się z tymi co wygenerowałem na Windowsie.
Jak widać, PIC poradził sobie z wyłuskaniem danych z pliku o rozmiarze 1GB. Oczywiście należy tu pamiętać, że PIC nie przetworzył całego pliku, tylko odczytał z niego dane z wybranej pozycji.
Pełne kody/projekty moich przykładów do pobrania
Kody powyższych przykładów użycia hosta USB do obsługi pendrive/karty SD umieszczam do pobrania poniżej:
Zasadniczo jest to zaktualizowane repozytorium wspomniane wcześniej, do którego dodałem kolejne przykłady.
Zawartość paczki .zip:

Dalsze plany
Na przedstawionej tutaj płytce można zrealizować znacznie więcej ciekawych projektów wykorzystujących tryb hosta USB, np:
- obsługa myszki USB
- obsługa klawiatury USB
- obsługa pada USB
- obsługa huba USB
- aktualizacja firmware poprzez bootloader z pendrive
Mam w planach zrobić niedługo do któregoś z tych tematów podejście. Po zrealizowaniu każdego z pomysłów będę dawać pełen kod i dokumentację na forum.
Podsumowanie
Przetestowałem tutaj PIC32MX250F128B w roli hosta USB obsługującego pendrive i kartę SD w czytniczku. W obu przypadkach wszystko zadziałało.
Biblioteka pozytywnie mnie zaskoczyła - obsługuje nawet system plików FAT32 i jest w stanie przetworzyć pliki nawet o wielkości 1GB. W przyszłości użyję tego w jakiś konkretniejszych projektach.
Cool? Ranking DIY