933 lines
31 KiB
C++
933 lines
31 KiB
C++
/*
|
||
Доработка библиотеки для "Народного контроллера теплового насоса"
|
||
Автор pav2000 firstlast2007@gmail.com
|
||
Добавлены изменения для работы с инвертором Omron MX2
|
||
- поддерживается функция проверки связи (код функции 0х08)
|
||
для проверки функции используйте LinkTestOmronMX2Only(code)
|
||
где code - проверочный код (любое число uint16_t),
|
||
в случае успеха первый элемент буфера будет содержать этот код
|
||
- сделана обработка ошибок инвертора (в коде функции добавляется 0х80)
|
||
при этом возвращается состяние ku8MBErrorOmronMX2,
|
||
первый элемент буфера при этом содержит код ошибки
|
||
*
|
||
* Some additional - vad7@yahoo.com
|
||
*
|
||
*/
|
||
/**
|
||
@file
|
||
Arduino library for communicating with Modbus slaves over RS232/485 (via RTU protocol).
|
||
*/
|
||
/*
|
||
|
||
ModbusMaster.cpp - Arduino library for communicating with Modbus slaves
|
||
over RS232/485 (via RTU protocol).
|
||
|
||
Library:: ModbusMaster
|
||
|
||
Copyright:: 2009-2016 Doc Walker
|
||
|
||
Licensed under the Apache License, Version 2.0 (the "License");
|
||
you may not use this file except in compliance with the License.
|
||
You may obtain a copy of the License at
|
||
|
||
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
||
Unless required by applicable law or agreed to in writing, software
|
||
distributed under the License is distributed on an "AS IS" BASIS,
|
||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
See the License for the specific language governing permissions and
|
||
limitations under the License.
|
||
*/
|
||
#include "ModbusMaster.h"
|
||
|
||
ModbusMaster::ModbusMaster(void)
|
||
{
|
||
_idle = 0;
|
||
_preTransmission = 0;
|
||
_postTransmission = 0;
|
||
last_transaction_time = 0;
|
||
}
|
||
|
||
/**
|
||
Initialize class object.
|
||
|
||
Assigns the Modbus slave ID and serial port.
|
||
Call once class has been instantiated, typically within setup().
|
||
|
||
@param slave Modbus slave ID (1..255)
|
||
@param &serial reference to serial port object (Serial, Serial1, ... Serial3)
|
||
@ingroup setup
|
||
*/
|
||
void ModbusMaster::begin(uint8_t slave, Stream &serial)
|
||
{
|
||
// txBuffer = (uint16_t*) calloc(ku8MaxBufferSize, sizeof(uint16_t));
|
||
_u8MBSlave = slave;
|
||
_serial = &serial;
|
||
_u8TransmitBufferIndex = 0;
|
||
u16TransmitBufferLength = 0;
|
||
}
|
||
|
||
|
||
void ModbusMaster::beginTransmission(uint16_t u16Address)
|
||
{
|
||
_u16WriteAddress = u16Address;
|
||
_u8TransmitBufferIndex = 0;
|
||
u16TransmitBufferLength = 0;
|
||
}
|
||
|
||
/*/ eliminate this function in favor of using existing MB request functions
|
||
uint8_t ModbusMaster::requestFrom(uint16_t address, uint16_t quantity)
|
||
{
|
||
// clamp to buffer length
|
||
if (quantity > ku8MaxBufferSize)
|
||
{
|
||
quantity = ku8MaxBufferSize;
|
||
}
|
||
// set rx buffer iterator vars
|
||
_u8ResponseBufferIndex = 0;
|
||
_u8ResponseBufferLength = quantity;
|
||
|
||
return quantity;
|
||
}
|
||
*/
|
||
|
||
void ModbusMaster::sendBit(bool data)
|
||
{
|
||
uint8_t txBitIndex = u16TransmitBufferLength % 16;
|
||
if ((u16TransmitBufferLength >> 4) < ku8MaxBufferSize)
|
||
{
|
||
if (0 == txBitIndex)
|
||
{
|
||
_u16TransmitBuffer[_u8TransmitBufferIndex] = 0;
|
||
}
|
||
bitWrite(_u16TransmitBuffer[_u8TransmitBufferIndex], txBitIndex, data);
|
||
u16TransmitBufferLength++;
|
||
_u8TransmitBufferIndex = u16TransmitBufferLength >> 4;
|
||
}
|
||
}
|
||
|
||
|
||
void ModbusMaster::send(uint16_t data)
|
||
{
|
||
if (_u8TransmitBufferIndex < ku8MaxBufferSize)
|
||
{
|
||
_u16TransmitBuffer[_u8TransmitBufferIndex++] = data;
|
||
u16TransmitBufferLength = _u8TransmitBufferIndex << 4;
|
||
}
|
||
}
|
||
|
||
|
||
void ModbusMaster::send(uint32_t data)
|
||
{
|
||
send(lowWord(data));
|
||
send(highWord(data));
|
||
}
|
||
|
||
|
||
void ModbusMaster::send(uint8_t data)
|
||
{
|
||
send(word(data));
|
||
}
|
||
|
||
|
||
uint8_t ModbusMaster::available(void)
|
||
{
|
||
return _u8ResponseBufferLength - _u8ResponseBufferIndex;
|
||
}
|
||
|
||
|
||
uint16_t ModbusMaster::receive(void)
|
||
{
|
||
if (_u8ResponseBufferIndex < _u8ResponseBufferLength)
|
||
{
|
||
return _u16ResponseBuffer[_u8ResponseBufferIndex++];
|
||
}
|
||
else
|
||
{
|
||
return 0xFFFF;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
Set idle time callback function (cooperative multitasking).
|
||
|
||
This function gets called in the idle time between transmission of data
|
||
and response from slave. Do not call functions that read from the serial
|
||
buffer that is used by ModbusMaster. Use of i2c/TWI, 1-Wire, other
|
||
serial ports, etc. is permitted within callback function.
|
||
|
||
@see ModbusMaster::ModbusMasterTransaction()
|
||
*/
|
||
void ModbusMaster::idle(void (*idle)())
|
||
{
|
||
_idle = idle;
|
||
}
|
||
|
||
/**
|
||
Set pre-transmission callback function.
|
||
|
||
This function gets called just before a Modbus message is sent over serial.
|
||
Typical usage of this callback is to enable an RS485 transceiver's
|
||
Driver Enable pin, and optionally disable its Receiver Enable pin.
|
||
|
||
@see ModbusMaster::ModbusMasterTransaction()
|
||
@see ModbusMaster::postTransmission()
|
||
*/
|
||
void ModbusMaster::preTransmission(void (*preTransmission)())
|
||
{
|
||
_preTransmission = preTransmission;
|
||
}
|
||
|
||
/**
|
||
Set post-transmission callback function.
|
||
|
||
This function gets called after a Modbus message has finished sending
|
||
(i.e. after all data has been physically transmitted onto the serial
|
||
bus).
|
||
|
||
Typical usage of this callback is to enable an RS485 transceiver's
|
||
Receiver Enable pin, and disable its Driver Enable pin.
|
||
|
||
@see ModbusMaster::ModbusMasterTransaction()
|
||
@see ModbusMaster::preTransmission()
|
||
*/
|
||
void ModbusMaster::postTransmission(void (*postTransmission)())
|
||
{
|
||
_postTransmission = postTransmission;
|
||
}
|
||
|
||
|
||
/**
|
||
Retrieve data from response buffer.
|
||
|
||
@see ModbusMaster::clearResponseBuffer()
|
||
@param u8Index index of response buffer array (0x00..0x3F)
|
||
@return value in position u8Index of response buffer (0x0000..0xFFFF)
|
||
@ingroup buffer
|
||
*/
|
||
uint16_t ModbusMaster::getResponseBuffer(uint8_t u8Index)
|
||
{
|
||
if (u8Index < ku8MaxBufferSize)
|
||
{
|
||
return _u16ResponseBuffer[u8Index];
|
||
}
|
||
else
|
||
{
|
||
return 0xFFFF;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
Clear Modbus response buffer.
|
||
|
||
@see ModbusMaster::getResponseBuffer(uint8_t u8Index)
|
||
@ingroup buffer
|
||
*/
|
||
void ModbusMaster::clearResponseBuffer()
|
||
{
|
||
uint8_t i;
|
||
|
||
for (i = 0; i < ku8MaxBufferSize; i++)
|
||
{
|
||
_u16ResponseBuffer[i] = 0;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
Place data in transmit buffer.
|
||
|
||
@see ModbusMaster::clearTransmitBuffer()
|
||
@param u8Index index of transmit buffer array (0x00..0x3F)
|
||
@param u16Value value to place in position u8Index of transmit buffer (0x0000..0xFFFF)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup buffer
|
||
*/
|
||
uint8_t ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
|
||
{
|
||
if (u8Index < ku8MaxBufferSize)
|
||
{
|
||
_u16TransmitBuffer[u8Index] = u16Value;
|
||
return ku8MBSuccess;
|
||
}
|
||
else
|
||
{
|
||
return ku8MBIllegalDataAddress;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
Clear Modbus transmit buffer.
|
||
|
||
@see ModbusMaster::setTransmitBuffer(uint8_t u8Index, uint16_t u16Value)
|
||
@ingroup buffer
|
||
*/
|
||
void ModbusMaster::clearTransmitBuffer()
|
||
{
|
||
uint8_t i;
|
||
|
||
for (i = 0; i < ku8MaxBufferSize; i++)
|
||
{
|
||
_u16TransmitBuffer[i] = 0;
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x01 Read Coils.
|
||
|
||
This function code is used to read from 1 to 2000 contiguous status of
|
||
coils in a remote device. The request specifies the starting address,
|
||
i.e. the address of the first coil specified, and the number of coils.
|
||
Coils are addressed starting at zero.
|
||
|
||
The coils in the response buffer are packed as one coil per bit of the
|
||
data field. Status is indicated as 1=ON and 0=OFF. The LSB of the first
|
||
data word contains the output addressed in the query. The other coils
|
||
follow toward the high order end of this word and from low order to high
|
||
order in subsequent words.
|
||
|
||
If the returned quantity is not a multiple of sixteen, the remaining
|
||
bits in the final data word will be padded with zeros (toward the high
|
||
order end of the word).
|
||
|
||
@param u16ReadAddress address of first coil (0x0000..0xFFFF)
|
||
@param u16BitQty quantity of coils to read (1..2000, enforced by remote device)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup discrete
|
||
*/
|
||
uint8_t ModbusMaster::readCoils(uint16_t u16ReadAddress, uint16_t u16BitQty)
|
||
{
|
||
_u16ReadAddress = u16ReadAddress;
|
||
_u16ReadQty = u16BitQty;
|
||
return ModbusMasterTransaction(ku8MBReadCoils);
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x02 Read Discrete Inputs.
|
||
|
||
This function code is used to read from 1 to 2000 contiguous status of
|
||
discrete inputs in a remote device. The request specifies the starting
|
||
address, i.e. the address of the first input specified, and the number
|
||
of inputs. Discrete inputs are addressed starting at zero.
|
||
|
||
The discrete inputs in the response buffer are packed as one input per
|
||
bit of the data field. Status is indicated as 1=ON; 0=OFF. The LSB of
|
||
the first data word contains the input addressed in the query. The other
|
||
inputs follow toward the high order end of this word, and from low order
|
||
to high order in subsequent words.
|
||
|
||
If the returned quantity is not a multiple of sixteen, the remaining
|
||
bits in the final data word will be padded with zeros (toward the high
|
||
order end of the word).
|
||
|
||
@param u16ReadAddress address of first discrete input (0x0000..0xFFFF)
|
||
@param u16BitQty quantity of discrete inputs to read (1..2000, enforced by remote device)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup discrete
|
||
*/
|
||
uint8_t ModbusMaster::readDiscreteInputs(uint16_t u16ReadAddress,
|
||
uint16_t u16BitQty)
|
||
{
|
||
_u16ReadAddress = u16ReadAddress;
|
||
_u16ReadQty = u16BitQty;
|
||
return ModbusMasterTransaction(ku8MBReadDiscreteInputs);
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x03 Read Holding Registers.
|
||
|
||
This function code is used to read the contents of a contiguous block of
|
||
holding registers in a remote device. The request specifies the starting
|
||
register address and the number of registers. Registers are addressed
|
||
starting at zero.
|
||
|
||
The register data in the response buffer is packed as one word per
|
||
register.
|
||
|
||
@param u16ReadAddress address of the first holding register (0x0000..0xFFFF)
|
||
@param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup register
|
||
*/
|
||
uint8_t ModbusMaster::readHoldingRegisters(uint16_t u16ReadAddress,
|
||
uint16_t u16ReadQty)
|
||
{
|
||
_u16ReadAddress = u16ReadAddress;
|
||
_u16ReadQty = u16ReadQty;
|
||
return ModbusMasterTransaction(ku8MBReadHoldingRegisters);
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x04 Read Input Registers.
|
||
|
||
This function code is used to read from 1 to 125 contiguous input
|
||
registers in a remote device. The request specifies the starting
|
||
register address and the number of registers. Registers are addressed
|
||
starting at zero.
|
||
|
||
The register data in the response buffer is packed as one word per
|
||
register.
|
||
|
||
@param u16ReadAddress address of the first input register (0x0000..0xFFFF)
|
||
@param u16ReadQty quantity of input registers to read (1..125, enforced by remote device)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup register
|
||
*/
|
||
uint8_t ModbusMaster::readInputRegisters(uint16_t u16ReadAddress,
|
||
uint8_t u16ReadQty)
|
||
{
|
||
_u16ReadAddress = u16ReadAddress;
|
||
_u16ReadQty = u16ReadQty;
|
||
return ModbusMasterTransaction(ku8MBReadInputRegisters);
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x05 Write Single Coil.
|
||
|
||
This function code is used to write a single output to either ON or OFF
|
||
in a remote device. The requested ON/OFF state is specified by a
|
||
constant in the state field. A non-zero value requests the output to be
|
||
ON and a value of 0 requests it to be OFF. The request specifies the
|
||
address of the coil to be forced. Coils are addressed starting at zero.
|
||
|
||
@param u16WriteAddress address of the coil (0x0000..0xFFFF)
|
||
@param u8State 0=OFF, non-zero=ON (0x00..0xFF)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup discrete
|
||
*/
|
||
uint8_t ModbusMaster::writeSingleCoil(uint16_t u16WriteAddress, uint8_t u8State)
|
||
{
|
||
_u16WriteAddress = u16WriteAddress;
|
||
_u16WriteQty = (u8State ? 0xFF00 : 0x0000);
|
||
// Serial.println(_u16WriteQty) ;
|
||
return ModbusMasterTransaction(ku8MBWriteSingleCoil);
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x06 Write Single Register.
|
||
|
||
This function code is used to write a single holding register in a
|
||
remote device. The request specifies the address of the register to be
|
||
written. Registers are addressed starting at zero.
|
||
|
||
@param u16WriteAddress address of the holding register (0x0000..0xFFFF)
|
||
@param u16WriteValue value to be written to holding register (0x0000..0xFFFF)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup register
|
||
*/
|
||
uint8_t ModbusMaster::writeSingleRegister(uint16_t u16WriteAddress,
|
||
uint16_t u16WriteValue)
|
||
{
|
||
_u16WriteAddress = u16WriteAddress;
|
||
_u16WriteQty = 0;
|
||
_u16TransmitBuffer[0] = u16WriteValue;
|
||
return ModbusMasterTransaction(ku8MBWriteSingleRegister);
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x0F Write Multiple Coils.
|
||
|
||
This function code is used to force each coil in a sequence of coils to
|
||
either ON or OFF in a remote device. The request specifies the coil
|
||
references to be forced. Coils are addressed starting at zero.
|
||
|
||
The requested ON/OFF states are specified by contents of the transmit
|
||
buffer. A logical '1' in a bit position of the buffer requests the
|
||
corresponding output to be ON. A logical '0' requests it to be OFF.
|
||
|
||
@param u16WriteAddress address of the first coil (0x0000..0xFFFF)
|
||
@param u16BitQty quantity of coils to write (1..2000, enforced by remote device)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup discrete
|
||
*/
|
||
uint8_t ModbusMaster::writeMultipleCoils(uint16_t u16WriteAddress,
|
||
uint16_t u16BitQty)
|
||
{
|
||
_u16WriteAddress = u16WriteAddress;
|
||
_u16WriteQty = u16BitQty;
|
||
return ModbusMasterTransaction(ku8MBWriteMultipleCoils);
|
||
}
|
||
uint8_t ModbusMaster::writeMultipleCoils()
|
||
{
|
||
_u16WriteQty = u16TransmitBufferLength;
|
||
return ModbusMasterTransaction(ku8MBWriteMultipleCoils);
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x10 Write Multiple Registers.
|
||
|
||
This function code is used to write a block of contiguous registers (1
|
||
to 123 registers) in a remote device.
|
||
|
||
The requested written values are specified in the transmit buffer. Data
|
||
is packed as one word per register.
|
||
|
||
@param u16WriteAddress address of the holding register (0x0000..0xFFFF)
|
||
@param u16WriteQty quantity of holding registers to write (1..123, enforced by remote device)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup register
|
||
*/
|
||
uint8_t ModbusMaster::writeMultipleRegisters(uint16_t u16WriteAddress,
|
||
uint16_t u16WriteQty)
|
||
{
|
||
_u16WriteAddress = u16WriteAddress;
|
||
_u16WriteQty = u16WriteQty;
|
||
return ModbusMasterTransaction(ku8MBWriteMultipleRegisters);
|
||
}
|
||
|
||
// new version based on Wire.h
|
||
uint8_t ModbusMaster::writeMultipleRegisters()
|
||
{
|
||
_u16WriteQty = _u8TransmitBufferIndex;
|
||
return ModbusMasterTransaction(ku8MBWriteMultipleRegisters);
|
||
}
|
||
|
||
|
||
/**
|
||
Modbus function 0x16 Mask Write Register.
|
||
|
||
This function code is used to modify the contents of a specified holding
|
||
register using a combination of an AND mask, an OR mask, and the
|
||
register's current contents. The function can be used to set or clear
|
||
individual bits in the register.
|
||
|
||
The request specifies the holding register to be written, the data to be
|
||
used as the AND mask, and the data to be used as the OR mask. Registers
|
||
are addressed starting at zero.
|
||
|
||
The function's algorithm is:
|
||
|
||
Result = (Current Contents && And_Mask) || (Or_Mask && (~And_Mask))
|
||
|
||
@param u16WriteAddress address of the holding register (0x0000..0xFFFF)
|
||
@param u16AndMask AND mask (0x0000..0xFFFF)
|
||
@param u16OrMask OR mask (0x0000..0xFFFF)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup register
|
||
*/
|
||
uint8_t ModbusMaster::maskWriteRegister(uint16_t u16WriteAddress,
|
||
uint16_t u16AndMask, uint16_t u16OrMask)
|
||
{
|
||
_u16WriteAddress = u16WriteAddress;
|
||
_u16TransmitBuffer[0] = u16AndMask;
|
||
_u16TransmitBuffer[1] = u16OrMask;
|
||
return ModbusMasterTransaction(ku8MBMaskWriteRegister);
|
||
}
|
||
|
||
/**
|
||
Modbus function 0x17 Read Write Multiple Registers.
|
||
|
||
This function code performs a combination of one read operation and one
|
||
write operation in a single MODBUS transaction. The write operation is
|
||
performed before the read. Holding registers are addressed starting at
|
||
zero.
|
||
|
||
The request specifies the starting address and number of holding
|
||
registers to be read as well as the starting address, and the number of
|
||
holding registers. The data to be written is specified in the transmit
|
||
buffer.
|
||
|
||
@param u16ReadAddress address of the first holding register (0x0000..0xFFFF)
|
||
@param u16ReadQty quantity of holding registers to read (1..125, enforced by remote device)
|
||
@param u16WriteAddress address of the first holding register (0x0000..0xFFFF)
|
||
@param u16WriteQty quantity of holding registers to write (1..121, enforced by remote device)
|
||
@return 0 on success; exception number on failure
|
||
@ingroup register
|
||
*/
|
||
uint8_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress,
|
||
uint16_t u16ReadQty, uint16_t u16WriteAddress, uint16_t u16WriteQty)
|
||
{
|
||
_u16ReadAddress = u16ReadAddress;
|
||
_u16ReadQty = u16ReadQty;
|
||
_u16WriteAddress = u16WriteAddress;
|
||
_u16WriteQty = u16WriteQty;
|
||
return ModbusMasterTransaction(ku8MBReadWriteMultipleRegisters);
|
||
}
|
||
uint8_t ModbusMaster::readWriteMultipleRegisters(uint16_t u16ReadAddress,
|
||
uint16_t u16ReadQty)
|
||
{
|
||
_u16ReadAddress = u16ReadAddress;
|
||
_u16ReadQty = u16ReadQty;
|
||
_u16WriteQty = _u8TransmitBufferIndex;
|
||
return ModbusMasterTransaction(ku8MBReadWriteMultipleRegisters);
|
||
}
|
||
|
||
// Только для OMRON MX2 - проверка связи с инвертором
|
||
// едиственный параметр - проверчый код - он должен вернуться с инвертора
|
||
uint8_t ModbusMaster::LinkTestOmronMX2Only(uint16_t code)
|
||
{
|
||
_u16WriteAddress=0x0; // первые два байта 0
|
||
_u16TransmitBuffer[0] = code;
|
||
return ModbusMasterTransaction(ku8MBLinkTestOmronMX2Only);
|
||
}
|
||
|
||
/* _____PRIVATE FUNCTIONS____________________________________________________ */
|
||
|
||
/**
|
||
Modbus transaction engine.
|
||
Sequence: Последовательность:
|
||
- assemble Modbus Request Application Data Unit (ADU),
|
||
based on particular function called
|
||
- transmit request over selected serial port
|
||
- wait for/retrieve response
|
||
- evaluate/disassemble response
|
||
- return status (success/exception)*/
|
||
// @param u8MBFunction Modbus function (0x01..0xFF)
|
||
// @return 0 on success; exception number on failure
|
||
uint8_t ModbusMaster::ModbusMasterTransaction(uint8_t u8MBFunction)
|
||
{
|
||
static uint8_t u8ModbusADU[128]; // буфер
|
||
uint8_t u8ModbusADUSize = 0; // текущее положение в буфере (длина данных)
|
||
uint8_t i, u8Qty;
|
||
uint16_t u16CRC;
|
||
uint32_t u32StartTime;
|
||
uint8_t u8BytesLeft = 8; // число оставшихся байт для чтения (минимальна длина ответа??)
|
||
uint8_t u8MBStatus = ku8MBSuccess; // текущий статус
|
||
|
||
if((u32StartTime = millis() - last_transaction_time) < MIN_TIME_BETWEEN_TRANSACTION) {
|
||
#ifdef MODBUSMASTER_DEBUG
|
||
Serial.print("#");
|
||
#endif
|
||
u32StartTime = MIN_TIME_BETWEEN_TRANSACTION - u32StartTime;
|
||
#ifdef MODBUS_FREERTOS
|
||
while(u32StartTime--) if(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) vTaskDelay(1); else delay(1);
|
||
#else
|
||
while(u32StartTime--) delay(1);
|
||
#endif
|
||
}
|
||
#ifdef MODBUSMASTER_DEBUG
|
||
Serial.print("MB"); Serial.print(_u8MBSlave); Serial.print(": ");
|
||
#endif
|
||
// assemble Modbus Request Application Data Unit (ADU)
|
||
// Сборка блока запроса Modbus Application Data (ADU)
|
||
u8ModbusADU[u8ModbusADUSize++] = _u8MBSlave; // номер устройства (Slave)
|
||
u8ModbusADU[u8ModbusADUSize++] = u8MBFunction; // номер функции Modbus
|
||
|
||
// ЧТЕНИЕ ДАННЫХ в зависимости от функции Modbus поместить адрес регистра и длину данных
|
||
switch(u8MBFunction)
|
||
{
|
||
case ku8MBReadCoils: // 0x01 < Modbus function 0x01 Read Coils
|
||
case ku8MBReadDiscreteInputs: // 0x02 < Modbus function 0x02 Read Discrete Inputs
|
||
case ku8MBReadInputRegisters: // 0x04 < Modbus function 0x04 Read Input Registers
|
||
case ku8MBReadHoldingRegisters: // 0x03 < Modbus function 0x03 Read Holding Registers
|
||
case ku8MBReadWriteMultipleRegisters:
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16ReadAddress);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16ReadAddress);
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16ReadQty);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16ReadQty);
|
||
break;
|
||
}
|
||
// ЗАПИСЬ ДАННЫХ в зависимости от функции Modbus поместить адрес регистра
|
||
switch(u8MBFunction)
|
||
{
|
||
case ku8MBWriteSingleCoil: // 0x05 < Modbus function 0x05 Write Single Coil
|
||
case ku8MBMaskWriteRegister: // 0x16 < Modbus function 0x16 Mask Write Register
|
||
case ku8MBWriteMultipleCoils: // 0x0F < Modbus function 0x0F Write Multiple Coils
|
||
case ku8MBWriteSingleRegister: // 0x06 < Modbus function 0x06 Write Single Register
|
||
case ku8MBWriteMultipleRegisters: // 0x10 < Modbus function 0x10 Write Multiple Registers
|
||
case ku8MBReadWriteMultipleRegisters: // 0x17 < Modbus function 0x17 Read Write Multiple Registers
|
||
case ku8MBLinkTestOmronMX2Only: // 0x08 < Modbus function 0x08 Тест связи с инвертром Omron MX2 функция только для него
|
||
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteAddress);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteAddress);
|
||
break;
|
||
}
|
||
// ЗАПИСЬ ДАННЫХ в зависимости от функции Modbus поместить данные
|
||
switch(u8MBFunction)
|
||
{
|
||
case ku8MBWriteSingleCoil:
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteQty);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty);
|
||
break;
|
||
|
||
case ku8MBWriteSingleRegister:
|
||
case ku8MBLinkTestOmronMX2Only: // проверка связи поместить данные
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[0]);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[0]);
|
||
break;
|
||
|
||
case ku8MBWriteMultipleCoils:
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteQty);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty);
|
||
u8Qty = (_u16WriteQty % 8) ? ((_u16WriteQty >> 3) + 1) : (_u16WriteQty >> 3);
|
||
u8ModbusADU[u8ModbusADUSize++] = u8Qty;
|
||
for (i = 0; i < u8Qty; i++)
|
||
{
|
||
switch(i % 2)
|
||
{
|
||
case 0: // i is even
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[i >> 1]);
|
||
break;
|
||
|
||
case 1: // i is odd
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[i >> 1]);
|
||
break;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case ku8MBWriteMultipleRegisters:
|
||
case ku8MBReadWriteMultipleRegisters:
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16WriteQty);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16WriteQty << 1);
|
||
|
||
for (i = 0; i < lowByte(_u16WriteQty); i++)
|
||
{
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[i]);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[i]);
|
||
}
|
||
break;
|
||
|
||
case ku8MBMaskWriteRegister:
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[0]);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[0]);
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(_u16TransmitBuffer[1]);
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(_u16TransmitBuffer[1]);
|
||
break;
|
||
case ku8MBCustomRequest:
|
||
for (i = 0; i < _u8TransmitBufferIndex; i++)
|
||
{
|
||
u8ModbusADU[u8ModbusADUSize++] = _u16TransmitBuffer[i];
|
||
}
|
||
break;
|
||
}
|
||
|
||
// вычисление контрольной суммы
|
||
u16CRC = 0xFFFF;
|
||
for (i = 0; i < u8ModbusADUSize; i++)
|
||
{
|
||
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
|
||
}
|
||
u8ModbusADU[u8ModbusADUSize++] = lowByte(u16CRC);
|
||
u8ModbusADU[u8ModbusADUSize++] = highByte(u16CRC);
|
||
u8ModbusADU[u8ModbusADUSize] = 0;
|
||
|
||
// flush receive buffer before transmitting request
|
||
// Очистка приемного буфера перед передачей запроса
|
||
while (_serial->read() != -1);
|
||
|
||
// transmit request
|
||
// вызов функции перед началом передачи - дернуть ногу передачи max485 (помним про полудуплекс)
|
||
if (_preTransmission) { _preTransmission(); }
|
||
|
||
// передаем данные
|
||
for (i = 0; i < u8ModbusADUSize; i++)
|
||
{
|
||
_serial->write(u8ModbusADU[i]);
|
||
}
|
||
|
||
_serial->flush(); // Очистить передающий буфер
|
||
// вызов функции в конце передачи - дернуть ногу передачи max485 + задержка перед чтением(помним про полудуплекс)
|
||
if (_postTransmission) { _postTransmission(); }
|
||
|
||
// -------------------- ЧТЕНИЕ ОТВЕТА --------------------------------------
|
||
u8ModbusADUSize = 0; // Сбросить длину буфера
|
||
|
||
#ifdef MODBUSMASTER_DEBUG
|
||
Serial.print("St: "); Serial.print(millis()); Serial.print(" ");
|
||
#endif
|
||
|
||
// Цикл чтения из входного буфера пока нет ошибок и не прошло время ожидания
|
||
u32StartTime = millis(); // время начала
|
||
// Ожидание и чтение ответа
|
||
while(u8BytesLeft && !u8MBStatus) {
|
||
if(_serial->available()) // есть символы во входном буфере
|
||
{
|
||
#ifdef MODBUSMASTER_DEBUG
|
||
if(u8ModbusADUSize == 0) { Serial.print("1: "); Serial.print(millis()); Serial.print(" "); }
|
||
#endif
|
||
u8ModbusADU[u8ModbusADUSize++] = _serial->read();
|
||
u8BytesLeft--; // байт прочли уменьшили счетчик
|
||
u32StartTime = millis(); // время продолжения
|
||
} else { // нет символов во входном буфере
|
||
if(_idle) { _idle(); } // если разрешено - операция ожидания
|
||
}
|
||
|
||
// evaluate slave ID, function code once enough bytes have been read
|
||
// определение ID ведомого и кода функции - один байт
|
||
if(u8ModbusADUSize == (u8MBFunction == ku8MBCustomRequest && _u8TransmitBufferIndex < 2 ? _u8TransmitBufferIndex + 3 : 5)) // Разбор заголовка
|
||
{
|
||
// verify response is for correct Modbus slave
|
||
// Сравнение ID с посланым в запросе
|
||
if(u8ModbusADU[0] != _u8MBSlave) {
|
||
u8MBStatus = ku8MBInvalidSlaveID;
|
||
break;
|
||
}
|
||
|
||
// verify response is for correct Modbus function code (mask exception bit 7)
|
||
else if((u8ModbusADU[1] & 0x80) == 0x80) {
|
||
u8MBStatus = ku8MBExtendedError + u8ModbusADU[2]; // найдена ошибка, кодируем код исключения
|
||
break;
|
||
} else if((u8ModbusADU[1] & 0x7F) != u8MBFunction) {
|
||
u8MBStatus = ku8MBInvalidFunction;
|
||
break;
|
||
}
|
||
|
||
// check whether Modbus exception occurred; return Modbus Exception Code
|
||
else if(bitRead(u8ModbusADU[1], 7)) {
|
||
u8MBStatus = u8ModbusADU[2];
|
||
break;
|
||
}
|
||
|
||
// evaluate returned Modbus function code
|
||
// Оценить в зависимости от функции требуемое число байт
|
||
switch(u8ModbusADU[1]) // код функции
|
||
{
|
||
case ku8MBReadCoils:
|
||
case ku8MBReadDiscreteInputs:
|
||
case ku8MBReadInputRegisters:
|
||
case ku8MBReadHoldingRegisters:
|
||
case ku8MBReadWriteMultipleRegisters:
|
||
u8BytesLeft = u8ModbusADU[2];
|
||
break;
|
||
case ku8MBWriteSingleCoil:
|
||
case ku8MBWriteMultipleCoils:
|
||
case ku8MBWriteSingleRegister:
|
||
case ku8MBWriteMultipleRegisters:
|
||
u8BytesLeft = 3;
|
||
break;
|
||
case ku8MBMaskWriteRegister:
|
||
u8BytesLeft = 5;
|
||
break;
|
||
case ku8MBLinkTestOmronMX2Only:
|
||
u8BytesLeft = 3;
|
||
break;
|
||
default:
|
||
if(u8MBFunction == ku8MBCustomRequest) {
|
||
u8BytesLeft = _u8TransmitBufferIndex - (u8ModbusADUSize - 3);
|
||
}
|
||
}
|
||
} // if (u8ModbusADUSize == 5)
|
||
|
||
// проверка привышения времени ожидания
|
||
if((millis() - u32StartTime) > ku16MBResponseTimeout) {
|
||
u8MBStatus = ku8MBResponseTimedOut;
|
||
break;
|
||
}
|
||
} //Конец цикла приема while (u8BytesLeft && !u8MBStatus)
|
||
|
||
// verify response is large enough to inspect further
|
||
// Проверить является ли длина ответ достаточно большой
|
||
if (!u8MBStatus && u8ModbusADUSize >= 5)
|
||
{
|
||
// calculate CRC
|
||
u16CRC = 0xFFFF;
|
||
for (i = 0; i < (u8ModbusADUSize - 2); i++)
|
||
{
|
||
u16CRC = crc16_update(u16CRC, u8ModbusADU[i]);
|
||
}
|
||
|
||
// verify CRC
|
||
if (!u8MBStatus && (lowByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 2] ||
|
||
highByte(u16CRC) != u8ModbusADU[u8ModbusADUSize - 1]))
|
||
{
|
||
u8MBStatus = ku8MBInvalidCRC;
|
||
}
|
||
} //if (!u8MBStatus && u8ModbusADUSize >= 5)
|
||
|
||
// Разобрать ADU по словам в буфер (данные готовы)
|
||
if (!u8MBStatus)
|
||
{
|
||
// Разбор данных в зависимости от кода функции Modbus
|
||
switch(u8ModbusADU[1])
|
||
{
|
||
case ku8MBReadCoils:
|
||
case ku8MBReadDiscreteInputs:
|
||
// load bytes into word; response bytes are ordered L, H, L, H, ...
|
||
// Загрузка байт в слово; байты ответа упорядочиваются L, H, L, H, ...
|
||
for (i = 0; i < (u8ModbusADU[2] >> 1); i++)
|
||
{
|
||
if (i < ku8MaxBufferSize)
|
||
{
|
||
_u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 4], u8ModbusADU[2 * i + 3]);
|
||
}
|
||
|
||
_u8ResponseBufferLength = i;
|
||
}
|
||
|
||
// in the event of an odd number of bytes, load last byte into zero-padded word
|
||
// в случае нечетного числа байт, загрузка последнего байта в дополняется нулями
|
||
if (u8ModbusADU[2] % 2)
|
||
{
|
||
if (i < ku8MaxBufferSize)
|
||
{
|
||
_u16ResponseBuffer[i] = word(0, u8ModbusADU[2 * i + 3]);
|
||
}
|
||
|
||
_u8ResponseBufferLength = i + 1;
|
||
}
|
||
break;
|
||
|
||
case ku8MBReadInputRegisters:
|
||
case ku8MBReadHoldingRegisters:
|
||
case ku8MBReadWriteMultipleRegisters:
|
||
// Загрузка байт в слово; байты ответа упорядочиваются H, L, H, L, ...
|
||
for (i = 0; i < (u8ModbusADU[2] >> 1); i++)
|
||
{
|
||
if (i < ku8MaxBufferSize)
|
||
{
|
||
_u16ResponseBuffer[i] = word(u8ModbusADU[2 * i + 3], u8ModbusADU[2 * i + 4]);
|
||
}
|
||
|
||
_u8ResponseBufferLength = i;
|
||
}
|
||
break;
|
||
case ku8MBLinkTestOmronMX2Only: // Сохранение кода возврата
|
||
_u16ResponseBuffer[0] = word(u8ModbusADU[4], u8ModbusADU[5]);
|
||
break;
|
||
} // switch(u8ModbusADU[1])
|
||
} else {
|
||
_u16ResponseBuffer[0] = word(0,u8ModbusADU[2]); // ошибка - записать код ошибки в буфер
|
||
}
|
||
|
||
_u8TransmitBufferIndex = 0;
|
||
u16TransmitBufferLength = 0;
|
||
_u8ResponseBufferIndex = 0;
|
||
last_transaction_time = millis();
|
||
#ifdef MODBUSMASTER_DEBUG
|
||
Serial.print(" E: "); Serial.println(millis());
|
||
#endif
|
||
return u8MBStatus;
|
||
}
|
||
|
||
/** @ingroup util_crc16
|
||
Processor-independent CRC-16 calculation.
|
||
|
||
Polynomial: x^16 + x^15 + x^2 + 1 (0xA001)<br>
|
||
Initial value: 0xFFFF
|
||
|
||
This CRC is normally used in disk-drive controllers.
|
||
|
||
@param uint16_t crc (0x0000..0xFFFF)
|
||
@param uint8_t a (0x00..0xFF)
|
||
@return calculated CRC (0x0000..0xFFFF)
|
||
*/
|
||
uint16_t crc16_update(uint16_t crc, uint8_t a)
|
||
{
|
||
int i;
|
||
|
||
crc ^= a;
|
||
for (i = 0; i < 8; ++i)
|
||
{
|
||
if (crc & 1)
|
||
crc = (crc >> 1) ^ 0xA001;
|
||
else
|
||
crc = (crc >> 1);
|
||
}
|
||
|
||
return crc;
|
||
}
|