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:
- Otwórz projekt w STM32CubeMX dla np. STM32F401RE (Nucleo-64).
- 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.
- W Clock Configuration ustaw zegary (np. HCLK 84 MHz dla STM32F4) tak, by generator timingów I2C był poprawnie wyliczony.
- W NVIC włącz przerwania I2C (opcjonalnie, jeśli planujesz tryb nieblokujący/DMA).
- 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*)¤t_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(folderRepositories).