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
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 18 w I2C 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 wysylka danych*/ 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 sa na 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
Piszesz Autorze: "#include /*dyrektywy do uint8_t itp*/"
OdpowiedzUsuńPlik nagłówkowy zawiera definicje typów, a nie dyrektywy, które są na ogół poleceniami dla kompilatora/preprocesora.
Dziękuję zwrócę uwagę na ten błąd.
OdpowiedzUsuńWitam,
OdpowiedzUsuń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 ;)
Witaj
OdpowiedzUsuń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