Obsługa FAT w mikrokontrolerach STM32 – obsługa kart
Moduł FatFs
System plików w urządzeniach wbudowanych można zaimplementować na dwa sposoby: pisząc obsługę systemu plików od podstaw, lub też wykorzystać gotowe rozwiązania. W zasadzie nie ma żadnego sensownego uzasadnienia pierwsze wymienione podejście. System plików FAT jest na tyle dobrze udokumentowany, a przy tym stosunkowo prosty, że powstało wiele darmowych narzędzi, które radzą sobie bardzo dobrze z administracją zawartości nośnika danych z systemem plików FAT. Z reguły otwarty charakter kodu pozwala na wprowadzenie koniecznych zmian i poprawek, które mogą się okazać niezbędne dla stabilności pracy urządzenia.
Jednym z takich ogólnodostępnych narzędzi jest moduł FatFs, którego zadaniem jest stanowienie pomostu pomiędzy warstwą fizyczną (nośnikiem pamięci), a aplikacją uruchomioną na mikrokontrolerze. Szczegółowych informacji na temat FatFs należy szukać na stronie internetowej autora: http://elm-chan.org/fsw/ff/00index_e.html . Rolę modułu FatFs zilustrowano na rys. 3.
Rys. 3. Lokalizacja modułu FatFs w projekcie programistycznym

Rys. 4. Struktura plików FatFs
Sam moduł FatFs jest napisany języku C. Pliki, które są niezbędne do poprawnej pracy FatFs przedstawiono na rys. 4 w formie drzewa skopiowanego z projektu wykorzystującego system plików FAT. Teoretycznie, do poprawnej pracy moduł FatFs wymaga obecności w systemie wbudowanym zegara czasu rzeczywistego (RTC). Można ten wymóg bardzo łatwo obejść wpisując stałe wartości w miejsce daty i czasu.
Implementacja FatFs w mikrokontrolerach STM32 – warstwa fizyczna
Do opracowania warstwy sprzętowej został wykorzystany przykładowy projekt, zamieszczony na stronie internetowej modułu FatFs. Wszystkie funkcje, których zadaniem jest sterowanie urządzeniami peryferyjnymi mikrokontrolera, oraz zapis i odczyt danych z karty pamięci, zostały umieszczone w jednym pliku sd_stm32.c .
List. 1. Funkcja rcvr_spi() odpowiedzialna za odbieranie danych z kontrolera magistrali SPI
static
void SELECT (void) // CS w stan niski
{
GPIO_ResetBits(GPIOC, GPIO_Pin_12);
}
static
void DESELECT (void) // CS w stan wysoki
{
GPIO_SetBits(GPIOC, GPIO_Pin_12);
}
static
void xmit_spi (BYTE Data) // Wyslanie bajtu do SD
{
// Wyslanie bajtu
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, Data);
}
static
BYTE rcvr_spi (void) // Odebranie bajtu z SD
{
u8 Data = 0;
// Wyslanie 0xFF
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, 0xFF);
// Odebranie bajtu
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
Data = SPI_I2S_ReceiveData(SPI1);
return Data;
}
Bezpośrednio ze sprzętem komunikują się cztery funkcje. Za odbieranie danych z kontrolera magistrali SPI odpowiada funkcja rcvr_spi() , która została przedstawiona, wraz z pozostałymi trzema na list. 1. Wysyłaniem bajtów przez SPI do karty pamięci zajmuje się funkcja xmit_spi() . Do zadań interfejsu sprzętowego należy jeszcze sterowanie sygnałem wyboru układu CS, co należy do obowiązków funkcji SELECT() i DESELECT() .
Czasem, gdy mikrokontroler zażąda dostępu do zasobów karty pamięci, może się okazać, że ta ostatnia jest w danym momencie zajęta wykonywaniem innych operacji. Wtedy istotna jest możliwość sprawdzania zajętości karty. W tym celu została napisana funkcja wait_ready() , przedstawiona na list. 2. Jej zadaniem jest oczekiwanie przez maksymalny czas 500 ms, aż odebrany bajt będzie miał wartość 0xFF. Jeżeli w ciągu 500 ms nie zostanie odebrany bajt 0xFF, to funkcja kończy swoje działanie, zwracając ostatnią wartość odczytaną z kontrolera SPI.
List. 2. Funkcja wait_ready() , której zadaniem jest oczekiwanie przez maksymalny czas do 500 ms, aż odebrany bajt będzie miał wartość 0xFF
static
BYTE wait_ready (void)
{
BYTE res;
Timer2 = 50; // Czeka przez 500ms
rcvr_spi();
do
res = rcvr_spi();
while ((res != 0xFF) && Timer2);
return res;
}
Mamy już wszystkie niezbędne funkcje zajmujące się interfejsem sprzętowym, jednak, aby mogły one w ogóle pracować, to sam sprzęt musi zostać odpowiednio skonfigurowany.
Za konfigurację kontrolera SPI, portów wejścia/wyjścia i ich sygnałów zegarowych odpowiada funkcja power_on() , którą zamieszczono na list. 3. Po zdefiniowaniu zmiennych, wykorzystywanych w dalszym kodzie funkcji, następuje włączenie sygnałów zegarowych dla wyprowadzeń (porty GPIOA i GPIOC) i kontrolera SPI. Następnie konfigurowane jest wyprowadzenie PC12 do sterowania wyborem układu (CS) oraz piny PA5, PA6, PA7 jako linie magistrali SPI.
List. 3. Funkcja power_on() odpowiedzialna za konfigurację kontrolera SPI, portów wejścia/wyjścia i włączenie ich sygnałów zegarowych
static
void power_on (void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
u8 i, cmd_arg[6];
u32 Count = 0xFFF;
// Konfiguracja wyprowadzen i kontrolera SPI:
// Wlaczenie sygnalow zegarowych dla peryferiow
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC |
RCC_APB2Periph_SPI1 | RCC_APB2Periph_AFIO, ENABLE);
// PA4 jako CS
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//SCK, MISO and MOSI
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Konfiguracja SPI
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitStructure);
// Wlacz SPI
SPI_Cmd(SPI1, ENABLE);
// Inicjalizacja karty i przelaczenie w tryb SPI:
DESELECT(); // CS = 1
for (i = 0; i < 10; i++)
xmit_spi(0xFF); // Wyslij 0xFF 10 razy = 80 cykli zegarowych
// (wymaganych co najmniej 74 cykli)
SELECT(); // CS = 0
// Przygotowanie ramki inicjujacej do wyslania
cmd_arg[0] = (CMD0 | 0x40);
cmd_arg[1] = 0; // Argument komendy
cmd_arg[2] = 0; // nawet, gdy komenda go nie ma
cmd_arg[3] = 0; // musi zostac wyslany w postaci zer
cmd_arg[4] = 0;
cmd_arg[5] = 0x95; // CRC = 0x95
for (i = 0; i < 6; i++) // Wyslanie ramki
xmit_spi(cmd_arg[i]);
while ((rcvr_spi() != 0x01) && Count) // Czeka na 0x01
Count--;
DESELECT(); // CS = 1
xmit_spi(0XFF); // Wyslij 0xFF
PowerFlag = 1;
}
Kontroler SPI jest ustawiany jako master do pracy w trybie full dupleks . Ramka danych będzie wynosić 8 bitów, a zatrzaskiwanie stanu linii będzie następować na zboczu narastającym sygnału zegarowego. W stanie nieaktywnym na linii SCK będzie występował stan wysoki. Preskaler dla zegara kontrolera SPI został ustawiony na 4, co oznacza, że dane będą przesyłane z niebagatelną prędkością 18Mbit/sekundę. Po ustawieniu wszystkich parametrów SPI, kontroler zostaje włączony przez wywołanie funkcji SPI_Cmd() .
Od tego momentu mikrokontroler jest już prawidłowo skonfigurowany, natomiast karta SD domyślnie po włączeniu zasilania pracuje w trybie obsługi dedykowanego standardu SDBus. Aby komunikacja (odbieranie komend) była w ogóle możliwa, należy w pierwszej kolejności wysłać co najmniej 74 cykle zegarowe, w celu zainicjowania karty. Następnie, żeby przejść do trybu pracy z SPI, należy wysłać komendę CMD0. Jeśli inicjalizacja karty do pracy w trybie SPI zostanie przeprowadzona poprawnie, to karta zwróci bajt potwierdzenia wynoszący 0x01.
Moduł FatFs wymaga do pracy sygnału zegarowego, który co 10ms będzie wywoływał funkcję disk_timerproc() , która jest wykorzystywana dalej do odmierzania czasu. Do cyklicznego wywoływania wymienionej wyżej funkcji został wykorzystany 24-bitowy timer SysTick. Jego konfiguracja została przedstawiona na list. 4.
List. 4. Procedura odpowiedzialna za konfigurację timera SysTick
void SysTick_Conf(void)
{
// SysTick bedzie taktowany z f = 72MHz/8 = 9MHz
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
// Przerwanie ma byc co 10ms, f = 9MHz czyli liczy od 90000
SysTick_SetReload(90000);
// Odblokowanie przerwania od timera SysTick
SysTick_ITConfig(ENABLE);
// Wlaczenie timera
SysTick_CounterCmd(SysTick_Counter_Enable);
}
Domyślnie główny zegar systemowy, po powieleniu przez pętlę PLL, wynosi 72 MHz i z taką częstotliwością domyślnie jest taktowany SysTick. Aby uzyskać przerwanie co 10 ms zastosowano preskaler, dzielący sygnał 72 MHz przez 8, co w efekcie daje 9 MHz. Jeśli chcemy, aby funkcja obsługi przerwania od timera SysTick ( SysTickHandler() ) była wywoływana z częstotliwością 100 Hz, to należy sprawić, aby licznik liczył od 90000. Bardziej szczegółowo timer SysTick omówiono w rozdziale 5, natomiast w tym przypadku funkcja obsługi jego przerwania wygląda następująco:
void SysTickHandler(void)
{
disk_timerproc();
}
Omówione funkcje są jedynymi zależnymi od sprzętu fragmentami kodu w module FatFs, zatem teraz zajmiemy się już najwyższą jego warstwą, umożliwiającą operacje na plikach i katalogach.

Technologie End of Life i bezpieczeństwo sieci – wyzwania Europy związane z tzw. długiem technologicznym
Najczęstsze błędy firm przy wyborze dostawcy energii i jak ich uniknąć
Fotorezystor, czyli czujnik światła dwojakiego działania. Przykład innowacji w automatyce i elektronice możliwej dzięki technologii fotooporników 



