Biblioteka libModbusRTUserialLineSlave
-- Sebastian Pawlak, 2014.
Przedstawiam moją bibliotekę implementującą protokół MODBUS/RTU dla urządzeń typu slave dla linii szeregowej RS485. Zamieściłem także kod krótkiego programu wykorzystującego bibliotekę, a także implementującego funkcje callback.
Kod źródłowy pliku "libModbusRTUserialLineSlave.h":
/* libModbusSerialLineSlave: Modbus RTU protocol library for slave devices * on serial line * * This library was designed to operate with Atmel AVR ATmega128 and RS485 * * Sebastian Pawlak, 2015, v1.3 */ #ifndef _MODBUS_H_ #define _MODBUS_H_ #ifndef F_CPU #define F_CPU 16000000UL /* set your uC frequency */ #endif /* set proper values */ extern uint8_t modbusSlaveAddress; extern uint8_t serialLineBaudRate; extern uint16_t serialLineBaudRateUBRR; extern uint8_t serialLineParity; extern uint8_t serialLineStopBits; extern uint8_t serialLineCharacterSize; extern volatile uint8_t modbusBusy; /* set it to 1 while the device * is busy and cannot process * incoming Modbus commands */ /* serial line diagnostic counters */ extern volatile uint16_t returnBusMessageCount; extern volatile uint16_t returnBusCommunicationErrorCount; extern volatile uint16_t returnSlaveExceptionErrorCount; extern volatile uint16_t returnSlaveMessageCount; extern volatile uint16_t returnSlaveNoResponseCount; extern volatile uint16_t returnSlaveNAKcount; extern volatile uint16_t returnSlaveBusyCount; extern volatile uint16_t returnBusCharacterOverrunCount; /* own useful counters, not demanded by Modbus */ extern volatile uint16_t busCharacterParityErrorCount; extern volatile uint16_t busCharacterFrameErrorCount; /* these variables are extern because of diagnostic purposes in main program */ extern volatile uint8_t crcError; extern volatile uint16_t crcErrorCntr; extern volatile uint8_t frameError; extern volatile uint8_t tooShortFrame; extern volatile uint8_t wrongAddress; extern volatile uint8_t modeState; extern volatile uint8_t listenOnlyMode; extern volatile uint8_t bufferOverflow; extern volatile uint8_t charCntr; extern volatile uint16_t crc, crc1, crc2; extern volatile uint8_t modbusBuffer[]; extern volatile uint8_t charReceived; extern volatile uint8_t charSent; /* set proper values */ #define MODBUS_MEI_VENDOR_NAME "Pawlak" #define MODBUS_MEI_PRODUCT_CODE SIGNATURE #define MODBUS_MEI_MAJOR_MINOR_REVISION VERSION #define NUMBER_OF_DISCRETE_INPUTS 0 #define NUMBER_OF_COILS 26 #define NUMBER_OF_INPUT_REGISTERS 0 #define NUMBER_OF_HOLDING_REGISTERS 603 /* 592 - welding parameters, * 2 - drop/holding times, * 9 - max currents */ /* set proper port and pin */ #define TX_RX_SWITCH_OUTPUT_PORT PORTE #define TX_RX_SWITCH_OUTPUT_PIN PORTE2 #define TX_RX_SWITCH_DDR DDRE #define TX_RX_SWITCH_DDR_PIN DDE2 /* set proper registers and flags */ #define SIG_UART_RECV SIG_UART0_RECV #define SIG_UART_TRANS SIG_UART0_TRANS #define USART_UBRRH UBRR0H #define USART_UBRRL UBRR0L #define USART_UCSRA UCSR0A #define USART_UCSRB UCSR0B #define USART_UCSRC UCSR0C #define USART_TXEN TXEN0 #define USART_RXEN RXEN0 #define USART_UCSZ2 UCSZ02 #define USART_UCSZ1 UCSZ01 #define USART_UCSZ0 UCSZ00 #define USART_UDRE UDRE0 #define USART_UDR UDR0 #define USART_RXCIE RXCIE0 #define USART_TXCIE TXCIE0 #define USART_UPM1 UPM01 #define USART_UPM0 UPM00 #define USART_USBS USBS0 #define USART_UPE UPE0 #define USART_FE FE0 #define USART_DOR DOR0 #define UBRRVAL(BAUD) ((F_CPU + ((BAUD) * 8UL)) / (16UL * (BAUD)) - 1) enum { USART_BAUD_RATE_9600 = 0, USART_BAUD_RATE_19200 = 1, USART_DEFAULT_BAUD_RATE = USART_BAUD_RATE_19200, USART_BAUD_RATE_9600_UBRR = UBRRVAL(9600), USART_BAUD_RATE_19200_UBRR = UBRRVAL(19200), USART_DEFAULT_BAUD_RATE_UBRR = USART_BAUD_RATE_19200_UBRR, USART_PARITY_NONE = 0, USART_PARITY_EVEN = 1, USART_PARITY_ODD = 2, USART_DEFAULT_PARITY = USART_PARITY_EVEN, USART_CHARACTER_SIZE_8_BITS = 0, USART_CHARACTER_SIZE_7_BITS = 1, USART_DEFAULT_CHARACTER_SIZE = USART_CHARACTER_SIZE_8_BITS, USART_1_STOP_BIT = 0, USART_2_STOP_BITS = 1, USART_DEFAULT_STOP_BITS = USART_1_STOP_BIT, MODBUS_SLAVE_ADDRESS_MIN = 1, MODBUS_SLAVE_ADDRESS_MAX = 247, MODBUS_BROADCAST_ADDRESS = 0, }; #define _BV(bit) (1 << (bit)) #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) void initUSART(void); void stopUSART(void); uint8_t (*modbusReadCoilsCallbackPtr)(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf); uint8_t (*modbusReadDiscreteInputsCallbackPtr)(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf); uint8_t (*modbusReadHoldingRegistersCallbackPtr)(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf); uint8_t (*modbusReadInputRegistersCallbackPtr)(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf); uint8_t (*modbusWriteCoilsCallbackPtr)(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf); uint8_t (*modbusWriteHoldingRegistersCallbackPtr)(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf); #endif
Kod źródłowy pliku "libModbusRTUserialLineSlave.c":
/* libModbusSerialLineSlave: Modbus RTU protocol library for slave devices * on serial line * * This library was designed to operate with Atmel AVR ATmega128 and RS485 * * * Sebastian Pawlak, 2015, v1.3 * * Changelog: * 2015-10-04, v1.3: bugfix in SIG_UART_TRANS * 2015-03-01, v1.2: bugfix of modbus busy response * 2014-10-26, v1.1: new variables added charReceived, charSent; * exception code no. 1 returned for nonexistent callbacks */ #include <stdlib.h> #include <string.h> #include <avr/interrupt.h> #include <avr/signal.h> #include <avr/io.h> #include <inttypes.h> #include "version.h" #include "libModbusRTUserialLineSlave.h" #include <util/delay.h> #include <avr/crc16.h> #define OCRVAL(BAUD, RATIO) (F_CPU / (1024UL * (BAUD) / (11UL * (RATIO))) - 1) void sendBuffer(uint8_t *buffer, uint8_t length); uint8_t modbusSlaveAddress; uint8_t serialLineBaudRate = USART_DEFAULT_BAUD_RATE; uint16_t serialLineBaudRateUBRR = USART_DEFAULT_BAUD_RATE_UBRR; uint8_t serialLineParity = USART_DEFAULT_PARITY; uint8_t serialLineStopBits = USART_DEFAULT_STOP_BITS; uint8_t serialLineCharacterSize = USART_DEFAULT_CHARACTER_SIZE; volatile uint8_t modbusBusy; /* set it to 1 while the device is busy * and cannot process incoming Modbus commands */ /* serial line diagnostic counters */ volatile uint16_t returnBusMessageCount; volatile uint16_t returnBusCommunicationErrorCount; volatile uint16_t returnSlaveExceptionErrorCount; volatile uint16_t returnSlaveMessageCount; volatile uint16_t returnSlaveNoResponseCount; volatile uint16_t returnSlaveNAKcount; volatile uint16_t returnSlaveBusyCount; volatile uint16_t returnBusCharacterOverrunCount; /* own useful counters, not demanded by Modbus */ volatile uint16_t busCharacterParityErrorCount; volatile uint16_t busCharacterFrameErrorCount; volatile uint16_t diagnosticRegister; /* OCRt4_5 corresponds to t3,5, * OCRt2_5 corresponds to t1,5, * OCRt2_0 corresponds to t3,5 - t1,5 = t2,0 */ uint8_t OCRt4_5; uint8_t OCRt2_5; uint8_t OCRt2_0; volatile uint8_t crcError; volatile uint16_t crcErrorCntr; volatile uint8_t frameError; volatile uint8_t tooShortFrame; volatile uint8_t wrongAddress; volatile uint8_t modeState; volatile uint8_t listenOnlyMode; volatile uint8_t bufferOverflow; volatile uint8_t charCntr; volatile uint16_t crc, crc1, crc2; volatile uint8_t charReceived; volatile uint8_t charSent; enum { MODBUS_BUFFER_SIZE = 83, /* max. 256 */ MODE_STATE_INITIAL_STATE = 0, MODE_STATE_IDLE, MODE_STATE_EMISSION, MODE_STATE_RECEPTION, MODE_STATE_CONTROL_AND_WAITING, }; volatile uint8_t modbusBuffer[MODBUS_BUFFER_SIZE] = "\0"; volatile uint8_t *bufPtr = modbusBuffer; volatile uint8_t data1, data2; /* initUSART: USART initialization */ void initUSART(void) { cli(); TX_RX_SWITCH_DDR |= _BV(TX_RX_SWITCH_DDR_PIN); /* transmission baud rate */ USART_UBRRH = (uint8_t)(serialLineBaudRateUBRR >> 8); USART_UBRRL = (uint8_t)serialLineBaudRateUBRR; USART_UCSRB = _BV(USART_RXCIE) | /* receiver complete interrupt */ _BV(USART_RXEN) | _BV(USART_TXEN); /* enable receiver */ /* frame format */ #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega16__) || \ defined(__AVR_ATmega32__) USART_UCSRC = _BV(URSEL); #else USART_UCSRC = 0; #endif if (serialLineParity == USART_PARITY_EVEN) USART_UCSRC |= _BV(USART_UPM1); else if (serialLineParity == USART_PARITY_ODD) USART_UCSRC |= _BV(USART_UPM0) | _BV(USART_UPM1); if (serialLineStopBits == USART_2_STOP_BITS) USART_UCSRC |= _BV(USART_USBS); if (serialLineCharacterSize == USART_CHARACTER_SIZE_7_BITS) USART_UCSRC |= _BV(USART_UCSZ1); else /* USART_CHARACTER_SIZE_8_BITS */ USART_UCSRC |= _BV(USART_UCSZ1) | _BV(USART_UCSZ0); TX_RX_SWITCH_OUTPUT_PORT &= ~_BV(TX_RX_SWITCH_OUTPUT_PIN); /* receive */ _delay_us(1); /* timer initialization */ TCCR3B = 0; /* timer stop - prescaler 0 */ ETIFR |= _BV(OCF3A); ETIMSK |= _BV(OCIE3A); switch (serialLineBaudRate) { case USART_BAUD_RATE_9600: OCRt4_5 = OCRVAL(9600, 4.5); OCRt2_5 = OCRVAL(9600, 2.5); OCRt2_0 = OCRVAL(9600, 2.0); break; case USART_BAUD_RATE_19200: OCRt4_5 = OCRVAL(19200, 4.5); OCRt2_5 = OCRVAL(19200, 2.5); OCRt2_0 = OCRVAL(19200, 2.0); break; } crcError = 0; frameError = 0; tooShortFrame = 0; wrongAddress = 0; crc = 0xffff; modeState = MODE_STATE_INITIAL_STATE; sei(); TCCR3B = 0; TCNT3 = 0; ETIFR |= _BV(TOV3); /* clear interrupt flag */ OCR3A = OCRt4_5; TCCR3B = _BV(WGM32) | _BV(CS32) | _BV(CS30); /* prescaler 1024 */ } /* stopUSART: USART stop */ void stopUSART(void) { ETIMSK &= ~_BV(OCIE3A); USART_UCSRB &= ~(_BV(USART_RXCIE) | _BV(USART_RXEN) | _BV(USART_TXEN)); } SIGNAL (SIG_OUTPUT_COMPARE3A) { uint16_t addr, qty; uint8_t m; if (modeState == MODE_STATE_RECEPTION) { modeState = MODE_STATE_CONTROL_AND_WAITING; if (charCntr >= 3) { if ((data2 != (uint8_t)crc2) || (data1 != (uint8_t)(crc2 >> 8))) crcError = 1, crcErrorCntr++; } else tooShortFrame = 1; if ((frameError == 0) && (tooShortFrame == 0) && (crcError == 0)) { returnBusMessageCount++; if (wrongAddress == 0) { returnSlaveMessageCount++; if (modbusBuffer[0] == 0) returnSlaveNoResponseCount++; } } else returnBusCommunicationErrorCount++; TCCR3B = 0; TCNT3 = 0; ETIFR |= _BV(TOV3); /* clear interrupt flag */ OCR3A = OCRt2_0; TCCR3B = _BV(WGM32) | _BV(CS32) | _BV(CS30); /* prescaler 1024 */ } else if (modeState == MODE_STATE_CONTROL_AND_WAITING) { modeState = MODE_STATE_IDLE; TCCR3B = 0; TCNT3 = 0; ETIFR |= _BV(TOV3); /* clear interrupt flag */ if ((wrongAddress == 0) && (tooShortFrame == 0) && (frameError == 0) && (crcError == 0) && (bufferOverflow == 0) && ((listenOnlyMode == 0) || ((modbusBuffer[1] == 8) && (modbusBuffer[2] == 0) && (modbusBuffer[3] == 1)))) { uint8_t exceptionCode = 0; if (modbusBusy == 1) { returnSlaveBusyCount++; exceptionCode = 6; goto exception; } switch (modbusBuffer[1]) { case 1: /* 01 (0x01) Read Coils */ if (modbusReadCoilsCallbackPtr == NULL) { exceptionCode = 1; break; } qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5]; if ((qty >= 1) && (qty <= 0x07D0)) { addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3]; if (addr + qty <= NUMBER_OF_COILS) { m = (qty >> 3) + ((qty & 0x07) != 0); if (3 + 2 + m <= MODBUS_BUFFER_SIZE) { if ((exceptionCode = modbusReadCoilsCallbackPtr(addr, qty, (uint8_t *)&modbusBuffer[3])) == 0) { modbusBuffer[2] = m; sendBuffer((uint8_t *)modbusBuffer, 3 + modbusBuffer[2]); } } else exceptionCode = 4; } else exceptionCode = 2; } else exceptionCode = 3; break; case 2: /* 02 (0x02) Read Discrete Inputs */ if (modbusReadDiscreteInputsCallbackPtr == NULL) { exceptionCode = 1; break; } qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5]; if ((qty >= 1) && (qty <= 0x07D0)) { addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3]; if (addr + qty <= NUMBER_OF_DISCRETE_INPUTS) { m = (qty >> 3) + ((qty & 0x07) != 0); if (3 + 2 + m <= MODBUS_BUFFER_SIZE) { if ((exceptionCode = modbusReadDiscreteInputsCallbackPtr(addr, qty, (uint8_t *)&modbusBuffer[3])) == 0) { modbusBuffer[2] = m; sendBuffer((uint8_t *)modbusBuffer, 3 + modbusBuffer[2]); } } else exceptionCode = 4; } else exceptionCode = 2; } else exceptionCode = 3; break; case 3: /* 03 (0x03) Read Holding Registers */ if (modbusReadHoldingRegistersCallbackPtr == NULL) { exceptionCode = 1; break; } qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5]; if ((qty >= 1) && (qty <= 0x07D)) { addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3]; if (addr + qty <= NUMBER_OF_HOLDING_REGISTERS) { m = qty << 1; if (3 + 2 + m <= MODBUS_BUFFER_SIZE) { if ((exceptionCode = modbusReadHoldingRegistersCallbackPtr(addr, qty, (uint8_t *)&modbusBuffer[3])) == 0) { modbusBuffer[2] = m; sendBuffer((uint8_t *)modbusBuffer, 3 + modbusBuffer[2]); } } else exceptionCode = 4; } else exceptionCode = 2; } else exceptionCode = 3; break; case 4: /* 04 (0x04) Read Input Registers */ if (modbusReadInputRegistersCallbackPtr == NULL) { exceptionCode = 1; break; } qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5]; if ((qty >= 1) && (qty <= 0x07D)) { addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3]; if (addr + qty <= NUMBER_OF_INPUT_REGISTERS) { m = qty << 1; if (3 + 2 + m <= MODBUS_BUFFER_SIZE) { if ((exceptionCode = modbusReadInputRegistersCallbackPtr(addr, qty, (uint8_t *)&modbusBuffer[3])) == 0) { modbusBuffer[2] = m; sendBuffer((uint8_t *)modbusBuffer, 3 + modbusBuffer[2]); } } else exceptionCode = 4; } else exceptionCode = 2; } else exceptionCode = 3; break; case 5: /* 05 (0x05) Write Single Coil */ if (modbusWriteCoilsCallbackPtr == NULL) { exceptionCode = 1; break; } if ((modbusBuffer[5] != 0) || ((modbusBuffer[4] != 0) && (modbusBuffer[4] != 0xff))) { exceptionCode = 3; break; } addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3]; if (addr < NUMBER_OF_COILS) { if ((exceptionCode = modbusWriteCoilsCallbackPtr(addr, 1, (uint8_t *)&modbusBuffer[4])) == 0) { sendBuffer((uint8_t *)modbusBuffer, 6); } } else exceptionCode = 2; break; case 6: /* 06 (0x06) Write Single Register */ if (modbusWriteHoldingRegistersCallbackPtr == NULL) { exceptionCode = 1; break; } addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3]; if (addr < NUMBER_OF_HOLDING_REGISTERS) { if ((exceptionCode = modbusWriteHoldingRegistersCallbackPtr(addr, 1, (uint8_t *)&modbusBuffer[4])) == 0) { sendBuffer((uint8_t *)modbusBuffer, 6); } } else exceptionCode = 2; break; case 8: /* 08 (0x08) Diagnostics (Serial Line only) */ if (modbusBuffer[2] != 0) { exceptionCode = 1; break; } switch (modbusBuffer[3]) { case 0: /* Return Query Data */ sendBuffer((uint8_t *)modbusBuffer, charCntr - 2); break; case 1: /* Restart Communications Option */ if ((modbusBuffer[5] != 0) || ((modbusBuffer[4] != 0) && (modbusBuffer[4] != 0xff))) { exceptionCode = 3; break; } returnBusMessageCount = returnBusCommunicationErrorCount = returnSlaveExceptionErrorCount = returnSlaveMessageCount = returnSlaveNoResponseCount = returnBusCharacterOverrunCount = 0; busCharacterParityErrorCount = busCharacterFrameErrorCount = 0; sendBuffer((uint8_t *)modbusBuffer, charCntr - 2); listenOnlyMode = 0; break; case 2: /* Return Diagnostic Register */ if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) { exceptionCode = 3; break; } modbusBuffer[4] = diagnosticRegister >> 8; modbusBuffer[5] = diagnosticRegister; sendBuffer((uint8_t *)modbusBuffer, 6); break; case 4: /* Force Listen Only Mode */ if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) { exceptionCode = 3; break; } listenOnlyMode = 1; break; case 10: /* Clear Counters and Diagnostic Register */ if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) { exceptionCode = 3; break; } returnBusMessageCount = returnBusCommunicationErrorCount = returnSlaveExceptionErrorCount = returnSlaveMessageCount = returnSlaveNoResponseCount = returnSlaveNAKcount = returnSlaveBusyCount = returnBusCharacterOverrunCount = diagnosticRegister = 0; busCharacterParityErrorCount = busCharacterFrameErrorCount = 0; crcErrorCntr = 0; sendBuffer((uint8_t *)modbusBuffer, charCntr - 2); break; case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: /* diagnostic counters */ if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) { exceptionCode = 3; break; } uint16_t w = 0; switch (modbusBuffer[3]) { case 11: w = returnBusMessageCount; break; case 12: w = returnBusCommunicationErrorCount; break; case 13: w = returnSlaveExceptionErrorCount; break; case 14: w = returnSlaveMessageCount; break; case 15: w = returnSlaveNoResponseCount; break; case 16: w = returnSlaveNAKcount; break; case 17: w = returnSlaveBusyCount; break; case 18: w = returnBusCharacterOverrunCount; break; } modbusBuffer[4] = w >> 8; modbusBuffer[5] = w; sendBuffer((uint8_t *)modbusBuffer, 6); break; case 20: /* Clear Overrun Counter and Flag */ if ((modbusBuffer[4] != 0) || (modbusBuffer[5] != 0)) { exceptionCode = 3; break; } returnBusCharacterOverrunCount = 0; sendBuffer((uint8_t *)modbusBuffer, charCntr - 2); break; default: exceptionCode = 1; break; } break; case 15: /* 15 (0x0F) Write Multiple Coils */ if (modbusWriteCoilsCallbackPtr == NULL) { exceptionCode = 1; break; } qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5]; if ((qty >= 1) && (qty <= 0x07B0) && (modbusBuffer[6] == charCntr - 9)) { addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3]; if (addr + qty <= NUMBER_OF_COILS) { if ((exceptionCode = modbusWriteCoilsCallbackPtr(addr, qty, (uint8_t *)&modbusBuffer[7])) == 0) { sendBuffer((uint8_t *)modbusBuffer, 6); } } else exceptionCode = 2; } else exceptionCode = 3; break; case 16: /* 16 (0x10) Write Multiple Registers */ if (modbusWriteHoldingRegistersCallbackPtr == NULL) { exceptionCode = 1; break; } qty = (uint16_t)(modbusBuffer[4] << 8) | modbusBuffer[5]; if ((qty >= 1) && (qty <= 0x07B) && (modbusBuffer[6] == charCntr - 9)) { addr = (uint16_t)(modbusBuffer[2] << 8) | modbusBuffer[3]; if (addr + qty <= NUMBER_OF_HOLDING_REGISTERS) { if ((exceptionCode = modbusWriteHoldingRegistersCallbackPtr(addr, qty, (uint8_t *)&modbusBuffer[7])) == 0) { sendBuffer((uint8_t *)modbusBuffer, 6); } } else exceptionCode = 2; } else exceptionCode = 3; break; case 43: /* 43/14 (0x2B/0x0E) Read Device Identification */ if (modbusBuffer[2] != 14) { exceptionCode = 1; break; } /* Read Device ID code: basic, regular, extended, specific */ if ((modbusBuffer[3] < 1) || (modbusBuffer[3] > 4)) { exceptionCode = 3; break; } uint8_t d = modbusBuffer[4], i = 8, l = 0; modbusBuffer[5] = 0; /* More Follows */ modbusBuffer[6] = 0; /* Next Object Id */ if (modbusBuffer[3] <= 3) { /* stream access */ modbusBuffer[4] = 1; /* Conformity Level */ modbusBuffer[7] = 3 - d; /* Number Of Objects */ switch (d) { /* Object Id */ case 0: default: modbusBuffer[i++] = 0; /* Object Id */ modbusBuffer[i++] = l = strlen(MODBUS_MEI_VENDOR_NAME); strcpy((uint8_t *)&modbusBuffer[i], MODBUS_MEI_VENDOR_NAME); i += l; /* do not use break; */ case 1: modbusBuffer[i++] = 1; /* Object Id */ modbusBuffer[i++] = l = strlen(MODBUS_MEI_PRODUCT_CODE); strcpy((uint8_t *)&modbusBuffer[i], MODBUS_MEI_PRODUCT_CODE); i += l; /* do not use break; */ case 2: modbusBuffer[i++] = 2; /* Object Id */ modbusBuffer[i++] = l = strlen(MODBUS_MEI_MAJOR_MINOR_REVISION); strcpy((uint8_t *)&modbusBuffer[i], MODBUS_MEI_MAJOR_MINOR_REVISION); i += l; break; } } else { /* individual access */ modbusBuffer[4] = 0x81; /* Conformity Level */ modbusBuffer[7] = 1; /* Number Of Objects */ modbusBuffer[8] = d; /* Object Id */ if (d > 2) { exceptionCode = 2; break; } switch (d) { /* Object Id */ case 0: modbusBuffer[9] = l = strlen(MODBUS_MEI_VENDOR_NAME); strcpy((uint8_t *)&modbusBuffer[10], MODBUS_MEI_VENDOR_NAME); break; case 1: modbusBuffer[9] = l = strlen(MODBUS_MEI_PRODUCT_CODE); strcpy((uint8_t *)&modbusBuffer[10], MODBUS_MEI_PRODUCT_CODE); break; case 2: modbusBuffer[9] = l = strlen(MODBUS_MEI_MAJOR_MINOR_REVISION); strcpy((uint8_t *)&modbusBuffer[10], MODBUS_MEI_MAJOR_MINOR_REVISION); break; } i = 10 + l; } sendBuffer((uint8_t *)modbusBuffer, i); break; default: exceptionCode = 1; break; } exception: if (exceptionCode != 0) { returnSlaveExceptionErrorCount++; modbusBuffer[1] |= 0x80; modbusBuffer[2] = exceptionCode; sendBuffer((uint8_t *)modbusBuffer, 3); } } crcError = 0; frameError = 0; tooShortFrame = 0; wrongAddress = 0; crc = 0xffff; } else if (modeState == MODE_STATE_INITIAL_STATE) { modeState = MODE_STATE_IDLE; TCCR3B = 0; TCNT3 = 0; ETIFR |= _BV(TOV3); /* clear interrupt flag */ crcError = 0; frameError = 0; tooShortFrame = 0; wrongAddress = 0; crc = 0xffff; } } /* SIG_UART_RECV: receiver */ SIGNAL (SIG_UART_RECV) { uint8_t status = USART_UCSRA; uint8_t data = USART_UDR; uint8_t n; charReceived++; /* Parity Error */ if ((USART_UCSRC & _BV(USART_UPM1)) && (status & _BV(USART_UPE))) { busCharacterParityErrorCount++; frameError = 1; } /* Frame Error - stop bit was incorrectly read which may mean out-of-sync * condition */ if (status & _BV(USART_FE)) { busCharacterFrameErrorCount++; frameError = 1; } /* Data OverRun - data loss due to a receiver buffer full condition */ if (status & _BV(USART_DOR)) { returnBusCharacterOverrunCount++; frameError = 1; } if (modeState == MODE_STATE_IDLE) { modeState = MODE_STATE_RECEPTION; bufPtr = modbusBuffer; charCntr = 1; bufferOverflow = 0; if ((data == modbusSlaveAddress) || (data == 0)) *bufPtr++ = data; else wrongAddress = 1; data2 = data1, data1 = data; crc2 = crc1, crc1 = crc; crc ^= data; for(n = 0; n < 8; n++) crc = (crc & 1) ? ((crc >> 1) ^ 0xa001) : (crc >> 1); TCCR3B = 0; TCNT3 = 0; ETIFR |= _BV(TOV3); /* clear interrupt flag */ OCR3A = OCRt2_5; TCCR3B = _BV(WGM32) | _BV(CS32) | _BV(CS30); /* prescaler 1024 */ } else if (modeState == MODE_STATE_RECEPTION) { if (charCntr < MODBUS_BUFFER_SIZE) { charCntr++; if (wrongAddress == 0) *bufPtr++ = data; } else bufferOverflow = 1; data2 = data1, data1 = data; crc2 = crc1, crc1 = crc; crc ^= data; for(n = 0; n < 8; n++) crc = (crc & 1) ? ((crc >> 1) ^ 0xa001) : (crc >> 1); TCCR3B = 0; TCNT3 = 0; ETIFR |= _BV(TOV3); /* clear interrupt flag */ OCR3A = OCRt2_5; TCCR3B = _BV(WGM32) | _BV(CS32) | _BV(CS30); /* prescaler 1024 */ } else if (modeState == MODE_STATE_CONTROL_AND_WAITING) { frameError = 1; } else if (modeState == MODE_STATE_INITIAL_STATE) { TCCR3B = 0; TCNT3 = 0; ETIFR |= _BV(TOV3); /* clear interrupt flag */ OCR3A = OCRt4_5; TCCR3B = _BV(WGM32) | _BV(CS32) | _BV(CS30); /* prescaler 1024 */ } } volatile uint8_t *sendBufPtr = NULL; volatile uint8_t frameLength; /* SIG_UART_TRANS: transmiter */ SIGNAL (SIG_UART_TRANS) { if (--frameLength == 0) { USART_UCSRB &= ~_BV(USART_TXCIE); /* stop transmiter interrupt */ TX_RX_SWITCH_OUTPUT_PORT &= ~_BV(TX_RX_SWITCH_OUTPUT_PIN); /* receive */ } else { USART_UDR = *sendBufPtr++; charSent++; } } /* sendBuffer: sends data */ void sendBuffer(uint8_t *buffer, uint8_t length) { uint16_t crc = 0xffff; uint8_t i; if ((length == 0) || (length > MODBUS_BUFFER_SIZE - 2) || (listenOnlyMode != 0) || (buffer[0] == 0)) return; TX_RX_SWITCH_OUTPUT_PORT |= _BV(TX_RX_SWITCH_OUTPUT_PIN); /* transmit */ frameLength = length; for (i = 0; i < length; i++) crc = _crc16_update(crc, buffer[i]); buffer[frameLength++] = crc; buffer[frameLength++] = crc >> 8; while (!(USART_UCSRA & _BV(USART_UDRE))) ; sendBufPtr = (uint8_t *)buffer; USART_UCSRB |= _BV(USART_TXCIE); /* start transmiter interrupt */ USART_UDR = (uint8_t)*sendBufPtr++; /* send first byte of data */ }
Kod źródłowy pliku "main.c":
/* Sample usage of libModbusSerialLineSlave * * Sebastian Pawlak, 2014 */ #include <stdlib.h> #include <inttypes.h> #include <avr/io.h> #include "libModbusRTUserialLineSlave.h" uint8_t isFanRunning = 1; uint8_t alarmNumber[4] = { 0, 1, 1, 0 }; /* 0000b - no alarm, 0001b - alarm #1, * 0010b - alarm #2, 0011b - alarm #3 */ uint8_t coils[5] = { 0, 1, 0, 1, 0 }; uint16_t registerA = 77, registerX = 4444; uint16_t registers[10] = { 65, 7778, 3234, 2151, 0, 7, 55, 640, 223, 224 }; /* modbusReadCoilsCallback: used by Modbus function * 01 (0x01) Read Coils */ uint8_t modbusReadCoilsCallback(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf) { uint16_t i, m = addr + qty; uint8_t j = 0, k = 0, b = 0, mask = 1; for (i = addr; i < m; i++) { switch (i) { case 0: b |= isFanRunning ? mask : 0; break; case 1: case 2: case 3: case 4: b |= alarmNumber[i - 1] ? mask : 0; break; case 5: b |= (PORTA & _BV(PORTA3)) ? mask : 0; break; case 6: case 7: case 8: case 9: case 10: b |= coils[i - 6] ? mask : 0; break; } if (++j == 8) buf[k++] = b, b = j = 0, mask = 1; else mask <<= 1; } if (j != 0) buf[k] = b; return 0; } /* modbusWriteCoilsCallback: used by Modbus function * 05 (0x05) Write Single Coil * 15 (0x0F) Write Multiple Coils */ uint8_t modbusWriteCoilsCallback(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf) { uint16_t i, m = addr + qty; uint8_t j = 0, k = 1, b = buf[0], mask = 1; for (i = addr; i < m; i++) { switch (i) { case 0: isFanRunning = ((b & mask) != 0); break; case 1: case 2: case 3: case 4: alarmNumber[i - 1] = ((b & mask) != 0); break; case 5: if ((b & mask) != 0) PORTA |= _BV(PORTA3); else PORTA &= ~_BV(PORTA3); break; case 6: case 7: case 8: case 9: case 10: coils[i - 6] = ((b & mask) != 0); break; } if (++j == 8) b = buf[k++], j = 0, mask = 1; else mask <<= 1; } return 0; } /* modbusReadDiscreteInputsCallback: used by Modbus function * 02 (0x02) Read Discrete Inputs */ uint8_t modbusReadDiscreteInputsCallback(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf) { uint16_t i, m = addr + qty; uint8_t j = 0, k = 0, b = 0, mask = 1; for (i = addr; i < m; i++) { if (i < 8) b |= (PING & _BV(i)) ? mask : 0; else if (i < 16) b |= (PIND & _BV(i - 8)) ? mask : 0; if (++j == 8) buf[k++] = b, b = j = 0, mask = 1; else mask <<= 1; } if (j != 0) buf[k] = b; return 0; } /* modbusReadHoldingRegistersCallback: used by Modbus function * 03 (0x03) Read Holding Registers */ uint8_t modbusReadHoldingRegistersCallback(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf) { uint16_t i, m = addr + qty; uint8_t k = 0; for (i = addr; i < m; i++) { if (i == 0) buf[k++] = registerA >> 8, buf[k++] = registerA; else if (i == 1) buf[k++] = registerX >> 8, buf[k++] = registerX; else if (i < 12) buf[k++] = registers[i - 2] >> 8, buf[k++] = registers[i - 2]; } return 0; } /* modbusWriteHoldingRegistersCallback: used by Modbus function * 06 (0x06) Write Single Register * 16 (0x10) Write Multiple Registers */ uint8_t modbusWriteHoldingRegistersCallback(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf) { uint16_t i, m = addr + qty; uint8_t k = 0; for (i = addr; i < m; i++) { if (i == 0) registerA = (buf[k++] << 8), registerA |= buf[k++]; else if (i == 1) registerX = (buf[k++] << 8), registerX |= buf[k++]; else if (i < 12) registers[i - 2] = (buf[k++] << 8), registers[i - 2] |= buf[k++]; } return 0; } /* modbusReadInputRegistersCallback: used by Modbus function * 04 (0x04) Read Input Registers */ uint8_t modbusReadInputRegistersCallback(const uint16_t addr, const uint16_t qty, volatile uint8_t *buf) { uint16_t i, m = addr + qty, w; uint8_t k = 0; for (i = addr; i < m; i++) { if (i == 0) { w = PING, buf[k++] = w >> 8, buf[k++] = w; } else if (i == 1) { w = PIND, buf[k++] = w >> 8, buf[k++] = w; } } return 0; } int main(void) { DDRG = 0xff; PORTG = 0xff; DDRD = 0xff; PORTD = 0xff; modbusReadCoilsCallbackPtr = &modbusReadCoilsCallback; modbusReadDiscreteInputsCallbackPtr = &modbusReadDiscreteInputsCallback; modbusReadHoldingRegistersCallbackPtr = &modbusReadHoldingRegistersCallback; modbusReadInputRegistersCallbackPtr = &modbusReadInputRegistersCallback; modbusWriteCoilsCallbackPtr = &modbusWriteCoilsCallback; modbusWriteHoldingRegistersCallbackPtr = &modbusWriteHoldingRegistersCallback; modbusSlaveAddress = 10; initUSART(); while (1) ; return 0; }
Poniżej przedstawiono typy, adresy oraz wartości początkowe danych obsługiwanych przez program przykładowy korzystający z zaprezentowanej biblioteki.
Cewki (ang. coils) adres MODBUS PDU wartość zmienna w programie 0 1 isFanRunning 1 0 alarmNumber[0] 2 1 alarmNumber[1] 3 1 alarmNumber[2] 4 0 alarmNumber[3] 5 x PORTA3 6 0 coils[0] 7 1 coils[1] 8 0 coils[2] 9 1 coils[3] 10 0 coils[4] Wejścia dyskretne (ang. discrete inputs) adres MODBUS PDU wartość zmienna w programie 0 - 7 x PINGn 8 - 15 x PINDn Rejestry typu Holding Register adres MODBUS PDU wartość zmienna w programie 0 77 registerA 1 4444 registerB 2 65 registers[0] 3 7778 registers[1] 4 3234 registers[2] 5 2151 registers[3] 6 0 registers[4] 7 7 registers[5] 8 55 registers[6] 9 650 registers[7] 10 223 registers[8] 11 224 registers[9] Rejestry typu Input Register adres MODBUS PDU wartość zmienna w programie 0 x PING 1 x PIND Przykładowa sesja: > 0A 22 87 09 < 0A A2 01 E9 62 > 0A 01 00 00 FF FF 3C C1 < 0A 81 03 71 93 > 0A 01 00 00 00 BB 7D 02 < 0A 81 02 B0 53 > 0A 01 00 00 00 0B 7C B6 < 0A 01 02 8D 02 F8 AC > 0A 05 00 02 00 00 6D 71 < 0A 05 00 02 00 00 6D 71 > 0A 01 00 00 00 0B 7C B6 < 0A 01 02 89 02 FA 6C > 0A 05 00 02 FF 00 2C 81 < 0A 05 00 02 FF 00 2C 81 > 0A 01 00 00 00 0B 7C B6 < 0A 01 02 8D 02 F8 AC > 0A 0F 00 00 00 0B 02 FF 07 97 C6 < 0A 0F 00 00 00 0B 15 77 > 0A 01 00 00 00 0B 7C B6 < 0A 01 02 DF 07 05 CF > 0A 03 00 02 00 02 04 B0 28 < 0A 03 04 00 41 1E 62 98 AE > 0A 10 00 02 00 01 02 FF FF D5 32 < 0A 10 00 02 00 01 A1 72 > 0A 03 00 02 00 02 04 B0 28 < 0A 03 04 FF FF 1E 62 C8 9E > 0A 08 00 0B 00 00 90 B2 < 0A 08 00 0B 00 0E 11 76 > 0A 2B 0E 01 00 D5 B6 < 0A 2B 0E 01 01 00 00 03 00 06 50 61 77 6C 61 6B 01 08 4D 52 4D 69 43 5A 33 67 02 03 31 2E 30 D7 C2