Как построить схему управления на Arduino и ESP8266 с настраиваемыми таймерами, контролируемую через Wi-Fi
В этой статье вы узнаете, как создать систему, которая может включать и выключать нагрузки постоянного тока с помощью мобильного приложения. Вы также узнаете, как выполнить эту задачу мгновенно или по таймерам, заранее установленным для включения и выключения нагрузок.
Обзор проекта
Вы можете реализовать эту систему там, где вам нужно включать нагрузку постоянного тока на определенное время. В этом вам поможет наше Android приложение, не требуя аппаратного интерфейса, клавиатуры и LCD дисплея.
Комплектующие
Компонент | Количество |
---|---|
Arduino UNO или любая совместимая плата | 1 |
Wi-Fi модуль ESP8266 | 1 |
USB TTL конвертер (необязательно) | 1 |
Реле SPDT 12V | 2 |
Биполярный NPN транзистор BC337 | 2 |
MOSFET транзистор с каналом N-типа BS170 | 1 |
Резистор 1 кОм | 2 |
Резистор 10 кОм | 2 |
Диод 1N4007 | 2 |
Зажимные клеммы | 2 |
Настраиваемый регулятор напряжения LM317 | 1 |
Конденсатор 0.1 мкФ | 2 |
Разъемы, мама и папа | - |
Пустая печатная плата | 2 |
Сборка макетной платы ESP8266
ESP8266 – недорогой SoC-чип со встроенным микроконтроллером и полным стеком протоколов TCP/IP, что означает, что он может напрямую обращаться к вашей Wi-Fi сети.
Поскольку у этого чипа есть свой микроконтроллер, вы можете поместить в него код своего приложения или можете использовать модуль просто как Wi-Fi приемопередатчик, что мы и собираемся сделать в данном проекте. Более эффективно было бы использовать этот модуль и как приемопередатчик, и как контроллер, но в целях обучения мы будем взаимодействовать с модулем, используя Arduino.
Чип ESP8266 поставляется в разных модулях. Мы будем использовать модуль ESP-01. Конечно, вы можете использовать любой другой модуль.
Во-первых, вы должны знать, что модуль работает с напряжением 3,3 В, и напряжение высокого логического уровня от Arduino должно быть таким же, чтобы не повредить наш модуль. Для этого требуется преобразователь уровня напряжения между платой Arduino (которая работает на 5 В) и модулем. Хорошей новостью является то, что в преобразователе будет нуждаться только вывод для передачи на Arduino, поскольку приемный вывод обычно распознает логические сигналы с напряжением 3,3 В от ESP8266.
Одним из простейших способов выполнения этого преобразования является схема от Sparkfun. Вы можете заказать готовый модуль.
На рисунке ниже показана распиновка нашего модуля на ESP8266:
Вывод | Назначение |
---|---|
UTXD | Передача данных через UART |
URXD | Прием данных через UART. Выход, к которому он подключается, должен быть 3,3 В. |
CH_PD | Выключение: низкий уровень на входе выключает чип, высокий уровень на входе включает его; для нормальной работы модуля необходимо подтянуть его к линии питания. |
GPIO0 | При загрузке: должен быть высокий уровень, чтобы входить в нормальный режим загрузки; низкий уровень вводит в специальные режимы загрузки. |
GPIO2 | При загрузке: низкий уровень заставляет загрузчик войти в режим загрузки флеш-памяти; высокий уровень вызывает нормальный режим загрузки. |
RST | Сброс; активный уровень – низкий. |
GND | Земля. |
VCC | Питание/3,3В. |
Я использовал LM317, настраиваемый линейный регулятор напряжения с выходным током до 1,5 А, для обеспечения модуля подходящим источником питания 3,3 В.
Примечание: Не используйте вывод 3,3 В от Arduino, так как стабилизатор напряжения 3,3 В на плате Arduino не может обеспечить необходимую для модуля величину тока, особенно при пиковом потреблении энергии во время передачи.
Я использовал BS170 (вместо BSS138) для преобразователя логических уровней; оба работают хорошо.
Теперь вы можете подключить свой модуль к компьютеру, используя USB-TTL преобразователь, и испытать его.
Сборка макетной платы реле
Для управления реле я использовал биполярный NPN транзистор BC337 с резистором 1 кОм на базе. Для защиты от обратного напряжения катушки я использовал диод 1n4007.
Нормально замкнутый (NC) контакт реле я решил подключить к земле.
Код Arduino
Теперь мы сталкиваемся с проблемой. ESP8266 использует UART в качестве интерфейса для AT-команд, а Arduino Uno (которая использует Atmega328) имеет только один порт UART. Этот порт уже подключен к мосту USB-TTL, а также к выводам 0 и 1.
В качестве решения вы можете использовать эмулятор для UART порта на другом цифровом выводе Arduino с помощью библиотек AltSoftSerial или SoftwareSerial. Это позволит вам по-прежнему иметь аппаратный порт UART для отладки и печати сообщений в консоли, а программный порт – для связи с модулем.
Многие люди (включая меня) сообщают о проблемах с программным последовательным портом при высоких скоростях передачи – как на тех, что мы будем использовать с esp8266, 115200 бит/с. Я могу сказать, что у вас 50% принятых от модуля данных будет повреждено, если вы используете программный UART, а из переданных от Arduino к модулю данных почти 100% будет корректно. Я получил эти результаты после отслеживания сигналов на линиях RX и TX.
В качестве решения я добавил в код несколько директив define
, чтобы облегчить вам выбор между аппаратным и программным UART портами. Имейте в виду, что вы не можете использовать один и тот же порт для отладки и общения с модулем, поэтому вам нужно выбирать между ними.
//раскомментируйте Serial.*** , если хотите для связи с ESP использовать аппаратный последовательный порт (выводы 0,1)
//раскомментируйте esp8266.*** , если хотите для связи с ESP использовать программный последовательный порт (выводы 2,3)
#define esp8266_Available() Serial.available() //esp8266.available()
#define esp8266_Find(ARG) Serial.find(ARG) //esp8266.find(ARG)
#define esp8266_Read() Serial.read() //esp8266.read()
#define esp8266_Write(ARG1,ARG2) Serial.write(ARG1,ARG2) //esp8266.write(ARG1,ARG2)
#define esp8266_Print(ARG) Serial.print(ARG) //esp8266.print(ARG)
В исходнике вы найдете часть кода, которая устанавливает модуля с вашим роутером:
sendCommand("AT+RST\r\n", 2000, DEBUG); // перезапустить модуль
sendCommand("AT+CWMODE=1\r\n", 1000, DEBUG); // настроить как точку доступа
sendCommand("AT+CWJAP=\"tur\",\"341983#tur\"\r\n", 3000, DEBUG); //**** ИЗМЕНИТЬ SSID и ПАРОЛЬ В СООТВЕТСТВИИ С ВАШЕЙ СЕТЬЮ ******//
delay(10000);
sendCommand("AT+CIFSR\r\n", 1000, DEBUG); // получить ip адрес
sendCommand("AT+CIPMUX=1\r\n", 1000, DEBUG); // настроить для нескольких соединений
sendCommand("AT+CIPSERVER=1,1337\r\n", 1000, DEBUG); // включить сервер на порту 1337
Цикл скетча ожидает команды, которые должны прийти через Wi-Fi соединение. В настоящее время поддерживаются следующие команды:
- ‘con’ для получения состояния выводов, высокий или низкий логический уровень;
- ‘on=’ включить соответствующий вывод;
- ‘of=’ выключить соответствующий вывод;
- ‘Tm=n/fS’ установить таймер включения (n) или выключения (f) соответствующего вывода.
Все команды имеют отклик подтверждения.
Примечания:
- некоторые части скетча основаны на этом руководстве;
- если вы используете модули со старым SDK, у вас могут быть такие же ошибки, как и у меня. Единственным решением в этом случае является обновление вашей прошивки до последней версии. Посмотрите эту статью, для получения помощи в обновлении прошивки модуля на ESP8266. Я обновил прошивку с версии 1.3 до 1.5.4.
Полный код программы:
#include <SoftwareSerial.h>
#define DEBUG 0 // если вы для связи с ESP используете аппаратный последовательный порт, измените значение на 0
#define ESPBaudRate 115200
#define HWSBaudRate 115200
#define OUTPUT1 11
#define OUTPUT2 12
#define OUTPUT3 13
//раскомментируйте Serial.*** , если для связи с ESP хотите использовать аппаратный последовательный порт (выводы 0,1)
//раскомментируйте esp8266.*** , если для связи с ESP хотите использовать программный последовательный порт (выводы 2,3)
#define esp8266_Available() Serial.available() //esp8266.available()
#define esp8266_Find(ARG) Serial.find(ARG) //esp8266.find(ARG)
#define esp8266_Read() Serial.read() //esp8266.read()
#define esp8266_Write(ARG1,ARG2) Serial.write(ARG1,ARG2) //esp8266.write(ARG1,ARG2)
#define esp8266_Print(ARG) Serial.print(ARG) //esp8266.print(ARG)
// Делает RX линию Arduino выводом 2, а TX линию Arduino выводом 3.
// Это означает, что вам необходимо подключить TX линию от ESP к выводу 2 Arduino,
// а RX линию от ESP к выводу 3 Arduino.
SoftwareSerial esp8266(2, 3);
/*************/
byte OUTPUTstate[3];
byte OUTPUTTMRIsSet[3] ;
byte OUTPUTTMRState[3] ;
long OUTPUTTimer[3];
/*************/
/***Commands**/
String GETSTATE = "con"; // Строка запроса от мобильного приложения, чтобы узнать состояние каждого выхода
String SETON = "on="; // Строка запроса от мобильного приложения, чтобы включить выход
String SETOFF = "of="; // Строка запроса от мобильного приложения, чтобы выключить выход
String TIMER = "tm="; // Строка запроса от мобильного приложения, чтобы задать таймер для выхода
/*************/
void setup()
{
Serial.begin(HWSBaudRate); // Последовательный порт для отправки сообщений от Arduino на компьютер
esp8266.begin(ESPBaudRate); // Программный последовательный порт для отправки сообщений от Arduino на ESP8266
pinMode(OUTPUT1, OUTPUT);
digitalWrite(OUTPUT1, LOW);
pinMode(OUTPUT2, OUTPUT);
digitalWrite(OUTPUT2, LOW);
pinMode(OUTPUT3, OUTPUT);
digitalWrite(OUTPUT3, LOW);
// перезапустить модуль
sendCommand("AT+RST\r\n", 2000, DEBUG);
// настроить как точку доступа
sendCommand("AT+CWMODE=1\r\n", 1000, DEBUG);
//**** ИЗМЕНИТЬ SSID и ПАРОЛЬ В СООТВЕТСТВИИ С ВАШЕЙ СЕТЬЮ ******//
sendCommand("AT+CWJAP=\"tur\",\"341983#tur\"\r\n", 3000, DEBUG);
delay(10000);
// получить ip адрес
sendCommand("AT+CIFSR\r\n", 1000, DEBUG);
// настроить для нескольких соединений
sendCommand("AT+CIPMUX=1\r\n", 1000, DEBUG);
// включить сервер на порту 1337
sendCommand("AT+CIPSERVER=1,1337\r\n", 1000, DEBUG);
if (DEBUG == true)
Serial.println("Server Ready");
}
void loop()
{
if (esp8266_Available()) // проверить, послал ли esp сообщение
{
if (esp8266_Find("+IPD,"))
{
// ждать, когда последовательный буфер заполнится (прочитаются все последовательные данные)
delay(1000);
// получить id подключения, чтобы мы могли отключиться
int connectionId = esp8266_Read() - 48;
// вычитаем 48 потому, что функция read() возвращает
// десятичное значение в ASCII, а 0 (первое десятичное число) начинается с 48
String closeCommand = "AT+CIPCLOSE="; // создание команды закрытия подключения
closeCommand += connectionId; // добавить id подключения
closeCommand += "\r\n";
esp8266_Find('?'); // Этот символ определяет начало команды теле нашего сообщения
String InStream;
InStream = (char) esp8266_Read();
InStream += (char) esp8266_Read();
InStream += (char) esp8266_Read();
if (DEBUG == true)
Serial.println(InStream);
if (InStream.equals(GETSTATE))
{
// отклик на команду Status=<состояние_выхода_1><состояние_выхода_2><состояние_выхода_3>
String response = "Status=";
response += OUTPUTstate[0];
response += OUTPUTstate[1];
response += OUTPUTstate[2];
sendHTTPResponse(connectionId, response);
sendCommand(closeCommand, 1000, DEBUG); // закрыть подключение
}
else if (InStream.equals(SETON))
{
int pinNumber = (esp8266_Read() - 48); // получить первую цифру, т.е., если вывод 13, то 1-ая цифра равна 1
int secondNumber = (esp8266_Read() - 48);
if (secondNumber >= 0 && secondNumber <= 9)
{
pinNumber *= 10;
pinNumber += secondNumber; // получить вторую цифру, т.е., если вывод 13, то 2-ая цифра равна 3,
// и добавить ее к первой цифре
}
if (pinNumber == OUTPUT1)
OUTPUTstate[0] = 1;
else if (pinNumber == OUTPUT2)
OUTPUTstate[1] = 1;
else if (pinNumber == OUTPUT3)
OUTPUTstate[2] = 1;
digitalWrite(pinNumber, 1);
String response = "Confg="; // Отклик на команду Confg=<номер_вывода>
response += pinNumber;
sendHTTPResponse(connectionId, response);
sendCommand(closeCommand, 1000, DEBUG); // закрыть подключение
}
else if (InStream.equals(SETOFF))
{
int pinNumber = (esp8266_Read() - 48); // получить первую цифру, т.е., если вывод 13, то 1-ая цифра равна 1
int secondNumber = (esp8266_Read() - 48);
if (secondNumber >= 0 && secondNumber <= 9)
{
pinNumber *= 10;
pinNumber += secondNumber; // получить вторую цифру, т.е., если вывод 13, то 2-ая цифра равна 3,
// и добавить ее к первой цифре
}
if (pinNumber == OUTPUT1)
OUTPUTstate[0] = 0;
else if (pinNumber == OUTPUT2)
OUTPUTstate[1] = 0;
else if (pinNumber == OUTPUT3)
OUTPUTstate[2] = 0;
digitalWrite(pinNumber, 0); // изменить состояние вывода
String response = "Confg="; // Отклик на команду Confg=<номер_вывода>
response += pinNumber;
sendHTTPResponse(connectionId, response);
sendCommand(closeCommand, 1000, DEBUG); // закрыть подключение
}
else if (InStream.equals(TIMER))
{
int pinNumber = (esp8266_Read() - 48); // получить первую цифру, т.е., если вывод 13, то 1-ая цифра равна 1
int secondNumber = (esp8266_Read() - 48);
if (secondNumber >= 0 && secondNumber <= 9)
{
pinNumber *= 10;
pinNumber += secondNumber; // получить вторую цифру, т.е., если вывод 13, то 2-ая цифра равна 3,
// и добавить ее к первой цифре
}
if (esp8266_Read() == 'n')
{
if (DEBUG == true)
Serial.println("on");
if (pinNumber == OUTPUT1)
OUTPUTTMRState[0] = 1;
else if (pinNumber == OUTPUT2)
OUTPUTTMRState[1] = 1;
else if (pinNumber == OUTPUT3)
OUTPUTTMRState[2] = 1;
}
else
{
if (DEBUG == true)
Serial.println("off");
if (pinNumber == OUTPUT1)
OUTPUTTMRState[0] = 0;
else if (pinNumber == OUTPUT2)
OUTPUTTMRState[1] = 0;
else if (pinNumber == OUTPUT3)
OUTPUTTMRState[2] = 0;
}
int j = 0;
byte Atime[5]; // Таймер может настроен на максимальное значение в 1 сутки
// поэтому программа может принять 5 цифр, так как 1 сутки равны 86400 секундам
long Time;
// Прочитать секунды, значение имеет переменное количество цифр, поэтому читать, пока не получим 's',
// что является символом завершения в теле моего сообщения от мобильного телефона
while (1)
{
Time = esp8266_Read();
if (Time == 's')
break;
Atime[j] = Time - 48 ;
j++;
}
switch (j) // секунды ...
{
case 1: // одна цифра
Time = Atime[0];
break;
case 2: // две цифры
Time = Atime[0] * 10 + Atime[1];
break;
case 3: // три цифры
Time = Atime[0] * 100 + Atime[1] * 10 + Atime[2];
break;
case 4: // четыре цифры
Time = Atime[0] * 1000 + Atime[1] * 100 + Atime[2] * 10 + Atime[3];
break;
case 5: // пять цифр
Time = Atime[0] * 10000 + Atime[1] * 1000 + Atime[2] * 100 + Atime[3] * 10 + Atime[j];
break;
}
if (DEBUG == true)
{
Serial.println("Timer:");
Serial.println(Time);
}
Time = Time * 1000 + millis();
if (DEBUG == true)
{
Serial.println("Pin:");
Serial.println(pinNumber);
}
if (pinNumber == OUTPUT1)
{
OUTPUTTMRIsSet[0] = 1;
OUTPUTTimer[0] = Time;
}
else if (pinNumber == OUTPUT2)
{
OUTPUTTMRIsSet[1] = 1;
OUTPUTTimer[1] = Time;
}
else if (pinNumber == OUTPUT3)
{
OUTPUTTMRIsSet[2] = 1;
OUTPUTTimer[2] = Time;
}
String response = "tConfg="; // Отклик на команду tConfg=<номер_вывода>
response += pinNumber;
sendHTTPResponse(connectionId, response);
sendCommand(closeCommand, 1000, DEBUG); // закрыть подключение
}
else // принята неподдерживаемая команда
{
String response = "ERROR";
sendHTTPResponse(connectionId, response);
sendCommand(closeCommand, 1000, DEBUG); // закрыть подключение
}
}
}
/*****Проверить таймер для каждого выхода******/
if (OUTPUTTMRIsSet[0] != 0 && (OUTPUTTimer[0] < millis()))
{
digitalWrite(OUTPUT1, OUTPUTTMRState[0]);
OUTPUTstate[0] = OUTPUTTMRState[0];
OUTPUTTMRIsSet[0] = 0;
}
if (OUTPUTTMRIsSet[1] != 0 && (OUTPUTTimer[1] < millis()))
{
digitalWrite(OUTPUT2, OUTPUTTMRState[1]);
OUTPUTstate[1] = OUTPUTTMRState[1];
OUTPUTTMRIsSet[1] = 0;
}
if (OUTPUTTMRIsSet[2] != 0 && (OUTPUTTimer[2] < millis()))
{
digitalWrite(OUTPUT3, OUTPUTTMRState[2]);
OUTPUTstate[2] = OUTPUTTMRState[2];
OUTPUTTMRIsSet[2] = 0;
}
/***************************************/
}
/*
Name: sendData
Description: Функция, используемая для отправки данных на ESP8266.
Params: command - данные/команда для отправки; timeout - время ожидания отклика; debug - печатать в консоль?(true = да, false = нет)
Returns: Отклик от esp8266 (если есть отклик)
*/
String sendData(String command, const int timeout, boolean debug)
{
String response = "";
int dataSize = command.length();
char data[dataSize];
command.toCharArray(data, dataSize);
esp8266_Write(data, dataSize); // передача символов на esp8266
if (debug)
{
Serial.println("\r\n====== HTTP Response From Arduino ======");
Serial.write(data, dataSize);
Serial.println("\r\n========================================");
}
long int time = millis();
while ( (time + timeout) > millis())
{
while (esp8266_Available())
{
// У esp есть данные, поэтому вывести их в консоль
char c = esp8266_Read(); // прочитать следующий символ.
response += c;
}
}
if (debug)
{
Serial.print(response);
}
return response;
}
/*
Name: sendHTTPResponse
Description: Функция, которая посылает HTTP 200, HTML UTF-8 отклик
*/
void sendHTTPResponse(int connectionId, String content)
{
// создать HTTP отклик
String httpResponse;
String httpHeader;
// HTTP заголовок
httpHeader = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n";
httpHeader += "Content-Length: ";
httpHeader += content.length();
httpHeader += "\r\n";
httpHeader += "Connection: close\r\n\r\n";
httpResponse = httpHeader + content + " "; // Здесь в коде баг: последний символ в "content" не посылается, поэтому я добавил дополнительный пробел
sendCIPData(connectionId, httpResponse);
}
/*
Name: sendCIPDATA
Description: посылает команду CIPSEND=<id_подключения>,<данные>
*/
void sendCIPData(int connectionId, String data)
{
String cipSend = "AT+CIPSEND=";
cipSend += connectionId;
cipSend += ",";
cipSend += data.length();
cipSend += "\r\n";
sendCommand(cipSend, 1000, DEBUG);
sendData(data, 1000, DEBUG);
}
/*
Name: sendCommand
Description: Функция, используемая для отправки данных на ESP8266.
Params: command - данные/команда для отправки; timeout - время ожидания отклика; debug - печатать в консоль?(true = да, false = нет)
Returns: Отклик от esp8266 (если есть отклик)
*/
String sendCommand(String command, const int timeout, boolean debug)
{
String response = "";
esp8266_Print(command); // передача символов на esp8266
long int time = millis();
while ( (time + timeout) > millis())
{
while (esp8266_Available())
{
// У esp есть данные, поэтому вывести их в консоль
char c = esp8266_Read(); // прочитать следующий символ.
response += c;
}
}
if (debug)
{
Serial.print(response);
}
return response;
}
Android приложение
Чтобы управлять всеми выше перечисленными аппаратными компонентами, мы будем использовать простое приложение для Android. Это приложение позволит нам включать или выключать выход напрямую или через определенный период времени.
Примечание: Приложение требует Android 4.0 (IceCreamSandwich) или выше.
- Прежде всего, вы должны знать IP адрес своего модуля. Если вы использовали программный последовательный порт, IP адрес будет напечатан в консоли. Если вы использовали аппаратный последовательный порт, то вы должны использовать кабель для отслеживания данных на линиях RX и TX, чтобы увидеть IP адрес. Вам также нужно знать номер порта, который был указан в скетче для Arduino. После этого нажмите "connect", чтобы получить состояние всех трех выходов. Вам нужно убедиться, что ваш Wi-Fi роутер включен, и вы подключены к локальной сети.
- Теперь нажмите на любой переключатель, который вы хотите включить/выключить. Всякий раз, когда захотите, вы можете нажать "refresh", чтобы обновить состояние всех выходов.
- На вкладке "Timers" вы можете установить любой из этих трех выходов для включения/выключения через определенный промежуток времени (от 0 до 24 часов).
- После любого действия вы получите сообщение с подтверждением о том, выполнилась ли команда успешно, или возникла какая-то ошибка.
Демонстрационное видео
Вот и всё! Надеюсь, статья оказалась полезной. Оставляйте комментарии!