Ultrasonic Eyes: интерактивные «глаза» на 8×8 LED-матрицах и ультразвуке
Компоненты и материалы
| Ультразвуковой датчик HC-SR04 | 2 | |
| Arduino Nano R3 | 1 | |
| Фоторезистор (LDR) | 1 | Автояркость |
| LED-матрица 8×8 MAX7219 | 2 | Каскадируемые (DIN/DOUT) |
| Половинчатый бредоборд | 1 | |
| Перемычки | 1 набор | |
| Резистор 330 Ом | 1 | Для делителя с LDR |
Программное обеспечение
- Arduino IDE
О проекте
Весёлый и немного жуткий проект: два «глаза» на 8×8 матрицах следят за движением перед собой (два HC-SR04), моргают случайно, адаптируют яркость к освещению. Идеально для полки в гостиной или офиса.
Подключение
Питание и земля
Nano на бредоборде, USB висит сбоку. GND и 3.3V (или 5V) к шинам бредоборда, перемычки между шинами по бокам.
HC-SR04 (×2)
- VCC → +, GND → -
- Sensor 1: TRIG → D2, ECHO → D3
- Sensor 2: TRIG → D4, ECHO → D5
MAX7219 матрицы (×2, каскад)
- Матрица 1 (вход): VCC→+, GND→-, CS→D10, MOSI→D11, SCK→D13 (Hardware SPI!)
- Матрица 2 (выход 1 → вход 2): GND→GND, VCC→VCC, CS→CS, MOSI→MOSI, SCK→SCK
Важно: код использует Hardware SPI (D11=MOSI, D13=SCK на Nano). На других платах проверьте SPI-пины.
LDR + 330Ω
LDR между + и делителем: один ножка → +, другая → A5 и через 330Ω → GND. A5 — аналоговый вход для измерения освещённости (0–255).
Алгоритм работы
- Каждые 250 мс: опрос левого датчика (медиана 5 замеров), пауза 500 мс, опрос правого.
- Если разница расстояний < 25 см или оба 0 → глаза смотрят прямо.
- Если левый датчик дальше правого → глаза смотрят вправо.
- Если правый дальше левого → глаза смотрят влево.
- Случайная моргалка: каждые 2–10 сек одинарная или двойная (вероятность 6/13).
- Яркость матриц пропорциональна показаниям LDR (0–MAX_INTENSITY).
Код (C/C++)
Библиотеки: MD_MAX72xx (матрицы), NewPing (HC-SR04). Установка через Library Manager.
#include <MD_MAX72xx.h>
#include <NewPing.h>
#define CS_PIN 10
#define MAX_DEVICES 2
#define LIGHT A5
#define t1 2; #define e1 3
#define t2 4; #define e2 5
#define maxDist 400
MD_MAX72XX mx = MD_MAX72XX(CS_PIN, MAX_DEVICES);
NewPing eyeL(t1, e1, maxDist);
NewPing eyeR(t2, e2, maxDist);
int currentState = -1; // 0=forward, 1=right, 2=left
float nextBlink = 0;
float lightAmount = 0;
// Спрайты глаз 8x8 (COL_SIZE=8)
uint8_t eye_forward[8] = { 0b00111100, 0b01000010, 0b01011010, 0b10101101, 0b10111101, 0b10011001, 0b01000010, 0b00111100 };
uint8_t eye_right[8] = { 0b00111100, 0b01000010, 0b01110010, 0b11011001, 0b11111001, 0b10110001, 0b01000010, 0b00111100 };
uint8_t eye_left[8] = { 0b00111100, 0b01000010, 0b01001110, 0b10010111, 0b10011111, 0b10001101, 0b01000010, 0b00111100 };
uint8_t eye_blink[8] = { 0b00000000, 0b00111100, 0b01111110, 0b11111111, 0b10111101, 0b11000011, 0b01111110, 0b00111100 };
void setup() {
mx.begin();
pinMode(t1, OUTPUT); pinMode(e1, INPUT);
pinMode(t2, OUTPUT); pinMode(e2, INPUT);
digitalWrite(t1, LOW); digitalWrite(t2, LOW);
pinMode(LIGHT, INPUT);
ShowEye_Forward(); currentState = 0;
nextBlink = millis() + 1000;
}
void loop() {
lightAmount = analogRead(LIGHT);
lightAmount = (lightAmount / 255.0) * MAX_INTENSITY;
mx.control(MD_MAX72XX::INTENSITY, lightAmount);
int distance1 = eyeL.ping_median(5);
delay(500);
int distance2 = eyeR.ping_median(5);
if (nextBlink < millis()) {
nextBlink = millis() + random(2000, 10000);
if (random(1, 13) <= 6) ShowEye_Blink_Dbl(); else ShowEye_Blink();
delay(250); return;
}
float diff = distance2 - distance1;
if (abs(diff) < 250 || (distance1 == 0 && distance2 == 0)) { ShowEye_Forward(); currentState = 0; }
else if (distance2 < distance1 && distance1 > 0) { ShowEye_Right(); currentState = 1; }
else if (distance1 < distance2 && distance2 > 0) { ShowEye_Left(); currentState = 2; }
delay(250);
}
void ShowEye_Right() { mx.clear(); mx.control(MD_MAX72XX::INTENSITY, lightAmount); for (uint8_t r=0;r<8;r++) { mx.setColumn(r, eye_right[r]); mx.setColumn(r+8, eye_right[r]); } }
void ShowEye_Left() { mx.clear(); mx.control(MD_MAX72XX::INTENSITY, lightAmount); for (uint8_t r=0;r<8;r++) { mx.setColumn(r, eye_left[r]); mx.setColumn(r+8, eye_left[r]); } }
void ShowEye_Forward() { mx.clear(); mx.control(MD_MAX72XX::INTENSITY, lightAmount); for (uint8_t r=0;r<8;r++) { mx.setColumn(r, eye_forward[r]); mx.setColumn(r+8, eye_forward[r]); } }
void ShowEye_Blink() {
mx.clear(); mx.control(MD_MAX72XX::INTENSITY, lightAmount);
for (uint8_t r=0;r<8;r++) { mx.setColumn(r, eye_blink[r]); mx.setColumn(r+8, eye_blink[r]); }
delay(150);
if (currentState==0) ShowEye_Forward(); else if (currentState==1) ShowEye_Right(); else ShowEye_Left();
}
void ShowEye_Blink_Dbl() {
ShowEye_Blink(); delay(75);
if (currentState==0) ShowEye_Forward(); else if (currentState==1) ShowEye_Right(); else ShowEye_Left();
delay(75); ShowEye_Blink();
}
3D-корпус
Две детали (основание и крышка), печать любым принтером, без спец-настроек. Файлы в разделе CAD.
Схема (Fritzing)
Файл ultrasoniceyes_final_Th84xpUyB2.fzz и PNG в разделе Schematics.
Производственный процесс
- Игра «UFO Attack» на Arduino UNO и матрице MAX7219 — Собираем аркаду с джойстиком Atari
- Умные шторы: автоматическое управление светом на Arduino с солнечной энергией
- 3D RGB Arduclock — Bluetooth-часы на адресных LED с RTC
- Мониторинг состояния реки: Arduino-система контроля качества воды
- Строим Iron Man на Arduino: компоненты, сенсоры и пошаговый гайд
- Простой ИК-датчик препятствий на Arduino: ИК-светодиод и фотодиод
- Интерактивная игра с джойстиком и LED-обратной связью на Arduino
- Find Me: Умный поисковик вещей на Arduino и Bluetooth
- DIY-контроллер увлажнителя на Arduino с реле: безопасная работа с высоким напряжением
- Маленький ровер на Arduino: собираем автономного робота своими руками