W projektach robotycznych i elektronicznych mikrokontrolery STM32 z bibliotekami HAL (Hardware Abstraction Layer) oferują potężne narzędzia do komunikacji z urządzeniami zewnętrznymi. Interfejs I2C (Inter-Integrated Circuit) jest jednym z najpopularniejszych protokołów szeregowych, umożliwiającym podłączenie wielu urządzeń na dwóch liniach: SDA (dane) i SCL (zegar). Szczególnie przydatne są pamięci EEPROM, takie jak popularne układy serii 24Cxx, które pozwalają na trwałą pamięć konfiguracji robota, logów sensorów czy kalibracji silników – bez potrzeby programatora po każdym resecie.

Ten praktyczny poradnik krok po kroku pokazuje konfigurację I2C w STM32CubeMX, inicjalizację w HAL, podstawową i zaawansowaną komunikację z DMA oraz gotowe przykłady obsługi EEPROM. Przykłady oparto na rodzinie STM32F4 i nowszych, z myślą o robotach mobilnych, dronach i manipulatorach. Zakładamy znajomość podstaw C i CubeMX.

Podstawy protokołu I2C w kontekście STM32

I2C to protokół master–slave; w typowym układzie STM32 działa jako master. Adresy urządzeń slave są 7- lub 10-bitowe, a popularne prędkości to 100 kHz (Standard), 400 kHz (Fast Mode) oraz wyższe w FM+. W robotyce I2C łączy m.in. IMU (np. MPU6050), wyświetlacze OLED/SSD1306 oraz EEPROM.

Najważniejsze funkcje warstwy HAL dla I2C to:

  • HAL_I2C_Init() – inicjalizacja peryferium i timingów I2C;
  • HAL_I2C_Master_Transmit() / HAL_I2C_Master_Receive() – proste, blokujące transfery w trybie master;
  • HAL_I2C_Mem_Write() / HAL_I2C_Mem_Read() – wygodne do EEPROM i rejestrów, automatycznie wysyłają adres wewnętrzny.

Piny I2C konfiguruj jako Open-Drain i zastosuj rezystory podciągające ok. 4,7 kΩ do 3,3 V na liniach SDA/SCL.

Konfiguracja I2C w STM32CubeMX

Poniższe kroki ułatwią szybkie uruchomienie magistrali I2C:

  1. Otwórz projekt w STM32CubeMX dla np. STM32F401RE (Nucleo-64).
  2. W sekcji Pinout & Configuration wykonaj:
  • I2C1 – wybierz tryb I2C na wybranym kontrolerze;
  • I2C Speed – ustaw Fast Mode (400 kHz) dla szybszej komunikacji;
  • Piny – przypisz PB6 (SCL) i PB7 (SDA) z funkcją alternatywną AF4.
  1. W Clock Configuration ustaw zegary (np. HCLK 84 MHz dla STM32F4) tak, by generator timingów I2C był poprawnie wyliczony.
  2. W NVIC włącz przerwania I2C (opcjonalnie, jeśli planujesz tryb nieblokujący/DMA).
  3. Wygeneruj kod z biblioteką HAL (i FreeRTOS, jeśli robot ma multitasking).

Po wygenerowaniu kodu uchwyt do peryferium znajdziesz jako hi2c1 w pliku main.c.

Podstawowa inicjalizacja i test komunikacji HAL

Po zainicjalizowaniu I2C funkcją MX_I2C1_Init() możesz przetestować połączenie z EEPROM 24C02. W HAL adres urządzenia przekazujemy jako adres 7-bitowy przesunięty w lewo o 1 bit (np. 0x50 << 1 = 0xA0). Przykładowy test zapisu i odczytu wygląda tak:

#include "main.h"

I2C_HandleTypeDef hi2c1;

uint8_t test_data[] = {0x01, 0x02};
uint8_t read_data[2];
HAL_StatusTypeDef status;

/* Zapis do EEPROM pod adres wewnętrzny 0x00 */
status = HAL_I2C_Mem_Write(&hi2c1, 0xA0, 0x00, I2C_MEMADD_SIZE_8BIT, test_data, 2, HAL_MAX_DELAY);
if (status != HAL_OK) {
// Błąd – sprawdź pull-upy, adres slave i timing
}

/* Czas wewnętrznego zapisu (tzw. write cycle time) */
HAL_Delay(5);

/* Odczyt zwrotny i weryfikacja */
status = HAL_I2C_Mem_Read(&hi2c1, 0xA0, 0x00, I2C_MEMADD_SIZE_8BIT, read_data, 2, HAL_MAX_DELAY);

Po każdym zapisie do pamięci EEPROM konieczna jest krótka zwłoka (np. 5 ms) na wewnętrzny cykl zapisu.

Zaawansowana obsługa EEPROM z DMA – automatyzacja dla robotyki

W systemach czasu rzeczywistego blokujące wywołania I2C mogą obciążać procesor. DMA zdejmuje z CPU transfery, pozostawiając czas na sterowanie silnikami, czujniki czy regulację PID.

Zapis do EEPROM z DMA (HAL)

W CubeMX włącz DMA dla kanałów I2C1_TX i (docelowo) I2C1_RX, przypisz odpowiednie strumienie/kanały i przerwania. Poniżej przykład zapisu bloku danych (pilnuj granic strony, np. 64 bajty dla 24C64):

// Bufor danych – np. konfiguracja robota (maks. rozmiar pojedynczej strony EEPROM)
uint8_t eeprom_data[64] = { /* ... */ };
uint16_t mem_addr = 0x0000; // Adres wewnętrzny w EEPROM (dla 24Cxx bywa 8- lub 16-bitowy)
uint16_t size = sizeof(eeprom_data);

// Zapis z użyciem DMA – w HAL są dedykowane funkcje Mem_*_DMA
if (HAL_I2C_Mem_Write_DMA(&hi2c1, 0xA0, mem_addr, I2C_MEMADD_SIZE_16BIT, eeprom_data, size) != HAL_OK) {
// Obsłuż błąd
}

// Callback po zakończeniu transmisji pamięci
void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if (hi2c->Instance == I2C1) {
// Transfer zakończony – odczekaj cykl zapisu i kontynuuj
HAL_Delay(5);
}
}

// Obsługa przerwania DMA – zgodnie z konfiguracją CubeMX
void DMA1_Stream6_IRQHandler(void)
{
HAL_DMA_IRQHandler(hi2c1.hdmatx);
}

Odczyt z EEPROM z DMA (HAL)

Analogicznie, do szybkiego odczytu bloków danych użyj funkcji DMA dla pamięci:

uint8_t recv_buffer[64];
uint16_t mem_addr = 0x0000;
uint16_t size = sizeof(recv_buffer);

if (HAL_I2C_Mem_Read_DMA(&hi2c1, 0xA0, mem_addr, I2C_MEMADD_SIZE_16BIT, recv_buffer, size) != HAL_OK) {
// Obsłuż błąd
}

void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
if (hi2c->Instance == I2C1) {
// Dane gotowe w recv_buffer
}
}

// Obsługa przerwania DMA dla RX zgodnie z konfiguracją
void DMA1_Stream0_IRQHandler(void)
{
HAL_DMA_IRQHandler(hi2c1.hdmarx);
}

DMA znacząco obniża obciążenie CPU względem trybu blokującego i poprawia responsywność całego systemu. Pamiętaj o wytrzymałości zapisu (endurance) EEPROM – planuj rzadsze, porcjowane zapisy i unikaj niepotrzebnych cykli.

Przykłady praktyczne w robotyce

Zapis konfiguracji robota (PID, kalibracja)

Poniżej prosty przykład strukturyzowanego zapisu ustawień:

typedef struct {
float kp, ki, kd;
uint8_t motor_id;
} RobotConfig_t;

RobotConfig_t config = (RobotConfig_t){1.5f, 0.1f, 0.05f, 1};

HAL_I2C_Mem_Write(&hi2c1, 0xA0, 0x0000, I2C_MEMADD_SIZE_16BIT, (uint8_t*)&config, sizeof(config), HAL_MAX_DELAY);
HAL_Delay(5); // Write cycle time

Logowanie danych sensorów (INA219 → EEPROM)

W robotach monitorujących prąd silników (np. INA219, adres 0x40) najpierw czytaj rejestry czujnika, a następnie buforuj i porcjami zapisuj do EEPROM, aby chronić trwałość pamięci:

uint16_t current_raw = 0;
HAL_I2C_Mem_Read(&hi2c1, 0x40 << 1, 0x04, I2C_MEMADD_SIZE_8BIT, (uint8_t*)&current_raw, 2, HAL_MAX_DELAY); // Dodaj do bufora RAM i zapisuj do EEPROM rzadziej, większymi blokami (z uwzględnieniem granic strony).

Integracja wielu urządzeń na jednej magistrali

Na jednej linii I2C mogą współistnieć: EEPROM (0x50), OLED (0x3C), IMU (0x68) – każdy z unikalnym adresem i własnym protokołem rejestrowym.

Debugowanie i typowe błędy

Najczęstsze problemy i ich rozwiązania zestawiliśmy w tabeli:

Problem Przyczyna Rozwiązanie
HAL_TIMEOUT brak ACK od slave sprawdź adres (0xA0 vs 0x50 zależnie od przesunięcia i pinów A0–A2), poprawne pull‑upy 4,7 kΩ, integralność przewodów,
Bus Busy poprzedni transfer niezamknięty, zawieszony slave wykonaj reset magistrali lub ponowną inicjalizację: HAL_I2C_DeInit(&hi2c1); HAL_I2C_Init(&hi2c1);,
DMA Error nieprawidłowy strumień/kanał DMA w CubeMX przypisz dedykowane strumienie RX/TX do I2C1 i włącz przerwania DMA,
EEPROM corrupt przekroczenie granicy strony lub brak opóźnienia po zapisie pisz porcjami ≤ rozmiar strony i dodaj HAL_Delay(5) między porcjami,
Niska prędkość niewłaściwy timing lub zbyt duże pojemności linii ustaw Fast Mode (400 kHz) i zweryfikuj parametry timingu dla aktualnego HCLK; skróć przewody, zmniejsz pojemność.

Do diagnozy użyj analizatora stanów logicznych lub oscyloskopu na liniach SDA/SCL i włącz logi przez UART/SWO. W aplikacjach robotycznych warto dodać watchdog magistrali (np. cykliczne HAL_I2C_DeInit() i ponowne HAL_I2C_Init() w razie wykrycia błędu).

Porównanie HAL vs LL/DMA dla EEPROM

Poniższa tabela pomaga szybko dobrać odpowiednią metodę transmisji:

Metoda Zalety Wady Typowe zastosowanie
HAL (polling) prosty kod blokuje CPU prototypy, małe porcje danych.
HAL (DMA) niskie obciążenie CPU konieczna konfiguracja DMA/IRQ okresowe zapisy/odczyty bloków, konfigi, buforowane logi.
LL (niskopoziomowy) maksymalna kontrola i wydajność większa złożoność kodu aplikacje twardego real-time (np. drony).

Praktyczne wskazówki dla projektów robotycznych

Aby zwiększyć niezawodność i trwałość systemu, zwróć uwagę na poniższe kwestie:

  • bezpieczeństwo danych – zapisuj krytyczne informacje (np. stan baterii) z CRC i weryfikuj sumę przy odczycie;
  • operacje wielobajtowe – respektuj rozmiar strony EEPROM (np. 64 bajty) i dziel transfery na porcje;
  • FreeRTOS – kolejkuj żądania I2C i obsługuj wyniki w callbackach DMA, unikając blokad w wątkach;
  • przykłady STM – skorzystaj z gotowych projektów w pakietach STM32Cube (folder Repositories).