/* * 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_ */