Передача структуры

Тема в разделе "ESP8266, ESP32", создана пользователем GeoShu, 27 дек 2021.

  1. parovoZZ

    parovoZZ Гуру

    да сколько раз говорить - не будут совпадать (точнее - не обязаны) смещения адресов у полей структуры на 8-ми битной машине и на 32-х битной. Так понятно?
     
    GeoShu нравится это.
  2. DetSimen

    DetSimen Гик

    #pragma pack(push, 1) и там и там - спасёт, если использовать универсальные типы, int16_t, uint8_t и т.д.
     
    GeoShu нравится это.
  3. akl

    akl Гуру

    предлагаю соломоново решение.
    создать буфер в виде массива (пакет)
    типа например
    uint8_t buf[SIZE];
    и структуру указателей
    struct StrSend {
    char* testStr;
    uint16_t* test;
    uint8_t* crc;
    };
    и эти указатели нацелить на нужные места массива, а пересылать туда-сюда собственно сам массив без всяких побочных эффектов
     
  4. DetSimen

    DetSimen Гик

    нахрена так сложно? ТС пока так и не ответил, какие типы входят в его структуру. Я подожду.
     
  5. SergeiL

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

    Так об этом я @GeoShu еще в первом ответе написал, но он же так и не прислушался.

    Повторяю еще раз,
    Код ТС подправил, убрал тип String, он здесь совсем не в тему:
    Код (C++):
    //#pragma pack(1)
    struct StrSend {
      char     buff[10];
      uint16_t test;
      uint8_t  crc;
    };
    StrSend dataTx;
    //#pragma pack()

    byte  crc8(byte *buffer, byte size)
    {
        byte crc = 0;
        for(byte i = 0; i < size; i++){
            byte data = buffer[i];
            for(int j = 8; j > 0; j--){
               crc = ((crc ^ data) & 1) ? (crc >> 1) ^ 0x8C : (crc >> 1);
               data >>= 1;
            }
        }
        return crc;
    }
    void setup () {

      uint8_t  *ptr;
      uint8_t  i;

      Serial.begin(115200);

      Serial.println("Start");

      strcpy (dataTx.buff,"testing");
      dataTx.crc = crc8((byte*)&dataTx, sizeof(dataTx) - 1);
      byte crc = crc8((byte*)&dataTx, sizeof(dataTx));
      Serial.println(crc);
      Serial.println(sizeof(dataTx));
      ptr=(uint8_t*)&dataTx;

      for (i=0; i<sizeof(dataTx); i++)
      {
        Serial.print(*(ptr+i),HEX);
        Serial.print(" ");
      }
    }
    void loop()
    {

    }
     
    Компилируем без изменений, запускаем на Меге (у ТС Нано, но это почти без разницы если нет указателей) получаем следующий вывод:
    Код (Text):
    0
    13
    74 65 73 74 69 6E 67 0 0 0 0 0 EF
    ( 0 - CRC, 13 - размер структуры в байтах, последняя строка - данные памяти структуры)


    Компилируем без изменений, запускаем на ESP, получаем следующий вывод:
    Код (Text):
    245
    14
    74 65 73 74 69 6E 67 0 0 0 0 0 A8 0

    Раскомментируем #pragma pack (1) / #pragma pack()
    Компилируем, запускаем на ESP, получаем следующий вывод:
    Код (Text):
    0
    13
    74 65 73 74 69 6E 67 0 0 0 0 0 EF


    Со Cтрингом без #pragma pack (1) / #pragma pack() на ESP структура будет еще больше из за размерности указателя:
    Мега:
    Код (Text):
    0
    9
    DB 2 7 0 7 0 0 0 19
    ESP :
    Код (Text):
    102
    16
    74 65 73 74 69 6E 67 0 0 0 0 0 0 0 98 0
     
    ESP c #pragma pack (1) / #pragma pack():
    Код (Text):
    0
    15
    74 65 73 74 69 6E 67 0 0 0 0 0 0 0 6D
     
    Объяснять нужно???
     
    DetSimen, akl и GeoShu нравится это.
  6. akl

    akl Гуру

    а почему там где в меге напечаталась какая-то дичь DB 2 7 0 7 ... в есп все равно строчка "testing"? там чтоль String как-то иначе работает?

    и вообще мне непонятно что это за дичь. вряд ли это адрес на который показывает указатель
     
    SergeiL нравится это.
  7. SergeiL

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

    Да, не заметил, что там "testing" в начале.
    Я String не пользуюсь совсем, нужно смотреть String, разбираться.
     
  8. akl

    akl Гуру

    вот это мне и непонятно больше всего
    String в структуре - это что? очевидно в меге и леонарде это не сама строка, но что это? адрес? чего? и не факт что адрес. а в есп если ты не ошибся - получается это сама строка.
    загадочно

    призываю аспер даффи. только ему под силу подобные тайны DB 2 7 0 7
     
  9. SergeiL

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

    На Меге DB 02 07 00 07 00 - точно адрес.

    А вот на ESP, похоже данные в памяти структуры зависят от длины строки.
    Потому, что если строка "testing" данные в структуре:
    Код (Text):
    0
    15
    74 65 73 74 69 6E 67 0 0 0 0 0 0 0 6D
    А если строка "testing - testing"
    то данные:
    Код (Text):
    0
    15
    F4 EF FE 3F 1F 0 11 0 0 0 0 FF 0 0 69
     
    Вот...
     
  10. GeoShu

    GeoShu Гик

    Какая разница какие типы я использую? Сегодня одни, завтра в другом проекте, буду другие использовать. Вот сейчас выяснилось, что со строками esp и ардуины по-разному работают. Если есть решение, то всем может пригодится. Если решения нет - тоже поможет не наступать на эти грабли.
     
  11. akl

    akl Гуру

    лучше не считать String строками. потому что строка это массив чаров с нулем на конце, а стринг это неведомый объект с кучей свойств и тонкостей, еще и сильно зависящих от реализации.
     
    GeoShu нравится это.
  12. SergeiL

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

    В общем экспериментальным путем выяснено, что на ESP если:
    String <= 11 символов (без завершающего 0) то в структуре будет 12 байт. 11 символов + 0.
    String >= 12 символов (без завершающего 0) то в структуре будет указатель + FF (двенадцатым байтом).
    Код (C++):
    #pragma pack(1)
    struct StrSend {
      String testStr;
      uint16_t test;
      uint8_t  crc;
    };
    StrSend dataTx;
    #pragma pack()

    byte  crc8(byte *buffer, byte size)
    {
        byte crc = 0;
        for(byte i = 0; i < size; i++){
            byte data = buffer[i];
            for(int j = 8; j > 0; j--){
               crc = ((crc ^ data) & 1) ? (crc >> 1) ^ 0x8C : (crc >> 1);
               data >>= 1;
            }
        }
        return crc;
    }
    void setup () {

      uint8_t  *ptr;
      uint8_t  i;

      Serial.begin(115200);

      Serial.println("Start");

      dataTx.testStr = "testing1234";
      dataTx.test    = 0xAA55;
      dataTx.crc = crc8((byte*)&dataTx, sizeof(dataTx) - 1);
      byte crc = crc8((byte*)&dataTx, sizeof(dataTx));
      Serial.println(crc);
      Serial.println(sizeof(dataTx));
      ptr=(uint8_t*)&dataTx;

      for (i=0; i<sizeof(dataTx); i++)
      {
        Serial.print(*(ptr+i),HEX);
        Serial.print(" ");
      }
      Serial.println("");
      Serial.println(dataTx.testStr);
    }
    void loop()
    {

    }
     
    Код (Text):
    dataTx.testStr = "testing1234";

    0
    15
    74 65 73 74 69 6E 67 31 32 33 34 0 55 AA 12
    testing1234

    ----------------------------------------------------------------

    dataTx.testStr = "testing12345";

    0
    15
    E4 EF FE 3F F 0 C 0 0 0 0 FF 55 AA A6
    testing12345
     
    DetSimen и Рокки1945 нравится это.
  13. Igor68

    Igor68 Гуру

    Прошу прощения, если не в тему.
    Вот:
    Код (C++):
    typedef struct
    {
            uint64_t        Time;
            uint64_t        TimClock;
            float           U220;                   //напряжение питания
            float           F220;                   //частота напряжения питания
            uint16_t        F1_RdInIO;              //прочитанная маска входы IO
            uint16_t        F1_RdOutIO;             //прочитанная маска выходы IO
            uint16_t        F1_WrOutIO;             //знаения для выходов IO
            uint8_t         constat_F1_RdInIO;      //статус связи чтения устойства ввода(IN)
            uint8_t         constat_F1_RdOutIO;     //статус связи чтения устойства вывода(IN)
            uint8_t         constat_F1_WrOutIO;     //статус связи чтения устойства вывода(OUT)
            uint8_t         constat_Upower;         //статус связи с устройством мониторинга питания
    } F1DATA;
    и
    Код (C++):
    typedef struct
    {
            uint64_t        Time;
            uint64_t        TimClock;
            uint64_t        SetTWaitPUMPelOFF;      //заданное время ожидания выключения насоса электрооборудования
            uint64_t        SetTWaitPUMPindOFF;     //заданное время ожидания выключения насоса индукторов
            uint64_t        SetTWaitPUMPextOFF;     //заданное время ожидания выключения насоса внешнего контура
            float           SetTFur;                //температура индукторов(поддержание)
            float           SetHTFur;               //гистерезис температуры индукторов(поддержание)
            float           SetTEl;                 //температура электрооборудования(поддержание)
            float           SetHTEl;                //гистерезис температуры электрооборудования(поддержание)
            float           SetTDeltaFur;           //Раница температур прямой и обратки температуры контура индуторов(выключение)
            float           SetTDeltaEl;            //Раница температур прямой и обратки температуры электрооборудования(выключение)
            float           SetTelCoolON;           //заданная температура включения насоса электрооборудования от заморозки
            float           SetTindCoolON;          //заданная температура включения насоса индукторов от заморозки
            uint8_t         src;                    //источник задания уставок(0 - модуль управления, иначе внешний источник)
            uint8_t         XorCRC;                 //контроль достоверности данных
    } SETPARAM_COOL;
    Тут была долгая "битва" по поводу выравнивания и #pragma pack(push,1) и #pragma pack(1) и прочее применялось. Обмен шел(и сейчас идет исправно) между x86(всякие интелы и амд), ARM9(moxa). Везде ранее была разная длина в передаче от одной платформы к другой. В обиходе и GCC и WisualStudio в системах Debian, WinXP, Win7/8, Linux устройства(moxa). И вывод был сделан - самые "тяжелые" типы в начале, и по мере облегчения вниз структуры. Выравнивать приходилось и вручную, зачастую добавлением пустых переменных. Жаль что pragma pack на разных компиляторах по разному. Даже голый GCC и QtCreator с компилятором по умолчанию по разному. Надо строить сразу вручную. И @parovoZZ верно сказал - массив, где все переменные выровнены... искать компромисс надо, если разные платформы. В моём случае обмен по ethernet между разными устройствами.
    Вот как-то так.
    И простите, если что!
     
    parovoZZ нравится это.
  14. SergeiL

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

    Честно говоря не встречал где #pragma pack(1) по разному работает с данными типа uint8_t, uint16_t.
    Переменные типа int, unsigned могут отличаться размером на разных платформах, например на ESP int - 4 байта, на AVR - 2 байта.
     
  15. SergeiL

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

    В общем вопрос со String не связан со структурами, а связан именно со String и платформой (AVR, ESP).
    Можно убрать структуры, оставить только два String-а

    Код (C++):
      String Str_1;
      String Str_2;
     
      char   buff[300];

    void setup () {
     
      uint8_t  *ptr;
      uint8_t  i;
      char     buff_tmp[5];

      Str_1 = "t";
      Str_2 = "testing12345";
     
      Serial.begin(115200);
      Serial.println("");
      Serial.println("");
      Serial.print("Str_1 = ");
      Serial.println(Str_1);
     
      sprintf(buff,"sizeof(Str_1))= %d\n",sizeof(Str_1));
      Serial.print(buff);
     
      ptr=(uint8_t*)&Str_1;
      buff[0]=0;
      for (i=0; i<sizeof(Str_1); i++)
      {
        sprintf(buff_tmp,"%02X,",*(ptr+i));
        strcat( buff,buff_tmp);
      }
      Serial.println(buff);
     
      Serial.print("\n\rStr_2 = ");
      Serial.println(Str_2);
     
      sprintf(buff,"sizeof(Str_2))= %d\n",sizeof(Str_2));
      Serial.print(buff);
     
      ptr=(uint8_t*)&Str_2;
      buff[0]=0;
      for (i=0; i<sizeof(Str_2); i++)
      {
        sprintf(buff_tmp,"%02X,",*(ptr+i));
        strcat( buff,buff_tmp);
      }
      Serial.println(buff);
     
    }
    void loop()
    {

    }
     

    Вывод данных в монитор порта на Mega2560:
    Код (Text):
    Str_1 = t
    sizeof(Str_1))= 6
    50,04,01,00,01,00,


    Str_2 = testing12345
    sizeof(Str_2))= 6
    54,04,0C,00,0C,00,
     
    Вывод данных в монитор порта на ESP8266:
    Код (Text):
    Str_1 = t
    sizeof(Str_1))= 12
    74,00,00,00,00,00,00,00,00,00,00,00,


    Str_2 = testing12345
    sizeof(Str_2))= 12
    24,F0,FE,3F,0F,00,0C,00,00,00,00,FF,
     
    На Меге мы видим два адреса, вне зависимости от длины строки.
    На ESP8266, если строка короткая - видим строку, длинная - адрес.
     
    akl нравится это.
  16. Igor68

    Igor68 Гуру

    Я не хочу спорить - ведь заранее извинился. Но встретите ещё, когда код вроде и одного и того же проекта перестанет работать на разных устройствах... то длина не та, то данные почему-то не там где надо. Вот я и сказал - надо испытывать на связи между разными устройствами.
    Вот к примеру как будет такое:
    Код (C++):

    #define _AV1  0x33
    #define _AV2  0x08

    uint8_t   data[0xFF];

    uint32_t  Val1 = (*(uint32_t*)(&data[_AV1]));
    uint32_t  Val2 = (*(uint32_t*)(&data[_AV2]));
     
    попробуйте сформировать на 8-ми битном и 32-х битном, а потом обменять между собой. Да, верно тут грубое нарушение выравнивания. Вот только для 8-ми битного вроде всё нормально и никаких проблем. Жаль что pragma pack работает по разному... хуже того от компилятора к компилятору даже инструкция разно пишется. Один понимает так: #pragma pack(push,1), для другого приемлемо так:#pragma pack(1). А Вы на цифру-то не смотрите, я про синтаксис. Потому и выравниваю сам вручную, хотя наверное просто не очень в смысле знания. Но какие познания в 2008 году у меня могли быть в момент, когда контроллер смонтирован на DIN рейке, автоматика подключена, надо написать программу, перед этим испытать протокол обмена "ТЕНЗО-М", так же испытать обмен по ModbusRTU, визуально контролировать состояние устройств. Тут тебе и летняя жара в цехе, где-то в 20-м кто-то молотком по железу БАХ-БАБАХ, тут погрузчик дизельный что-то выгружает. А ты с ноутом рядом с вибродозатором. Романтика, верно? Думаю и pragma pack из головы вылетит сразу.
     
    GeoShu нравится это.
  17. akl

    akl Гуру

    почему адрес 6 байтов? а в есп ваще 12?
     
  18. SergeiL

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

    Так а тут то что?
    Естественно валится предупреждение:
    Код (Text):
    C:\Users\Documents\Arduino\Tests\pointer_tst\pointer_tst.ino:19:43: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

    uint32_t  Val1 = (*(uint32_t*)(&data[_AV1]));

                                               ^

    C:\Users\Documents\Arduino\Tests\pointer_tst\pointer_tst.ino:20:43: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]

    uint32_t  Val2 = (*(uint32_t*)(&data[_AV2]));
     
    А если сделать так:
    Код (C++):
    #define _AV1  0x33
    #define _AV2  0x08

    void setup () {

      uint8_t   data[0xFF];
      uint16_t  i;

      Serial.begin(115200);
      Serial.println("");
      Serial.println("");
      Serial.println("Start:");

      for (i=0; i < 0xFF; i++)
      {
        data[i] = (uint8_t) i;
      }

    uint32_t  *Val1 = (uint32_t*)  &(data[_AV1]);
    uint32_t  *Val2 = (uint32_t*)  &(data[_AV2]);

      Serial.print("Val1 = ");
      Serial.println(*Val1,HEX);

      Serial.print("Val2 = ");
      Serial.println(*Val2,HEX);

    }
    void loop()
    {

    }

    То предупреждения уже нет :).

    И вывод одинаковый, что на Меге2560, что на ESP:

    Mega2560:
    Код (Text):
    Start:
    Val1 = 36353433
    Val2 = B0A0908
    ESP-8266:
    Код (Text):
    Start:
    Val1 = 36353433
    Val2 = B0A0908
     
    Последнее редактирование: 28 дек 2021
    GeoShu нравится это.
  19. SergeiL

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

    Потому, что String, и там не только адрес строки.
    Тут можно почитать, а вообще нужно разбираться, если уж хочется пользоваться String в МК и хочется понимать, как все это работает и где и как размещается данные в памяти.
    Как выясняется есть еще и разные реализации...

    ИМХО, не нужен String в МК.
    Я как то без String жил, и не страдал.