[Źródło: http://www.st.com/en/evaluation-tools/32f746gdiscovery.html]
Modbus:
Jest to protokół, który określa zasadę wymiany informacji między jednym bądź wieloma urządzeniami. Jedno urządzenie jest nadrzędne tzw Master. Pozostałe pełnią rolę podrzędną czyli slave. Opisywany protokół występuje w dwóch odmianach ASCII oraz późniejszej implementacji czyli RTU.
Ramka danych protokołu MODBUS RTU wygląda następująco:
- 1 bajt - adres urządzenia - adres urządzenia slave czyli od 1 do 247 bądź 0 jako adres rozgłoszeniowy.
- 1 bajt - kod funkcji
- 0 - 252 bajtów - ilość rejestrów do odczytania oraz dane odczytane lub do zapisania.
- 2 bajty - suma kontrolna CRC16
- Read Coils - Odczyt wyjść binarnych - 0x01
- Read Discrete Inputs - Odczyt wejść binarnych - 0x02
- Read Holding Register - Odczyt rejestrów pamiętających - 0x03
- Read Input Register - Odczyt rejestrów wejściowych - 0x04
- Write Single Coils - Zapis pojedynczego wyjścia binarnego - 0x05
- Write Single Register - Zapis do jednego rejestru typu holding - 0x06
- Write Multiple Coils - Zapis wielu wyjść binarnych - 0x0F
- Write Multiple Registers - Zapis wielu rejestrów - 0x10
- Error - Diagnostyka błędów - 0x08
Program:
W programie zaimplementowałem wykonywanie rozkazów na przerwaniach od UARTa, zarówno wysyłanie jak i odebranie danych.
Funkcja obsługi błędów:
- static uint8_t TransmitError(ModbusDataStructure *ModbusStrPtr, ModbusErrorTypeDef_Enum error_type, uint8_t *brodcastMsgPtr)
- {
- errorMsgCounter++;
- if(*brodcastMsgPtr == 1)
- {
- return 1;
- }
- ModbusStrPtr->sendRecBuffer[0] = SLAVE_ID;
- ModbusStrPtr->sendRecBuffer[1] = (ModbusStrPtr->sendRecBuffer[1] | 0x80);
- ModbusStrPtr->sendRecBuffer[2] = error_type;
- ModbusStrPtr->txlen=5;
- return 0;
- }
Do funkcji podawana jest struktura z danymi dla protokołu, kod błędu oraz znacznik czy odebrano dane z adresu rozgłoszeniowego. Jeśli przesłano ramkę z takim adresem to nie przesyłamy kodu błędu.
Funkcja odpowiedzialna za obsługę zapisu ramki danych:
W celu zapisania pojedynczego rejestru przesyłany jest adres układu slave po nim następuje adres rejestru do zapisania. Dalej wpisywany jest adres rejestru do zapisu po nim następuje wartość jaka będzie wpisana pod podany adres. Na końcu następuje kod CRC.
Teraz przejdę przez komendą zapisu wielu danych do rejestrów:
Tutaj po adresie oraz kodzie funkcji przesyłany jest adres startowy, zapisany na dwóch bajtach. Następnie pojawia się ilość rejestrów do wpisania, Po nich następuje ilość danych jakie mają zostać wpisane. W odpowiedzi należy przesłać adres, kod funkcji, rejestr startowy, ilość rejestrów zapisanych oraz kod CRC.
Kolejnym elementem jest komenda pozwalająca na zapis jednego wyjścia binarnego:
Wpisanie pojedynczego wyjścia polega na podaniu adresu, kodu informacji po nich następują dwa bajty danych z adresem tych informacji, po tym następuje informacja o stanie czyli czy wejście uruchomione czy wyłączone. Na samym końcu podawane jest kod CRC.
Poniżej przedstawię funkcje odczytujące status przekaźników:
Odczyt rejestrów wejściowych:
Odczyt rejestrów pamiętających:
Funkcje odczytu przesyłają ramkę, która wygląda następująco: najpierw adres, następnie ilość odczytanych bajtów, po nim następują dane przesyłane od najniższego adresu. Na końcu wprowadza się kod CRC16.
Zdekodowanie ramki i przesłanie odpowiedzi odbywa się w jednej głównej funkcji:
Pozostałe funkcje pomocnicze zostały opisane bezpośrednio w bibliotece.
Bibliotekę do postu można pobrać z dysku Google pod tym linkiem.
- static uint8_t writeSingleRegister(ModbusDataStructure *ModbusStructPtr)
- {
- uint16_t tmpData = 0;
- tmpData=((ModbusStructPtr->sendRecBuffer[2]<<8) + ModbusStructPtr->sendRecBuffer[3]); //Starting address
- if(tmpData < HOLDING_REGISTER_SIZE) /* Check exception Illegal Data Address */
- {
- ModbusStructPtr->transmitDataLength = ModbusStructPtr->recDataCount; //responce length
- res_table[tmpData]=(ModbusStructPtr->sendRecBuffer[4]<<8) + ModbusStructPtr->sendRecBuffer[5]; //Register status
- return 0;
- }
- else /* Illegal data adrress */
- {
- TransmitError(ModbusStructPtr, Error_IllegalDataAddress, &brodcastMsgRec);
- return 0;
- }
- }
W celu zapisania pojedynczego rejestru przesyłany jest adres układu slave po nim następuje adres rejestru do zapisania. Dalej wpisywany jest adres rejestru do zapisu po nim następuje wartość jaka będzie wpisana pod podany adres. Na końcu następuje kod CRC.
Teraz przejdę przez komendą zapisu wielu danych do rejestrów:
- static uint8_t writeMultipleRegisters(ModbusDataStructure *ModbusStructPtr)
- {
- if (brodcastMsgRec == 1)
- {
- return 1;
- }
- // check if the recieved number of bytes matches the calculated bytes minus the request bytes
- // id + function + (2 * address bytes) + (2 * no of register bytes) + byte count + (2 * CRC bytes) = 9 bytes
- if ((ModbusStructPtr->sendRecBuffer[6]) == (ModbusStructPtr->recDataCount - 9))
- {
- uint16_t tmpData_1 = 0;
- tmpData_1=((ModbusStructPtr->sendRecBuffer[2]<<8) + ModbusStructPtr->sendRecBuffer[3]); //Starting address
- if(tmpData_1 < HOLDING_REGISTER_SIZE) // check exception 2 ILLEGAL DATA ADDRESS
- {
- uint16_t tmpData_2 = 0;
- tmpData_2=((ModbusStructPtr->sendRecBuffer[4]<<8) + ModbusStructPtr->sendRecBuffer[5]); //Number of register bytes
- if((((tmpData_1+tmpData_2)<HOLDING_REGISTER_SIZE) && (tmpData_2<MODBUS_WORD_SIZE+1)))// check exception 3 ILLEGAL DATA VALUE
- {
- uint16_t maxDataValue = tmpData_1 + tmpData_2;
- uint8_t positionInBuffer=0;
- positionInBuffer = 7; // start at the 8th byte in the frame
- for (uint8_t index = tmpData_1; index < maxDataValue; index++)
- {
- res_table[index]=(ModbusStructPtr->sendRecBuffer[positionInBuffer]<<8) + ModbusStructPtr->sendRecBuffer[positionInBuffer]; //Register status
- positionInBuffer+=2;
- }
- ModbusStructPtr->transmitDataLength = 8; //responce length
- return 0;
- }
- else
- {
- TransmitError(ModbusStructPtr, Error_IllegalDataValue, &brodcastMsgRec);
- return 0;
- }
- }
- else
- {
- TransmitError(ModbusStructPtr, Error_IllegalDataAddress, &brodcastMsgRec);
- return 0;
- }
- }
- return 1;
- }
Tutaj po adresie oraz kodzie funkcji przesyłany jest adres startowy, zapisany na dwóch bajtach. Następnie pojawia się ilość rejestrów do wpisania, Po nich następuje ilość danych jakie mają zostać wpisane. W odpowiedzi należy przesłać adres, kod funkcji, rejestr startowy, ilość rejestrów zapisanych oraz kod CRC.
Kolejnym elementem jest komenda pozwalająca na zapis jednego wyjścia binarnego:
- static uint8_t writeSingleCoil(ModbusDataStructure *ModbusStructPtr)
- {
- uint16_t tmpData = 0;
- tmpData=((ModbusStructPtr->sendRecBuffer[2]<<8) + ModbusStructPtr->sendRecBuffer[3]);
- if(tmpData < COILS_DATA_REG_SIZE)
- {
- if(ModbusStructPtr->sendRecBuffer[4] == 0xFF && ModbusStructPtr->sendRecBuffer[5] == 0x00)
- {
- coils[tmpData] = 1;
- }
- else
- {
- coils[tmpData] = 0;
- }
- ModbusStructPtr->transmitDataLength = ModbusStructPtr->recDataCount; //responce length
- return 0;
- }
- else
- {
- TransmitError(ModbusStructPtr,Error_IllegalDataAddress, &brodcastMsgRec);
- return 0;
- }
- return 1;
- }
Wpisanie pojedynczego wyjścia polega na podaniu adresu, kodu informacji po nich następują dwa bajty danych z adresem tych informacji, po tym następuje informacja o stanie czyli czy wejście uruchomione czy wyłączone. Na samym końcu podawane jest kod CRC.
Poniżej przedstawię funkcje odczytujące status przekaźników:
- static uint8_t readCoil(ModbusDataStructure *ModbusStructPtr)
- {
- uint16_t tempData_1 = 0;
- uint16_t tempData_2 = 0;
- uint16_t positionInBuffer=0;
- uint16_t packageLength = 3;
- uint8_t temporaryData = 0;
- //Start address
- tempData_1=((ModbusStructPtr->sendRecBuffer[2]<<8) + ModbusStructPtr->sendRecBuffer[3]) + 1;
- //Number of coils to read
- tempData_2=((ModbusStructPtr->sendRecBuffer[4]<<8) + ModbusStructPtr->sendRecBuffer[5]);
- if((((tempData_1+tempData_2)<HOLDING_REGISTER_SIZE)&(tempData_2<MODBUS_WRD_SZ+1)))
- {
- for(positionInBuffer=0;positionInBuffer<tempData_2;positionInBuffer++)
- {
- for(uint8_t i = positionInBuffer; i<8; i++)
- {
- if(coils[i+tempData_1] == 1)
- {
- temporaryData = temporaryData<<1;
- temporaryData |= 1;
- }
- else
- {
- temporaryData = temporaryData<<1;
- temporaryData |= 0;
- }
- positionInBuffer++;
- }
- temporaryData = reverseByte(temporaryData);
- ModbusStructPtr->sendRecBuffer[packageLength]=temporaryData;
- packageLength+=1;
- }
- ModbusStructPtr->sendRecBuffer[2] = packageLength - 3;
- ModbusStructPtr->transmitDataLength = ModbusStructPtr->sendRecBuffer[2] + 5;
- return 0;
- }
- else
- {
- TransmitError(ModbusStructPtr,Error_IllegalDataAddress, &brodcastMsgRec);
- return 0;
- }
- return 1;
- }
Odczyt rejestrów wejściowych:
- static uint8_t readInputRegister(ModbusDataStructure *ModbusStructPtr)
- {
- uint16_t tempData_1 = 0;
- uint16_t tempData_2 = 0;
- uint16_t positionInBuffer=0;
- uint16_t packageLength = 3;
- int16_t temporaryData = 0;
- int16_t temporaryPosition = 0;
- tempData_1=((ModbusStructPtr->sendRecBuffer[2]<<8) + ModbusStructPtr->sendRecBuffer[3]);
- tempData_2=((ModbusStructPtr->sendRecBuffer[4]<<8) + ModbusStructPtr->sendRecBuffer[5]);
- if((((tempData_1+tempData_2)<HOLDING_REGISTER_SIZE) && (tempData_2<MODBUS_WRD_SZ+1)))
- {
- for(positionInBuffer=0;positionInBuffer<tempData_2;positionInBuffer++)
- {
- temporaryData=res_table[positionInBuffer+tempData_1];
- if(temporaryData<0)
- {
- temporaryPosition=temporaryData;
- ModbusStructPtr->sendRecBuffer[packageLength]=(temporaryPosition>>8)|0b10000000;
- ModbusStructPtr->sendRecBuffer[packageLength+1]=temporaryPosition;
- }
- else
- {
- ModbusStructPtr->sendRecBuffer[packageLength]=temporaryData>>8;
- ModbusStructPtr->sendRecBuffer[packageLength+1]=temporaryData;
- }
- packageLength+=2;
- }
- ModbusStructPtr->sendRecBuffer[2] = positionInBuffer*2;
- ModbusStructPtr->transmitDataLength = positionInBuffer*2+5;
- return 0;
- }
- else
- {
- TransmitError(ModbusStructPtr,Error_IllegalDataAddress, &brodcastMsgRec);
- return 0;
- }
- return 1;
- }
Odczyt rejestrów pamiętających:
- static uint8_t readHoldingRegister(ModbusDataStructure *ModbusStructPtr)
- {
- uint16_t tempData_1 = 0;
- uint16_t tempData_2 = 0;
- uint16_t positionInBuffer=0;
- uint16_t packageLength = 3;
- int16_t temporaryData = 0;
- int16_t temporaryPosition = 0;
- tempData_1=((ModbusStructPtr->sendRecBuffer[2]<<8) + ModbusStructPtr->sendRecBuffer[3]); //Starting adress bytes
- tempData_2=((ModbusStructPtr->sendRecBuffer[4]<<8) + ModbusStructPtr->sendRecBuffer[5]); //Number of register bytes
- if((((tempData_1+tempData_2)<HOLDING_REGISTER_SIZE)&(tempData_2<MODBUS_WORD_SIZE+1)))
- {
- for(positionInBuffer=0;positionInBuffer<tempData_2;positionInBuffer++)
- {
- temporaryData = res_table[positionInBuffer+tempData_1];
- if(temporaryData < 0)
- {
- temporaryPosition=temporaryData;
- ModbusStructPtr->sendRecBuffer[packageLength]=(temporaryPosition>>8)|0b10000000;
- ModbusStructPtr->sendRecBuffer[packageLength+1]=temporaryPosition;
- }
- else
- {
- ModbusStructPtr->sendRecBuffer[packageLength]=temporaryData>>8;
- ModbusStructPtr->sendRecBuffer[packageLength+1]=temporaryData;
- }
- packageLength+=2;
- }
- ModbusStructPtr->sendRecBuffer[2] = positionInBuffer*2; //byte count
- ModbusStructPtr->transmitDataLength = positionInBuffer*2+5; //responce length
- return 0;
- }
- else
- {
- TransmitError(ModbusStructPtr,Error_IllegalDataAddress, &brodcastMsgRec);
- return 0;
- }
- return 1;
- }
Funkcje odczytu przesyłają ramkę, która wygląda następująco: najpierw adres, następnie ilość odczytanych bajtów, po nim następują dane przesyłane od najniższego adresu. Na końcu wprowadza się kod CRC16.
Zdekodowanie ramki i przesłanie odpowiedzi odbywa się w jednej głównej funkcji:
- void modbusDecodeMsg(ModbusDataStructure *ModbusStructPtr)
- {
- uint16_t operationStatus = 0;
- checkIfBroadcastMsgRecSetFlag(ModbusStructPtr, &brodcastMsgRec);
- if((ModbusStructPtr->sendRecBuffer[0] == SLAVE_ID) && (ModbusStructPtr->recDataCount>5) &&
- ((ModbusStructPtr->sendRecBuffer[0]==SET_PAR[0]) || (ModbusStructPtr->sendRecBuffer[0]==255)))
- {
- operationStatus=calculateCrc16(ModbusStructPtr->sendRecBuffer,ModbusStructPtr->recDataCount-2);
- if((ModbusStructPtr->sendRecBuffer[ModbusStructPtr->recDataCount-2]==(operationStatus&0x00FF)) &&
- (ModbusStructPtr->sendRecBuffer[ModbusStructPtr->recDataCount-1]==(operationStatus>>8)))
- {
- switch(ModbusStructPtr->sendRecBuffer[1])
- {
- case READ_COILS:
- operationStatus = readCoil(ModbusStructPtr);
- break;
- case READ_HOLDING_REGISTER:
- operationStatus = readHoldingRegister(ModbusStructPtr);
- break;
- case READ_INPUT_REGISTER:
- operationStatus = readInputRegister(ModbusStructPtr);
- break;
- case WRITE_SINGLE_COILS:
- operationStatus = writeSingleCoil(ModbusStructPtr);
- break;
- case WRITE_SINGLE_REGISTER:
- operationStatus = writeSingleRegister(ModbusStructPtr);
- break;
- case WRITE_MULTIPLE_REGISTERS:
- operationStatus = writeMultipleRegisters(ModbusStructPtr);
- break;
- default:
- operationStatus = TransmitError(ModbusStructPtr, Error_IllegalFunction, &brodcastMsgRec);
- break;
- }
- if(brodcastMsgRec == 1)
- {
- brodcastMsgRec = 0;
- }
- if(operationStatus == 0)
- {
- operationStatus=calculateCrc16(ModbusStructPtr->sendRecBuffer,ModbusStructPtr->transmitDataLength-2);
- ModbusStructPtr->sendRecBuffer[ModbusStructPtr->transmitDataLength-2]=operationStatus;
- ModbusStructPtr->sendRecBuffer[ModbusStructPtr->transmitDataLength-1]=operationStatus>>8;
- ModbusStructPtr->transmitDataCount=0;
- }
- }
- }
- ModbusStructPtr->recDataGap=0;
- ModbusStructPtr->recDataCount=0;
- ModbusStructPtr->rxTimeoutTimer=0xFFFF;
- }
Pozostałe funkcje pomocnicze zostały opisane bezpośrednio w bibliotece.
Bibliotekę do postu można pobrać z dysku Google pod tym linkiem.