Варианты протокола для обмена данными между Arduino и ESP8266
У меня есть устройство, построенное на базе Arduino uno:
Программное обеспечение Arduino, установленное на Arduino uno
можно управлять с помощью последовательных команд
можно управлять с помощью физических кнопок и датчиков
при изменении состояния любой кнопки/датчика текущее состояние записывается в последовательный порт
если в течение 5 секунд не было отправлено ни одного сообщения, будет отправлено последовательное сообщение об отсутствии изменений
Что нужно:
Используйте ESP8266, чтобы обеспечить мост между текущим программным обеспечением Arduino и MQTT/web
Я могу запрограммировать ESP8266 в качестве веб-сервера, клиента MQTT и т. д. с помощью Arduino IDE или Lua (но я предпочитаю Arduino IDE, поскольку я могу повторно использовать части кода для генерации/интерпретации связи).
ESP8266 справится со всем, что требуется для Wi-Fi/Интернета/MQTT; без модуля MQTT часть Arduino будет работать автономно, не будет только пульта дистанционного управления.
Я хотел бы внести минимальные изменения в код Arduino (или вообще ничего, если возможно). Любые изменения потребуют повторного тестирования, чего я стараюсь избегать.
в некоторых установках может отсутствовать ESP8266.
Какие варианты я нашел:
- Серийный
ESP8266 может считывать последовательный вывод и быть мостом между веб/MQTT и последовательным, будет хранить текущее состояние в памяти для отправки по запросу, чтобы избежать опроса устройства каждый раз.
Одним из преимуществ является то, что для части Arduino не требуется никаких изменений кода или тестирования.
- I2C
Назначьте Arduino ведущим устройством I2C, а ESP8266 — подчиненным (или наоборот) и реализуйте двунаправленную связь. Идея появилась после прочтения этой темы.
Некоторая другая информация о последовательных командах:
Пакет данных (команда или описание состояния) содержит от 1 до 20 символов с возможным пиковым значением 20 пакетов за 5 секунд и в среднем одним пакетом каждые 3 секунды. При необходимости я могу заставить его отправлять 5 целых чисел без знака вместо буквенно-цифровых символов.
Если требуется больше контактов I2C/serial, я могу перейти на Arduino Mega (поэтому количество свободных контактов не является проблемой).
Есть ли другие варианты для этого? (протоколы, готовые библиотеки для последовательной связи и т. д.). Я стараюсь не изобретать велосипед..
Спасибо за ваше время!
@vlad b., 👍5
Обсуждение2 ответа
В большинстве учебных пособий по I2C каждая плата Arduino рассматривается как подчиненная и главная, но этот подход лучше, потому что каждый Arduino является либо ведущим, либо ведомым (но не обоими), и переключение не требуется. Это упрощает задачу.
I2C лучше, чем последовательный, потому что вы можете добавить больше Arduino на одну шину.
Я реализовал I2C между двумя Arduino, и это не сложнее, чем чтение/запись в последовательный порт (что вы уже сделали). И я уверен, что вы можете обобщить свой код последовательного порта для работы как с последовательными, так и с I2C-коммуникациями.
Это мой пример (просто доказательство концепции). Ведомый Arduino управляет некоторыми контактами, темп. датчик и сторожевой таймер по приказу хозяина Arduino. Если ведомое устройство не получает такт вовремя, оно сбрасывает ведущее устройство Arduino.
Мастер-код
#include <Wire.h>
#define CMD_SENSOR 1
#define CMD_PIN_ON 2
#define CMD_PIN_OFF 3
#define CMD_LUMEN 4
#define CMD_BEAT 5
const byte SLAVE_ADDRESS = 42;
const byte LED = 13;
char buffer[32];
void setup ()
{
Serial.begin(9600);
Serial.println("Master");
Wire.begin ();
pinMode (LED, OUTPUT);
digitalWrite(LED, HIGH);
delay(1000);
digitalWrite(LED, LOW);
Wire.beginTransmission (SLAVE_ADDRESS);
Serial.println("Send LED on");
Wire.write (CMD_PIN_ON);
Wire.write (2);
Wire.write (10);
Wire.endTransmission();
int x = Wire.requestFrom(SLAVE_ADDRESS, 1);
Serial.print("status=");
Serial.println(x);
} // конец настройки
void loop ()
{
Serial.println(".");
Wire.beginTransmission (SLAVE_ADDRESS);
Wire.write (CMD_SENSOR);
Wire.endTransmission();
int x = Wire.requestFrom(SLAVE_ADDRESS, 1);
Serial.print("Disponibles = ");
Serial.println(x);
int temp = (int) Wire.read();
Serial.println(temp);
Wire.beginTransmission (SLAVE_ADDRESS);
Wire.write (CMD_LUMEN);
Wire.endTransmission();
Wire.requestFrom(SLAVE_ADDRESS, 2);
int light = Wire.read() << 8 | Wire.read();
Serial.print("Light=");
Serial.println(light);
Wire.beginTransmission (SLAVE_ADDRESS);
Wire.write (CMD_BEAT);
Wire.endTransmission();
Wire.requestFrom(SLAVE_ADDRESS, 1);
delay (5000);
} // конец цикла
Подчиненный код
подчиненный I2C
Получите следующие команды
<- 1-й байт -> <- 2-й байт -> <- 3-й байт ->
CMD_SENSOR
CMD_PIN_ON продолжительность номера контакта в секундах
CMD_PIN_OFF номер вывода.
CMD_LUMEN
CMD_BEAT
Каждая команда получает ответ, либо запрошенное значение
или статус.
*/
#include <max6675.h>
#include <Wire.h>
typedef struct {
int pin;
unsigned long off;
} PIN_PGMA;
/*
Список сосен, который можно активировать с помощью CMD_PIN_ON.
*/
#define PIN_LUMEN A0
#define PIN_LED 2
#define PIN_RESET 3
PIN_PGMA pgma[] = {
{PIN_LED, 0},
{PIN_RESET, 0}
};
const int pgmaSize = sizeof(pgma) / sizeof(PIN_PGMA);
#define CMD_SENSOR 1
#define CMD_PIN_ON 2
#define CMD_PIN_OFF 3
#define CMD_LUMEN 4
#define CMD_BEAT 5
#define ST_OK 0
#define ST_BAD_PIN 1
#define ST_TIME_0 2
#define ST_BAD_LEN 3
#define MY_ADDRESS 42
// Максимальное время работы между командами CMD_BEAT. Пасадо
// Это время, активируйте PIN_RESET.
// En milisegundos.
#define BEAT_INTERVAL 10000
unsigned long lastBeat;
// Ларго дель сбросить в milisegundos.
#define RESET_LENGTH 250
byte cmd = 0;
byte status = 0;
int thermoDO = 11;
int thermoCS = 12;
int thermoCLK = 13;
MAX6675 thermocouple(thermoCLK, thermoCS, thermoDO);
void setup ()
{
Serial.begin(9600);
pinMode(PIN_LUMEN, INPUT);
analogRead(PIN_LUMEN);
for (int i = 0; i < pgmaSize; i++) {
pinMode(pgma[i].pin, OUTPUT);
digitalWrite(pgma[i].pin, LOW);
}
lastBeat = millis();
Wire.begin (MY_ADDRESS);
Wire.onReceive (receiveCommand);
Wire.onRequest (sendAnswer);
}
void loop()
{
unsigned long now = millis();
// Baja la linea de RESET si no ha recibido un beat ultimamente.
unsigned long diff = now - lastBeat;
if (diff > BEAT_INTERVAL) {
resetPin();
}
// Recorre la lista de pines y apaga aquellos cuyo tiempo termino.
for (int i = 0; i < pgmaSize; i++) {
if (pgma[i].off > 0 && pgma[i].off <= now) {
Serial.print("off pin="); Serial.println(pgma[i].pin);
pgma[i].off = 0;
digitalWrite(pgma[i].pin, LOW);
}
}
}
// вызывается процедурой обслуживания прерывания при запросе исходящих данных
void sendAnswer() {
byte temp;
int lightReading;
switch (cmd) {
case CMD_SENSOR:
temp = thermocouple.readCelsius();
Wire.write(temp);
break;
case CMD_LUMEN:
lightReading = analogRead(PIN_LUMEN);
Wire.write(lightReading >> 8);
Wire.write(lightReading % 0xFF);
break;
case CMD_PIN_ON:
case CMD_PIN_OFF:
case CMD_BEAT:
Wire.write(status);
status = ST_OK;
break;
}
cmd = 0;
}
// вызывается подпрограммой обслуживания прерывания при поступлении входящих данных
void receiveCommand (int howMany) {
cmd = Wire.read ();
status = ST_OK;
switch (cmd) {
case CMD_PIN_ON:
cmdPinOn();;
break;
case CMD_PIN_OFF:
cmdPinOff();
break;
case CMD_BEAT:
lastBeat = millis();
break;
}
} // конец приема события
void cmdPinOff() {
if (Wire.available() != 1) {
status = ST_BAD_LEN;
} else {
int pin = Wire.read();
int i = searchPin(pin);
if (i < 0) {
status = ST_BAD_PIN;
}
else {
pgma[i].off = 0;
digitalWrite(pin, LOW);
}
}
}
int searchPin(int pin) {
int i = pgmaSize - 1;
while (i >= 0 && pgma[i].pin != pin) {
i--;
}
return i;
}
/*
* Programa el encendido y duracion del RESET.
*/
void resetPin() {
if (digitalRead(PIN_RESET) == LOW) {
unsigned long now = millis();
int i = searchPin(PIN_RESET);
pgma[i].off = now + RESET_LENGTH;
lastBeat = now;
digitalWrite(PIN_RESET, HIGH);
}
}
void cmdPinOn() {
if (Wire.available() != 2) {
status = ST_BAD_LEN;
} else {
int pin = Wire.read();
int len = Wire.read();
int i = searchPin(pin);
Serial.print("pin="); Serial.print(pin); Serial.print(",index="); Serial.println(i);
if (i < 0) {
status = ST_BAD_PIN;
Serial.println("bad pin");
} else {
if (len == 0) {
status = ST_TIME_0;
Serial.println("ban len");
}
else {
pgma[i].off = millis() + len * 1000;
digitalWrite(pin, HIGH);
Serial.println("ok");
}
}
}
}
исправление: Arduino может быть ведущим ИЛИ ведомым. NodeMCU ESP8266 может быть только ведущим., @tony gil
Используя I2C (который я рекомендую вместо последовательного), вы не можете использовать NodeMCU ESP8266 в качестве ведомого. Он может работать только как мастер. С другой стороны, Arduino может быть и тем, и другим. Поэтому Arduino всегда будет пассивным, а NodeMCU будет ведущим.
Что вы можете сделать, так это реализовать семафор (как описано Majenko здесь), где Arduino сигнализирует, что ему есть что сказать мастер, заставляя мастер запрашивать у Arduino. В этих обстоятельствах его можно было бы назвать «услужливым хозяином» (неологизм).
НИКОГДА не печатайте в последовательный порт из requestEvent
или receiveEvent
.
- ошибка: espcomm_upload_mem failed при загрузке скетча
- Могу ли я использовать выход 3,3 В Arduino напрямую к esp8266?
- Сдвиг уровня 5В <-> 3,3В
- Отправка значений из arduino uno в wemos d1 r1
- Соединение i2c для MCP4725 (Dac) с Esp8266 wemos d1 mini
- Не удается загрузить скетчи или прошить Nodemcu 1.0 12E (CP2102), но у него есть драйверы и он реагирует на RST на последовательном мониторе
- Разбор сообщений с сервера MQTT
- Плата управления реле I2C от двух Arduino Uno
*Firmata* (https://github.com/firmata/protocol/blob/master/protocol.md) — один из таких существующих протоколов связи. Однако неясно, чего вы хотите - вы уже определили и построили последовательный протокол, и вы говорите, что не хотели бы изменять этот код... Если это так, просто закодируйте свой ESP, чтобы получить последовательные данные, проанализировать их и отправить в веб/MQTT-сервер., @KennetRunner