Лаги при bluetooth-соединении

Тема в разделе "Arduino & Shields", создана пользователем wiznet, 17 мар 2016.

  1. wiznet

    wiznet Нерд

    Всем привет.

    Нашел в загашнике aruino nano и адресную светодиодную ленту - захотелось запилить ambilight подсветку. Чтобы не придумывать велосипед - взял уже готовый софт (http://lightpack.tv/downloads.php) и запилил скетч реализующий этот протокол. Через USB все работает отлично и без нареканий.

    Однако, там же в загашнике валялся bt-модуль hc-05 - решил и его приспособить чтобы, значится, еще и с телефона светомузыку играть :)
    Подключил как serial и обнаружил неожиданную проблему: через bluetooth теряются пакеты.

    1) Ресивер лежит в 15 см от трансмиттера (скорее всего дело не в помехах)
    2) Пробовал разные скорости (9600 до 115200, устанавливая идентичные скорости и в софте, и в скетче, и в самом модуле через AT-команды) - говорю это для того чтобы отмести самые очевидные вопросы про несоответствие baud rate
    3) Ни с одной библиотекой не срослось (подключал к HardwareSerial вместо usb, использовал идущую в комплекте с IDE SoftwareSerial, скачивал отдельую NewSoftSerial)

    Результат один - если посылать данные с интервалом то все отлично проходит, как только слать друг за другом байты то даже в первых 100 байтах начинаются жуткие лаги и потери символов.

    Собственно, вывод тут один: виноваты чьи-то руки из задницы :)
    Возможно, переполняется какой-нибудь буфер и теряются пакеты? Возможно, уже есть готовые рецепты исправления которые я не знаю. К сожалению я не силен в особенностях arduino и микроконтроллеров в целом, а для кого-то проблема может быть сразу очевидна.

    Поэтому, предлагаю на ваш суд мой скромный скетч (один из, этот эмулирует программно serial чтобы иметь возможность посылать AT-команды блютусине и вообще в два порта писать) - возможно, вы подскажете в чем может быть трабла? Сообщайте пожалуйста если нужна дополнительная инфорамция. Заранее спасибо.

    Код (C++):
    // This sketch provide ability to control LED using USB and Bluetooth
    // (with latency and packet losts but its work)
    //
    // 1) can understand Adalight protocol (Adalight, Prismatic etc software)
    // 2) can switch custom leds using (to use in own plugins/implementations)
    //
    // Adalight protocol specification:
    // A 'magic word' (along with LED count & checksum) precedes each block
    // of LED data; this assists the microcontroller in syncing up with the
    // host-side software and properly issuing the latch (host I/O is
    // likely buffered, making usleep() unreliable for latch).  You may see
    // an initial glitchy frame or two until the two come into alignment.
    // The magic word can be whatever sequence you like, but each character
    // should be unique, and frequent pixel values like 0 and 255 are
    // avoided -- fewer false positives.  The host software will need to
    // generate a compatible header: immediately following the magic word
    // are three bytes: a 16-bit count of the number of LEDs (high byte
    // first) followed by a simple checksum value (high byte XOR low byte
    // XOR 0x55).  LED data follows, 3 bytes per LED, in order R, G, B,
    // where 0 = off and 255 = max brightness.

    // <a href="https://github.com/FastLED/FastLED" title="https://github.com/FastLED/FastLED" rel="nofollow">https://github.com/FastLED/FastLED</a>
    #include <FastLED.h>
    #include <SoftwareSerial.h>


    /* === feel free to change this values for your board */
    #define STRIP_TYPE WS2812B          // change to type you need
    #define NUM_LEDS 120                // leds count in strip
    #define LED_PIN 6                   // pin stip data output
    #define USB_SERIAL_RATE 115200      // usb connection speed
    #define BT_SERIAL_RATE 115200       // bt connection speed (default is 9600)
    #define BT_RX_PIN 11                //
    #define BT_TX_PIN 10                // bluetooth module rx/tx pins
    /* ==== */

    #define SEED 0x55
    #define STATE_WAITING 0
    #define STATE_HEADER 1
    #define STATE_PAYLOAD 2

    #define PROTO_DEFAULT 0
    #define PROTO_CUSTOM 1

    #define HEADERSIZE 3

    String magic = "Ada";
    CRGB leds[NUM_LEDS];

    SoftwareSerial btSerial(BT_RX_PIN, BT_TX_PIN);

    typedef struct Storage{
      uint8_t state;
      String buffer;
      int16_t expectedPayloadSize;
      uint8_t proto;
    };

    Storage usb;
    Storage bt;

    // flash message on USB connection
    void flash() {
      LEDS.showColor(CRGB(255, 0, 0));
      delay(500);
      LEDS.showColor(CRGB(0, 255, 0));
      delay(500);
      LEDS.showColor(CRGB(0, 0, 255));
      delay(500);
      LEDS.clear(true);
    }

    // parse adalight-compatible packet
    void parseDefaultPacket(Storage *storage) {
        // reset all leds in strip first
       memset(leds, 0, NUM_LEDS * sizeof(struct CRGB));
       // iterate over payload and set next led in row
       uint16_t ptr = 0;
       for (uint16_t i = 0; i < NUM_LEDS; i++) {
        leds[i].r = (byte)storage->buffer[ptr++];
        leds[i].g = (byte)storage->buffer[ptr++];
        leds[i].b = (byte)storage->buffer[ptr++];
       }
    }

    // parse custom user packet
    void parseCustomPacket(Storage *storage) {
       // iterate over payload and set only required LEDs
      uint16_t i = 0;
      while (i < storage->buffer.length()) {
        byte hi = (byte)storage->buffer[i++];
        byte lo = (byte)storage->buffer[i++];
        uint16_t addr = (hi << 8) + lo;
        if (addr >= NUM_LEDS || addr < 0) {
          i += 3; // skip incorrect values
        } else {
          leds[addr].r = (byte)storage->buffer[i++];
          leds[addr].g = (byte)storage->buffer[i++];
          leds[addr].b = (byte)storage->buffer[i++];
       }
      }
    }

    // switch port state (waiting for packet, waiting for packet header, waiting for data for led's)
    void updateState(Storage *storage, uint8_t flag) {
      storage->state = flag;
      storage->buffer = "";
    }

    // process incoming serial byte according current state
    void handleIncomingByte(Storage *storage, char input) {
       storage->buffer += input;
        // new packet incoming
        if (storage->buffer.endsWith(magic)) {
          return updateState(storage, STATE_HEADER);
        }

        switch (storage->state) {
          // test header
          case STATE_HEADER:
            // time to check packet
            if (storage->buffer.length() >= HEADERSIZE) {
              byte hi = (byte)storage->buffer[0];
              byte lo = (byte)storage->buffer[1];
              byte chk = (byte)storage->buffer[2];
              if((hi ^ lo ^ SEED) != chk) {
                return updateState(storage, STATE_WAITING);
              }
              uint8_t len = (hi << 8) + lo;
              if (len == (NUM_LEDS - 1)) {
                storage->expectedPayloadSize = (len + 1) * 3;
                storage->proto = PROTO_DEFAULT;
              } else {
                storage->expectedPayloadSize = len * 5;
                storage->proto = PROTO_CUSTOM;
              }
              return updateState(storage, STATE_PAYLOAD);
            }
            break;
          case STATE_PAYLOAD:
            if (storage->buffer.length() >= storage->expectedPayloadSize) {
              switch (storage->proto) {
                case PROTO_DEFAULT:
                  parseDefaultPacket(storage);
                  break;
                case PROTO_CUSTOM:
                  parseCustomPacket(storage);
                  break;
              }
              FastLED.show();
              return updateState(storage, STATE_WAITING);
            }
            break;
          case STATE_WAITING:
          default:
            if (storage->buffer.length() > magic.length()) {
              storage->buffer = storage->buffer.substring(storage->buffer.length() - magic.length());
            }

        }
    }

    // execute code on startup
    void setup() {
      FastLED.addLeds<STRIP_TYPE, LED_PIN, GRB>(leds, NUM_LEDS);
      flash();
      updateState(&usb, STATE_WAITING);
      updateState(&bt, STATE_WAITING);

      Serial.begin(USB_SERIAL_RATE);
      btSerial.begin(BT_SERIAL_RATE);
      Serial.println(magic);
    }

    // receive packets in endless loop
    void loop() {
      if (Serial.available()) {
        char input = Serial.read();
        handleIncomingByte(&usb, input);
      }
      if (btSerial.available()) {
        char input = btSerial.read();
        handleIncomingByte(&bt, input);
      }
    }
     
    ИгорьК нравится это.
  2. Onkel

    Onkel Гуру

    а какой протокол? Адресные светодиоды бывают разные.
    Скорее всего при посылке данных на ленту отключаются все прерывания, а данные приходят во время работы функции посылки данных на ленту. Посмотрите скорость и число св.диодов в функции управления лентой.
    похоже у вас время работы функции вписывается в паузу между байтами. Ну и посылайте управляющие сигналы с паузой между ними, большими времени работы функции упр. светодиодами.
     
  3. ИгорьК

    ИгорьК Гуру

    :) где же так долго скрывался такой замечательный автор-новичок?
     
    wiznet нравится это.
  4. wiznet

    wiznet Нерд

    ирония? одобряю :) я в курсе что скетч наговнокожен и его можно улучшить (например, вместо конвертации char->ascii code сразу принимать байты и не иметь оверхеда) ... но опыта в arduino мало от слова "совсем нет", поэтому любые советы принимаются :)
     
  5. wiznet

    wiznet Нерд

    Делал скетч чтобы он был совместим с софтом для работы с амбилайтом (https://www.adafruit.com, http://lightpack.tv). Протокол там крайне неоптимален и выглядит следующим образом:
    [заголовок 6 байт][3 байта RGB для первого светодиода][3 байта RGB для второго светодиода]...[3 байта RGB для последнего светодиода]

    В моем случае на ленту из 120 лампочек происходит следующее:
    1) Из serial по bluetooth считывается 366 байт (заголовок + значения rgb для каждой лампочки)
    2) Одним циклом устанавливаются значения всех ламп

    Меня смущает тот момент что через usb/uart все отлично работает - по логике прерывание должно и на этот интерфейс влиять? Или нет?

    Однако, очень может быть что вы правы и в тот момент когда идет цикл записи в ленту данные теряются. Можете посоветовать как можно протестировать этот момент и/или организовать код таким образом чтобы исключить данный косяк? Заранее спасибо.
     
  6. ИгорьК

    ИгорьК Гуру

    Нет! Я его даже не смотрел! Вы просто напомнили мне одного очень хорошего человека, который куда-то пропал с этого форума :)
    Честно, честно! Код не сморю, не до этого.
     
  7. Onkel

    Onkel Гуру

    с виду по ссылкам никакого кода нет, так что не уверен- но скорее всего NRZ 800 кГц (это в лучшем случае), тогда посылка 366 байт занимает 366*8/800000=4 мс. Вот и выбирайте частоту uart чтобы время передачи одного бита было более этих 4 мс, ну грубо это 600 бод, можно попробовать 1200.
    в железном uart'е буфер на байт- это, с учетом стоп и старт бита, получается буфер на 10 бит, позволяет практически без потерь работать на 19200 (uart получает следующий байт даже когда предыдущий не считан). В софтовом сериале буфера нет.
     
  8. wiznet

    wiznet Нерд

    если вы имели ввиду спецификацию самой ленты то это ws2812b: https://www.adafruit.com/datasheets/WS2812B.pdf

    хм, я подключал bluetooth-плату (HC-05) к контактам rx/tx ардуины нано и отключал usb - правильно ли я понимаю что в этом случае она начинает работать с железным uart? байты тоже терялись так что я в смущении
     
  9. Onkel

    Onkel Гуру

    я уже догадался, NRZ800 - это ее родной протокол
    тут много тонкостей, в которых можно закопаться и не откопаться, поскольку вы используете библиотечные функции с асмом (а иначе ws2812 не зажечь), а там хрен знает какие регистры на что задействованы. Соответсвенно - попробуйте на 600 (1200) бод, либо попробуйте на максимальной скорости, но повторяйте посылки, чтобы хотя бы одна (c гарантией!!!) влезла в паузу между выполнениями функции ws 28121, это надо просто посчитать исходя из времени выполнения этой ws 2812 функции (можете считать ее 4 мс для вашего случая) и паузы между посылками, при этом надо будет ввести в посылку контрольную сумму, по которой отбраковывать битые посылки.
     
    Последнее редактирование: 17 мар 2016
  10. wiznet

    wiznet Нерд

    К сожалению, не могу изменить протокол взаимодействия с софтом для включения ambilight-подсветки (ну кроме как написать свою версию), поэтому будем считать что ambilight не будет работать без usb :)

    А для кастомного протокола попробую передавать чексуммы и спамить на большой скорости раз это единственный вариант (честно говоря, думал что arduino более гибкая в этом плане).
    Огромное спасибо за консультацию.

    Для тех кто случайно набредет на тему: если что то результат своих потуг (скетчи, драйвера, софт) выложу тут - https://github.com/vkfont/led-strip