/* ModbusSlave.cpp - Source for Modbus Slave Library Copyright (C) 2022 OpenPLC - Thiago Alves */ #include "ModbusSlave.h" //Global Modbus vars struct MBinfo modbus; uint8_t mb_frame[MAX_MB_FRAME]; uint16_t mb_frame_len; Stream* mb_serialport; int8_t mb_txpin; uint16_t mb_t15; // inter character time out uint16_t mb_t35; // frame delay #ifdef MBTCP_ETHERNET EthernetServer mb_server(502); uint8_t mb_mbap[MBAP_SIZE]; #ifdef BOARD_PORTENTA EthernetClient mb_serverClients[MAX_SRV_CLIENTS]; #endif #endif #ifdef MBTCP_WIFI WiFiServer mb_server(502); uint8_t mb_mbap[MBAP_SIZE]; #if defined(BOARD_ESP8266) || defined(BOARD_ESP32) || defined(BOARD_PORTENTA) WiFiClient mb_serverClients[MAX_SRV_CLIENTS]; #endif #endif bool init_mbregs(uint8_t size_holding, uint8_t size_coils, uint8_t size_inputregs, uint8_t size_inputstatus) { //Save sizes modbus.holding_size = size_holding; modbus.coils_size = size_coils; modbus.input_regs_size = size_inputregs; modbus.input_status_size = size_inputstatus; //round discrete regs sizes if (size_coils % 8 > 0) size_coils = (size_coils / 8) + 1; else size_coils = size_coils / 8; if (size_inputstatus % 8 > 0) size_inputstatus = (size_inputstatus / 8) + 1; else size_inputstatus = (size_inputstatus / 8); modbus.coils = (uint8_t *)malloc(size_coils * sizeof(uint8_t)); if (modbus.coils == NULL) return false; memset(modbus.coils, 0, size_coils * sizeof(uint8_t)); modbus.holding = (uint16_t *)malloc(size_holding * sizeof(uint16_t)); if (modbus.holding == NULL) return false; memset(modbus.holding, 0, size_holding * sizeof(uint8_t)); modbus.input_status = (uint8_t *)malloc(size_inputstatus * sizeof(uint8_t)); if (modbus.input_status == NULL) return false; memset(modbus.input_status, 0, size_inputstatus * sizeof(uint8_t)); modbus.input_regs = (uint16_t *)malloc(size_inputregs * sizeof(uint16_t)); if (modbus.input_regs == NULL) return false; memset(modbus.input_regs, 0, size_inputregs * sizeof(uint8_t)); return true; } bool get_discrete(uint16_t addr, bool regtype) { uint8_t byte_addr = addr / 8; uint8_t bit_addr = addr % 8; if (regtype == COILS) return bitRead(modbus.coils[byte_addr], bit_addr); else return bitRead(modbus.input_status[byte_addr], bit_addr); } void write_discrete(uint16_t addr, bool regtype, bool value) { uint8_t byte_addr = addr / 8; uint8_t bit_addr = addr % 8; if (regtype == COILS) bitWrite(modbus.coils[byte_addr], bit_addr, value); else bitWrite(modbus.input_status[byte_addr], bit_addr, value); } void mbconfig_serial_iface(HardwareSerial* port, long baud, int txPin) { mb_serialport = port; mb_txpin = txPin; (*port).begin(baud); //RS-485 control if (txPin >= 0) { pinMode(txPin, OUTPUT); digitalWrite(txPin, LOW); } #if defined(CONTROLLINO_MAXI) || defined(CONTROLLINO_MEGA) if (mb_serialport == &Serial3) Controllino_RS485Init(); #endif // Modbus states that a baud rate higher than 19200 must use a fixed 750 us // for inter character time out. For baud rates below 19200 the timing // is more critical and has to be calculated. // E.g. 9600 baud in a 11 bit packet is 9600/11 = 872 characters per second // In milliseconds this will be 872 characters per 1000ms. So for 1 character // 1000ms/872 characters is 1.14583ms per character. Finally modbus states // an inter-character must be 1.5T or 1.5 times longer than a character. Thus // 1.5T = 1.14583ms * 1.5 = 1.71875ms. // Thus the formula is T1.5(us) = (1000ms * 1000(us) * 1.5 * 11bits)/baud // 1000ms * 1000(us) * 1.5 * 11bits = 16500000 can be calculated as a constant if (baud > 19200) mb_t15 = 750; else mb_t15 = 16500000/baud; // 1T * 1.5 = T1.5 /* The modbus definition of a frame delay is a waiting period of 3.5 character times between packets.*/ mb_t35 = mb_t15 * 3.5; } #ifdef MBTCP void mbconfig_ethernet_iface(uint8_t *mac, uint8_t *ip, uint8_t *dns, uint8_t *gateway, uint8_t *subnet) { #ifdef MBTCP_ETHERNET if (ip == NULL) Ethernet.begin(mac); else if (dns == NULL) Ethernet.begin(mac, IPAddress(ip)); else if (gateway == NULL) Ethernet.begin(mac, IPAddress(ip), IPAddress(dns)); else if (subnet == NULL) Ethernet.begin(mac, IPAddress(ip), IPAddress(dns), IPAddress(gateway)); else Ethernet.begin(mac, IPAddress(ip), IPAddress(dns), IPAddress(gateway), IPAddress(subnet)); #endif #ifdef MBTCP_WIFI #if defined(BOARD_ESP8266) || defined(BOARD_ESP32) if (ip != NULL && gateway != NULL && subnet != NULL && dns != NULL) { uint8_t secondaryDNS[] = {8, 8, 8, 8}; WiFi.config(IPAddress(ip), IPAddress(gateway), IPAddress(subnet), IPAddress(dns), IPAddress(secondaryDNS)); } mb_server.setNoDelay(true); #elif defined(BOARD_PORTENTA) if (ip != NULL && subnet != NULL && gateway != NULL) { WiFi.config(IPAddress(ip), IPAddress(subnet), IPAddress(gateway)); } #else if (ip != NULL) { if (dns == NULL) WiFi.config(IPAddress(ip)); else if (gateway == NULL) WiFi.config(IPAddress(ip), IPAddress(dns)); else if (subnet == NULL) WiFi.config(IPAddress(ip), IPAddress(dns), IPAddress(gateway)); else WiFi.config(IPAddress(ip), IPAddress(dns), IPAddress(gateway), IPAddress(subnet)); } #endif WiFi.begin(MBTCP_SSID, MBTCP_PWD); int num_tries = 0; while (WiFi.status() != WL_CONNECTED) { delay(500); num_tries++; if (num_tries == 10) break; } #endif mb_server.begin(); } #endif void mbtask() { #ifdef MBTCP handle_tcp(); #endif #ifdef MBSERIAL handle_serial(); #endif } #ifdef MBTCP void handle_tcp() { #ifdef MBTCP_ETHERNET EthernetClient client = mb_server.available(); #endif #if defined(MBTCP_WIFI) && !defined(BOARD_ESP8266) && !defined(BOARD_ESP32) WiFiClient client = mb_server.available(); #endif //ESP and Portenta boards have a slightly different implementation of the WiFi/Ethernet API - therefore their specific //code lies below #if (defined(BOARD_ESP8266) || defined(BOARD_ESP32) || defined(BOARD_PORTENTA)) && (defined(MBTCP_WIFI) || defined(MBTCP_ETHERNET)) #ifdef BOARD_PORTENTA if (client) #else if (mb_server.hasClient()) #endif { for (int i = 0; i < MAX_SRV_CLIENTS; i++) { if (!mb_serverClients[i]) //equivalent to !serverClients[i].connected() { #ifdef BOARD_PORTENTA mb_serverClients[i] = client; #else mb_serverClients[i] = mb_server.available(); #endif break; } } } //search all clients for data for (int i = 0; i < MAX_SRV_CLIENTS; i++) { int j = 0; if (mb_serverClients[i].connected() && mb_serverClients[i].available()) { //Read packet while (mb_serverClients[i].available()) { mb_mbap[j] = mb_serverClients[i].read(); j++; if (j==MBAP_SIZE) break; //MBAP has 6 bytes (we use UnitID as SlaveID) } mb_frame_len = mb_mbap[4] << 8 | mb_mbap[5]; if (mb_mbap[2] !=0 || mb_mbap[3] !=0) return; //Not a MODBUSIP packet if (mb_frame_len < 6 || mb_frame_len > MAX_MB_FRAME) return; //Packet is too small or too big j = 0; while (mb_serverClients[i].available()) { mb_frame[j] = mb_serverClients[i].read(); j++; if (j==mb_frame_len) break; } //Safety check - discard packages that lie about their size if (j != mb_frame_len) return; //Process packet and write back process_mbpacket(); //Calculate packet length for MBAP header (mb_frame_len + 1) mb_mbap[4] = (mb_frame_len) >> 8; mb_mbap[5] = (mb_frame_len) & 0x00FF; uint8_t sendbuffer[mb_frame_len + MBAP_SIZE]; //MBAP for (j = 0 ; j < MBAP_SIZE ; j++) sendbuffer[j] = mb_mbap[j]; //PDU Frame for (j = 0 ; j < mb_frame_len ; j++) sendbuffer[j+MBAP_SIZE] = mb_frame[j]; //Write back mb_serverClients[i].write(sendbuffer, mb_frame_len + MBAP_SIZE); } } //If this is not an ESP board or Portenta board, then here is the default code #else if (client) { if (client.connected()) { int i = 0; while (client.available()) { mb_mbap[i] = client.read(); i++; if (i==MBAP_SIZE) break; //MBAP has 6 bytes (we use UnitID as SlaveID) } mb_frame_len = mb_mbap[4] << 8 | mb_mbap[5]; if (mb_mbap[2] !=0 || mb_mbap[3] !=0) return; //Not a MODBUSIP packet if (mb_frame_len < 6 || mb_frame_len > MAX_MB_FRAME) return; //Packet is too small or too big i = 0; while (client.available()) { mb_frame[i] = client.read(); i++; if (i==mb_frame_len || i==MAX_MB_FRAME) break; } //Safety check - discard packages that lie about their size if (i != mb_frame_len) return; //Process packet and write back process_mbpacket(); //Calculate packet length for MBAP header (mb_frame_len + 1) mb_mbap[4] = (mb_frame_len) >> 8; mb_mbap[5] = (mb_frame_len) & 0x00FF; uint8_t sendbuffer[mb_frame_len + MBAP_SIZE]; //MBAP for (i = 0 ; i < MBAP_SIZE ; i++) sendbuffer[i] = mb_mbap[i]; //PDU Frame for (i = 0 ; i < mb_frame_len ; i++) sendbuffer[i+MBAP_SIZE] = mb_frame[i]; //Write back client.write(sendbuffer, mb_frame_len + MBAP_SIZE); } } #endif } #endif #ifdef MBSERIAL void handle_serial() { mb_frame_len = 0; if ((*mb_serialport).available() == 0) return; while ((*mb_serialport).available() > mb_frame_len) { mb_frame_len = (*mb_serialport).available(); delayMicroseconds(mb_t15); } //Check if packet is too big or too small if ((*mb_serialport).available() > MAX_MB_FRAME || (*mb_serialport).available() < 6) { (*mb_serialport).flush(); return; } //Read packet for (uint16_t i = 0; i < mb_frame_len; i++) { mb_frame[i] = (*mb_serialport).read(); } //Validate crc uint16_t packet_crc = ((mb_frame[mb_frame_len - 2] << 8) | mb_frame[mb_frame_len - 1]); if (packet_crc != calcCrc()) { (*mb_serialport).flush(); return; } //Validate SlaveID if (mb_frame[0] != modbus.slaveid) { (*mb_serialport).flush(); return; } //Remove CRC (must do that before processing packet) mb_frame_len -= 2; //Process packet and write back process_mbpacket(); //Add CRC //Check if response message is too big for this device if (mb_frame_len + 2 > MAX_MB_FRAME) exceptionResponse(mb_frame[1], MB_EX_SLAVE_FAILURE); mb_frame_len += 2; //increase frame length by two bytes to acomodate CRC packet_crc = calcCrc(); //calculate CRC of the new packet mb_frame[mb_frame_len - 2] = (uint8_t)(packet_crc >> 8); mb_frame[mb_frame_len - 1] = (uint8_t)(packet_crc & 0x00FF); if (mb_txpin >= 0) { digitalWrite(mb_txpin, HIGH); delayMicroseconds(mb_t35); } #if defined(CONTROLLINO_MAXI) || defined(CONTROLLINO_MEGA) if (mb_serialport == &Serial3) // RS485 serial port Controllino_RS485TxEnable(); // Enable RS485 chip to transmit #endif (*mb_serialport).write(mb_frame, mb_frame_len); (*mb_serialport).flush(); delayMicroseconds(mb_t35); if (mb_txpin >= 0) digitalWrite(mb_txpin, LOW); #if defined(CONTROLLINO_MAXI) || defined(CONTROLLINO_MEGA) if (mb_serialport == &Serial3) // RS485 serial port Controllino_RS485RxEnable(); // Go back to receive mode after transmitted data #endif } #endif void process_mbpacket() { uint8_t fcode = mb_frame[1]; uint16_t field1 = (uint16_t)mb_frame[2] << 8 | (uint16_t)mb_frame[3]; uint16_t field2 = (uint16_t)mb_frame[4] << 8 | (uint16_t)mb_frame[5]; uint8_t flag = mb_frame[4]; uint16_t len = (uint16_t)mb_frame[5] << 8 | (uint16_t)mb_frame[6]; void *value = &mb_frame[7]; void *endianness_check = &mb_frame[2]; switch (fcode) { case MB_FC_WRITE_REG: //field1 = reg, field2 = value writeSingleRegister(field1, field2); break; case MB_FC_READ_REGS: //field1 = startreg, field2 = numregs readRegisters(field1, field2); break; case MB_FC_WRITE_REGS: //field1 = startreg, field2 = status writeMultipleRegisters(field1, field2, mb_frame[6]); break; case MB_FC_READ_COILS: //field1 = startreg, field2 = numregs readCoils(field1, field2); break; case MB_FC_READ_INPUT_STAT: //field1 = startreg, field2 = numregs readInputStatus(field1, field2); break; case MB_FC_READ_INPUT_REGS: //field1 = startreg, field2 = numregs readInputRegisters(field1, field2); break; case MB_FC_WRITE_COIL: //field1 = reg, field2 = status writeSingleCoil(field1, field2); break; case MB_FC_WRITE_COILS: //field1 = startreg, field2 = numoutputs writeMultipleCoils(field1, field2, mb_frame[6]); break; case MB_FC_DEBUG_INFO: debugInfo(); break; case MB_FC_DEBUG_GET: //field1 = startidx, field2 = endidx debugGetTrace(field1, field2); break; case MB_FC_DEBUG_GET_LIST: //field1 = numIndexes debugGetTraceList(field1, &mb_frame[4]); break; case MB_FC_DEBUG_SET: //field1 = varidx debugSetTrace(field1, flag, len, value); break; case MB_FC_DEBUG_GET_MD5: debugGetMd5(endianness_check); break; default: exceptionResponse(fcode, MB_EX_ILLEGAL_FUNCTION); } } //Modbus handling functions void readRegisters(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_VALUE); return; } //Check Address if ((startreg+numregs) > modbus.holding_size) { exceptionResponse(MB_FC_READ_REGS, MB_EX_ILLEGAL_ADDRESS); return; } //calculate the query reply message length //for each register queried add 2 bytes mb_frame_len = 3 + (numregs * 2); if (mb_frame_len > MAX_MB_FRAME) { //Response message is too big for this device exceptionResponse(MB_FC_READ_REGS, MB_EX_SLAVE_FAILURE); return; } //Clean frame buffer (leave only SlaveID) for (int i = 1; i < mb_frame_len; i++) mb_frame[i] = 0; mb_frame[1] = MB_FC_READ_REGS; mb_frame[2] = mb_frame_len - 3; //byte count uint16_t val; uint16_t i = 0; while(numregs--) { //retrieve the value from the register bank for the current register val = modbus.holding[startreg + i]; //write the high byte of the register value mb_frame[3 + (i * 2)] = val >> 8; //write the low byte of the register value mb_frame[4 + (i * 2)] = val & 0xFF; i++; } } void writeSingleRegister(uint16_t reg, uint16_t value) { if (reg > (modbus.holding_size - 1)) { exceptionResponse(MB_FC_WRITE_REG, MB_EX_ILLEGAL_ADDRESS); return; } modbus.holding[reg] = value; } void writeMultipleRegisters(uint16_t startreg, uint16_t numoutputs, uint8_t bytecount) { //Check value if (numoutputs < 0x0001 || numoutputs > 0x007B || bytecount != 2 * numoutputs) { exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_VALUE); return; } //Check Address (startreg...startreg + numregs) if ((startreg + numoutputs) > modbus.holding_size) { exceptionResponse(MB_FC_WRITE_REGS, MB_EX_ILLEGAL_ADDRESS); return; } //Prepare answer frame buffer mb_frame_len = 6; mb_frame[1] = MB_FC_WRITE_REGS; mb_frame[2] = startreg >> 8; mb_frame[3] = startreg & 0x00FF; mb_frame[4] = numoutputs >> 8; mb_frame[5] = numoutputs & 0x00FF; uint16_t val; uint16_t i = 0; while(numoutputs--) { val = (uint16_t)mb_frame[7+i*2] << 8 | (uint16_t)mb_frame[8+i*2]; modbus.holding[startreg + i] = val; i++; } } void exceptionResponse(uint16_t fcode, uint16_t excode) { //Clean frame buffer (leave only SlaveID) mb_frame_len = 3; for (int i = 0; i < mb_frame_len; i++) mb_frame[i] = 0; mb_frame[0] = modbus.slaveid; mb_frame[1] = fcode + 0x80; mb_frame[2] = excode; } void readCoils(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x07D0) { exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_VALUE); return; } //Check Address if (startreg + numregs > modbus.coils_size) { exceptionResponse(MB_FC_READ_COILS, MB_EX_ILLEGAL_ADDRESS); return; } //Determine the message length = slaveid + function type + byte count and //for each group of 8 registers the message length increases by 1 mb_frame_len = 3 + numregs/8; if (numregs%8) mb_frame_len++; //Add 1 to the message length for the partial byte. if (mb_frame_len > MAX_MB_FRAME) { //Response message is too big for this device exceptionResponse(MB_FC_READ_COILS, MB_EX_SLAVE_FAILURE); return; } //Clean frame buffer (leave only SlaveID) for (int i = 1; i < mb_frame_len; i++) mb_frame[i] = 0; mb_frame[1] = MB_FC_READ_COILS; mb_frame[2] = mb_frame_len - 3; //byte count (mb_frame_len - slave id, function code and byte count) uint8_t bitn = 0; uint16_t totregs = numregs; uint16_t i; while (numregs) { i = (totregs - numregs--) / 8; if (get_discrete((uint8_t)startreg, COILS)) bitSet(mb_frame[3+i], bitn); else bitClear(mb_frame[3+i], bitn); //increment the bit index bitn++; if (bitn == 8) bitn = 0; //increment the register startreg++; } } void readInputStatus(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x07D0) { exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_ILLEGAL_VALUE); return; } //Check Address if ((startreg + numregs) > modbus.input_status_size) { exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_ILLEGAL_ADDRESS); return; } //Determine the message length = function type, byte count and //for each group of 8 registers the message length increases by 1 mb_frame_len = 3 + numregs/8; if (numregs%8) mb_frame_len++; //Add 1 to the message length for the partial byte. if (mb_frame_len > MAX_MB_FRAME) { //Response message is too big for this device exceptionResponse(MB_FC_READ_INPUT_STAT, MB_EX_SLAVE_FAILURE); return; } //Clean frame buffer (leave only SlaveID) for (int i = 1; i < mb_frame_len; i++) mb_frame[i] = 0; mb_frame[1] = MB_FC_READ_INPUT_STAT; mb_frame[2] = mb_frame_len - 3; byte bitn = 0; uint16_t totregs = numregs; uint16_t i; while (numregs) { i = (totregs - numregs--) / 8; if (get_discrete(startreg, INPUTSTATUS)) bitSet(mb_frame[3+i], bitn); else bitClear(mb_frame[3+i], bitn); //increment the bit index bitn++; if (bitn == 8) bitn = 0; //increment the register startreg++; } } void readInputRegisters(uint16_t startreg, uint16_t numregs) { //Check value (numregs) if (numregs < 0x0001 || numregs > 0x007D) { exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_ILLEGAL_VALUE); return; } //Check Address if ((startreg + numregs) > modbus.input_regs_size) { exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_ILLEGAL_ADDRESS); return; } //calculate the query reply message length //for each register queried add 2 bytes mb_frame_len = 3 + (numregs * 2); if (mb_frame_len > MAX_MB_FRAME) { //Response message is too big for this device exceptionResponse(MB_FC_READ_INPUT_REGS, MB_EX_SLAVE_FAILURE); return; } //Clean frame buffer (leave only SlaveID) for (int i = 1; i < mb_frame_len; i++) mb_frame[i] = 0; mb_frame[1] = MB_FC_READ_INPUT_REGS; mb_frame[2] = mb_frame_len - 3; uint16_t val; uint16_t i = 0; while(numregs--) { //retrieve the value from the register bank for the current register val = modbus.input_regs[startreg + i]; //write the high byte of the register value mb_frame[3 + (i * 2)] = val >> 8; //write the low byte of the register value mb_frame[4 + (i * 2)] = val & 0xFF; i++; } } void writeSingleCoil(uint16_t reg, uint16_t status) { //Check value (status) if (status != 0xFF00 && status != 0x0000) { exceptionResponse(MB_FC_WRITE_COIL, MB_EX_ILLEGAL_VALUE); return; } //Check Address if (reg > (modbus.coils_size - 1)) { exceptionResponse(MB_FC_WRITE_COIL, MB_EX_ILLEGAL_ADDRESS); return; } //Execute write_discrete(reg, COILS, status == 0xFF00 ? true : false); } void writeMultipleCoils(uint16_t startreg, uint16_t numoutputs, uint16_t bytecount) { //Check value uint8_t bytecount_calc = numoutputs / 8; if (numoutputs%8) bytecount_calc++; if (numoutputs < 0x0001 || numoutputs > 0x07B0 || bytecount != bytecount_calc) { exceptionResponse(MB_FC_WRITE_COILS, MB_EX_ILLEGAL_VALUE); return; } //Check Address (startreg...startreg + numregs) if ((startreg + numoutputs) > modbus.coils_size) { exceptionResponse(MB_FC_WRITE_COILS, MB_EX_ILLEGAL_ADDRESS); return; } //Prepare answer frame buffer mb_frame_len = 6; mb_frame[1] = MB_FC_WRITE_COILS; mb_frame[2] = startreg >> 8; mb_frame[3] = startreg & 0x00FF; mb_frame[4] = numoutputs >> 8; mb_frame[5] = numoutputs & 0x00FF; //Execute uint8_t bitn = 0; uint16_t totoutputs = numoutputs; uint16_t i; while (numoutputs) { i = (totoutputs - numoutputs--) / 8; write_discrete(startreg, COILS, bitRead(mb_frame[7+i], bitn)); //increment the bit index bitn++; if (bitn == 8) bitn = 0; //increment the register startreg++; } } /** * @brief Sends a Modbus response frame for the DEBUG_INFO function code. * * This function constructs a Modbus response frame for the DEBUG_INFO function code. * The response frame includes the number of variables defined in the PLC program. * * Modbus Response Frame (DEBUG_INFO): * +-----+-------+-------+ * | MB | Count | Count | * | FC | | | * +-----+-------+-------+ * |0x41 | High | Low | * | | Byte | Byte | * | | | | * +-----+-------+-------+ * * @return void */ void debugInfo() { uint16_t variableCount = get_var_count(); mb_frame_len = 4; mb_frame[1] = MB_FC_DEBUG_INFO; mb_frame[2] = (uint8_t)(variableCount >> 8); // High byte mb_frame[3] = (uint8_t)(variableCount & 0xFF); // Low byte } /** * @brief Sends a Modbus response frame for the DEBUG_SET function code. * * This function constructs a Modbus response frame for the DEBUG_SET function code. * The response frame indicates whether the set trace command was successful or if * there was an error, such as an out-of-bounds index. * * Modbus Response Frame (DEBUG_SET): * +-----+------+ * | MB | Resp.| * | FC | Code | * +-----+------+ * |0x42 | Code | * +-----+------+ * * @param varidx The index of the variable to set trace for. * @param flag The trace flag. * @param len The length of the trace data. * @param value Pointer to the trace data. * * @return void */ void debugSetTrace(uint16_t varidx, uint8_t flag, uint16_t len, void *value) { uint16_t variableCount = get_var_count(); if (varidx >= variableCount || len > (MAX_MB_FRAME - 7)) { // Respond with an error indicating that the index is out of range mb_frame_len = 3; mb_frame[1] = MB_FC_DEBUG_SET; mb_frame[2] = MB_DEBUG_ERROR_OUT_OF_BOUNDS; return; } // Execute set trace command set_trace((size_t)varidx, (bool)flag, value); // Response mb_frame_len = 3; mb_frame[1] = MB_FC_DEBUG_SET; mb_frame[2] = MB_DEBUG_SUCCESS; } /** * @brief Sends a Modbus response frame for the DEBUG_GET function code. * * This function constructs a Modbus response frame for the DEBUG_GET function code. * The response frame includes the trace data for variables within the specified index range. * * Modbus Response Frame (DEBUG_GET): * +-----+-------+-------+-------+-------+-------+-------+-------+-------+------+-------+ * | MB | Resp. | Last | Last | Tick | Tick | Tick | Tick | Resp. | Resp.| Data | * | FC | Code | Index | Index | | | | | Size | Size | Bytes | * +-----+-------+-------+-------+-------+-------+-------+-------+-------+------+-------+ * |0x44 | Code | High | Low | High | Mid | Mid | Low | High | Low | Data | * | | | Byte | Byte | Byte | Byte | Byte | Byte | Byte | Byte | Bytes | * +-----+-------+-------+-------+-------+-------+-------+-------+-------+------+-------+ * * @param startidx The start index of the variables to get trace for. * @param endidx The end index of the variables to get trace for. * * @return void */ void debugGetTrace(uint16_t startidx, uint16_t endidx) { uint16_t variableCount = get_var_count(); // Verify that startidx and endidx fall within the valid range of variables if (startidx >= variableCount || endidx >= variableCount || startidx > endidx) { // Respond with an error indicating that the indices are out of range mb_frame_len = 3; mb_frame[1] = MB_FC_DEBUG_GET; mb_frame[2] = MB_DEBUG_ERROR_OUT_OF_BOUNDS; return; } uint16_t lastVarIdx = startidx; size_t responseSize = 0; uint8_t *responsePtr = &(mb_frame[11]); // Start of response data for (uint16_t varidx = startidx; varidx <= endidx; varidx++) { size_t varSize = get_var_size(varidx); if ((responseSize + 11) + varSize <= MAX_MB_FRAME) // Make sure the response fits { void *varAddr = get_var_addr(varidx); // Copy the variable value to the response buffer memcpy(responsePtr, varAddr, varSize); // Update response pointer and size responsePtr += varSize; responseSize += varSize; // Update the lastVarIdx lastVarIdx = varidx; } else { // Response buffer is full, break the loop break; } } mb_frame_len = 7 + responseSize; // Update response length mb_frame[1] = MB_FC_DEBUG_GET; mb_frame[2] = MB_DEBUG_SUCCESS; mb_frame[3] = (uint8_t)(lastVarIdx >> 8); // High byte mb_frame[4] = (uint8_t)(lastVarIdx & 0xFF); // Low byte mb_frame[5] = (uint8_t)((__tick >> 24) & 0xFF); // Highest byte mb_frame[6] = (uint8_t)((__tick >> 16) & 0xFF); // Second highest byte mb_frame[7] = (uint8_t)((__tick >> 8) & 0xFF); // Second lowest byte mb_frame[8] = (uint8_t)(__tick & 0xFF); // Lowest byte mb_frame[9] = (uint8_t)(responseSize >> 8); // High byte mb_frame[10] = (uint8_t)(responseSize & 0xFF); // Low byte } /** * @brief Sends a Modbus response frame for the DEBUG_GET_LIST function code. * * This function constructs a Modbus response frame for the DEBUG_GET_LIST function code. * The response frame includes the trace data for variables specified in the provided index list. * * Modbus Response Frame (DEBUG_GET_LIST): * +-----+-------+-------+-------+-------+-------+-------+-------+-------+------+-------+ * | MB | Resp. | Last | Last | Tick | Tick | Tick | Tick | Resp. | Resp.| Data | * | FC | Code | Index | Index | | | | | Size | Size | Bytes | * +-----+-------+-------+-------+-------+-------+-------+-------+-------+------+-------+ * |0x44 | Code | High | Low | High | Mid | Mid | Low | High | Low | Data | * | | | Byte | Byte | Byte | Byte | Byte | Byte | Byte | Byte | Bytes | * +-----+-------+-------+-------+-------+-------+-------+-------+-------+------+-------+ * * @param numIndexes The number of indexes requested. * @param indexArray Pointer to the array containing variable indexes. * * @return void */ void debugGetTraceList(uint16_t numIndexes, uint8_t *indexArray) { uint16_t response_idx = 11; // Start of response data in the response buffer uint16_t responseSize = 0; uint16_t lastVarIdx = 0; uint16_t variableCount = get_var_count(); uint16_t *varidx_array = NULL; // Allocate space for all indexes varidx_array = (uint16_t *)malloc(numIndexes * sizeof(uint16_t)); if (varidx_array == NULL) { // Respond with a memory error mb_frame_len = 3; mb_frame[1] = MB_FC_DEBUG_GET_LIST; mb_frame[2] = MB_DEBUG_ERROR_OUT_OF_MEMORY; return; } // Copy all indexes to array for (uint16_t i = 0; i < numIndexes; i++) { varidx_array[i] = (uint16_t)indexArray[i * 2] << 8 | indexArray[i * 2 + 1]; } // Validate if all requested indexes are in range for (uint16_t i = 0; i < numIndexes; i++) { if (varidx_array[i] >= variableCount) { // Respond with an error indicating that the index is out of range mb_frame_len = 3; mb_frame[1] = MB_FC_DEBUG_GET_LIST; mb_frame[2] = MB_DEBUG_ERROR_OUT_OF_BOUNDS; free(varidx_array); return; } // Add requested indexes and their traces to the response buffer size_t varSize = get_var_size(varidx_array[i]); // Make sure there is enough space in the response buffer if (response_idx + varSize <= MAX_MB_FRAME) { // Add variable data to the response buffer void *varAddr = get_var_addr(varidx_array[i]); memcpy(&mb_frame[response_idx], varAddr, varSize); response_idx += varSize; responseSize += varSize; // Update the lastVarIdx lastVarIdx = varidx_array[i]; } else { // Response buffer is full, break the loop break; } } // Update response length, lastVarIdx, and response size mb_frame_len = response_idx; mb_frame[1] = MB_FC_DEBUG_GET_LIST; mb_frame[2] = MB_DEBUG_SUCCESS; mb_frame[3] = (uint8_t)(lastVarIdx >> 8); // High byte mb_frame[4] = (uint8_t)(lastVarIdx & 0xFF); // Low byte mb_frame[5] = (uint8_t)((__tick >> 24) & 0xFF); // Highest byte mb_frame[6] = (uint8_t)((__tick >> 16) & 0xFF); // Second highest byte mb_frame[7] = (uint8_t)((__tick >> 8) & 0xFF); // Second lowest byte mb_frame[8] = (uint8_t)(__tick & 0xFF); // Lowest byte mb_frame[9] = (uint8_t)(responseSize >> 8); // High byte mb_frame[10] = (uint8_t)(responseSize & 0xFF); // Low byte free(varidx_array); } void debugGetMd5(void *endianness) { // Check endianness uint16_t endian_check = 0; memcpy(&endian_check, endianness, 2); if (endian_check == 0xDEAD) { set_endianness(SAME_ENDIANNESS); } else if (endian_check == 0xADDE) { set_endianness(REVERSE_ENDIANNESS); } else { // Respond with an error indicating that the argument is wrong mb_frame_len = 3; mb_frame[1] = MB_FC_DEBUG_GET_MD5; mb_frame[2] = MB_DEBUG_ERROR_OUT_OF_BOUNDS; //return; } mb_frame[1] = MB_FC_DEBUG_GET_MD5; mb_frame[2] = MB_DEBUG_SUCCESS; // Copy MD5 string byte by byte to mb_frame starting from index 3 const char md5[] = PROGRAM_MD5; int md5_len = 0; for (md5_len = 0; md5[md5_len] != '\0'; md5_len++) { mb_frame[md5_len + 3] = md5[md5_len]; } // Calculate mb_frame_len (MD5 string length + 3) mb_frame_len = md5_len + 3; } uint16_t calcCrc() { uint8_t CRCHi = 0xFF, CRCLo = 0x0FF, Index; int i = 0; Index = CRCHi ^ mb_frame[i]; CRCHi = CRCLo ^ _auchCRCHi[Index]; CRCLo = _auchCRCLo[Index]; i++; while (i < (mb_frame_len - 2)) { Index = CRCHi ^ mb_frame[i]; i++; CRCHi = CRCLo ^ _auchCRCHi[Index]; CRCLo = _auchCRCLo[Index]; } return ((uint16_t)CRCHi << 8) | (uint16_t)CRCLo; }