Komunikacja mikrokontrolerów STM32 z pamięciami DataFlash
Wspomniana wyżej niezerowa wartość zmiennej SPI_InUse wynosząca szesnastkowo 0x11 może wydawać się zupełnie bez sensu. Uzasadnienie znajdujemy w funkcji obsługi przerwania od kontrolera SPI przedstawionej na listingu 3. W części nadawczej, gdy wysłany zostanie ostatni bajt nasza flaga SPI_InUse ma kasowany najmłodszy bit (stąd maska 0x01). W części odbiorczej kasowany jest natomiast bit czwarty (maska 0x10). Te wartości mogą być ustalane dowolnie. Celowo tutaj nie zastosowano definicji w miejsce „magicznych” liczb, ponieważ w surowej postaci łatwiej zrozumieć, co się właściwie dzieje. Przedstawiony wyżej mechanizm to nic innego, jak uproszczona wersja zastosowania muteksów.
Zatrzymajmy się na chwile przy muteksach, aby wyjaśnić, co to takiego jest i po co się je stosuje. Każdy zasób, czy to będzie konkretny fragment pamięci, czy też jakieś urządzenie peryferyjne, musi być w danej chwili wykorzystywany jednoznacznie. Wyobraźmy sobie sytuację, w której aplikacja wykorzystuje jakieś urządzenie peryferyjne, niech będzie to kontroler SPI. Jeśli nie chcemy blokować wykonywania programu na czas komunikacji, to może wystąpić sytuacja, w której mikrokontroler będzie próbował uzyskać dostęp do kontrolera SPI w czasie, kiedy poprzedni fragment wymiany danych nie został jeszcze zakończony. Najpewniej spowoduje to wystąpienie błędu lub nie wysłania danych.
Rozwiązanie takiego konfliktu jest łatwe. Narażonemu na hazardy zasobowi przyporządkowuje się obiekt (zmienną). Owa zmienna, którą można już nazwać muteksem, może przyjmować dwie wartości, dajmy na to zerową (fałsz) i niezerową (prawda). W założeniach projektowych przyjmujemy, że zasób jest wolny, czyli może być wykorzystany, tylko wtedy, kiedy wartość muteksa będzie fałszywa, czyli zerowa. Takie założenie jest oczywiście dowolne, można by przyjąć odwrotną logikę.
Programista, który będzie chciał skorzystać z chronionego zasobu musi najpierw sprawdzić jaka jest wartość muteksa. Jeśli jest ona różna od zera, to należy poczekać, aż muteks zostanie „oddany”, co będzie równoznaczne z przypisaniem mu wartości zerowej. Tylko w takiej sytuacji można użyć zasobu, pamiętając o tym, że zaraz na początku należy „zabrać” muteks, a po wykonaniu żądanych operacji „oddać” go.
List. 4. Funkcja main() programu odczytującego rejestr statusu układu pamięci
int main(void) { /* W??czenie i konfiguracja sygna?ˇw zegarowych */ SystemInit(); /* Inicjalizacja niezbŕdnych peryferiˇw */ dataflashInit(); /* Wyzwolenie procesu odczytu rejestru statusu */ readStatusRegister(); /* Oczekiwanie na zwolnienie muteksa */ while(SPI_InUse); /* ZawartoťŠ rejestru gotowa do skopiowania */ statusRegister = RxBuf[1]; while (1) { } }
Wracając do obsługi pamięci – główną funkcję main() programu zamieszczono na listingu 4. Po wywołaniu funkcji odczytu rejestru statusu następuje oczekiwanie na zwolnienie muteksa. Gdyby program był bardziej rozbudowany, można by w tym miejscu wykonać inne operacje, a do skopiowania odebranej danej przystąpić później, już po zwolnieniu zasobu, w tym przypadku kontrolera SPI. Powód, dla którego odczytywany jest drugi bajt bufora odbiorczego RxBuf[] wynika wprost ze sposobu działania interfejsu SPI, co zobrazowano na rysunku 6. Rejestr przesuwny jest z jednej strony zapełniany bitami przychodzącymi po SPI, a drugiej strony poszczególne bity są wysyłane. Ponieważ przerwania od bufora nadawczego i odbiorczego są włączane w tym samym czasie (patrz funkcja readStatusRegister z listingu 2), to pierwsze przerwanie od części odbiorczej zostanie wygenerowane już po wysłaniu bajta komendy 0xD7. Ten pierwszy odebrany bajt jest nieistotny, dopiero drugie przerwanie odczytuje właściwą daną.
Rys. 6. Schemat działania interfejsu SPI
Kasowanie strony
Rozkaz kasowania całej wybranej strony ma wartość 0x81, a następujących po nim bajtach adresowych przesyłany jest adres strony według schematu z rysunku 7. Pierwszy bajt adresowy zawiera siedem bitów adresu, natomiast dugi sześć. Pozostałe pola, łącznie z całym trzecim bajtem adresowym są bez znaczenia. Proces kasowania strony rozpoczyna po przejściu linii CS w stan wysoki. Zgodnie z informacjami wynikającymi z noty katalogowej układu AT45DB321D maksymalny czas kasowania strony wynosi 35 ms, a typowo 15 ms. Zakończenie operacji kasowania jest również sygnalizowane na linii BUSY układu oraz przez zawartość rejestru statusu.
Rys. 7. Kasowanie wybranej strony w pamięci
List. 5. Ciało funkcji kasującej zawartość strony
void pageErase(uint16_t address) { /* sprawdzanie muteksa */ while(SPI_InUse); /* "weź" muteks */ SPI_InUse = 0x11; SPI_Cmd(SPI1, DISABLE); SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE); SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_TXE, ENABLE); /* pierwszy bajt to komenda, trzy pozostałe to bajty adresu */ Nof_Bytes_To_TxRx = 4; /* Komenda "erase page" */ TxBuf[0] = 0x81; /* Starsze 7 bitów adresu strony */ TxBuf[1] = (uint8_t) address>>6; /* Pozostałe (młodsze) 6 bitów adresu strony, * ale wyrównane do lewej strony */ TxBuf[2] = (uint8_t) address<<2; SPI_Cmd(SPI1, ENABLE); GPIO_SetBits(GPIOA, CS); /* CS w stan niski */ GPIO_ResetBits(GPIOA, CS); /* Oczekiwanie na zakończenie wysyłania komendy i adresu strony */ while(SPI_InUse); /* ponownie "weź" muteks */ SPI_InUse = 0x11; /* SysTick bedzie taktowany z f = 72MHz/8 = 9MHz. * Przerwanie ma byc po 35 ms, f = 9MHz, czyli liczy od 320000 - 1 */ SysTick->LOAD = 320000 - 1; /* Włączenie przerwania od timera SysTick */ NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* Zerowanie aktualnej wartości licznika */ SysTick->VAL = (0x00); /* Wypełnienie rejestru kontrolnego timera SysTick: * -SYSTICK_CLKSOURCE - bit 2, 0 - HCLK/8, 1 - HCLK * -SYSTICK_TICKINT - bit 1, włączenie przerwania od timera * -SYSTICK_ENABLE - bit 0, włączenie timera */ SysTick->CTRL = (0<