Kurs STM32 LL cz. 19. Komunikacja I2C w trybie przerwań

Przerwania pozwalają nam uniknąć niepotrzebnego sprawdzania flag i oczekiwania na wysłanie lub odebranie danych. Przy ich użyciu możemy znacznie ograniczyć zaangażowanie CPU w proces komunikacji. Podobnie jak dla innych układów peryferyjnych wygląda to w przypadku I2C.

Przerwania magistrali I2C

Magistrala I2C pozwala na obsługę zarówno zdarzeń od transmisji, jak i błędów transmisji I2C. Wszystkie przerwania dostępne w STM32 przy komunikacji przez I2C przedstawia poniższa tabela.

Nie będziemy zajmowali się dzisiaj wszystkimi przerwaniami. Obsłużymy tylko podstawowe, niezbędne do zrealizowania przesyłania i odbierania danych. Interesować nas będzie przerwanie od przesłania bajtu (TXIS), odebrania (RXNE) oraz od flagi STOP (STOPF).

Aby włączy poszczególne przerwania, należy ustawić odpowiadający im bit w rejestrze CR1. Flagi czyścimy analogicznie jak przy transmisji danych bez przerwań.

Pamięć EEPROM AT24CXX

Wykorzystanie przerwań w przypadku czujników rzadko kiedy da nam widoczną korzyść w postaci szybszej komunikacji. Aby pobrać temperaturę z HTS221 musimy odczytać tylko kilka rejestrów. Dodatkowo często do dalszej realizacji programu potrzebujemy tych danych, dlatego tak czy inaczej, poczekać na nie musimy. 

Co innego, gdy mamy do czynienia z układem pamięci. Interfejs I2C jest bardzo popularny w przypadku nieulotnej pamięci EEPROM, które zazwyczaj wykorzystywane są do zapisywania danych konfiguracyjnych. Nie wymagają dużych prędkości działania, dlatego wykorzystują magistralę I2C. Gdy chcemy zapisać lub odczytać np. 128 B danych, wykorzystanie przerwań może znacząco odciążyć jednostkę obliczeniową mikrokontrolera.

W przykładzie wykorzystamy popularną pamięć z serii 24Cxx firmy Atmel, a konkretnie układ o pojemności 2 kbit (256 B), czyli AT24C02.

Proces zapisu danych z pamięci jest bardzo prosty. Przesyłamy adres urządzenia, adres komórki pamięci i następnie bajty do zapisania w pamięci.

Analogicznie odczytujemy dane z pamięci.

Adres urządzenia w przypadku układów EEPROM 24Cxx może być konfigurowany za pomocą pinów adresowych A0, A1 i A2. Tabela poniżej przedstawia adresy w zależności od ich stanu.

Korzystam z modułu, na którym piny A0, A1 i A2 są zwarte do masy. Adres urządzenia będzie w takim wypadku wynosił 0xA0 (0b10100000).

Wszystkie projekty z kursu dostępne są w moim repozytorium GitHub.

[PROGRAM] Zapis i odczyt danych z pamięci EEPROM w trybie przerwań

Wiemy już jakie przerwania przydadzą nam się w podstawowej komunikacji oraz znamy zasadę działania pamięci EEPROM. Czas zatem na przykład.

Inicjalizacja

Inicjalizacja I2C będzie wyglądała bardzo podobnie jak w przypadku zwykłej komunikacji w trybie polling. Konfigurujemy piny SDA i SCL w trybie funkcji alternatywnej jako wyjścia Open-Drain.

LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);

LL_GPIO_SetPinSpeed(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinOutputType(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, LL_GPIO_OUTPUT_OPENDRAIN);
LL_GPIO_SetPinPull(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetAFPin_8_15(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, LL_GPIO_AF_6);
LL_GPIO_SetPinMode(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, LL_GPIO_MODE_ALTERNATE);

LL_GPIO_SetPinOutputType(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, LL_GPIO_OUTPUT_OPENDRAIN);
LL_GPIO_SetPinPull(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, LL_GPIO_PULL_NO);
LL_GPIO_SetPinSpeed(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, LL_GPIO_SPEED_FREQ_VERY_HIGH);
LL_GPIO_SetAFPin_8_15(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, LL_GPIO_AF_6);
LL_GPIO_SetPinMode(I2C1_SDA_GPIO_Port, I2C1_SDA_Pin, LL_GPIO_MODE_ALTERNATE);

Następnie włączamy taktowanie I2C.

LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);

Analogicznie jak w poprzedniej części, konfigurujemy filtry, rejestr TIMINGR, oraz tryb pracy I2C przy wyłączonym układzie peryferyjnym. Następnie włączamy I2C.

LL_I2C_Disable(i2c);
LL_I2C_ConfigFilters(i2c, LL_I2C_ANALOGFILTER_ENABLE, 0x00);
LL_I2C_SetTiming(i2c, 0x10707DBC);
LL_I2C_EnableClockStretching(i2c);
LL_I2C_SetMode(i2c, LL_I2C_MODE_I2C);
LL_I2C_Enable(i2c);

Aby skorzystać z przerwań, włączamy je w kontrolerze NVIC. Przerwania w rejestrach I2C włączymy trochę później w momencie transmisji danych.

NVIC_SetPriority(I2C1_IRQn, 0);
NVIC_EnableIRQ(I2C1_IRQn);

Wysyłanie danych

Transmisję rozpoczynamy w identyczny sposób, jak w przypadku trybu polling. Ustawiamy adres urządzenia slave oraz ilość danych do wysłanie. Korzystamy z trybu AUTOEND, aby wygenerować po transmisji sygnał STOP.

LL_I2C_HandleTransfer(i2c, slave_addr, LL_I2C_ADDRSLAVE_7BIT, (uint32_t)(size+1), LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE);

Adres komórki pamięci EEPROM wyślemy “ręcznie”. W przerwaniach będziemy wysyłali dane, które chcemy w pamięci zapisać.

while(LL_I2C_IsActiveFlag_TXIS(i2c) == 0)
	;
LL_I2C_TransmitData8(i2c, reg_addr);

Teraz włączamy przerwania od wysłania danych oraz od sygnału STOP.

LL_I2C_EnableIT_TX(I2C1);
LL_I2C_EnableIT_STOP(I2C1);

Jak już wspomniałem, wysyłanie danych będzie odbywało się w obsłudze przerwania. W funkcji I2C1_IRQHandler() sprawdzamy flagę od TX oraz upewniamy się, że przerwania od wysyłania są włączone (czyli jesteśmy w trybie wysyłania). Jeżeli wszystko się zgadza, przesyłamy bajt danych, zwiększamy wskaźnik danych oraz zmniejszamy licznik przechowujący ilość danych do wysłania. Dodatkowo sprawdzamy czy był to ostatni bajt – jeżeli tak to wyłączamy przerwania.

if(LL_I2C_IsActiveFlag_TXIS(I2C1) && LL_I2C_IsEnabledIT_TX(I2C1))
{
	if(tx_buffer.count > 0)
	{
		LL_I2C_TransmitData8(i2c, *tx_buffer.data_ptr);
		tx_buffer.data_ptr++;
		tx_buffer.count--;
	}
	if(tx_buffer.count <= 0)
	{
		LL_I2C_DisableIT_TX(I2C1);
	}
}

Odbieranie danych

Odbieranie danych będzie wyglądało podobnie. Najpierw wysyłamy jeden bajt z adresem komórki w pamięci EEPROM. Nie chcemy generować sygnału STOP po zakończeniu wysyłania.

LL_I2C_HandleTransfer(i2c, slave_addr, LL_I2C_ADDRSLAVE_7BIT, 1, LL_I2C_MODE_SOFTEND, LL_I2C_GENERATE_START_WRITE);

while(LL_I2C_IsActiveFlag_TXIS(i2c) == 0)
	;
LL_I2C_TransmitData8(i2c, reg_addr);

Czekamy na zakończenie transferu.

while(LL_I2C_IsActiveFlag_TC(i2c) == 0)
	;

Teraz inicjalizujemy odbiór. Tym razem chcemy, aby po odebraniu wszystkich danych został wygenerowany sygnał STOP.

LL_I2C_HandleTransfer(i2c, slave_addr, LL_I2C_ADDRSLAVE_7BIT, (uint32_t)size, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_READ);

Na koniec włączamy przerwania od RX oraz STOP.

LL_I2C_EnableIT_RX(I2C1);
LL_I2C_EnableIT_STOP(I2C1);

Odbieranie danych realizujemy w funkcji obsługi przerwania I2C1_IRQHandler();

Sprawdzamy flagę RX. Jeżeli zgadza się tryb pracy, odbieramy daną i zwiększamy wskaźnik. Jeżeli odebrane zostały już wszystkie dane, wyłączamy przerwanie.

if(LL_I2C_IsActiveFlag_RXNE(I2C1) && LL_I2C_IsEnabledIT_RX(I2C1))
{
	if(rx_buffer.count > 0)
	{
		*rx_buffer.data_ptr = LL_I2C_ReceiveData8(i2c);
		rx_buffer.data_ptr++;
		rx_buffer.count--;
	}
	if(rx_buffer.count <= 0)
	{
		LL_I2C_DisableIT_RX(I2C1);
	}
}

Jeżeli potrzebujemy wykonać jeszcze jakieś czynności po zakończeniu transmisji (po sygnale STOP), możemy skorzystać jeszcze z tego przerwania.

if(LL_I2C_IsActiveFlag_STOP(I2C1) && LL_I2C_IsEnabledIT_STOP(I2C1))
{
	LL_I2C_ClearFlag_STOP(I2C1);
	LL_I2C_DisableIT_STOP(I2C1);
}

Test komunikacji

W funkcji main() przetestujemy działanie komunikacji. Przed pętlą główną inicjalizujemy interfejs I2C i wysyłamy 8 bajtów danych pod adres początkowy 0x00. W ten sposób umieścimy dane pod adresami 0x00-0x07.

i2c_init();

static uint8_t data_w[8] = {0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18};
i2c_reg_write_it(EEPROM_ADDR, 0x00, data_w, sizeof(data_w));

Następnie w pętli głównej co 1s będziemy odczytywali dane spod adresu 0x00.

static uint8_t data_r[8] = {0};
i2c_reg_read_it(EEPROM_ADDR, 0x00, data_r, sizeof(data_r));

Możemy uruchomić przykład i podejrzeć tablicę data_r[] w oknie Live Expression. Zobaczymy, że odczytaliśmy zapisane dane.

Chciałbyś otrzymywać na bieżąco informacje o nowych artykułach z kursu? Zapisz się do newslettera!

TO NIE TYLKO MAIL Z INFORMACJĄ O NOWEJ LEKCJI, ALE TAKŻE DODATKOWE MATERIAŁY. NIE PRZEGAP NOWEJ TREŚCI I DODATKOWYCH BONUSÓW. PRZEJDŹ DO STRONY KURSU I PODAJ SWÓJ ADRES E-MAIL. NIE ZAPOMNIJ POTWIERDZIĆ CHĘCI DOŁĄCZENIA W PIERWSZEJ WIADOMOŚCI!
Repozytorium GitHub

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *