Skip to content

Библиотека для передачи любых данных по проводу/ИК каналу/радио 433 МГц

License

Notifications You must be signed in to change notification settings

GyverLibs/GyverWire

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

latest PIO Foo Foo Foo

Foo

GyverWire

Лёгкая библиотека для передачи данных любого типа и размера по программному интерфейсу GyverWire (GW)

  • Надёжный DC-сбалансированный пакетный интерфейс связи на базе manchester encoding с дополнительными фреймами
  • Приём полностью в прерывании без опроса миллисов в loop, самосинхронизация, фильтрация шумов
  • Отправка и приём полностью своих "сырых" данных
  • Готовый инструмент для создания протоколов связи: отправка любых данных с указанием типа, целостность пакета контролируется библиотекой
  • Из коробки реализована передача по проводу, радио 433 МГц и ИК-каналу
  • Расчёт качества связи на приёмнике

Библиотека легче, удобнее и надёжнее библиотек Gyver433 и GyverTransfer и призвана их заменить

Совместимость

Совместима со всеми Arduino платформами (используются Arduino-функции)

Зависимости

  • GyverIO

Содержание

Использование

Библиотека содержит 3 класса передатчиков и 1 общий класс приёмника:

Передатчики

  • GW_TX <pin, baud = 5000> - для передачи по проводу (чистый сигнал без модификаций)
  • GW_TX_RF <pin, baud = 5000> - радиомодули 433 МГц и им подобные (встроен механизм "тренировки" канала связи)
  • GW_TX_IR <pin, baud = 5000, freq = 38000> - ИК-канал (модуляция 38 кГц с инверсией)
    • pin - пин МК, baud - скорость (бит/с, бод), freq - частота модуляции для ИК (Гц)

GW_TX, GW_TX_IR

// прошло времени с конца последней отправки, мс
uint16_t lastSend();

// ======== PACKET ========

// отправить пакет авто (тип GW_AUTO_TYPE, 255)
void sendPacket(const Td& data);

// отправить пакет авто (тип GW_AUTO_TYPE, 255)
void sendPacket(const void* data, size_t len);

// отправить пакет с типом
void sendPacketT(Tp type, const Td& data);

// отправить пакет с типом
void sendPacketT(Tp type, const void* data, size_t len);

// ======== RAW ========

// начать отправку сырых данных
void beginRaw();

// отправить сырые данные
template <typename T>
void sendRaw(const T& data);

// отправить сырые данные
void sendRaw(const void* data, size_t len);

// отправить raw байт
void sendByte(uint8_t b);

// закончить отправку сырых данных
void endRaw();

GW_TX_RF

GW_TX_RF(uint8_t trainMs = 30);

// установить время раскачки синхронизации в мс
void setTrain(uint16_t ms);

Приёмник

  • GW_RX <pin, baud = 5000, bufsize = 64> - приёмник для всех типов передачи
    • pin - пин МК, baud - скорость (бит/с, бод), bufsize - размер приёмного буфера (байт) - должен быть больше, чем самый большой потенциальный пакет данных
// подключить обработчик пакета вида f(uint8_t type, void* data, size_t len)
void onPacket(PacketCallback cb);

// подключить обработчик сырых данных вида f(void* data, size_t len)
void onRaw(RawCallback cb);

// вызывать при изменении сигнала на пине
void pinChange();

// вызывать в loop
void tick();

// получить качество приёма в процентах
uint8_t getRSSI();

Отправка

Отправка блокирующая, на высоких скоростях передачи рекомендуется оборачивать отправку в запрет прерываний для улучшения качества связи.

Сырые данные

Для отправки сырых данных нужно вызвать метод начала отправки, отправить данные, вызвать завершение:

GW_TX<2> tx;

struct Data {
  int i;
  float f;
};

uint32_t data_32 = 123456;             // целое
uint8_t data_arr[] = {1, 2, 3, 4, 5};  // массив
char cstr[10] = "hello";               // char array
String str = "hello";                  // String
Data data{1234, 3.1415};               // структура

// noInterrupts();
tx.beginRaw();

// размер вручную
// tx.sendRaw(&data_32, sizeof(data_32));
// tx.sendRaw(data_arr, sizeof(data_arr));
// tx.sendRaw(cstr, strlen(cstr));
// tx.sendRaw(str.c_str(), str.length());
// tx.sendRaw(&data, sizeof(data));

// авто размер
// tx.sendRaw(data_32);
// tx.sendRaw(data_arr);
// tx.sendRaw(data);

tx.endRaw();
// interrupts();

Библиотека никак не контролирует размер и целостность данных - можно реализовать полностью свой протокол связи без оверхэда

Пакет

Библиотека также позволяет передавать данные по универсальному пакетному протоколу связи - метод sendPacketT - указывается тип передаваемых данных, чтобы на приёмном устройстве удобнее парсить пакет. В этом случае библиотека контролирует целостность и размер данных и не вызовет обработчик, если они повреждены. Например пакет типа 1 - 32 бит целое, пакет типа 2 - байтовый массив, 3 - строка произвольной длины, и так далее.

  • Тип пакета - число от 0 до 30
  • Размер данных - до 2047 Байт
GW_TX<2> tx;

struct Data {
  int i;
  float f;
};

uint32_t data_32 = 123456;             // целое
uint8_t data_arr[] = {1, 2, 3, 4, 5};  // массив
char cstr[10] = "hello";               // char array
String str = "hello";                  // String
Data data{1234, 3.1415};               // структура

// noInterrupts();

// размер вручную
// tx.sendPacketT(0, &data_32, sizeof(data_32));
// tx.sendPacketT(1, data_arr, sizeof(data_arr));
// tx.sendPacketT(2, cstr, strlen(cstr));
// tx.sendPacketT(3, str.c_str(), str.length());
// tx.sendPacketT(4, &data, sizeof(data));

// авто размер
// tx.sendPacketT(0, data_32);
// tx.sendPacketT(1, data_arr);
// tx.sendPacketT(4, data);

// interrupts();

Тип для удобства может быть enum:

enum class packet_t {
  Data32,
  Array,
  Cstring,
  String,
  Struct,
};

// tx.sendPacketT(packet_t::Data32, data_32);
// tx.sendPacketT(packet_t::Array, data_arr);
// tx.sendPacketT(packet_t::Cstring, cstr, strlen(cstr));
// tx.sendPacketT(packet_t::String, str.c_str(), str.length());
// tx.sendPacketT(packet_t::Struct, data);

Если не указывать тип пакета (метод sendPacket), то он будет равен типу 31 при парсинге (константа GW_AUTO_TYPE). Удобно, если в системе присутствует только один тип пакетов:

// размер вручную
// tx.sendPacket(&data_32, sizeof(data_32));
// tx.sendPacket(data_arr, sizeof(data_arr));
// tx.sendPacket(cstr, strlen(cstr));
// tx.sendPacket(str.c_str(), str.length());
// tx.sendPacket(&data, sizeof(data));

// авто размер
// tx.sendPacket(data_32);
// tx.sendPacket(data_arr);
// tx.sendPacket(data);

Приём

  • Приём асинхронный - нужно вызывать метод pinChange() в момент изменения сигнала на пине: в прерывании по CHANGE или вручную
  • Для получения данных нужно подключить функцию-обработчик
  • В основном цикле программы нужно вызывать тикер tick() - в нём будет обработан пакет и вызван подключенный обработчик
// пример для Arduino NANO

GW_RX<2> rx;  // пин 2, прерывание 0

void setup() {
  // !!! опрос в прерывании CHANGE
  attachInterrupt(0, []() { rx.pinChange(); }, CHANGE);

  // обработчик сырых данных
  rx.onRaw([](void* data, size_t len) {
    // ...
  });

  // обработчик пакетов
  rx.onPacket([](uint8_t type, void* data, size_t len) {
    // ...
  });
}

void loop() {
  rx.tick();

  // !!! или опрос вручную без прерываний
  // static bool prev;
  // if (prev != gio::read(2)) {
  //   prev ^= 1;
  //   rx.pinChange();
  // }
}

Парсинг пакетов

Если с сырыми данными всё понятно, то у пакета при получении нужно сверить тип и преобразовать данные в нужный формат. Пример с теми данными, которые отправляли выше:

rx.onPacket([](uint8_t type, void* data, size_t len) {
    Serial.print("received type ");
    Serial.print(type);
    Serial.print(": ");

    switch (packet_t(type)) {
        case packet_t::Data32: {
            // можно дополнительно проверить длину данных, например тут if (len == 4)
            Serial.print(*((uint32_t*)data));
        } break;

        case packet_t::Array: {
            uint8_t* arr = (uint8_t*)data;
            for (size_t i = 0; i < len; i++) {
                Serial.print(arr[i]);
                Serial.print(',');
            }
        } break;

        case packet_t::Cstring: {
            Serial.write((uint8_t*)data, len);
        } break;

        case packet_t::String: {
            Serial.write((uint8_t*)data, len);
        } break;

        case packet_t::Struct: {
            Data& p = *((Data*)data);
            Serial.print(p.i);
            Serial.print(',');
            Serial.print(p.f);
        } break;
    }
    Serial.println();
});

Типы связи

Радио 433 МГц

Данные можно передавать при помощи самых простых радио модулей, у которых сигнал передатчика просто дублируется на выходе приёмника (например FS1000A и MX-RM-5V на 433 МГц). Для этого используется класс GW_TX_RF, он добавляет при отправке данных "раскачку" канала связи для синхронизации передатчика и приёмника, в конструктор можно передать продолжительность раскачки в миллисекундах. Раскачка будет происходить перед каждой отправкой данных, если они отправляются реже, чем через 50 мс (получено экспериментально). То есть если отправлять данные чаще - раскачки не будет и на передачу будет уходить меньше времени.

GW_TX_RF<3> tx(20);   // раскачка 20 мс

Чем хуже потенциальное качество связи и чем хуже по качеству сами модули, тем дольше нужна раскачка. Хорошие модули (например зелёные FS1000A и MX-RM-5V) раскачиваются за 10 мс, плохие (например SYNxxx) - за 100 мс.

Максимальная стабильная скорость для пары зелёных модулей (FS1000A и MX-RM-5V) - в районе 15'000 бод, быстрее не может сам модуль.

ИК

Библиотека может модулировать сигнал 38 кГц для ИК-светодиода, чтобы он принимался ИК-приёмником - например стандартный Ардуиновский набор из пульта, приёмника и светодиода, светодиод подключается без инверсии (GND-GND, анод на пин МК через резистор или транзистор для усиления). Для отправки нужно использовать класс GW_TX_IR.

Фильтрация

По умолчанию библиотека использует фильтр шумов для класса приёмника - он фильтрует случайные пики, когда сигнал на короткое время меняет состояние, это встречается при передаче по радио или при плохой схемотехнике и передаче по проводам (наличие рядом катушек, моторов итд). Для несущественного облегчения библиотеки фильтр можно отключить так:

#define GW_NO_FILTER
#include <GW_RX.h>

Примеры

Строка

Отправляем пакетом без типа, выводим в порт всю принятую длину:

// отправка
#include <Arduino.h>
#include <GW_TX.h>

void setup() {
}

void loop() {
    GW_TX<3> tx;

    String s;
    s += "hello! ";
    static uint8_t i;
    s += ++i;

    noInterrupts();
    tx.sendPacket(s.c_str(), s.length());
    interrupts();

    delay(1000);
}
// приём
#include <Arduino.h>
#include <GW_RX.h>

GW_RX<2> rx;

void setup() {
    Serial.begin(115200);
    attachInterrupt(0, []() { rx.pinChange(); }, CHANGE);

    rx.onPacket([](uint8_t type, void* data, size_t len) {
        Serial.write((uint8_t*)data, len);
        Serial.println();
    });
}

void loop() {
    rx.tick();
}

Структура

// отправка
#include <Arduino.h>
#include <GW_TX.h>

struct Data {
    int i;
    float f;
};

void setup() {
}

void loop() {
    GW_TX<3> tx;

    static int i;
    Data data{i++, 3.14};

    noInterrupts();
    tx.sendPacket(data);
    interrupts();

    delay(1000);
}
// приём
#include <Arduino.h>
#include <GW_RX.h>

struct Data {
    int i;
    float f;
};

GW_RX<2> rx;

void setup() {
    Serial.begin(115200);
    attachInterrupt(0, []() { rx.pinChange(); }, CHANGE);

    rx.onPacket([](uint8_t type, void* data, size_t len) {
        if (sizeof(Data) != len) return;  // проверка корректности длины
        
        Data& d = *((Data*)data);
        Serial.print(d.i);
        Serial.print(',');
        Serial.println(d.f);

        // или
        // Serial.println(static_cast<Data*>(data)->f);
    });
}

void loop() {
    rx.tick();
}

Версии

  • v1.0

Установка

  • Библиотеку можно найти по названию GyverWire и установить через менеджер библиотек в:
    • Arduino IDE
    • Arduino IDE v2
    • PlatformIO
  • Скачать библиотеку .zip архивом для ручной установки:
    • Распаковать и положить в C:\Program Files (x86)\Arduino\libraries (Windows x64)
    • Распаковать и положить в C:\Program Files\Arduino\libraries (Windows x32)
    • Распаковать и положить в Документы/Arduino/libraries/
    • (Arduino IDE) автоматическая установка из .zip: Скетч/Подключить библиотеку/Добавить .ZIP библиотеку… и указать скачанный архив
  • Читай более подробную инструкцию по установке библиотек здесь

Обновление

  • Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
  • Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
  • Вручную: удалить папку со старой версией, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!

Баги и обратная связь

При нахождении багов создавайте Issue, а лучше сразу пишите на почту alex@alexgyver.ru
Библиотека открыта для доработки и ваших Pull Request'ов!

При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:

  • Версия библиотеки
  • Какой используется МК
  • Версия SDK (для ESP)
  • Версия Arduino IDE
  • Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
  • Какой код загружался, какая работа от него ожидалась и как он работает в реальности
  • В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код

About

Библиотека для передачи любых данных по проводу/ИК каналу/радио 433 МГц

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages