В этой статье мы рассмотрим вариант подключения С2000-ПП к Arduino MEGA через конвертирующий модуль на базе чипа MAX485, научимся рассчитывать контрольную сумму CRC16 для протокола Modbus, отправим простейший запрос от Ардуино Мега к С2000-ПП и получим ответ. Итак, начнем.
Для начала нам необходимо как-то подключить С2000-ПП к ардуино. Как мы знаем из описаний, С2000-ПП работает по интерфейсу RS485, а у ардуинки его нет, зато есть UART. Значит нам необходимо некоторое промежуточное устройство, которое будет конвертировать RS485 в UART и обратно. Благо, такие штучки китайцы выпускают в огромных количествах. В данном примере я применил TTL to RS-485 module. Выглядит он вот так:
Данный модуль физически преобразует импульсы, не обладая никакой программной логикой. Давайте пройдемся по контактам:
- Vcc — питание +5В;
- Gnd — питание 0В;
- A — прямой дифференциальный вход/выход;
- B — инверсный дифференциальный вход/выход;
- DI driver input — цифровой вход передатчика;
- DE driver enable — разрешение работы передатчика;
- RE receiver enable — разрешение работы приемника;
- RO receiver output — цифровой выход приемника.
Нужно знать, что приемник и передатчик на стороне RS-485 соединены между собой и данные с передатчика попадают на приемник. В таком режиме можно проверить модуль «на себя». Нам же нужно управлять процессами приема и передачи, отключая приемник при включенном передатчике и наоборот. Для управления режимами служат входы RE и DE. Вход RE — инверсный и при подаче на него лог. нуля и подаче лог. нуля на DE мы разрешаем работу приемника и запрещаем работу передатчика. Соответственно при подаче лог. единицы на входы мы разрешаем работу передатчика и запрещаем работу приемника. Если хотим завернуть приемник на передатчик — подаем лог. 0 на RE и лог. 1 на DE. Таким образом, мы можем объеденить входы DE и RE и управлять ими одним цифровым выходом с ардуино, если нам не нужны завороты.
Теперь, зная эти нехитрые моменты, соберем схему:
В качестве источника питания не обязательно использовать РИП-12, подойдет любой источник напряжением 12В и током 0,3А. Ардуино МЕГА, в принципе, можно не запитывать от 12В, однако если у вас не надежный источник питания 5В, то я рекомендую это сделать, т.к. конвертирующий модуль от 4,5В уже работает не стабильно. Ардуино МЕГА была мною выбрана по причине того, что на ее борту есть несколько аппаратных UART интерфейсов. На ардуино UNO с помощью библиотеки SoftwareSerial с ходу запустить не удалось. Итак, пин управления у нас седьмой, а для связи мы будем использовать UART номер один. Питание конвертирующего модуля берем с выходов питания Arduino Mega. Если вы подключаете как-то иначе, то не забывайте вносить изменения в скетч.
Отлично! Железо подготовили, займемся программой. В прошлом посте С2000-ПП и Arduino. Соединяем Болид и ардуино по протоколу Modbus RTU. Часть 1. я рассказывал из чего состоит и как формируется запрос к С2000-ПП по протоколу Modbus. Кто не ознакомился — настоятельно рекомендую, чтобы избежать вопросов «У меня не получилось. Что делать?». Если получилось через AccessPort — здесь тоже получится.
Единственный момент, который я не осветил — как рассчитывать контрольную сумму CRC16 Modbus. В прошлом посте мы использовали онлайн-калькулятор. Сейчас все исправим.
Итак, обратимся к оригинальным источникам:
Что же нам тут предлагают проделать, если перевести на русский и немного подумать:
алгоритм расчета контрольной суммы CRC16 Modbus
- Создаем 16ти разрядную беззнаковую переменную uint16_t (регистр CRC) и записываем в нее 0xFFFF, т.е. все разряды устанавливаем в еденицы
- Первые 8 бит (первый байт) нашего запроса (это адрес устройства к которому мы обращаемся) складывается по модулю 2 (исключающее ИЛИ, xor) с содержимым регистра CRC. Результат операции помещается в регистр CRC.
- Производим сдвиг содержимого регистра CRC вправо (в направлении младшего бита) на 1 бит, старший бит регистра заполняется нулем и анализируем «выпавший» справа бит.
- Если «выпавший» справа бит равен нулю, то производим действие 3 (сдвиг вправо, заполнение, анализ), если «выпавший» справа бит равен единице — производим сложение по модулю 2 (исключающее ИЛИ, xor) содержимого регистра с полиномиальным числом 0xA001, полученное значение сохраняем в регистре CRC
- Повторяем шаги 3 и 4 пока не будет проделано 8 сдвигов (обработан весь 1й байт нашего запроса.
- Не загружая более в регистр число 0xFFFF, повторяем шаги 2 — 5 до тех пор, пока все байты нашего запроса не будут обработаны.
- Содержимое регистра CRC (переменной) и есть наша контрольная сумма.
- Контрольная сумма состоит из 2х байт. При передаче нашего запроса первым передается младший байт, затем старший, т.е. передать значение таким, каким мы его получили — нельзя. Нужно поменять байты местами.
Небольшие комментарии к шагам 2-4. Прежде чем сдвигать регистр вправо нам нужно проанализировать младший байт, так как после сдвига он пропадет. Для этого мы сначала проделаем операцию логического «И» с числом 0x0001 и узнаем, что там: единица или нуль, а после этого произведем сдвиг.
Комментарий к шагу 8. Т.к. информацию через ардуино нам нужно передавать побайтно, да еще и байты CRC поменять местами, есть смысл поместить контрольную сумму в массив из двух uint8_t, с байтами, расположенными в нужном нам порядке.
Теперь, собственно, сам код расчета контрольной суммы CRC16 Modbus на языке c++:
void CRC16ModbusCalc(uint8_t frame[], uint8_t len, uint8_t crc16stor[]){
uint16_t crcReg = 0xFFFF; // создаем и заполняем регистр единицами
for (uint8_t i = 0; i < len; i++){ // для количества байт нашего запроса
crcReg ^= frame[i]; // исключающее ИЛИ (сумма по модулю 2) с содержимым регистра с присвоением
for (uint8_t j=0; j<8; j++){ // для каждого бита байта
if (crcReg & 0x01) { // если младший бит равен единице
crcReg = (crcReg >>=1) ^ 0xA001; // сдвигаем вправо на 1 бит и исключающее ИЛИ (сумма по модулю 2) с полиномиальным числом 0xA001
}
else crcReg >>= 1; // если младший бит равен нулю - просто сдвиг
}
}
// после окончания циклов в переменной crcReg лежит наша контрольная сумма
// поменяем байты местами и поместим ее в массив crc16stor
*crc16stor = crcReg & 0x00FF; // сначала выполним логическое умножение (И) содержимого регистра на число 0x00FF, тем самым получим младший байт и разместим его в нулевом члене массива
crc16stor ++; // переместим указатель на 1 первый член массива
*crc16stor = crcReg >> 8; // сдвинем содержимое регистра вправо на 8 бит (1 байт) и получим старший байт. Разместим его в первом члене массива.
};
Данная функция ничего не возвращает. Принимает 3 параметра:
- Указатель на массив uint8_t в котором побайтно лежит наш запрос;
- Число типа uint8_t — длина массива в котором лежит запрос;
- Указатель на массив uint8_t с числом членов равным двум, в который помещается контрольная сумма.
Ниже приведен простейший скетч для отправки запроса от Arduino Mega к С2000-ПП и получения ответа.
const uint8_t QUERY_LEN = 6; // размер массива для запроса
const uint8_t CRC16_LEN = 2; // размер массива для хранения контрольной суммы
const uint8_t PIN_TX_RX = 7; // номер вывода (пина) для управления приемником / передатчиком конвертирующего модуля
uint8_t query[QUERY_LEN] = {0x03, 0x03, 0xb4, 0x48, 0x00, 0x02}; // массив содержащий запрос для определения версии и типа прибора
uint8_t crc16storage[CRC16_LEN]; // массив в который будет сохраняться контрольная сумма
void setup(){
pinMode(PIN_TX_RX, OUTPUT); // устанавливаем режим работы вывода PIN_TX_RX, как "выход"
digitalWrite(PIN_TX_RX, LOW); // устанавливаем лог. 0 на выводе PIN_TX_RX (разрешаем прием, запрещаем передачу)
Serial.begin(9600); // открываем последовательный порт Serial 9600 8-N-1
Serial1.begin(9600); // открываем последовательный порт Serial1 9600 8-N-1
}
void loop(){
if (Serial.available()) {// при передаче в Serial символа 'q' запускается функция tx() и через Serial1 запрос отправляется к С2000-ПП
byte symbol = Serial.read();
if (symbol == 'q') tx();
}
rx(); // функция rx() принимает данные из Serial1 и выводит их в Serial
};
void tx(){ //функция через Serial1 отправляет запрос к С2000-ПП
CRC16ModbusCalc(query, 6, crc16storage); // расчитываем контрольную сумму и помещаем ее в crc16storage
digitalWrite(PIN_TX_RX, HIGH); // устанавливаем лог. 1 на выводе PIN_TX_RX (разрешаем передачу, запрещаем прием)
delay(10);
Serial1.write(query, QUERY_LEN); // отправляем в Serial1 байты нашего запроса
Serial1.write(crc16storage,CRC16_LEN); // отправляем в Serial1 байты нашей контрольной суммы
delay(10);
digitalWrite(PIN_TX_RX, LOW); // устанавливаем лог. 0 на выводе PIN_TX_RX (разрешаем прием, запрещаем передачу)
}
void rx(){ // принимает данные из Serial1 и выводит их в Serial
if (Serial1.available()) { // если есть непрочитанные данные
uint8_t symbol = Serial1.read(); // читаем байт из Serial1
Serial.print(symbol , HEX); // выводим его в Serial
Serial.print(F(" "));
}
}
void CRC16ModbusCalc(uint8_t frame[], uint8_t len, uint8_t crc16stor[]){
uint16_t crcReg = 0xFFFF;
for (uint8_t i = 0; i < len; i++){
crcReg ^= frame[i];
for (uint8_t j=0; j<8; j++){
if (crcReg & 0x01) {
crcReg = (crcReg >>=1) ^ 0xA001;
}
else crcReg >>= 1;
}
}
*crc16stor = crcReg & 0x00FF;
crc16stor ++;
*crc16stor = crcReg >> 8;
};
Комментариев в скетче, в принципе, достаточно. Вкратце: в переменной query побайтно хранится запрос на определение типа и версии прибора, тот самый, что мы посылали в прошлом посте с помощью программы AccessPort через С2000-USB. Если мы отправим в Serial символ ‘q’, например с помощью Arduino IDE, выполнится функция tx(), которая рассчитает контрольную сумму и побайтно отправит наш запрос и CRC в интерфейс UART 1.

Функция rx(), которая постоянно крутится в Loop, получит байты ответа от С2000-ПП и выведет их в Serial.

Вот, собственно и все. Нужно понимать, что в данной статье описан самый примитивный способ коммуникации между Arduino и С2000-ПП: без настройки самого С2000-ПП, без опросов приборов, без проверок получаемых сообщений на ошибки и т.д. Буду по мере сил и возможностей развивать проект. Есть много интересных мыслей и задумок. Если есть желание как-то помочь — пишите в комментариях.
Скачать и протестировать мои программы для подключения С2000-ПП к Raspberry Pi вы можете по ссылке : Приложения и утилиты для подключения С2000-ПП к Raspberry, Orange и т.д. с архитектурой ARM 32-bit с операционными системами семейства Unix (Linux).