Obsługa pamięci masowych z USB za pomocą mikrokontrolerów LPC1300 firmy NXP

Konfiguracja przerwań sterownika USB

Wbudowany sterownik USB wykorzystuje przerwania, aby odpowiadać na zdarzenia generowane przez kontroler USB mikrokontrolera LPC1300. Przerwanie USB znajduje się w tablicy wektorów przerwań rdzenia Cortex, która zaczyna się od 0x00000000. Kiedy takie przerwanie zostanie odebrane przez kod aplikacji, musi zostać przekazane do programu obsługującego przerwania (interrupt handler) w sterowniku USB, znajdującego się w pamięci ROM.

W układach LPC1300 kod użytkownika jest kompilowany i wprowadzany do pamięci Flash, gdzie zaczyna się od adresu 0x00000000. Przy resecie jednak, wbudowany ROM jest mapowany na 0x00000000, zamiast pamięci Flash. Gdy wykonany zostanie zamieszczony w pamięci ROM kod inicjalizacyjny, pamięć jest mapowana ponownie, tak że Flash rozpoczyna się znowu od adresu 0x00000000, a ROM (zawierający sterownik USB) – od 0x1FFF1000. W tym momencie, obsługa przerwań jest przekazywana z ROMu do pamięci Flash, zawierającej kod użytkownika.

Aby zapewnić wywoływanie handlera przerwań USB mieszczącego się w pamięci ROM, w kodzie użytkownika musi być zadeklarowane przerwanie, wywołujące go. Jeśli użyjemy standardowych nagłówków CMSIS dla rdzeni Cortex, program obsługi przyjmuje postać taką, jak jest to przedstawione poniżej, niezależnie od tego, z jakiego środowiska programistycznego korzystamy.

 

USB_IRQHandler(void)

{

(*rom)->pUSBD->isr();

}

Konfiguracja struktur danych dla sterownika USB

Aby można było używać wbudowanego sterownika USB w trybie Mass Storage Class, trzeba skonfigurować kilka struktur danych. Konieczna jest deklaracja struktury USB_DEV_INFO, której składnik DevType musi zostać zainicjalizowany jako USB_DEVICE_CLASS_STORAGE, a składnik DevDetailPtr wskazywać na strukturę MSC_DEVICE_INFO. Z kolei w MSC_DEVICE_INFO, składnik StrDescPtr trzeba zainicjalizować, by wskazywał na USB String Descriptor używanego układu. Najważniejsze natomiast jest zdefiniowanie funkcji odczytujących i zapisujących bloki danych do pamięci lokalnej (na której mają docelowo być przechowywane) oraz inicjalizacja struktury MSC_DEVICE_INFO tak, by wskazywała na te funkcje. W zamieszczonym poniżej prostym przykładzie odczytane i zapisane zostaną dane do wbudowanej pamięci RAM, ale możliwa jest również implementacja odczytu i zapisu na zewnętrzny Flash przez SPI. Szczegółowe informacje o polach opisywanych struktur można znaleźć w podręczniku użytkownika (User’s Manual), w rozdziale zatytułowanym USB on-chip driver.

Inicjalizacja struktury USB_DEVICE_INFO

Aby zaznaczyć zainicjalizowanie sterownika do pracy w trybie Mass Storage Class należy ustawić pole struktury DevType. Z kolei DevDetailPtr wskazuje na strukturę _DEVICE_INFO zawierającą specyficzne dla trybu MSC pola konfiguracyjne.

DeviceInfo.DevType = USB_DEVICE_CLASS_STORAGE;

DeviceInfo.DevDetailPtr = (uint32_t)&MscDevInfo;

Inicjalizacja struktury MSC_DEVICE_INFO

Struktura MSC_DEV_INFO zawiera informacje potrzebne do konfiguracji wbudowanego sterownika USB, aby zaimplementować urządzenie USB klasy Mass Storage Class. Kluczowymi składowymi do zainicjalizowania są identyfikatory dostawcy (Vendor ID) i produktu (Product ID), opis tekstowy (string descriptor) oraz liczba i rozmiary bloków. Dla napędów dyskowych standardowy rozmiar bloku to 512 bajtów. Liczba bloków powinna być ustawiona tak, by odpowiadać liczbie bloków w używanym mikrokontrolerze. MSC_MemorySize powinno z kolei być równe MSC_BlockSize*MSC_BlockCount.

 

MscDevInfo.idVendor = USB_VENDOR_ID;

MscDevInfo.idProduct = USB_PROD_ID;

MscDevInfo.StrDescPtr = (uint32_t)&USB_StringDescriptor[0];

MscDevInfo.BlockSize = MSC_BlockSize;

MscDevInfo.BlockCount = MSC_BlockCount;

MscDevInfo.MemorySize = MSC_MemorySize;

 

Do odczytu i zapisu danych do pamięci lokalnej potrzebne są dwie funkcje, które przyjmują jako argumenty: offset bajtowy, mały bufor w pamięci RAM oraz długość. Wskaźniki na funkcje, nazwane MSC_Write i MSC_Read, które znajdują się w strukturze HID_DEVICE_INFO trzeba zainicjalizować tak, by wskazywały na te funkcje, żeby sterownik USB mógł je wywoływać i transferować dane, gdy komputer-host zgłasza żądania odczytu lub zapisu na urządzenie MSC.

 

MscDevInfo.MSC_Read = MSC_MemoryRead;

MscDevInfo.MSC_Write = MSC_MemoryWrite;

Inicjalizacja pola USB string descriptor

String Descriptor jest tablicą znaków dwubajtowych, zapewniającą czytelny dla człowieka opis, ułatwiający identyfikację i instalację urządzenia. String Descriptor używa znaków w formacie Unicode, aby umożliwić zapis w różnych językach. Szczegóły dotyczące formatu pola String Format można znaleźć w specyfikacji standardu USB.

 

/* USB String Descriptor (optional) */

const uint8_t USB_StringDescriptor[] = {

/* Index 0x00: LANGID Codes */

0x04,                              /* bLength */

USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */

WBVAL(0x0409), /* US English */    /* wLANGID */

/* Index 0x04: Manufacturer */

0x1C,                              /* bLength */

USB_STRING_DESCRIPTOR_TYPE,        /* bDescriptorType */

’N’,0,

’X’,0,

’P’,0,

’ ’,0,

’S’,0,

’E’,0,

’M’,0,

’I’,0,

’C’,0,

’O’,0,

’N’,0,

’D’,0,

’ ’,0,

 

Funkcje obsługi wywołania

Jako dodatek do konfiguracji opisanej powyżej, w tym małym rozdziale podana jest lista funkcji, które trzeba wywołać, aby dopełnić inicjalizację wbudowanego sterownika USB. Aby dowiedzieć się więcej, warto przeanalizować przykładowy projekt.

  1. Uruchomić 32-bitowy Timer 1
    Timer32 1 jest używany przez sterownik umieszczony w pamięci ROM do wewnętrznego zliczania i nie można go używać przez pisaną aplikację.
  2. Wywołać init_clk_pins();
  3. Wywołać init();
  4. Wywołać connect();

Do pobrania

O autorze