Преобразователь CAN Ethernet и обратно

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
Тема - программирование конструирование.
Мне нужна помощь с -
Вопросы:
1) Конверсия числа из байтов в UINT32?
2) CAN id, почему используется операция И 0х1FFFFFF? И из за этого ли конечные устройства не понимают когда идет обращение к ним?

Источники внизу.

С характеристиками следующее:
2 платы Uno и 2 МСР2515, 2 Интернет шилда на базе W5100.

Соединены по схеме
Соединение MCP2515 с Uno следующее:

MCP2515​
Uno​
INT​
2​
SCK​
13​
SI​
11​
SO​
12​
CS​
10​
GND​
GND​
VCC​
+5V​
Контакты INT и CS могут быть переназначены в скетче.

1645618323419.jpeg

Установка шилда на ардуино в описании не нуждается, собрать как лего.
На плате MCP2515 устанавливается перемычка J1 - терминирующий резистор.

Первая плата - та у которой порт 2000, вторая - порт 2001.
Эта связка включена в разрыв шины, передача двунаправленная, проверено - работает.
Конечные устройства теряют связь друг с другом из за проблем с пакетами.


1)
Проблема - конвертация числа UINT32 из 4х Uchar
если использовать метод
1645613417766.png
то в результате любое число будет FFFFxxxx где x это нормальная часть числа без потерь.
если использовать вариант:
unsigned long number3 = pow(256, 0) * bytes[0] + pow(256, 1) * bytes[1] + pow(256, 2) * bytes[2] + pow(256, 3) * bytes[3];
то число будет иметь вид хххххх00 где х это нормально восстановленная часть числа а 00 всегда в конце

Почему так происходит? В VS19 тестовый пример с обеими данными функциями не приводит к потере фрагментов числа.

2)
Теперь вторая часть, касаемо формата CAN rxID он имеет расширение UINT32 зачем в тестовом примере сделано вот так? (См выделенную желтым область)
1645613753594.png
Прилагаю разъяснение работы устройств.
Для работы с UDP используется библиотека <EthernetUdp.h>
Подготовка параметров выглядит так:
1645614005513.png
1645614077413.png
Указываем желаемый MAC и IP. IP dest это целевой адрес устройства с которым будем связывать первую ардуину, а REMOTE_PORT соответственно куда мы будем посылать данные.
SERV_PORT будет ожидать данные от второго устройства.

Передача данных начинается с того, что нужно прочитать посылку CAN - ее читать с помощью следующей функции:
с начала проверяем какой уровень сигнала на контакте CAN0_UNT и если он высокий значит есть посылка.
в функцию readMsgBuf должно быть передано три параметра - 1 идентификатор пакета, длина (DLC) и массив данных, в случае чтения эти параметры будут перезаписаны да же если в них есть данные.
далее создаем пакет на 13 элементов где 0-3 будет хранить 4 байта числа UINT32, 4 - количество данных (DLC), и оставшиеся 5-12 блок данных.
После окончания формирования массива передадим его в функцию отправки UDP для этого вызвав метод write() в него необходимо передать массив и размер в байтах, в данном случае с типом char размер будет 13 байт.
функция НЕ умеет работать с UINT32 и больше.
А что бы отправить данные конкретному устройству - в функции beginPacket() нужно передать IP адрес и порт того устройства которому это все адресовано.
1645614353519.png
Не забываем endPacket(), иначе данные не отправятся.
Работа с CAN:
Инициализировать объект класса следующими параметрами.
begin()
MCP_ANY - не знаю что такое.
KBPS - скорость CAN шины, в текущем устройстве будет 50.
MHZ - частота работы кварца модуля МСР2515 (есть версии на 16 МГц).
setMode()
режимы работы (информации об этом нет) мне известно что режим Normal обеспечивает прием/передачу в нормальном режиме.
На рисунке показаны все доступные режимы работы.
1645615364213.png
pinMode()
выбрать контакт назначенный на прерывания при получении посылки.
1645615116337.png
Теперь для работы с отправкой сообщений нужно создать буфер с подготовленными данными.
Поскольку они получены по UDP в том же виде как мы их передали то их необходимо собрать.
Первые 4 байта это будущее число UINT32 хранящее rxID.
Восстановим число с помощью метода pow(), но на конце будет всегда 00, поэтому по завершению преобразования прибавим еще раз 0 элемент полученного пакета.
1645615676885.png
В примере используется дополнительно объявленный буфер, на самом деле он не нужен и лишь лишний. Работать можно сразу с полученными данными.

Теперь после формирования rxID нужно сформировать блок данных пакета CAN, теперь уже нужно создать отдельный массив на 8 элементов и перенести в него 5-12 элементы принятого массива.
1645616171011.png
Теперь можно отправлять посылку вызовом метода sendMsgBuf() в нее нужно передать наше число UINT32 - rxIDб.
Второе число 1 - *ext, это переменная отвечающая за выбор типа посылки (Если 0 то STANDART а если 1 то EXTENDED) если будет выбор стандартного пакета то половина rxID будет потеряна и посылка отправится с коротким ID пакета.
1645616300591.png
И обязательно не забываем про включение режима однократной отправки enOneShotTX().
Если не использовать эту команду то модуль МСР2515 имеющий 3 буфера сообщений будет бесконечно, на максимальной скорости слать последние 3 сообщения.
Об этом нет информации нигде просто, я догадался сам анализируя голую библиотеку CAN рассматривая в блокноте.
что бы выключить этот режим вызовите функцию disOneShotTX().
Так же прилагаю полный список функций класса CAN
1645616828378.png1645616839261.png
Большинству этих функций описание не известно, я бы сказал его вообще нет. Применение функций можно увидеть только в тестовых скетчах идущих с библиотекой.
Далее в функцию передаем 4 элемент - который хранит длину поля данных (DLC) и передаем блок данных.
Не стоит волноваться о том что при DLC = 4 блок будет на 8 элементов, функция отправки обрежет сообщение до 4х ячеек и отправит.
На этом отправка сообщения завершена и МСР2515 готов к получению данных.

Моя система работает в обе стороны одновременно.
В текущий момент есть проблемы - не корректно собирается rxID, конечные устройства теряют связь друг с другом поскольку что то не так с rxID.

Полезные источники:
1) Библиотека которая поддерживает сразу CAN EXTEND+STANDART frames, CAN2.0B https://github.com/coryjfowler/MCP_CAN_lib
2) Datasheet для МСР2515 https://ww1.microchip.com/downloads...d-Alone-CAN-Controller-with-SPI-20001801J.pdf
3) Описание команд для UDP с примерами http://mypractic.ru/urok-64-tcp-server-i-klient-na-arduino-biblioteka-uipethernet.html
4) Если нет второй ардуины то можно достучаться к ней с ПК http://mypractic.ru/urok-69-protoko...hyu-biblioteki-uipethernet.html#comment-72076

Примечание: если вы используете какую то другую библиотеку MCP и CAN не с этого репозитория (см источник 1) то эта информация описанная выше не актуальна.
Каждый (не) уважаемый программист создал свой клон библиотеки для работы с CAN и MCP2515 при этом не указав первоисточник.
Всякие can_hack lib сделаны на коленке и не умеют работать с extended фреймами. Так же can hack tool скетч ни разу не рабочий, он не видит да же обычные фреймы.
В примерах из этой библиотеки все работает, главное настроить скорость и не забыть про частоту.
 

Вложения

Изменено:

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
@bort707,
bytes[0] = recived_udp[0];
bytes[1] = recived_udp[1];
bytes[2] = recived_udp[2];
bytes[3] = recived_udp[3];

unsigned long rx_ID = pow(256, 0) * bytes[0] + pow(256, 1) * bytes[1] + pow(256, 2) * bytes[2] + pow(256, 3) * bytes[3]; //тераяет последний байт

rx_ID = rx_ID + bytes[0];
 

bort707

★★★★★★✩
21 Сен 2020
2,900
863
@xanstar6067, во первых, этот код опять вставлен неправильно. Почитайте уже, наконец, как вставлять код в сообщения.

А во-вторых, ЭТО НЕ ТОТ КОД.
Я вас просил вставить код со второй картинки, который идет сразу под строчками
1)
Проблема - конвертация числа UINT32 из 4х Uchar
если использовать метод
вставьте код с той картинки без изменений, прямо так как он у вас там. Не надо его "сокращать". Вы потеряли важные детали. Пока вы не разбираетесь в коде - копируйте его буквально, ибо вы пока не понимаете, что важно, а что нет.
 
Изменено:

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
@bort707, как на этой картинке?
1645622484199.png
unsigned long rx_ID = (recived_udp[3] << 24) + (recived_udp[2]<<16) + (recived_udp[1] << 8) + recived_udp[0]; вот так?
в результате будет FFFFAA20 где FFFF будет всегда не зависимо от входных данных. Я приложил же скетчи.

Вот результат работы двух методов в студии
1645623230775.png
 
Изменено:

bort707

★★★★★★✩
21 Сен 2020
2,900
863
Я приложил же скетчи.
предлагаете мне самому искать в ваших скетчах этот кусок? - не пойдет, так далеко мое желание помочь не простирается...
Запомните - если вы задаете вопрос - то в ваших интересах сделать так, чтобы отвечающему БЫЛО УДОБНО отвечать на него.
А не хотите - разбирайтесь сами.
Без нормально вставленного кода могу только сказать - обратите внимание на типы переменных, в которые вы помещаете разряды при разложении числа на байты (на байты, ась?) и из которых вы потом это число собираете обратно.

И еще одно - вы там спрашивали, почему один и тот же код на ПК и в ардуино работает по разному.
Дело в размере типа инт - на ПК это 4 байта, а в Ардуино - 2 байта.
 

poty

★★★★★★✩
19 Фев 2020
2,989
895
Использовать pow в целочисленных вычислениях сильно избыточно и опасно потерями из-за ограниченной точности преобразования. pow(256,0), например, может вернуть 0,999999 вместо 1, что при преобразовании в целое станет =0.
В остальном
@bort707, прав, проблема промежуточных преобразований типов.
 
  • Лойс +1
Реакции: xanstar6067

bort707

★★★★★★✩
21 Сен 2020
2,900
863
После окончания формирования массива передадим его в функцию отправки UDP для этого вызвав метод write() в него необходимо передать массив и размер в байтах, в данном случае с типом char размер будет 13 байт.
функция НЕ умеет работать с UINT32 и больше.
выделенное черным (про не умеет работать) - вранье
Точнее сказать - из того, что эта функция ничего не знает про числовые типы большей разрядностью чем uint32 - вовсе не следует, что она не сможет отправить байтовый массив длиной 13 байт
У вас в голове каша, uint32 можно представить и как одно целое число, и как массив 4х байт. А для массива в 13 байт варианта "целое число" нет, но как массив-то оно существует... И именно как массив его можно отправить оператором write
 

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
@bort707, только я использую тип UINT32 (unsigned long) который 4 байта. Иначе бы не работал класс MCP. Я и так методом логики выяснил часть функций класса работы с CAN. Скрин с примером взял с форума ардуинщиков, или уже в Arduinо IDE 2.0 поменялись типы данных, конечно же нет.
Я в курсе что массив мой в 1 байтовых ячейках так как это чар, с этим и расчет был.
Но не может вот эта функция работать неправильно unsigned long rx_ID = (recived_udp[3] << 24) + (recived_udp[2]<<16) + (recived_udp[1] << 8) + recived_udp[0];
я на атмеге 8 такое делал с бобитовыми операциями и там было все нормально. я делал вывод моих 4 байт по отдельности, на бумажке сидел складывал и у меня сошлось с висуал студией. я уже просто не понимаю что упустил.
Может ли быть какая то оптимизация кода в ардуино ide? Взгляните пожалуйста на скетч.
начало блока разбивки числа строка 136 - 139.
блок с математикой по восстановлению 69 - 87. там же и старые попытки в другой метод.
всякие сериал принт не нужно учитывать, я их удалю.
 

bort707

★★★★★★✩
21 Сен 2020
2,900
863
не может вот эта функция работать неправильно
unsigned long rx_ID = (recived_udp[3] << 24) + (recived_udp[2]<<16) + (recived_udp[1] << 8) + recived_udp[0];
у вас все может!
третий раз - что мешает вам вставлять код в сообщения правильно???
не так
unsigned long rx_ID = (recived_udp[3] << 24) + (recived_udp[2]<<16) + (recived_udp[1] << 8) + recived_udp[0];
а вот так
C++:
unsigned long rx_ID = (recived_udp[3] << 24) + (recived_udp[2]<<16) + (recived_udp[1] << 8) + recived_udp[0];
разницу видите?

а теперь по сути - именно эта функция и работает НЕПРАВИЛЬНО. Точнее, работает в ардуино и в ПК по разному.

Прична - в том что в языке Си все вычисления, если не указано иное, делаются в размерности типа int. В ПК тип инт - 4 байта, а в ардуино - только два.
А теперь подумайте, что получится, если первую скобку вашей формулы попытаться вычислить в размере uint16 ? - дальше сами
 

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
@bort707, Понятно что не особо получится. А как тогда для ардуино это сделать? Есть функция которая собирает int из двух байт, для 4х только эти способыб не вижу других.
 

bort707

★★★★★★✩
21 Сен 2020
2,900
863
Понятно что не особо получится. А как тогда для ардуино это сделать?
там же оговорка есть -
в языке Си все вычисления, если не указано иное, делаются в размерности типа int.
то есть компилятор считает в типе инт, если программист явно не указал, в каком типе считать. Так укажите ему!
Вот так, например:
C++:
((uint32_t)recived_udp[3] << 24)
только не делайте так:
C++:
(uint32_t)(recived_udp[3] << 24)
ничего не выйдет.

А теперь обьясните, почему второй вариант не работает - в качестве проверки понимания - раз уж эта ветка "для студентов"
 
  • Лойс +1
Реакции: xanstar6067

poty

★★★★★★✩
19 Фев 2020
2,989
895
@bort707, единственная поправочка: умалчиваемых типов в С ни в определениях, ни в вычислениях нет. Берутся явные определения операндов, участвующих в операции, приводятся к операнду с максимальным размером и производится вычисление. Если операнд - константа, то ее тип берётся либо из явно указанных модификаторов, либо из ближайшего типа, в котором может быть такое значение.
В приведенной выше формуле: если reciver_udp имеет размерность 1 байт, то любой сдвиг в сторону старшего разряда - потеря данных.
 

bort707

★★★★★★✩
21 Сен 2020
2,900
863
@poty, вы не правы. В стандарте это как раз есть.

Если бы все было как вы пишете - формула ТС одинаково не работала и в пк и в ардуино, а она в пк работает.
Если хотите - часа через три напишу подробнее.
 
  • Лойс +1
Реакции: xanstar6067

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
@poty, Я делал с явным преобразованием, результат не отличается. На атмега8 в атмел студии я спокойно могу так делать не указывая явно или не явно. Я сделал управление адресной лентой на голой атмеге, сам писал код генерации импульсов с заданной длинной. Там для управления цветом нужно именно UINT32 и что бы сделать нужный цвет надо указать степень яркости трех цветов и передать потом 24 байта где первый это зеленый, второй красный а третий синий. И вот так смещениями <<8, 16 я собирал пакет цвета. С первого раза получилось. Тут же с ардуино я на месте топчусь. Может я не очень умный но так не бывает, пишу одно и то же и в 1 из трех сред программирования математика вдруг отличается.
 

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
@bort707, А по поводу CAN и логической операции & для rxID не знаете зачем это делается?
rxID & 0x1FFFFFFF
 

bort707

★★★★★★✩
21 Сен 2020
2,900
863
но так не бывает, пишу одно и то же и в 1 из трех сред программирования математика вдруг отличается.
бывает. Почему - я уже обьяснил выше.
Но у меня такое впечатление, что вы мало что поняли....
Ну так что, попробовали мой пример? Собирается лонг из 4х байт?
 
Изменено:

poty

★★★★★★✩
19 Фев 2020
2,989
895
@bort707, вот явно я в этом не уверен. Судя по приводимым примерам используется разный код, и, в частности, в некоторых приведённых выдержках с таким объявлением переменных в правильное преобразование можно реально попасть.
А то, что в С отсутствуют умалчиваемые типы - я уверен (за исключением специальных объявлений, но они, фактически, все из разряда нововведений). Точно нигде нет типа int в качестве default.
делал с явным преобразованием, результат не отличается
предъявите код, как уже писал @bort707, иногда одна не в том месте поставленная скобочка меняет полностью результат вычислений.
 

bort707

★★★★★★✩
21 Сен 2020
2,900
863
Точно нигде нет типа int в качестве default.
ну не "тип дефаулт", а просто в стандарте сказано, что все операнды целочисленных типов размерности менее инт перед вычислением преобразуются к инт. Таким образом формула нашего ТС на ардуине вычисляется в размерности uint16, а на ПК - в размерности uint32_t, отсюда и разный результат.
Если вас интересует пункт стандарта, то это сказано в п 7.6(1), в разделе integral promotions

Кстати это очень легко проверить. Если правы вы, то, как вы сами и пишете:
если reciver_udp имеет размерность 1 байт, то любой сдвиг в сторону старшего разряда - потеря данных.
Проверочный код:
C++:
uint8_t x8 = 0x88;
uint16_t x16 = x_8 << 8;
Serial.print(x16, HEX);
Если прав я, то результат будет 0х8800, а если вы - то результат не может быть более 255 никак :)
Проверьте.

PS Запускать, конечно же, надо на Ардуине.
 
Изменено:

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
бывает. Почему - я уже обьяснил выше.
Но у меня такое впечатление, что вы мало что поняли....
Ну так что, попробовали мой пример? Собирается лонг из 4х байт?
Да, результат идентичен тому где без явных преобразований.
Я так же пробовал создать массив на 4 элемента UINT32 и переместил в него 4 Char с явным преобразованием. Все одно, первые четыре цифры будут FFFF.
Не знаю сколько точно вариантов записи я испробовал, не получилось. Только с POW более менее пригодный результат с 00 на конце.
 

bort707

★★★★★★✩
21 Сен 2020
2,900
863
Да, результат идентичен тому где без явных преобразований.
то есть неправильный? - ищите ошибку.
Я вчера попробовал тоже и результат с преобразованием работает, так что проблема точно в вас.
 

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
Избыточное цитирование. Отредактируй или сообщение будет удалено
Кстати это очень легко проверить. Если правы вы, то, как вы сами и пишете:
Проверочный код:
C++:
uint8_t x8 = 0x88;
uint16_t x16 = [U]x_8[/U] << 8;
Serial.print(x16, HEX);
Результат запуска именно такой. У вас только маленькая опечатка была, подчеркивание между х и 8.
1645683720913.png
Провел эксперимент с смещением еще на 8 - 16 бит и получил 0. Конечно создав перед этим переменную соответствующей величины.
 
Изменено:

xanstar6067

✩✩✩✩✩✩✩
3 Фев 2022
20
1
26
@bort707,
предъявите код, как уже писал @bort707, иногда одна не в том месте поставленная скобочка меняет полностью результат вычислений.
Копирую переменные на всякий случай, а так это вовсе не обязательно.:
    unsigned char bytes[4] = {};
    bytes[0] = recived_udp[0];
    bytes[1] = recived_udp[1];
    bytes[2] = recived_udp[2];
    bytes[3] = recived_udp[3];
После копирования вариант работы номер 1, который на конце выдает 00, а первые 6 цифр нормальные.
преобразование из 4 байт в целое число:
unsigned long rx_ID = pow(256, 0) * bytes[0] + pow(256, 1) * bytes[1] + pow(256, 2) * bytes[2] + pow(256, 3) * bytes[3];
ну и совсем не удачный вариант
C++:
    unsigned long rx_IDA = ((recived_udp[3] << 24) +(recived_udp[2] << 16 )+(recived_udp[1] << 8) + (recived_udp[0]));
Пробовал указывать явно преобразование, буфер делать не char а long int и ничего не меняется. Первые 4 цифры всегда FFFF.

@bort707, Мне проще перенести проект на Atmel студию, выну микроконтроллер из платы и прошью на прямую. Какой то странный очень компилятор в ардуино. У меня Uno r3, там съемная мега328р.
 

bort707

★★★★★★✩
21 Сен 2020
2,900
863
@xanstar6067, я бы вам просто посоветовал не браться не за свое дело.
Я вчера вам полдня обьяснял, почему у вас не работает - а вы ничего не поняли, буквально по нулям! Какой вам КАН с такими знаниями, о чем вы? Вам обьясняют - а вы опять пишете формулы, повторяя в них те же ошибки. Если ничего не понимаете в программировании, то хотя бы слушайте внимательно и делайте то, что вам говорят. А я от вас второй день не могу добиться, чтобы вы показали свой код ПОЛНОСТЬЮ.
Разбирайтесь дальше сами.

И да, переходом на Атмелстудию проблему вы не решите, потому что проблема не в ардуино, а в вас. У меня это преобразование и в ардуино работает без ошибок.
 
Изменено: