ModBUS. О нем много было рассказано, много копий сломано. Но сложилась ситуация таким образом, что никто и ничего не рассказал о том, как пишется прошивка для ведомого, а не для ведущего. Как создаются регистры, как они заполняются, каким образом отыскивается нужный регистр. Вот общение с ведомым обсосано со всех сторон, но разговор сейчас не о нём. Разговор о библиотеке, которая позволяет общаться с ведомым, когда у него висит не очень много датчиков. Например, в трёх точках на расстоянии 30 метров друг от друга нужно собрать данные о температуре, давлении, влажности, освещённости и так далее. А затем передать ведущему. Данный протокол подсмотрен в библиотеках фирмы Mikroelektronica (да, да именно так и пишется, как это не удивительно). Только у них он может передать три байта, я расширил до 32-х.
Идея передачи протокола такова. Передаётся стартовый байт, адрес ведомого, количество передаваемых байт, 5 байт коррекции, массив данных, CRC8, стоповый байт. По стартовому и стоповому байту определяется начало и конец пакета, CRC позволяет проверить корректность передаваемых данных, байты корректировки корректируют байты данных, если они похожи на байты начала/конца передачи.
| № байта | Назначение | Байт | Бит | Описание |
| 1 | Стартовый байт | 0х96 | 0-7 | |
| 2 | Адрес ведомого | 0х01 - 0хFF | 0-7 | |
| 3 | Количество байт | 0х01 - 0х3F | 0-5 | Адрес |
| 6 | Флаг коррекции адреса | |||
| 7 |
1 - передача от ведущего |
|||
| 4 | Байт коррекции маски |
0x10 |
0-7 |
1 в четвёртом разряде указывает на необходимость корректирования CRC |
| 5 | Байты коррекции данных | 0х01 - 0хFF | 0-7 | Четыре байта, объединены в 32-разрядную переменную, каждый бит которой установленный в 1, указывает на необходимость корректировки соответствующего ей байта данных |
| 6 | 0х01 - 0хFF | 0-7 | ||
| 7 | 0х01 - 0хFF | 0-7 | ||
| 8 | 0х01 - 0хFF | 0-7 | ||
| 9 ... N | Данные | 0х00 - 0хFF | 0-7 | От одного до 32-х байт |
| N + 1 | CRC | 0х00 - 0хFF | 0-7 |
CRC - считается по некорректированным данным |
| N + 2 | Стоповый байт | 0хA9 | 0-7 |
В таблице расписан каждый байт и за что он отвечает. Можно довольно часто встретить слово "коррекция". Что это и с чем едят?
Всё довольно интересно. Мы имеем протокол, который начало и конец пакета отлавливает по двум байтам, стартовому - 0х96 и стоповому - 0xA9, всё, что заключено между ними и является необходимым нам пакетом. Но тут нам встретился байт, который равен или стартовому или стоповому байту. И всё, пакет пропал, мы никогда не примем его, пока байт не изменится.
Чтобы этого не происходило, младший бит этого упрямого байта инвертируется и пакет спокойно передаётся. На принимающей стороне этот бит инвертируется обратно. А для того, чтобы знать, нужно нам корректировать этот байт или нет, существует 4 байта, в которых содержится маска. Эти 4 байта объединены в 32-разрядную переменную, каждый бит которой, со старшего разряда, указывает, нужно ли корректировать текущий байт. Из-за этой разрядности у нас и введено ограничение на количество байт в пакете данных. Но у нас возникают вторые грабли. Один из байтов в этой переменной тоже может принять значение старт/стоп байта. Вот тут у нас вступает в борьбу за правильную передачу "байт коррекции маски". В нем с помощью единичного значения в одном из 4-х младших бит, указывается, нужно ли корректировать байт маски и один, 5-й, бит отведён под корректор CRC. Ведь CRC тоже может быть равна старт/стоп байту. Сам этот байт принять такое значение не может, поэтому мы избавляемся от проблемы "кто будет контролировать контролирующего".
Ввиду того, что адресов периферии у нас может быть 254, мы не можем гарантировать, что адрес не совпадёт с старт/стопом. Поэтому в байте, в котором указывается количество передаваемых байт, 6-й бит является индикатором коррекции адреса. Ну а 7-й бит указывает, от кого идёт передача: от ведущего или ведомого.
Ну и по-быстрому подготовка и передача пакета. Как и описывал, всё разделено по шагам. Для заголовка у нас создаётся структура:
struct _Heading
{
uint8_t StartByte = 0x96; // Стартовый байт
uint8_t Address; // Адрес периферии
uint8_t QuantBytes; // Размер пакета данных
uint8_t MaskCorrect; // Корректор маски и CRC
uint32_t Mask; // Корректор данных
} __attribute__ ((__packed__));
Класс:
class RS485: public uart
{
public:
RS485(USART_TypeDef *Usart, bool Alternate, GPIO_TypeDef *GPIO_Port, uint16_t GPIO_Pin, uint8_t Address = 0) :
uart::uart(Usart, Alternate)
{
_GPIO_Port = GPIO_Port;
_GPIO_Pin = GPIO_Pin;
_SetPin(_GPIO_Port, _GPIO_Pin, Output, Pull_Down);
_DigitalWriteBit(_GPIO_Port, _GPIO_Pin, Low); // По умолчанию слушаем линию
_Address = Address;
if(_Address) _Role = RS485_SLAVE; else _Role = RS485_MASTER; // Если адрес назначен, мы ведомый
}
Ему передаётся порт UART, альтернативные выводы, порт и вывод для управления микросхемой RS485 на приём/передачу и адрес. Если адрес указать, то устройство инициализируется как ведомое, если н