228 lines
7.6 KiB
C
228 lines
7.6 KiB
C
/*
|
|
* File: modbus.h
|
|
* Description: MODBUS RTU library
|
|
* Author: Jan Mrna
|
|
* Date: 2021-07-18
|
|
*
|
|
* Modbus slave RTU library (does NOT support ASCII and TCP)
|
|
*
|
|
* Note that byte order is big endian.
|
|
*
|
|
*
|
|
* Copyright (c) 2024 Veles Labs s.r.o.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#ifndef SRC_MODBUS_H_
|
|
#define SRC_MODBUS_H_
|
|
|
|
#include "stdint.h"
|
|
#include "stddef.h"
|
|
#include "string.h"
|
|
|
|
/*
|
|
* Defines & macros
|
|
*/
|
|
|
|
#define MODBUS_BROADCAST_ADDR 0
|
|
#define MODBUS_DEFAULT_SLAVE_ADDRESS 247 /* 255 may be used for bridge device */
|
|
/* minimal frame length is 4 bytes: 1 B slave address, 1 B function code, 2 B CRC */
|
|
#define MODBUS_MINIMAL_FRAME_LEN 4
|
|
#define MODBUS_MINIMAL_READWRITE_LEN 4
|
|
#define MODBUS_MINIMAL_WRITE_MULTIPLE_LEN 5
|
|
#define MODBUS_READ_DEVICE_ID_REQUEST_LEN 4
|
|
#define MODBUS_READ_DEVICE_ID_RESPONSE_HEADER_LEN 4
|
|
#define MODBUS_READ_DEVICE_ID_RESPONSE_OFFSET 3
|
|
#define MODBUS_MAX_RTU_FRAME_SIZE 256
|
|
#define MODBUS_BUFFER_SIZE MODBUS_MAX_RTU_FRAME_SIZE /* alias */
|
|
#define MODBUS_ERROR_FLAG 0x80
|
|
#define MODBUS_MAX_REGISTERS 125
|
|
/* read device id constants */
|
|
#define MODBUS_MEI 0x0E
|
|
#define MODBUS_DEVICE_ID_INDIVIDUAL_ACCESS_FLAG 0x80
|
|
#define MODBUS_MORE_FOLLOWS 0xFF
|
|
#define MODBUS_NO_MORE_FOLLOWS 0x00
|
|
#define MODBUS_BASIC_OBJECT_COUNT 3
|
|
#define MODBUS_REGULAR_OBJECT_COUNT 7
|
|
|
|
/*
|
|
* Return values
|
|
*/
|
|
|
|
#define MODBUS_OK 0
|
|
#define MODBUS_ERROR -1 // generic error
|
|
#define MODBUS_ERROR_CRC -2 // checksum failed
|
|
#define MODBUS_ERROR_FRAME_INVALID -3 // invalid frame format / length
|
|
#define MODBUS_ERROR_OUT_OF_BOUNDS -4 // requested register is out of bounds
|
|
#define MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED -5 // function not implemented in callback
|
|
#define MODBUS_ERROR_REGISTER_NOT_IMPLEMENTED -6 // register not implemented in callback
|
|
#define MODBUS_ERROR_DEVICE_ID_NOT_IMPLEMENTED -7
|
|
|
|
/*
|
|
* Data types
|
|
*/
|
|
|
|
/* Public functions codes (Modbus Application protocol specification, section 5.1) */
|
|
typedef enum {
|
|
/* single bit access functions */
|
|
MODBUS_READ_COILS = 1,
|
|
MODBUS_READ_DO = 1, // alias
|
|
MODBUS_READ_DISCRETE_INPUTS = 2,
|
|
MODBUS_READ_DI = 2, // alias
|
|
MODBUS_WRITE_SINGLE_COIL = 5,
|
|
MODBUS_WRITE_SINGLE_DO = 5, // alias
|
|
MODBUS_WRITE_MULTIPLE_COILS = 15,
|
|
MODBUS_WRITE_MULTIPLE_DO = 15, // alias
|
|
/* 16-bit access functions */
|
|
MODBUS_READ_HOLDING_REGISTERS = 3,
|
|
MODBUS_READ_AO = 3, // alias
|
|
MODBUS_READ_INPUT_REGISTERS = 4,
|
|
MODBUS_READ_AI = 4, // alias
|
|
MODBUS_WRITE_SINGLE_REGISTER = 6,
|
|
MODBUS_WRITE_SINGLE_AO = 6, // alias
|
|
MODBUS_WRITE_MULTIPLE_REGISTERS = 16,
|
|
MODBUS_WRITE_MULTIPLE_AO = 16, // alias
|
|
MODBUS_MASK_WRITE_REGISTER = 22,
|
|
MODBUS_READ_WRITE_MULTIPLE_REGISTERS = 23,
|
|
MODBUS_READ_FIFO_QUEUE = 24,
|
|
/* file record access */
|
|
MODBUS_READ_FILE_RECORD = 20,
|
|
MODBUS_WRITE_FILE_RECORD = 21,
|
|
/* diagnostics */
|
|
MODBUS_READ_EXCEPTION_STATUS = 7,
|
|
MODBUS_DIAGNOSTIC = 8, /* sub codes: 00-18,20 */
|
|
MODBUS_GET_COM_EVENT_COUNTER = 11,
|
|
MODBUS_GET_COM_EVENT_LOG = 12,
|
|
MODBUS_REPORT_SLAVE_ID = 17,
|
|
MODBUS_READ_DEVICE_IDENTIFICATION = 43, /* sub codes: 14 */
|
|
} modbus_function_code_t;
|
|
|
|
typedef enum {
|
|
MODBUS_EXCEPTION_ILLEGAL_FUNCTION = 1,
|
|
MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2,
|
|
MODBUS_EXCEPTION_ILLEGAL_REGISTER_QUANTITY = 2,
|
|
MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE = 3,
|
|
MODBUS_EXCEPTION_SLAVE_DEVICE_FAILURE = 4,
|
|
MODBUS_EXCEPTION_ACKNOWLEDGE = 5,
|
|
MODBUS_EXCEPTION_SLAVE_DEVICE_BUSY = 6,
|
|
MODBUS_EXCEPTION_MEMORY_PARITY_ERROR = 8,
|
|
MODBUS_EXCEPTION_GATEWAY_PATH_UNAVAILABLE = 10,
|
|
MODBUS_EXCEPTION_GATEWAY_TARGET_DEVICE_FAILED_TO_RESPOND = 11,
|
|
MODBUS_EXCEPTION_ILLEGAL_DEVICE_ID_CODE = 3
|
|
} modbus_exception_code_t;
|
|
|
|
typedef struct {
|
|
modbus_function_code_t function_code : 8;
|
|
uint16_t register_address; // e.g. first register of A0: 0
|
|
uint16_t register_number; // e.g. first register of A0: 40001
|
|
uint8_t register_count; // number of registers to be read/written
|
|
|
|
modbus_exception_code_t exception;
|
|
|
|
uint8_t broadcast; // 1 if broadcast, 0 otherwise
|
|
|
|
union {
|
|
uint8_t buffer8b[MODBUS_MAX_RTU_FRAME_SIZE];
|
|
uint16_t buffer16b[MODBUS_MAX_RTU_FRAME_SIZE/2];
|
|
uint16_t input_registers[MODBUS_MAX_REGISTERS];
|
|
uint16_t holding_registers[MODBUS_MAX_REGISTERS];
|
|
int16_t input_registers_signed[MODBUS_MAX_REGISTERS];
|
|
int16_t holding_registers_signed[MODBUS_MAX_REGISTERS];
|
|
};
|
|
|
|
/* process device id */
|
|
uint8_t read_device_id_code;
|
|
uint8_t object_id;
|
|
} modbus_transaction_t;
|
|
|
|
typedef enum {
|
|
MODBUS_DO_START_NUMBER = 1, // Discrete output coils
|
|
MODBUS_DO_END_NUMBER = 9999,
|
|
MODBUS_DI_START_NUMBER = 10001, // Discrete input contacts
|
|
MODBUS_DI_END_NUMBER = 19999,
|
|
MODBUS_AI_START_NUMBER = 30001, // Analog input registers
|
|
MODBUS_AI_END_NUMBER = 39999,
|
|
MODBUS_AO_START_NUMBER = 40001, // Analog output (holding registers)
|
|
MODBUS_AO_END_NUMBER = 49999
|
|
} modbus_register_number_t;
|
|
|
|
typedef enum {
|
|
MODBUS_CONFORMITY_BASIC = 1,
|
|
MODBUS_CONFORMITY_REGULAR = 2,
|
|
MODBUS_CONFORMITY_EXTENDED = 3,
|
|
MODBUS_INDIVIDUAL_ACCESS = 4 /* not actually part of conformity, but I'll keep it here anyway */
|
|
} modbus_conformity_level_t;
|
|
|
|
/* Device ID datatypes */
|
|
#define MODBUS_DEVICE_ID_OBJECT_NUM 7
|
|
typedef struct {
|
|
union {
|
|
struct {
|
|
/* Basic category (mandatory part) */
|
|
char *VendorName;
|
|
char *ProductCode;
|
|
char *MajorMinorRevision;
|
|
/* Regular category (optional part) */
|
|
char *VendorUrl;
|
|
char *ProductName;
|
|
char *ModelName;
|
|
char *UserApplicationName;
|
|
/* Extended category (optional part) */
|
|
// Nothing here yet!
|
|
} object_name;
|
|
char *object_id[MODBUS_DEVICE_ID_OBJECT_NUM];
|
|
};
|
|
uint8_t conformity_level;
|
|
} modbus_device_id_t;
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
|
|
/* device address: declared in modbus.c */
|
|
extern uint8_t modbus_slave_address;
|
|
|
|
/* shared modbus buffer; defined in modbus.c; may be used elsewhere in code */
|
|
extern uint8_t modbus_buffer[];
|
|
|
|
/* modbus device id struct */
|
|
extern modbus_device_id_t *modbus_device_id;
|
|
|
|
/*
|
|
* Function prototypes
|
|
*/
|
|
|
|
/* process message: should be called in when modbus message was received (e.g. in main.c)
|
|
* modbus_process_msg() may call following functions:
|
|
* - modbus_callback_function() if data readout is requested
|
|
* - modbus_uart_transmit_function() if response is required
|
|
* Both functions have to be implemented by user.
|
|
*/
|
|
int8_t modbus_slave_process_msg(const uint8_t *buffer, int len);
|
|
int8_t modbus_slave_init_device_id(modbus_device_id_t *device_id);
|
|
int8_t modbus_slave_set_address(uint8_t address);
|
|
/* modbus callback function type - should be implemented by user (e.g. in main.c) */
|
|
int8_t modbus_slave_callback(modbus_transaction_t *transaction);
|
|
/* UART transmit function type - should be implemented by user (e.g. in main.c) */
|
|
int8_t modbus_transmit_function(uint8_t *buffer, uint16_t data_len);
|
|
|
|
#endif /* SRC_MODBUS_H_ */
|