Mikrokontrolery - Jak zacząć?

... czyli zbiór praktycznej wiedzy dot. mikrokontrolerów.

czwartek, 3 marca 2011

Akcelerometr LIS302DL + ATtiny2313 + biblioteka


Autor: Michał Smalera
Redakcja: Dondu

Przeglądając listę proponowanych tematów zauważyłem zapytanie o artykuł nt. akcelerometrów i żyroskopów.

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 na płycie telefonu Nokia N95.
Wygląda on tak:

Akcelerometr LIS302DL - przód.    Widok strona pól lutowniczych.
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.
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).
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ń



Akcelerometr LIS302DL - Schemat podłączenia.
Akcelerometr LIS302DL - Schemat podłączenia.

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:
  1. rozkaz zapisu, 
  2. adres (adres naszego LIS-a) 
  3. 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:
  1. Mikroprocesor, to Master (Pan, albo Szef) , LIS to Slave (Sługa) - tak dla uzmysłowienia - Pan mówi, Sługa słucha.
  2. 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.
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:


Akcelerometr LIS302DL - Zapis jednego bajtu.

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:

  1. (ST) STart - wiadomo
  2. (SAD) SlaveADdress + W - jw, czyli znowu 00111010
  3. (SAK) Slave ACK - LIS potwierdza odebranie danych
  4. (SUB) SUBaddress - teraz chcemy danych spod 0x0F, więc 00001111
  5. (SAK) ACK od LIS-a - jak poprzednio
  6. (SR) Powtórne wysłanie sygnału Start (Start Repeated)
  7. (SAD+R) wysłanie adresu Slave-a plus bit Read -tym razem 00111000
  8. (SAK) - ACK od LIS-a
  9. (DATA) - tym razem nasz Master odbiera dane, a LIS nadaje (mała zamiana ról)
  10. (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)
  11. (SP) - STOP, czyli Master zakańcza transmisję


I znowu datasheet:

Akcelerometr LIS302DL - Odczyt jednego bajtu.
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

Oceń artykuł.
Wasze opinie są dla nas ważne, gdyż pozwalają dopracować poszczególne artykuły.
Pozdrawiamy, Autorzy
Ten artykuł oceniam na:

20 komentarzy:

  1. 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ń
  2. Czy ten akcelerometr można gdzieś kupić? Ile kosztuje?

    OdpowiedzUsuń
    Odpowiedzi
    1. Tak - w dużych sklepach internetowych znajdziesz go za około 18-20zł brutto.

      Usuń
  3. 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ń
  4. 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ń
  5. 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ń
  6. istny odlot!
    spory kawał dobrej roboty!
    urządzenie działa bez zarzutu - widziałem je osobiście.
    Zigi ;)

    OdpowiedzUsuń
  7. Wykorzystałeś to już do jakiegoś konkretnego celu?

    OdpowiedzUsuń
  8. 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ść.
    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;
    }

    OdpowiedzUsuń
  9. Lutowanie:
    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ć...

    OdpowiedzUsuń
    Odpowiedzi
    1. 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ń
  10. 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ń
  11. Jak na pierwszy artykuł, to wyszedł całkiem fajny. Gratulacje!

    OdpowiedzUsuń
  12. //TIMER
    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 :)

    OdpowiedzUsuń
    Odpowiedzi
    1. niee, tez sie pomylilem
      0x05 to binarnie 101 :P
      /ten sam anonim ^

      Usuń
  13. 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ń
    Odpowiedzi
    1. No, ta biblioteka to nie taka znowu "moja". Złożyłem ją z kilku różnych znalezionych w necie.
      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ę.

      Usuń
  14. 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.
    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?

    OdpowiedzUsuń

Działy
Działy dodatkowe
Inne
O blogu




Dzisiaj
--> za darmo!!! <--
1. USBasp
2. microBOARD M8


Napisz artykuł
--> i wygraj nagrodę. <--


Co nowego na blogu?
Śledź naszego Facebook-a



Co nowego na blogu?
Śledź nas na Google+

/* 20140911 Wyłączona prawa kolumna */
  • 00

    dni

  • 00

    godzin

  • :
  • 00

    minut

  • :
  • 00

    sekund

Nie czekaj do ostatniego dnia!
Jakość opisu projektu także jest istotna (pkt 9.2 regulaminu).

Sponsorzy:

Zapamiętaj ten artykuł w moim prywatnym spisie treści.