Autor: Michał Smalera
Redakcja: Dondu
Ponieważ udało mi się zdobyć a następnie "dogadać" z jednym z akcelerometrów, więc spróbuję nieco swoich sił w komponowaniu artykułu i podzielę się swoimi doświadczeniami. Kiedy uda mi się zdobyć jakiś żyroskop również podzielę się wiedzą.
Wstęp
Opisywany akcelerometr to LIS302DL znaleziony w popularnym niegdyś telefonie Nokia N95. Niedawno wpadła mi w ręce sfatygowana NOKIA E52 (podkuty koń się po niej "przeszedł"), tam również jest takowy komponent. Jest więc szansa, że ów sprytny LISek znajduje się w innych modelach, ale to już musicie sprawdzić sami.
Odlutowałem go za pomocą hot-guna i odpowiedniej ilości topnika w żelu (szybciej się rozgrzewa, ponadto polecam zastosować jakiś pre-heater), można spróbować za pomocą kropli cyny i standardowej lutownicy, jednak jest to metoda wysoce ryzykowna!
Jest to MEMS (MicroElectroMechanical System), akcelerometr lub sensor ruchu. Oferuje możliwości detekcji przyśpieszeń do ±2g/±8g (przypominam, że 1g to przyśpieszenie ziemskie, które notabene zostanie wykorzystane do późniejszej kalibracji naszego akcelerometru).
Ponieważ jest to akcelerometr cyfrowy, więc do komunikacji ze światem zewnętrznym wykorzystuje szynę danych I2C (w mikrokontrolerach AVR, jest to TWI) lub SPI. W internecie znalazłem opisy komunikacji z tym akcelerometrem wykorzystujące szynę SPI, ale ja zaparłem się i skoncentrowałem się na I2C. Jeśli jednak ktoś desperacko będzie potrzebował komunikacji na SPI, to postaram się i napiszę coś w tym temacie.
Umiejscowienie komponentu na płycie telefonu Nokia N95:
Akcelerometr LIS302DL na płycie telefonu Nokia N95. |
Akcelerometr LIS302DL - obudowa. |
Obudowa to LGA-14 (rozmiary 3x5x0.9mm), montaż powierzchniowy, więc do prototypów nie jest to najlepszy wybór. Z drugiej strony większość (o ile nie wszystkie) tego typu elementy elektroniczne są wykonane w podobnych obudowach.
Ja się zawziąłem, a płytki robić mi się nie chciało, więc oto moje rozwiązanie: położyłem komponent na płytce uniwersalnej frontem do spodu na kropli kleju, tak żeby wyeksponować piny, przylutowałem cienkie druty i połączyłem z okolicznymi polami lutowniczymi.
Akcelerometr LIS302DL na płytce. |
Teraz można obudować komponent "niezbędnikami". A jest ich trochę ze względu na fakt, że nasz akcelerometr akceptuje napięcia w zakresie od 2.16V do 3.6V. Tabela Absolute maximum mówi o zasilaniu max 6V, dla bezpieczeństwa trzymam się jednak napięcia 3,3V.
Konwerter poziomów
Najłatwiejszy i zarazem najbardziej uniwersalny wydaje się być wybór 3.3V. Niestety jeżeli mikroprocesor zasilany jest napięciem 5V (a ja tak właśnie mam) potrzebny będzie translator 3,3V na 5V (level shifter lub level converter). Ja zastosowałem taki:
Level shifter (konwerter poziomów). |
Regulator napięcia 3,3V
Ponieważ część mikroprocesorowa której używam zasilana jest z napięcia 5V na prototypowej płytce z akcelerometrem wlutowany jest liniowy regulator 3.3V typu AMS1117-3.3 oraz kondensatory: filtrujący i odsprzęgający.
Schemat połączeń
Z akcelerometru syganły SDA oraz SCL są podłączone do ATtiny2313 odpowiednio do SDA i SCL (bez krzyżowania) poprzez wyżej opisany konwerter poziomów. Do mikrokontrolera podłączyłem jeszcze wyświetlacz LCD 2x16 (HD44780). Opis podłączenia pinów LCD znajdziesz w pliku lcd.c.
Komunikacja z LIS302DL
Jak już wspomniałem do komunikacji z mikroprocesorem wykorzystałem szynę I2C (TWI). Zasad komunikacji nie będę opisywał, to jest temat na osobny artykuł. Niemniej obsługa szyny jest zaimplementowana w listingu jako osobna biblioteka, podobnie zresztą jak obsługa LCD.
Zajmijmy się sposobem na komunikację. LIS302DL posiada serię rejestrów dzięki którym można "powiedzieć mu" jak ma się zachowywać, lub "zapytać go" o wybrane dane.
Na przykład, każdy LIS ma swoje "imię" zakodowane pod adresem 0F (szesnastkowo), a brzmi ono 3B (również szesnastkowo). Żeby nie pisać szesnastkowo za każdym razem umówmy się, że będę pisał 0Fh oraz 3Bh.
Takie zapytanie "o imię" zrealizujemy następująco: na szynę I2C podajemy kolejno:
- rozkaz zapisu,
- adres (adres naszego LIS-a)
- a na końcu subadres (tutaj: 0Fh).
Następnie "słuchamy" co LIS ma do powiedzenia, a w odpowiedzi powinniśmy otrzymać 3Bh. Wyświetlamy to sobie na takim LCD i gotowe, pierwsze koty za płoty!
Jednak powstają pytania:
Jaki jest adres LISa i w jaki sposób zaznaczyć, że "chcemy coś do niego powiedzieć"?
I jak zabrać się do komunikacji w ogóle?
Zanim do tego przystąpimy musimy umówić się w kilku kwestiach:
- Mikroprocesor, to Master (Pan, albo Szef) , LIS to Slave (Sługa) - tak dla uzmysłowienia - Pan mówi, Sługa słucha.
- Jeśli mówimy o "mówieniu" do LISa to znaczy, że zapisujemy do niego (inaczej "Write"), jeśli zaś "słuchamy" co LIS ma do powiedzenia, to odczytujemy z niego (inaczej "Read")
Na razie to tyle ustaleń.
Adres LIS-a to (wbudowana na stałe część - pięć najbardziej znaczących bitów) 001110xyb oraz x. Literka b na końcu oznacza oczywiście kod binarny. Ten właśnie x oznacza stan dowolny - konfigurowalny. Co to znaczy? Ano, że za pomocą pinu o nazwie SDO (pin numer 12 w naszym akcelerometrze) możemy zdecydować, czy x będzie zerem, czy jedynką. Wystarczy pin ten dołączyć do masy lub do zasilania. Jak do masy, to x będzie 0, jeśli do zasilania to x będzie 1. Wszystko po to, aby można było na jednej szynie umieścić dwa takie akcelerometry i "dogadać" się z każdym z nich z osobna.
W przypadku podłączenia pinu SDO do zasilania (ja zrobiłem to za pomocą rezystora 4,7kΩ dla bezpieczeństwa) pierwsze siedem bitów adresu będą następujące: 0011101yb. Ostatni, ósmy bit (literka y), to bit, w którym Master "zaznacza", czy chce "mówić" (Write), czy "słuchać" (Read).
Czyli: Pin SDO do zasilania i wtedy: adres 00111010b to Write, 00111011b to Read.
Pierwsze kroki
Zacznijmy komunikację z LIS-em. Aby to zrobić trzeba naszego bohatera wyprowadzić ze stanu uśpienia w którym znajduje się domyślnie.
Aby to zrobić należy zmienić ustawienie jednego bitu (PD) w rejestrze kontrolnym CTRL_REG1. Adres zaczyna się w 20h. Rejestr wygląda następująco:
Akcelerometr LIS302DL - Rejestr kontrolny CTRL_REG1. |
Żeby obudzić akcelerometr wystarczy nam zmiana jednej pozycji w rejestrze kontrolnym LISa. Robimy to ustalając kolejne stany (w nawiasach pogrubione skróty używane w dokumentacji):
Poniższe objaśnienia dokładniej wyjaśnione są w dokumentacji samego akcelerometru, ponadto cała "głębsza" komunikacja zrealizowana będzie za pomocą gotowych bibliotek, poniższe wiadomości mogą okazać się przydatne w momencie debuggowania, w moim przypadku początkowe problemy z komunikacją wymagały dokładniejszego zapoznania się z poniższym.
Szkoda, żebyś wyważał otwarte drzwi, więc podzielę się informacjami:
1. (ST) STart (zmiana SDA ze stanu wysokiego do niskiego podczas gdy SCL jest trzymany w stanie wysokim)
2. (SAD+W) SlaveADdress + W --> adres naszego LIS-a plus bit zapisu, czyli 00111010
3. (SAK) czekamy na sygnał ACK od LIS-a --> (Master zwalnia linię SDA i obserwuje jej stan. Slave komunikuje że odebrał dane przez "ściągnięcie" linii SDA do zera na jeden cykl zegara na linii SCL)
4. (SUB) SUBaddress --> adres pod który dokonamy zapisu danych, czyli 20h (inaczej 0010 0000)
5. (SAK) ponownie czekamy na ACK od LISa
6. (DATA) DATA --> tutaj nadajemy 01000111 do LISa. Tak naprawdę nie chcemy zmieniać nic więcej niż bit PD (patrz rys powyżej)
7. (SAK) czekamy na ACK od LIS-a
8. (SP) StoP (zmiana stanu z niskiego do wysokiego na linii SDA podczas gdy SCL jest w stanie wysokim)
W dokumentacji wygląda to tak:
Po wyprowadzeniu akcelerometru ze stanu uspienia odczytamy jego "imię" (rejestr nazywa się Who_I_Am, a zaczyna się pod 0Fh). Tym razem oprócz wysłania danych do Slave-a odbędzie się również odczyt danych ze Slave-a. Proces wygląda tak:
- (ST) STart - wiadomo
- (SAD) SlaveADdress + W - jw, czyli znowu 00111010
- (SAK) Slave ACK - LIS potwierdza odebranie danych
- (SUB) SUBaddress - teraz chcemy danych spod 0x0F, więc 00001111
- (SAK) ACK od LIS-a - jak poprzednio
- (SR) Powtórne wysłanie sygnału Start (Start Repeated)
- (SAD+R) wysłanie adresu Slave-a plus bit Read -tym razem 00111000
- (SAK) - ACK od LIS-a
- (DATA) - tym razem nasz Master odbiera dane, a LIS nadaje (mała zamiana ról)
- (NMAK) - No Master AcKnowledge (pokemon mi wyszedł, ale chciałem zaznaczyć tylko które literki są odpowiedzialne za pogrubiony skrót). Tym razem coś odmiennego: Po wysłaniu paczuszki danych LIS oczekuje na ACK od Mastera, gdy dostanie taki sygnał, to wyśle następną paczuszkę danych, która będzie zawierała dane z następnego rejestru LISa. W naszym przypadku chcemy otrzymać zawartość tylko jednego rejestru, więc zamiast MAK wysyłamy NMAK (czyli, efektywnie nie wysyłamy ACK)
- (SP) - STOP, czyli Master zakańcza transmisję
I znowu datasheet:
Akcelerometr LIS302DL - Odczyt jednego bajtu. |
Tak wygląda komunikacja pomiędzy mikrokontrolerem a akcelerometrem. W praktyce zajmie się tym biblioteka, a w kodzie zapiszemy tylko adresy do których chcemy sie "dobrać". W przypadku akcelerometru interesują nas OutX, OutY oraz OutZ. W tych trzech rejestrach - jak łatwo się domyśleć - zawarte są chwilowe wartości przyśpieszenia w poszczególnych osiach.
W przypadku, gdy akcelerometr leży sobie grzecznie i płasko na swoich padach i nie jest poruszany, przyśpieszenie działa w kierunku osi Z. Jest to przyśpieszenie ziemskie. Skorzystamy z niego, aby sprawdzić, czy nasz akcelerometr działa. Kiedy będziemy obracać naszego LIS-a w różne strony, zaobserwujemy wzrost lub spadek wartości przyśpieszenia w różnych osiach.
Aby zrealizować odczyt przyspieszenia i nie zapychać procesora tylko tym jednym zadaniem, zrealizowałem to w postaci następującej. Przerwanie od timera ustawia flagę, ustawiona flaga daje zielone światło dla odczytu wartości z rejestrów "trzymających" wartości przyśpieszenia dla poszczególnych osi.
Wartości są następnie nieco obrabiane - tutaj następuje pokazanie znaku minus w przypadku przyśpieszenia mającego przeciwny zwrot, oraz przeliczenie na wartości dziesiętne. Wartości przekazywane przez LIS-a są podawane w kodzie uzupełnień do dwóch, dlatego potrzebna jest mała konwersja.
Tak naprawdę powinienem również dodać małe przeliczenie, żeby wartość przyciągania ziemskiego była równa 1g, a inne osie zostały przeliczone proporcjonalnie, ale myślę, że dla nikogo chcącego takie przeliczenie i dodanie kilku linijek kodu nie powinno stanowić problemu. Również w przypadku implementacji dla innych procesorów, zwłaszcza tych z wbudowanym interfejsem I2C (TWI) nie powinno być większych trudności.
Pliki, kody źródłowe
Do pobrania: Komplet poniższych plików + hex (kopia)
main.c
/* * main.c w projekcie ATtiny2313+Accel+LCD * * Created on: 2013-09-05 * Author: Michal Smalera * Processor: ATtiny2313 @ 8MHz * Periphery: LCD + LIS302DL * Podłączenia opisane poniżej */ #include <avr/io.h> #include <stdlib.h> //fcja itoa() #include <avr/interrupt.h> #include "lcd.h" //Sprawdź w lcd.c jak podłączony jest LCD do ATTiny #include "i2c.h" //sprawdź w i2c.c które porty użyte są jako SDA i SCL #define TIMER_INIT 0 #define LIS302_Addr 0b00111010//0x3A //"podstawowy" adres LISa #define WhoAmI 0x0F //pod tym adresem siedzi wartość 0011 1011 (0x3B) (0d59) #define CtrlReg 0x20 //tu ustawić trzeba bit PD na jedynkę //(PowerDown - default=0) // DR PD FS STP STM Zen Yen Xen #define OutX 0x29 //adres przyspieszenia w osi X #define OutY 0x2B //adres przyspieszenia w osi Y #define OutZ 0x2D //adres przyspieszenia w osi Z volatile uint8_t timer0_flag= 0; // flaga zmieniana w przerwaniu i sprawdzana w pętli głównej uint8_t count=0; //---------------obsługa TIMER0 ISR(TIMER0_OVF_vect) { //TCNT0=TIMER_INIT; //zaladowanie do timera wart. pocz. czyli zera count++; if(count==0x0B) { timer0_flag=1; count=0; } } //-----MAIN------- int main(void) { DDRD=0x00; DDRB = 0xFF; PORTD=0x00; PORTB=0x00; //TIMER TCCR0B=0x05; //tryb pracy licznika (0000 0100 -0x04- to preskaler 256, a //0000 1000 -0x05- to 1024) TCNT0=TIMER_INIT; // wpisanie wartosci poczatkowej TIMSK=0x02; //odblokowanie przerwania lokalnego licznika TC0 sei(); //globalne odblokowanie przerwan //-------------------- char x,y,z; uint8_t bufor[1]; // rezerwacja bufora 1 bajt uint8_t bufor2[1]; lcd_init(); LCDxy(8,0); write_text("X=");//za tym napisem (0,10) wymagane: pozycja na znak+/- potem //3 pozycje + spacja - razem:5 LCDxy(0,1); write_text("Y=");//jw(1,3) LCDxy(8,1); write_text("Z=");//jw(1,10) //jak wygląda na LCD // 0|5|9| | | | |X|=| -| 0| 0| 0| // 1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16| // Y|=|-|0|0|0| |Z|=|- | 0| 0| 0| bufor[0]=0x40; bufor2[0]=0x00; I2C_write_buf(LIS302_Addr,CtrlReg,1,bufor); //power mode ON I2C_read_buf(LIS302_Addr,WhoAmI,1,bufor2); //przedstaw się ładnie LCDxy(0,0); x=*bufor2; lcd_int(x);//odpowiedź na 'kim jesteś' na LCD w wartościach decymalnych while(1) { if(timer0_flag) { //teraz dokonujemy zapytania o adresy z wartościami X, Y, Z i będziemy //je wyświetlać na LCD I2C_read_buf(LIS302_Addr,OutX,1, bufor);//odczyt z LISa spod OutX //jednego bajtu i zapisanie wartości pod adres wskazywany //wskaźnikiem bufor x=*bufor; //wrzucenie do x wartości bufora I2C_read_buf(LIS302_Addr,OutY,1,bufor);//odczyt wartości Y y=*bufor; I2C_read_buf(LIS302_Addr,OutZ,1,bufor);//Z z=*bufor; //--- X --- LCDxy(10,0); write_text(" ");//wyczyść poprzednią zawartość (4 pola( minus i max //3 pozycje)) LCDxy(10,0); //powrót kursora if(x&0b10000000) //jak warunek spełniony to x jest ujemny //(kod uzupełnień do 2), bo na 1-szej pozycji jest jedynka { // i wtedy: write_char(0x2D);//pokaż "-" przed liczbą x=x&0b01111111;//usuń jedynkę z przodu lcd_int(128-x); //od 128 odejmij liczbę zawartą w pozostałych 7 bitach } //ale jeśli jedynki nie ma z przodu, to else { lcd_int(x); //x jest dodatni, bez minusa, pokaż X } //--- Y --- LCDxy(3,1); write_text(" ");//wyczyść poprzednią zawartość (4 pola( //minus i max 3 pozycje)) LCDxy(3,1);//wróć kursor if(y&0b10000000) //jak warunek spełniony to Y jest ujemny //bo na 1-szej pozycji jest jedynka { write_char(0x2D);//pokaż "-" przed liczbą y=y&0b01111111;//usuń jedynkę z przodu lcd_int(128-y); //od 128 odejmij liczbę zawartą w pozostałych 7 bitach } else { lcd_int(y); //x jest dodatni } //--- Z --- LCDxy(10,1); write_text(" ");//wyczyść poprzednią zawartość (4 pola( minus //i max 3 pozycje)) LCDxy(10,1);//powrót kursora if(z&0b10000000) //jak warunek spełniony to Z jest ujemne //bo na 1-szej pozycji jest jedynka { write_char(0x2D);//pokaż "-" przed liczbą z=z&0b01111111;//usuń jedynkę z przodu lcd_int(128-z); //od 128 odejmij liczbę zawartą w pozostałych 7 bitach } else { lcd_int(z); //Z jest dodatni, pokaż jego wartość } timer0_flag=0; } } }
lcd.h
/* * lcd.h * * Created on: 2011-08-20 * Author: Michal Smalera */ #ifndef LCD_H_ #define LCD_H_ void waitms(char x); void write_to_lcd(char x); void write_command(char x); void write_char(char x); void write_text(char *s); void LCDxy(char x,char y); void lcd_init(void); void lcd_int(int val); #endif /* LCD_H_ */
lcd.c
/* * lcd.c * * Created on: 2011-08-20 */ #include <avr/io.h> #include "lcd.h" //-----LCD--------------------------------------------- //Połączenie LCDka do Portów: //PB2 - RS, PB3 - E, PB4 - PB7 D4-D7 #define PORT_LCD PORTB//do ktorego portu podłączono linie "Data" LCD #define E 3 //do ktorego numeru pinu portu_lcd podłączony E #define RS 2 // RS // #define SET_E PORT_LCD |= _BV(E) #define CLR_E PORT_LCD &= ~_BV(E) // #define SET_RS PORT_LCD |= _BV(RS) #define CLR_RS PORT_LCD &= ~_BV(RS) // funkcja opóźniająca o x*1ms void waitms(char x) { unsigned char a, b; // zmnienne licznikowe for(; x > 0; --x) // ta pętla zostanie wykonana x-razy for(b = 10; b > 0; --b) // a ta 10 razy for(a = 100; a > 0; --a) { // natomiast ta 100 razy __asm("nop"); // dodatkowa instrukcja opóźniająca o 1 cykl } // razem to da opóźnienie ok. x * 1ms // x od 0 do 255 } // procedura zapisu bajtu do wyświetlacza LCD // bez rozróżnienia instrukcja/dana //zeby zapisac znak do LCD trzeba ustawic RS - 1, dane na pinach portu 4 do 7 //tryb 4-bity i "zatrzasnąć" sygnalem E void write_to_lcd(char x) { PORT_LCD = ((PORT_LCD & 0x0F) | (x & 0xF0)); // dane- pierwszej połówki //bajtu (jeśli szyna danych to piny 4 do 7) SET_E; // ustaw na E stan wysoki waitms(1); CLR_E; // opadające zbocze na E -> zapis do wyświetlacza PORT_LCD = ((PORT_LCD & 0x0F) | ((x & 0x0F) << 4)); // zapis drugiej //połowki bajtu SET_E; // ustaw na E stan wysoki waitms(1); CLR_E; // opadające zbocze na E -> zapis do wyświetlacza waitms(1); // czekaj 1ms } // procedura zapisu instrukcji do wyświetlacza LCD void write_command(char x) { CLR_RS; // niski stan na RS -> zapis instrukcji write_to_lcd(x); // zapis do LCD } // procedura zapisu danej do wyświetlacza LCD void write_char(char x) { SET_RS; // wysoki stan na RS -> zapis danej CLR_E; write_to_lcd(x); // zapis do LCD } // procedura zapisu tekstu do wyświetlacza LCD void write_text(char *s) { while(*s) { // do napotkania 0 write_char(*s); // zapisz znak wskazywany przez s na LCD s++; // zwiększ s (przygotuj nastepny znak) } } //pozycjonowanie na LCD void LCDxy(char x,char y) //0,0 -lewy górny róg LCDka, 0,1 -lewy dolny róg { unsigned char com=0x80;//1000 0000 com|=(x|(y<<6)); write_command(com); //po write command(com) do wyświetlacza zostaje zapisana komenda 1xxx xxxx, //co daje nam polecenie wybrania określonego adresu w pamięci DDRAM //wyświetlacza, teraz operacja zapisu będzie się odnosić do tej komórki //w pamięci } void lcd_int(int val) { char bufor[17]; write_text(itoa(val, bufor, 10)); } //inicjalizacja LCDka //-------------------------------------------------------------------------- void lcd_init(void) { waitms(100); // czekaj 100ms na ustabilizowanie się napięcia zasilającego CLR_E; // E = 0 CLR_RS; // RS = 0 char i; // zmienna licznikowa for(i = 0; i < 3; i++) { // trzykrotne powtórzenie bloku instrukcji PORTB = 0x30; // 30h to jest //7654 3210 //0011 0000 waitms(1); SET_E; // E = 1 waitms(1); // sygnał E -stan wysoki ma być utrzymany co najmniej tEN>450ns CLR_E; // E = 0 waitms(5); // czekaj 5ms } PORT_LCD = 0x20; // //7654 3210 //0010 0000 SET_E; // E = 1 waitms(1); CLR_E; // E = 0 waitms(2); // czekaj 1ms write_command(0x28); // interfejs 4-bity, 2-linie, znak 5x7 0010 1000 //organizacja: 0010 NFxx N- liczba linii (1-2linie, 0-1linia), F-znak //(1-5x10, 0-5x7) write_command(0x08); // wyłącz LCD, kursor i miganie //organizacja: 0000 1000 - wyłączanie LCD write_command(0x01); // czyść LCD //organizacja: 0000 0001 - włącz LCD (czysty) write_command(0x06); // bez przesuwania w prawo //0000 01D0 gdzie D to kierunek przesuwu kursora 1 to + zaś 0 to - write_command(0x0C); // włącz LCD, bez kursora i mrugania //0000 1101 ??? }
i2c.h
/* * i2c.h * * Created on: 2011-08-20 */ #ifndef I2C_H_ #define I2C_H_ //#define ACK 1 //#define NACK 0 void delay(void); void i2cstart(void); void i2cstop(void); void i2cwrite(unsigned char x); unsigned char i2cread(unsigned char ack); void I2C_read_buf(uint8_t sla, uint8_t adr, uint8_t len, uint8_t *buf); void I2C_write_buf(uint8_t sla, uint8_t adr, uint8_t len, uint8_t *buf ); #endif /* I2C_H_ */
i2c.c
/* * i2c.c * * Created on: 2011-08-20 */ #include <avr/io.h> #include "i2c.h" //-----I2C--------------------- #define I2CDir DDRD #define I2COut PORTD #define I2CIn PIND // #define SDA 5 #define SCL 6 // #define SET_SDA I2COut |= (1 << SDA) #define CLR_SDA I2COut &= ~(1 << SDA) // #define SET_SCL I2COut |= (1 << SCL) #define CLR_SCL I2COut &= ~(1 << SCL) // #define SDA_OUT I2CDir |= (1 << SDA) #define SDA_IN I2CDir &= ~(1 << SDA) // #define SCL_OUT I2CDir |= (1 << SCL) #define SCL_IN I2CDir &= ~(1 << SDA) // #define GET_SDA (I2CIn & (1 << SDA)) // #define ACK 1 #define NACK 0 // void delay(void) { asm("nop"); asm("nop"); asm("nop"); asm("nop"); } // funkcja generujaca sygnał start void i2cstart(void) { SDA_OUT; // SCL_OUT; // ustawienie linii SDA i SCL w tryb wyjściowy SET_SDA; // SET_SCL; // ustawienie na liniach SDA i SCL stanu wysokiego delay(); // opóźnienie CLR_SDA; delay(); CLR_SCL; } // funkcja generujaca sygnał stop void i2cstop(void) { CLR_SDA; delay(); SET_SCL; delay(); SET_SDA; delay(); } // funkcja wysyłająca bajt na szynę I2C void i2cwrite(unsigned char x) { unsigned char count = 9; do { CLR_SCL; if(x & 0x80) { SET_SDA; } else { CLR_SDA; } x <<= 1; delay(); SET_SCL; delay(); } while(--count); CLR_SCL; } // funkcja odczytujaca bajt z szyny I2C unsigned char i2cread(unsigned char ack) { unsigned char count = 8, temp = 0; SET_SDA; SDA_IN; do { delay(); SET_SCL; delay(); temp <<= 1; if(GET_SDA) { temp++; } CLR_SCL; } while(--count); if(ack) { SET_SDA; } delay(); SET_SCL; delay(); CLR_SCL; return (temp); } //odczyt z I2C spod urządzenia o adresie 'SLA' z podadresu 'adr' ilości //bajtów 'len' i zachowanie w 'buf' void I2C_read_buf(uint8_t sla, uint8_t adr, uint8_t len, uint8_t *buf) { i2cstart(); i2cwrite(sla); i2cwrite(adr); i2cstart(); i2cwrite(sla + 1); while(len--) { *buf++ = i2cread(len ? ACK : NACK); } i2cstop(); } //zapis na I2C do urządzenia o adresie 'SLA' do podadresu 'adr' ilości //bajtów 'len' spod podręcznego 'buf' void I2C_write_buf(uint8_t sla, uint8_t adr, uint8_t len, uint8_t *buf) { i2cstart(); i2cwrite(sla); //wysyłka adresu slave-a i2cwrite(adr); //wysyłka adresu rejestru slave-a (SUB) while(len--) { i2cwrite(*buf++); //wysyłka danych } i2cstop(); //koniec transmisji }
Filmik obrazujący podstawową komunikację pomiędzy ATtiny2313, a LIS302DL:
Na koniec drobna uwaga. Starałem się wyjaśnić wszystko w przystępny i zrozumiały sposób, unikając zbyt zawiłych terminów i wyjaśnień. Z drugiej strony jednak weźcie proszę pod uwagę, że jest to tekst techniczny i bez pewnych podstaw nie da się go łatwo "wchłonąć".
Proszę również o odrobinę wyrozumiałości: to mój pierwszy tak poważny artykuł (no, oprócz pracy dyplomowej, ale tamto musiałem, a poza tym nie publikowane przed takim gronem, a to po prostu chcę, no i odbiorcy inni :)
W razie pytań - komentarze. Postaram się odpowiedzieć.
Powodzenia w przyśpieszaniu!
Michał Smalera
Opublikowałem artykuł jeszcze przed sprawdzeniem go przez autora. Stąd mogą być jeszcze jakieś drobne poprawki z jego strony. Dam znać, gdy artykuł będzie sprawdzony.
OdpowiedzUsuńCzy ten akcelerometr można gdzieś kupić? Ile kosztuje?
OdpowiedzUsuńTak - w dużych sklepach internetowych znajdziesz go za około 18-20zł brutto.
UsuńHe, he, znalazłem w szafie starą nokie i jest!!! No to wiem już co będę robił w weekend :) Dzięki za ten artykuł 5 gwiazdek poleciało do ciebie.
OdpowiedzUsuńCzy dużo przeróbek jest niezbędnych, by dla atmega8 ten program przystosować? Możesz wskazać co powinienem zmienić lub zwrócić uwagę?
OdpowiedzUsuńGdy lutowałeś druciki do układu, to najpierw lutowałeś do układu a później do płytki, czy odwrotnie? Ode mnie także 5!
OdpowiedzUsuńistny odlot!
OdpowiedzUsuńspory kawał dobrej roboty!
urządzenie działa bez zarzutu - widziałem je osobiście.
Zigi ;)
Wykorzystałeś to już do jakiegoś konkretnego celu?
OdpowiedzUsuńZwrócę uwagę tylko na zbyt skomplikowaną główną pętlę w tym programie. itoa() samo w sobie generuje stringi ze znakami "-" więc szkoda marnować taką funkcjonalność.
OdpowiedzUsuńWystarczy odpowiedni typ zmiennych i kilka dodatkowych rzutowań i cała funkcja bardzo się upraszcza i staje się bardziej czytelna dla mniej doświadczonych.
int8_t x,y,z;
while(1) {
if(timer0_flag) {
//teraz dokonujemy zapytania o adresy z wartościami X, Y, Z i będziemy
//je wyświetlać na LCD
I2C_read_buf(LIS302_Addr,OutX,1, bufor);//odczyt z LISa spod OutX
//jednego bajtu i zapisanie wartości pod adres wskazywany
//wskaźnikiem bufor
x=(int8_t)*bufor; //wrzucenie do x wartości bufora
I2C_read_buf(LIS302_Addr,OutY,1,bufor);//odczyt wartości Y
y=(int8_t)*bufor;
I2C_read_buf(LIS302_Addr,OutZ,1,bufor);//Z
z=(int8_t)*bufor;
//--- X ---
LCDxy(10,0);
write_text(" ");//wyczyść poprzednią zawartość (4 pola( minus i max
//3 pozycje))
LCDxy(10,0); //powrót kursora
lcd_int(x);
//--- Y ---
LCDxy(3,1);
write_text(" ");//wyczyść poprzednią zawartość (4 pola(
//minus i max 3 pozycje))
LCDxy(3,1);//wróć kursor
lcd_int(y);
//--- Z ---
LCDxy(10,1);
write_text(" ");//wyczyść poprzednią zawartość (4 pola( minus
//i max 3 pozycje))
LCDxy(10,1);//powrót kursora
lcd_int(z);
}
timer0_flag=0;
}
Lutowanie:
OdpowiedzUsuń1. podstawa to odpowiednio cienki drucik, można rozpleść taki miedziany kabelek składający się z kilkunastu cienkich,
2. przygotuj pola lutownicze - niech będą odpowiednio pocynowane, żeby podczas lutowania nie trzeba było nadmiernie się męczyć,
3. przewody używane do połączeń też pocynuj - niech się łatwo lutują
4. przymocuj akcelerometr do płyty, płytę do stołu, albo w uchwyt
5. pęsetą albo dwiema spróbuj wyprofilować przewód, żeby miał kształt docelowy, no chyba, że jest tak giętki, że ugnie się bez problemów
5. ręka podparta w nadgarstku (oprzyj na stole)
Osobiście lutowałem najpierw do LIS-a, później do płyty (ma większe pola lutownicze, więc tolerancja na niedobraną długość jest większa)
-------
ATmega8 - proponuję użyć TWI i gotowych bibliotek do obsługi I2C. Reszta podobnie jak w przykładzie, proponuję co jakiś czas (przerwanie z Timer0 lub 1, w dramatycznych okolicznościach _delay, aczkolwiek nie polecam) odczytywać wartości przyśpieszenia. W razie problemów z I2C i komunikacją (zdarzało się) należy obniżyć prędkość I2C do 100kHz
Gdzieś mam listing z wykorzystaniem na ATmegę 16, ale użyłem tam gotowych bibliotek z pewnej książki, dlatego nie chciałem ich publikować, bo to nie moje.
------------
Wykorzystanie w drodze: na razie nie chcę zdradzać dokładnego pomysłu, ale z innych zastosowań: balansujący robot (podobny jak Segway, tylko zasada inna), miernik przyśpieszenia do samochodu (nie polecam, zamiast patrzeć na drogę, gapisz się na to ile G miałeś na zakręcie i nagle widzisz -10g!! No tak, drzewo... ) , aczkolwiek mogłoby być małe urządzonko logujące wartości, bez wyświetlacza, żeby nie kusiło :)
------------
Deucalion:
Dziękuję za podpowiedź, popróbuję w najbliższej wolnej chwili, jak już itoa tam siedzi, szkoda jej nie wykorzystać...
To urządzonko mogłoby informować głosowo, to już jest akceptowalne dla kierowcy. Ale 10g to chyba za dużo dla tego akcelerometru? Dzięki za program!
Usuńa jak rozpoznałeś że to ten model akcelerometru siedzi w tych nokiach? przecie tam nie ma żadnych oznaczeń mówiących o modelu?
OdpowiedzUsuńPomocny okazal sie service manual.
UsuńJak na pierwszy artykuł, to wyszedł całkiem fajny. Gratulacje!
OdpowiedzUsuńDziekuje, milo mi :)
OdpowiedzUsuń//TIMER
OdpowiedzUsuńTCCR0B=0x05; //tryb pracy licznika (0000 0100 -0x04- to preskaler 256, a
//0000 1000 -0x05- to 1024)
" //0000 1000 -0x05- to 1024) "
nie
0x05 to binarnie 1001 :)
niee, tez sie pomylilem
Usuń0x05 to binarnie 101 :P
/ten sam anonim ^
Czy twoją bibliotekę do i2c mogę używać w innych projektach? Czy coś w niej trzeba zmienić lub czy coś do niej jeszcze dodać? Z góry dziękuję za odpowiedź i pozdrawiam autora!
OdpowiedzUsuńNo, ta biblioteka to nie taka znowu "moja". Złożyłem ją z kilku różnych znalezionych w necie.
UsuńJako, że to biblioteka do I2C, więc nadaje się do komunikacji z innymi urządzeniami komunikującymi się dzięki temu protokołowi. Ja osobiście wykorzystywałem ją do obsługi RTC (Real Time Clock) PCF8583 oraz przetwornik A/D PCF8591. W obu przypadkach powodzenie. Pamiętaj jednak, że taki manewr warto zastosować dla małych mikrokontrolerów, większe - jak np ATmega 8 mają wbudowaną obsługę I2C (pod nazwą TWI) i szkoda marnotrawić miejsce we flashu na osobną bibliotekę.
Mam pytanie.Na schemacie napisane jest, że pin nr12 czyli SDO ma wisieć w powietrzu a w opisie Autor pisze ,że podłączył go przez rezystor 4,7k do VCC.
OdpowiedzUsuńJak to ma w końcu być bo nie wiem.Próbowałem i tak i tak, zmieniałem adres lisa i już nie wiem co robić.W końcu urwałem to pole nr12 i teraz nie wiem jak mam to zrobić.Nie mogę tego ruszyć.Podłączone mam tak
1.VCC
2.GND
3.VCC
4.GND
5.GND
6.VCC i kondensator
7.VCC
8.nc
9.nc
10.GND
11.GND
12.nc
13.SDA
14.SCL
Rozumiem, że obsługa I2c jest programowa a nie sprzętowa?