Чтение одномерного массива байт как набор разных переменных

Имеем структуру сообщения 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 или еще каке механизмы?

Некоторую сложность вызывает то что некоторые параметры хранятся часть в одном байте, часть в другом.

Как то не состыкуется вот с этим высказыванием:

Откуда там 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 момента:

  1. правильное ли будет расположение байт (старший/младший) в представлении dadta (полученной по CAN) и в представлении esp32
  2. Правильно ли произойдет извлечение данных учитывая что первый элемент структуры 1 байт, а int_16 из двух байт смещен в памяти (не выровнен)

А если switch использовать? Т.е. в зависимости от значения switch(data[0]), отрабатывать case , в котором и читать “сырые” данные, как надо.

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

Все это сейчас в контексте балансирующего робота. Хочется максимально освободить процессор от неэффективных шагов :slight_smile:

фрагмент из старого проекта. очень давно было.

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коп надо же вставить))

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]

Какой выигрыш? И то и другое простейшие операции. Вы тут как будто решаете, как “правильнее” получить число 6 - 3+3 или 4+2:)

Операции не сложные. Но они выполняются с частотой 200 Гц. И разница уже будет.

и как memcpy решить вопрос несоответствия порядка байт в CAN и памяти eps32?

Вообще ни о чем

Никак
Используйте то, что подходит. Вы опять пытаетесь найти разницу между 3+3 и 4+2

Хорошо.

У меня сложилось мнение, что не стоит излишне усложнять код.

пока остановлюсь на варианте предложенном

По крайней мере мне он наиболее понятен.

Всем спасибо за участие. Это реально помогает вправить мозги на место :slight_smile:

Какую именно и с какой целью?

Высвобождение ресурсов процессора для других задач за счет оптимизации кода.

Всего предполагается управление 6-тью моторами, получение значений от датчиков: гироскопа, дальномера, цветовых датчиков, энкодеров моторов, Обработка блютус интерфейса.

Пока пошагово вопросы прорабатываем.

С первым роботом на шаговых моторах при микрошаге 16, при высоких скоростях начинались томоза из-за частых прерываний step.

пошли по пути передачи части функционала на драйвера моторов. Но теперь нужно минимизировать лаг от подачи команды до ее исполнения драйвером.

Завтра попробую скорость CAN поднять до 1 Мбит

Не знаю, учите ли вы С++, я просто пока только С “юзаю”. Но , если бы учил “плюсы” - обязательно бы разобрался с ответом от @ЕвгенийП (#5). Чего и вам желаю)).
А скорость выполнения отдельных блоков можно сравнить, да, сами, наверно знаете как…

Всё, пропал проект…