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

wiznet
Offline
Зарегистрирован: 13.02.2015
Всем привет.
 
Нашел в загашнике 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-команды блютусине и вообще в два порта писать) - возможно, вы подскажете в чем может быть трабла? Сообщайте пожалуйста если нужна дополнительная инфорамция. Заранее спасибо.
 
// 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.

// https://github.com/FastLED/FastLED
#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);
  }
}

 

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

В loop вместо if(...available) поставьте while (...available) - как минимум. Иначе - неизбежные потери данных на больших скоростях UART.

releyshic
Offline
Зарегистрирован: 20.11.2015

мимо пробегал, интересно стало а почему так?

wiznet
Offline
Зарегистрирован: 13.02.2015
выскажу предположение что уважаемы DIYMan имеет ввиду:
 
- в случае while пока не будут считаны все данные из буфера функция loop не завершится
 
- в случае if функция считывает по одному байту и вызывает саму себя заново (чтобы считать еще один байт)
получается что между двумя считываниями происходит очень маленький но перерыв (а в зависимости от дополнительного кода навешеного в loop может быть и больше) - если в это время при заполненом буфере придут новые данные они потеряются (или потеряются те что были первыми, в зависимости от того какой там у serial-like библиотек тип буфера, FIFO или FILO) 
 
 
в моем случае эта конструкция не поможет - подозреваю что основной зависон происходит там где записываются данные в адресную ленту и это конструктивная особенность
хочу попробовать поиграться с размером буфера (посмотрел, у SoftwareSerial он совсем уж маленький), если не получится буду высчитывать чексумму пакетов и просить повторить сбойные :)
releyshic
Offline
Зарегистрирован: 20.11.2015

Аааа а почему там вообще if стоит )) оно и понятно ))

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

wiznet
Offline
Зарегистрирован: 13.02.2015

releyshic пишет:
Аааа а почему там вообще if стоит )) оно и понятно ))

Видимо потому что опрашивается не один serial а два

 

releyshic пишет:
Подозреваю что для таких вещей вообще нужно по прерываниям работать...

Ну, есть Serial.SerialEvent который как бы для этого предназначен. Но, во первых, один фиг цикл loop на время выполнения блокируется так что я че-то не вижу преимуществ. Во вторых, hardware serial уже занят usb, поэтому bluetooth приходится эмулировать через software serial а там и такого нет.

Оффтоп: до этого ардуиной не занимался, просто делаю "проект выходного дня". Похоже на квест: найди библиотеку актуальную которая обновлялась хотя бы раз за последний год, прочитай мануалы и удивись что официальный Serial.flush() не смотря на название не очищает буфер и тп... Жалко времени свободного мало, так бы позависал : )