Загрузка данных
#include <Arduino.h>
// ============================================================
// Протокол Modbus RTU — ведомое устройство (Slave)
// Вариант 10
//
// Slave ID = 12
// AO-регистр = 5
// Условие LED = Hreg < 73
//
// Поддерживаемые функции:
// 0x03 — Read Holding Registers
// 0x06 — Write Single Register
// ============================================================
// ---------- Прототипы функций ----------
void MB_F03(uint8_t* data, uint16_t len);
void MB_F06(uint8_t* data, uint16_t len);
uint16_t GetCrc16(uint8_t* data, uint16_t len);
// ---------- Настройки варианта 10 ----------
const uint16_t Slave_ID = 12;
const uint16_t Hreg_Addr = 5;
// ---------- Глобальные переменные ----------
uint16_t Hreg = 0;
uint16_t crc16;
uint8_t rcvData[256];
uint8_t idxBuf = 0;
#define debug false
// ============================================================
// SETUP
// ============================================================
void setup()
{
// Скорость последовательного порта
Serial.begin(115200);
// Встроенный светодиод Arduino
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
}
// ============================================================
// LOOP
// ============================================================
void loop()
{
// --------------------------------------------------------
// Принимаем Modbus RTU запрос.
//
// Стандартные запросы функций 03 и 06 имеют длину 8 байт:
//
// [Slave ID]
// [Function]
// [Address Hi]
// [Address Lo]
// [Data Hi]
// [Data Lo]
// [CRC Lo]
// [CRC Hi]
// --------------------------------------------------------
while (Serial.available())
{
if (idxBuf < sizeof(rcvData))
{
rcvData[idxBuf] = Serial.read();
if (debug)
{
Serial.write(rcvData[idxBuf]);
}
idxBuf++;
}
else
{
// Защита от переполнения буфера
idxBuf = 0;
}
}
// --------------------------------------------------------
// Для функций 03 и 06 запрос содержит 8 байт.
// Обрабатываем только полный кадр.
// --------------------------------------------------------
if (idxBuf >= 8)
{
// Проверяем Slave ID
if (rcvData[0] == Slave_ID)
{
// Проверяем CRC
crc16 = GetCrc16(rcvData, 6);
uint8_t crcLow = crc16 & 0xFF;
uint8_t crcHigh = (crc16 >> 8) & 0xFF;
if ((crcLow == rcvData[6]) &&
(crcHigh == rcvData[7]))
{
// Определяем функцию Modbus
switch (rcvData[1])
{
// Read Holding Registers
case 0x03:
MB_F03(rcvData, 8);
break;
// Write Single Register
case 0x06:
MB_F06(rcvData, 8);
break;
default:
// Неизвестная функция
break;
}
}
}
// После обработки начинаем принимать следующий кадр
idxBuf = 0;
}
// ========================================================
// Управление встроенным светодиодом
//
// Вариант 10:
// LED включён, если значение AO-регистра < 73
// ========================================================
if (Hreg < 73)
{
digitalWrite(LED_BUILTIN, HIGH);
}
else
{
digitalWrite(LED_BUILTIN, LOW);
}
}
// ============================================================
// ФУНКЦИЯ 03
// Read Holding Registers
// ============================================================
void MB_F03(uint8_t* data, uint16_t len)
{
// Адрес регистра состоит из двух байт:
// data[2] — старший байт
// data[3] — младший байт
uint16_t address =
((uint16_t)data[2] << 8) |
data[3];
// Количество регистров:
// data[4] — старший байт
// data[5] — младший байт
uint16_t quantity =
((uint16_t)data[4] << 8) |
data[5];
// Проверяем, что запрошен именно наш регистр
// и количество регистров равно 1.
if (address != Hreg_Addr || quantity != 1)
{
return;
}
// --------------------------------------------------------
// Формируем ответ:
//
// Slave ID
// Function
// Number of data bytes
// Register High
// Register Low
// CRC Low
// CRC High
// --------------------------------------------------------
uint8_t response[7];
response[0] = Slave_ID;
response[1] = 0x03;
response[2] = 0x02;
response[3] = Hreg >> 8;
response[4] = Hreg & 0xFF;
// Рассчитываем CRC
crc16 = GetCrc16(response, 5);
response[5] = crc16 & 0xFF;
response[6] = (crc16 >> 8) & 0xFF;
// Отправляем ответ
Serial.write(response, sizeof(response));
}
// ============================================================
// ФУНКЦИЯ 06
// Write Single Register
// ============================================================
void MB_F06(uint8_t* data, uint16_t len)
{
// Адрес регистра
uint16_t address =
((uint16_t)data[2] << 8) |
data[3];
// Проверяем, что запись идёт именно в AO-регистр №5
if (address != Hreg_Addr)
{
return;
}
// Получаем новое значение регистра
Hreg =
((uint16_t)data[4] << 8) |
data[5];
// --------------------------------------------------------
// Согласно Modbus RTU при функции 06 Slave
// возвращает запрос обратно.
// --------------------------------------------------------
uint8_t response[8];
for (uint8_t i = 0; i < 6; i++)
{
response[i] = data[i];
}
// Рассчитываем CRC
crc16 = GetCrc16(response, 6);
response[6] = crc16 & 0xFF;
response[7] = (crc16 >> 8) & 0xFF;
// Отправляем ответ
Serial.write(response, sizeof(response));
}
// ============================================================
// CRC16 MODBUS
// ============================================================
uint16_t GetCrc16(uint8_t* data, uint16_t len)
{
uint16_t crc = 0xFFFF;
for (uint16_t pos = 0; pos < len; pos++)
{
crc ^= (uint16_t)data[pos];
for (uint8_t i = 0; i < 8; i++)
{
if (crc & 0x0001)
{
crc >>= 1;
crc ^= 0xA001;
}
else
{
crc >>= 1;
}
}
}
return crc;
}