Дроны используют различные датчики для поддержания стабильности полёта, особенно в условиях недоступности или ненадёжности GPS. Одной из важных функций в данном случае является удержание позиции, при котором дрон остаётся на одном месте, не дрейфуя. Это критически важно для навигации в помещении, зависания в воздухе и точной посадки. Оптические датчики потока, такие как PMW3901, помогают дронам достигать этой цели, отслеживая движение относительно земли.
PMW3901 — это небольшой маломощный оптический датчик потока, который измеряет движение, анализируя рельеф поверхности. Он широко используется в робототехнике, дронах и системах автоматизации для оценки изменений положения без GPS. Это делает его отличным выбором для приложений, требующих точного отслеживания движения. В этой статье мы рассмотрим датчик PMW3901, его технические характеристики и способы его подключения к модулю ESP32. Если вы читаете эту статью, вас, вероятно, также интересуют дроны, поэтому обратите внимание на дрон на основе ESP32 , который можно программировать и управлять с помощью Arduino и Python.
Необходимые компоненты
- Модуль ESP32 (купить на AliExpress).
- Оптический датчик потока PMW3901.
- OLED-дисплей.
Реклама: ООО «АЛИБАБА.КОМ (РУ)» ИНН: 7703380158
Оптический датчик потока PMW3901
PMW3901 — это оптический датчик потока (optical flow sensor), который обнаруживает движение поверхности с помощью интегрированного алгоритма оптического потока. Он работает аналогично датчику компьютерной мыши, отслеживая относительное движение, сравнивая изображения, полученные его миниатюрной камерой. PMW3901 вычисляет поток внутри себя и выдаёт разницу в пикселях между каждым кадром. Сам датчик предоставляет не абсолютные данные о местоположении, а лишь скорость движения по осям X и Y.
Принцип работы PMW3901
Ниже для облегчения понимания представлена блок-схема работы датчика PMW3901.
Основные характеристики датчика
- Тип датчика: оптический датчик потока.
- Рабочее напряжение: VDD: 1,8 — 2,1 В, VDDIO: 1,8 — 3,6 В.
- Интерфейс связи: 4-проводной SPI @ 2 МГц.
- Потребляемый ток (основной режим): 9 мА.
- Потребляемый ток (режим пониженного потребления): 12 мкА.
- Максимальная скорость обнаружения движения: 7,4 рад/с.
- Поле зрения (FoV): ~42°.
- Дальность обнаружения: 80–300 мм (зависит от текстуры поверхности).
- Широкий рабочий диапазон: от 80 мм до бесконечности.
- В процессе установки объектива фокусировка не требуется.
- Регистры данных движения с 16-битным разрешением
- Обнаружение движения Intruupt.
PMW3901 широко используется в дронах, роботах-пылесосах и промышленных системах отслеживания движения. Его компактный размер и поддержка интерфейса SPI позволяют легко интегрировать его во встраиваемые системы, особенно с микроконтроллерами, такими как ESP32.
Разновидности датчика PMW3901
Для датчика PMW3901 доступно несколько плат расширения. Два наиболее популярных модуля:
Модуль Pimoroni PMW3901
- Бренд: Pimoroni.
- Напряжение: 3-5 В.
- Интерфейс: SPI.
- Размер: компактный, легко монтируется.
- Преимущества: встроенный регулятор уровня, встроенная светодиодная подсветка для условий слабого освещения.
Универсальный (Generic) модуль PMW3901
- Напряжение: 3,3 В.
- Интерфейс: SPI.
- Размер: аналогичен модулю Pimoroni, но не имеет встроенного сдвига уровня.
- Плюсы: дешево и легко найти в разных магазинах, дополнительный штифт для сброса настроек.
- Минусы: нет встроенного сдвига уровня, требуется внешний сдвиг уровня при использовании с системами 5 В.
Распиновка модуля Pimoroni PMW3901
VCC — входное напряжение 3-5 В.
CS — выбор чипа (Chip Select).
SCK — синхронизация SPI (SPI Clock).
MOSI — ввод данных SPI.
MISO — вывод данных SPI.
INT — контакт прерывания (Interrupt Pin).
GND — земля (Ground).
Модуль Pimoroni PMW3901 имеет семь контактов. Два из них предназначены для питания, при этом контакт VCC может работать в широком диапазоне напряжений от 3 до 5 В. Четыре контакта предназначены для шины SPI, а оставшийся контакт предназначен для прерывания движения.
Маркировка компонентов модуля Pimoroni PMW3901
В центре модуля расположен датчик движения PMW3901, окруженный другими компонентами. Как видите, модуль Pimoroni оснащен некоторыми дополнительными компонентами для расширения функций. Имеется стабилизатор напряжения, который отвечает за формирование напряжения питания датчика из входного напряжения. Также имеется схема сдвига уровня, которая упрощает взаимодействие модуля с различными микроконтроллерами, независимо от уровня логики. Кроме того, на плате имеются два светодиода для работы в условиях низкой освещенности. Светодиоды можно включать и выключать с помощью соответствующих регистров в PMW3901.
Распиновка универсального модуля PMW3901
VCC — входное напряжение 3,3 В.
GND — земля (Ground).
MOSI — ввод данных SPI.
CLK — синхронизация SPI (SPI Clock).
MISO — вывод данных SPI.
CS — выбор чипа (Chip Select).
RST — контакт сброса.
MOT — контакт прерывания (Interrupt Pin).
VRE — выход внутреннего регулятора PMW3901.
В отличие от модуля Pimoroni, универсальный модуль PMW3901 оснащён двумя дополнительными контактами, благодаря чему общее количество контактов равно девяти. Первые два контакта предназначены для питания, включая контакт VCC, принимающий напряжение 3,3 В, и контакт заземления. Далее следуют четыре контакта SPI и контакт сброса. Контакт, обозначенный как MOT, используется для прерывания движения, а последний контакт, обозначенный как VRE, подключен к внутреннему регулятору напряжения модуля.
Маркировка компонентов универсального модуля PMW3901
Как видите, стандартный модуль PMW3901 физически немного больше модуля Pimoroni из-за дополнительного вывода и выбора расположения монтажных отверстий. Несмотря на большую площадь, количество компонентов на печатной плате меньше по сравнению с предыдущим модулем, поскольку в нём отсутствует схема сдвига уровня и дополнительные светодиоды для подсветки. Помимо модуля датчика PMW3901, на плате имеется стабилизатор напряжения с малым падением напряжения, отвечающий за линию 1,8 В, а также все блокировочные и фильтрующие конденсаторы. На плате также имеется светодиод питания. Остальные подтягивающие резисторы предназначены для линий SPI и прерывания.
Принципиальная схема модуля оптического датчика потока PMW3901
Эта схема соответствует универсальному модулю PMW3901.
Схема проекта
Схема подключения оптического датчика потока PMW3901 к модулю ESP32 представлена на следующем рисунке. Для подключения мы использовали интерфейс SPI. Поскольку у нас был модуль Pimoroni PWM3901, мы использовали его, но подключение стандартное, и его можно использовать и с другими модулями PMW3901.
Для начала мы подключили питание к модулю. Для этого мы соединили вывод VCC модуля с выводом 3,3 В ESP32, а вывод GND модуля — с выводом GND ESP32. Далее подключите вывод SCK к GPIO18, вывод MISO — к GPIO19, вывод MOSI — к GPIO23, а вывод CS — к GPIO5.
Собранная на макетной плате конструкция проекта выглядит следующим образом.
Код Arduino для просмотра буфера кадра PMW3901
Теперь, когда мы успешно подключили датчик движения PMW3901 к ESP32, давайте посмотрим на код программы. Для начала убедитесь, что в вашей Arduino IDE установлен менеджер плат ESP32 и выбран модуль разработки ESP32 в качестве платы. Затем установите библиотеки Bitcraze PMW3901 и ESPAsyncWebServer из менеджера библиотек Arduino и создайте новый скетч с кодом, приведённым ниже. Не забудьте изменить SSID и пароль Wi-Fi в коде, затем скомпилируйте код и загрузите его на плату ESP32. Прежде чем проверять вывод, давайте посмотрим на код.
С помощью этого кода мы получим данные кадрового буфера с сенсора и отобразим их на веб-странице для удобства просмотра. Хотя обычно нам не нужен кадровый буфер, а нужны только данные о движении, гораздо проще понять принцип работы, имея в поле зрения кадровый буфер. Выходное изображение масштабируется и для удобства восприятия отображается цветовая карта.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <SPI.h> #include <Bitcraze_PMW3901.h> #include <WiFi.h> #include <ESPAsyncWebServer.h> // WiFi credentials const char* ssid = "Your_WiFi_SSID"; const char* password = "Your_WiFi_Password"; // PMW3901 (CS pin on GPIO 5) Bitcraze_PMW3901 flow(5); // Web server on port 80 AsyncWebServer server(80); // Frame buffer (35x35 = 1225 bytes) char frame[35 * 35]; |
Сначала мы подключили все необходимые библиотеки, такие как SPI и Bitcraze_PMW3901 для датчика PMW3901, а также WiFi и ESPAsyncWebServer для подключения и создания веб-сервера. Затем мы создали экземпляры для датчика и веб-сервера, а затем переменную для хранения данных буфера кадра.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
void setup() { Serial.begin(115200); // Initialize PMW3901 if (!flow.begin()) { Serial.println("PMW3901 init failed"); while (1); // halt } flow.enableFrameBuffer(); Serial.println("PMW3901 frame buffer enabled"); // Connect to Wi-Fi WiFi.begin(ssid, password); WiFi.setSleep(false); // improves performance while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.print("Connected. IP address: "); Serial.println(WiFi.localIP()); // Serve HTML UI server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { String html = R"rawliteral( <!DOCTYPE html> <html> <head> <title>PMW3901 Frame Buffer</title> <style> canvas { display: block; margin: auto; image-rendering: pixelated; width: 350px; height: 350px; border: 1px solid black; } body { font-family: sans-serif; background: #111; color: #fff; text-align: center; } #fps { margin: 10px; font-size: 1.2em; } </style> </head> <body> <h1>PMW3901 Frame Buffer</h1> <div id="fps">FPS: 0</div> <canvas id="frameBuffer" width="35" height="35"></canvas> <script> const canvas = document.getElementById('frameBuffer'); const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(35, 35); // Ironhot pseudo-color palette (33 entries) const ironhot = [ [0, 0, 0], [35, 0, 0], [64, 0, 0], [96, 0, 0], [128, 0, 0], [160, 0, 0], [192, 0, 0], [224, 0, 0], [255, 0, 0], [255, 32, 0], [255, 64, 0], [255, 96, 0], [255, 128, 0], [255, 160, 0], [255, 192, 0], [255, 224, 0], [255, 255, 0], [224, 255, 32], [192, 255, 64], [160, 255, 96], [128, 255, 128], [96, 255, 160], [64, 255, 192], [32, 255, 224], [0, 255, 255], [0, 224, 255], [0, 192, 255], [0, 160, 255], [0, 128, 255], [0, 96, 255], [0, 64, 255], [0, 32, 255], [0, 0, 255] ]; let lastTime = performance.now(); async function fetchFrameBuffer() { try { const response = await fetch('/data'); const buffer = await response.arrayBuffer(); const data = new Uint8Array(buffer); for (let i = 0; i < data.length; i++) { const val = data[i]; const colorIndex = Math.floor((val / 255) * (ironhot.length - 1)); const [r, g, b] = ironhot[colorIndex]; const index = i * 4; imageData.data[index] = r; imageData.data[index + 1] = g; imageData.data[index + 2] = b; imageData.data[index + 3] = 255; } ctx.putImageData(imageData, 0, 0); const now = performance.now(); const fps = (1000 / (now - lastTime)).toFixed(1); lastTime = now; document.getElementById('fps').innerText = `FPS: ${fps}`; requestAnimationFrame(fetchFrameBuffer); } catch (err) { console.error("Fetch error:", err); setTimeout(fetchFrameBuffer, 100); } } fetchFrameBuffer(); </script> </body> </html> )rawliteral"; request->send(200, "text/html", html); }); // Binary data response server.on("/data", HTTP_GET, [](AsyncWebServerRequest* request) { flow.readFrameBuffer(frame); request->send_P(200, "application/octet-stream", (uint8_t*)frame, sizeof(frame)); }); server.begin(); } void loop() { // Nothing here } |
Затем, в функции setup() мы инициализировали последовательный порт для отладки и сразу же после этого инициализировали датчик PMW3901. Если инициализация PMW3901 не удалась, ESP32 выведет отладочное сообщение и будет ожидать сброса. В противном случае код продолжит работу, включив буфер кадра и установив соединение по Wi-Fi. При успешном подключении по Wi-Fi IP-адрес ESP32 будет выведен на последовательный монитор, который затем можно использовать для доступа к веб-странице, где мы можем визуализировать буфер кадра с датчика. После успешного подключения по Wi-Fi мы запустим веб-сервер для обслуживания веб-страницы визуализации буфера кадра. Как видите, HTML-код веб-страницы встроен в скетч Arduino. После запуска сервера и доступа к веб-странице через веб-браузер страница автоматически инициирует HTTP-запрос GET для получения данных от ESP32. Поскольку задача веб-сервера выполняется в фоновом режиме, нам не нужен код в функции цикла для предотвращения сброса сторожевого таймера.
Теперь, чтобы просмотреть визуализированные данные, введите IP-адрес, полученный от последовательного монитора, в любой веб-браузер, и вы увидите данные буфера FAM. Имейте в виду, что PMW3901 использует изображения в оттенках серого.
Пример указателя поворота на PMW3901
В этом примере мы добавим OLED-дисплей к нашей существующей схеме и отобразим на нём направление движения. Для этого, следуя схеме ниже, подключите OLED-дисплей к схеме.
Схема подключения оптического датчика потока PMW3901 остаётся прежней. Вывод VCC OLED-дисплея подключён к линии 3,3 В, вывод GND — к заземлению, вывод SCL — к выводу IO22, а вывод SDA — к выводу IO21. Вот как выглядит фактическое подключение:
Код ARDUINO для указателя поворота на основе PMW3901
Помимо уже установленных библиотек, установите библиотеки Adafruit GFX и Adafruit SSD1306 вместе со всеми необходимыми зависимостями. После этого создайте новый скетч с кодом, приведённым ниже.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <SPI.h> #include <Bitcraze_PMW3901.h> // OLED Display setup #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // PMW3901 Sensor setup #define CS_PIN 5 // Chip Select Pin for PMW3901 Bitcraze_PMW3901 flow(CS_PIN); const unsigned char Start_icon [] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xe0, 0x07, 0xfc, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x3f, 0x80, 0x03, 0xf8, 0x00, 0x00, 0x1f, 0xc0, 0x03, 0xf0, 0x00, 0x00, 0x0f, 0xc0, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x0f, 0xc0, 0x00, 0x00, 0x03, 0xf0, 0x0f, 0x80, 0x38, 0x00, 0x01, 0xf0, 0x1f, 0x00, 0xfc, 0x00, 0x00, 0xf8, 0x1f, 0x01, 0xff, 0x00, 0x00, 0x78, 0x3e, 0x01, 0xff, 0x80, 0x00, 0x7c, 0x3c, 0x01, 0xff, 0xe0, 0x00, 0x3c, 0x3c, 0x01, 0xef, 0xf0, 0x00, 0x3e, 0x7c, 0x01, 0xe3, 0xfc, 0x00, 0x3e, 0x78, 0x01, 0xe1, 0xfe, 0x00, 0x3e, 0x78, 0x01, 0xe0, 0x7f, 0x80, 0x1e, 0x78, 0x01, 0xe0, 0x3f, 0xc0, 0x1e, 0x78, 0x01, 0xe0, 0x0f, 0xc0, 0x1e, 0x78, 0x01, 0xe0, 0x07, 0xc0, 0x1e, 0x78, 0x01, 0xe0, 0x07, 0xc0, 0x1e, 0x78, 0x01, 0xe0, 0x0f, 0xc0, 0x1e, 0x78, 0x01, 0xe0, 0x3f, 0xc0, 0x1e, 0x78, 0x01, 0xe0, 0x7f, 0x80, 0x1e, 0x7c, 0x01, 0xe1, 0xfe, 0x00, 0x3e, 0x7c, 0x01, 0xe3, 0xfc, 0x00, 0x3e, 0x3c, 0x01, 0xef, 0xf0, 0x00, 0x3e, 0x3c, 0x01, 0xff, 0xe0, 0x00, 0x3c, 0x3e, 0x01, 0xff, 0x80, 0x00, 0x7c, 0x1f, 0x01, 0xff, 0x00, 0x00, 0x78, 0x1f, 0x00, 0xfc, 0x00, 0x00, 0xf8, 0x0f, 0x80, 0x30, 0x00, 0x01, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x03, 0xf0, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x03, 0xf0, 0x00, 0x00, 0x0f, 0xc0, 0x01, 0xf8, 0x00, 0x00, 0x1f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x7f, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x3f, 0xf0, 0x0f, 0xfc, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char LEFT_arrow [] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char UP_arrow [] PROGMEM = { 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00 }; const unsigned char RIGHT_arrow [] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char DOWN_arrow [] PROGMEM = { 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x00, 0x00 }; // Arrow width & height #define ARROW_WIDTH 48 #define ARROW_HEIGHT 48 // Store last movement direction String lastMovement = "No Movement"; const unsigned char* lastArrow = nullptr; |
Как всегда, мы включили в код все необходимые заголовочные файлы, а затем создали экземпляры дисплея и PMW3901. После этого вы видите несколько массивов, содержащих данные растрового изображения стрелки направления. Кроме того, мы объявили несколько глобальных переменных для использования в будущем.
|
1 2 3 4 5 6 7 8 9 10 |
void updateDisplay() { display.clearDisplay(); // Center the arrow int x = (SCREEN_WIDTH - ARROW_WIDTH) / 2; int y = (SCREEN_HEIGHT - ARROW_HEIGHT) / 2; if (lastArrow) { display.drawBitmap(40, 8, lastArrow, ARROW_WIDTH, ARROW_HEIGHT, WHITE); } display.display(); } |
Затем функция updateDisplay отвечает за отображение значка направления в зависимости от имени значка, хранящегося в переменной LastArrowb. При вызове она очищает буфер отображения, отображает значок в его центре, а затем выводит его на экран.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void setup() { Serial.begin(115200); // Initialize OLED if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("SSD1306 allocation failed"); while (1); } // Initialize PMW3901 SPI.begin(); if (!flow.begin()) { Serial.println("PMW3901 Initialization failed!"); display.clearDisplay(); display.setTextColor(WHITE); display.setCursor(10, 25); display.println("Sensor Fail!"); display.display(); while (1); } Serial.println("PMW3901 Initialized"); display.clearDisplay(); lastArrow = Start_icon; updateDisplay(); } |
В функции setup() мы инициализировали последовательный порт для отладки, а затем инициализировали OLED-дисплей и датчик PMW3901. После успешной инициализации всего оборудования появится значок воспроизведения, указывающий на готовность оборудования.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
void loop() { int16_t deltaX = 0, deltaY = 0; // Read motion data flow.readMotionCount(&deltaX, &deltaY); // Directly updates deltaX & deltaY Serial.print("DX: "); Serial.print(deltaX); Serial.print(" DY: "); Serial.println(deltaY); // Ignore small movements if (abs(deltaX) < 3 && abs(deltaY) < 3) { return; // Do not update display if no new movement } // Detect only Up, Down, Left, Right if (deltaX > 3 && abs(deltaY) < 3) { lastMovement = "Right"; lastArrow = RIGHT_arrow; } else if (deltaX < -3 && abs(deltaY) < 3) { lastMovement = "Left"; lastArrow = LEFT_arrow; } else if (deltaY > 3 && abs(deltaX) < 3) { lastMovement = "UP"; lastArrow = UP_arrow; } else if (deltaY < -3 && abs(deltaX) < 3) { lastMovement = "Down"; lastArrow = DOWN_arrow; } else { return; // Ignore diagonal movements } updateDisplay(); // Update OLED only when movement occurs } |
Наконец, в функции loop() мы будем непрерывно опрашивать данные о движении с модуля PMW3901, и при обнаружении движения значение переменной lastArrow будет обновляться в зависимости от полученных данных. После этого вызывается функция updateDisplay для обновления отображения. Этот процесс повторяется непрерывно для отображения направления движения.
Репозиторий GitHub с кодом и схемой
Полный код для этого руководства по взаимодействию оптического датчика потока PMW3901 с ESP32 можно скачать с репозитория Github по следующей ссылке.
