piątek, 11 grudnia 2015

[10a] STM32 M3 - Nucleo - F103RB - SPI - Ekspander portów wejścia/wyjścia

W tym poście chciałbym opisać sposób podłączenia oraz obsługi ekspandera portów MCP23S08

Wstęp


Ekspander portów wejścia/wyjścia pozwala na zwiększenie liczby dostępnych pinów. Komunikacja przebiega poprzez interfejs SPI. W przypadku zakupy układu MCP23008 zamiast interfejsu SPI będzie dostępny I2C.

Rys. 1. Ekspander [microchip.com]



Główne parametry układu to:

  • Zasilanie: 1,8 - 5,5 V
  • Prędkość transmisji do 10kbps
  • Ilość kanałów 8
  • Taktowanie do 10MHz 
  • Komunikacja SPI
  • Niski pobór prądu w trybie oczekiwania 1uA


Schemat wyprowadzeń poszczególnych pinów z ekspandera. Został on zaczerpnięty z dokumentacji układu:

Rys. 2. Wyprowadzenia układu

Poniżej przedstawiam opis poszczególnych wyprowadzeń:

  • SCK - Zegar taktujący
  • SI - Wejście danych
  • SO - Wyjście danych
  • A1 - Sprzętowe wejście danych adresowych, musi być podciągnięty do zasilania
  • A0 - Sprzętowe wejście danych, musi być podciągnięty do zasilania
  • RESET - Reset, musi być podciągnięty do zasilania
  • CS - Wybranie układu
  • INT - Wyjście przerwań. Można skonfigurować jako aktywny wysoki, aktywny niski oraz wyjście typu otwarty dren
  • VSS - Masa
  • VDD - Zasilanie
  • GP0 - GP7 - Wyprowadzenia sygnałów z ekspandera


Kolejnym ważnym elementem po sprawdzeniu wyprowadzeń jest zapoznanie się z rejestrami ekspandera. Można je znaleźć w dokumentacji układu:

Rys. 3. Rejestry układu

Zestawy rejestrów są takie same zarówno dla układu z SPI jak i dla wersji z I2C.

Poszczególne rejestry pełnią następujące funkcje:

  • IODIR - pozwala na wybranie kierunku działania linii
  • IPOL - ustawienie biegunowości
  • GPINTEN - konfiguracja przerwania dla poszczególnych pinów
  • DEFVAL - porównywanie wartości rejestrów z przerwania
  • INTCON - konfiguracja przerwania
  • IOCON - konfiguracja urządzenia 
  • GPPU - daje możliwość podłączenia dodatkowych rezystorów podciągających
  • INTF - odbicie parametrów przerwań ustawionych za pomocą rejestry GPINTEN
  • INTCAP - pozwala na przechwycenie wartości z rejestru kiedy nastąpi przerwanie
  • GPIO - pozwala na odczytanie stanu jaki znajduje się na linii
  • OLAT - ustawienie stanu linii

Kolejnym elementem są parametry rejestrów, czyli w jaki sposób należy je skonfigurować. Schemat wygląda następująco:

Rys. 4. Konfiguracja rejestrów

Mikrokontroler zastosowany na płytce posiada następujące wyprowadzenia interfejsu SPI:


  • SPI1:
    • NSS(CS) - PA4
    • MISO - PA6
    • MOSI - PA7
    • SCK - PA5


  • SPI2:
    • NSS(CS) - PB12
    • MISO - PB14
    • MOSI - PB15
    • SCK - PB13

Programowanie


Inicjalizacja

Pierwszym elementem jaki należy wykonać jest odpowiednie ustawienie pinów wejścia, oraz wyjścia.
Następnie należy ustawić odpowiednio interfejs SPI oraz włączyć odpowiednie zegary. Nie zaszkodzi także zdefiniować sobie podstawowych wartości rejestrów oraz pinów za pomocą #define.

void GPIOInit(void)
{
 GPIO_InitTypeDef GPIOInit;
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
 
 GPIO_StructInit(&GPIOInit);
 
 //piny SCK, MOSI
 GPIOInit.GPIO_Pin = SCK_PIN|MOSI_PIN; // SCK, MOSI
 GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(SCK_LINE, &GPIOInit);
 
 //Pin Miso
 GPIOInit.GPIO_Pin = MISO_PIN; // MISO
 GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIOInit.GPIO_Speed = GPIO_Speed_10MHz;
 GPIO_Init(MISO_LINE, &GPIOInit);
 
 //Pin CS
 GPIOInit.GPIO_Pin = CS_PIN;
 GPIOInit.GPIO_Mode = GPIO_Mode_Out_PP;
 GPIOInit.GPIO_Speed = GPIO_Speed_10MHz;
 GPIO_Init(CS_LINE, &GPIOInit);
 
 //Ustawienie pinu CS na niski, następuje wybranie urządzenia
 GPIO_SetBits(GPIOC, GPIO_Pin_0);
}
//Inicjalizacja spi
void SPIInit(void)
{
 SPI_InitTypeDef SPIInit;
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
 
 SPI_StructInit(&SPIInit);
 //Transmisja z wykorzystaniem dwóch linii
 SPIInit.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
 //Tryb pracy mikrokontrolera
 SPIInit.SPI_Mode = SPI_Mode_Master;
 //Stan sygnału taktującego przy braku transmisji
 SPIInit.SPI_CPOL = SPI_CPOL_Low;
 //Aktywne zbocze sygnału taktującego
 SPIInit.SPI_CPHA = SPI_CPHA_1Edge;
 //Wylaczenie sprzetowej obslugi linii CS
 SPIInit.SPI_NSS = SPI_NSS_Soft;
 //Szybkośc transmisji 64MHz/16 = 4MHz
 SPIInit.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
 //Inicjalizacja SPI
 SPI_Init(SPI1, &SPIInit);
 //Włączenie SPI
 SPI_Cmd(SPI1, ENABLE);
}

Wysyłanie danych

SPI jest interfejsem szeregowym, dwukierunkowym. Oznacza to, że nie można tylko odbierać z niego danych. Należy także jakieś dane do niego wysyłać. 

Rozpoczęcie komunikacji rozpoczyna się poprzez podanie stanu niskiego na pin CS. Następnie należy podać komendę pozwalającą na wprowadzanie danych. Adresowanie wygląda następująco:

Rys. 5. Rejestr adresów

Dla bitu R/W wartość 0 oznacza wpisywanie danych do urządzenia (Write), natomiast 1 odczytuje dane z urządzenia (Read).

Aby rozpocząć procedurę komunikacji należy przesłać identyfikator urządzenia, który wygląda następująco: 1000000 lub w postaci szesnastkowej 0x40.

Następnym elementem jest przesłanie informacji do jakiego rejestru dane będą wprowadzane, po czym następuje przesłanie jego ustawień. 

Część programu odpowiedzialna za przesyłanie danych wygląda następująco:

//Wysłanie danych
uint8_t SPI_SEND(uint8_t byte)
{
 //Sprawdzanie zajętości bufora nadawczego, jak będzie wolny nastąpi wysłanie danych
 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
 SPI_I2S_SendData(SPI1, byte);
 
 //Sprawdzanie, czy dane są w buforze odbiorczym, jeśli tak to zostaną zwrócone
 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
 return SPI_I2S_ReceiveData(SPI1);
}
 
//Wpisanie danych do rejestrów
void MCP_WRITE(uint8_t rejestr, uint8_t wartosc)
{
 //Ustawienie pinu CS na stan niski, wybranie urządzenia
 GPIO_ResetBits(CS_LINE, CS_PIN);
 //wpisanie danych do CS.
 SPI_SEND(0x40);
 //wyslanie informacji do którego rejestru dane zostaną przesłane
 SPI_SEND(rejestr);
 //wysłanie ustawienia wartości
 SPI_SEND(wartosc);
 //Zakończenie transmisji, zmiana stanu linii CS na wysoki
 GPIO_SetBits(CS_LINE, CS_PIN);
}

Program 1


W tym programie zaprezentuję obsługę pięciu portów GPIO ekspandera. Do jego wyjść podłączyłem diody świecące, które będą się zapalały oraz gasły w zależności od stanów na portach.

#include "stm32f10x.h"
 
//Deklaracje rejestrów
#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
 
//Deklaracja pinów
#define CS_PIN   GPIO_Pin_0
#define CS_LINE   GPIOC
 
#define SCK_PIN   GPIO_Pin_5
#define SCK_LINE  GPIOA
 
#define MISO_PIN  GPIO_Pin_6
#define MISO_LINE  GPIOA
 
#define MOSI_PIN  GPIO_Pin_7
#define MOSI_LINE  GPIOA
 
#define GP0   0x01
#define GP1   0x02
#define GP2   0x04
#define GP3   0x08
#define GP4   0x10
#define GP5   0x20
#define GP6   0x40
#define GP7   0x80
#define Low   0x00
 
void SPIInit(void);
void GPIOInit(void);
uint8_t SPI_SEND(uint8_t byte);
void MCP_WRITE(uint8_t, uint8_t);
void Delay(int);
void GP_ACTIVE(int);
void GP_ACTIVE_GROUP(int PinEnd);
 
int main(void)
{
 GPIOInit();
 SPIInit();
 
 GP_ACTIVE_GROUP(4);
 
 while (1) {
  //Ustawienie rejestru dla diody podłączonej do GP0
  //Zapalenie diody
  MCP_WRITE(MCP_OLAT, GP0);
  Delay(500);
  //Zgaszeniediody
  MCP_WRITE(MCP_OLAT, Low);
  Delay(500);
 
  //Ustawienie rejestru dla diody podłączonej do GP1
  MCP_WRITE(MCP_OLAT, GP1);
  Delay(500);
  MCP_WRITE(MCP_OLAT, Low);
  Delay(500);
 
  //Ustawienie rejestru dla diody podłączonej do GP2
  MCP_WRITE(MCP_OLAT, GP2);
  Delay(500);
  MCP_WRITE(MCP_OLAT, Low);
  Delay(500);
 
  //Ustawienie rejestru dla diody podłączonej do GP3
  MCP_WRITE(MCP_OLAT, GP3);
  Delay(500);
  MCP_WRITE(MCP_OLAT, Low);
  Delay(500);
 
  //Ustawienie rejestru dla diody podłączonej do GP4
  MCP_WRITE(MCP_OLAT, GP4);
  Delay(500);
  MCP_WRITE(MCP_OLAT, Low);
  Delay(500);
 }
}
 
void GPIOInit(void)
{
 GPIO_InitTypeDef GPIOInit;
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
 
 GPIO_StructInit(&GPIOInit);
 
 //piny SCK, MOSI
 GPIOInit.GPIO_Pin = SCK_PIN|MOSI_PIN; // SCK, MOSI
 GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(SCK_LINE, &GPIOInit);
 
 //Pin Miso
 GPIOInit.GPIO_Pin = MISO_PIN; // MISO
 GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIOInit.GPIO_Speed = GPIO_Speed_10MHz;
 GPIO_Init(MISO_LINE, &GPIOInit);
 
 //Pin CS
 GPIOInit.GPIO_Pin = CS_PIN;
 GPIOInit.GPIO_Mode = GPIO_Mode_Out_PP;
 GPIOInit.GPIO_Speed = GPIO_Speed_10MHz;
 GPIO_Init(CS_LINE, &GPIOInit);
 
 //Ustawienie pinu CS na niski, następuje wybranie urządzenia
 GPIO_SetBits(GPIOC, GPIO_Pin_0);
}
//Inicjalizacja spi
void SPIInit(void)
{
 SPI_InitTypeDef SPIInit;
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
 
 SPI_StructInit(&SPIInit);
 //Transmisja z wykorzystaniem dwóch linii
 SPIInit.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
 //Tryb pracy mikrokontrolera
 SPIInit.SPI_Mode = SPI_Mode_Master;
 //Stan sygnału taktującego przy braku transmisji
 SPIInit.SPI_CPOL = SPI_CPOL_Low;
 //Aktywne zbocze sygnału taktującego
 SPIInit.SPI_CPHA = SPI_CPHA_1Edge;
 //Wylaczenie sprzetowej obslugi linii CS
 SPIInit.SPI_NSS = SPI_NSS_Soft;
 //Szybkośc transmisji 64MHz/16 = 4MHz
 SPIInit.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
 //Inicjalizacja SPI
 SPI_Init(SPI1, &SPIInit);
 //Włączenie SPI
 SPI_Cmd(SPI1, ENABLE);
}
 
//Wysłanie danych
uint8_t SPI_SEND(uint8_t byte)
{
 //Sprawdzanie zajętości bufora nadawczego, jak będzie wolny nastąpi wysłanie danych
 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
 SPI_I2S_SendData(SPI1, byte);
 
 //Sprawdzanie, czy dane są w buforze odbiorczym, jeśli tak to zostaną zwrócone
 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
 return SPI_I2S_ReceiveData(SPI1);
}
 
//Wpisanie danych do rejestrów
void MCP_WRITE(uint8_t rejestr, uint8_t wartosc)
{
 //Ustawienie pinu CS na stan niski, wybranie urządzenia
 GPIO_ResetBits(CS_LINE, CS_PIN);
 //wpisanie danych do CS.
 SPI_SEND(0x40);
 //wyslanie informacji do którego rejestru dane zostaną przesłane
 SPI_SEND(rejestr);
 //wysłanie ustawienia wartości
 SPI_SEND(wartosc);
 //Zakończenie transmisji, zmiana stanu linii CS na wysoki
 GPIO_SetBits(CS_LINE, CS_PIN);
}
//Funckcja do ustawiania wybranego portu
void GP_ACTIVE(int Pin)
{
 //Ustawienie pinu jako wejście przebiega poprzez wyzerowanie
 //odpowiedniego zestawu bitow
 
 switch(Pin)
 {
  case 0: MCP_WRITE(MCP_IODIR, ~0x01); break; //GP0 bit0 Dec: 1
  case 1: MCP_WRITE(MCP_IODIR, ~0x02); break; //GP1 bit1 Dec: 2
  case 2: MCP_WRITE(MCP_IODIR, ~0x04); break; //GP2 bit2 Dec: 4
  case 3: MCP_WRITE(MCP_IODIR, ~0x08); break; //GP3 bit3 Dec: 8
  case 4: MCP_WRITE(MCP_IODIR, ~0x10); break; //GP4 bit4 Dec: 16
  case 5: MCP_WRITE(MCP_IODIR, ~0x20); break; //GP5 bit5 Dec: 32
  case 6: MCP_WRITE(MCP_IODIR, ~0x40); break; //GP6 bit6 Dec: 64
  case 7: MCP_WRITE(MCP_IODIR, ~0x80); break; //GP7 bit7 Dec: 128
  default: MCP_WRITE(MCP_IODIR, ~0x01); break;
 }
}
 
void GP_ACTIVE_GROUP(int PinEnd)
{
 //Ta funkcja ustawia odpowiednie wartości dla pinu początkowego oraz końcowego
 //Ustawienie pinu jako wejście przebiega poprzez wyzerowanie
 //odpowiedniego zestawu bitow
 
 switch(PinEnd)
 {
  case 1: MCP_WRITE(MCP_IODIR, ~0x03); break; //GP0 bit0 Dec: 3
  case 2: MCP_WRITE(MCP_IODIR, ~0x07); break; //GP1 bit1 Dec: 7
  case 3: MCP_WRITE(MCP_IODIR, ~0x0F); break; //GP2 bit2 Dec: 15
  case 4: MCP_WRITE(MCP_IODIR, ~0x1F); break; //GP3 bit3 Dec: 31
  case 5: MCP_WRITE(MCP_IODIR, ~0x3F); break; //GP4 bit4 Dec: 63
  case 6: MCP_WRITE(MCP_IODIR, ~0x7F); break; //GP5 bit5 Dec: 127
  case 7: MCP_WRITE(MCP_IODIR, ~0xFF); break; //GP6 bit6 Dec: 255
  default: MCP_WRITE(MCP_IODIR, ~0xFF); break;
 }
}
//Pętla opóźniająca
void Delay(int czas)
{
    int i;
    for (i = 0; i < czas * 5000; i++) {}
}
 

Program 2


Program poniżej działa prawie tak samo jak poprzedni, jedyną różnicą jest sterowanie portami za pomocą interfejsu UART. Wpisanie odpowiedniej komendy spowoduje zapalenie, bądź zgaszenie wybranej diody. Wszystkie informacje odnoście działania programu umieściłem bezpośrednio w nim.

#include "stm32f10x.h"
 
//Deklaracje rejestrów
#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
 
//Deklaracja pinów
#define CS_PIN   GPIO_Pin_0
#define CS_LINE   GPIOC
 
#define SCK_PIN   GPIO_Pin_5
#define SCK_LINE  GPIOA
 
#define MISO_PIN  GPIO_Pin_6
#define MISO_LINE  GPIOA
 
#define MOSI_PIN  GPIO_Pin_7
#define MOSI_LINE  GPIOA
 
#define GP0    0x01
#define GP1    0x02
#define GP2    0x04
#define GP3    0x08
#define GP4    0x10
#define GP5    0x20
#define GP6    0x40
#define GP7             0x80
#define Low    0x00
 
void SPIInit(void);
void GPIOInit(void);
uint8_t SPI_SEND(uint8_t byte);
void MCP_WRITE(uint8_t, uint8_t);
void Delay(int);
void GP_ACTIVE(int);
void GP_ACTIVE_GROUP(int PinEnd);
void GPIOInit_USART(void);
void USARTInit(void);
void SendCharc(volatile char);
void USARTSend(volatile char *c);
uint8_t USART_Getc(USART_TypeDef* USARTx);
 
int8_t USART_Buffer[8][32];
uint16_t usart_buf_in[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint16_t usart_buf_out[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint16_t usart_buf_num[8] = {0, 0, 0, 0, 0, 0, 0, 0};
 
int main(void)
{
 uint16_t c;
 
 GPIOInit();
 GPIOInit_USART();
 USARTInit();
 SPIInit();
 
 GP_ACTIVE_GROUP(4);
 
 USARTSend("Aby zapalic bądz zgasic wybrane diody nalezy:\n");
 USARTSend("GP0 - Zapal 1, Zgas 2\n");
 USARTSend("GP1 - Zapal 3, Zgas 4\n");
 USARTSend("GP2 - Zapal 5, Zgas 6\n");
 USARTSend("GP3 - Zapal 7, Zgas 8\n");
 USARTSend("GP4 - Zapal 9, Zgas 0\n");
 while (1)
 {
    c = USART_Getc(USART2);
    if(c)
    {
        switch (c)
        {
            case '1':
             USARTSend("GP0 - Odebrano 1. Dioda zapalona!\n");
             MCP_WRITE(MCP_OLAT, GP0);
             break;
            case '2':
             USARTSend("GP0 - Odebrano 2. Dioda zgaszona!\n");
             MCP_WRITE(MCP_OLAT, (1<<0));
             break;
            case '3':
             USARTSend("GP1 - Odebrano 3. Dioda zapalona!\n");
             MCP_WRITE(MCP_OLAT, GP1);
             break;
            case '4':
             USARTSend("GP1 - Odebrano 4. Dioda zgaszona!\n");
             MCP_WRITE(MCP_OLAT, Low);
             break;
            case '5':
             USARTSend("GP2 - Odebrano 5. Dioda zapalona!\n");
             MCP_WRITE(MCP_OLAT, GP2);
             break;
            case '6':
             USARTSend("GP2 - Odebrano 6. Dioda zgaszona!\n");
             MCP_WRITE(MCP_OLAT, Low);
             break;
            case '7':
             USARTSend("GP3 - Odebrano 7. Dioda zapalona!\n");
             MCP_WRITE(MCP_OLAT, GP3);
             break;
            case '8':
             USARTSend("GP3 - Odebrano 8. Dioda zgaszona!\n");
             MCP_WRITE(MCP_OLAT, Low);
             break;
            case '9':
             USARTSend("GP4 - Odebrano 9. Dioda zapalona!\n");
             MCP_WRITE(MCP_OLAT, GP4);
             break;
            case '0':
             USARTSend("GP4 - Odebrano 0. Dioda zgaszona!\n");
             MCP_WRITE(MCP_OLAT, Low);
             break;
            case 'C':
             USARTSend("Gasze wszystkie diody\n");
             MCP_WRITE(MCP_OLAT, Low);
             break;
            default:
             USARTSend("Wybierz poprawna wartosc \n");
             SendCharc(c);
             break;
        }
    }
 }
}
 
//Ustawienie pinów SPI
void GPIOInit(void)
{
 GPIO_InitTypeDef GPIOInit;
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
 
 GPIO_StructInit(&GPIOInit);
 
 //piny SCK, MOSI
 GPIOInit.GPIO_Pin = SCK_PIN|MOSI_PIN; // SCK, MOSI
 GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(SCK_LINE, &GPIOInit);
 
 //Pin Miso
 GPIOInit.GPIO_Pin = MISO_PIN; // MISO
 GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;
 GPIOInit.GPIO_Speed = GPIO_Speed_10MHz;
 GPIO_Init(MISO_LINE, &GPIOInit);
 
 //Pin CS
 GPIOInit.GPIO_Pin = CS_PIN;
 GPIOInit.GPIO_Mode = GPIO_Mode_Out_PP;
 GPIOInit.GPIO_Speed = GPIO_Speed_10MHz;
 GPIO_Init(CS_LINE, &GPIOInit);
 
 //Ustawienie pinu CS na niski, następuje wybranie urządzenia
 GPIO_SetBits(GPIOC, GPIO_Pin_0);
}
 
//Ustawienie portów dla USARTU
void GPIOInit_USART(void)
{
  GPIO_InitTypeDef GPIOInit;
 
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
 
  //PA2 Tx
  GPIO_StructInit(&GPIOInit);
  GPIOInit.GPIO_Pin = GPIO_Pin_2;
  GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  GPIOInit.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOA, &GPIOInit);
 
  //PA3 Rx
  GPIOInit.GPIO_Pin = GPIO_Pin_3;
  GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  GPIOInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOA, &GPIOInit);
}
 
//Inicjalizacja spi
void SPIInit(void)
{
 SPI_InitTypeDef SPIInit;
 
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
 
 SPI_StructInit(&SPIInit);
 //Transmisja z wykorzystaniem dwóch linii
 SPIInit.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
 //Tryb pracy mikrokontrolera
 SPIInit.SPI_Mode = SPI_Mode_Master;
 //Stan sygnału taktującego przy braku transmisji
 SPIInit.SPI_CPOL = SPI_CPOL_Low;
 //Aktywne zbocze sygnału taktującego
 SPIInit.SPI_CPHA = SPI_CPHA_1Edge;
 //Wylaczenie sprzetowej obslugi linii CS
 SPIInit.SPI_NSS = SPI_NSS_Soft;
 //Szybkośc transmisji 64MHz/16 = 4MHz
 SPIInit.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
 //Inicjalizacja SPI
 SPI_Init(SPI1, &SPIInit);
 //Włączenie SPI
 SPI_Cmd(SPI1, ENABLE);
}
 
//Wysłanie danych
uint8_t SPI_SEND(uint8_t byte)
{
 //Sprawdzanie zajętości bufora nadawczego, jak będzie wolny nastąpi wysłanie danych
 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
 SPI_I2S_SendData(SPI1, byte);
 
 //Sprawdzanie, czy dane są w buforze odbiorczym, jeśli tak to zostaną zwrócone
 while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
 return SPI_I2S_ReceiveData(SPI1);
}
 
//Wpisanie danych do rejestrów
void MCP_WRITE(uint8_t rejestr, uint8_t wartosc)
{
 //Ustawienie pinu CS na stan niski, wybranie urządzenia
 GPIO_ResetBits(CS_LINE, CS_PIN);
 //wpisanie danych do CS.
 SPI_SEND(0x40);
 //wyslanie informacji do którego rejestru dane zostaną przesłane
 SPI_SEND(rejestr);
 //wysłanie ustawienia wartości
 SPI_SEND(wartosc);
 //Zakończenie transmisji, zmiana stanu linii CS na wysoki
 GPIO_SetBits(CS_LINE, CS_PIN);
}
 
//Funckcja do ustawiania wybranego portu
void GP_ACTIVE(int Pin)
{
 //Ustawienie pinu jako wejście przebiega poprzez wyzerowanie
 //odpowiedniego zestawu bitow
 
 switch(Pin)
 {
  case 0: MCP_WRITE(MCP_IODIR, ~0x01); break; //GP0 bit0 Dec: 1
  case 1: MCP_WRITE(MCP_IODIR, ~0x02); break; //GP1 bit1 Dec: 2
  case 2: MCP_WRITE(MCP_IODIR, ~0x04); break; //GP2 bit2 Dec: 4
  case 3: MCP_WRITE(MCP_IODIR, ~0x08); break; //GP3 bit3 Dec: 8
  case 4: MCP_WRITE(MCP_IODIR, ~0x10); break; //GP4 bit4 Dec: 16
  case 5: MCP_WRITE(MCP_IODIR, ~0x20); break; //GP5 bit5 Dec: 32
  case 6: MCP_WRITE(MCP_IODIR, ~0x40); break; //GP6 bit6 Dec: 64
  case 7: MCP_WRITE(MCP_IODIR, ~0x80); break; //GP7 bit7 Dec: 128
  default: MCP_WRITE(MCP_IODIR, ~0x01); break;
 }
}
 
//Grupowa aktywacja pinów od GP0 do wybranego
void GP_ACTIVE_GROUP(int PinEnd)
{
 //Ta funkcja ustawia odpowiednie wartości dla pinu początkowego oraz końcowego
 //Ustawienie pinu jako wejście przebiega poprzez wyzerowanie
 //odpowiedniego zestawu bitow
 
 switch(PinEnd)
 {
  case 1: MCP_WRITE(MCP_IODIR, ~0x03); break; //GP0 bit0 Dec: 3
  case 2: MCP_WRITE(MCP_IODIR, ~0x07); break; //GP1 bit1 Dec: 7
  case 3: MCP_WRITE(MCP_IODIR, ~0x0F); break; //GP2 bit2 Dec: 15
  case 4: MCP_WRITE(MCP_IODIR, ~0x1F); break; //GP3 bit3 Dec: 31
  case 5: MCP_WRITE(MCP_IODIR, ~0x3F); break; //GP4 bit4 Dec: 63
  case 6: MCP_WRITE(MCP_IODIR, ~0x7F); break; //GP5 bit5 Dec: 127
  case 7: MCP_WRITE(MCP_IODIR, ~0xFF); break; //GP6 bit6 Dec: 255
  default: MCP_WRITE(MCP_IODIR, ~0xFF); break;
 }
}
 
//Pętla opóźniająca
void Delay(int czas)
{
    int i;
    for (i = 0; i < czas * 5000; i++) {}
}
 
//Inicjalizacja USARTA
void USARTInit(void)
{
 USART_InitTypeDef USARTInit;
 NVIC_InitTypeDef NVIC_InitStruct;
 
 NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
 
 //Ustawienie prędkości transmisji 9600bps
 USARTInit.USART_BaudRate = 9600;
 
 //Ustawienie dlugosci slowa
 USARTInit.USART_WordLength = 8;
 
 //Ustawienie bitu stopu
 USARTInit.USART_StopBits = USART_StopBits_1;
 
 //Brak kontroli parzystosci
 USARTInit.USART_Parity = USART_Parity_No;
 
 //Kontrola przepływu danych
 USARTInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
 
 //Tryb pracy TX i RX
 USARTInit.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
 
 USART_Init(USART2, &USARTInit);
 USART_Cmd(USART2, ENABLE);
 
 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //Wlaczenie przerwania dla RX
 
 //Włącznie obslugi IRQ
 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
 //Ustawienie priorytetu grupowego dla USART1 przypisane jest 0
 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0x00;
 //Podpriorytet
 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
 
 NVIC_Init(&NVIC_InitStruct);
}
 
//Wysłanie danej
void SendCharc(volatile char c)
{
 while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
 USART_SendData(USART2, c);
}
 
//Wysłanie danych
void USARTSend(volatile char *c)
{
  //Pętla działa do puki będzie jakiś znak do wysłania
  while(*c)
  {
    //Sprawdza czy rejestr danych został opróżniony
    while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
    //Prześlij dane,
    USART_SendData(USART2, *c);
    *c++;
  }
}
 
//Wprowadzenie danych do bufora
void USART_InsertToBuffer(uint8_t usart_num, uint8_t c)
{
 usart_num = 0;
 //Sprawdza dostepne miejsce w buforze
 if (usart_buf_num[usart_num] < 32)
 {
  if (usart_buf_in[usart_num] > (32 - 1))
  {
   usart_buf_in[usart_num] = 0;
  }
  USART_Buffer[usart_num][usart_buf_in[usart_num]] = c;
  usart_buf_in[usart_num]++;
  usart_buf_num[usart_num]++;
 }
}
 
//Pobranie danej
uint8_t USART_Getc(USART_TypeDef* USARTx)
{
 uint8_t usart_num = 0;
 uint8_t c = 0;
 //Sprawdza czy sa dane w buforze
 if (usart_buf_num[usart_num] > 0)
 {
  if (usart_buf_out[usart_num] > (32 - 1))
  {
   usart_buf_out[usart_num] = 0;
  }
  c = USART_Buffer[usart_num][usart_buf_out[usart_num]];
  USART_Buffer[usart_num][usart_buf_out[usart_num]] = 0;
  usart_buf_out[usart_num]++;
  usart_buf_num[usart_num]--;
 }
 return c;
}
 
//Obsługa przerwania od USARTU
#ifdef USART2
void USART2_IRQHandler(void)
{
 //Sprawdzenie czy wystapilo przerwanie z powodu odebrania danych
 if (USART_GetITStatus(USART2, USART_IT_RXNE))
 {
  #ifdef USART2_USE_CUSTOM_IRQ
  //Wywołaj funckje przerwania
  USART2_ReceiveHandler(USART2->DR);
      #else
  //Podaj dane do Buffera
  USART_InsertToBuffer(0, USART2->DR);
      #endif
 }
}
#endif