poniedziałek, 6 listopada 2017

Szybka randka z SPI i PIC24HJ

SPI (Serial Peripheral Interface)
Szeregowy interfejs służy do dwukierunkowej (full-duplex), synchronicznej transmisji danych pomiędzy mikrokontrolerem, a zewnętrznymi układami peryferyjnymi (np. przetwornik A/C i C/A, szeregowa pamięć zewnętrzna, zegar, potencjometr cyfrowy lub inny mikrokontroler)

Artykuł piszę w biegu ponieważ przy okazji rozpoczęcia testów kontrolera CAN MCP2517FD okazało się , że nie mam rozpracowanego interfejsu SPI do mikrokontrolerów PIC (wstyd jak beret) a bez niego nic nie zrobię z testowanym kontrolerem :)

Celem artykułu będzie nawiązanie komunikacji po SPI pomiędzy dwoma mikrokontrolerami 16-bitowymi PIC24HJ czyli w sumie moimi ulubionymi.

Zaczynamy od ogólnej części teoretycznej o SPI :

Komunikacja poprzez SPI odbywa się synchronicznie (czyli w takt zegara) za pomocą 3 linii:
  1. MOSI (ang. Master Output Slave Input) – dane dla układu peryferyjnego,
  2. MISO (ang. Master Input Slave Output) – dane z układu peryferyjnego,
  3. SCLK (ang. Serial CLocK) – sygnał zegarowy (taktujący).

Do aktywacji wybranego układu peryferyjnego służy dodatkowo linia SS (Slave Select – wybór układu podrzędnego).
Jeśli na magistrali SPI mamy tylko dwa urządzenia to od strony Mastera linii SS nie wykorzystujemy (nie podłączamy do Slave'a) a od strony Slave-a podłączamy do masy. W przypadku kilku urządzeń, każdemu musimy przyporządkować oddzielną linię SS czyli de fakto inny dowolny pin mikrokontrolera . SPI nie ma  implementacji automatycznego sterowania liniami SS dlatego musimy to robić na zasadzie amadeusz fajchę przełóż.Aktywny jest ten Slave, który oddzielną linią SS dostał sygnał niski.

Transfer danych przez interfejs SPI odbywa się w układzie master-slave. Jeżeli w systemie znajduje się więcej niż 1 mikrokontroler, to tylko jeden z nich w danej chwili pełni rolę urządzenia Master. Sposób połączenia układu nadrzędnego (Master) z układem podrzędnym (Slave) przedstawiono na rysunku.


Na rysunku poniżej widać poglądowo jak chodzą dane w rejestrach nadawczym i odbiorczym w procesie transmisji SPI. Master wysyła bit nr 7 który wchodzi na pozycję bitu nr 0 w Slave , jednocześnie Slave w tym samym czasie wysyła bit nr 7  i ten bit wchodzi na pozycję bitu nr 0 Mastera. I tak w koło Macieju następuje przesunięcie aż wszystkie nadawane bity przez Mastera osadzą się w swoich miejscach w rejestrze Slave a to co w tym samym czasie nadał Slave osadzi się na swoich miejscach w Masterze .
O Matko w głowie mi się zakręciło , bo SPI to taka dwustronna karuzela.



Interfejs SPI składa się z dwóch rejestrów przesuwnych i generatora sygnału taktującego . Generator sygnału taktującego znajduje się zawsze w układzie nadrzędnym (Master). Linia MISO jest wejściem danych dla urządzenia Master, a wyjściem dla Slave, natomiast linia MOSI jest wyjściem dla urządzenia Master, a wejściem dla Slave. Linia SCK jest wejściem taktującym dla układu Slave oraz wyjściem dla Master. Sygnał taktujący jest zawsze generowany przez układ nadrzędny (Master) bez względu na to czy dane są przez niego nadawane czy też odbierane. Sygnał taktujący jest nadawany jedynie podczas transmisji. Transmisje na liniach magistrali SPI są zawsze dwukierunkowe. Nadawaniu danych na linii MOSI przez układ Master towarzyszy zawsze nadawanie danych na linii MISO przez układ Slave. Nie wszystkie nadawane dane niosą informacje, najczęściej w jednym kierunku są nadawane dane niosące informacje, podczas gdy w drugim są nadawane dane puste. Typowo pierwszym nadawanym i pierwszym odbieranym bitem jest najstarszy bit (MSB), zdarzają się jednak konstrukcje np pamięci , które wymagają aby pierwszym bitem transmitowanym i odbieranym był najmłodszy bit (LSB). Rejestry konfiguracyjne umożliwiają zmianę tego parametru.
Slave nie może inicjować transmisji !!!!!!

Doktoratu z teorii o SPI nie będziemy robić dlatego część teoretyczną kończymy a bierzemy się za manula o SPI dla mikrokontrolerów 16-bitowych od Microchipa . Manual dedykowany dla serii PIC24HJ znajduje się w linkach i na nim opieramy nasze rozważania.

Mikrokontroler PIC24HJ posiada na pokładzie dwa niezależne moduły SPI oznaczone SPI1 i SPI2. Moduły SPI mogą współpracować z kanałami DMA. Warto tu nadmienić , że Microchip w swoich wybranych produktach oferuje tryb rozszerzony SPI. W trybie tym mamy do dyspozycji osiem 16-bitowych buforów FIFO. Tryb rozszerzony znajdziemy np.w rodzinie mikrokontrolerów PIC24FJ.

Moduł SPI  na obrazku wygląda tak :



W mikrokontrolerze PIC24HJ mamy aż cztery lub tylko cztery rejestry 16-bitowe. Lubię rejestry 8-bitowe i 16-bitowe a zdecydowanie mniej lubię rejestry 32-bitowe bo żeby je ogarnąć wzrokiem trzeba się odsunąć na dalszą odelgłość :)
To co najważniejsze czyli rejestry :

SPIxSTAT : SPIx Status and Control Register (x=1,2)
Rejestr statusu i kontroli.
W tym rejestrze m.in włączamy/wyłączamy moduł SPI. Tu znajdziemy różne statusy dla bufora w trybie odbioru (np.stan przepełnienia) i nadawania(np.czy bufor nadawczy zapełniony danymi) oraz określimy operacje podczas trybu oczekiwania.

SPIxCON1 : SPIx Control Register 1 (x=1,2)
Rejestr kontrolny nr 1
Tutaj ustawiamy m.in  parametry czasowe zegara takie jak preskalery (primary i secondary), włączamy/wyłączamy zegar na pinie SCK. Ustawiamy tryb Master lub Slave. Wybieramy tryb przesyłanych danych  8 bitowy lub 16 bitowy . Ustawiamy polaryzację zegara i inne fikołki na zegarze.

SPIxCON2 : SPIx Control Register 2 (x=1,2)
Rejestr kontrolny nr 2
Włącza / wyłącza operację SPI z ramkami (FSYNCx: Frame Synchronization I/O Pulse) . Ten rejestr określa również kierunek impulsu synchronizacji ramki, polaryzację i wybór zbocza.
Tryb z ramkami to tryb pracy na pinie SS w którym uzyskujemy dodatkową synchronizacje wysyłanych danych ,  pin jest aktywny podczas wysyłania ramki z danymi , generalnie tego nie będziemy używać .

SPIxBUF : SPIx Data Receive/Transmit Buffer Register (x=1,2)
Bufor odbiorczo/nadawczy

Jest to 16 bitowy bufor, który wewnętrznie składa się z dwóch oddzielnych buforów : bufor transmisji (SPIxTXB) i bufor odbiorczy (SPIxRXB). Te dwa wewnętrzne jednokierunkowe bufory współdzielą adres z SPIxBUF czyli to są takie rejestry/bufory duchy. Jeśli aplikacja użytkownika zapisuje dane do wysłania w SPIxBUF, wewnętrznie dane są zapisywane w SPIxTXB. Gdy aplikacja odczytuje odebrane dane z SPIxBUF, wewnętrznie dane są odczytywane z rejestru SPIxRXB.

Technika podwójnego buforowania operacji wysyłania / odbierania umożliwia ciągłe przesyłanie danych w tle.
Ponadto istnieje wewnętrzny 16-bitowy rejestr przesuwny (SPIxSR), który nie jest odwzorowany w pamięci. Przesuwa dane wejściowe i wyjściowe  z portu SPI.

Mikrokontroler PIC24HJ nie ma dedykowanych pinów do SPI , moduł ten możemy zdefiniowac odpowiednimi rejestrami na wybranych pinach.

Potrzebujemy zatem wybrać i ustawić piny pod SDO (MOSI), SDI (MISO) i SCK (SCLK). Musimy pamiętać o właściwej konfiguracji kierunku np. dla Mastera SCK będzie wyjściem cyfrowym a dla Slave SCK to będzie wejście cyfrowe, w praktyce załatwia nam ten problem remapping pinów. W kodzie programu zwrócimy na to uwagę. Ważne jeszcze jest podłączenie samych pinów bo jest ono trochę inne w tym przypadku niż to by wynikało z rysunków w części teoretycznej, uczulam na to bo sam się na tym dałem złapać :). W praktyce połączenia powinny wyglądać następująco : 

SDO (Master) ---> SDI (Slave)
SDI   (Master) ---> SDO (Slave)
SCK (Master)  ---> SCK (Slave)



Ustawiamy sobie zatem piny tak :

Pin 11 (RP4 - Remapping Pin 4)  - SDO
Pin 14 (RP5 - Remapping Pin 5)  - SDI
Pin 15 (RP6 - Remapping Pin 6)  - SCK


Teraz pytanie jak zrobić przyporządkowanie poszczególnych pinów do modułu SPI ? Poniżej przedstawiam jeden z takich sposobów, nadmienię tylko, że istnieje prostszy.

****************************************************************
USTAWIENIE PINÓW DLA MASTERA (remaping pinów) : 
****************************************************************
Dla podpięcia pinu 11 (RP4) do SDO korzystamy z Table 11-2 na stronie 139 (datasheet) i rejestru RPOR2 (Peripheral Pin Select Output Register 2). Z tabelki odczytujemy wartość jaką trzeba wpisać do rejestru .Dla funkcji SDO1 odczytujemy z tabelki wartość bitową 00111 (dziesiętnie 7) i to musimy wpisać na 5-ciu bitach do rejestru RPOR2 dokładnie na bicie od 0 do 4 (przyporządkowanie do pinu 11 oznaczonego RP4). Zapis do rejestru umożliwia nam wygodna struktura oferowana przez plik nagłówkowy naszego mikrokontrolera i wygląda to tak :RPOR2bits.RP4R = 7; 
I tak jednym prostym wpisem w rejestr załatwimy przyporządkowanie SDO do pinu nr 11.

Dla podpięcia pinu 14 (RP5) do SDI korzystamy z Table 11-1 na stronie 138 (datasheet) i rejestru RPINR20 (Peripheral Pin Select Input Register 20). Na pięciu pierwszych bitach w/w rejestru wpisujemy numer RPx (w naszym przypadku pin nr 14 ma oznaczenie RP5) czyli 5 (dziesiętnie) i tyle trzeba wpisać w rejestr RPINR20. Wygląda to tak :RPINR20bits.SDI1R = 5;

Dla podpięcia pinu 15 (RP6) do SCK korzystamy z Table 11-2 na stronie 139 (datasheet) i rejestru RPOR3 (Peripheral Pin Select Output Register 3). Z tabelki odczytujemy wartość jaką trzeba wpisać do rejestru .Dla funkcji SCK1 odczytujemy z tabelki wartość bitową 01000 (dziesiętnie 8) i to musimy wpisać na 5-ciu bitach do rejestru RPOR3 dokładnie na bicie od 0 do 4 (przyporządkowanie do pinu 15 oznaczonego RP6). Zapis do rejestru umożliwia nam wygodna struktura oferowana przez plik nagłówkowy naszego mikrokontrolera i wygląda to tak :RPOR3bits.RP6R = 8;

************************************************************
USTAWIENIE PINÓW DLA SLAVE (remaping pinów) :
************************************************************
Pin 11 i 14 czyli SDO i SDI  konfigurujemy tak samo jak w przypadku Mastera.
Inaczej natomiast będzie konfigurowany pin 15 czyli SCK. W przypadku Mastera mieliśmy SPI1 Clock Output a w przypadku Slave mamy SPI1 Clock Input.

Dla podpięcia pinu 15 (RP6) do SCK korzystamy z Table 11-1 na stronie 138 (datasheet) i rejestru RPINR20 (Peripheral Pin Select Input Register 20). Na bitach 8-12 w/w rejestru wpisujemy numer RPx (w naszym przypadku pin nr 15 ma oznaczenie RP6) czyli 6 (dziesiętnie) i tyle trzeba wpisać w rejestr RPINR20. Wygląda to tak :RPINR20bits.SCK1R = 6;.

I to by było na tyle jeśli chodzi o remaping pinów dla potrzeb interfejsu SPI.
Teraz przechodzimy do części sprzętowej :

Do testów SPI potrzebować będziemy :
po stronie Mastera : mikrokontroler PIC24HJ128GP502 i wyświetlacz LCD
po stronie Slave : tylko mikrokontroler PIC24HJ128GP502

Poniżej schemat elektryczny łącznie dla Mastera i Slave'a . Sekcję LED i PICkit3 powielić musimy dla Mastera i Slave'a oddzielnie.




Schemat komunikacji będzie wyglądał tak, że Master wysyła jakieś dane na które Slave reaguje i odsyła inne dane. Master wyświetla odebrane dane na wyświetlaczu. Najprościej jak tylko się da bo naszym celem nadrzędnym jest uruchomienie i skonfigurowanie transmisji SPI tak aby można było ją zaprząc do testów kontrolera CAN MCP2517FD

Słów kilka o zegarze magistrali SPI czyli sygnał zegarowy jaki lata na SCK. Podczas testów nie udało mi się pchnąć transmisji SPI z zegarem SCK = 10 MHz . Testy odpaliły przy SCK = 5 MHz. Nie miałem czasu wnikać w istotę problemu być może ta notatka na wykresie poniżej to tłumaczy. Zegar ustawiamy za pomocą dwóch preskalerów :


Warto pochylić głowę nad wykorzystaniem kanałów DMA do transmisji SPI, ale to może kiedyś rozkminimy oddzielnym wpisem.



Pozdrawiam
picmajster.blog@gmail.com

Linki :
SPI - manual dla PIC24HJ
SPI - manual dla rodziny PIC24/ds33 
PIC24HJ128GP502 - datasheet


Co robi program :

Master wysyła bajt 49 i jednocześnie sprawdza czy odebrany bajt ma wartość 50 jeśli tak to zmienia stan diody LED. Ponadto jeśli przyjdzie jakikolwiek bajt od Slave zostanie wyświetlony na LCD.
Tutaj zwracam uwagę , że odebrany od Slave bajt możemy uzyskać zarówno w funkcji wysyłającej Transfer_SPI(bajt) bo wysłanie bajtu jest równoważne z zapełnieniem rejestru danymi przychodzącymi. Drugim źródłem pozyskania danych od Slave jest przerwanie.

Pliki projektu :

dogm204.h     - plik deklaracji dla wyświetlacza firmy EA model DOGM204-A (4x20)

ustaw_zegar.h - plik deklaracji dla obsługi zegara wewnętrznego ok 40 MHz.

dogm204.c     - plik definicji dla wyświetlacza firmy EA model DOGM204-A (4x20)

ustaw_zegar.c - plik definicji dla obsługi i konfiguracji zegara
main.c        - główny plik programu

Powoli będę przechodził z kodami na GIT HUB-a, będzie bardziej przejrzyście.

KOD DLA MASTERA : 


/*****************************************************************************
  FileName:        ustaw_zegar.h
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/

#ifndef USTAW_ZEGAR_H
#define USTAW_ZEGAR_H

#define FCY 40000000 /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
/*deklaracja funkcji*/
void ustaw_zegar(void) ;

#endif  /* USTAW_ZEGAR_H */
/*****************************************************************************
  FileName:        dogm204.h
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/
#ifndef DOGM204_H
#define DOGM204_H

/*_TRISB3 --> TRISBbits.TRISB3*/
#define TRIS_RESET   _TRISA0
#define TRIS_RW      _TRISB3
#define TRIS_RS      _TRISA2
#define TRIS_E       _TRISB2
#define TRIS_DB4     _TRISB12
#define TRIS_DB5     _TRISB13
#define TRIS_DB6     _TRISB14
#define TRIS_DB7     _TRISB15
/*_RB3 --> PORTBbits.RB3*/
#define RESET   _RA0
#define RW      _RB3
#define RS      _RA2
#define E       _RB2
#define DB4     _RB12
#define DB5     _RB13
#define DB6     _RB14
#define DB7     _RB15

/* przyporzadkowanie adresow pamieci DD-RAM do pol wyswietlacza*/
#define LCD_Line1 0x00 /*adres 1 znaku 1 wiersza */
#define LCD_Line2 0x20 /*adres 1 znaku 2 wiersza */
#define LCD_Line3 0x40 /*adres 1 znaku 3 wiersza */
#define LCD_Line4 0x60 /*adres 1 znaku 4 wiersza */

#define CGRAM_SET 0x40

 void Wyslij_do_LCD(unsigned char bajt);
 void WlaczLCD();
 void WyswietlLCD(char *napis);
 void lcd_int(uint16_t val);
 void UstawKursorLCD(uint8_t y, uint8_t x);
 void CzyscLCD();
 void WpiszSwojeZnaki(void);
 void DefineCharacter(int8_t nr, char *znak);
#endif  /* DOGM204_H */
/*****************************************************************************
  FileName:        ustaw_zegar.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
 *****************************************************************************/
    /*Ustawiamy zegar wewnetrzny na ok 40 MHz (dokladnie na 39.61375 MHz*/
    #include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik 
    naglówkowy "p24HJ128GP502.h"*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h> /*definicje typów do uint8_t itp*/
    #include "ustaw_zegar.h" /*z uwagi na FCY musi byc przed #include <libpic30.h>*/

    /*definicja funkcji*/
    void ustaw_zegar(void) {
    /*
    * to co mozemy ustawic za pomoca '#pragma' jest dostepne w pliku 
    * xc16/docs/config_index.html
    */
    #pragma config JTAGEN = OFF
    // Watchdog timer enable/disable by user software
    #pragma config FWDTEN = OFF 

    //********************Start Ustawien Zegara************************
    /* 
    * Fcy - zegar instrukcji , Fosc - zegar rdzenia (jest zawsze dwa razy wiekszy 
    * od zegara instrukcji)) Ustawiamy zegar instrukcji na 40 Mhz z wewnetrznego 
    * oscylatora Fin=7.37 MHz w/g wzoru Fcy=Fosc/2 gdzie Fosc=Fin x (M/(N1+N2))
    * gdzie M=43, N2=2, N1=2 ustawiane w rejestrach PLLFBD/PLLPOST/PLLPRE
    */
    //Select Internal FRC (Fast RC Oscillator)
    #pragma config FNOSC = FRC // FOSCSEL-->FNOSC=0b000 (Fast RC Oscillator (FRC))
    //Enable Clock Switching and Configure
    #pragma config FCKSM = CSECMD //FOSC-->FCKSM=0b01 - wlacz zegar
    #pragma config OSCIOFNC = OFF //FOSC-->OSCIOFNC=1 - Fcy b?dzie na pinie OSCO

    /*Config PLL prescaler, PLL postscaler, PLL divisor*/
    PLLFBD = 41 ; //M=43 (0 bit to 2 st?d 41 = 43 patrz w rejestrze), tutaj 3.685 x 43 = 158.455MHz
    CLKDIVbits.PLLPRE=0 ;  //N1=2, tutaj 7.37 MHz / 2 = 3.685 MHz
    CLKDIVbits.PLLPOST=0 ; //N2=2, tutaj 158.455 MHz / 2 = 79.2275 MHz (Fosc)
    /*   
    * UWAGA przerwania musza byc wylaczone podczas wywolywania ponizszych 
    * dwóch funkcji __builtin_write_...brak definicji w pliku naglówkowym
    * to wewnetrzne funkcje kompilatora patrz help M-LAB IDE
    * i datasheet str 140(11.6.3.1 Control Register Lock)
    */
    /*Initiate Clock Switch to Internal FRC with PLL (OSCCON-->NOSC = 0b001)*/
      __builtin_write_OSCCONH(0x01); //tutaj argumentem jest wartosc z NOSC
    /*Start clock switching*/
      __builtin_write_OSCCONL(0x01);

    /*Wait for Clock switch to occur*/
      while(OSCCONbits.COSC !=0b001);

      /*Wait for PLL to lock*/
      while(OSCCONbits.LOCK !=1) {};

    }
/*****************************************************************************
  FileName:        dogm204.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/
#include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h> /*definicje typów uint8_t itp*/
#define FCY 40000000UL /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
aby przed includowaniem <libpic30.h>, potrzebne to jest to wyliczania delay-i*/
#include <libpic30.h> /*biblioteka dajaca dostepp do delay-i.*/
#include "dogm204.h"

/*definicje funkcji*/
void Wyslij_do_LCD(unsigned char bajt)
{
    /*ustaw linie EN, przed wysylka danych*/

    E = 1;
    /*wyslanie 4 najstarszych bitow danych*/
    if(bajt & 0x80) DB7 = 1else DB7 = 0;
    if(bajt & 0x40) DB6 = 1else DB6 = 0;
    if(bajt & 0x20) DB5 = 1else DB5 = 0;
    if(bajt & 0x10) DB4 = 1else DB4 = 0;
    __delay_us(1);
    /*potwierdzenie wyslania danych (opadajacym zboczem EN)*/
    E = 0;
        
    /*ustawienie EN*/
    __delay_us(1);
    E = 1;
    /*wyslanie 4 najmlodszych bitow danych*/    
    if(bajt & 0x08) DB7 = 1else DB7 = 0;
    if(bajt & 0x04) DB6 = 1else DB6 = 0;
    if(bajt & 0x02) DB5 = 1else DB5 = 0;
    if(bajt & 0x01) DB4 = 1else DB4 = 0;
    __delay_us(1);
    /*potwierdz wysylke danych opadajacym zboczem EN*/
    E = 0;

    __delay_us(16);
    
}   


void WlaczLCD()
{
    /*ustawienie kierunku wyjsciowego linii podlaczonych do LCD*/
    TRIS_RESET = 0 ;
    TRIS_RW = 0 ;
    TRIS_RS = 0;
    TRIS_E = 0;
    TRIS_DB7 = 0;
    TRIS_DB6 = 0;
    TRIS_DB5 = 0;
    TRIS_DB4 = 0;

    /*zerowanie linii*/
    RESET = 1 ; /* 0 - Stan aktywny*/
    RW = 0 ;
    RS = 0/* 0 - wskazuje na rejestr rozkazow / 1 - wskazuje na rejestr danych*/
    E = 0;
    DB7 = 0;
    DB6 = 0;
    DB5 = 0;
    DB4 = 0;

    /*Start Inicjalizacji DOGM204 tryb 4-bity*/
    /*zaczekaj co najmniej 5 ms na ustabilizowanie sie napiecia*/
    __delay_ms(5);
    /*Hardware Reset 10ms*/
    RESET = 0 ;
    __delay_ms(10);
    RESET = 1 ;
    __delay_ms(1);
    
  /*Sekwencja startowa dla trybu 4-bit, patrz mini-datasheet str 5*/
  Wyslij_do_LCD(0x33);//wysylamy instrukcje do rejestru rozkazow
  Wyslij_do_LCD(0x32);
  Wyslij_do_LCD(0x2A);
  Wyslij_do_LCD(0x09);
  Wyslij_do_LCD(0x06);
  Wyslij_do_LCD(0x1E);
  Wyslij_do_LCD(0x29);
  Wyslij_do_LCD(0x1B);
  Wyslij_do_LCD(0x6E);
  Wyslij_do_LCD(0x57);
  Wyslij_do_LCD(0x72);
  Wyslij_do_LCD(0x28);
  Wyslij_do_LCD(0x0F); /*Display on, cursor on, blink on*/
  CzyscLCD();
RS = 1 ; /*przelacz na rejestr danych*/  
        
 /*Koniec inicjalizacji i ustawien wyswietlacza DOGM204*/      
}

void WyswietlLCD(char *napis)
{
    while(*napis){
    Wyslij_do_LCD(*napis++);
    }
         
}
// wyslanie liczby dziesietnej
    void lcd_int(uint16_t val)
    {
    char bufor[17];
    sprintf(bufor,"%i",val);
    WyswietlLCD(bufor);
    }

void UstawKursorLCD(uint8_t y, uint8_t x)
{
    uint8_t n ;
    /*y (wiersze) = 1 do 4*/
    /*x (kolumna) = 1 do 20*/
    /*ustal adres pocz?tku znaku w wierszu*/
    switch(y)
    {
        case 1: y = LCD_Line1 ;break;
        case 2: y = LCD_Line2 ;break;
        case 3: y = LCD_Line3 ;break;
        case 4: y = LCD_Line4 ;break;
    
    }
    /*ustal nowy adres pami?ci DD RAM*/
    /*ustaw bajt do Set DDRAM adres*/
    /* x odejmujemy jeden aby przekonwertowa? z 0-19 na 1-20 */
    n = 0b10000000 + y + (x-1) ;
    
    /*wy?lij rozkaz ustawienia nowego adresu DD RAM*/
    RS = 0/*stan niski na lini? RS, wybieramy rejestr instrukcji*/
    Wyslij_do_LCD(n);
    RS = 1;  /*prze??cz na rejestr danych */ 
}

void CzyscLCD()
{
    RS = 0/*przelacz na rejestr rozkazow*/
    Wyslij_do_LCD(1);
    RS = 1/*przelacz na rejestr danych*/
    /*czekaj ??*/
    __delay_us(1);
/*funkcja definiuje jeden znak*/
void WpiszSwojeZnaki(void) {
    /*definicja wlasnych znaków maks 8 szt*/
    char znak1[]= {0,0,14,17,31,16,14,2}; /* definicja literki e z ogonkiem */
    char znak2[]= {0x0C,0x12,0x12,0x0C,0,0,0,0};/*definicja stC*/
    int i; 
    /* adresy poczatku definicji znaku to wielokrotnosc osmiu DEC(0,8,16,24,32,40,48,56)
     * ale uwaga wazne ! adresy kodowane sa na 6 mlodszych bitach dwa najstarsze bity
     * to zawsze  01 (01AAAAAA-gdzie A adres).Uwzgledniajac wartosc calego bajtu
     * adresy poczatku beda wygladal tak HEX(0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x78)
     * Aby wpisac do pamieci wyswietlacza zdefiniowany znak nalezy najpierw wyslac 
     * do rejestru rozkazów (RS na 0) adres poczatku definicji znaku 
     * a w drugim kroku przesylamy dane (RS=1) 8 x bajt (tablica) definjujace obraz znaku*/
    
    RS = 0 ;/*stan niski na linii RS, wybieramy rejestr instrukcji*/
     /*wysylamy instrukcje do rejestru rozkazow (ustaw adres poczatkowy w CGRAM 
      na nasz znak w tym przypadku znak na pozycji drugiej) */
    Wyslij_do_LCD(0x48);/*wysylamy instrukcje do rejestru rozkazow 
     (ustaw adres poczatkowy w CGRAM na nasz znak w tym przypadku znak na pozycji drugiej) */
    
    RS = 1 ;/*stan wysoki na linii RS, wybieramy rejestr danych*/
    /*wysylamy 8 x bajt zdefiniowanego w tablicy znak1[] znaku*/
    for(i=0;i<=7;i++)
    {
       Wyslij_do_LCD(znak1[i]);
    }
   
    RS = 0 ;/*stan niski na lini RS, wybieramy rejestr instrukcji*/
    /*ustawiamy adres DDRAM na pierwszy znak w pierwszej linii, nie zapomnijmy
     o tym poniewaz inaczej zostaniemy w pamieci CGRAM*/
    Wyslij_do_LCD(0x80);
    RS = 1 ; /*stan wysoki na linii RS, wybieramy rejestr danych*/
}
/*funkcja uniwersalna do definiowania znaku*/
void DefineCharacter(int8_t nr, char *znak)/*nr 0...7, *znak to wskaznik na tablice z danymi*/
{
    int i; 
    /* adresy poczatku definicji znaku to wielokrotnosc osmiu DEC(0,8,16,24,32,40,48,56)
     * ale uwaga wazne ! adresy kodowane sa na 6 mlodszych bitach dwa najstarsze bity
     * to zawsze  01 (01AAAAAA-gdzie A adres).Uwzgledniajac wartosc calego bajtu
     * adresy poczatku beda wygladal tak HEX(0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x78)
     * Aby wpisac do pamieci wyswietlacza zdefiniowany znak nalezy najpierw wyslac 
     * do rejestru rozkazów (RS na 0) adres poczatku definicji znaku 
     * a w drugim kroku przesylamy dane (RS=1) 8 x bajt (tablica) definjujace obraz znaku*/
    
    RS = 0 ;/*stan niski na linii RS, wybieramy rejestr instrukcji*/
     /*wysylamy instrukcje do rejestru rozkazow*/
    Wyslij_do_LCD((nr*8)|CGRAM_SET);/*ustaw adres poczatkowy w CGRAM na nasz znak*/
    
    RS = 1 ;/*stan wysoki na linii RS, wybieramy rejestr danych*/
    /*wysylamy 8 x bajt zdefiniowanego w tablicy znak[] znaku*/
    for(i=0;i<=7;i++)
    {
       Wyslij_do_LCD(*znak++);
    }
   
    RS = 0 ;/*stan niski na lini RS, wybieramy rejestr instrukcji*/
    /*ustawiamy adres DDRAM na pierwszy znak w pierwszej linii, nie zapomnijmy
     o tym poniewaz inaczej zostaniemy w pamieci CGRAM*/
    Wyslij_do_LCD(0x80);
    RS = 1 ; /*stan wysoki na linii RS, wybieramy rejestr danych*/ 
    
    /*definiowanie znaku DefineCharacter(1,tablica) gdzie nr 0...7 a tablica to 
     np char tablica[]= {0x0C,0x12,0x12,0x0C,0,0,0,0} definicja stC*/
    
    /*wywolanie zdefiniowanego znaku Wyslij_do_LCD(nr) gdzie nr to 0...7 lub
     WyswietlLCD("\nr"*/
}
/*****************************************************************************
  FileName:        main.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
  Created on:      14 listopada 2017, 09:37
  Description:     SPI MASTER
 ******************************************************************************/

#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*definicje typów uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/
#include "dogm204.h"

void spi_config_master(void);
uint8_t Transfer_SPI(uint8_t bajt);

volatile uint8_t SPI_receive_data ;

#define LED1_TOG PORTA ^= (1<<_PORTA_RA1_POSITION) /*zmienia stan bitu na przeciwny*/

int main(void) {
    ustaw_zegar(); /*odpalamy zegar wewnetrzny na ok 40MHz*/
    __delay_ms(50); /*stabilizacja napiec*/
    /*
     * wylaczamy ADC , wszystkie piny chcemy miec cyfrowe
     * pojedynczo piny analogowe wylaczamy w rejestrze AD1PCFGL 
     * Po resecie procka piny oznaczone ANx sa w trybie analogowych wejsc.
     */
    PMD1bits.AD1MD = 1/*wylaczamy ADC*/
    /* 
     * ustawiamy wszystkie piny analogowe (oznacznone ANx) jako cyfrowe
     * do zmiany mamy piny AN0-AN5 i AN9-AN12 co daje hex na 16 bitach = 0x1E3F
     */
    AD1PCFGL = 0x1E3F;
    TRISAbits.TRISA1 = 0 ; // RA1 jako wyjscie tu mamy LED
/*remaping pinow na potrzeby SPI
 SDO --> pin 11
 SDI --> pin 14
 SCK --> pin 15
 */
   
    RPOR2bits.RP4R = 7;     /*inaczej _RP4R = 7*/
    RPINR20bits.SDI1R = 5;  /*inaczej _SDI1R = 5*/
    RPOR3bits.RP6R = 8;     /*inaczej _RP6R = 8*/
    
    WlaczLCD(); /*LCD Init*/
    UstawKursorLCD(1,6);
    WyswietlLCD("TEST SPI");
    UstawKursorLCD(2,1);
    WyswietlLCD("Send data :");
    UstawKursorLCD(3,1);
    WyswietlLCD("Receive data :"); 
        
    spi_config_master();
    SPI_receive_data = 0;
    
    while (1) {
        
        if(Transfer_SPI(49) == 50) LED1_TOG  ; /*Send and Receive data to be transmitted*/
        
        WyswietlLCD("  ");
        
        if(SPI_receive_data){ /*jesli cos odebrane*/
            UstawKursorLCD(3,16);
            lcd_int(SPI_receive_data); /*daj na ekran odebrany bajt*/
            SPI_receive_data = 0;
        }            
        __delay_ms(1000) ; /*bee*/
                      
    }

}
/*konfiguracja SPI dla Mastera*/
void spi_config_master(void) {
     
IFS0bits.SPI1IF = 0;                  /*Clear the Interrupt Flag*/
IEC0bits.SPI1IE = 0;                  /*Disable the Interrupt*/
/*Set clock SPI on SCK, 40 MHz / (4*2) = 5 MHz*/
SPI1CON1bits.PPRE = 0b10;             /*Set Primary Prescaler 1:4*/
SPI1CON1bits.SPRE = 0b110;            /*Set Secondary Prescaler 2:1*/

SPI1CON1bits.MODE16 = 0;              /*Communication is word-wide (8 bits)*/
SPI1CON1bits.MSTEN = 1;               /*Master Mode Enabled*/
SPI1STATbits.SPIEN = 1;               /*Enable SPI Module*/
SPI1BUF = 0x0000;
IFS0bits.SPI1IF = 0;                  /*Clear the Interrupt Flag*/
IEC0bits.SPI1IE = 1;                  /*Enable the Interrupt*/
}

uint8_t Transfer_SPI(uint8_t bajt){
    SPI1BUF = bajt; /*wysylamy bajt*/
    UstawKursorLCD(2,16);
    lcd_int(bajt); /*daj na ekran wysylany bajt*/
    while(SPI1STATbits.SPITBF); /*czekamy na zakonczenie transferu*/
    return SPI1BUF ; /*odczytujemy dane*/
}

/*Obsluga wektora przerwania dla SPI1 , przerwanie zglaszane po zakonczeniu transferu*/
void __attribute__((interrupt, no_auto_psv))_SPI1Interrupt(void)
{
    SPI_receive_data = SPI1BUF; /*pobieramy dane przychodzace*/

    IFS0bits.SPI1IF = 0 ; /*Clear SPI1 Interrupt Flag*/
}


KOD DLA SLAVE : 


/*****************************************************************************
  FileName:        ustaw_zegar.h
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
******************************************************************************/

#ifndef USTAW_ZEGAR_H
#define USTAW_ZEGAR_H

#define FCY 40000000 /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
/*deklaracja funkcji*/
void ustaw_zegar(void) ;

#endif  /* USTAW_ZEGAR_H */
/*****************************************************************************
  FileName:        ustaw_zegar.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
 *****************************************************************************/
    /*Ustawiamy zegar wewnetrzny na ok 40 MHz (dokladnie na 39.61375 MHz*/
    #include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik 
    naglówkowy "p24HJ128GP502.h"*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h> /*dyrektywy do uint8_t itp*/
    #include "ustaw_zegar.h" /*z uwagi na FCY musi byc przed #include <libpic30.h>*/

    /*definicja funkcji*/
    void ustaw_zegar(void) {
    /*
    * to co mozemy ustawic za pomoca '#pragma' jest dostepne w pliku 
    * xc16/docs/config_index.html
    */
    #pragma config JTAGEN = OFF
    // Watchdog timer enable/disable by user software
    #pragma config FWDTEN = OFF 

    //********************Start Ustawien Zegara************************
    /* 
    * Fcy - zegar instrukcji , Fosc - zegar rdzenia (jest zawsze dwa razy wiekszy 
    * od zegara instrukcji)) Ustawiamy zegar instrukcji na 40 Mhz z wewnetrznego 
    * oscylatora Fin=7.37 MHz w/g wzoru Fcy=Fosc/2 gdzie Fosc=Fin x (M/(N1+N2))
    * gdzie M=43, N2=2, N1=2 ustawiane w rejestrach PLLFBD/PLLPOST/PLLPRE
    */
    //Select Internal FRC (Fast RC Oscillator)
    #pragma config FNOSC = FRC // FOSCSEL-->FNOSC=0b000 (Fast RC Oscillator (FRC))
    //Enable Clock Switching and Configure
    #pragma config FCKSM = CSECMD //FOSC-->FCKSM=0b01 - wlacz zegar
    #pragma config OSCIOFNC = OFF //FOSC-->OSCIOFNC=1 - Fcy b?dzie na pinie OSCO

    /*Config PLL prescaler, PLL postscaler, PLL divisor*/
    PLLFBD = 41 ; //M=43 (0 bit to 2 st?d 41 = 43 patrz w rejestrze), tutaj 3.685 x 43 = 158.455MHz
    CLKDIVbits.PLLPRE=0 ;  //N1=2, tutaj 7.37 MHz / 2 = 3.685 MHz
    CLKDIVbits.PLLPOST=0 ; //N2=2, tutaj 158.455 MHz / 2 = 79.2275 MHz (Fosc)
    /*   
    * UWAGA przerwania musza byc wylaczone podczas wywolywania ponizszych 
    * dwóch funkcji __builtin_write_...brak definicji w pliku naglówkowym
    * to wewnetrzne funkcje kompilatora patrz help M-LAB IDE
    * i datasheet str 140(11.6.3.1 Control Register Lock)
    */
    /*Initiate Clock Switch to Internal FRC with PLL (OSCCON-->NOSC = 0b001)*/
      __builtin_write_OSCCONH(0x01); //tutaj argumentem jest wartosc z NOSC
    /*Start clock switching*/
      __builtin_write_OSCCONL(0x01);

    /*Wait for Clock switch to occur*/
      while(OSCCONbits.COSC !=0b001);

      /*Wait for PLL to lock*/
      while(OSCCONbits.LOCK !=1) {};

    }
/*****************************************************************************
  FileName:        main.c
  Processor:       PIC24HJ128GP502
  Compiler:        XC16 ver 1.30
  Created on:      14 listopada 2017, 09:37
  Description:     SPI SLAVE
 ******************************************************************************/

#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*definicje typów uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/

void spi_config_slave(void);

volatile uint16_t SPI_receive_data ;

#define LED1_TOG PORTA ^= (1<<_PORTA_RA1_POSITION) /*zmienia stan bitu na przeciwny*/
int main(void) {
    ustaw_zegar(); /*odpalamy zegar wewnetrzny na ok 40MHz*/
    __delay_ms(50); /*stabilizacja napiec*/
    /*
     * wylaczamy ADC , wszystkie piny chcemy miec cyfrowe
     * pojedynczo piny analogowe wylaczamy w rejestrze AD1PCFGL 
     * Po resecie procka piny oznaczone ANx sa w trybie analogowych wejsc.
     */
    PMD1bits.AD1MD = 1/*wylaczamy ADC*/
    /* 
     * ustawiamy wszystkie piny analogowe (oznacznone ANx) jako cyfrowe
     * do zmiany mamy piny AN0-AN5 i AN9-AN12 co daje hex na 16 bitach = 0x1E3F
     */
    AD1PCFGL = 0x1E3F;
    
    TRISAbits.TRISA1 = 0 ; // RA1 jako wyjscie tu mamy LED
/*remaping pinow na potrzeby SPI
 SDO --> pin 11
 SDI --> pin 14
 SCK --> pin 15
 */
    RPOR2bits.RP4R = 7;     /*inaczej _RP4R  = 7*/
    RPINR20bits.SDI1R = 5;  /*inaczej _SDI1R = 5*/
    RPINR20bits.SCK1R = 6;  /*inaczej _SCKIR = 6*/
    
    spi_config_slave();
       
    while (1)
    {
           
    }

}

/*Konfiguracja SPI dla SLAVE*/
void spi_config_slave(void) {
    
SPI1BUF = 0;
IFS0bits.SPI1IF = 0;       /*Clear the Interrupt Flag*/
IEC0bits.SPI1IE = 0;       /*Disable The Interrupt*/

SPI1CON1bits.MODE16 = 0;   /*Communication is word-wide (8 bits)*/
SPI1STATbits.SPIEN = 1;    /*Enable SPI Module*/
/*Interrupt Controller Settings*/
IFS0bits.SPI1IF = 0;       /*Clear the Interrupt Flag*/
IEC0bits.SPI1IE = 1;       /*Enable The Interrupt*/
}


/*Obsluga wektora przerwania dla SPI1 , przerwanie zglaszane po zakonczeniu transferu*/
void __attribute__((interrupt, no_auto_psv))_SPI1Interrupt(void)
{
SPI_receive_data = SPI1BUF; /*pobieramy odebrane dane*/
LED1_TOG ; /*zmieniaj stan wyjscia na przeciwny*/
if(SPI_receive_data == 49) SPI1BUF = 50 ; /*jesli Master przyslal 49 zaladuj dane do wyslania czyli 50*/
SPI_receive_data = 0/*zeruj*/
IFS0bits.SPI1IF = 0 ; /*Clear SPI1 Interrupt Flag*/
}

1 komentarz:

  1. A person necessarily help to make seriously articles
    I'd state. That is the first time I frequented your web
    page and thus far? I amazed with the research you made to make this actual publish incredible.
    Fantastic job!

    OdpowiedzUsuń