Передача структуры с текстовой строкой через NRF24L01

Тема в разделе "Arduino & Shields", создана пользователем uscr, 5 ноя 2013.

  1. uscr

    uscr Нерд

    Здравствуйте! Решил научиться передавать структуры по воздуху. Использую 2 Arduino Nano с модулями NRF24L01+

    Скетч передатчика:
    Код (Text):
    #include <SPI.h>
    #include <Mirf.h>
    #include <nRF24L01.h>
    #include <MirfHardwareSpiDriver.h>
    #include <Communicator.h>

    #define SendCommandTimeout 3000
    #define ScanTimeout 100

    void setup(){
      Serial.begin(115200);
      Mirf.cePin = 9;
      Mirf.csnPin = 10;
      Mirf.spi = &MirfHardwareSpi;
      Mirf.init();
      Mirf.setRADDR((byte *)"serv");
      Mirf.payload = sizeof(unsigned long);
      Mirf.config();
      Serial.println("Beginning...");
    }

    void loop() {
      SendRequestStruct request;
      request.Status=0;
      request.Request=123;
      memcpy(&request.Comment, &"test", StringLen);
      Serial.print("Sending ");
      Serial.println((char*)request.Comment);
      Mirf.setTADDR((byte*)"0001");
      Mirf.send((byte *)&request);
      while(Mirf.isSending()){};
      delay(1000);
    }
    Приёмник:

    Код (Text):
    #include <SPI.h>
    #include <Mirf.h>
    #include <nRF24L01.h>
    #include <MirfHardwareSpiDriver.h>
    #include <Communicator.h>

    #define SendCommandTimeout 3000
    #define ScanTimeout 100

    void setup(){
      Serial.begin(115200);
      Mirf.cePin = 9;
      Mirf.csnPin = 10;
      Mirf.spi = &MirfHardwareSpi;
      Mirf.init();
      Mirf.setRADDR((byte*)"0001");
      Mirf.payload = sizeof(unsigned long);
      Mirf.config();
      Serial.println("Beginning ... ");
    }

    void loop() {
      if(!Mirf.isSending() && Mirf.dataReady()){
        SendRequestStruct request;

        Mirf.getData((byte*)&request);
        Serial.print("Get command: ");
        Serial.print(request.Request);
        Serial.print(" with string: ");
        Serial.println((char*)request.Comment);
        delay(100);
      }

    }
    В Communicator.h описываются просто структуры, но покажу и его:

    Код (Text):
    #ifndef Communicator
    #define Communicator

    #define StringLen 20 //Длина строк в структурах

    typedef struct _REQUEST_STRUCTS { //Структура запроса

      int Status; //0 - всё ок, 1- ошибка;
      int Request;
      byte Comment[StringLen];

    }SendRequestStruct;

    typedef struct _ANSWER_STRUCTS { //Структура ответа

    int Status; //0 - всё ок, 1- ошибка;
    int Answer;
    byte Comment[StringLen];

    }SendAnswerStruct;

    #endif // Communicator
    В монитор порта передатчика всё как надо:
    Код (Text):
    Beginning...
    Sending test
    Sending test
    Sending test
    Sending test
    Sending test
    Sending test
    Sending test
    Приёмник пишет вот что:
    Код (Text):
    Beginning ...
    Get command: 0 with string:
    Get command: 123 with string:
    Get command: 123 with string:
    Get command: 123 with string:
    Get command: 123 with string:
    Get command: 123 with string:
    Get command: 123 with string:
    Get command: 123 with string:
    После string в мониторе порта отображается пустой квадратик, просто он не скопировался. В первой строке действительно 0, это не ошибка. Что я делаю не так?
     
  2. Megakoteyka

    Megakoteyka Оракул Модератор

    А Mirf.payload не должно быть равно размеру структуры?
     
    Последнее редактирование: 5 ноя 2013
  3. uscr

    uscr Нерд

    Увеличение payload дало получить строку до конца. Думал, что payload - это, грубо говоря, размер пакета. Считал, что ардуина общается с модулем на высоких уровнях. Но...нет. Payload-максимальный размер буфера в байтах. Должен быть не меньше максимальной длинны передаваемых данных и не больше 32(железное ограничение). Но, честно говоря, понятнее не стало. Если бы я передавал массив байтов, то "максимальная длинна передаваемых данных" - один байт, угадал? А если этот массив в структуре, то буфер должен быть уже размером с структуру?
     
  4. uscr

    uscr Нерд

    Увеличил payload до максимальных 32. Гугл сказал, что payload всё таки должен быть таким, что бы моя структура полностью впихнулась в буфер. Добавил ещё один массив байтов:

    Код (Text):
    typedef struct _REQUEST_STRUCTS { //Структура запроса

      byte Status; //0 - всё ок, 1- ошибка;
      byte Request;
      byte Comment[StringLen];
      byte Bytearray[12];

    }SendRequestStruct;
    В скетчах дописал lоop():

    передатчик:
    Код (Text):
      SendRequestStruct request;
      request.Status=0;
      request.Request=123;
      memcpy(&request.Comment, &"HHHHhhhhHHHHhhhhHHH", StringLen);
      for (byte i=0; i<13; i++) {
        request.Bytearray[i]=100+i;
      }
      Serial.print("Sending ");
      Serial.println((char*)request.Comment);
      Mirf.setTADDR((byte*)"0001");
      Mirf.send((byte *)&request);
      while(Mirf.isSending()){};
      delay(1000);
    приёмник:
    Код (Text):
      if(!Mirf.isSending() && Mirf.dataReady()){
        SendRequestStruct request;

        Mirf.getData((byte*)&request);
        Serial.print("Get command: ");
        Serial.print(request.Request);
        Serial.print(" with string: ");
        Serial.println((char*)request.Comment);
        for (byte i=0; i<10; i++) {
          Serial.print("Bytearray ");
          Serial.print(i);
          Serial.print("-");
          Serial.println(request.Bytearray[i]);
        }
        delay(100);
      }
    Итак, я посчитал, что сейчас в мою структуру влезает аккурат 32 байта - 2 байтовые переменные+2 массива с 30 элементами в сумме.

    Строка "HHHHhhhhHHHHhhhhHHH" приходит целиком, 10 элементов массива приходят целиком, ещё 2 переменные приходят целиком. Первое сообщение всё равно отображается с нулями вместо всех элементов. Но, delay(1000) после Mirc.config() на приёмнике решает проблему. Передача на ~метрах через перегородку идёт без потерь. Позже попробую в помещении с капитальными стенами.

    Код (Text):
    Beginning ...
    Get command: 123 status: 0 with string: HHHHhhhhHHHHhhhhHHH
    Bytearray 0-100
    Bytearray 1-101
    Bytearray 2-102
    Bytearray 3-103
    Bytearray 4-104
    Bytearray 5-105
    Bytearray 6-106
    Bytearray 7-107
    Bytearray 8-108
    Bytearray 9-109
     
     
  5. Megakoteyka

    Megakoteyka Оракул Модератор

    Для передачи данных произвольного размера можно воткнуть между структурой и передатчиком пакетный протокол.
    Сначала преобразуем структуру в массив байт, режем массив на куски по 30 байт, добавляем спереди номер куска и количество кусков, затем передаем все это дело, на приеме складываем куски по счетчику в первом байте, проверяя количество пришедших кусков по второму байту. В конце преобразуем все обратно в структуру. Примерно так, возможна куча вариаций.
     
  6. uscr

    uscr Нерд

    Звучит круто. Возможно, слишком круто :)
    Я хочу сделать вот что: расставить по дому модули с датчиками, релюшками и прочими штуками. И наваять управляющий модуль. Задумка такая: при включении управляющий модуль ищет управляемые модули, тупо перебирая адреса из заранее известного списка (например 0001-0020). При обнаружении формируется список модулей с их "комментарием" (это как раз строка, которую я только что научился передавать). Из комментариев формируется меню, где можно выбрать модуль и войти уже в меню управления модулем. Дальше я в раздумьях. Первый вариант (очень крутой): модуль начинает передавать управляющему команды, формирующие меню для управления конкретным модулем, то есть управляющему модулю не нужно знать, что есть у ведомого, ему нужно просто отобразить меню. Таким образом, в управляющий я зашиваю список адресов с запасом и реализую построение меню. А дальше реализую любые управляемые модулю с любыми функциями. Второй вариант проще: модуль просто сообщает свой тип и текущие состояния чего-либо (температура, состояние реле). Управляющий модуль знает ограниченный набор типов управляемого и формирует меню по заданному заранее алгоритму.

    Вот в первом случае - мне понадобится реализовать протокол, во втором- 32 байта это даже ещё с запасом мне хватает. Учитывая, что про реализацию пакетного протокола у меня пока нет оформившихся мыслей, а набор модулей вряд ли будет шире 3-4 типов, я выбираю второй вариант и говорю вам спасибо за участие и пинок в нужную сторону.
     
  7. Megakoteyka

    Megakoteyka Оракул Модератор

    Всегда пожалуйста, приходите еще :)
     
  8. uscr

    uscr Нерд

    Очень интересная фигня... Если структура меньше payload, то начинаются неведомые глюки. Например, вот 2 скетча:

    Вещатель:
    Код (Text):
    #include <SPI.h>
    #include <Mirf.h>
    #include <nRF24L01.h>
    #include <MirfHardwareSpiDriver.h>
    #include <Communicator.h>

    #define SendCommandTimeout 3000
    #define ScanTimeout 100

    void setup(){
      Serial.begin(9600);
      Mirf.cePin = 9;
      Mirf.csnPin = 10;
      Mirf.spi = &MirfHardwareSpi;
      Mirf.init();
      Mirf.setRADDR((byte *)"serv");
      Mirf.payload = PAYLOAD;
      Mirf.config();
      delay(1000);
      Serial.println("Beginning...");
    }

    void loop() {
      SendRequestStruct request;
      request.Retransmit=0;
      request.Request=0;
      request.Time=millis();

      ScanAnswerStruct scan_ans;

      Serial.println("Sending... ");
      Serial.print("Retransmit ");
      Serial.println(request.Retransmit);
      Serial.print("Request ");
      Serial.println(request.Request);
      Serial.print("Time ");
      Serial.println(request.Time);
      Mirf.setTADDR((byte*)"0001");
      Mirf.send((byte *)&request);
      while(Mirf.isSending()){};
      delay(1000);
    }
    Приниматель:
    Код (Text):
    #include <SPI.h>
    #include <Mirf.h>
    #include <nRF24L01.h>
    #include <MirfHardwareSpiDriver.h>
    #include <Communicator.h>

    #define SendCommandTimeout 3000
    #define ScanTimeout 100

    void setup(){
      Serial.begin(9600);
      Mirf.cePin = 9;
      Mirf.csnPin = 10;
      Mirf.spi = &MirfHardwareSpi;
      Mirf.init();
      Mirf.setRADDR((byte*)"0001");
      Mirf.payload = PAYLOAD;
      Mirf.config();
      delay(1000);
      Serial.println("Beginning ... ");
    }

    void loop() {
      if(!Mirf.isSending() && Mirf.dataReady()){
        SendRequestStruct request;

        Mirf.getData((byte*)&request);
        Serial.println("Get struct: ");
        Serial.print("Retransmit ");
        Serial.println(request.Retransmit);
        Serial.print("Request ");
        Serial.println(request.Request);
        Serial.print("Time ");
        Serial.println(request.Time);
        }
        delay(100);

    }
    В случае. если передаваемая структура выглядит так:


    Код (Text):
    typedef struct _REQUEST_STRUCTS { //Структура запроса
      byte Retransmit; //0 - первое сообщение, иначе - номер ретрансмита
      byte Request;
      unsigned long Time;
    }SendRequestStruct;
     
    а пейлоад равен 32, в мониторе порта появляется такой бред:
    Код (Text):
    Beginning ...
    Sending..
    etransmit 0
    Request 0
    Time 159333
    Sending..
    etransmit 0
    Request 0
    Time 160334
    Sending..
    etransmit 0
    Request 0
    Time 161337
     
    Откуда то берётся "Sending" и выводится не до конца, а слово "etransmit" без первой буквы...

    Когда уменьшаю пейлоад до 6, либо просто объявляю массив, что бы довести размер структуры до 32, всё ок:

    Код (Text):
    Beginning ...
    Get struct:
    Retransmit 0
    Request 0
    Time 0
    Get struct:
    Retransmit 0
    Request 0
    Time 1000
    Get struct:
    Retransmit 0
    Request 0
    Time 2001
     
    Это я опять не понял назначение payload или Mirf не зря ругают? Хотя ругают за скудность параметров в конфигурировании...

    UPD:
    Изменил вещатель:
    Код (Text):
      SendRequestStruct request;
      request.Retransmit=0;
      request.Request=0;
      request.Time=millis();

      ScanAnswerStruct scan_ans;

      Mirf.setTADDR((byte*)"0001");
      Mirf.send((byte *)&request);
      while(Mirf.isSending()){};
      Serial.println("Sending... ");
      Serial.print("Retransmit ");
      Serial.println(request.Retransmit);
      Serial.print("Request ");
      Serial.println(request.Request);
      Serial.print("Time ");
      Serial.println(request.Time);
      delay(1000);
    Приниматель принимает совсем жуть:
    Код (Text):
    Beginning ...
    0001
    .. 0
    Request 0
    Time 9016
    Beginning ...
    0001
    .. 0
    Request 0
    Time 10018
     
    А судя по "Beginning ..." всё время перезагружается. Вопрос, собственно не в том, откуда он берёт мусор, а почему этот мусор летит с вещателя...

    UPD2:
    Хм. Как я понимаю, из-за того что я передаю указатель на структуру в Mirf.send, передаётся белиберда из памяти, размером с Payload. Нужно найти способ указать Mirf.send размер передаваемых данных...

    UPD3:
    Попробовал в Mirf.cpp переписать вызов функции отправки в void Nrf24l::send с transmitSync(value,payload); на transmitSync(value,(sizeof(value)/sizeof(value[0]))); ничего не изменилось.
     
    Последнее редактирование: 7 ноя 2013
  9. Megakoteyka

    Megakoteyka Оракул Модератор

    Строчка Mirf.getData((byte*)&request); записывает PAYLOAD байт по адресу начала структуры. Т.к. структура имеет размер 6 байт, все остальное пишется дальше и портит данные, которые лежат в памяти после структуры. Размер структуры должен совпадать с PAYLOAD, иначе будут неизбежные глюки.

    Mirf.getData тупо пишет PAYLOAD байт по тому адресу, что ей передан. В Вашем случае по этому адресу сидит структура размером 6 байт. Mirf.getData записывает эти 6 байт, а потом еще 26 байт после структуры и этим самым портит данные, лежащие в памяти после структуры.
    Можете проверить: выдайте в порт 32 байта по адресу структуры до вызова Mirf.getData, а потом то же самое после вызова и сравните разницу.

    Думайте свой собственный протокол, чтобы передавать данные произвольной длины.
    В простейшем случае возьмите простой пакетный протокол с заголовком, длиной пакета и контрольной суммой. Передавайте байты пакета по отдельности и на приеме из них снова формируйте пакет.
     
  10. uscr

    uscr Нерд

    Эх! Всегда радовался тому, что arduino даёт возможность не вникать в тонкости. Протокол, получается, всё равно упрётся в payload и мне придётся добивать неполный пакет нулями до 32 байт?
    А нет известных готовых реализаций? А то мне, честно говоря, гораздо больше нравится паять и созерцать мигающие лампочки, чем программировать сами устройства.
     
  11. Megakoteyka

    Megakoteyka Оракул Модератор

    Либо добивать пакет и на приеме учитывать это, либо передавать каждое слово пакета отдельной посылкой. В простейшем случае - передавать отдельной посылкой каждый байт пакета.
    Если принять, что размер пакета всегда кратен 2-м, можно передавать по 2 байта и так далее.
    Тогда общая длина пакета никак не будет зависеть от размера payload. Максимальной пропускной способности достичь не удастся, но реализация будет самая простая.
    Готовые реализации пакетного протокола можно на форуме поискать - тут это много обсуждалось применительно к передаче через последовательный порт.
     
  12. uscr

    uscr Нерд

    Я снова вернулся. Решил пока обойтись без протокола, добив структуру до 32 байт.

    Передатчик:
    Код (Text):
    #include <SPI.h>
    #include <Mirf.h>
    #include <nRF24L01.h>
    #include <MirfHardwareSpiDriver.h>
    #include <Communicator.h>

    #define SendCommandTimeout 3000
    #define ScanTimeout 250
    #define Retransmits 5 //количество попыток отправки

    unsigned long time;  // Время для отслеживания таймаута при запросе данных

    void setup(){
      Serial.begin(9600);
      Mirf.cePin = 9;
      Mirf.csnPin = 10;
      Mirf.spi = &MirfHardwareSpi;
      Mirf.init();
      Mirf.setRADDR((byte *)"MASTER");
      Mirf.payload = PAYLOAD;
      Mirf.config();
      Serial.println("Beginning ... ");

      scan();
    }

    void loop() {
      BroadcastCommand(1);
      delay(5000);
    }

    void BroadcastCommand(byte com) {
      for(byte i=0; i<SLAVE_COUNT; i++) {
        SlaveInfo &slave = slaves[i];
        if (slave.Online) {
            Serial.print("Send request to ");
            Serial.println((char*)slave.Desc);
            SendRequestStruct request;
            request.Request=1;
            request.Time=millis();
         
            SendAnswerStruct ans;
         
            SendData(&slave, &request, &ans, com, Retransmits);
            Serial.print((char*)slave.Desc);
            Serial.print(" return ");
            Serial.print(ans.UL_Answer);
            Serial.print(" with status: ");
            Serial.println(ans.Status);
        }
      }
    }

    void SendData(SlaveInfo *slave, SendRequestStruct *request, SendAnswerStruct *ans,
                  byte com, byte max_retransmit)
    {
      boolean exit=false;
      byte retransmit=0;

      Serial.println("Set TADDR");
      Mirf.setTADDR((byte*)slave->TADDR);
      while(!exit) {
        Serial.println("Set request->Retransmit");
        request->Retransmit=retransmit;
        Serial.println("Mirf.send");
        Mirf.send((byte *)&request);
        while(Mirf.isSending()){};
        Serial.println("check time");
        time = 0;
        Serial.println("Wait answer");
        while(!Mirf.dataReady()) {
          Serial.println("In 1st while");
          if ( ( millis() - time ) > SendCommandTimeout ) {
            retransmit++;
            if (retransmit>max_retransmit) {ans->Status=255; exit=true;}
            break;
          }
        }
        if (Mirf.dataReady()) {
          Mirf.getData((byte *) &ans);
          exit=true; break;
        }
      }
        Serial.print("Sending to: ");
        Serial.println(slave->TADDR);
        Serial.print("Retransmit: ");
        Serial.println(retransmit);
        Serial.println("----------------------");
    }

    void scan() {
      SendRequestStruct request;
      request.Retransmit=0;
      request.Request=0;
      request.Time=millis();
      ScanAnswerStruct scan_ans;
      for(int i=0; i<SLAVE_COUNT; i++) {
        SlaveInfo &slave = slaves[i];
        slave.TADDR=Slaves_TADDR[i];
        Mirf.setTADDR((byte*)slave.TADDR);
        Mirf.send((byte *)&request);
        while(Mirf.isSending()){};
        time = millis();
        while(!Mirf.dataReady()){
          if ( ( millis() - time ) > ScanTimeout ) {
            Serial.print(slave.TADDR); Serial.println(" Offline...");
            slave.Online=false; delay(50); break;}
        }
        if (Mirf.dataReady()) {
            Mirf.getData((byte *) &scan_ans);
            slave.Last_connect=millis();
            for (byte i=0; i<SCAN_StringLen; i++) {
              slave.Desc[i]=scan_ans.Comment[i];
            }
            slave.Online=true;
            slave.Status=true;
        }
      }
    }
     
    Код на приёмнике не важен, Communicator.h может пригодиться:
    Код (Text):
    #ifndef Communicator
    #define Communicator

    #define PAYLOAD 32
    #define SCAN_StringLen 27 //Длина строк в структурe ответа при сканировании
    #define ANS_StringLen 23 //Длинна строки в структуре простого ответа

    char* Slaves_TADDR[]={ //Адреса модулей
    "0001","0002","0003","0004","0005",
    };
    byte SLAVE_COUNT=sizeof(Slaves_TADDR)/sizeof(Slaves_TADDR[0]);

    typedef struct _REQUEST_STRUCTS { //Структура запроса
      byte Retransmit; //0 - первое сообщение, иначе - номер ретрансмита
      byte Request;
      unsigned long Time;
      byte bytes[26]; //Тупо для соответствия размеру буфера, но может передаваться полезная нагрузка
    }SendRequestStruct;

    typedef struct _ANSWER_STRUCTS { //Структура ответа
    byte Status; //0 - всё ок, 1 - Ответ на ретрансмит, 255- ошибка передачи;
    //Разные переменные для разных типов ответов:
    byte B_Answer;
    float F_Answer;
    unsigned long UL_Answer;
    byte Comment[ANS_StringLen];//Текст
    }SendAnswerStruct;

    typedef struct _SCAN_ANSWER_STRUCTS { //Структура ответа
    byte Status; //0 - всё ок, 1- ошибка;
    unsigned long Time;
    byte Comment[SCAN_StringLen];//Текст
    }ScanAnswerStruct;


    typedef struct _SLAVE_INFO { //Структура для хранения информации о модулях
                    //на ведущем устройстве
      unsigned long Last_connect; // Когда последний раз обновляли статус датчика
      char* TADDR; // Адрес модуля
      byte Desc[SCAN_StringLen]; //Описание модуля
      bool Online; //Обновляется при сканировании
      bool Status; //Есть ли связь

    }SlaveInfo;

    SlaveInfo slaves[20] = {};

    #endif // Communicator
     
    Как видно, SendData полна отладочного вывода. И это неспроста. Вот, что приходит на последовательный порт (тут виден отладочный вывод из scan, который я убрал из кода, что бы не мешался):


    Код (Text):
    Beginning ...
    Scanning...
    Scan 0001
    0 say: Very long string with spac
    Time on 0 is 6462328
    Scan 0002
    0002 Offline...
    Scan 0003
    0003 Offline...
    Scan 0004
    0004 Offline...
    Scan 0005
    0005 Offline...
    Send request to Very long string with spac
    Set TADDR
    Set request->Retransmit
    Mirf.send
    check time
    Wait Beginning ...
    Scanning...
    Scan 0001
    0 say: Very long string with spac
    Time on 0 is 6465097
    и так далее... †
    "Very long string with spac" - это правильно. Именно такая строка и должна передаваться, больше не влезает в массив. Остальное - печально.
    Так же, подозреваю, что я перемудрил с указателями, потому что раньше простой (простой==без ретрансмитов) был втиснут прямо в BroadcastCommand и всё было хорошо.
     
  13. -Mark-

    -Mark- Гик

  14. uscr

    uscr Нерд

    Я извиняюсь, но либо вы не поняли вопрос, либо я не уловил суть в той темке. Протокол не нужен :)
    Я всё таки "перемудрил с указателями". В процедуре SendData строка Mirf.getData((byte *) &ans); должна выглядеть вот так: Mirf.getData((byte*)ans);