Kursy • Poradniki • Inspirujące DIY • Forum
SPI vs. UART - najważniejsze różnice
Interfejs UART wykorzystywaliśmy do komunikacji z komputerem PC. Jego podstawową cechą jest asynchroniczność - czyli brak konieczności przesyłania sygnału zegarowego. Jak pamiętamy interfejs do komunikacji wykorzystuje raptem dwie linie RX oraz TX. Wadą takiego rozwiązania jest niewielka prędkość transmisji. Więcej na ten temat znaleźć można w 5 części kursu STM32.
Teraz poznamy nowy interfejs - SPI. Jest to interfejs synchroniczny, więc sygnał zegarowy będzie przesyłany między komunikującymi się układami. W odróżnieniu od UART, w przypadku SPI, komunikujące się układy nie są równorzędne. Jeden jest układem nadzorującym (ang. Master), a drugi podwładnym (ang. Slave). Komunikacja podobnie jak poprzednio odbywa się jednocześnie w obu kierunkach.
Interfejs pozwala również na podłączenie więcej niż jednego slave-a do magistrali.
Podłączenie SPI
Schemat podstawowego podłączenia jest następujący:
Jak widzimy do podłączenia potrzebujemy 4 linii. Najczęściej master-em jest mikrokontroler i takim przypadkiem będziemy się dalej zajmować. Można również wykorzystywać STM32 jako slave, ale wbrew pozorom jest to trudniejszy przypadek, niż wersja master.
Pierwsza linia jest oznaczana SS (Slave Select), która służy do wyboru urządzenia – pozostałe linie mogą być podpięte do wielu układów, ale tylko jeden może być w danej chwili aktywny. Linia ta często jest nazywana CS, czyli Chip Select - i tej nazwy będziemy używać podczas kursu.
Pozostałe linie to:
- MOSI (Master Output Slave Input) – dane wysyłane z mastera do slave
- MISO (Master Input Slave Output) – dane wysyłane z slave do mastera
- SCLK (Serial Clock) – linia zegara
Dzięki temu, że sygnał zegarowy jest przesyłany na liniach interfejsu, komunikacja jest znacznie łatwiejsza. Możliwe jest również uzyskanie znaczenie większych prędkości transmisji niż w przypadku interfejsu UART.
Podłączając więcej niż jeden układ peryferyjny do interfejsu SPI, będziemy potrzebowali kolejnych linii CS - po jednej na każdy podłączony układ slave.
Wybór aktywnego układu dokonywany jest przez wystawienie stanu niskiego na odpowiedniej linii CS. Tylko jeden układ może być aktywny w danej chwili (pozostałych liniach CS stan wysoki).
Teraz pora na przejście do praktyki. Na początku zajmiemy się ekspanderem portów, a w kolejnej części wyświetlaczem graficznym!
Komunikacja przez SPI
Mikrokontroler STM32F103RB posiada dwa interfejsy SPI, każdy o prędkości transmisji do 18 Mbit/s. Do przykładów wykorzystamy pierwszy interfejs, czyli SPI1. Na początek musimy odszukać w dokumentacji, na których pinach dostępne są wyprowadzenia interfejsu:
- SPI1_SCK - PA5
- SPI1_MISO - PA6
- SPI1_MOSI - PA7
Moglibyśmy wykorzystać sprzętowe sterowanie linią CS (dostępna na linii PA4), jednak w przypadku master-a sterowanie jest bardzo proste - wystarczy wystawić logiczne 0 na początek transmisji oraz 1, gdy komunikacja zostanie zakończona.
Wykorzystamy więc zwykłą linię GPIO. Dzięki temu będziemy mogli podłączyć więcej niż jeden układ slave (sprzętowo możemy obsłużyć tylko jedną linię CS, więc tylko jeden układ slave). Sprzętowe sterowanie linią CS jest natomiast bardzo wygodnie, gdybyśmy implementowali układ typu slave.
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Zestaw ponad 120 elementów do przeprowadzenia wszystkich ćwiczeń z kursu można nabyć u naszych dystrybutorów! Dostępne są wersje z płytką Nucleo lub bez niej!
Zamów w Botland.com.pl »Wstępne ustawienia SPI
Chcąc oprogramować interfejs SPI, zaczynamy jak zawsze - od podłączenia zegarów:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
Następnie konfigurujemy piny interfejsu. Zaczniemy od linii wyjściowych, czyli SCK oraz MOSI:
GPIO_InitTypeDef gpio;
GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7; // SCK, MOSI
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
Konfiguracja przypomina stosowaną dla linii TX interfejsu UART. Jest to wyjście sterowane sprzętowo, w trybie push-pull. Zmieniamy prędkość linii na 50 MHz - domyślnie konfiguracja używa linii o maksymalnej prędkości 2MHz, więc zbyt wolnych dla interfejsu, który może osiągać prędkość 18 Mbit/s (czyli 18 MHz).
Interfejs SPI w który wyposażony jest STM32F103RB
może pracować z prędkością do 18Mbit/s.
Kolejny krok to konfiguracja linii wejściowej MISO. Tutaj też zobaczymy podobieństwo do RX interfejsu UART:
gpio.GPIO_Pin = GPIO_Pin_6; // MISO
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio);
Ostatni krok to linia CS. Ponieważ stosujemy sterowanie programowe, możemy wybrać dowolny, dostępny pin. Wykorzystamy PC0 ponieważ mamy już opanowane sterowanie portem C:
gpio.GPIO_Pin = GPIO_Pin_0; // CS
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &gpio);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
Zaraz po inicjalizacji linii wystawiamy na niej logiczną 1. Linia CS jest aktywowana stanem niskim, a nie chcemy, żeby układ podłączony do tej linii CS działał w tej chwili - wystawiając 1, wyłączamy komunikację z podłączonym układem slave.
Konfiguracja interfejsu SPI
Mając skonfigurowane piny, możemy uruchomić sam interfejs SPI. Postępowanie przebiega jak zwykle: deklarujemy zmienną konfiguracyjną typu SPI_InitTypeDef, inicjalizujemy jej pola domyślnymi wartościami za pomocą funkcji SPI_StructInit, ustawiamy interesującą nas konfigurację, po czym wywołujemy SPI_Init, aby przygotować moduł SPI do pracy:
SPI_InitTypeDef spi;
SPI_StructInit(&spi);
spi.SPI_Mode = SPI_Mode_Master;
spi.SPI_NSS = SPI_NSS_Soft;
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
SPI_Init(SPI1, &spi);
SPI_Cmd(SPI1, ENABLE);
Ustawiamy tryb pracy jako SPI_Mode_Master. Ponieważ będziemy sami sterować linią CS (NSS to inna nazwa CS) wybieramy tryb programowy SPI_NSS_Soft. Zegar systemowy, jak pamiętamy ma częstotliwość 64 MHz - jest to zbyt wysoka wartość dla interfejsu SPI. Stosujemy więc dzielnik 16, aby otrzymać prędkość 4 MHz (czyli 4 Mbit/s).
Po inicjalizacji SPI, uruchamiamy interfejs wywołując funkcję SPI_Cmd.
Interfejs SPI może pracować w 4 trybach: od mode 0 do mode 3. Opisywana konfiguracja działa w trybie 0, który jest wybierany domyślnie. Gdybyśmy musieli wykorzystać inny tryb (układ peryferyjny może tego wymagać), należałoby zmienić konfigurację pól SPI_CPOL oraz SPI_CPHA.
Więcej informacji o SPI można przeczytać na Wikipedii.
Procedura komunikacji
Gdy mamy skonfigurowane SPI, możemy napisać procedurę komunikującą się poprzez ten interfejs. Dane są jednocześnie wysyłane (przez linię MOSI) oraz odbierane (linia MISO), więc zamiast typowych dwóch procedur - do wysyłania i odbierania, napiszemy jedną. Jeśli będziemy chcieli tylko wysyłać, zignorujemy zwracaną wartość. Natomiast odbierając, i tak musimy coś wysłać (najczęściej wysyła się wtedy same logiczne jedynki, czyli wartość 0xff).
Kod procedury wygląda następująco:
uint8_t spi_sendrecv(uint8_t byte)
{
// poczekaj az bufor nadawczy bedzie wolny
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, byte);
// poczekaj na dane w buforze odbiorczym
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
Aby skorzystać z interfejsu, musimy jeszcze wysterować odpowiednio linię CS. Przykladowe wysłanie jednego bajtu wygląda więc następująco:
GPIO_ResetBits(GPIOC, GPIO_Pin_0);
spi_sendrecv(0x40);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
W pierwszej linii zerujemy stan CS, czyli aktywujemy układ slave. Następnie wysyłamy bajt o wartości 0x40 (przykładowa wartość). Możemy wysłać na raz więcej danych i najczęściej właśnie tak się postępuje. Gdy skończymy komunikować się z układem, zwalniamy interfejs wystawiając logiczne 1 na linii CS.
Pełny kod przykładu:
#include "stm32f10x.h"
uint8_t spi_sendrecv(uint8_t byte)
{
// poczekaj az bufor nadawczy bedzie wolny
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, byte);
// poczekaj na dane w buforze odbiorczym
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
int main(void)
{
GPIO_InitTypeDef gpio;
SPI_InitTypeDef spi;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7; // SCK, MOSI
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
gpio.GPIO_Pin = GPIO_Pin_6; // MISO
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio);
gpio.GPIO_Pin = GPIO_Pin_0; // CS
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &gpio);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
SPI_StructInit(&spi);
spi.SPI_Mode = SPI_Mode_Master;
spi.SPI_NSS = SPI_NSS_Soft;
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
SPI_Init(SPI1, &spi);
SPI_Cmd(SPI1, ENABLE);
GPIO_ResetBits(GPIOC, GPIO_Pin_0);
spi_sendrecv(0x40);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
while (1) {
}
}
Ekspander portów MCP23S08
Umiemy już skonfigurować SPI, czas wykorzystać naszą wiedzę w praktyce. Jako pierwszy przykład wykorzystamy układ MCP23S08, czyli ekspander wyprowadzeń I/O.
Układ pozwala na podłączenie dodatkowych 8 linii wejścia/wyjścia do naszego mikrokontrolera. Więcej o MCP23S08 znajdziemy w jego dokumentacji.
Zaczniemy od powtórzenia przykładu z migającą diodą, który wykorzystywaliśmy w części 4 kursu, omawiając GPIO. Tym razem diodę podłączymy nie bezpośrednio do mikrokontrolera, ale do I/O ekspandera. Schemat podłączenia przedstawia rysunek:
Trzeba pamiętać o podłączeniu podciągnięcia linii RESET układu MCP23S08. Linia nie posiada wbudowanego rezystora pull-up. Układ nie będzie działał, jeśli reset zostawimy niepodłączony.
Połączenie między mikrokontrolerem, a MCP23S08 jest zgodne z tym, co mówiliśmy o SPI. Jako linię CS wykorzystujemy PC0, poza tym podłączyliśmy MISO, MOSI oraz SCLK. MCP23S08 posiada 8 linii wejścia/wyjścia. Są one sterowane za pomocą rejestrów, podobnie jak np.: układy AVR. W dokumentacji znajdziemy pełny opis dostępnych rejestrów:
Nas będą interesowały rejestry:
- IODIR - ustawia kierunek działania linii, podobnie jak DDRA w przypadku AVR
- GPIO - pozwala na odczyt stanów linii, odpowiada PINA dla AVR
- OLAT - zapis do tego rejestru ustawia stan linii wyjściowych - odpowiada PORTA
- GPPU - pozwala na podłączenie rezystorów podciągających
Ponieważ niewygodne jest pamiętanie adresu każdego rejestru, zdefiniujemy odpowiednie stałe:
#define MCP_IODIR 0x00
#define MCP_IPOL 0x01
#define MCP_GPINTEN 0x02
#define MCP_DEFVAL 0x03
#define MCP_INTCON 0x04
#define MCP_IOCON 0x05
#define MCP_GPPU 0x06
#define MCP_INTF 0x07
#define MCP_INTCAP 0x08
#define MCP_GPIO 0x09
#define MCP_OLAT 0x0a
Komunikacja z ekspanderem
Kolejny krok, to odszukanie w dokumentacji, jak wygląda zapis i odczyt z odpowiednich rejestrów. Zapis wygląda następująco
- wysyłamy identyfikator urządzenia 0x40,
- następnie adres rejestru, przykładowo MCP_OLAT,
- oraz wartość do zapisania.
Całość możemy zapisać jako krótką procedurę:
void mcp_write_reg(uint8_t addr, uint8_t value)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_0);
spi_sendrecv(0x40);
spi_sendrecv(addr);
spi_sendrecv(value);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
}
Teraz możemy napisać właściwy program. Po skonfigurowaniu SPI trzeba ustawić odpowiednie rejestry układu MCP23S08. Chcemy, aby pin zerowy był wyjściem. W tym celu musimy wyzerować pierwszy bit rejestru IODIR (1 - oznacza wejście, 0 - wyjście):
mcp_write_reg(MCP_IODIR, ~0x01);
Włączenie i wyłączenie diody odbywa się poprzez zapis do rejestru OLAT:
//Wlacz diode
mcp_write_reg(MCP_OLAT, 0x01);
//Wylacz diode
mcp_write_reg(MCP_OLAT, 0x00);
Kompletny program migający diodą wygląda następująco:
#include "stm32f10x.h"
#define MCP_IODIR 0x00
#define MCP_IPOL 0x01
#define MCP_GPINTEN 0x02
#define MCP_DEFVAL 0x03
#define MCP_INTCON 0x04
#define MCP_IOCON 0x05
#define MCP_GPPU 0x06
#define MCP_INTF 0x07
#define MCP_INTCAP 0x08
#define MCP_GPIO 0x09
#define MCP_OLAT 0x0a
uint8_t spi_sendrecv(uint8_t byte)
{
// poczekaj az bufor nadawczy bedzie wolny
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, byte);
// poczekaj na dane w buforze odbiorczym
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
void mcp_write_reg(uint8_t addr, uint8_t value)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_0);
spi_sendrecv(0x40);
spi_sendrecv(addr);
spi_sendrecv(value);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
}
int main(void)
{
volatile int dly;
GPIO_InitTypeDef gpio;
SPI_InitTypeDef spi;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7; // SCK, MOSI
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
gpio.GPIO_Pin = GPIO_Pin_6; // MISO
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio);
gpio.GPIO_Pin = GPIO_Pin_0; // CS
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &gpio);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
SPI_StructInit(&spi);
spi.SPI_Mode = SPI_Mode_Master;
spi.SPI_NSS = SPI_NSS_Soft;
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
SPI_Init(SPI1, &spi);
SPI_Cmd(SPI1, ENABLE);
mcp_write_reg(MCP_IODIR, ~0x01);
while (1) {
// zapal diode
mcp_write_reg(MCP_OLAT, 0x01);
for (dly=0;dly<1000000;dly++) {}
// zgas diode
mcp_write_reg(MCP_OLAT, 0x00);
for (dly=0;dly<1000000;dly++) {}
}
}
Działanie programu w praktyce widoczne jest na poniższym filmie:
Zadanie domowe 9.1
Podłącz do ekspandera kilka diod świecących np.: 4 i napisz program, który pozwala na włączanie i wyłączanie diod poprzez wysyłanie odpowiednich komend sterujących przez UART (przykładowo 1on, 1off itd.).
Zadanie domowe 9.2
Wykorzystaj podłączenie z zadanie 9.1 i stwórz licznik Johnsona. Do układu podłącz dodatkowo potencjometr, którym będzie można regulować prędkość pracy licznika (w tym celu użyj ADC).
Obsługa przycisku
Sprawdziliśmy jak sterować wyjściem ekspandera. Teraz przetestujemy linię wejściową. W tym celu możemy dodać na płytce jakiś przełącznik, lub najprościej wykorzystać przewód, którym będziemy zwierać odpowiednie sygnały. Na poniższym schemacie montażowym role przełącznika odgrywa pomarańczowy przewód, który wyróżniony został strzałką.
Ponieważ nie podłączyliśmy zewnętrznego rezystora podciągającego, będziemy musieli włączyć odpowiedni rezystor wbudowany w MCP23S08. W tym celu wystarczy ustawić odpowiedni bit rejestru GPPU:
mcp_write_reg(MCP_GPPU, 0x02);
Stan przycisku odczytamy z rejestru GPIO. Musimy więc przygotować procedurę odczytywania rejestrów (zapisywać już potrafimy).
Opis protokołu znajdziemy w dokumentacji MCP23S08 - odczyt z rejestru przebiega następująco:
- wysyłamy identyfikator urządzenia 0x41,
- następnie adres rejestru np. MCP_GPIO,
- odbieramy zawartość rejestru.
Funkcja odczytująca rejestr ma więc postać:
uint8_t mcp_read_reg(uint8_t addr)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_0);
spi_sendrecv(0x41);
spi_sendrecv(addr);
uint8_t value = spi_sendrecv(0xff);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
return value;
}
Jak pamiętamy komunikacja przez SPI za każdym razem odbywa się w obu kierunkach. Więc kiedy wysyłamy np. identyfikator 0x41, jednocześnie odbieramy wartość od układu peryferyjnego. Jednak ta wartość jest nieistotna, więc nie zapamiętujemy jej. Podobnie dzieje się podczas wysyłania adresu rejestru.
Natomiast odczyt polega na wysłaniu czegokolwiek (wysyłamy 0xff) oraz zapamiętaniu odebranej wartości.
Teraz możemy napisać program, który będzie włączał diodę po naciśnięciu przycisku:
#include "stm32f10x.h"
#define MCP_IODIR 0x00
#define MCP_IPOL 0x01
#define MCP_GPINTEN 0x02
#define MCP_DEFVAL 0x03
#define MCP_INTCON 0x04
#define MCP_IOCON 0x05
#define MCP_GPPU 0x06
#define MCP_INTF 0x07
#define MCP_INTCAP 0x08
#define MCP_GPIO 0x09
#define MCP_OLAT 0x0a
uint8_t spi_sendrecv(uint8_t byte)
{
// poczekaj az bufor nadawczy bedzie wolny
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, byte);
// poczekaj na dane w buforze odbiorczym
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
void mcp_write_reg(uint8_t addr, uint8_t value)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_0);
spi_sendrecv(0x40);
spi_sendrecv(addr);
spi_sendrecv(value);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
}
uint8_t mcp_read_reg(uint8_t addr)
{
GPIO_ResetBits(GPIOC, GPIO_Pin_0);
spi_sendrecv(0x41);
spi_sendrecv(addr);
uint8_t value = spi_sendrecv(0xff);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
return value;
}
int main(void)
{
GPIO_InitTypeDef gpio;
SPI_InitTypeDef spi;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_StructInit(&gpio);
gpio.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_7; // SCK, MOSI
gpio.GPIO_Mode = GPIO_Mode_AF_PP;
gpio.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio);
gpio.GPIO_Pin = GPIO_Pin_6; // MISO
gpio.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &gpio);
gpio.GPIO_Pin = GPIO_Pin_0; // CS
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &gpio);
GPIO_SetBits(GPIOC, GPIO_Pin_0);
SPI_StructInit(&spi);
spi.SPI_Mode = SPI_Mode_Master;
spi.SPI_NSS = SPI_NSS_Soft;
spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
SPI_Init(SPI1, &spi);
SPI_Cmd(SPI1, ENABLE);
mcp_write_reg(MCP_IODIR, ~0x01);
mcp_write_reg(MCP_GPPU, 0x02);
while (1) {
if ((mcp_read_reg(MCP_GPIO) & 0x02) == 0) {
mcp_write_reg(MCP_OLAT, 0x01);
} else {
mcp_write_reg(MCP_OLAT, 0x00);
}
}
}
Zadanie domowe 9.3
Skonfiguruj jeden pin ekspandera jako wejście, a pozostałe jako wyjścia, do których podłączone będą diody świecące. Stan diod powinien wizualizować wyjście licznika binarnego, którego kierunek pracy określany ja za pomocą stanu panującego na jedynym wejściu ekspandera.
Podsumowanie
W tej części poznaliśmy interfejs SPI, za pomocą którego możemy rozszerzać możliwości mikrokontrolera poprzez podłączanie układów peryferyjnych. Jako przykład posłużył nam moduł dodatkowych linii wejścia-wyjścia MCP23S08. Układy tego okazują się niezastąpione, gdy zaczyna brakować wolnych portów I/O mikrokontrolerze.
Nawigacja kursu
W kolejnej części kursu zobaczymy jak za pomocą SPI sterować graficznym wyświetlaczem LCD! Od tego momentu wszystkie projekty będą mogły komunikować się z użytkownikiem w kolejny, ciekawy sposób.
Autor kursu: Piotr (Elvis) Bugalski
Redakcja: Damian (Treker) Szymański
Powiązane wpisy
ekspander, kursSTM32, SPI, stm32
Trwa ładowanie komentarzy...