From 3df2002a3be877ab535d600b1d68baaa54b7f8b8 Mon Sep 17 00:00:00 2001 From: Jan Mrna Date: Sun, 12 Jun 2022 22:36:32 +0200 Subject: [PATCH] Added files --- README.md | 31 ++++ modbus.c | 431 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ modbus.h | 231 +++++++++++++++++++++++++++++ 3 files changed, 693 insertions(+) create mode 100644 README.md create mode 100644 modbus.c create mode 100644 modbus.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..c862133 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# Modbus slave RTU library + +*(does NOT support ASCII and TCP)* + +USAGE: + +1. Implement functions modbus_callback_function() and modbus_uart_transmit_function() + * modbus_uart_transmit_function() sends data via UART + * modbus_callback_function() does the real work: read sensors, set outputs... + +note that when filling buffers (e.g. input_registers[]) user must ensure that all data is big-endian. These functions are implementation-specific. + +2. Set device address (variable modbus_device_address); you can do this either + * setting modbus_device_address directly (modbus.h needs to be included, duh) + * using modbus_set_device_address(uint8_t address) function + +Or you can leave address as-is (MODBUS_DEFAULT_SLAVE_ADDRESS) and set it via Modbus during runtime + +3. Call modbus_process_msg() after message reception; you need to observe Modbus RTU timing: + * pauses between chars in frame are less or equal to 1.5 char + * pauses between frames are at least 3.5 chars (of silence) + +For more information see section 2.5.1.1 (MODBUS Message RTU Framing) in "MODBUS over Serial Line: Specification and Implementation Guide" + +Note that byte order is big endian. + +## Useful links: +https://www.picotech.com/library/oscilloscopes/modbus-serial-protocol-decoding +https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/ +https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf +https://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf \ No newline at end of file diff --git a/modbus.c b/modbus.c new file mode 100644 index 0000000..ddf37b6 --- /dev/null +++ b/modbus.c @@ -0,0 +1,431 @@ +/* + * modbus.c + * + * Created on: Jul 18, 2021 + * Author: user + */ + +#include "modbus.h" + +/* + * Global variables + */ + +/* Modbus TX buffer; can be also used for RX in memory constrained systems (e.g. in main.c); + * NOTE if shared buffer is used for TX/RX, care must be taken to prevent writing into buffer + * during execution of modbus_process_message() */ +uint8_t modbus_buffer[MODBUS_MAX_RTU_FRAME_SIZE]; + +/* MODBUS device address */ +uint8_t modbus_slave_address = MODBUS_DEFAULT_SLAVE_ADDRESS; + +/* Device ID struct */ +modbus_device_id_t *modbus_device_id = NULL; + +/* + * CRC16 functions + * see https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + * section 6.2.2 + */ + +/* CRC16 (without memory mapped values) + * taken from https://ctlsys.com/support/how_to_compute_the_modbus_rtu_message_crc/ */ +uint16_t modbus_CRC16(const uint8_t *buf, int len) +{ + uint16_t crc = 0xFFFF; + + for (int pos = 0; pos < len; pos++) { + crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc + + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } else { // Else LSB is not set + crc >>= 1; // Just shift right + } + } + } + // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes) + return crc; +} + +/* + * Private functions + */ + +static uint8_t modbus_fill_device_id_objects(uint8_t *buffer, modbus_transaction_t *transaction) +{ + /* we assume buffer is 256 - MODBUS_READ_DEVICE_ID_RESPONSE_HEADER_LEN = 252 bytes long */ + /* find out how many objects we copy to buffer */ + int len; + uint8_t object_index = transaction->object_id; + uint8_t object_count; + uint8_t more_follows = MODBUS_NO_MORE_FOLLOWS; + uint8_t next_object_id; + uint8_t last_object; + const uint8_t max_len = 256 - MODBUS_READ_DEVICE_ID_RESPONSE_HEADER_LEN; + + /* last object index */ + if (transaction->read_device_id_code == MODBUS_CONFORMITY_BASIC) { + last_object = MODBUS_BASIC_OBJECT_COUNT; + } else if (transaction->read_device_id_code == MODBUS_CONFORMITY_REGULAR) { + last_object = MODBUS_REGULAR_OBJECT_COUNT; + /* extended not implemented */ +// } else if (transaction->read_device_id_code == MODBUS_CONFORMITY_EXTENDED){ +// last_object = MODBUS_EXTENDED_OBJECT_COUNT; + } else { + /* fallback: regular */ + last_object = MODBUS_REGULAR_OBJECT_COUNT; + } + last_object--; // we need index + /* copy as many objects as possible */ + do { + /* copy object */ + int object_len = strlen(modbus_device_id->object_id[object_index]); + if (len + object_len + 2 > max_len) { + more_follows = MODBUS_MORE_FOLLOWS; + next_object_id = object_index; + break; + } + /* offset is for "more follows", "next object id", "object count" */ + buffer[MODBUS_READ_DEVICE_ID_RESPONSE_OFFSET + len++] = object_index; + buffer[MODBUS_READ_DEVICE_ID_RESPONSE_OFFSET + len++] = object_len; + /* note that string copied to buffer is not null-terminated */ + strncpy((char*)(buffer + len), (char*)modbus_device_id->object_id[object_index++], object_len); + len += object_len; + object_count++; + } while (object_index < last_object); + buffer[0] = more_follows; + buffer[1] = next_object_id; + buffer[2] = object_count; + return MODBUS_READ_DEVICE_ID_RESPONSE_OFFSET + len; +} + +/* here we assume buffer has minimal size of MODBUS_MAX_RTU_FRAME_SIZE; + * this function is private, so hopefully it's going to be ok */ +static int8_t modbus_transaction_to_buffer(uint8_t *buffer, uint8_t *msg_len, modbus_transaction_t *transaction) +{ + uint16_t crc16; + uint8_t byte_count; + uint8_t buffer_pos = 0; + + // TODO use relative indices (increments) instead of absolute + buffer[buffer_pos++] = modbus_slave_address; + buffer[buffer_pos++] = transaction->function_code; + *msg_len = 5; + + if (transaction->function_code & MODBUS_ERROR_FLAG) { + /* sending error reply */ + buffer[buffer_pos++] = transaction->exception; + } else { + switch (transaction->function_code) { + case MODBUS_READ_HOLDING_REGISTERS: + case MODBUS_READ_INPUT_REGISTERS: + byte_count = transaction->register_count * 2; + buffer[buffer_pos++] = byte_count; + *msg_len = byte_count + 5; + for (int i = 0; i < transaction->register_count; i++) { + // TODO endianness handling + /* buffer16b is alias for both holding and input register buffers */ + buffer[buffer_pos++] = transaction->buffer16b[i] >> 8; + buffer[buffer_pos++] = transaction->buffer16b[i] & 0xff; + } + break; + case MODBUS_WRITE_SINGLE_REGISTER: + buffer[buffer_pos++] = (uint8_t) (transaction->register_address >> 8); + buffer[buffer_pos++] = (uint8_t) transaction->register_address; + buffer[buffer_pos++] = (uint8_t) (transaction->holding_registers[0] >> 8); + buffer[buffer_pos++] = (uint8_t) transaction->holding_registers[0]; + *msg_len = 8; /* includes 2 bytes for CRC */ + break; + case MODBUS_WRITE_MULTIPLE_REGISTERS: + buffer[buffer_pos++] = (uint8_t) (transaction->register_address >> 8); + buffer[buffer_pos++] = (uint8_t) transaction->register_address; + buffer[buffer_pos++] = (uint8_t) (transaction->register_count >> 8); + buffer[buffer_pos++] = (uint8_t) transaction->register_count; + *msg_len = 8; /* includes 2 bytes for CRC */ + break; + case MODBUS_READ_DEVICE_IDENTIFICATION: + /* MEI type */ + buffer[buffer_pos++] = MODBUS_MEI; + /* read device id */ + buffer[buffer_pos++] = transaction->read_device_id_code; + /* conformity level */ + buffer[buffer_pos++] = modbus_device_id->conformity_level; + /* fill buffer with as many objects as possible */ + *msg_len = modbus_fill_device_id_objects(buffer+buffer_pos, transaction); + *msg_len += 7; /* includes 2 bytes for CRC */ + break; + } + } + crc16 = modbus_CRC16(buffer, buffer_pos); /* last two bytes is the checksum itself */ + buffer[buffer_pos++] = crc16 & 0xff; + buffer[buffer_pos++] = crc16 >> 8; + return MODBUS_OK; +} + +static int8_t modbus_process_device_id_request(const uint8_t *buffer, int len, modbus_transaction_t *transaction) +{ + uint8_t MEI_type; + uint8_t read_device_id_code; + uint8_t object_id; + uint8_t buffer_pos = 0; + + if (transaction->broadcast == 1) { + /* Read device ID broadcast - invalid; ignore (master will get timeout) */ + return MODBUS_ERROR; + } + if (modbus_device_id == NULL) { + /* modbus_device_id not initialized; user should use modbus_slave_init_device_id() first */ + transaction->exception = MODBUS_EXCEPTION_ILLEGAL_DEVICE_ID_CODE; + return MODBUS_OK; + } + if (len < MODBUS_READ_DEVICE_ID_REQUEST_LEN) { + /* frame too short, ignore */ + return MODBUS_ERROR; + } + /* next byte should be MEI = 0x0E */ + MEI_type = buffer[buffer_pos++]; + if (MEI_type != MODBUS_MEI) { + /* invalid MEI, ignore. I have no idea what MEI does, but it should always be 0x0E */ + return MODBUS_ERROR; + } + /* next byte is read device id code */ + read_device_id_code = buffer[buffer_pos++]; + /* read device id code can only have values 1,2,3,4 */ + if (read_device_id_code < 1 || read_device_id_code > 4) { + transaction->exception = MODBUS_EXCEPTION_ILLEGAL_DEVICE_ID_CODE; + return MODBUS_OK; + } + transaction->read_device_id_code = read_device_id_code; + /* next byte is object id */ + object_id = buffer[buffer_pos++]; + transaction->object_id = object_id; + if (object_id > MODBUS_DEVICE_ID_OBJECT_NUM) { + /* illegal object ID */ + transaction->exception = MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + return MODBUS_OK; + } + /* Message processed */ + return MODBUS_OK; +} + +/* returns ERROR only when no response to master is needed */ +static int8_t modbus_process_read_write_request(const uint8_t *buffer, int len, modbus_transaction_t *transaction) +{ + uint8_t byte_count; + int8_t callback_result; + uint8_t buffer_pos = 0; + + /* set starting register number */ + switch (transaction->function_code) { + /* coils */ + case MODBUS_READ_DO: + case MODBUS_WRITE_SINGLE_DO: + case MODBUS_WRITE_MULTIPLE_DO: + transaction->register_number = MODBUS_DO_START_NUMBER; + break; + /* discrete inputs */ + case MODBUS_READ_DI: + transaction->register_number = MODBUS_DI_START_NUMBER; + break; + /* input registers */ + case MODBUS_READ_AI: + transaction->register_number = MODBUS_AI_START_NUMBER; + break; + /* holding registers */ + case MODBUS_READ_AO: + case MODBUS_WRITE_SINGLE_AO: + case MODBUS_WRITE_MULTIPLE_AO: + case MODBUS_READ_WRITE_MULTIPLE_REGISTERS: + transaction->register_number = MODBUS_AO_START_NUMBER; + break; + } + + #define MODBUS_FLAG_WRITE 0x01 + #define MODBUS_FLAG_SINGLE 0x02 + uint8_t flags = 0x00; + + /* process message */ + switch (transaction->function_code) { + case MODBUS_WRITE_SINGLE_COIL: + case MODBUS_WRITE_SINGLE_REGISTER: /* holding register */ + flags |= MODBUS_FLAG_SINGLE; + case MODBUS_WRITE_MULTIPLE_COILS: + case MODBUS_WRITE_MULTIPLE_REGISTERS: + flags |= MODBUS_FLAG_WRITE; + case MODBUS_READ_DISCRETE_INPUTS: + case MODBUS_READ_COILS: + case MODBUS_READ_INPUT_REGISTERS: + case MODBUS_READ_HOLDING_REGISTERS: + if (len < MODBUS_MINIMAL_READWRITE_LEN) { + /* buffer too short to contain everything we need */ + return MODBUS_ERROR; + } + transaction->register_address = (buffer[buffer_pos] << 8) | buffer[buffer_pos + 1]; + buffer += 2; + // TODO check length! + if (flags & MODBUS_FLAG_WRITE) { + if (flags & MODBUS_FLAG_SINGLE) { + transaction->holding_registers[0] = (buffer[buffer_pos] << 8) | buffer[buffer_pos + 1]; + buffer_pos += 2; + } else { + /* Write multiple registers */ + transaction->register_count = (buffer[buffer_pos] << 8) | buffer[buffer_pos + 1]; + buffer_pos += 2; + if (len < MODBUS_MINIMAL_WRITE_MULTIPLE_LEN) { + return MODBUS_ERROR; + } + byte_count = buffer[buffer_pos++]; + if (transaction->register_count > 123 || 2*transaction->register_count != byte_count) { + /* Max number of register is defined by Modbus_Application_Protocol_V1_1b, section 6.12 */ + transaction->exception = MODBUS_EXCEPTION_ILLEGAL_REGISTER_QUANTITY; + } else { + if (len < MODBUS_MINIMAL_WRITE_MULTIPLE_LEN + byte_count) { + return MODBUS_ERROR; + } + for (uint8_t i = 0; i < transaction->register_count; i++) { + transaction->holding_registers[i] = (buffer[buffer_pos] << 8) | buffer[buffer_pos + 1]; + buffer_pos += 2; + } + } + } + } else { + transaction->register_count = (buffer[buffer_pos] << 8) | buffer[buffer_pos + 1]; + buffer_pos += 2; + if ( + transaction->register_count < 1 || + transaction->register_count > MODBUS_MAX_REGISTERS + ) { + transaction->exception = MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE; + } + } + // add offset to register number + transaction->register_number += transaction->register_address; + break; + default: + /* function code not known / not implemented, reply with + * ExceptionCode 1 */ + transaction->exception = MODBUS_EXCEPTION_ILLEGAL_FUNCTION; + break; + } + /* data in modbus_buffer have been processed and buffer can be re-used for TX */ + /* handle reply */ + if (transaction->exception != 0) { + /* indicate error */ + transaction->function_code |= MODBUS_ERROR_FLAG; + } else { + callback_result = modbus_slave_callback(transaction); + /* error handling */ + if (callback_result != MODBUS_OK) { + transaction->function_code |= MODBUS_ERROR_FLAG; + if (callback_result == MODBUS_ERROR_FUNCTION_NOT_IMPLEMENTED) { + transaction->exception = MODBUS_EXCEPTION_ILLEGAL_FUNCTION; + } else if (callback_result == MODBUS_ERROR_REGISTER_NOT_IMPLEMENTED) { + transaction->exception = MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + } + } + } + return MODBUS_OK; +} + +/* + * Public function definitions + */ + +int8_t modbus_slave_set_address(uint8_t address) +{ + if (address == 0) { + /* address 0 is broadcast address */ + return MODBUS_ERROR; + } + modbus_slave_address = address; + return MODBUS_OK; +} + + +int8_t modbus_slave_process_msg(const uint8_t *buffer, int len) +{ + + + /* + * TODO list: + * + * 1) check that errors and exceptions are handled according to Modbus_Application_Protocol_V1_1b.pdf + * 2) buffer overflow prevention: for each function code, check that buffer is long enough + */ + + + /* transaction holds message context and content: + * it wraps all necessary buffers and variables */ + modbus_transaction_t transaction; + uint8_t buffer_pos = 0; + + if (len < MODBUS_MINIMAL_FRAME_LEN) { + /* frame too short; return error (no reply needed) */ + return MODBUS_ERROR_FRAME_INVALID; + } + /* check CRC first */ + uint16_t crc_received = (buffer[len - 1] << 8) | buffer[len - 2]; + uint16_t crc_calculated = modbus_CRC16(buffer, len - 2); + if (crc_received != crc_calculated) { + /* CRC mismatch, return error (no reply needed) */ + return MODBUS_ERROR_CRC; + } + /* check if address matches ours */ + uint8_t address = buffer[buffer_pos++]; + transaction.broadcast = (address == MODBUS_BROADCAST_ADDR); + if (address != modbus_slave_address && transaction.broadcast != 1) { + /* Message is not for us (no reply needed) */ + return MODBUS_OK; + } + /* get function code */ + transaction.function_code = buffer[buffer_pos++]; + transaction.exception = 0; + uint8_t request_processing_result; + if (transaction.function_code == MODBUS_READ_DEVICE_IDENTIFICATION) { + /* Read device ID request is quite complicated, therefore it has its own processing function */ + request_processing_result = modbus_process_device_id_request(buffer + buffer_pos, len - buffer_pos, &transaction); + } else { + /* process other requests: input register read, holding register read/write */ + request_processing_result = modbus_process_read_write_request(buffer + buffer_pos, len - buffer_pos, &transaction); + } + uint8_t msg_len; + /* reply only if request was processed successfully and message was not broadcast */ + if (request_processing_result == MODBUS_OK && transaction.broadcast == 0) { + modbus_transaction_to_buffer(modbus_buffer, &msg_len, &transaction); + /* send reply */ + modbus_transmit_function(modbus_buffer, msg_len); + } + return MODBUS_OK; +} + +int8_t modbus_slave_init_device_id(modbus_device_id_t *device_id) +{ + if (device_id == NULL) { + return MODBUS_ERROR; + } + /* at least basic category objects have to be implemented */ + if ( device_id->object_name.VendorName == NULL || + device_id->object_name.ProductCode == NULL || + device_id->object_name.MajorMinorRevision == NULL + ) { + return MODBUS_ERROR; + } + /* set conformity level: currently only "basic" and "regular" is implemented */ + if ( device_id->object_id[3] != NULL && + device_id->object_id[4] != NULL && + device_id->object_id[5] != NULL + + ) { + /* strings are present in regular category (optional) */ + device_id->conformity_level = MODBUS_CONFORMITY_REGULAR; + } else { + device_id->conformity_level = MODBUS_CONFORMITY_BASIC; + } + /* we support both stream and individual access to objects */ + device_id->conformity_level |= MODBUS_DEVICE_ID_INDIVIDUAL_ACCESS_FLAG; + modbus_device_id = device_id; + return MODBUS_OK; +} diff --git a/modbus.h b/modbus.h new file mode 100644 index 0000000..ff01f65 --- /dev/null +++ b/modbus.h @@ -0,0 +1,231 @@ +/* + * modbus.h + * + * Created on: Jul 18, 2021 + * Author: user + * + * Modbus slave RTU library (does NOT support ASCII and TCP) + * + * Useful links: + * https://www.picotech.com/library/oscilloscopes/modbus-serial-protocol-decoding + * https://ipc2u.com/articles/knowledge-base/modbus-rtu-made-simple-with-detailed-descriptions-and-examples/ + * https://modbus.org/docs/Modbus_over_serial_line_V1_02.pdf + * https://www.modbus.org/docs/Modbus_Application_Protocol_V1_1b.pdf + * + * Note that byte order is big endian. + * + * USAGE: + * + * 1) Implement functions modbus_callback_function() and modbus_uart_transmit_function() + * - modbus_uart_transmit_function() sends data via UART + * - modbus_callback_function() does the real work: read sensors, set outputs... + * note that when filling buffers (e.g. input_registers[]) user must + * ensure that all data is big-endian + * These functions are implementation-specific. + * 2) Set device address (variable modbus_device_address); you can do this either + * - setting modbus_device_address directly (modbus.h needs to be included, duh) + * - using modbus_set_device_address(uint8_t address) function + * Or you can leave address as-is (MODBUS_DEFAULT_SLAVE_ADDRESS) and set it via + * Modbus during runtime + * 3) Call modbus_process_msg() after message reception; you need to observe Modbus RTU timing: + * - pauses between chars in frame are less or equal to 1.5 char + * - pauses between frames are at least 3.5 chars (of silence) + * For more information see section 2.5.1.1 (MODBUS Message RTU Framing) + * in "MODBUS over Serial Line: Specification and Implementation Guide" + * + */ + +#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_ */