logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[ATMega32] ATMega32: Obsługa I2C na przerwaniach w C - kiedy i dlaczego warto?

A.T. 22 Sie 2012 12:45 2883 21
  • #1 11233408
    A.T.
    Poziom 20  
    Witam
    założyłem ten temat, ponieważ ciekawi mnie obsługa I2C na przerwaniach. Próbowałem szukać w całym Internecie ale niestety nic ciekawego na jej temat nie znalazłem. Jedynie co udało mi się znaleźć to jakiś strasznie zagmatwany kod, w którym użyto przerwań do I2C. Jednak jest on strasznie skomplikowany. W jakich sytuacjach się ją stosuje, oraz w czym jest lepsza od standardowego obsługiwania I2C np takiego jak tutaj:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    To jest obsługa ze strony: http://radzio.dxp.pl/twi/
    Byłbym bardzo wdzięczny, gdyby ktoś mógł wkleić tutaj kod który pozwala na obsługę I2C w przerwaniu dla modułu TWI dla Atmegi.
    Dziękuję i pozdrawiam
    A.T.
  • Pomocny post
    #2 11233466
    Andrzej__S
    Poziom 28  
    Niedawno było: link. Sam kod plików TWI_master.c i TWI_master.h może wydawać się nieco skomplikowany, ale skorzystanie z zaimplemntowanych tam funkcji jest stosunkowo proste. We wątku, do którego link podałem, jest przykład obsługi pamięci I2C z wykorzystaniem tych funkcji.
  • #3 11233578
    A.T.
    Poziom 20  
    Dziękuję za szybką odpowiedź. Przejrzałem już kody plików TWI_master.c i TWI_master.h, które umieścił Pan w tamtym temacie. Interesuje mnie ostatnia Pana wypowiedź z tamtego tematu. Czy jeśli podmieniłbym to co Pan tam opisał to zredukowało by to bezsensowne czekanie?
  • Pomocny post
    #4 11233692
    Andrzej__S
    Poziom 28  
    Niektóre z funkcji faktycznie zawierają pętlę oczekującą na zwolnienie magistrali:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Autorzy założyli, że można będzie użyć tej funkcji w dowolnym momencie, niestety powoduje to, że kiedy wywołamy funkcję, kiedy magistrala jest zajęta, to będzie ona czekała na zwolnienie magistrali, co wprowadza czasami niepotrzebne opóźnienia.
    Aby tego uniknąć, można użyć np. jakiegoś bufora cyklicznego do którego zapisujemy dane do wysłania (zamiast od razu wysyłać je przez magistralę) i w pętli głównej programu (lub np. w przerwaniu timera) sprawdzać stan magistrali i - jeśli jest wolna oraz bufor nie jest pusty - inicjować następną transmisję. Pozwoli to na usunięcie pętli oczekujących z funkcji, bo w momencie ich wywoływania jesteśmy pewni, że magistrala jest wolna (jeśli nie jest wolna, mikrokontroler może realizować inne zadania, zamiast czekać na jej zwolnienie).
    W zacytowanym przeze mnie wątku przedstawiłem fragment pseudo-kodu, jak to mniej więcej zrealizować. Oczywiście wymaga to dopisania dobrej obsługi bufora kołowego, co może nastręczyć nieco problemów, gdyż w odróżnieniu np. do USART, długość transmisji pomiędzy warunkiem startu i stopu jest zmienna.
  • #5 11234018
    A.T.
    Poziom 20  
    Dziękuję za odpowiedź.
    Czyli Twój pseudokod to jest taka jakby kolejka FIFO? Wiem o co chodzi, jednak nie umiem tego przepisać w C. Gubię się. Nie wiem np. jak oddzielić kolejne dane. Chodzi o to, że wysyłam np. adres I2C jakiegoś układu1 i informację_dla_ukł1. I po chwili od razu kolejny adres układu2 i informację_dla_ukł2. I wszystko trafia do jakiegoś bufora. To w jaki sposób mam zrealizować to w tym przerwaniu od timera, żeby było wiadomo, że należy pierwsze dwie dane z tego bufora przesłać do ukł1, a drugie dwie do ukł2, tzn. zakończyć transmisję i rozpocząć nową.
    Kurcze, jeśliby to nie był dla Ciebie duży problem, to mógłbyś napisać mi jakiś bardziej szczegółowy pseudokod? Bardzo mnie to zaciekawiło i chciałbym posiadać taki w miarę uniwersalny kod do obsługi I2C na przerwaniach.
    Dziękuję i pozdrawiam
    A.T.
  • Pomocny post
    #6 11234516
    Andrzej__S
    Poziom 28  
    A.T. napisał:
    Czyli Twój pseudokod to jest taka jakby kolejka FIFO?

    No niezupełnie. To co przedstawiłem tutaj, to pseudo-kod wykorzystujący kolejkę FIFO i funkcje z noty AVR315 Atmela, który należy umieścić w pętli głównej programu lub alternatywnie w przerwaniu timera. Sprawdza on, czy magistrala jest wolna, jaki był status poprzedniej transmisji (czy nie zakończył się błędem), czy poprzednia transmisja odebrała jakieś dane do przeanalizowania i - jeśli kolejka FIFO nie jest pusta - inicjuje nową transmisję. Same funkcje obsługi bufora cyklicznego (kolejki FIFO), czyli dodawanie danych do bufora, odczyt danych z bufora, sprawdzanie, czy jest pusty (w pseudo-kodzie funkcja buffer_is_empty() zwracająca prawdę lub fałsz) należy sobie dopisać.

    Sprawa jest dosyć prosta np. w przypadku USART. Wystarczy dodać bajt do bufora, później - jeśli bufor nie jest pusty - pobrać bajt i wysłać. W przypadku I2C sprawa jest o tyle skomplikowana, że zapis do i odczyt z bufora musi odbywać się "pakietami" o zmiennej długości, w zależności od tego, jaki układ obsługujemy i jaką operację chcemy wykonać. Przy obsłudze pamięci EEPROM przykładowo długość "pakietu" może wahać się od 2 bajtów (adres układu z bitem kierunku transmisji i adres komórki) w przypadku ustawienia adresu komórki przed operacją odczytu, do kilkuset bajtów w przypadku operacji "page write". Można to rozwiązać na różne sposoby, na przykład przyjąć, że pierwszy bajt zapisywany do, a później odczytywany z bufora to ilość bajtów do wysłania podczas jednej transmisji, a po nim następują dane (tyle bajtów, ile wynosiła wartość bajtu pierwszego).

    Poczytaj o buforach cyklicznych i spróbuj coś sam napisać na podstawie tych wskazówek. Możesz nawet wykorzystać jakiś gotowy kod obsługi bufora cyklicznego, dopisując do niego funkcje odpowiedzialne za zapis i odczyt odpowiedniej ilości bajtów.
  • #7 11235034
    janbernat
    Poziom 38  
    Przyznam się że ostatnio nie miałem siły aby odrabiać lekcje.
    Jak miałem to raczej zajmowałem się układem slave na ATtiny2313.
    Rozwiązanie Andrzej __S jest sensowne.
    W AVR315 jest bufor- ale nie kołowy.
    Gotowy bufor kołowy jest w książce Mirka.
    Gotowe wywoływanie callback w drugiej książce.
    Czy to jest to samo albo podobne do tego co opisywał tymon_x- no jeszcze nie wiem.
    Jak coś napiszą to zaraz się pokłócą.
    Nawet jak to będzie tylko inny opis czegoś bardzo podobnego.
    Ale zanim się pokłócą to może coś da się zrozumieć.
  • #8 11235084
    A.T.
    Poziom 20  
    Dziękuję za kolejną podpowiedź:) Dzięki niej udało mi się stworzyć jakiś kod. Niestety nie mam pojęcia czy poprawny. Dlatego bardzo bym prosił o oglądnięcie czy ma on w ogóle jakieś szanse działać.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #9 11235256
    Andrzej__S
    Poziom 28  
    A.T. napisał:
    Dlatego bardzo bym prosił o oglądnięcie czy ma on w ogóle jakieś szanse działać.

    Wybacz, ale zawsze miałem problemy z rachunkiem prawdopodobieństwa, więc nie potrafię odpowiedzieć ;)
    Proponowałbym jednak napisać jakiś minimalny kod, który np. będzie zapisywał jeden bajt danych do pamięci EEPROM, a później go odczytywał, który się skompiluje, spróbować go uruchomić i sprawdzić, co ewentualnie nie działa prawidłowo i wtedy przedstawić konkretny problem. Zapewne ktoś pomoże.
  • #10 11235344
    janbernat
    Poziom 38  
    Bufor kołowy ma mieć ogon i głowę.
    Każdy zapis do bufora ma zwiększać wartość głowy a każdy odczyt ma zmniejszać.
    Jak się zrównają- to alarm.
  • #11 11235358
    A.T.
    Poziom 20  
    Myślałem, że przynajmniej zobaczysz, czy kroki postępowania są poprawne. A co do prób to niestety nie dzisiaj, bo nie mam aktualnie przy sobie lutownicy i części.
  • #12 11235541
    Andrzej__S
    Poziom 28  
    Nie chciałem Cię urazić. Przejrzałem Twój kod, ale na prawdę trudno tak na szybko ocenić, czy to będzie działać. Trop niby prawidłowy, ale zawsze łatwiej jest odpowiedzieć na konkretny problem niż przeanalizować większy fragment kodu i ocenić, czy będzie działał.

    Widzę, że napisałeś własne funkcje, które dodają do bufora całe pakiety. Może jednak lepszym rozwiązaniem byłoby użyć jakiegoś gotowego, sprawdzonego bufora kołowego, a przygotowanie i odczyt pakietów zrobić w osobnych funkcjach wykorzystujących tylko gotowe funkcje wprowadzające i wyprowadzające dane z bufora.

    Naprawdę lepiej byłoby jednak, gdybyś napisał jakiś gotowy kod, wypróbował i przedstawił konkretny problem.
  • #13 11235641
    A.T.
    Poziom 20  
    Niestety teraz troszkę mnie osłabiłeś z tym, że najlepiej byłoby wykorzystać jakieś gotowe funkcje:P Bo właśnie funkcje bufferin(...) i bufferout(...) są gotowe, ściągnięte z tej strony: http://www.mikrocontroller.net/articles/FIFO
    Ja tylko je zmieniłem aby dodawały i odczytywały do/z kolejki FIFO dane w odpowiednio sformatowanych blokach.
    Niestety sam od siebie już na chwilę obecną nic więcej nie wymyśle. Może, jeśli posiadasz gotowy taki kod, mógłbyś go udostępnić.
    Dziękuję za dotychczasową pomoc.
    Pozdrawiam:)
    A.T.
  • #14 11236188
    LordBlick
    VIP Zasłużony dla elektroda
    janbernat napisał:
    Bufor kołowy ma mieć ogon i głowę.
    Każdy zapis do bufora ma zwiększać wartość głowy a każdy odczyt ma zmniejszać.
    Jak się zrównają- to alarm.
    To jest tylko jedna z przykładowych implementacji, która nie może dopuścić do zrównania się, bo wtedy jak rozróżnić czy bufor jest opróżniony, czy przepełniony ?
    Osobiście preferuję taką implementację, w której początek danych jest krótkim wskaźnikiem w ciele bufora, a druga zmienna wskazuje jego zajętość.
  • #15 11236354
    Andrzej__S
    Poziom 28  
    A.T. napisał:

    Niestety teraz troszkę mnie osłabiłeś z tym, że najlepiej byłoby wykorzystać jakieś gotowe funkcje:P Bo właśnie funkcje bufferin(...) i bufferout(...) są gotowe, ściągnięte z tej strony: http://www.mikrocontroller.net/articles/FIFO
    Ja tylko je zmieniłem aby dodawały i odczytywały do/z kolejki FIFO dane w odpowiednio sformatowanych blokach.


    No właśnie chodziło mi tylko o to, żeby nie przerabiać już gotowych, sprawdzonych funkcji, tylko użyć ich do stworzenia nowych funkcji w niezmienionej formie, na zasadzie:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    Mając pewność, że funkcja bufferin() działa prawidłowo, bo została już kiedyś przetestowana, możemy ograniczyć poszukiwanie ewentualnych błędów do nowo napisanych funkcji.
  • #16 11236845
    A.T.
    Poziom 20  
    Oki to zmienię to tak jak powiedziałeś. Mam jeszcze tylko pytanie o funkcje: error_occurred() i data_received(). Nie wiem jak one mają wyglądać. Szczególnie ta data_received. Mam coś kombinować z jakąś zmienną która będzie zmieniała wartość w przerwaniu od I2C gdy ono wykryje ze coś przyszło? Czy jakoś sprawdzać rejestr TWPS?
  • #17 11249986
    A.T.
    Poziom 20  
    Udało mi się uruchomić zapis bez opóźnień z buforem kołowym. Jedynie nie wiem jak obsłużyć odczytywanie danych. Do tego chyba ten bufor nie jest potrzebny, tylko jak to uwzględnić w przerwaniu?
    Poniżej działający kod. Na chwilę obecną odczytuję z opóźnieniami.
    Pozdrawiam
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #18 11251895
    A.T.
    Poziom 20  
    Brakuje mi tylko jedenej rzeczy. W jaki sposób mam wykrywać, że nadeszły jakieś dane z I2C i ile ich nadeszło? Próbowałem tak, że po prostu w przerwaniu I2C przypisuję jakiejś zmiennej wartość TWI_bufPtr. I w przerwaniu timera sprawdzam, gdy ta zmienna (w moim przypadku received_len) jest różna od 0 to wywołuję funkcje TWI_Get_Data_From_Transceiver(rec_data, received_len );, gdzie długością jest właśnie received_len. Na koniec zeruje received_len. Niestety nie działa taki sposób. Tylko nie wiem w którym momencie tkwi problem. Oto kod:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #19 11252052
    Andrzej__S
    Poziom 28  
    A.T. napisał:
    Jedynie nie wiem jak obsłużyć odczytywanie danych. Do tego chyba ten bufor nie jest potrzebny, tylko jak to uwzględnić w przerwaniu?

    Sprawa z odczytem jest nieco bardziej skomplikowana, ale to też idzie rozwiązać. Niestety nie przedstawię na forum ani gotowego kodu, ani szczegółowego algorytmu.

    Mogę podpowiedzieć tyle. Przykładowo, jeśli chcesz odczytać datę z układu PCF8563 i wyświetlić ją na wyświetlaczu:
    - dodajesz do bufora (kolejki) polecenie odczytaj datę i umieść dane w zmiennej data,
    - ustawiasz flagę o nazwie odczytywanie_daty_uruchomiono,
    - kończysz działanie funkcji i nie musisz na nic czekać.

    Funkcję odczytująca dane z magistrali modyfikujesz tak, aby po odczytaniu danych z magistrali we wskazane miejsce:
    - sprawdziła, która z flag odczytu została ustawiona (jakie dane były odczytywane),
    - wyzerowała tę flagę,
    - ustawiła flagę adekwatną do odczytanych danych, niech to będzie tutaj flaga o nazwie data_odczytana.

    Później w pętli głównej (lub w przerwaniu timera) tak jak pisałem wcześniej:
    - sprawdzasz, czy magistrala jest wolna (co oznacza, że transfer się zakończył),
    - jeśli tak, to sprawdzasz, czy ostatnia transmisja dostarczyła jakichś danych - linijka if(data_received()),
    - jeśli tak sprawdzasz, jaka flaga jest ustawiona (jakie dane zostały odczytane),
    - adekwatnie do flagi uruchamiasz funkcję, która na tych danych operuje, w Twoim przypadku niech to będzie funkcja wyswietl_date(),
    - zerujesz flagę data_odczytana.

    Jeśli to będzie pomocne i potrafisz rozwiązać to programowo, to życzę powodzenia. Jeśli nie, to ja bardziej nie mogę pomóc.
  • #20 11252371
    A.T.
    Poziom 20  
    Dziękuję za podpowiedź. Jednak jednej, tej najważniejszej dla mnie rzeczy nie napisałeś. Chodzi mi o tę funkcję data_received(). Jak ją napisać, jak mam sprawdzić, czy przerwanie I2C coś odczytało? Ja kombinuję coś ze zmienną received_len(w kodzie powyżej). Ale niezbyt to działa.
  • #21 11252467
    janbernat
    Poziom 38  
    Sam nie mogę sobie z tym dać rady.
    Ale jeśli przerwanie ISR(TWI_vect) nastąpiło to znaczy że coś odebrało albo wysłało.
    A to czy to był odbiór czy nadawanie to Ty decydujesz jako master.
  • #22 11252483
    Andrzej__S
    Poziom 28  
    Nie widzę deklaracji zmiennej received_len, ale zakładam, że nie zapomniałeś użyć słowa kluczowego volatile?

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    W tym momencie zmienna TWI_bufPtr zawiera raczej indeks ostatniego elementu w tablicy TWI_buf[], a nie ilość odebranych bajtów. Jak odbierzesz 1 bajt, to TWI_bufPtr będzie równy 0.
    EDIT:
    Teraz zauważyłem, że chyba się pomyliłem. TWI_buPtr jest jednak zwiększony po wygenerowaniu warunku startu i pobraniu adresu układu slave z pierwszego elementu tablicy TWI_buf[0]. Odczytywane dane są zapisywane do bufora od indeksu 1. Przepraszam za wprowadzenie w błąd. Nie przeanalizowałem tego dokładnie. Dawno nie używałem tych driverów, a ja to po prostu rozwiązuję inaczej.

    EDIT2:
    janbernat napisał:
    Ale jeśli przerwanie ISR(TWI_vect) nastąpiło to znaczy że coś odebrało albo wysłało.

    Ująłbym to raczej, że na magistrali wystąpiło zdarzenie: wygenerowany został warunek startu lub stopu, wysłany został adres układu, odebrano lub nie odebrano potwierdzenia od układu slave itp. Każde takie zdarzenie wywołuje przerwanie. O tym, jakie to było zdarzenie mówi rejestr statusowy TWSR i procedura obsługi przerwania w oparciu o jego wartość podejmuje decyzje, jakie zdarzenie będzie następne.
REKLAMA