HC‑SR04 — ультразвуковой датчик расстояния. Он излучает ультразвуковой импульс и измеряет время до прихода эха. Применяется в робототехнике, системах парковки, бесконтактных измерителях уровня/расстояния.
Характеристики (классические для HC‑SR04):
В уроке сделаем живой дисплей, который постоянно показывает расстояние в сантиметрах. Добавим калибровку, чтобы учесть особенности конкретного датчика, температуры и геометрии макета.
💡 Для визуализации соединений можно использовать Fritzing. На схеме отобразим модуль HC‑SR04, дисплей I2C и кнопку «CAL».
HC‑SR04 → Arduino
LCD1602 I2C → Arduino
Кнопка «CAL» (опционально)
INPUT_PULLUP
), второй контакт → GNDПримечание: ECHO выдаёт 5 В — для Arduino Uno это нормально. Для плат на 3.3 В потребуется делитель уровня.
CAL_REF_CM
, calFactor
, offsetCm
.CAL_REF_CM
, нажмите кнопку CAL или отправьте в Serial букву c
— скетч посчитает новый calFactor
./*
* HC-SR04 + LCD1602 I2C — Живой дисплей расстояния с калибровкой
* Пины: TRIG=D8, ECHO=D9, LCD I2C=0x27 (SDA=A4, SCL=A5), опционально CAL_BTN=D7 (кнопка на GND)
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
// === ПОДКЛЮЧЕНИЯ ===
const uint8_t PIN_TRIG = 8;
const uint8_t PIN_ECHO = 9;
const uint8_t PIN_CAL = 7; // опционально кнопка калибровки (замыкает на GND)
const uint8_t LCD_ADDR = 0x27;
LiquidCrystal_I2C lcd(LCD_ADDR, 16, 2);
// === БАЗОВЫЕ НАСТРОЙКИ ===
const float SOUND_CM_PER_US = 0.0343f; // скорость звука ~0.0343 см/мкс при ~20°C
const uint16_t MEASURE_PERIOD_MS = 60; // период опроса датчика
const uint16_t PULSE_TIMEOUT_US = 25000; // таймаут ожидания эха (~4м)
// === КАЛИБРОВКА ===
// Известная опорная дистанция для автокалибровки (см)
const float CAL_REF_CM = 20.0f;
// Коэффициент и смещение (могут быть сохранены в EEPROM)
float calFactor = 1.00f; // множитель
float offsetCm = 0.0f; // добавочное смещение (см)
// Адреса EEPROM для хранения калибровки
const int EE_ADDR_MAGIC = 0;
const int EE_ADDR_FACTOR = 4;
const int EE_ADDR_OFFSET = 8;
const uint32_t EE_MAGIC = 0xA1C01AEE; // маркер валидности
// === ФИЛЬТРАЦИЯ ===
const uint8_t AVG_WINDOW = 8;
float ring[AVG_WINDOW];
uint8_t ringPos = 0;
bool ringFilled = false;
// === СЛУЖЕБНЫЕ ===
unsigned long lastMeasureMs = 0;
void setup() {
pinMode(PIN_TRIG, OUTPUT);
pinMode(PIN_ECHO, INPUT);
pinMode(PIN_CAL, INPUT_PULLUP); // кнопка на GND
digitalWrite(PIN_TRIG, LOW);
Serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.setCursor(0,0);
lcd.print("HC-SR04 DIST cm");
lcd.setCursor(0,1);
lcd.print("Init...");
delay(500);
// Попытка загрузить калибровку из EEPROM
loadCalibration();
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Dist:");
printCalLine();
Serial.println(F("=== HC-SR04 + LCD — started ==="));
Serial.print(F("Calibration: factor=")); Serial.print(calFactor, 4);
Serial.print(F(" offset=")); Serial.println(offsetCm, 2);
Serial.println(F("Type 'c' in Serial for autocalibration at known distance."));
}
void loop() {
// Обработка автокалибровки по кнопке
if (digitalRead(PIN_CAL) == LOW) {
autocalibrate();
delay(300); // антидребезг
}
// Обработка автокалибровки по Serial
if (Serial.available()) {
char ch = (char)Serial.read();
if (ch == 'c' || ch == 'C') {
autocalibrate();
}
}
// Периодический опрос датчика
unsigned long now = millis();
if (now - lastMeasureMs >= MEASURE_PERIOD_MS) {
lastMeasureMs = now;
float raw = measureCm();
float cm = applyCalibration(raw);
float avg = addAndAvg(validOrNaN(cm));
// Вывод на дисплей
lcd.setCursor(6,0);
if (isnan(avg)) {
lcd.print(" --- "); // нет эха/вне зоны
} else {
printCm(avg);
}
}
}
// === Измерение расстояния, см ===
float measureCm() {
// Триггерный импульс 10 мкс
digitalWrite(PIN_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(PIN_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(PIN_TRIG, LOW);
// Длительность HIGH на ECHO
unsigned long duration = pulseIn(PIN_ECHO, HIGH, PULSE_TIMEOUT_US);
if (duration == 0) {
// таймаут — нет эха
return NAN;
}
// Расстояние туда-обратно -> делим на 2
float cm = (duration * SOUND_CM_PER_US) / 2.0f;
return cm;
}
// === Применение калибровки ===
float applyCalibration(float cm) {
if (isnan(cm)) return NAN;
return cm * calFactor + offsetCm;
}
// === Кольцевой буфер и среднее ===
float addAndAvg(float cm) {
if (isnan(cm)) {
// Добавлять NAN не будем — просто вернём NAN,
// но окно не портим (оставляем предыдущие значения)
return NAN;
}
ring[ringPos++] = cm;
if (ringPos >= AVG_WINDOW) { ringPos = 0; ringFilled = true; }
uint8_t n = ringFilled ? AVG_WINDOW : ringPos;
float sum = 0.0f;
for (uint8_t i=0; i<n; i++) sum += ring[i];
return (n > 0) ? (sum / n) : NAN;
}
float validOrNaN(float v) { return v; }
// === Автокалибровка по опорной дистанции ===
void autocalibrate() {
// Делаем серию быстрых измерений и усредняем
const uint8_t N = 12;
float s = 0; uint8_t k = 0;
for (uint8_t i=0; i<N; i++) {
float raw = measureCm();
if (!isnan(raw)) { s += raw; k++; }
delay(20);
}
if (k == 0) {
Serial.println(F("[CAL] No echo. Calibration aborted."));
return;
}
float rawAvg = s / k;
float newFactor = CAL_REF_CM / rawAvg; // подгоняем множитель под эталон
calFactor = newFactor;
// offset оставим как есть; при желании можно автооценивать разницу
saveCalibration();
printCalLine();
Serial.print(F("[CAL] rawAvg=")); Serial.print(rawAvg, 2);
Serial.print(F(" -> factor=")); Serial.print(calFactor, 4);
Serial.print(F(" offset=")); Serial.println(offsetCm, 2);
}
// === EEPROM: сохранить/загрузить ===
void saveCalibration() {
EEPROM.put(EE_ADDR_MAGIC, EE_MAGIC);
EEPROM.put(EE_ADDR_FACTOR, calFactor);
EEPROM.put(EE_ADDR_OFFSET, offsetCm);
}
void loadCalibration() {
uint32_t magic; EEPROM.get(EE_ADDR_MAGIC, magic);
if (magic == EE_MAGIC) {
EEPROM.get(EE_ADDR_FACTOR, calFactor);
EEPROM.get(EE_ADDR_OFFSET, offsetCm);
}
}
// === Утилиты вывода ===
void printCm(float cm) {
// Формат: 000.0см (или 00.0см)
char buf[16];
if (cm < 0) cm = 0;
if (cm > 999.9f) cm = 999.9f;
dtostrf(cm, 6, 1, buf); // ширина 6, 1 знак после запятой
lcd.print(buf);
lcd.print("cm ");
}
void printCalLine() {
lcd.setCursor(0,1);
lcd.print("k="); lcd.print(calFactor, 3);
lcd.print(" b="); lcd.print(offsetCm, 1);
lcd.print(" ");
}
Перед компиляцией установите и подключите:
Дополнительные библиотеки (типа NewPing) не требуются: используем стандартный
pulseIn()
.
k
и b
(коэф. и смещение).CAL_REF_CM
(в нашем случае 20 см) и отправьте c
в Serial → коэффициент обновится.