Top.Mail.Ru

Часть 18. Не ModBUS, кое-что попроще.

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 - передача от ведущего
0 - передача от ведомого

4 Байт коррекции маски

0x10
0х08
0х04
0х02
0х01

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 Стоповый байт 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 на приём/передачу и адрес. Если адрес указать, то устройство инициализируется как ведомое, если н