Opisz swój projekt na forum i odbierz 50 zł rabatu w sklepie Botland. Sprawdź szczegóły »

Kurs STM32 F1 HAL – #11 – I2C w praktyce, pamięć EEPROM

Kurs STM32 F1 HAL – #11 – I2C w praktyce, pamięć EEPROM

Wcześniej poznaliśmy dwa interfejsy szeregowe: UART i SPI. Pierwszy wymagał tylko dwóch linii, ale był dość powolny. Drugi pracował znacznie szybciej wykorzystując więcej wyprowadzeń.

Teraz zajmiemy się I2C, który kwalifikuje się gdzieś pomiędzy wcześniejszymi interfejsami.

Na początku wykorzystamy I2C (czytaj: i kwadrat ce) do komunikacji z pamięcią EEPROM, a później z akcelerometrem. Przykład z pamięcią EEPROM jest bardzo ważny. Osoby przyzwyczajone do programowania układów AVR często zapominają, że STMy nie są wyposażone w pamięć EEPROM, więc taki dodatkowy układ może okazać się pomocny przy wielu projektach.

Główny bohater odcinka - pamięć EEPROM.

Główny bohater odcinka - pamięć EEPROM.

I2C - Najważniejsze informacje

Interfejs I2C podobnie do SPI działa w sposób synchroniczny. Oznacza to, że sygnał zegarowy jest przesyłany między układami, co odróżnia go od UART-a, który musiał polegać na zegarach wbudowanych w komunikujące się układy.

Projektanci interfejsu postanowili ograniczyć liczbę niezbędnych linii, ale jednocześnie umożliwić podłączanie wielu układów do wspólnego interfejsu. Wymagania na pierwszy rzut oka mogą się wydawać sprzeczne. Jednak ostatecznie założony cel został osiągnięty (kosztem komplikacji protokołu).

Tak samo jak w przypadku SPI układy komunikujące się przez I2C dzielą się na 2 kategorie: układów nadrzędnych (ang. master) oraz podrzędnych (ang. slave). Do jednego interfejsu może być jednocześnie podłączonych kilka układów nadrzędnych, my skupimy się na prostej wersji z jednym układem master, którym będzie nasz mikrokontroler.

Zestaw elementów do kursu

 999+ pozytywnych opinii  Gwarancja pomocy  Wysyłka w 24h

Zestaw ponad 120 elementów do przeprowadzenia wszystkich ćwiczeń z kursu można nabyć u naszych dystrybutorów! Dostępne są wersje z płytką Nucleo lub bez niej!

Zamów w Botland.com.pl »

Podłączenie do magistrali I2C

Schemat podłączenia układów do I2C wygląda następująco:

Magistrala I2C.

Magistrala I2C w praktyce.

Jak widać wykorzystywane są dwie linie komunikacyjne (Vcc, to zasilanie):

  • SDA - dwukierunkowa linia danych.
  • SCL - linia zegarowa sterowana przez układ nadrzędny.

Przy SPI do przesyłania danych wykorzystywane były dwie linie danych MOSI i MISO. Dzięki temu jednocześnie można wysyłać i odbierać informacje. Taki tryb działania nazywany jest full-duplex. I2C posiada tylko jedną linię danych, więc w danej chwili można na niej tylko wysyłać lub odbierać dane.

Najczęściej wyjścia mikrokontrolera są używane w tzw. trybie przeciwsobnym. Jeśli na wyjściu jest logiczne 0, odpowiednie wyprowadzenie jest zwierane do masy. Natomiast logiczna 1 to połączenie z zasilaniem (3.3V).

W takiej sytuacji połączenie wyjść dwóch układów może łatwo prowadzić do zwarcia. Jeśli jeden układ będzie próbował wystawić logiczne 0, a drugi 1, nastąpi zwarcie między masą i zasilaniem. Aby tego uniknąć konieczna jest synchronizacja dostępu do interfejsu i zapobieganie jednoczesnym zapisom do wspólnej linii.

Ponieważ I2C nie wykorzystuje sygnałów CS, konieczne było znalezienie innej metody współdzielenia linii. Wykorzystano w tym celu wyjście typu otwarty kolektor (open drain). Oznacza to, że gdy mikrokontroler wystawia na pinie logiczne 0, linia jest zwierana do masy. Natomiast logiczna 1 zamiast zwierać wyście do zasilania, pozostawia linię niepodłączoną - dokładnie tak samo, jak podczas pracy linii jako wejścia.

Sposób działania linii magistrali I2C

Jeśli którykolwiek z układów podłączonych do danej linii interfejsu wystawi logiczne 0, cała linia jest zwierana do masy. Natomiast kiedy wszystkie układy wystawiają wartość 1, linia za pomocą rezystora jest łączona z zasilaniem i pojawia się na niej napięcie 3.3 V.

Kolejne etapy komunikacji przez I2C

Komunikacja przez I2C wygląda następująco:

  1. Układ nadrzędny rozpoczyna komunikację poprzez wystawienie sygnału START - jest to specjalna kombinacja przebiegów na liniach SDA i SCL informująca o początku transmisji.
  2. Następnie wysyła adres układu, z którym chce się komunikować oraz bit informujący o kierunku transmisji (nadawanie albo odbiór). Układ podrzędny odpowiada bitem potwierdzenia ACK.
  3. Po wybraniu układu, następuje właściwa transmisja:
    1. jeśli zostało wybrane nadawanie, master wysyła kolejne bajty, a slave potwierdza odebranie każdego bitem ACK
    2. w przypadku odbioru, slave wysyła kolejne bajty, a master odbiera i wysyła ACK.
  4. Na koniec master wysyła sygnał STOP informujący o zakończeniu transmisji.

Są jeszcze dwie rzeczy, które zostały pominięte w powyższym opisie - jeśli master chce zacząć kolejną transmisję natychmiast po poprzedniej, może pominąć końcowe STOP i od razu wysłać kolejne START.

Druga sprawa to potwierdzenia - pisaliśmy o ACK, ale istnieje jeszcze negatywne potwierdzenie NACK. Najczęściej informuje ono o końcu danych, przykładowo master odczytując dane powinien potwierdzać odbiór za pomocą ACK, poza ostatnim bajtem, kiedy powinien odesłać NACK tak, aby slave wiedział, że nie musi już więcej danych wysyłać.

Pamięć EEPROM

Gdy wiemy już jak działa ten interfejs możemy przejść do praktyki. Teraz zajmiemy się pamięcią eeprom, a w kolejnym odcinku przyjdzie pora na akcelerometr cyfrowy.

STM32_EEPROM

Elementy niezbędne do przetestowania komunikacji I2C.

Jako pierwszy przykład wykorzystamy komunikację z pamięcią EEPROM, konkretnie z układem typu 24AA01. Jak zawsze należy zacząć od przeczytania dokumentacji. Jest to relatywnie mała pamięć nieulotna o pojemności 128B. Może się to wydawać niewiele w dobie dysków o pojemności TB, jednak taka pamięć jest zaskakująco użyteczna.

Oczywiście w mikrokontrolerze znajdziemy dużo więcej pamięci nieulotnej typu Flash, którą moglibyśmy wykorzystać do przechowywania danych. Jednak pamięć Flash ma dość ograniczoną liczbę cykli zapisu. W przypadku STM32F103 producent deklaruje 10000 cykli zapisu, więc jeśli chcielibyśmy w tej pamięci przechowywać dane, które często zmieniamy, moglibyśmy szybko przekroczyć ten limit. Dla porównania - nasza pamięć EEPROM wytrzyma aż 1000000 cykli zapisu. Poza tym łatwiej ją wymienić niż cały mikrokonotroler.

Przyjrzyjmy się wyprowadzeniom układu 24AA01:

Opis wyprowadzeń pamięci eeprom.

Opis wyprowadzeń pamięci eeprom.

Linie Vss i Vcc to odpowiednio masa i zasilanie. SCL i SDA to linie naszego interfejsu I2C. WP oznacza Write Protect i musi być połączona z masą, jeśli chcemy mieć możliwość zapisu do pamięci EEPROM. Są jeszcze trzy wyprowadzenia adresowe A0, A1, A2 - nie są one używane w układzie 24AA01 i mogą pozostać niepodłączone.

Podłączenie pamięci EEPROM do STM32

Układ powinniśmy podłączyć zgodnie z poniższym rysunkiem. Do komunikacji z pamięcią EEPROM wykorzystamy interfejs I2C1. Jego linie SCL i SDA są dostępne na pinach PB6 i PB7.

i2c-eeprom_bb

Schemat podłączenie pamięci EEPROM do STM32.

Zapis do pamięci EEPROM

Schemat komunikacji z układem 24AA01 znajdziemy we wspomnianej wcześniej dokumentacji. Poniżej umieściłem najważniejszy dla nas diagram:

Sposób zapisu do pamięci EEPROM przez I2C.

Sposób zapisu do pamięci EEPROM przez I2C.

Jak widzimy, jest to typowa komunikacja za pomocą I2C. Najpierw wysyłamy sygnał START. Po nim adres urządzenia wraz z bitem kierunku transmisji. Przy nadawaniu (zapisie) jest to zero, a przy odbiorze (odczycie) jedynka.

Układ pamięci odpowiada przesyłając potwierdzenie ACK. Następnie wysyłamy adres komórki pamięci, do której chcemy zapisywać. Pamiętamy przy tym, że dostępnych jest (aż) 128 adresów. Pamięć ponownie potwierdza odbiór danych za pomocą ACK.

Następnie wysyłamy jedną lub więcej wartości, każda jest potwierdzana za pomocą ACK. Na koniec wysyłamy STOP, aby zakończyć transmisję i umożliwić pamięci rzeczywisty zapis danych.

STM32 i EEPROM - zapis danych w praktyce

Wiemy już jak ma wyglądać transmisja, teraz możemy przejść do jej implementacji na naszym mikrokontrolerze. Jak zwykle zaczynamy od podłączenia odpowiedniego sygnału zegarowego:

Konfigurujemy piny PB6 i PB7 do pracy z interfejsem I2C. Tryb którego użyjemy to open-drain:

Kolejny krok to zadeklarowanie zmiennej konfigurującej I2C oraz inicjalizacja modułu. Opcji jest dość sporo, ale taki jest właśnie urok biblioteki HAL:

Wybrana prędkość, to 100kHz. Maksymalna dla naszego układu wynosi 400kHz, jednak wtedy dość długie przewody łączące mogłyby spowodować problemy z komunikacją. Biblioteka HAL udostępnia parę funkcji: HAL_I2C_Master_Transmit oraz HAL_I2C_Master_Receive, które pozwalają wysyłanie oraz odbieranie danych przez I2C tak jak opisywaliśmy to wcześniej.

Okazuje się, że wiele urządzeń stosuje nieco inny schemat komunikacji. Najpierw wysyłany jest adres urządzenia slave, następnie adres w pamięci lub numer rejestru do którego chcemy się odwoływać. Można taką komunikację uzyskać wielokrotnie wywołując wspomniane funkcje, ale dla wygody i optymalizacji kodu HAL udostępnia również parę funkcji HAL_I2C_Mem_Write i HAL_I2C_Mem_Read.

Funkcje są zadeklarowane następująco:

Parametry ich wywołania to kolejno:

  • hi2c - struktura opisująca interfejs i2c
  • DevAddress - adres układu
  • MemAdres - adres w pamięci lub numer rejestru
  • MemAddSize - długość adresu (liczba bajtów)
  • pData - bufor na dane które chcemy wysłać lub odebrać
  • Size - wielkość bufora pData (liczba bajtów)
  • Timeout - maksymalny czas transmisji

Może się wydawać, że to sporo pracy, ale jak za chwilę zobaczymy ich użycie jest bardzo proste. Wcześniej wspomnieliśmy, że nasza pamięć eeprom stosuje dokładnie taki schemat komunikacji. Oznacza to, że do zapisania danych wystarczy jedno wywołanie funkcji. Gdybyśmy chcieli zapisać jeden bajt wystarczyłoby więc napisać:

STM32 i EEPROM - odczyt danych w praktyce

Wiemy już jak zapisać dane, teraz spróbujemy je odczytać i sprawdzimy, czy wartości się zgadzają. Ponownie, przebieg odczytu jest opisany w dokumentacji układu pamięci.

Odpowiedni diagram widzimy poniżej:

Sposób odczytu do pamięci EEPROM przez I2C.

Sposób odczytu do pamięci EEPROM przez I2C.

Jak widać komunikacja jest nieco skomplikowana, ale uratuje nas gotowa funkcja HAL_I2C_Mem_Read. Wystarczy więc, że napiszemy:

Teraz możemy napisać krótki program, który zapisze wartość zmiennej test do pamięci eeprom, a następnie wczyta zapisaną wartość do zmiennej result.

Całość powinna wyglądać następująco:

Jak sprawdzić, czy program działa? Najlepiej uruchomić go pod debuggerem i zobaczyć czy zawartość zmiennych test i result jest zgodna z oczekiwaniami. O ile poprzednio zapisaliśmy dane, a nasz układ jest poprawnie podłączony powinniśmy zobaczyć rezultat jak na obrazku:

Zapis i odczyt danych z pamięci EEPROM w praktyce.

Zapis i odczyt danych z pamięci EEPROM w praktyce.

Licznik uruchomień układu

Umiemy już zapisywać i odczytywać dane z pamięci EEPROM, czas napisać program, który wykorzysta naszą nową umiejętność. Napiszmy program, który będzie zliczał ile razy został uruchomiony. Informacje o liczbie uruchomień będziemy wysyłać za pomocą UARTa.

Poprzednio zapisywaliśmy i odczytywaliśmy tylko jeden bajt. Jednak taki licznik szybko by się przepełnił i po 256 uruchomieniach mielibyśmy znowu wartość zero. Zamiast tego użyjemy licznika 32-bitowego.

Program jest dość prosty. Najpierw odczytuje ile razy uruchomiliśmy urządzenie, następnie zwiększa stan licznika, wypisuje komunikat oraz zapisuje nową wartość. Cały program wygląda następująco:

Rezultat działania programu w praktyce widoczny jest poniżej:

STM32 i EEPROM w praktyce - licznik uruchomienia programu.

STM32 i EEPROM w praktyce - licznik uruchomienia programu.

Podsumowanie

Komunikacja za pomocą I2C jest nieco bardziej skomplikowana niż za pomocą SPI, czy UART, jednak interfejs ten ma naprawdę wiele zalet. Za pomocą raptem dwóch linii można połączyć wiele urządzeń i komunikować się z nimi z zadowalającą prędkością.

Komunikacja może w pierwszej chwili wydawać się skomplikowana, ale raz napisane procedury mogą być równie łatwo używane w przykładowym programie, jak i w przypadku innych rozwiązań.

Nawigacja kursu

Autor kursu: Piotr Bugalski
Redakcja: Damian Szymański
Testy: Piotr Adamczyk

eeprom, i2c, interfejs, komunikacja, kursSTM32F1HAL, stm32

Trwa ładowanie komentarzy...