Имеем структуру сообщения CAN шины
typedef struct {
uint32_t identifier; // CAN ID (11‑битный или 29‑битный)
uint8_t data_length_code; // DLC — длина данных (0–8 байт)
uint8_t data[8]; // Массив данных (до 8 байт)
uint32_t flags; // Битовые флаги типа сообщения
} twai_message_t;
в data[8] находятся закодированные данные параметров.
Пример сообщения установки скорости мотора
twai_message_t message;
message.identifier = 0x01; //slave address
message.data_length_code = 5; //DLC
message.data[0] = 0xF6; //function code
message.data[1] = ( (speed >> 8) & 0x0F); //High 4 bits for direction and speed
message.data[2] = speed & 0x00FF; //8 bits lower
message.data[3] = 0; //0xF5; // acc
message.data[4] = CalcCRC(message);
sendMessage(message);
Ответные сообщения похожи по структуре.
В зависимости от значения data[0] в следующих байтах 1…N-1 будут значения параметров. Возможно не одно значения а два int16 или 1 int , второй in16 и uint8_t и т.д.
Вопрос: как бы правильно читать параметры за минимальное время?
Например использовать структуры с union или еще каке механизмы?
Некоторую сложность вызывает то что некоторые параметры хранятся часть в одном байте, часть в другом.
BOOM
19.Июнь.2026 17:04:10
2
Как то не состыкуется вот с этим высказыванием:
Откуда там int16_t возьмётся?
так производитель моторов закодировал can сообщения. Есть даже такой как int48_t
Такие “особенности” я бы пока опустил. Сделать доступ к переменным с дискретностью байт
как пример:
а вот экзотика
и еще попутный вопрос…
представление int16_t в памяти esp32
представление int16_t при передаче по CAN шине. (размещение в .data[] )
Если порядок байт отличается как их правильно привести к единому представлению?
зы. Или я все сильно усложняю и проще присвоить значения простым сдвигом бит?
Да.
Это ещё сложнее.
Я бы взял в качестве базового типа uint64_t (как раз 8 байтов), навесил ба на него сверху класс (структуру, если Вам так больше нравится) который взял бы на себя всю головную боль с префиксами в нулевом байте и с зависящей от них структурой сообщения, а наружу выдавал бы уже готовые значения (через методы “геттеры” и “сеттеры”). Эту байду отладил бы намертво в отдельном скетче из десятка строк, а потом вставил бы в программу и “забыл бы о перхоти”
Да, интересный вариант. Можно сделать универсальный. Но не приведет ли это к излишним затратам?
Набор кодов сообщений у меня ограничен.
Прочитать текущую скорость, положение - часто
Чтение тока потребления - редко (1 сек например)
(на картинках почему-то номер байта с 1 начинается)
Чтение сдвигом я представляю так:
int16_t speed = (int16_t)((msg.data[1] << 8) | msg.data[2]);
или как я предполагал..
union CODE_32 {
uint8_t data[8];
struct cmd {
uint8_t code;
uint16_t speed;
unt8_t none1;
uint32_t none2;
}
};
т.е. получили массив data[8] но смотрим на него как на структуру cmd
Меня только смущает 2 момента:
правильное ли будет расположение байт (старший/младший) в представлении dadta (полученной по CAN) и в представлении esp32
Правильно ли произойдет извлечение данных учитывая что первый элемент структуры 1 байт, а int_16 из двух байт смещен в памяти (не выровнен)
А если switch использовать? Т.е. в зависимости от значения switch(data[0]), отрабатывать case , в котором и читать “сырые” данные, как надо.
такой вариант я реализовывал очень давно, когда знакомился с С. Но думаю должны же быть в С более изящные и скоростные методы.
Все это сейчас в контексте балансирующего робота. Хочется максимально освободить процессор от неэффективных шагов
фрагмент из старого проекта. очень давно было.
void CAN_Decode(CanRxMsg frame) {
volatile uint8_t bit;
volatile uint32_t mask;
volatile uint16_t rpm;
switch (frame.StdId) {
case (0x0190): // Сигналы двигателя
rpm = frame.Data[0];
rpm = (rpm << 8) + frame.Data[1];
CanSignal.ecm_EngineRpm_0x190 = rpm;
break;
case (0x27d): // ABS
CanMsgState.id_0x27D.exist = 1;
CanMsgState.id_0x27D.time = osKernelGetTickCount();
CanSignal.ecm_Speed_0x27D = (frame.Data[4] << 8) + frame.Data[5];
// volatile uint16_t currentSpeed = GetSpeed();
//
// TxMsgABS_0x27d.Data[4] = (uint8_t) (( (uint16_t) currentSpeed >> 8) & 0x00ff);
// TxMsgABS_0x27d.Data[5] = (uint8_t) ( (uint16_t) currentSpeed & 0x00ff);
break;
Я , увы, не понимаю ,(надеюсь, что временно))), многого из того, что пишет @ЕвгенийП , но люблю, чтобы код был , как можно проще.
Строго не судите, ведь свои 5коп надо же вставить))
MMM
19.Июнь.2026 18:37:39
10
memcpy с нужного смещения в переменную типа int16 решит проблему.
Вообще вся задачка для первого класса
соглашусь с таким подходом. Особенно если потребуется через пару лет в него заглянуть.
Но в то же время хочется получить максимальную производительность.
Хотя конечно не факт, что я ее улучшу учитывая мои 2 опасения выше, а с ними потребность еще какого-то дополнительного кода
боюсь я использовать эти memcpy.
Особенно в среде FreeRTOS.
Да и какой выигрыш она дает по сравнению со сдвигом бит?
По моему вот так будет быстрее доступ
union {
uint8_t b[4];
uint16_t w[2];
uint32_t d;
} xVal;
xVal a;
a.d = 0x11223344;
...
uint16_t speed = a.w[0]
MMM
19.Июнь.2026 18:44:48
13
Какой выигрыш? И то и другое простейшие операции. Вы тут как будто решаете, как “правильнее” получить число 6 - 3+3 или 4+2:)
Операции не сложные. Но они выполняются с частотой 200 Гц. И разница уже будет.
и как memcpy решить вопрос несоответствия порядка байт в CAN и памяти eps32?
MMM
19.Июнь.2026 19:00:52
15
Вообще ни о чем
Никак
Используйте то, что подходит. Вы опять пытаетесь найти разницу между 3+3 и 4+2
Хорошо.
У меня сложилось мнение, что не стоит излишне усложнять код.
пока остановлюсь на варианте предложенном
Дим-мычъ:
Switch использовать
По крайней мере мне он наиболее понятен.
Всем спасибо за участие. Это реально помогает вправить мозги на место
Какую именно и с какой целью?
Высвобождение ресурсов процессора для других задач за счет оптимизации кода.
Всего предполагается управление 6-тью моторами, получение значений от датчиков: гироскопа, дальномера, цветовых датчиков, энкодеров моторов, Обработка блютус интерфейса.
Пока пошагово вопросы прорабатываем.
С первым роботом на шаговых моторах при микрошаге 16, при высоких скоростях начинались томоза из-за частых прерываний step.
пошли по пути передачи части функционала на драйвера моторов. Но теперь нужно минимизировать лаг от подачи команды до ее исполнения драйвером.
Завтра попробую скорость CAN поднять до 1 Мбит
Не знаю, учите ли вы С++, я просто пока только С “юзаю”. Но , если бы учил “плюсы” - обязательно бы разобрался с ответом от @ЕвгенийП (#5 ). Чего и вам желаю)).
А скорость выполнения отдельных блоков можно сравнить, да, сами, наверно знаете как…