EBYTE MA01-AACX2240: программирование работы реле

60kvtmmgzsdzg-zpolenwhnb_gg.jpeg

Продолжаем изучение внешнего RS485/ModbusRTU блока расширения MA01-AACX2240 компании EBYTE. Сегодня мы разберём устройство Modbus RTU регистров, принципов доступа к ним и получения информации от MA01-AACX2240 и управления его работой.

А также разберём практические примеры скетчей и программирования блока MA01-AACX2240. Особую ценность нашему исследованию придаёт то, что подобная информация отсутствует в интернете и мы выступим тут в качестве пионеров и первопроходцев на этом непростом пути.

В результате MA01-AACX2240 и все блоки линейки MA0x-xxCXxxx0 станут доступны для практического использования.

Итак…

Официальный вариант работы с MA01-AACX2240


Официальный вариант работы с MA01-AACX2240, на мой взгляд, не выдерживает никакой критики: производитель предлагает скачать проприетарную утилиту для компьютера и использовать её для управления блоком MA01-AACX2240.

По большому счёту это просто несерьёзно — весь смысл блока MA01-AACX2240 в управлении им при помощи Modbus RTU с контроллеров, вариант управления им при помощи компьютерной утилиты — это не более, чем экзотический частный случай, непонятно кому и зачем вообще нужный. (Я тут даже не упоминаю о необходимости устанавливать на свой компьютер непонятно какую программу с неизвестно каким набором функций.)

hjkhcgteo1yt_7k1mzb0qjavakq.png

В общем, это определённо не наш путь — единственный осмысленный вариант использования блока MA01-AACX2240 и прочих блоков из этой серии — это подключение их к контроллеру, чем мы и займёмся далее.

Организация регистров MA01-AACX2240


Начнём мы с теории, поскольку без хорошего понимания того, что и как хранится в памяти MA01-AACX2240 и как это работает, невозможно программировать работу этого блока и вообще как-то его использовать.

Производитель в официальной документации приводит данные по Modbus RTU регистрам MA01-AACX2240, сгруппированные в две таблицы.

mnth5g_qairjnwcvo_pfjohitua.png

Во второй таблице собраны параметры, ассоциированные с работой аналоговых входов. При ближайшем изучении этих таблиц выяснилось, что они содержат множество неточностей и даже ошибок (я обнаружил ошибки в адресах регистров и т. д.). Я здесь привожу уже исправленные варианты таблиц — официальная документация так до сих пор и содержит эти ошибки и неточности.

ki4zvpgy-xnjdv82zimws-ad8mc.png

После беглого взгляда на таблицы видно, что MA01-AACX2240 содержит множество настроек и является довольно функциональным устройством с широкими возможностями по своему конфигурированию и работе.

Также можно заметить, что данные в этих таблицах сгруппированы по функциональным признакам, а не по принадлежности к тому или иному типу регистров, поэтому «разгребать» всё это нам придётся вручную, по мере изучения каждого конкретного параметра.

Организация доступа


Теперь немного об организации доступа к Modbus RTU регистрам MA01-AACX2240. Этот блок является Modbus RTU слейвом и его адрес задаётся смешанным хардверно — софтовым способом: часть адреса задаётся переключателями на плате (по умолчанию 31), а вторая часть — программно (по умолчанию 1). Полный адрес MA01-AACX2240 по умолчанию 32 (31 + 1) и его можно изменять в диапазоне от 1 до 247.

6-2sk1zaw00tcas2klgtcsd5e04.png

Сам принцип установки адреса переключателями прост — это обычное бинарное кодирование. Выше вы можете видеть иллюстрацию из официальной документации, поясняющую этот принцип.

Скорость связи по умолчанию выставлена 9600 и её можно потом изменить в настройках, хотя 9600 — это хороший выбор в большинстве случаев и, скорее всего, вам её не придётся изменять.

Тестовое оборудование


В качестве управляющего мы будем использовать контроллер Lavritech V7.1 Lite на ESP32 с внутренним модулем Lavritech RS485 V1. Можно было бы применить любой другой контроллер с поддержкой RS485, но под рукой был Lavritech V7.1 Lite, поэтому все эксперименты по управлению блоком MA01-AACX2240 мы будем проводить с ним.

-xdmpgn8crtuixjoedyiups1nlo.jpeg

Перечень оборудования для проведения тестов:

  • Клеммы на DIN-рейку ABB MA2,5 для подключения сетевых проводов
  • Автоматический выключатель ABB S201 C16 для подачи сетевого питания
  • Блок питания на DIN-рейку MEAN WELL 12В 2А для запитки MA01-AACX2240
  • Контроллер Lavritech V7.1 Lite с модулем Lavritech RS485 V1
  • Блок EBYTE MA01-AACX2240


Далее устанавливаем всё это на стенд и соединяем соответствующим образом. Контроллер Lavritech V7.1 Lite будет запитываться от USB разъёма компьютера, а соединение по интерфейсу RS485 между контроллером и блоком MA01-AACX2240 будет производится при помощи отрезка витой пары на контакты A-A и B-B клеммных колодок обоих устройств.

e7vsiappqdcmzoel4lqwlkhb1yq.jpeg

Программирование


Теперь займёмся непосредственно программированием работы блока MA01-AACX2240 при помощи микроконтроллера, в нашем случае на ESP32. И начнём мы с управления реле. Тут можно ещё заметить, что MA01-AACX2240 позволяет не только увеличить количество реле, подключённых к вашему контроллеру, но и, например, вынести эти реле на значительное расстояние от контроллера (десятки и сотни метров).

Управление реле


За работу с реле отвечают две строки из вышеприведённой таблицы. Первая строка отвечает за текущее состояние реле, а вторая — за их состояние при подаче питания. В этой статье мы будем заниматься только вопросами, связанными с первой строкой, то есть текущим состоянием реле.

Всё сказанное по первой строке можно будет отнести и ко второй — вы сможете легко управлять состоянием реле при включении питания, просто изменив один адрес регистров на другой.

2xo-qfgtiukvibav7xdv9wz64he.png

Тут нужно ещё напомнить, что все Modbus RTU регистры делятся на четыре поддиапазона, которые различаются своими свойствами, разрядностью, возможностью записи и т. д.

1. Discrete Inputs. 8-битные регистры, доступные только для чтения (код функции 0×02). Используются в MA01-AACX2240 для представления данных о состоянии DI входов.

2. Coils. 8-битные регистры, доступные для чтения и записи (коды функций 0×01, 0×05, 0×0F). Используются в MA01-AACX2240 для обслуживания DO (реле).

3. Input Registers. 16-битные регистры, доступные только для чтения (код функции 0×04). Используются в MA01-AACX2240 для предоставления информации о состоянии AI.

4. Holding Registers. 16-битные регистры, доступные для чтения и записи (коды функций 0×03, 0×06, 0×10). Используются в MA01-AACX2240 для всего остального (хранения настроек, текущих значений и т. п.).

Как вы уже поняли, в этой статье мы будем разбирать проблематику второго пункта «Coils», поскольку он отвечает за работу с реле.

Здесь используются четыре 8-битных регистра

0х0000 (00000)
0х0001 (00001)
0х0002 (00002)
0х0003 (00003)


для хранения данных о текущем состоянии каждого из четырёх реле на борту MA01-AACX2240. Мы можем считывать эти регистры и записывать их (тем самым меняя состояние реле).

Скетч чтения состояния реле


Для управления блоком MA01-AACX2240, выступающего в качестве слейва, мы будем использовать Arduino библиотеку ModbusMaster.

Исходные данные:

Поскольку управляющий контроллер Lavritech V7.1 Lite имеет в своей основе ESP32, то для работы с интерфейсом RS485 мы будем использовать «хардверный» Serial1.

#define RS485_SerialNum  1


Далее определяем RX и TX пины интерфейса RS485 на Lavritech V7.1 Lite (на вашем контроллере значения GPIO могут быть другими)

#define RS485_RX  16
#define RS485_TX  17


В нашем случае управление приёмом/передачей осуществляется автоматически, поэтому пин DE не используется

//#define RS485_DE  -1


и соответствующие пре- и пост- функции остаются пустыми (нам не нужно программно переключать направление передачи).

void preTransmission()  {;}
void postTransmission() {;}


Затем задаём значение скорости передачи (9600 выставлено по умолчанию в MA01-AACX2240).

#define MODBUS_BAUD 9600


Номер слейва нашего блока MA01-AACX2240 (32 по умолчанию).

#define MODBUS_ID   32


И типовую задержку проведения операции. Вы можете поэкспериментировать с этим значением и уменьшить его, тем самым увеличив производительность обмена по Modbus RTU.

#define REQ_DELAY   100


Далее определяем адреса регистров всех четырёх реле MA01-AACX2240.

#define ADR_COILS_DO0 0
#define ADR_COILS_DO1 1
#define ADR_COILS_DO2 2
#define ADR_COILS_DO3 3


Полный код скетча, который реализует чтения регистров состояния реле на блоке MA01-AACX2240:

/*
  Lavritech V7.1 Lite (RS485 V1)
  EBYTE MA01-AACX2240
  Modbus RTU Read DO example
*/

#include 
#include 

#define RS485_SerialNum  1

#define RS485_RX  16
#define RS485_TX  17
//#define RS485_DE  -1

#define MODBUS_BAUD 9600
#define MODBUS_ID   32
#define REQ_DELAY   100

#define ADR_COILS_DO0 0
#define ADR_COILS_DO1 1
#define ADR_COILS_DO2 2
#define ADR_COILS_DO3 3

int cntOk = 0;
int cntEr = 0;
uint8_t result;

HardwareSerial RsSerial(RS485_SerialNum);

ModbusMaster RS;

byte do4[4] = {0, 0, 0, 0,};

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("Start MA01-AACX2240 Read DO example..."));
  
  RsSerial.begin(MODBUS_BAUD, SERIAL_8N1, RS485_RX, RS485_TX);

  RS.begin(MODBUS_ID, RsSerial);
  RS.preTransmission(preTransmission);
  RS.postTransmission(postTransmission);
} // setup

void preTransmission()  {;}
void postTransmission() {;}

word readCoils1(word shift) {
  word res = 0;
  
  result = RS.readCoils(shift, 1);
  if (result == RS.ku8MBSuccess) {
    res = RS.getResponseBuffer(0);
    cntOk++;
  } else {
    cntEr++;
  }
  delay(REQ_DELAY);

  return res;
}

void displayInfo() {
  Serial.print(F(" ok:")); Serial.print(cntOk);
  Serial.print(F("/")); Serial.print(cntEr);
  
  Serial.print(F(" DO:"));
  Serial.print(do4[0]);
  Serial.print(do4[1]);
  Serial.print(do4[2]);
  Serial.print(do4[3]);
  
  Serial.println();
}

void loop() {
  do4[0] = readCoils1(ADR_COILS_DO0);
  do4[1] = readCoils1(ADR_COILS_DO1);
  do4[2] = readCoils1(ADR_COILS_DO2);
  do4[3] = readCoils1(ADR_COILS_DO3);

  displayInfo();
  delay(1000);
} // loop


Здесь чтением значений регистров занимается функция readCoils (shift, 1)

  result = RS.readCoils(shift, 1);


где ей в качестве параметров передаётся адрес регистра (смещение в поддиапазоне Coils) и »1» в качестве инструкции прочитать значение одного регистра.

Таким образом производится четыре запроса, по одному на каждый регистр.

  do4[0] = readCoils1(ADR_COILS_DO0);
  do4[1] = readCoils1(ADR_COILS_DO1);
  do4[2] = readCoils1(ADR_COILS_DO2);
  do4[3] = readCoils1(ADR_COILS_DO3);


Затем результат работы нашего скетча выводится в Serial:

xtvhajo_pa0zd3ry-5qsj8dvpdw.png

Здесь видно, что реле #0 и реле #3 включены, а реле #1 и #2 — выключены.

Также видно, что все запросы проходят успешно и нет ни одной ошибки проведения операций чтения регистров DO с блока MA01-AACX2240. Также обратите внимание, что количество запросов возрастает на 4 за один цикл. Это непроизводительно и неэффективно — мы делаем 4 запроса для получения состояния всех реле.

Функция readCoils () позволяет читать сразу несколько последовательно расположенных регистров и можно получить все 4 значения за один запрос. Этот вариант скетча мы разберём далее.

Скетч чтения состояния 4-х реле за 1 запрос


Я не буду приводить здесь полный код скетча чтения состояния 4-х реле за 1 запрос, он практически полностью повторяет предыдущий, приведу только изменения кода.

Вызываем функцию чтения состояния 4-х реле за 1 запрос, передавая ей в качестве параметра адрес реле #0 (остальные три идут последовательно за ним).

readCoils4(ADR_COILS_DO0);


Код самой функции чтения состояния 4-х реле за 1 запрос. Здесь также последовательно читаются 4 байта из приёмного буфера.

void readCoils4(word shift) {
  result = RS.readCoils(shift, 4);
  if (result == RS.ku8MBSuccess) {
    do4[0] = bitRead(RS.getResponseBuffer(0), 0);
    do4[1] = bitRead(RS.getResponseBuffer(0), 1);
    do4[2] = bitRead(RS.getResponseBuffer(0), 2);
    do4[3] = bitRead(RS.getResponseBuffer(0), 3);
    cntOk++;
  } else {
    cntEr++;
  }
  delay(REQ_DELAY);
}


Результат работы модернизированного скетча — как вы видите, результат тот же, только для его получения используется всего один запрос, вместо четырёх, что гораздо более эффективно и правильно.

8sgiq6cvqavv6th1cy3jndpirry.png

Скетч управления состоянием реле


Получение информации о состоянии реле — это хорошо, но не менее интересно научиться изменять состояние реле — это позволит нам управлять подключённым к MA01-AACX2240 оборудованием.

Для этого модернизируем скетч и добавляем в него функцию writeCoils1().

void writeCoils1(word shift, byte val) {
  result = RS.writeSingleCoil(shift, val);
  if (result == RS.ku8MBSuccess) {
    cntOk++;
  } else {
    cntEr++;
  }
  delay(REQ_DELAY);
}


А также её вызов для изменения состояния реле (в данном случае мы включаем все четыре реле).

  writeCoils1(ADR_COILS_DO0, 1);
  writeCoils1(ADR_COILS_DO1, 1);
  writeCoils1(ADR_COILS_DO2, 1);
  writeCoils1(ADR_COILS_DO3, 1);


Полный код скетча управления реле.

/*
  Lavritech V7.1 Lite (RS485 V1)
  EBYTE MA01-AACX2240
  Modbus RTU Write DO example
*/

#include 
#include 

#define RS485_SerialNum  1

#define RS485_RX  16
#define RS485_TX  17
//#define RS485_DE  -1

#define MODBUS_BAUD 9600
#define MODBUS_ID   32
#define REQ_DELAY   100

#define ADR_COILS_DO0 0
#define ADR_COILS_DO1 1
#define ADR_COILS_DO2 2
#define ADR_COILS_DO3 3

int cntOk = 0;
int cntEr = 0;
uint8_t result;

HardwareSerial RsSerial(RS485_SerialNum);

ModbusMaster RS;

byte do4[4] = {0, 0, 0, 0,};

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("Start MA01-AACX2240 Write DO example..."));
  
  RsSerial.begin(MODBUS_BAUD, SERIAL_8N1, RS485_RX, RS485_TX);

  RS.begin(MODBUS_ID, RsSerial);
  RS.preTransmission(preTransmission);
  RS.postTransmission(postTransmission);
} // setup

void preTransmission()  {;}
void postTransmission() {;}

void writeCoils1(word shift, byte val) {
  result = RS.writeSingleCoil(shift, val);
  if (result == RS.ku8MBSuccess) {
    cntOk++;
  } else {
    cntEr++;
  }
  delay(REQ_DELAY);
}

void readCoils4(word shift) {
  result = RS.readCoils(shift, 4);
  if (result == RS.ku8MBSuccess) {
    do4[0] = bitRead(RS.getResponseBuffer(0), 0);
    do4[1] = bitRead(RS.getResponseBuffer(0), 1);
    do4[2] = bitRead(RS.getResponseBuffer(0), 2);
    do4[3] = bitRead(RS.getResponseBuffer(0), 3);
    cntOk++;
  } else {
    cntEr++;
  }
  delay(REQ_DELAY);
}

void displayInfo() {
  Serial.print(F(" ok:")); Serial.print(cntOk);
  Serial.print(F("/")); Serial.print(cntEr);
  
  Serial.print(F(" DO:"));
  Serial.print(do4[0]);
  Serial.print(do4[1]);
  Serial.print(do4[2]);
  Serial.print(do4[3]);
  
  Serial.println();
} // setup

void loop() {
  writeCoils1(ADR_COILS_DO0, 1);
  writeCoils1(ADR_COILS_DO1, 1);
  writeCoils1(ADR_COILS_DO2, 1);
  writeCoils1(ADR_COILS_DO3, 1);

  readCoils4(ADR_COILS_DO0);

  displayInfo();
  delay(1000);
} // loop


Вот результат работы нашего скетча по изменению состояния реле:

am4gm2kta55p00zcfoocctifzue.png

Здесь видно, что в этом варианте скетча нам приходится делать 5 запросов за один цикл (4 на запись и 1 на чтение), что тоже не очень эффективно.

Скетч изменения состояния 4-х реле за 1 запрос


Изменим немного предыдущий скетч, чтобы операция управления 4-я реле требовала не четыре, а всего один запрос. (Я снова не буду дублировать предыдущий скетч, приведу только его дополнения.)

Для этого добавляем функцию writeCoils4(), которая оперирует изменением состояния сразу 4-х реле.

void writeCoils4(word shift, word val) {
  RS.setTransmitBuffer(0, val);
  result = RS.writeMultipleCoils(shift, 4);
  if (result == RS.ku8MBSuccess) {
    cntOk++;
  } else {
    cntEr++;
  }
  delay(REQ_DELAY);
}


И её вызов.

writeCoils4(ADR_COILS_DO0, DO_1001);


Здесь требуются некоторые пояснения по механике работы функции writeCoils4(). Она использует библиотечную функцию writeMultipleCoils (), которая позволяет записывать сразу несколько последовательно расположенных регистров. Причём в качестве параметров ей передаётся начальный адрес и количество записываемых регистров.

А вот сами значения регистров берутся из внутреннего буфера (библиотеки) и устанавливаются функцией setTransmitBuffer ().

  RS.setTransmitBuffer(0, val);


Другими словами, прежде чем использовать функцию writeMultipleCoils (), нам нужно вызвать функцию setTransmitBuffer () и передать ей значения регистров в виде числа (используется двоичное кодирование).

Для упрощения группового манипулирования состоянием реле в скетче можно определить специальные константы, которые потом просто подставлять в функцию writeCoils4().

//         4321
#define DO_0000 0
#define DO_0001 1
#define DO_0010 2
#define DO_0100 4
#define DO_1000 8
#define DO_0011 3
#define DO_0110 6
#define DO_1100 12
#define DO_1001 9
#define DO_1111 15


В нашем примере это

writeCoils4(ADR_COILS_DO0, DO_1001);


константа DO_1001, то есть число 9.

Для особо пытливых читателей и лучшего понимания, приведу здесь ещё определения двух вышерассмотренных функций из библиотеки ModbusMaster.

//setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
//writeMultipleCoils(uint16_t u16WriteAddress, uint16_t u16BitQty)


В итоге мы получаем тот же самый результат, только для его достижения нам уже требуется сделать всего 2 запроса (на запись и чтение), а не 5 как в предыдущем случае.

j7hbibhpnodunjycavodt8jewcm.png

Что, как вы сами понимаете, благоприятно сказывается на загрузке Modbus интерфейса и работе всей системы.

Заключение


В этой статье мы познакомились с организацией регистров Modbus RTU интерфейса блока EBYTE MA01-AACX2240 и научились программировать работу его реле. В процентном отношении это примерно 5–10 процентов от общего количества информации о программировании MA01-AACX2240. Незатронутыми оказались вопросы работы с ещё тремя группами регистров, цифровыми и аналоговыми входами, настройками MA01-AACX2240 и т. д. и т. п., так что нам будет чем заняться в следующих статьях.

p-u9l27ynelxi92bcmdxhu76ma8.png

© Habrahabr.ru