да сколько раз говорить - не будут совпадать (точнее - не обязаны) смещения адресов у полей структуры на 8-ми битной машине и на 32-х битной. Так понятно?
#pragma pack(push, 1) и там и там - спасёт, если использовать универсальные типы, int16_t, uint8_t и т.д.
предлагаю соломоново решение. создать буфер в виде массива (пакет) типа например uint8_t buf[SIZE]; и структуру указателей struct StrSend { char* testStr; uint16_t* test; uint8_t* crc; }; и эти указатели нацелить на нужные места массива, а пересылать туда-сюда собственно сам массив без всяких побочных эффектов
Так об этом я @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 Объяснять нужно???
а почему там где в меге напечаталась какая-то дичь DB 2 7 0 7 ... в есп все равно строчка "testing"? там чтоль String как-то иначе работает? и вообще мне непонятно что это за дичь. вряд ли это адрес на который показывает указатель
Да, не заметил, что там "testing" в начале. Я String не пользуюсь совсем, нужно смотреть String, разбираться.
вот это мне и непонятно больше всего String в структуре - это что? очевидно в меге и леонарде это не сама строка, но что это? адрес? чего? и не факт что адрес. а в есп если ты не ошибся - получается это сама строка. загадочно призываю аспер даффи. только ему под силу подобные тайны DB 2 7 0 7
На Меге 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 Вот...
Какая разница какие типы я использую? Сегодня одни, завтра в другом проекте, буду другие использовать. Вот сейчас выяснилось, что со строками esp и ардуины по-разному работают. Если есть решение, то всем может пригодится. Если решения нет - тоже поможет не наступать на эти грабли.
лучше не считать String строками. потому что строка это массив чаров с нулем на конце, а стринг это неведомый объект с кучей свойств и тонкостей, еще и сильно зависящих от реализации.
В общем экспериментальным путем выяснено, что на 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
Прошу прощения, если не в тему. Вот: Код (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 между разными устройствами. Вот как-то так. И простите, если что!
Честно говоря не встречал где #pragma pack(1) по разному работает с данными типа uint8_t, uint16_t. Переменные типа int, unsigned могут отличаться размером на разных платформах, например на ESP int - 4 байта, на AVR - 2 байта.
В общем вопрос со String не связан со структурами, а связан именно со String и платформой (AVR, ESP). Можно убрать структуры, оставить только два String-а Спойлер: Код для Mega2560 и для ESP8266 Код (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, если строка короткая - видим строку, длинная - адрес.
Я не хочу спорить - ведь заранее извинился. Но встретите ещё, когда код вроде и одного и того же проекта перестанет работать на разных устройствах... то длина не та, то данные почему-то не там где надо. Вот я и сказал - надо испытывать на связи между разными устройствами. Вот к примеру как будет такое: Код (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 из головы вылетит сразу.
Так а тут то что? Естественно валится предупреждение: Код (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
Потому, что String, и там не только адрес строки. Тут можно почитать, а вообще нужно разбираться, если уж хочется пользоваться String в МК и хочется понимать, как все это работает и где и как размещается данные в памяти. Как выясняется есть еще и разные реализации... ИМХО, не нужен String в МК. Я как то без String жил, и не страдал.