piątek, 10 marca 2017

I2C - pierwsze starcie.

I2C należy do niezbyt lubianych peryferiów, taką opinię znajdziemy w książce Pawła Borkowskiego "Mikrokontrolery PIC w praktycznych zastosowaniach". Autor książki nazywa wręcz "koszmarem" ten model komunikacji :). Odsyłam jednak do tej pozycji i gorąco ją polecam, ponieważ I2C jest tam tak doskonale i prosto wyjaśnione od podstaw , że nie znajdziemy lepszej alternatywy w literaturze i w necie na ten temat.
To co dla innych jest "koszmarem" ja traktuję jako wyzwanie :).







Peryferia I2C w PIC24H/dsPIC33 są na stałe przyporządkowane do pinów. Nie ma tutaj opcji remappingu tak jak to było np z UART. Ale wcale nam to nie przeszkadza , mamy w tym przypadku ciut mniej roboty.
Do dyspozycji mamy dwa moduły I2C , przyporządkowanie pinów jak na rysunku powyżej. My na warsztat weźmiemy I2C nr 1 czyli użyjemy SDA1(pin18) i SCL1(pin17). SDA to linia nadawania/odbioru danych a SCL to linia zegara.

Do ćwiczeń będzie nam potrzebna pamięć EEPROM,
 ja dla wygody posłużyłem się małym modulikiem zakupionym w sieci, wystarczy w google wrzucić hasło "moduł pamięci EEPROM" i zgłoszą się sklepy czy allegro. Mój moduł wygląda tak :
Na pokładzie modułu była pamięć byłej firmy Atmel 24C256 ale zdecydowałem się zaaplikować pamięć firmy Microchip 24LC512, przystosowanej do pracy z niskimi napięciami zasilania.
Pamięć Atmel co prawda też daje rade przy 3.3 V ale ma dwa słabsze punkty w stosunku do pamięci Microchipa, czas retencji danych tylko 40 lat :) - Microchip oferuje ponad 200 lat :) i zapis jednej strony na 64 bajtach - Microchip oferuje 128 bajtów na stronę. Niby to pierdoły bo kto dziś w zdrowiu przeżyje ponad 200 lat aby mu się chciało odpytać po tylu latach EEPROM.
Ale przyjąłem założenie, że skoro moja fascynacja poszła w kierunku Microchipa i jego produktów to trzymam tam gdzie mogę konsekwentnie ten kierunek.

Podłączenie wszystkich elementów potrzebnych do zabawy na rysunku w dalszej części artykułu.

Zakresy  Adresów w zależności od typu pamięci EEPROM:
24C02 -> 0 - 255
24C04 -> 0 - 511
24C08 -> 0 - 1023
24C16 -> 0 - 2047
24C32 -> 0 - 4095
24C64 -> 0 - 8191
24C128 -> 0 - 16383
24C256 -> 0 - 32767
24LC512 -> 0 - 65535 (nasza pamięć)

Nasza pamięć podzielona  jest na 128 bajtowe strony mamy ich zatem 512 sztuk. Wynika stąd pewnego rodzaju ograniczenie , ponieważ możemy zapisać w jednym ciągu maksymalnie 128 bajtów (wymiar jednej strony) i wtedy wymagane jest wymuszenie opóźnienia maksymalnie 5 ms aby dane się zapisały. Żeby zatem zapisać całą pamięć stronami ciurkiem potrzeba by było wprowadzić opóźnienie rzędu ok 2.5 sekundy !!!!. Dla mikrokontrolera to wieczność. Trzeba szukać rozwiązania programowego aby zapis realizować w tle i nie blokować mikrokontrolera. Warto tutaj wspomnieć, że Microchip ma w swojej ofercie bardzo ciekawe pamięci pozbawione wady oczekiwania  na zapis EERAM




Najtrudniejszym elementem konfiguracji I2C było wyliczenie wartości BRG (Baud Rate Generator), która jest niezbędna do wygenerowania zadanej częstotliwości zegara na linii SCL. Trudność polegała na tym ,że trzeba było ruszyć tyłka i poszukać w domu kalkulatora, ostatecznie skorzystałem z tego w PC :). Wzór wygląda następująco :

gdzie FSCL - to częstotliwość pracy zegara na linii SCL, 100 kHz lub 400 kHz lub 1 MHz.
Delay - to jakieś tam opóźnienie, które trzeba wziąć pod uwagę mój wybór padł na wartość pośrodku czyli 120 ns.
FCY - to zegar naszego mikrokontrolera w naszym przypadku ok 40MHz.
Wyliczone wartości podane są w kodzie programu w funkcji inicjalizującej I2C.

Wyliczoną wartość trzeba wklepać przy inicjacji I2C do rejestru I2C1BRG.
Z rejestrami i ich opcjami zapoznać się można w materiałach w linku poniżej (I2C Reference Manual), nie będę tutaj ich opisywał , żeby głowa nie rozbolała. Naszym zadaniem jest w jak najprostszy sposób skonfigurować I2C bez doktoryzowania się w opcjach dodatkowych , których jest dosyć sporo. Przytoczę jednak podstawowe rejestry a są nimi : 
I2CxCON: I2Cx CONTROL REGISTER (gdzie x=1lub2)
I2CxSTAT: I2Cx STATUS REGISTER
Cała inicjalizacja I2C w wystarczający sposób opisana jest w kodzie.


Rozważmy zatem jak wygląda sekwencja zapisu danych do naszej pamięci EEPROM. Posłużymy się wykresami czasowym z datasheet-u pamięci.




Obraz wart 1000-słów, aczkolwiek dla niektórych może to być równoważne z bohomazem Piccasa. 
Z powyższych obrazków wynika z grubsza, że mamy do dyspozycji dwa tryby zapisu do pamięci. Pierwszy tryb "Byte Write" to po prostu zapis 1 bajtu i zakończenie transmisji, na końcu musi nastąpić zwłoka ok 5 ms przed następną operacją. W trybie "Page Write" zapisać możemy w jednym rzucie jedną stronę czyli maksymalnie 128 bajtów i na końcu ok 5 ms zwłoki. Widzimy , że z punktu widzenia czasu zwłoki niezbędnej do zapisu korzystniejszy jest tryb "Page Write" ale tylko wtedy kiedy mamy do zapisu więcej niż 1 bajt.

Co oznaczają poszczególne sekwencje na obrazku ? Idźmy zatem od lewej strony.
Początek transmisji ma swój początek (Start) i koniec (Stop).Od strony sygnałów sekwencje te są rozpoznawane poprzez wykrycie poniższej sekwencji w sygnale :



Sygnały Start i Stop generowane są odpowiednio opadającym i narastającym zboczem na linii SDA(dane) przy wysokim stanie na linii SCL(zegar). Nie musimy się martwić o rzeźbę takich sygnałów , robi to za nas automatycznie mikrokontroler, wystarczy mu wrzucić do odpowiedniego rejestru odpowiednią wartość. I tak aby wygenerować automatycznie sygnał Start wpisujemy : I2C1CONbits.SEN = 1; co tłumacząc z chińskiego na polski oznacza , że ustawiamy w rejestrze I2C1CON bit SEN.
Odpowiednio aby wygenerować sygnał Stop musimy zapisać I2C1CONbits.PEN=1;

Kocham opisanie rejestrów w PIC strukturami, jest to bardzo przyjazna forma rozmowy z mikrokontrolerem.

Po sekwencji Start mamy "Control Byte" czyli adres naszej pamięci EEPROM plus bit R/W. 
Na obrazku poniżej mamy wycinek pierwszego przesyłanego bajtu  :



Pierwsze cztery najstarsze bity (czyli te od lewej mańki) to stała sekwencja 1010 (taka specyfika naszego EEPROM) nazwana Control Code. Kolejne trzy bity oznaczone A2,A1,A0 (Chip Select Bits) to są bity , które możemy dowolnie zmieniać za pomocą zworek w naszym moduliku. Ja ustawiłem je na 0 czyli muszą być te wejścia połączone z masą wtedy adres naszej pamięci będzie wynosił 0xA0. Reasumując 7 pierwszych bitów to adres naszej pamięci, po którym jest ona rozpoznawalna. Najmłodszy bit tzw R/W sygnalizuje czy urządzenie , które rozpozna swój adres, ma po "Control Byte" wysłać dane (R/W=1), czy też dane odebrać (R/W=0).
Na końcu sekwencji Control Byte pojawia się bit ACK, jest to bit potwierdzający. Po pojawieniu się na linii sygnału ACK. Master czyli w naszym przypadku mikrokontroler zwalnia linię SDA , która przyjmie stan wysoki. Układ Slave czyli nasza pamięć, która rozpozna swój adres wymusi w czasie trwania bitu ACK stan niski i w ten sposób sygnalizuje Masterowi stan gotowości. Gdy Master generuje adres zerowy, wszystkie SLAVE wymuszą stan niski podczas trwania bitu potwierdzającego ACK.
Dopiero po otrzymaniu od SLAVE potwierdzenia, MASTER przystępuje do wysłania (lub pobierania) następnego bajtu.


Ponieważ pamięć EEPROM  jest adresowana na 16 bitach a po I2C przesyłamy tylko bajty dlatego dwa następne bajty w sekwencji z rysunku 6-1 to rozłożenie adresu 16 bitowego na Adress High Byte i Adres Low Byte. Jak rozbić słowo 16 bitowe na dwa słowa 8 bitowe ?
Zrobi to za nas poniższa sekwencja :
(Adres16bit & 0xFF00) >> 8)  - wycinamy starszy bajtu adresu 16 bitowego
Adres16bit & 0x00FF               - wycinamy młodszy bajt adresu 16 bitowego
Używamy tutaj maski bitowej i przesunięcia bitowego. Doczytać sobie o tych zagadnieniach.

Ponieważ bit R/W ustawiony był na 0 (patrz wykres 6-1), dlatego Master i Slave wiedzą , że bajty lub bajt po sekwencji Adress High Byte i Adres Low Byte będzie bajtem, który Slave będzie odbierał od Master-a (przy R/W=1, to Slave by wysyłał dane do Mastera) . Pojawia sią zatem z godnie z rysunkiem 6-1 bajt oznaczony Data. Zauważmy, że po każdym wysłanym bajcie pojawia się sygnał potwierdzający ACK. Sygnał ten jest generowany ustawieniem odpowiednich rejestrów i nie musimy się martwić o to jak on jest realizowany od strony sygnałowej.
Transmisję kończymy sekwencją STOP.

Na rysunku  6-2 zobrazowano zapis w trybie "Page Write" czyli walimy 128 bajtów (rozmiar jednej strony) w jednej transmisji.
Mniej więcej wiemy po obrazkach jak przebiegać powinien zapis do EEPROM teraz wystarczy to ubrać w kod i jazda. Ale moment trzeba zerknąć jeszcze jak wygląda odczyt danych z naszej pamięci.



Widzimy na podstawie rysunku 8-2 , że w odczycie mamy pewne zmiany w strukturze transmisji. Pierwsze trzy bajty są takie same jak w trybie zapisu, czyli mamy Control Byte z flagą R/W ustawioną na 0 (najmłodszy bit),potem dwa bajty adresowe wskazujące adres w pamięci EEPROM. I teraz pojawia się ponownie sekwencja Control Byte poprzedzona sygnałem START (czyli RESTART transmisji, od strony rejestrów jest przewidziany bit do RESTART-u), zauważmy przy tym , że flaga R/W jest ustawiona tym razem na 1 co oznacza tryb odczytu. Po sekwencji RESTART-u i Control Byte przesyłane są dane. Ostatnia Dana w transmisji nie jest potwierdzana sygnałem ACK, występuje tutaj sygnał braku potwierdzenia NO ACK. O postać sygnału NO ACK nie musimy się martwić , rejestry mikrokontrlolera ustawione odpowiednio załatwiają nam ten problem.

Rysunek 8-3 przedstawia nam odczyt sekwencyjny. Z rysunku wynika , że w trybie tym wystarczy tylko raz przesłać adres początku danych w pamięci, kolejne adresy przy odczycie będą automatycznie inkrementowane przez pamięć EEPROM. Jest to bardzo fajny tryb , który umożliwia nam praktycznie odczyt całej pamięci w jednej transmisji.

W sumie mając już jakieś mgliste pojęcie o I2C, spróbujemy to ogarnąć kodem. W materiałach dodatkowych o I2C od Microchipa nie ma przykładów dotyczących ustawienia rejestrów i przygotowania transmisji. Ale jak to się mówi Polak potrafi :) a jak nie potrafi to musi nadgonić sprytem.


Schemat połączeń niezbędny do zabawy z EEPROM :



Na powyższym schemacie widzimy , że linie SCL i SDA o zgrozo współdzielą piny ze sterowaniem LCD. Czy to się nie porypie?? Wszystko jest oki. W programie przed inicjalizacją LCD wyłączamy w rejestrze I2C za pomoca prostego wpisu : I2C1CONbits.I2CEN = 0.
Jeszcze istotna uwaga dotycząca rezystorów podciągających na liniach SCL i SDA, w przypadku wyboru szybkości 400 kHz rezystory należy zmniejszyć do np 2.2 k. W module z pamięcią, który mam rezystory mają po 10 k , przy tej rezystancji poprawnie będzie chodzić tylko 100 kHz.

....... piszemy kod. W międzyczasie zamówiłem u producenta genialne pamięci EERAM.
Nie omieszkam zrobić stosowny wpis na ich temat po przetestowaniu. 

W poniższym kodzie w pliku main.c tworzymy dwa bufory, nadawczy i odbiorczy. Zawartość bufora nadawczego jest zapisywana do pamięci EEPROM, zapis w trybie pojedyńczego bajtu.
W części odznaczonej jako Sekwencja 2, mamy zapis w trybie Page Write.
Po zapisie przechodzimy do odczytu, zawartość pamięci przekazana jest do bufora odbiorczego. Zawartość tego bufora wyświetlamy w pierwszym wierszu LCD. W drugim wierszu wyświetlana jest wartość pomocnicza pokazująca , że wyświetlacz działa poprawnie.
Bardzo ważne aby przed jakąkolwiek wymianą danych z wyświetlaczem wyłączyć I2C aby sygnały nie zakłócały się.

Wyjaśnienia wymaga jeszcze funkcja obsluga_error() z pliku i2c.c. Jest to taka namiastka sygnalizacji problemów z nadawaniem przez Mastera. Wykorzystałem tutaj flagę MI2C1IF rejestru IFS1 , która jest ustawiana po każdej poprawnej operacji I2C wykonanej przez Mastera. Przeczytamy o niej na stronie 18I2C Reference Manual . W przypadku jeśli jakaś operacja nie zostanie zakończona poprawnie i flaga nie zostanie ustawiona to do gry wchodzi Timer1, który zlicza ok 13 ms i po tym czasie zapali się dioda LED sygnalizując optycznie , że Master nawala w transmisji I2C.

Reasumując myślałem , że będę miał problemy z ogarnięciem I2C w PIC w szczególności jak zobaczyłem bogactwo opcji w rejestrach. Ale wilk nie był taki straszny jak wyglądał. W rzeczywistości okazało się, że 90 % ustawień w rejestrach jest ustawiona domyślnie tak jak powinna a te 10 % to był już pryszcz. I2C ujarzmione zatem. Wrócimy tutaj jeszcze jak dotrą do mnie pamięci EERAM i zobaczymy jak to hula. Mam też w planie zagadać z jakimś zegarkiem prawdopodobnie MCP7940N, pomimo ,że mikrokontroler PIC24HJ128GP502 ma na pokładzie swój RTC, który też trzeba odpytać.


Pozdrawiam
picmajster.blog@gmail.com


Linki :
EEPROM 24LC512 Datasheet
I2C Reference Manual
PIC24HJ128GP502 Datasheet
Understanding the I 2C Bus
Protokół I2C


 1 /************************* 
 2    File:   ustaw_zegar.h 
 3  *************************/
 4 
 5 #ifndef USTAW_ZEGAR_H
 6 #define USTAW_ZEGAR_H
 7 
 8 #define FCY 40000000 /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
 9 aby przed includowaniem <libpic30.h>, potrzebne to jest do wyliczania delay-i*/
10 /*deklaracja funkcji*/
11 void ustaw_zegar(void) ;
12 
13 #endif  /* USTAW_ZEGAR_H */
 1 /*********************** 
 2  File:   lcd.h                  
 3  ***********************/
 4 #ifndef LCD_H
 5 #define LCD_H
 6 
 7 /*_TRISB6 --> TRISBbits.TRISB6*/
 8 #define TRIS_RS_LCD   _TRISB11
 9 #define TRIS_EN_LCD   _TRISB10
10 #define TRIS_DB4_LCD  _TRISB9
11 #define TRIS_DB5_LCD  _TRISB8
12 #define TRIS_DB6_LCD  _TRISB7
13 #define TRIS_DB7_LCD  _TRISB6
14 /*_RB6 --> PORTBbits.RB6*/
15 #define RS_LCD   _RB11
16 #define EN_LCD   _RB10
17 #define DB4_LCD  _RB9
18 #define DB5_LCD  _RB8
19 #define DB6_LCD  _RB7
20 #define DB7_LCD  _RB6
21 
22 /* przyporzadkowanie adresow pamieci DD-RAM do pol wyswietlacza*/
23 /*Uwaga dla wyswietlacza 4x20 [0x00,0x40,0x14,0x54*/
24 #define LCD_Line1 0x00 /*adres 1 znaku 1 wiersza */
25 #define LCD_Line2 0x40 /*adres 1 znaku 2 wiersza */
26 #define LCD_Line3 0x10 /*adres 1 znaku 3 wiersza */
27 #define LCD_Line4 0x50 /*adres 1 znaku 4 wiersza */
28 
29  void Wyslij_do_LCD(unsigned char bajt);
30  void CzyscLCD();
31  void WlaczLCD();
32  void WyswietlLCD(char *napis);
33  void UstawKursorLCD(uint8_t y, uint8_t x);
34  void WpiszSwojeZnaki(void);
35 
36 #endif  /* LCD_H */
 1 /*********************** 
 2  File:   i2c.h                  
 3  ***********************/
 4 #ifndef I2C_H
 5 #define I2C_H
 6 extern uint8_t error_flag ;
 7 /*deklaracja funkcji*/
 8 void ustaw_I2C1(void);  /*Init the I2C module*/
 9 void i2c_start(void);   /*generates an I2C Start condition*/
10 void i2c_restart(void); /*generates an I2C Restart condition (for reads)*/
11 void i2c_stop(void);    /*generates an I2C Stop condition*/
12 void i2c_write(unsigned char i2c_data); /*writes a byte to the I2C bus*/
13 unsigned char i2c_read(void);    /*reads a byte from the I2C bus*/
14 void i2c_ack(void);     /*generates a Master Acknowledge*/
15 void i2c_nack(void);    /*generates a Master No Acknowledge*/
16 
17 void obsluga_error(void);
18 void i2c_write_buf( uint8_t adr_device, uint8_t adr, uint8_t len, uint8_t *buf);
19 void i2c_read_buf(uint8_t adr_device, uint8_t adr, uint8_t len, uint8_t *buf);
20 /*dedykowana obsluga EEPROM*/
21 void EEPROM_sequential_read_buf(uint8_t adr_device, uint16_t subAddr, uint16_t len, char *buf);
22 void EEPROM_write_buf(uint8_t adr_device, uint16_t subAddr,  uint16_t len, char *buf);
23 void EEPROM_write_buf_page_write(uint8_t adr_device, uint8_t numer_strony, uint8_t numer_komorki, uint16_t len, char *buf);
24 
25 #endif  /* I2C_H */
 1 /************************* 
 2    File:   ustaw_zegar.c 
 3  *************************/
 4 /*Ustawiamy zegar wewnetrzny na ok 40 MHz (dokladnie na 39.61375 MHz*/
 5 #include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik 
 6 naglówkowy "p24HJ128GP502.h"*/
 7 #include <stdio.h>
 8 #include <stdlib.h>
 9 #include <stdint.h> /*definicje typów do uint8_t itp*/
10 #include "ustaw_zegar.h" /*z uwagi na FCY musi byc przed #include <libpic30.h>*/
11 
12 /*definicja funkcji*/
13 void ustaw_zegar(void) {
14  /*
15   * to co mozemy ustawic za pomoca '#pragma' jest dostepne w pliku 
16   * xc16/docs/config_index.html
17   */
18  #pragma config JTAGEN = OFF
19  // Watchdog timer enable/disable by user software
20  #pragma config FWDTEN = OFF 
21   
22  //********************Start Ustawien Zegara************************
23  /* 
24   * Fcy - zegar instrukcji , Fosc - zegar rdzenia (jest zawsze dwa razy wiekszy 
25   * od zegara instrukcji)) Ustawiamy zegar instrukcji na 40 Mhz z wewnetrznego 
26   * oscylatora Fin=7.37 MHz w/g wzoru Fcy=Fosc/2 gdzie Fosc=Fin x (M/(N1+N2))
27   * gdzie M=43, N2=2, N1=2 ustawiane w rejestrach PLLFBD/PLLPOST/PLLPRE
28   */
29  //Select Internal FRC (Fast RC Oscillator)
30  #pragma config FNOSC = FRC // FOSCSEL-->FNOSC=0b000 (Fast RC Oscillator (FRC))
31  //Enable Clock Switching and Configure
32  #pragma config FCKSM = CSECMD //FOSC-->FCKSM=0b01 - wlacz zegar
33  #pragma config OSCIOFNC = OFF //FOSC-->OSCIOFNC=1 - Fcy b?dzie na pinie OSCO
34  
35      /*Config PLL prescaler, PLL postscaler, PLL divisor*/
36       PLLFBD = 41 ; //M=43 (0 bit to 2 st?d 41 = 43 patrz w rejestrze), tutaj 3.685 x 43 = 158.455MHz
37       CLKDIVbits.PLLPRE=0 ;  //N1=2, tutaj 7.37 MHz / 2 = 3.685 MHz
38       CLKDIVbits.PLLPOST=0 ; //N2=2, tutaj 158.455 MHz / 2 = 79.2275 MHz (Fosc)
39    /*   
40    * UWAGA przerwania musza byc wylaczone podczas wywolywania ponizszych 
41    * dwóch funkcji __builtin_write_...brak definicji w pliku naglówkowym
42    * to wewnetrzne funkcje kompilatora patrz help M-LAB IDE
43    * i datasheet str 140(11.6.3.1 Control Register Lock)
44    */
45   /*Initiate Clock Switch to Internal FRC with PLL (OSCCON-->NOSC = 0b001)*/
46       __builtin_write_OSCCONH(0x01); //tutaj argumentem jest wartosc z NOSC
47   /*Start clock switching*/
48       __builtin_write_OSCCONL(0x01);
49       
50   /*Wait for Clock switch to occur*/
51       while(OSCCONbits.COSC !=0b001);
52       
53       /*Wait for PLL to lock*/
54       while(OSCCONbits.LOCK !=1) {};
55      
56 }
  1 /************************* 
  2    File:   lcd.c         
  3  *************************/
  4 #include "xc.h" /* wykrywa rodzaj procka i includuje odpowiedni plik*/
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <stdint.h> /*definicje typów uint8_t itp*/
  8 #include "../string.h"
  9 #define FCY 40000000UL /* podajemy wartosc ustawionego zegara (40 MHz), wazne 
 10 aby przed includowaniem <libpic30.h>, potrzebne to jest to wyliczania delay-i*/
 11 #include <libpic30.h> /*biblioteka dajaca dostepp do delay-i.*/
 12 #include "lcd.h"
 13 /*deklaracje funkcji*/
 14 void Wyslij_do_LCD(unsigned char bajt);
 15 void CzyscLCD();
 16 void WlaczLCD();
 17 void WyswietlLCD(char *napis);
 18 void UstawKursorLCD(uint8_t y, uint8_t x);
 19 void WpiszSwojeZnaki(void);
 20 /*definicje funkcji*/
 21 void Wyslij_do_LCD(unsigned char bajt)
 22 {
 23         /*ustaw linie EN, przed wysylkadanych*/
 24 
 25     EN_LCD = 1;
 26         /*wyslanie 4 najstarszych bitow danych*/
 27         if(bajt & 0x80) DB7_LCD = 1; else DB7_LCD = 0;
 28         if(bajt & 0x40) DB6_LCD = 1; else DB6_LCD = 0;
 29         if(bajt & 0x20) DB5_LCD = 1; else DB5_LCD = 0;
 30         if(bajt & 0x10) DB4_LCD = 1; else DB4_LCD = 0;
 31         __delay_us(1);
 32         /*potwierdzenie wyslania danych (opadajacym zboczem EN)*/
 33         EN_LCD = 0;
 34                 
 35         /*ustawienie EN*/
 36         __delay_us(1);
 37     EN_LCD = 1;
 38         /*wyslanie 4 najmlodszych bitow danych*/        
 39         if(bajt & 0x08) DB7_LCD = 1; else DB7_LCD = 0;
 40         if(bajt & 0x04) DB6_LCD = 1; else DB6_LCD = 0;
 41         if(bajt & 0x02) DB5_LCD = 1; else DB5_LCD = 0;
 42         if(bajt & 0x01) DB4_LCD = 1; else DB4_LCD = 0;
 43         __delay_us(1);
 44         /*potwierdz wysylke danych opadajacym zboczem EN*/
 45         EN_LCD = 0;
 46 
 47         __delay_us(37);
 48     
 49 }       
 50 
 51 void CzyscLCD()
 52 {
 53         RS_LCD = 0;
 54         Wyslij_do_LCD(1);
 55         RS_LCD = 1;
 56         /*czekaj minimum 1.64 ms*/
 57         __delay_ms(2);
 58 } 
 59 
 60 void WlaczLCD()
 61 {
 62         /*ustawienie kierunku wyjsciowego linii podlaczonych do LCD*/
 63         TRIS_RS_LCD = 0;
 64         TRIS_EN_LCD = 0;
 65         TRIS_DB7_LCD = 0;
 66         TRIS_DB6_LCD = 0;
 67         TRIS_DB5_LCD = 0;
 68         TRIS_DB4_LCD = 0;
 69 
 70         /*zerowanie linii*/
 71         RS_LCD = 0; /*wskazuje na rejestr rozkazow*/
 72         EN_LCD = 0;
 73         DB7_LCD = 0;
 74         DB6_LCD = 0;
 75         DB5_LCD = 0;
 76         DB4_LCD = 0;
 77 
 78     /*Start Inicjalizacji HD44780*/
 79         /*zaczekaj co najmniej 45 ms na ustabilizowanie sie napiecia*/
 80         __delay_ms(45);
 81     
 82     /*powtarzaj 3 x sekwencje startowa… 0011 (hex 0x30 i wpisz ja do rejestru rozkazow */
 83     /*(RS=0 ustawione przy zerowaniu linii)*/
 84    
 85     /*ustaw linie EN, przed wysylka danych*/
 86         EN_LCD = 1;
 87         /*zaladuj sekwencje startowa 0011*/
 88         DB7_LCD = 0;
 89         DB6_LCD = 0;
 90         DB5_LCD = 1;
 91         DB4_LCD = 1;
 92         
 93         __delay_us(1);
 94         /*potwierdz wysylke danych opadajacym zboczem EN*/
 95         EN_LCD = 0;
 96         /*zaczekaj co najmniej minimum 4.1 ms*/
 97         __delay_ms(4.1);
 98    
 99          /*ustaw linie EN, przed wysylka danych*/
100         EN_LCD = 1;
101     /*zaladuj sekwencje startowa 0011*/
102         __delay_us(1);
103         /*potwierdz wysylke danych opadajacym zboczem EN*/
104         EN_LCD = 0;
105         /*zaczekaj 100 us*/
106         __delay_us(100);
107     
108      /*ustaw linie EN, przed wysylka danych*/
109         EN_LCD = 1;
110         /*zaladuj sekwencje startowa 0011*/
111         __delay_us(1);
112         /*potwierdz wysylke danych opadajacym zboczem EN*/
113         EN_LCD = 0;
114         /*zaczekaj 100 us*/
115         __delay_us(100);
116         
117    /* *********************Koniec Inicjalizacji HD44780***************** */
118     
119    /* ***********************Start ustawien HD44780 ******************** */   
120     /*ustaw parametry wyswietlacza, wyslanie slowa operacyjnego do rejestru rozkazow, RS na 0
121      bit 7 = 0 (musi byc 0)
122      bit 6 = 0 (musi byc 0)
123      bit 5 = 1 (musi byc 1)
124      bit 4 = 0 (slowo danych i interfejs ma 4 bity)
125          bit 3 = 1 (2 wiersze znakow)
126          bit 2 = 0 (matryca 5x8 pikseli)
127      bit 1 = 0 (bez znaczenia)
128          bit 0 = 0 (bez znaczenia) */
129     /*Uwaga po kazdym uzyciu RS = 0 (wybierz rejestr rozkazow), ustawiamy RS = 1*/
130     /*(rejestr danych)*/
131     
132     RS_LCD = 0; /*stan niski na lini RS, wybieramy rejestr instrukcji*/
133         Wyslij_do_LCD(0b00101000);//wysylamy instrukcję do rejestru rozkazow
134     RS_LCD = 1; /*przelacz na rejestr danych */    
135         
136     /*wlacz wyswietlacz, wyslanie slowa operacyjnego, RS na 0
137      bit 7 = 0 (musi byc 0)
138      bit 6 = 0 (musi byc 0)
139      bit 5 = 0 (musi byc 0)
140      bit 4 = 0 (musi byc 0)
141          bit 3 = 1 (musi byc 1)
142          bit 2 = 1 (wyswietlacz wlaczony)
143      bit 1 = 0 (kursor wylaczony)
144          bit 0 = 0 (migotanie kursora wylaczone) */
145         
146     RS_LCD = 0; /*stan niski na lini RS, wybieramy rejestr instrukcji*/
147     Wyslij_do_LCD(0b00001100);//wysylamy instrukcje do rejestru rozkazow
148         RS_LCD = 1;      /*przelacz na rejestr danych */ 
149     
150     CzyscLCD();
151        
152     /*ustaw tryb pracy wyswietlacza
153          bit 7 = 0 (musi byc 0)
154      bit 6 = 0 (musi byc 0)
155      bit 5 = 0 (musi byc 0)
156      bit 4 = 0 (musi byc 0)
157          bit 3 = 0 (musi byc 0)
158          bit 2 = 1 (musi byc 1)
159      bit 1 = 1 (inkremetacja)
160          bit 0 = 0 (wpis znaku od lewej) */
161     
162     RS_LCD = 0; /*stan niski na lini RS, wybieramy rejestr instrukcji*/
163         Wyslij_do_LCD(0b00000110);/*wysylamy instrukcje do rejestru rozkazow*/
164         RS_LCD = 1;  /*przelacz na rejestr danych */    
165        
166  /*Koniec inicjalizacji i ustawien wyswietlacza HD44780*/      
167 }
168 
169 void WyswietlLCD(char *napis)
170 {
171     while(*napis){
172     Wyslij_do_LCD(*napis++);
173     }
174          
175 }
176 
177 void UstawKursorLCD(uint8_t y, uint8_t x)
178 {
179         uint8_t n ;
180     /*y (wiersze) = 1 do 4*/
181     /*x (kolumna) = 1 do 16*/
182     /*ustal adres poczatku znaku w wierszu*/
183         switch(y)
184     {
185         case 1: y = LCD_Line1 ;break;
186         case 2: y = LCD_Line2 ;break;
187         case 3: y = LCD_Line3 ;break;
188         case 4: y = LCD_Line4 ;break;
189     
190     }
191     /*ustal nowy adres pamieci DD RAM*/
192         /*ustaw bajt do Set DDRAM adres*/
193     /* x odejmujemy jeden aby przekonwertowac z 0-15 na 1-16 */
194         n = 0b10000000 + y + (x-1) ;
195         
196         /*wyslij rozkaz ustawienia nowego adresu DD RAM*/
197         RS_LCD = 0; /*stan niski na liniÄ™ RS, wybieramy rejestr instrukcji*/
198         Wyslij_do_LCD(n);
199     RS_LCD = 1;  /*przelacz na rejestr danych */ 
200 }
201 
202 void WpiszSwojeZnaki(void) {
203     /*definicja wlasnych znakow maks 8 szt*/
204     char znak1[]= {0,0,14,17,31,16,14,2}; /* definicja literki e z ogonkiem */
205     int i; 
206     /* adresy poczatku definicji znaku to wielokrotnosc osmiu DEC(0,8,16,24,32,40,48,56)
207      * ale uwaga wlasne ! adresy kodowane sana 6 mlodszych bitach dwa najstarsze bity
208      * to zawsze  01 (01AAAAAA-gdzie A adres).Uwzgledniajac wartosc calego bajtu
209      * adresy poczatku bajtu wygladaja tak HEX(0x40,0x48,0x50,0x58,0x60,0x68,0x70,0x78)
210      * Aby wpisac do pamieci wyswietlacza zdefiniowany znak nalezy najpierw wyslac 
211      * do rejestru rozkazow (RS na 0) adres poczatku definicji znaku 
212      * a w drugim kroku przesylamy dane (RS=1) 8 x bajt (tablica) definjujace obraz znaku*/
213     
214     RS_LCD = 0 ;/*stan niski na linich RS, wybieramy rejestr instrukcji*/
215      /*wysylamy instrukcje do rejestru rozkazow (ustaw adres poczatkowy w CGRAM 
216       na nasz znak w tym przypadku znak na pozycji drugiej) */
217     Wyslij_do_LCD(0x48);/*wysylamy instrukcje do rejestru rozkazow 
218      (ustaw adres poczatkowy w CGRAM na nasz znak w tym przypadku znak na pozycji drugiej) */
219     
220     RS_LCD = 1 ;/*stan wysoki na lini RS, wybieramy rejestr danych*/
221     /*wysylamy 8 x bajt zdefiniowanego w tablicy znak1[] znaku*/
222     for(i=0;i<=7;i++)
223     {
224        Wyslij_do_LCD(znak1[i]);
225     }
226    
227     RS_LCD = 0 ;/*stan niski na lini RS, wybieramy rejestr instrukcji*/
228     /*ustawiamy adres DDRAM na pierwszy znak w pierwszej linii, nie zapomnijmy
229      o tym poniewaz inaczej zostaniemy w pamieci CGRAM*/
230     Wyslij_do_LCD(0x80);
231 }
  1 /************************ 
  2    File: i2c.c           
  3  ************************/
  4 #include "xc.h"
  5 #include <stdio.h>
  6 #include <stdlib.h>
  7 #include <stdint.h> /*definicje typów uint8_t itp*/
  8 #include <string.h>
  9 #include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
 10 #include <libpic30.h> /*biblioteka dajaca dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/
 11 #include "i2c.h"
 12 
 13 uint8_t error_flag ;
 14 /*definicje funkcji*/
 15 void ustaw_I2C1(void){
 16     /*pin 17(SCL) i 18(SDA) sa cyfrowe na starcie, nie trzeba tutaj wylaczac wejsc analogowych*/
 17     I2C1BRG = 391 ; /*przy FCY ok 40 MHz dla bitrate 100 kHz I2C1BRG = 391 dla 400 kHz I2C1BRG=91*/
 18     I2C1STAT = 0x0000; /*Status Register - pasuje wszystko na zero */
 19     I2C1CON = 0x1200; /*Control Register - Relase SCL clock / Slew rate control disabled / 7-bit slave address*/ 
 20     /*zerujemy rejestr nadawczy i odbiorczy*/
 21     I2C1RCV = 0;
 22     I2C1TRN = 0;
 23     /*Enable I2C1 module*/
 24     I2C1CONbits.I2CEN = 1;
 25     
 26 }
 27 
 28 void obsluga_error(void){
 29     /*Timer1 zlicza przez ok 13 ms przy FCY 40MHz*/
 30     TMR1 =0; /*clear Timer1*/
 31     IFS0bits.T1IF=0 ; /*zeruj flage od przepelnienia Timer1*/
 32     T1CONbits.TCKPS = 0b01 ; /*Prescaler 1:8 , daje nam 5 MHz*/
 33     T1CONbits.TON=1;    /*start Timer1*/
 34     /*czekaj na flage MI2C1IF po zakonczeniu poprawnie dowolnej operacji przez Mastera na I2c
 35       lub na przepelnienie Timer1 czyli ok 13ms*/ 
 36     while(!(IFS1bits.MI2C1IF | IFS0bits.T1IF ));  
 37     if(IFS0bits.T1IF){ /*jesli Timer1 przepelniony*/
 38         IFS0bits.T1IF=0 ; /*zeruj flage od przepelnienia Timer1*/
 39         IFS1bits.MI2C1IF=0; /*clear the I2C general flag*/
 40         error_flag = 1; /*set the error flag*/
 41         PORTAbits.RA1 = 1 ; /*LED ON*/
 42         /*tutaj kod uzytkownika do obslugi bledu, np zapalenie diody LED etc*/
 43     }
 44     else {error_flag = 0; /*clear the error flag*/
 45         IFS1bits.MI2C1IF=0; /*clear the I2C general flag*/
 46         PORTAbits.RA1 = 0; /*LED OFF*/
 47     }
 48     T1CONbits.TON=0; /*stop Timer1*/
 49     TMR1 =0; /*clear Timer1*/
 50 }
 51 
 52 
 53 void i2c_start(void){
 54    while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
 55    I2C1CONbits.SEN = 1;        /*generuj sygnal start*/
 56    obsluga_error();            
 57 }
 58 
 59 void i2c_restart(void){
 60     while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
 61     I2C1CONbits.RSEN=1; /*generuj restart*/
 62     obsluga_error();  
 63 }
 64 
 65 void i2c_stop(void){
 66     while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
 67     I2C1CONbits.PEN=1; /*generuj stop*/
 68     obsluga_error();  
 69 }
 70 
 71 void i2c_write(unsigned char i2c_data){
 72     while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
 73     I2C1TRN=i2c_data; /*load byte in the transmiter buffer*/
 74     obsluga_error();
 75 }
 76 
 77 unsigned char i2c_read(void){
 78     while(I2C1STATbits.TRSTAT); /*czekaj az linia bedzie wolna */
 79     I2C1CONbits.RCEN=1; /*enable Master receive*/
 80     obsluga_error();
 81     return(I2C1RCV); /*return data in buffer*/
 82 }
 83 
 84 void i2c_ack(void){
 85     I2C1CONbits.ACKDT=0; /*clear the related flag for ACK*/
 86     I2C1CONbits.ACKEN=1; /*start ACK sequence*/
 87     obsluga_error();
 88 }
 89 
 90 void i2c_nack(void){
 91     I2C1CONbits.ACKDT=1; /*set the related flag for NotACK*/
 92     I2C1CONbits.ACKEN=1; /*start ACK sequence*/
 93     obsluga_error();
 94     I2C1CONbits.ACKDT=0; /*clear the related flag for ACK*/
 95 }
 96 
 97 void i2c_write_buf( uint8_t adr_device, uint8_t adr, uint8_t len, uint8_t *buf ) {
 98         i2c_start();
 99         i2c_write(adr_device); /*wyslanie adresu urzadzenia z bitem R/W w stanie niskim*/
100         i2c_write(adr);
101         while (len--) i2c_write(*buf++);
102         i2c_stop();
103 }
104 
105 void i2c_read_buf(uint8_t adr_device, uint8_t adr, uint8_t len, uint8_t *buf) {
106 
107         i2c_start();
108         i2c_write(adr_device);/*wys?anie adresu urzadzenia z bitem R/W w stanie niskim*/
109         i2c_write(adr);
110         i2c_start();
111         i2c_write(adr_device + 1);/*zapisuje adres urzadzenia z bitem R/W ustawionym na 1 czyli o 1 zwiekszamy adres urzadzenia*/
112         while (len--){
113      if(len) {
114      *buf++ = i2c_read();
115      i2c_ack();
116         }
117      else {
118      *buf++ = i2c_read();
119      i2c_nack();
120          }
121      }
122      
123         i2c_stop();
124 }
125 /*Funkcje dedykowane do obslugi pamieci EEPROM PIC 24LCxxx*/
126 /*odczyt sekwencyjny danych z pamieci EEPROM.*/
127 void EEPROM_sequential_read_buf(uint8_t adr_device, uint16_t subAddr, uint16_t len, char *buf) {
128 
129         i2c_start();
130                 i2c_write(adr_device); /*wyslanie adresu urzadzenia z bitem R/W w stanie niskim*/
131         i2c_write((subAddr & 0xFF00) >> 8) ; /*wyslanie starszego bajtu adresu pamieci*/
132                 i2c_write(subAddr & 0x00FF)  ;       /*wyslanie mlodszego bajtu adresu pamieci*/
133         i2c_start();
134                 i2c_write(adr_device + 1);/*zapisuje adres urzadzenia z bitem R/W ustawionym na 1 czyli o 1 zwiekszamy adres urzadzenia*/
135                 i2c_ack();
136         while (len--)
137         {
138         *buf++ = i2c_read();
139         i2c_ack();
140         }
141         i2c_nack();
142         i2c_stop();
143 }
144 
145 /*zapis danych do pamieci EEPROM w pojedynczych bajtach*/
146 void EEPROM_write_buf(uint8_t adr_device, uint16_t subAddr,  uint16_t len, char *buf) {
147 
148         while (len--) {
149                 i2c_start();
150                 i2c_write(adr_device); /*wyslanie bajtu adresu z bitem R/W ustawionym na zero*/
151                 i2c_write((subAddr & 0xFF00) >> 8);  /*wyslanie starszego bajtu adresu pamieci*/
152                 i2c_write(subAddr & 0x00FF);             /*wyslanie mlodszego bajtu adresu pamieci*/
153                 i2c_write(*buf++);
154                 i2c_stop();
155                 __delay_ms(5); /*oczekiwanie na zapis*/
156                 subAddr++;
157         }
158 }
159 
160 /*zapis danych do pamieci EEPROM w trybie PAGE WRITE czyli maksymalnie 128 bajty w obrembie jednej strony*/
161 void EEPROM_write_buf_page_write(uint8_t adr_device, uint8_t numer_strony, uint8_t numer_komorki, uint16_t len, char *buf) {
162       uint16_t subAddr;
163       /*subAddr sklada sie z :
164         numer komorki to numer w obrebie jednej strony czyli od 0 do 127 
165         numer strony to numer jednej z 512 stron od 0 do 511
166         24LC512 ma 512 stron 128 bajtowych co lacznie daje 65536 bajtow*/
167                 i2c_start();
168         i2c_write(adr_device); /*wyslanie bajtu adresu Slave z bitem R/W ustawionym na zero*/
169         /*wyliczamy adres poczatku uwzgledniajac numer strony 0-511 i numer komorki na stronie 0-127*/
170                 subAddr = (numer_strony*128) + numer_komorki;
171         
172         i2c_write((subAddr & 0xFF00) >> 8);  /*wyslanie starszego bajtu adresu pamieci*/
173                 i2c_write(subAddr & 0x00FF);             /*wyslanie mlodszego bajtu adresu pamieci*/
174         
175         while (len--) {
176                 /*inkrementujemy tylko tablice z danymi,adres w obrebie strony 0-127 bajtow jest inkrementowany automatycznie przez eeprom*/
177             i2c_write(*buf++); 
178             }
179                 i2c_stop();
180                 __delay_ms(5); /*oczekiwanie na zapis*/
181                 }
 1 /************************ 
 2    File: main.c          
 3  ************************/
 4 /* 
 5  * Created on 15 marca 2017, 15:47
 6  */
 7 
 8 #include "xc.h"
 9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <stdint.h> /*definicje typów uint8_t itp*/
12 #include <string.h>
13 #include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
14 #include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/
15 #include "i2c.h"
16 #include "lcd.h"
17 
18 #define AdressEEPROM 0xA0 /*A0,A1,A2 - do masy*/
19 
20 int main(void) {
21     char bufor_nadawczy[]="Test EEPROM";
22     char bufor_odbiorczy[sizeof(bufor_nadawczy)];
23     ustaw_zegar();/*odpalamy zegar na ok 40MHz*/
24     ustaw_I2C1(); /*odpalamy I2C1*/
25     /* ustawiamy wszystkie piny analogowe (oznacznone ANx) jako cyfrowe
26        do zmiany mamy piny AN0-AN5 i AN9-AN12 co daje hex na 16 bitach = 0x1E3F*/
27     AD1PCFGL = 0x1E3F ;
28     TRISAbits.TRISA1 = 0 ; /*RA1 jako wyjscie, tu mamy podpieta LED*/
29     /*Sekwencja nr 1 - zapis w trybie Byte Write - po jednym bajcie*/
30     EEPROM_write_buf(AdressEEPROM,0,sizeof(bufor_nadawczy),bufor_nadawczy);
31     /*odczyt sekwencyjny*/
32     EEPROM_sequential_read_buf(AdressEEPROM,0,sizeof(bufor_nadawczy),bufor_odbiorczy);
33     
34     /*Sekwencja nr 2 - zapis w trybie Page Write - maksymalnie 128 bajtów*/
35     //EEPROM_write_buf_page_write(AdressEEPROM, 1, 0,sizeof(bufor_nadawczy),bufor_nadawczy);
36     /*odczyt sekwencyjny*/
37     //EEPROM_sequential_read_buf(AdressEEPROM,128,sizeof(bufor_nadawczy),bufor_odbiorczy);
38     
39     /*Disable I2C1 module*/
40     I2C1CONbits.I2CEN = 0;
41     
42     WlaczLCD();
43     UstawKursorLCD(1,1);
44     WyswietlLCD(bufor_odbiorczy);
45     /*napis testowy aby sprawdzic poprawnosc dzialania LCD*/
46     UstawKursorLCD(2,1);
47     WyswietlLCD("test");
48     while(1)
49       {
50         /*Glówna Petla Programu*/
51       }
52 }



Linki :
EEPROM 24LC512 Datasheet
I2C Reference Manual
PIC24HJ128GP502 Datasheet
Understanding the I 2C Bus
Protokół I2C

4 komentarze:

  1. Piszesz Autorze: "#include /*dyrektywy do uint8_t itp*/"
    Plik nagłówkowy zawiera definicje typów, a nie dyrektywy, które są na ogół poleceniami dla kompilatora/preprocesora.

    OdpowiedzUsuń
  2. Dziękuję zwrócę uwagę na ten błąd.

    OdpowiedzUsuń
  3. Witam,
    Chciałbym zapytać czy jest możliwe zapisanie 128Bajtów całej strony zaczynając od np środka strony. Wydaje mi się ,że to nie możliwe.
    Pozdrawiam PIC majstrów ;)

    OdpowiedzUsuń
  4. Witaj
    Na stronie możesz zapisać maksymalnie 128 bajtów, jak zaczniesz od środka to masz tylko do dyspozycji połowę tej wartości na tej stronie, zmieniając stronę w zapisie musisz odczekać "wieczność" .Polecam zapoznać się z EERAM Microchipa tu nie musisz się martwić o stronicowanie, możesz w jednym rzucie zapisać cały obszar dostępnej pamięci.
    Pomagaj Żonie przed świętami.
    Życzę wszystkiego dobrego :)
    PICmajster

    OdpowiedzUsuń