РадиоЛоцман

zeroParsing Modbus – на 48-ми байтах, без парсинга и ошибок.

Новичок
 
Регистрация: 09.03.2026
Сообщений: 10
Репутация: 10
 
10.03.2026 21:26 #1
Тут надысь в припадке работоспособности изваял одну штуку, и ношусь с ней, с целью куда-нибудь пристроить или хотя бы получить обратную связь.
В общем, если заинтересует – продолжу.

zeroParsing Modbus – на 48-ми байтах, без парсинга и ошибок.
В общем, это алгоритм приёма пакетов Modbus на паттерновом (или же абстрактном) принципе.
Сначала сразу код с необходимыми, как мне кажется, комментариями.

Код:
#define PRESSURE_KEY	0x5AA5000A1C2D0405  //Ключ - полная копия ожидаемого запроса мастера.
//Эта функция вызывается там, где байт материализуется из линии. 
//У меня это в isr
int mbNewByte(uint8_t byte)
{
	static volatile uint64_t shiftBuf;	 //Это самая главная переменная.
	shiftBuf = (ShiftBuf >> 8) | byte;
	if(shiftBuf == PRESSURE_KEY)
	{
		//Всё, разборка пакета успешно заверщена, можно отвечать.
		//Разумеется, актуальный ответ должен быть готов всегда в виде, пригодном для отправки.
		return 1; //Пакет принят!
	}
	return 0; //Процесс приёма не заверщён.
}
Да, это работает.
Примечательные особенности:

• полное отсутствие ошибок, и это самое главное
• темпоральная инвариантность – алгоритм безразличен к временным промежуткам между байтами в линии. Они могут отсутствовать либо исчисляться веками.
• 48 – байт – это минимальный контроллер, на котором алгоритм сможет работать. Но таких нет, так что начинаем с 64.
• Это не какой-то там кастомный модбас. Мастер не отличит такого слейва от настоящего. Это просто другой принцип.

Ограничения.
Ну куда же без них.
Этот вариант алгоритма для датчика - устройства, которое не выдаёт воздействия на объект и не конфигурируется. И первое, и второе можно победить, попутно потеряв красоту первоначального варианта, но практически без потери в эффективности.
А, ну ещё в этой версии не сообщает об ошибках. Тоже устранимо.
Будет интерес – продолжим.
Эксперт
 
Аватар для corbyn50011
 
Регистрация: 17.10.2010
Адрес: Томск
Сообщений: 3,602
Репутация: 960
 
11.03.2026 01:48 #2
Цитата:
Сообщение от Алопеций Шнифт
Ограничения.
Pressure Key/
А остальное-грамотно.
__________________
Никогда не спорьте с идиотами.Вы опуститесь до их уровня, где они задавят вас своим опытом. МАРК ТВЕН.
...
Знаток
 
Регистрация: 13.07.2007
Сообщений: 712
Репутация: 148
 
11.03.2026 11:56 #3
Уважаемый, Алопеций Шнифт,
Вы прошли первый квест в увлекательной игре Жизнь.
По ходу дальнейших квестов и для их прохода вы вынуждены будете совершенствовать
Навыки, приобретать опыт и покупать инструменты.
По ходу этой игры, вы поймёте, чтобы проходить квесты надо создавать СВОИ ФУНКЦИОНАЛЬНО законченные устройства. Помощи особо не ждите, сопрут код и идею,
как не прятатали бы.
А следующий этап создать изделия, при виде которых только у единиц шевельнулась мысль повторить.
Новичок
 
Регистрация: 09.03.2026
Сообщений: 10
Репутация: 10
 
11.03.2026 14:31 #4
Цитата:
Сообщение от Sergey57
Уважаемый, Алопеций Шнифт,
По ходу этой игры, вы поймёте, чтобы проходить квесты надо создавать СВОИ ФУНКЦИОНАЛЬНО законченные устройства.
Спасибо за предупреждение, но за свою карьеру я разработал достаточно функционально законченных устройств (несколько более сложных, чем простые датчики), чтобы понимать, что это такое.
Только в данном случае речь идёт об общем принципе, алгоритме, который может быть применён широким классом устройств для самых разных целей. А в 'функционально законченных' устройствах этот алгоритм вполне себе бегает в поле с чужими мастерами.

Цитата:
Сообщение от Sergey57
Помощи особо не ждите, сопрут код и идею
Жду этого с нетерпением. Не поможете? В смысле, не сопрёте ли?
Новичок
 
Регистрация: 09.03.2026
Сообщений: 10
Репутация: 10
 
11.03.2026 16:41 #5
Цитата:
Сообщение от corbyn50011
Pressure Key/
А остальное-грамотно.
Малодушно посчитаю это проявлением интереса (хоть и не понял суть поправки), и на этом основании разрешу себе продолжить.
Итак, в прошлый раз я предоставил полный код Модбас для датчика с серьёзным нарушением стандарта. А именно - отсутствуют ответные сообщения об ошибках запроса.
Нужно ли? Разработчику - нет. А вот наладчику...
В общем-то, и ему ненадолго, до первого ответа. Но если ответа нет, а в ответ глухое молчание, работа становится очень нервной. Поэтому
Прикручиваем к ZPM ошибки.
Точнее, сообщения о них. При этом пока никак не отступаемся от главного принципа: никакого парсинга. Тут в скобках замечу, что вообще Модбас - не самый удобный для этого алгоритма протокол. ODSP или 101-й подходят ничуть не хуже, а значительно лучше. Но Модбас и значительно попроще, и пораспространённей.
Это я к тому, что сравнение с ключом, по размеру равным пакету, отнюдь не единственный вариант паттерн-матчинга (сорри за матерщину) для этого алгоритма, чем мы сейчас с удовольствием и воспользуемся.

Код:
union x64_Union 
{
    uint64_t u64;        // всё 64-битное значение
    uint16_t words[4];    // 4 слова по 16 бит
    uint8_t bytes[8];     // 8 байт
};

#define PRESSURE_KEY	0x5AA5000A1C2D0405  //Ключ - полная копия ожидаемого запроса мастера.
int mbNewByte(uint8_t byte)
{
	static volatile uint64_t shiftBuf;
	uint64_t diff;
	shiftBuf = (ShiftBuf >> 8) | byte;
	diff = shiftBuf ^ PRESSURE_KEY;	//теперь в diff битовая разница между ключом и пакетом
	if(!diff) //Если отличий вообще нет - наш пакет
	{
		//Всё, разборка пакета успешно заверщена, можно отвечать.
		//Разумеется, актуальный ответ должен быть готов всегда в виде, пригодном для отправки.
		return 1; //Пакет принят!
	}
	if(bytes[7])
	{
		//но адрес-то наш! поэтому
		if(((x64_Union *)&diff)->words[1])
		{
			return -1; //обнаружена ошибка, требующая ответа
		}
		if(((x64_Union *)&diff)->words[2])
		{
			return -2;//обнаружена другая ошибке
		}
		return 0;	//продолжаем приём
	}
	return 0; //Продолжаем приём.
}
Где-то так. Первоначальная красота убита полностью, эффективность осталась приблизительно на том же уровне.
После этих проделок мы получили тот же датчик, но уже полностью соответствующий стандарту. Никто не придерётся. Данные отдаёт, об ошибках сигнализирует.
Не умеет принимать пока данные от мастера? Тоже исправимо, позже продолжу.
Ранее обещал, что продолжу только в случае проявления интереса, но попутно получил столько чрезвычайно полезных советов, что уже чувствую себя обязанным. Так что продолжу в любом случае.

А, там я ещё не вполне корректно обошёлся с crc (просто наплевав на него), но пониманию алгоритма это не мешает. А полный вариант уж точно по запросу.
Последний раз редактировалось Алопеций Шнифт; 11.03.2026 в 16:49.
Новичок
 
Регистрация: 30.03.2010
Сообщений: 26
Репутация: 14
 
16.03.2026 10:06 #6
Цитата:
Код:
	static volatile uint64_t shiftBuf;	 //Это самая главная переменная.
	shiftBuf = (ShiftBuf >> 8) | byte;
Эт чойта? Может shiftBuf = shiftBuf << 8 | byte;?
Новичок
 
Регистрация: 09.03.2026
Сообщений: 10
Репутация: 10
 
17.03.2026 11:35 #7
Цитата:
Сообщение от OKF
Эт чойта? Может shiftBuf = shiftBuf << 8 | byte;?
Можно сдвигать в любую сторону. Другое дело, что ключ должен быть развёрнут в ту же сторону.
Я же не конкретное решение (библиотеку или набор макросов) предлагаю, а алгоритм с кучей вариантов.
Которые между тем уже устарели. Причешу - опубликую актуальную версию.
Нет, всё ранее выложенное вполне себе работает. Просто уже есть (будет) получше.
Новичок
 
Регистрация: 09.03.2026
Сообщений: 10
Репутация: 10
 
17.03.2026 11:57 #8
А чего ждать-то?
Ниже последняя версия второго варианта алгоритма.
Это консольное приложение VS - тот случай, когда эмуляция ничем не хуже, чем отладка на целевой платформе - алгоритм чисто математический и работает везде.
Код:
//ВНИМАНИЕ!!! Мат в данных. Строго 18+

#include <stdint.h>
#include <stdio.h>

union ModbusFrame 
{
	uint64_t slider;
	uint32_t u32[2];
	uint16_t u16[4];
	uint8_t  u8[8];
};

uint16_t updcrc(uint16_t crc, uint8_t data) {
	crc ^= data;
	for (uint8_t i = 0; i < 8; i++) {
		if (crc & 1) {
			crc = (crc >> 1) ^ 0xA001;
		}
		else {
			crc >>= 1;
		}
	}
	return crc;
}

uint16_t crc16(void *buf,int sz)
{
	uint16_t crc = 0xFFFF;  // начальное значение
	for (int i = 0; i < sz; i++)
	{
		crc = updcrc(crc, ((uint8_t*)buf)[i]);
		//printf("%04x\n", crc);
	}
	return crc;
}

uint32_t Key = 0x5AA50607; 	// ключ сократился до 32 бит.
uint16_t hdr_crc;			// должно быть предрассчитано там же, где и ключ

static volatile union ModbusFrame frame;

void printState(union ModbusFrame* mf)
{
	printf("\n         0   1   2   3   4   5   6   7");
	printf("\nslider:|");
	for (int i = 0; i < 8; i++) printf("%02x |", frame.u8[i]);
}

int mbNewByte(uint8_t byte) 
{
	union ModbusFrame diff;
	frame.slider >>= 8;
	frame.u8[7] = byte;
	diff.u32[0] = frame.u32[0] ^ Key;	//тепреь в diff битывая разница со slider
	printState((ModbusFrame *)&frame);
	if (!diff.u8[0])	//ноль - наш адрес
	{
		uint16_t crc;
		crc = updcrc(hdr_crc, frame.u8[4]); //досчитываем crc для 2-х оставшихся байт
		crc = updcrc(crc, frame.u8[5]);
		//crc = crc16((void *)frame.u8, 6); //ну или считаем сразу.
		if (crc == frame.u16[3])	//сравниваем.
		{
			//Пакет корректен и адресован нам, обязаны что-то отвечать. Выясняем, что именно
			if (diff.u8[1])
			{
				printf("  error -1\n");
				return -1;//Мастер запросил не ту функцию
			}
			if (diff.u16[1])
			{
				printf("  error -2\n");
				return -2;//Мастер запросил не тот регистр
			}
			printf("  *** OK! ***\n");//рабочий запрос принят!
			return 1;
		}
	}
	return 0;      // приём в процессе
}


static uint8_t emu[] = {	
							'Х',  'У',  'Й',//Есть мнение, что без подобного паттерна отечественный софт
											//работать не будет.  Мнение ошибочное, но на всяк. случАй.
							0xff,											//короткий шум в линии
							0x07, 0x06, 0xA5, 0x5A, 0x12, 0x34, 0x86, 0x04, //рабочий пакет
							0x07, 0x06, 0xA6, 0x5A, 0x12, 0x34, 0x86, 0x04, //ошибка в регистре
							0x55, 0xAA, 0xff,								//шум в линии
							0x07, 0x06, 0xA5, 0x5A, 0x12, 0x34, 0x86, 0x04, //рабочий пакет
							0x07, 0x05, 0xA6, 0x5A, 0x12, 0x34, 0x86, 0x04  //ошибка в функции
						};
uint8_t Emulator()
{
	static int cnt;
	uint8_t ret = emu[cnt++];
	cnt = cnt % sizeof(emu);
	return ret;
}

void main()
{
	uint8_t byte;
	hdr_crc = crc16(&Key, 4);
	for (;;)
	{
		byte = Emulator();
		mbNewByte(byte);
	}
}
Можно менять входной поток и смотреть на результат.
Сразу сообщаю: помещение ключа в поток собьёт алгоритм. Другое дело, как он там окажется в реальной-то линии?
Последний раз редактировалось Алопеций Шнифт; 17.03.2026 в 12:05.
Новичок
 
Регистрация: 09.03.2026
Сообщений: 10
Репутация: 10
 
17.03.2026 12:11 #9
Ну и проект целиком, чтобы не мучиться.
Вложения
Тип файла: zip ZPM.ZIP (277.6 Кб, 0 просмотров)
Ответ
Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход
Часовой пояс GMT +3, время: 23:15.
Обратная связь РадиоЛоцман Вверх

Политика конфиденциальности