Автоматические раздвижные гаражные ворота на Arduino Nano
Компоненты и материалы
| Компонент | Количество | Примечание |
|---|---|---|
| Arduino Nano R3 | 1 | |
| Плата для прототипирования | 1 | Под Nano и OLED |
| OLED-дисплей 0.96" 128x64 (SPI) | 1 | Статус, таймеры, датчики |
| Модуль реле 4-канальный | 1 | Используются 3 реле |
| Блок питания 5В (от 220В) | 1 | Миниатюрный, для логики |
| Тактильная кнопка 12 мм | 1 | Кнопка сброса аварии |
| Двигатель 220В, концевики, фотоэлемент, радиоприемник пульта | - | От оригинального механизма |
Предыстория
Пульт от раздвижных ворот перестал работать. Причина — сгоревшая ящерица на плате управления (короткое замыкание на 220В). Плату восстановить было нельзя — решил собрать свою на Arduino.
Архитектура системы
Основной блок управления (Arduino Nano + OLED + реле) управляет:
- Двигателем открытия/закрытия (через 220В реле на модуле).
- Сигнальной лампой (реле 1).
- Опросом концевиков «Закрыто» и «Открыто».
- Опросом фотоэлемента безопасности (препятствие при закрытии).
- Сигналом от радиоприемника пульта (реле пульта -> вход Arduino).
OLED-дисплей (SPI, пины 11,13,9,10,8) находится внутри корпуса — для настройки и диагностики без ноутбука. Показывает: фазу работы, таймеры, состояния датчиков, команду «Открыть».
Распиновка (GPIO Arduino Nano)
| Пин | Назначение | Компонент |
|---|---|---|
| D2 | FOTOCELULA | Фотоэлемент (INPUT_PULLUP) |
| D3 | OBERT | Концевик «Открыто» (INPUT_PULLUP) |
| D4 | OBRIR | Команда «Открыть» от радиоприемника (INPUT_PULLUP) |
| D5 | BOMBETA | Реле 1: Сигнальная лампа (OUTPUT) |
| D6 | MOTOR_OBRIR | Реле 2: Двигатель открытия (OUTPUT) |
| D7 | TANCAT | Концевик «Закрыто» (INPUT_PULLUP) |
| D12 | MOTOR_TANCAR | Реле 3: Двигатель закрытия (OUTPUT) |
| D11,13,9,10,8 | OLED SPI | MOSI, SCK, DC, CS, RESET |
Алгоритм работы (автомат состояний)
Код построен на метках (goto) и четких фазах:
- BUCLE_PRINCIPAL — ожидание команды «Открыть», обновление OLED каждые 2 сек. При аварии (AVARIA) лампа мигает 60 сек.
- SEQUENCIA_OBERTURA — открытие. Макс. время 28 сек. Лампа медленно мигает (1 сек). Если пришел конец хода «Открыто» -> PORTA_OBERTA. Если повторная команда «Открыть» во время движения -> стоп и переход к закрытию.
- PORTA_OBERTA — дверь открыта, ожидание 30 сек перед автозакрытием. Лампа: короткая вспышка каждые 2 сек. Команда «Открыть» -> сразу закрытие.
- SEQUENCIA_TANCAMENT — закрытие. Макс. 28 сек. Лампа быстро мигает (300 мс). Фотоэлемент -> стоп и повторное открытие. Концевик «Закрыто» -> BUCLE_PRINCIPAL. Команда «Открыть» -> стоп и открытие.
Код (C/C++ для Arduino Nano)
Две версии: с английскими и каталонскими комментариями. Ниже — английская версия с пояснениями.
/*********************************************************************
* Автоматизм раздвижных ворот v1.0
*********************************************************************/
/* Словарь (каталонский -> английский):
* Bucle principal = Main loop
* Sequencia Obertura = Opening sequence
* Sequencia Tancament = Closing sequence
* Obrir = Open; Obert = Opened
* Tancar = Close; Tancat = Closed
* Bombeta = Light bulb; Llum = light
* Porta = Gate/door; Avaria = Failure
* Temps = Time; Recorregut = door travel
*/
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Пины автоматики
int FOTOCELULA = 2;
int BOMBETA = 5; // Relay 1 - лампа
int TANCAT = 7; // Концевик "Закрыто"
int OBERT = 3; // Концевик "Открыто"
int MOTOR_OBRIR = 6; // Relay 2 - мотор открытия
int MOTOR_TANCAR = 12; // Relay 3 - мотор закрытия
int OBRIR = 4; // Команда "Открыть" от радиомодуля
// Пины OLED (SPI)
#define OLED_MOSI 11
#define OLED_CLK 13
#define OLED_DC 9
#define OLED_CS 10
#define OLED_RESET 8
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
// Глобальные переменные
unsigned long TEMPS_PORTA_OBERTA = 20000; // не используется напрямую
unsigned long TEMPS_MAXIM_RECORREGUT = 15000;
int interval = 1000;
int TEMPS_DISPLAY = 1;
int DURADA = 300; // цикл лампы
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;
unsigned long TEMPS_LLUM = 1000;
bool LLUM = false; // состояние лампы
bool AVARIA = false; // превышено время хода без концевика
void setup() {
// Выключить моторы и лампу (реле активны по LOW)
digitalWrite(MOTOR_OBRIR, HIGH);
digitalWrite(MOTOR_TANCAR, HIGH);
digitalWrite(BOMBETA, HIGH);
pinMode(FOTOCELULA, INPUT_PULLUP);
pinMode(TANCAT, INPUT_PULLUP);
pinMode(OBERT, INPUT_PULLUP);
pinMode(OBRIR, INPUT_PULLUP);
pinMode(BOMBETA, OUTPUT);
pinMode(MOTOR_OBRIR, OUTPUT);
pinMode(MOTOR_TANCAR, OUTPUT);
Serial.begin(9600);
display.begin(SSD1306_SWITCHCAPVCC);
display.display(); delay(1000); // лого Adafruit
display.clearDisplay(); display.display();
display.setTextSize(1); display.setTextColor(WHITE);
}
void loop() {
BUCLE_PRINCIPAL:
delay(2000);
previousMillis = millis();
while (digitalRead(OBRIR) == HIGH) { // ждем команду "Открыть" (LOW = нажато)
updateDisplay("BUCLE PRINCIPAL ...");
if (AVARIA && (millis() - previousMillis < 60000)) digitalWrite(BOMBETA, LOW);
else digitalWrite(BOMBETA, HIGH);
}
goto SEQUENCIA_OBERTURA;
SEQUENCIA_OBERTURA:
interval = 28000; // макс. время открытия
previousMillis = millis();
digitalWrite(MOTOR_OBRIR, LOW); // ВКЛ мотор открытия
digitalWrite(BOMBETA, LOW); // ВКЛ лампу
delay(1000);
A:
if (digitalRead(OBERT) == HIGH) goto PORTA_OBERTA; // дошел до концевика "Открыто"
if (digitalRead(OBRIR) == LOW) { // повторная команда "Открыть" -> стоп и ждем закрытия
updateDisplay("Esperant ordre per tancar");
digitalWrite(MOTOR_OBRIR, HIGH); digitalWrite(BOMBETA, HIGH);
delay(1000);
while (digitalRead(OBRIR) == HIGH) {}; // ждем отпускания кнопки
goto SEQUENCIA_TANCAMENT;
}
updateTimingAndDisplay("Obrint porta");
if ((millis() - previousMillis) >= interval) { digitalWrite(MOTOR_OBRIR, HIGH); AVARIA = true; }
flashLight(1000); // медленное мигание
goto A;
SEQUENCIA_TANCAMENT:
interval = 28000; // макс. время закрытия
previousMillis = millis();
digitalWrite(MOTOR_TANCAR, LOW); digitalWrite(BOMBETA, LOW);
if (digitalRead(TANCAT) == LOW) delay(1000);
B:
if (digitalRead(TANCAT) == HIGH) { digitalWrite(MOTOR_TANCAR, HIGH); digitalWrite(BOMBETA, HIGH); goto BUCLE_PRINCIPAL; }
if (digitalRead(OBRIR) == LOW) { // команда "Открыть" при закрытии
updateDisplay("Esperant ordre per obrir");
digitalWrite(MOTOR_TANCAR, HIGH); digitalWrite(BOMBETA, HIGH);
delay(1000);
while (digitalRead(OBRIR) == HIGH) {};
goto SEQUENCIA_OBERTURA;
}
if (digitalRead(FOTOCELULA) == HIGH) { // фотоэлемент сработал
digitalWrite(MOTOR_TANCAR, HIGH); delay(1000); goto SEQUENCIA_OBERTURA;
}
updateTimingAndDisplay("Tancant porta");
if ((millis() - previousMillis) >= interval) { digitalWrite(MOTOR_TANCAR, HIGH); AVARIA = true; goto BUCLE_PRINCIPAL; }
flashLight(300); // быстрое мигание
goto B;
PORTA_OBERTA:
interval = 30000; // ожидание 30 сек перед закрытием
previousMillis = millis();
digitalWrite(MOTOR_OBRIR, HIGH); delay(500);
C:
if (digitalRead(OBRIR) == LOW) goto SEQUENCIA_TANCAMENT; // команда "Открыть" -> закрыть
updateTimingAndDisplay("Porta Oberta completament");
if ((millis() - previousMillis) >= interval) goto SEQUENCIA_TANCAMENT;
// Специальное мигание: 100 мс вкл / 2000 мс выкл
if (!LLUM) DURADA = 100; else DURADA = 2000;
if ((millis() - TEMPS_LLUM) >= DURADA) { LLUM = !LLUM; digitalWrite(BOMBETA, LLUM); TEMPS_LLUM = millis(); }
goto C;
}
// Вспомогательные функции (вынесены для чистоты)
void updateDisplay(const char* phase) {
currentMillis = millis();
TEMPS_DISPLAY = (currentMillis - previousMillis) / 1000;
display.clearDisplay();
display.setTextSize(1); display.setCursor(0, 0); display.println(phase);
display.setTextSize(2); display.setCursor(90, 0); display.println(TEMPS_DISPLAY);
display.setTextSize(1); display.setCursor(0, 20);
display.print("Fotocel.lula: "); display.println(digitalRead(FOTOCELULA));
display.print("Sensor TANCAT: "); display.println(digitalRead(TANCAT));
display.print("Sensor OBERT: "); display.println(digitalRead(OBERT));
display.print("Ordre OBRIR: "); display.println(!digitalRead(OBRIR));
display.display();
}
void updateTimingAndDisplay(const char* phase) {
currentMillis = millis();
TEMPS_DISPLAY = (currentMillis - previousMillis) / 1000;
updateDisplay(phase);
}
void flashLight(unsigned long period) {
if ((millis() - TEMPS_LLUM) >= period) {
LLUM = !LLUM; digitalWrite(BOMBETA, LLUM); TEMPS_LLUM = millis();
}
}
Схемы и документация
- Принципиальная схема (Fritzing) — подключение датчиков, реле, радиомодуля.
- Список пинов (pinouts.txt).
- Дополнение: аппаратный аддон для вопроса пользователя irshad2318.
Важные нюансы
- Реле модуля активируются логическим LOW (опторазвязка). HIGH = выключено.
- Концевики и фотоэлемент — нормально замкнутые? В коде: HIGH = не сработал, LOW = сработал (INPUT_PULLUP).
- Команда «Открыть» от радиоприемника: реле пульта замыкает вход на GND -> LOW.
- OLED инициализируется по SPI. Библиотеки:
Adafruit_GFX,Adafruit_SSD1306. - Код использует
gotoдля простоты автомата. В производстве лучше переписать наswitch(state)без goto.
Производственный процесс
- Компактный breakout для DHT температуры и влажности на Raspberry Pi — ATtiny85 и DHT22/DHT11
- Лучшие аксессуары для раскрытия полного потенциала Raspberry Pi
- SIGHT: умные очки для слабовидящих на Raspberry Pi
- Создание подводного дрона на Raspberry Pi: проектирование, разработка и сборка
- Прецизионная автоматизация скользящего головного блока для швейцарского и производственного токарения
- Автоматизация как драйвер роста: как малому и среднему бизнесу обеспечить светлое будущее
- Искреннее прощание: мой 40-летний путь в издательстве
- Как выбрать высококачественные амортизаторы для вашего автомобиля
- Ручная ленточная пила: долговечное, портативное решение для резки любого проекта
- Что такое автоматический силовой пресс? — Ультимативный станок для формовки листового металла