Дружим WirenBoard с Arduino (slave) по ModBus

Решил тут прикрутить к системе WirenBoard старый код, работающий на Arduino.
Пока лень его переписывать (а может и оставлю его, ибо там хвосты идут в не самых надежных местах, так надежнее) , потому решил связать все по ModBus (RS-485). Прошу оценить насколько это можно пускать в продакшн. Ибо я не силен в ModBus. Прошу поправить, если где не прав.

Итак, имеем Arduino Nano и модуль RS485 MAX485CSA , хотим взаимодействовать с Arduino по протоколу ModBus. В данном случае arduino выступает в роли slave и делится информацией с WirenBoard по ModBus.

1). Программируем arduino.
Я использовал код https://github.com/emelianov/modbus-esp8266 и модуль для modbus slave (кстати оно же должно работать для ESP32, но я не пробовал).

Мой скетч для демонстрации работы :

#include <ModbusRTU.h>
#define SLAVE_ID 22
ModbusRTU mb;

void setup() {
  Serial.begin(9600,SERIAL_8N2); //2 stop bits !
  Serial.println("Setup");

  mb.begin(&Serial, 10);  //это 10й пин, который будет подключен на DE+RE
  mb.setBaudrate(9600);
  mb.slave(SLAVE_ID);

// Задаем тестовые значения

mb.addCoil(1); //0x1
mb.addHreg(2); //0x3
mb.addIreg(3); //0x4

mb.Coil(1,1); //0x1
mb.Hreg(2,222); //0x3
mb.Ireg(3,444); //0x4

}

void loop() {
  mb.task();
  yield();
}

2). Соединяем по схеме


TX1 (arduino) → DI (RS-485 TTL Converter)
RX0 (arduino) → RO (RS-485 TTL Converter)
D10 (arduino) → DE+RE (RS-485 TTL Converter)
5V (arduino) → VCC (RS-485 TTL Converter)
GND (arduino) → GND (RS-485 TTL Converter)
A(RS-485 TTL Converter) → A (WirenBoard RS-485)
B(RS-485 TTL Converter) → B (WirenBoard RS-485)

*** питание с WirenBoard не брал, правильно ли?
upd: производитель рекомендует брать еще общую землю Дружим WirenBoard с Arduino (slave) по ModBus - #2 от пользователя Explorerol

3). Добавляем устройство в WirenBoard
в папке /usr/share/wb-mqtt-serial/templates
создаем файл
config-arduino-bridge.json
с содержанием:
(в данном коде 3 канала для примера, каждый для своего типа )

        {
            "device_type": "ARDUINO-Bridge",
            "device": {
                "name": "ARDUINO",
                "id": "arduino",
                "max_read_registers": 0,
                "response_timeout_ms": 1,
                "frame_timeout_ms": 12,
                "channels": [
                    {
                        "name": "TEST COIL",
                        "reg_type": "coil",
                        "address": 1,
                        "type": "switch"
                    },
                    {
                        "name": "TEST HREG",
                        "reg_type": "holding",
                        "address": 2,
                        "type": "value"
                    },
                    {
                        "name": "TEST IREG",
                        "reg_type": "input",
                        "address": 3,
                        "type": "value"
                    }
                ]
            }
        }

где поле address - это адрес соответствующего регистра из скетча, а типы соответственно:
coil,holding,input - mb.Coil, mb.Hreg, mb.Ireg регистры.

Больше про шаблоны почитать тут Драйвер wb-mqtt-serial: шаблоны — Wiren Board
про ModBus тут: Протокол Modbus — Wiren Board
также полезная тема Работа с устройствами Wiren Board без контроллера — Wiren Board

4). Добавляем наше новое устройство в UI
В Web UI Wirenboard в разделе Settings → Configs → /etc/wb-mqtt-serial.conf (Serial Device Driver Configuration)
добавляем новое устройство “ARDUINO-Bridge” , включаем его и задаем slave id (в скетче прописано 22, это значение задается произвольно, но не должно пересекаться с имеющимся). Сохраняемся.
Новое устройство должно появится в разделе Devices

P.S. для отладки можно использовать встроенную утилиту modbus_client ,
для этого в консоли WirenBoard выполняем
service wb-mqtt-serial stop (после этого UI перестанет работать. Для запуска опять выполнить: service wb-mqtt-serial start)

# modbus_client --debug -mrtu -b9600 -pnone -s2 /dev/ttyRS485-1 -a22 -t0x01 -r1
    SUCCESS: read 1 of elements:
          Data: 0x01
# modbus_client --debug -mrtu -b9600 -pnone -s2 /dev/ttyRS485-1 -a22 -t0x03 -r2
    SUCCESS: read 1 of elements:
          Data: 0x00de 
# modbus_client --debug -mrtu -b9600 -pnone -s2 /dev/ttyRS485-1 -a22 -t0x04 -r3
    SUCCESS: read 1 of elements:
          Data: 0x01bc
3 Likes

Здравствуйте! Да, в целом все правильно.

Если устройства запитаны от разных источников питания, то рекомендуется еще отдельно соединить GND (минусы) питания устройств: RS-485:Физическое подключение — Wiren Board

Также рекомендую прочитать информацию про подключение сторонних устройств к контроллеру по Modbus: Как подключить Modbus RTU-устройство стороннего производителя к контроллеру Wiren Board? — Wiren Board

1 Like

Отлично. Только еще сделать хранение адреса Modbus во флешке Ардуины, ну и обработчик изменения по Modbus чтоб не задавать для каждой при прошивке.

У меня пока в планах использовать arduino исключительно в качестве slave. Например те же температурные датчики I2C. Повесить через такой slave контроллер, а данные отдавать уже головному модулю WB.
Также 1-wire , которые проводами раскиданы по площади, хочу обезопасить WB6 и повесить их на отдельную дуину. Ибо дуин у меня много, а контроллер жалко и долго ждать в мои края.
Правильно ли я мыслю?
Нормально ли будут работать правила WB в такой связке с arduino ?

Ну тут скорее больше повысить надежность работы шины 1-wire и системы в целом. Если один датчик перестает работать, то перестанет работать вся шина 1-wire. А также это позволит снизить длину шины 1-wire и лучше диагностировать проблемы с датчиками, что хорошо. Шина RS-485 более помехозащищенная и позволяет передавать данные на большие расстояния. Поэтому в целом все правильно делаете.

Правила будут работать уже с топиками MQTT, а в топики MQTT данные записывает/читает по сети RS-485 драйвер wb-mqtt-serial. Если шина RS-485 будет надежно работать, то проблем не будет.

1 Like

Да, вполне нормально. У меня почти аналогичный проектик есть, только на Ардуинке веб-сервер, с поиском-отображением-привязкой 1-wire устройств.
Советую использовать watchdog и позабититься как о диагностике (вывести в регистр напряжение питания, uptime) так и о защите (обложить диодами или стабилитронами шины)

Мсье знает толк в.
:clap:

О да. Разбор-формирование http на сях… Жаль, забросил тогда еще в полуготовом виде, когда уперся в доступную флеш.

Ну - вообще оно не для того, мягко говоря.

И, да, вочдоги это чрезвычайно вредная практика. Оно просто не должно виснуть! Если виснет софтина, надо отлаживать ее до честной победы, при этом вочдог сильно затрудняет отладку.

Как практика, чтобы “не забыть” - очень даже неплохо.

Та же ардуинка с парой шин 1-wire у меня висла пару раз при грозе. Хотя - входы были защищены, шины в заземленном экране. А так работала стабильно.

Это - другое! (ц)
После отладки вочдог по таймауту цикла вполне поощряется. Хотя вот под боком уже больше 12 лет тикают часики на 328p - даже в грозу пока не висли…

А есть схемка рабочая? Ибо я больше прогер, чем электронщик. Я так понимаю хорошо бы купить отдельно WBE2-I-RS485-ISO , хотя бы на время отладки. Но мне его ждать теперь 2 недели.

А так, как я понял, можно подвязывать ардуинки/ESP32 слейвами в общую ModBus → mqtt.

Питать , буду от MW 5V , WB отдельно, дуины отдельно, землю беру общую + AB.

Пока на столе, схема из первого поста рабочая. Только земли rs485 соединить.
Потом все равно электронщика звать, не на соплях же это пускать в прод. :slight_smile:

Сам модулек на max485 - он достаточно “защищен” Про супрессоры, диоды надо будет думать уже при разработке платки.

Да, можно.

Выложу сюда текущую реализацию, с возможностью использовать “стандартные” регистры, такие как смена Modbus адреса, скорость-четность-битность шины.
WB_v_0.ino (20,5 КБ)

2 Likes

Текущий скетч:

/*
 * 
  WB some standard registers add
  v1.1
  Тут добавлены coil 0..5 включающие gpio. Перечислены ниже как "Выходы для Coil 00-05"  
*/

//Для работы с EEPROM
#include <EEPROM.h>

//https://github.com/emelianov/modbus-esp8266
#include <ModbusRTU.h>

//Объект md
ModbusRTU mb;

#define RXTX_PIN 12

#define LED_BUILTIN 13

//Адреса в EEPROM для хранения регистров
#define h80eeprom 0 //Modbus address
#define h6Eeeprom 1 //110 speed port
#define h6Feeprom 2 //111 parity bit
#define h70eeprom 3 //112 stop bit



//Выходы для Coil 00-05
#define Coil00  A4
#define Coil01  A3
#define Coil02  A2
#define Coil03  A1
#define Coil04  A0
#define Coil05  LED_BUILTIN

//Массив для хранения выходов, связанных с coil
static uint8_t coilOutList[] = {Coil00, Coil01, Coil02, Coil03, Coil04, Coil05};

//тут описываем прототипы функций. Чтобы при создании структуры уже были.
void h80setup(void);
uint16_t h80set(TRegister* reg, uint16_t val);

void h6Esetup(void); //скорость.
uint16_t h6Eset(TRegister* reg, uint16_t val);

void h6Fsetup(void); //четность
uint16_t h6Fset(TRegister* reg, uint16_t val);

void h70setup(void); // стопбиты
uint16_t h70set(TRegister* reg, uint16_t val);

void i68setup(void); // время работы
uint16_t i68set(TRegister* reg, uint16_t val);

//User function
void c00setup(void); // coil relay output
uint16_t c00set(TRegister* reg, uint16_t val);
uint16_t c00get(TRegister* reg, uint16_t val);

void hc8setup(void); // просто регистр
//void h111setup(void);
//void h112setup(void);

/*
void ftest80(int);
void ftest104(int);
*/

typedef void (* funcPtr) (); 
// Теперь создадим массив длиной три 
// и сложем в него указатели на функции.
// Массив имеет тип, который мы только что создали
//FuncPtr funcArray[2] = {h80setup, pf104};

//Все эти функции будут вызваны при запуске
//Сюда дописываем и свои тоже
const funcPtr funcArr[] = {
  h80setup,
  h6Esetup,
  h6Fsetup,
  h70setup,
  i68setup,
  //User function
  c00setup,
  hc8setup
  };

  //User function
  //c00setup,
  //hc8setup

/*
//Структура
struct structRegistersModbus {
  const byte rtype;             //Тип регистра. 1:coil 2:input 3:holding
  const int address;           //Адрес регистра
  const byte param2;           //
  const byte param3;           //
  const void (* FuncPtr) (int);//Указатель на функцию, обрабатывающую регистр
};

const structRegistersModbus  allRegs[] = {
  {3, 80,  33,  0, &f80 },
  {1, 104, 34,  0, &f104 },
  {2, 1,   35,  35, &f80 },
  {3, 2,   36,  36, &f80 },
  {4, 3,   37,  37, &f80 },
  {5, 4,   38,  38, &f80 },
  {1, 104, 34,  0, &f104 },
  {2, 1,   35,  35, &f80 },
  {3, 2,   36,  36, &f80 },
  {4, 3,   37,  37, &f80 },
  {5, 4,   38,  38, &f80 },
  {1, 104, 34,  0, &f104 },
  {2, 1,   35,  35, &f80 },
  {3, 2,   36,  36, &f80 },
  {4, 3,   37,  37, &f80 },
  {5, 4,   38,  38, &f80 },
  {1, 104, 34,  0, &f104 },
  {2, 1,   35,  35, &f80 },
  {3, 2,   36,  36, &f80 },
  {4, 3,   37,  37, &f80 },
  {5, 4,   38,  38, &f80 },

};
*/

/*
//Структура
struct structTest {
  const byte type; 
  const int address;
  const byte param2;
  const byte param3;
  const void (* FuncPtr) (int);
};

const structTest Test[] PROGMEM = {
  {0, 80, 33,  0, &ftest80 },
  {1, 104, 34,  0, &ftest104 },
  {2, 0, 0,  35, 0 },
  {0, 0, 0,  35, 0 },
  {1, 0, 0,  37, 0 },
  {2, 0, 0,  38, 0 },
};
*/

/*
//const structRegistersModbus menu[] PROGMEM = {
//  {3, 0, 0,  0, pf80 },
//  {3, 0, 0,  0, pf80 },
//};
*/

byte modbusNeedStart = 0; //флаг необходимости перезапуска Modbus

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  
  Serial.begin(9600);
  Serial.println("Start!");

  //переключаем GPIO в выходы 2do - в цикле надо
  //pinMode(LED_BUILTIN, OUTPUT);
  //for (byte i=0; i<(sizeof(coilOutList)/sizeof(coilOutList[0]); i++){
    for (byte i=0; i < (sizeof(coilOutList)/sizeof(coilOutList[0])); i++){
    pinMode(coilOutList[i], OUTPUT);
  }
  
  //Считаем количество элементов массива.
  //const int funcArrLenght = sizeof(funcArr)/sizeof(funcArr[0]);
  Serial.print("funcArrLenght=");
  Serial.println(sizeof(funcArr)/sizeof(funcArr[0]));
  //тут в цикле пройдем по всем элементам funcArr и запустим функцию.

  for (byte i=0; i < (sizeof(funcArr)/sizeof(funcArr[0])); i++){
    Serial.print(i);
    Serial.println("start");
    delay(40);
    funcArr[i]();
    Serial.println("startED");
    //Serial.print((byte)allRegs[i].rtype);
    //Serial.print("   ");
    //Serial.println((int)allRegs[i].address);

  }


//Настройка канала A таймера0
OCR0A = 128; //Устанавливаем регистр совпадения
TIMSK0 |= (1 << OCIE0A);  // включение прерываний по совпадению для 0 таймера, канал A
  
//Serial.println("STARTED!");delay (100);
}



//uint32_t i = 65280;
//byte *ptri = (byte*)&i;
//  Serial.print("byte0=: ");
//  Serial.println(*ptri);
//  Serial.print("byte1=: ");
//  Serial.println(*(ptri+1));

// the loop function runs over and over again forever
void loop() {
  if (modbusNeedStart){
    //Serial.println("modbusNeedStart!!!");
    modbusStart();//Запускаем Modbus
    modbusNeedStart = 0;
    //Serial.print("mb.Hreg(128) "); Serial.println(mb.Hreg(128));
  }
  mb.task();


  
  //digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(50);                       // wait
  //digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(50);     // wait
//  Serial.print("mb.Hreg(128) "); Serial.println(mb.Hreg(128));
  
/*
  if (Serial.available()){
    if (Serial.read() == '1'){
      // Обращаемся к текущему элементу массива,
      // тем самым делая вызов функции по очереди 
        //funcArray[i]();
      // Это тернарный оператор. Он проверяет на истинность
      // выражение после знака = и перед знаком ?.
      // если оно истинно, то переменной присваивается значение
      // после знака ?, если ложно - то значение после знака :.
      // здесь он нужен, чтобы листать индекс массива от 0 до 
      // длины массива минус один.  
      i = (i < 1) ? i+1 : 0;
    }
  }
  */

  //Serial.println(pgm_read_byte(allRegs[0].type));
//  Serial.print("type: ");
//  Serial.println(allRegs[0].type);
//  Serial.print("FuncPtr: ");
//  allRegs[0].FuncPtr(1);
//  allRegs[1].FuncPtr(1);
  //allRegs[0].FuncPtr();
  //Test[0].FuncPtr(15);
  //Test[1].FuncPtr(22);
}





// А вот и описания функций.
//
//Адрес 128 **************
void h80setup(){
  //Serial.println("enter h80setup");
  //Надо прочитать из EEPROM байт.
  byte readByte = eeRead(h80eeprom, 1);
  if (0 == readByte){//Если прочитанное равно нулю
    eeWrite(h80eeprom, 1, 1); //Записываем в EEprom 1
    readByte = 1;
  };
  //Serial.print("address=");Serial.println(readByte);
  mb.addHreg(128); //Создаем holding регистр с адресом 128
  mb.Hreg(128, readByte);//Устанавливаем значение
  mb.onSetHreg(128, h80set); // Add callback on Hreg 128 value set
  modbusNeedStart=1;//нужно перезапустить Modbus
}
uint16_t h80set(TRegister* reg, uint16_t val){
  //Serial.println("enter h80set");
  //Надо прочитать из EEPROM байт.
  byte readByte = eeRead(h80eeprom, 1);
  if (0!=val || 248>val){//Если прочитанное НЕ равно новому, не ноль и в диапазоне адресов
    eeWrite(h80eeprom, 1, val); //Записываем в EEprom 1
    modbusNeedStart=1;//нужно перезапустить Modbus
    return val;
  }
  else{
    return readByte; //Если значение неверно - просто оставляем старое
  }
}

//Скорость 110 **************
void h6Esetup(){
  //Serial.println("enter h6Esetup");
  //Надо прочитать из EEPROM байт.
  byte readByte = eeRead(h6Eeeprom, 1);
//  Serial.print("readByte");Serial.println(readByte);
  mb.addHreg(110); //Создаем holding регистр с адресом 110
  uint16_t speedReg = 0;
  switch(readByte){
    case 1:
      speedReg = 12;
      break;
    case 2:
      speedReg = 24;
      break;
    case 4:
      speedReg = 48;
      break;
    case 9:
      speedReg = 96;
      break;
    case 19:
      speedReg = 192;
      break;
    case 57:
      speedReg = 576;
      break;
    case 115:
      speedReg = 1152;
      break;
  }
  if (0 == speedReg){//Если прочитанное ни с чем не совпало
    eeWrite(h6Eeeprom, 1, 9); //Записываем в EEprom 9 (9600)
    speedReg = 96;
  }
  mb.Hreg(110, speedReg);//Устанавливаем значение
  mb.onSetHreg(110, h6Eset); // Add callback on Hreg 128 value set
  modbusNeedStart=1;//нужно перезапустить Modbus
}
uint16_t h6Eset(TRegister* reg, uint16_t val){
  //Serial.println("enter h6Eset");
  uint8_t toeeprom = 0;
  //Serial.print("val=");Serial.println(val);
  switch(val){
    case 12:
      toeeprom = 1;
      break;
    case 24:
      toeeprom = 2;
      break;
    case 48:
      toeeprom = 4;
      break;
    case 96:
      toeeprom = 9;
      break;
    case 192:
      toeeprom = 19;
      break;
    case 576:
      toeeprom = 57;
      break;
    case 1152:
      toeeprom = 115;
      break;
  }
  if (0 == toeeprom){//Если записанное не совпало со списком скоростей - то ой.
    eeWrite(h6Eeeprom, 1, toeeprom); //Записываем в EEprom 1
    modbusNeedStart=1;//нужно перезапустить Modbus
    return val;
  }
  else{
    return mb.Hreg(110); //Если значение неверно - просто оставляем старое
  }
}

//Четность 111 **************
void h6Fsetup(){
  //Serial.println("enter h6Esetup (111)");
  //Надо прочитать из EEPROM байт.
  byte readByte = eeRead(h6Feeprom, 1);
  //Serial.print("readByte h6Feeprom");Serial.println(readByte);
  //Serial.print("h6Feeprom=");Serial.println(h6Feeprom);
  //Serial.print("readByte 0 ");Serial.println(eeRead(0, 1));
  //Serial.print("readByte 1 ");Serial.println(eeRead(1, 1));
  //Serial.print("readByte 2 ");Serial.println(eeRead(2, 1));
  //Serial.print("readByte 3 ");Serial.println(eeRead(3, 1));
  
  mb.addHreg(111); //Создаем holding регистр с адресом 111
  uint8_t temp = 4;
  switch(readByte){
    case 1:
      temp = 0;
      break;
    case 2:
      temp = 1;
      break;
    case 3:
      temp = 2;
      break;
  }
  if (4 == temp){//Если прочитанное ни с чем не совпало
    eeWrite(h6Feeprom, 1, 1); //Записываем в EEprom 3 (2 стопбита)
    temp = 0;
  }
   /* 0 — нет бита чётности (none),
    1 — нечетный (odd),
    2 — четный (even) */
  //Serial.print("mb.Hreg(111, temp) ");Serial.println(temp);
  mb.Hreg(111, temp);//Устанавливаем значение четности
  mb.onSetHreg(111, h6Fset); // Add callback on Hreg 111 value set
  modbusNeedStart=1;//нужно перезапустить Modbus
}

uint16_t h6Fset(TRegister* reg, uint16_t val){
  //Serial.println("enter h6Fset (111)");
  uint8_t toeeprom = 0;
  switch(val){
    case 0:
      toeeprom = 1;
      break;
    case 1:
      toeeprom = 2;
      break;
    case 2:
      toeeprom = 3;
      break;
  }
  if (0!=toeeprom){//Если записанное не совпало со списком четностей
    eeWrite(h6Feeprom, 1, toeeprom); //Записываем в EEprom 1
    modbusNeedStart=1;//нужно перезапустить Modbus
    return val;
  }
  else{
    return mb.Hreg(111); //Если значение неверно - просто оставляем старое
  }
}

void h70setup(){
  //Serial.println("enter h70setup (112)");
  //Надо прочитать из EEPROM байт.
  byte readByte = eeRead(h70eeprom, 1);
  //Serial.print("readByte");Serial.println(readByte);
  mb.addHreg(112); //Создаем holding регистр с адресом 112
  uint8_t temp = 0;
  switch(readByte){
    case 1:
      temp = 1;
      break;
    case 2:
      temp = 2;
      break;
  }
  if (0 == temp){//Если прочитанное ни с чем не совпало
    eeWrite(h70eeprom, 1, 2); //Записываем в EEprom 9 (9600)
    temp = 2;
  }
  /*1 — 1 стопбит
    2 — 2 стопбит*/
  mb.Hreg(112, temp);//Устанавливаем значение четности
  mb.onSetHreg(112, h70set); // Add callback on Hreg 112 value set
  modbusNeedStart=1;//нужно перезапустить Modbus
}

uint16_t h70set(TRegister* reg, uint16_t val){
  //Serial.println("enter h70set (112)");
  uint8_t toeeprom = 0;
  //Serial.print("val=");Serial.println(val);
  switch(val){
    case 1:
      toeeprom = 1;
      break;
    case 2:
      toeeprom = 2;
      break;
  }
  if (0 != toeeprom){//Если записанное не совпало со списком четностей
    eeWrite(h70eeprom, 1, toeeprom); //Записываем в EEprom 1
    modbusNeedStart=1;//нужно перезапустить Modbus
    return val;
  }
  else{
    return mb.Hreg(112); //Если значение неверно - просто оставляем старое
  }
}


void i68setup(){
  Serial.println("enter h68setup (104)");
  //
  mb.addIreg(104, 0, 2); //Создаем input регистрЫ с адресом 104-105 записывая в них "0"
  //mb.Ireg(104, 0);//Устанавливаем значение счетчика. Только надо делать это не тут.
  //mb.Ireg(105, 0);//Устанавливаем значение счетчика. Только надо делать это не тут.
  mb.onGetIreg(104, i68get, 2);  // Add single callback for multiple Inputs. It will be called for each of these inputs value get
  //modbusNeedStart=1;//нужно перезапустить Modbus
}

uint16_t i68get(TRegister* reg, uint16_t val){
  //Serial.println("enter h68get (104)");
  //Serial.print(reg->address.address);
  //
  //mb.Ireg(104, TCCR0A);//Устанавливаем значение счетчика. Только надо делать это не тут.
  //так, надо взять ulong значение millis(), разделить его на 1000.
  uint32_t tempSecunds = millis()/1000;
  //Получим ulong секунд. Старшую часть записываем в 104 а младшую в 105
  //просто берем указатель16-разрядный на старшую часть, для начала.
  uint16_t* ptrTemp = (uint16_t*)&tempSecunds;
  if(reg->address.address == 104)
    //return OCR0A;
    return *(ptrTemp+1);
  if(reg->address.address == 105)
    return *(ptrTemp);
  return 256;
}


ISR(TIMER0_COMPA_vect)
{
     //digitalWrite(13, !digitalRead(13));
}



/*
void ftest80(int i){
  Serial.print("PACANI");
  Serial.println(i*2);
}

void ftest104(int i){
  Serial.print("VASCHE");
  Serial.println(i*2);
}
*/


void modbusStart(){
  //Serial.begin(9600, SERIAL_8N2); //четность и стопбиты - менять!
  //mb.begin(&Serial, RXTX_PIN); //запускаем на указанном порту Modbus
  
  uint32_t speedReg = 0;
  switch(mb.Hreg(110)){
    case 12:
      speedReg = 1200;
      break;
    case 24:
      speedReg = 2400;
      break;
    case 48:
      speedReg = 4800;
      break;
    case 96:
      speedReg = 9600;
      break;
    case 192:
      speedReg = 19200;
      break;
    case 576:
      speedReg = 57600;
      break;
    case 1152:
      speedReg = 115200;
      break;
  }

  // SERIAL_8N2 - это дефайн, смотреть можно на 
  // https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/HardwareSerial.h
  // с 68 строки
  // SERIAL_8N1 0x06
  // SERIAL_8N2 0x0E
  // SERIAL_8E1 0x26
  // SERIAL_8E2 0x2E
  // SERIAL_8O1 0x36
  // SERIAL_8O2 0x3E
  // видно что получается суммой четности 
  // N - 0x00
  // E - 0x20
  // O - 0x30
  // и стопбитов
  // 1 - 0x06
  // 2 - 0x0E
  uint8_t serialParam=0;
  switch(mb.Hreg(111)){
    case 0:
      serialParam = 0x0;
      break;
    case 1:
      serialParam = 0x30;
      break;
    case 2:
      serialParam = 0x20;
      break;
  }
    switch(mb.Hreg(112)){
    case 1:
      serialParam += 0x06;
      break;
    case 2:
      serialParam += 0x0E;
      break;
  }
  
  Serial.begin(speedReg, serialParam); //четность и стопбиты - менять!
  //Serial.begin(speedReg, SERIAL_8N2);
  mb.begin(&Serial, RXTX_PIN); //запускаем на указанном порту Modbus
  
  Serial.print("Modbus speed ");Serial.println(mb.Hreg(110));
  mb.setBaudrate(speedReg);// Это не скорость порта, это для задержек, смотри в исходник библиотеки.
  mb.slave(mb.Hreg(128));
  //mb.slave(1); // for debug
  //Serial.print("Modbus speed ");Serial.print(mb.Hreg(110));Serial.print(" speed ");Serial.println(speedReg);
  Serial.print("mb.Hreg(111) parity:");Serial.println(mb.Hreg(111));
  Serial.print("mb.Hreg(112) stopbit");Serial.println(mb.Hreg(112));
  Serial.print("Modbus parameter ");Serial.println(serialParam);
  //Serial.print("Modbus parameter SERIAL_8N2:");Serial.println(SERIAL_8N2);
  Serial.print("Modbus started with ");Serial.println(mb.Hreg(128));
}


uint32_t eeRead (int addr, byte lenght){
  //Возвращает считанное из EEPROM значение
  //Первый (младший) байт читается с addr, количество считывемых байт [1..2] задается в lenght
  uint32_t retVal = 0;
  byte *ptrb = (byte*)&retVal; //Указатель приведен к byte, чтобы обращаться к байтам int отдельно
    for (byte i = 0; i<lenght; i++){
      *(ptrb+i) = EEPROM.read(addr);
    }
  return *ptrb;
}

void eeWrite (uint32_t addr, byte lenght, uint32_t val){
  //Пишет в EEPROM значение
  //Первый (младший) байт пишется в addr, количество записываемых байт [1..2] задается в lenght
  byte *ptrb = (byte*)&val; //Указатель приведен к byte, чтобы обращаться к байтам int отдельно
  for (byte i = 0; i<lenght; i++){
      //Serial.print("write ");
      //Serial.print(*(ptrb+i));
      //Serial.print("write 1");
      //Serial.print(*(ptrb+0));
      EEPROM.update(addr, *(ptrb+i));
    }
}


//User register:

void c00setup(){
  Serial.println("enter c00setup (00)");
  //
  mb.addCoil(0, 0, 6); //Создаем coil регистрЫ с адресом 00-05 записывая в них "0"
  mb.onGetCoil(0, c00get, 6);  // Add single callback for multiple coils. It will be called for each of these coils value get
  mb.onSetCoil(0, c00set, 6);  // Add single callback for multiple coils. It will be called for each of these coils value SET
  //modbusNeedStart=1;//нужно перезапустить Modbus
  Serial.println("eXIT c00setup (00)");
}

uint16_t c00get(TRegister* reg, uint16_t val){
  //Serial.println("enter c00get (00)");
  //Serial.print(reg->address.address);
  //
  //uint32_t tempSecunds = millis()/1000;
  //Получим ulong секунд. Старшую часть записываем в 104 а младшую в 105
  //просто берем указатель16-разрядный на старшую часть, для начала.
  //uint16_t* ptrTemp = (uint16_t*)&tempSecunds;

  //if(reg->address.address == 0)
  //  return COIL_VAL(1);
  //  //return *(ptrTemp+1);
  //if(reg->address.address == 1)
  //  //return *(ptrTemp);
  //  return COIL_VAL(1);
  return val;
}

uint16_t c00set(TRegister* reg, uint16_t val) {
  //Serial.print("enter c00set (00)");
  //Serial.print(reg->address.address);
  //Serial.println(COIL_BOOL(val));
  digitalWrite(coilOutList[reg->address.address], COIL_BOOL(val));
  return val;
}

void hc8setup(){
  Serial.println("enter hс8setup (200)");
  //Надо прочитать из EEPROM байт.
//  byte readByte = eeRead(h70eeprom, 1);
//  Serial.print("readByte");Serial.println(readByte);
  mb.addHreg(200); //Создаем holding регистр с адресом 200
}

Ну и шаблон:

{
    "title": "arduino_title",
    "device_type": "arduino_modbus",
    "group": "g-diy",
    "device": {
        "name": "Arduino",
        "id": "arduino_modbus_slave",
        "min_read_registers": 2,
        "max_read_registers": 10,
        "response_timeout_ms": 200,
        "frame_timeout_ms": 100,
        "device_max_fail_cycles": 5,
        "guard_interval_us": 500,
        "groups": [
            {
                "title": "General",
                "id": "general"
            },
            {
                "title": "HW Info",
                "id": "hw_info"
            },
            {
                "title": "Debug",
                "id": "debug"
            }
        ],

        "parameters": {
            "baud_rate": {
                "title": "Baud rate",
                "description": "baud_rate_description",
                "address": 110,
                "reg_type": "holding",
                "enum": [96, 192, 384, 576, 1152],
                "default": 96,
                "enum_titles": [
                    "9600",
                    "19200",
                    "38400",
                    "57600",
                    "115200"
                ],
                "group": "general",
                "order": 1
            },
            "disable_indication": {
                "title": "Status LED",
                "address": 130,
                "reg_type": "holding",
                "enum": [0, 1],
                "enum_titles": ["Enabled", "Disabled"],
                "default": 0,
                "group": "general",
                "order": 3
            }
        },

        "channels": [
            {
                "name": "testreg",
                "reg_type": "holding",
                "address": 200,
                "scale": 1,
                "max": 255,
                "type": "value",
                "format": "u16",
                "group": "general"
            },

            {
                "name": "testreg0",
                "reg_type": "holding",
                "address": "200:0:1",
                "scale": 1,
                "max": 255,
                "type": "value",
                "format": "u16",
                "group": "general"
            },            

            {
                "name": "testreg1",
                "reg_type": "holding",
                "address": "200:1:1",
                "scale": 1,
                "max": 255,
                "type": "value",
                "format": "u16",
                "group": "general"
            },
            {
                "name": "testreg2",
                "reg_type": "holding",
                "address": "200:2:1",
                "scale": 1,
                "max": 255,
                "type": "value",
                "format": "u16",
                "group": "general"
            },
            {
                "name": "Out0",
                "reg_type": "coil",
                "address": 0,
                "type": "switch",
                "group": "general"
            },
            {
                "name": "Out1",
                "reg_type": "coil",
                "address": 1,
                "type": "switch",
                "group": "general"
            },
            {
                "name": "Out2",
                "reg_type": "coil",
                "address": 2,
                "type": "switch",
                "group": "general"
            },
            {
                "name": "Out3",
                "reg_type": "coil",
                "address": 3,
                "type": "switch",
                "group": "general"
            },
            {
                "name": "Out4",
                "reg_type": "coil",
                "address": 4,
                "type": "switch",
                "group": "general"
            },
            {
                "name": "Out5",
                "reg_type": "coil",
                "address": 5,
                "type": "switch",
                "group": "general"
            },

            {
                "name": "Supply Voltage",
                "reg_type": "holding",
                "address": 8,
                "scale": 0.1,
                "type": "voltage",
                "readonly": true,
                "enabled": false,
                "group": "debug"
            }
        ],
        "translations": {
            "en": {
                "arduino_title": "Arduino DIY modbus slave",
                "Current": "Load current"
        },
            "ru": {
                "General": "Общее",
                "HW Info": "Данные модуля",
                "Debug": "Диагностика",
                "no": "нет",
                "yes": "РґР°",
                "Disabled": "Отключен",
                "testreg": "Тестовый",
                "Supply Voltage": "Напряжение питания",
            }
        }
    }
}