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





