DS18B20 to popularny cyfrowy czujnik temperatury komunikujący się z mikrokontrolerami STM32 przez protokół 1‑Wire. Minimalna liczba przewodów, wysoka dokładność i łatwa integracja czynią go idealnym do projektów embedded oraz IoT.

Wprowadzenie do czujnika DS18B20

DS18B20 (Maxim Integrated) oferuje programowalną rozdzielczość od 9 do 12 bitów oraz dokładność ±0,5°C w zakresie od −10°C do +85°C (zakres pracy: −55°C do +125°C). Interfejs 1‑Wire wymaga tylko jednej linii danych i masy, co oszczędza piny GPIO w STM32.

Główne zalety DS18B20

Najważniejsze argumenty przemawiające za wyborem DS18B20 to:

  • interfejs 1‑Wire – jedna linia do komunikacji i opcjonalnego zasilania;
  • wiele sensorów na jednej magistrali – łatwe skalowanie i adresowanie (multidrop);
  • brak zewnętrznego ADC – czujnik dostarcza gotowe dane cyfrowe;
  • parasite power – możliwość zasilania z linii danych, redukcja komponentów.

Zastosowania w robotyce i elektronice

DS18B20 sprawdza się w licznych scenariuszach pomiarowych:

  • monitorowanie temperatury w systemach HVAC, stacjach pogodowych i IoT,
  • robotyka: detekcja przegrzania silników, kontrola chłodzenia autonomicznych urządzeń,
  • logowanie danych środowiskowych w projektach embedded.

Poniżej zestawiono najważniejsze parametry techniczne czujnika:

Cecha Opis
Zakres temperatur −55°C do +125°C
Dokładność ±0,5°C (−10°C do +85°C)
Rozdzielczość 9–12 bitów (konfigurowalna)
Szybkość Standard: 16,3 kb/s; Overdrive: 142 kb/s

Protokół 1‑Wire – podstawy komunikacji

1‑Wire to protokół półdupleksowy, w którym master (STM32) steruje linią danych z rezystorem podciągającym 4,7 kΩ do VDD (3,3 V lub 5 V). Linia spoczywa w stanie wysokim; master ściąga ją do stanu niskiego, aby generować sloty czasowe i wysyłać bity. Precyzyjne timingi w mikrosekundach są krytyczne dla poprawnej komunikacji.

Kluczowe sekwencje

  1. Reset i detekcja obecności – master ściąga linię do stanu niskiego na co najmniej 480 µs, następnie ją zwalnia; czujnik odpowiada impulsem niskim (60–240 µs).
  2. Zapis bitu – warianty czasowe: bit 1 – niski przez ~1–5 µs, następnie zwolnienie linii do końca slotu (~60 µs); bit 0 – niski przez ~60 µs, następnie zwolnienie.
  3. Odczyt bitu – master ściąga linię do stanu niskiego na ~2–5 µs, zwalnia ją i próbuje w okolicach 12–15 µs od początku slotu.

Nie używaj HAL_Delay do timingów mikrosekundowych – ma rozdzielczość 1 ms; skorzystaj z DWT lub timera.

Podłączenie sprzętowe

Podstawowa konfiguracja

Do uruchomienia jednej magistrali 1‑Wire potrzebujesz:

  • data (DQ) – pin GPIO STM32 (np. PA1) z rezystorem 4,7 kΩ do 3,3 V,
  • gnd – masa,
  • vdd – 3,3–5,5 V (tryb normalny) lub niepodłączone (tryb parasite).

Przykład dla STM32F103C6:

  • pa1 → DQ (konfiguracja open‑drain),
  • pc13 → LED (opcjonalnie),
  • uart (PA9/PA10) do debugowania.

W trybie parasite power czujnik czerpie prąd z linii danych podczas konwersji. Przy długich przewodach stosuj ekranowane kable o niskiej pojemności i rozważ niższą prędkość.

Oprogramowanie w STM32CubeIDE

Skonfiguruj pin 1‑Wire jako GPIO open‑drain (bez wewnętrznego podciągania), a dokładne opóźnienia mikrosekundowe realizuj z użyciem DWT lub timera. Stała konfiguracja open‑drain pozwala bezpiecznie „zrzucać” linię do zera i zwalniać ją, powierzając stan wysoki rezystorowi podciągającemu.

Generowanie precyzyjnych opóźnień

Poniższy przykład inicjalizuje i wykorzystuje licznik DWT (Data Watchpoint and Trace) do opóźnień w mikrosekundach:


void delay_us_dwt_init(void) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
DWT->CYCCNT = 0;
}

void delay_us_dwt(uint32_t us) {
uint32_t start = DWT->CYCCNT;
uint32_t delayTicks = us * (SystemCoreClock / 1000000U);
while ((DWT->CYCCNT - start) < delayTicks) {;}
}

Przy 16 MHz rozdzielczość opóźnień sięga poniżej 1 µs.

Podstawowe funkcje 1‑Wire

Najpierw zainicjalizuj pin magistrali jako wyjście typu open‑drain:


void onewire_gpio_init(void) {
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = ONE_WIRE_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; // open-drain
GPIO_InitStruct.Pull = GPIO_NOPULL; // zewnętrzny 4,7 kΩ
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(ONE_WIRE_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_SET); // zwolnij linię
}

Implementacja resetu, zapisu i odczytu (bit‑banging) z zachowaniem slotów czasowych:


// Reset i wykrycie obecności
uint8_t onewire_reset(void) {
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_RESET); // niski >= 480 µs
delay_us_dwt(480);
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_SET); // zwolnienie
delay_us_dwt(70);
uint8_t presence = (HAL_GPIO_ReadPin(ONE_WIRE_PORT, ONE_WIRE_PIN) == GPIO_PIN_RESET);
delay_us_dwt(410);
return presence; // 1 = obecny, 0 = brak
}

static inline void onewire_write_bit(uint8_t b) {
if (b) {
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_RESET);
delay_us_dwt(3); // ~1–5 µs
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_SET);
delay_us_dwt(60); // do końca slotu
} else {
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_RESET);
delay_us_dwt(60); // ~60 µs niski
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_SET);
delay_us_dwt(5);
}
}

static inline uint8_t onewire_read_bit(void) {
uint8_t bit;
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_RESET);
delay_us_dwt(3);
HAL_GPIO_WritePin(ONE_WIRE_PORT, ONE_WIRE_PIN, GPIO_PIN_SET);
delay_us_dwt(10); // próbkowanie ~12–15 µs
bit = (HAL_GPIO_ReadPin(ONE_WIRE_PORT, ONE_WIRE_PIN) == GPIO_PIN_SET);
delay_us_dwt(50); // do końca slotu
return bit;
}

void onewire_write_byte(uint8_t data) {
for (int i = 0; i < 8; i++) {
onewire_write_bit((data >> i) & 0x01);
}
}

uint8_t onewire_read_byte(void) {
uint8_t data = 0;
for (int i = 0; i < 8; i++) {
data |= (onewire_read_bit() << i);
}
return data;
}

Odczyt temperatury

Standardowa sekwencja odczytu temperatury wygląda następująco:

  1. Reset i potwierdzenie obecności czujnika.
  2. Skip ROM (0xCC) + Convert T (0x44) – rozpoczęcie konwersji (do 750 ms przy 12 bitach).
  3. Reset + Skip ROM (0xCC) + Read Scratchpad (0xBE).
  4. Odczytaj 2 bajty (LSB, MSB) i przelicz temperaturę: raw/16.0f.


float ds18b20_read_temp_c(void) {
if (!onewire_reset()) return -1000.0f; // błąd
onewire_write_byte(0xCC); // Skip ROM
onewire_write_byte(0x44); // Convert T
HAL_Delay(750); // czas konwersji (ms)

if (!onewire_reset()) return -1000.0f;
onewire_write_byte(0xCC);
onewire_write_byte(0xBE); // Read Scratchpad

uint8_t lsb = onewire_read_byte();
uint8_t msb = onewire_read_byte();
int16_t raw = (int16_t)((msb << 8) | lsb);
return (float)raw / 16.0f;
}

Zawsze weryfikuj CRC ramki (9. bajt scratchpada), aby mieć pewność integralności danych.

Alternatywne metody implementacji

UART w trybie jednoprzewodowym

STM32 UART w trybie półdupleksowym może emulować 1‑Wire, automatyzując timing slotów i ograniczając obciążenie CPU.

Biblioteki gotowe

Dostępne są gotowe biblioteki HAL/LL dla 1‑Wire (np. na GitHub), a także implementacje z wykorzystaniem DMA w celu dalszego odciążenia procesora.

Obsługa wielu sensorów

Do adresowania wielu urządzeń użyj Match ROM (0x55) z unikalnym 64‑bitowym identyfikatorem (odczytanym poleceniem Read ROM 0x33). Na jednej magistrali można bezpiecznie obsłużyć wiele czujników, jeśli zadbasz o topologię, długość przewodów i właściwe podciąganie.

Debugowanie i typowe problemy

W przypadku trudności z komunikacją zwróć uwagę na poniższe wskazówki:

  • brak presence – sprawdź rezystor podciągający 4,7 kΩ, poprawność połączeń i timingi;
  • nieprecyzyjne pomiary – stosuj DWT lub timer zamiast HAL_Delay przy slotach mikrosekundowych;
  • parasite mode – podczas konwersji włącz tzw. strong pull‑up (ok. 1 kΩ) lub tranzystor podciągający linię do VDD.