///*! //* \file profibus.c //* \brief Ablaufsteuerung Profibus DP-Slave Kommunikation //* \author © Joerg S. //* \date 9.2007 (Erstellung) 9.2009 (Aktueller Stand) //* \note Verwendung nur fuer private Zwecke / Only for non-commercial use //* \note Info: http://www.mikrocontroller.net/topic/106174 //* \date 2010.04.28 Porting to AVR (WinAVR), Einar S. //* \note Pins used: USART RX / TX, Portd:2 controls TX enable. //* \note Crystal: see header file. //*/ #include #include #include #include "Main.h" #include "var.h" #include "profibus.h" unsigned int uart_byte_cnt = 0; unsigned int uart_transmit_cnt = 0; // Profibus Flags und Variablen unsigned char profibus_status; unsigned char diagnose_status; unsigned char master_addr; unsigned char group; volatile unsigned char new_data; unsigned int delay_tbit= DELAY_TBIT; unsigned int delay_syn = TIMEOUT_MAX_SYN_TIME; unsigned int delay_rx = TIMEOUT_MAX_RX_TIME; unsigned int delay_sdr = TIMEOUT_MAX_SDR_TIME; #if (OUTPUT_DATA_SIZE > 0) volatile unsigned char data_out_register[OUTPUT_DATA_SIZE]; #endif #if (INPUT_DATA_SIZE > 0) unsigned char data_in_register [INPUT_DATA_SIZE]; #endif #if (VENDOR_DATA_SIZE > 0) unsigned char Vendor_Data[VENDOR_DATA_SIZE]; #endif #if (EXT_DIAG_DATA_SIZE > 0) unsigned char Diag_Data[EXT_DIAG_DATA_SIZE]; #endif unsigned char Input_Data_size; unsigned char Output_Data_size; //unsigned char Input_Data_size_Module[INPUT_MODULE_CNT]; //unsigned char Output_Data_size_Module[OUTPUT_MODULE_CNT]; unsigned char Vendor_Data_size; // Anzahl eingelesene Herstellerspezifische Bytes #define TX_IRQ /////////////////////////////////////////////////////////////////////////////////////////////////// /*! * \brief Initialize UART0 * Even parity, 1 stop bit. */ void init_UART0 (void) { UCSRB = 0x00; // disable while setting baud rate UCSRA = 0x00; UCSRA = _BV(U2X); UBRRH = 0; // set baud rate hi. UBRRL = UART_BAUD_45450; // set baud rate lo. UCSRC = _BV(URSEL) +(1< 126)) //slave_addr = DEFAULT_ADD; // Clear data #if (OUTPUT_DATA_SIZE > 0) for (cnt = 0; cnt < OUTPUT_DATA_SIZE; cnt++) { data_out_register[cnt] = 0xFF; } #endif #if (INPUT_DATA_SIZE > 0) for (cnt = 0; cnt < INPUT_DATA_SIZE; cnt++) { data_in_register[cnt] = 0x00; } #endif #if (VENDOR_DATA_SIZE > 0) for (cnt = 0; cnt < VENDOR_DATA_SIZE; cnt++) { Vendor_Data[cnt] = 0x00; } #endif #if (DIAG_DATA_SIZE > 0) for (cnt = 0; cnt < DIAG_DATA_SIZE; cnt++) { Diag_Data[cnt] = 0x00; } #endif new_data=0; // AVR Timer init RS485_RX_EN; UCSRB |= (1< DSAP 60 (Get Diagnostics Request) // 2) SSAP 62 -> DSAP 61 (Set Parameters Request) // 3) SSAP 62 -> DSAP 62 (Check Config Request) // 4) SSAP 62 -> DSAP 60 (Get Diagnostics Request) // 5) Data Exchange Request (normaler Zyklus) switch (DSAP_data) { case SAP_SET_SLAVE_ADR: // Set Slave Address (SSAP 62 -> DSAP 55) // Siehe Felser 8/2009 Kap. 4.2 // Nur im Zustand "Wait Parameter" (WPRM) moeglich slave_addr = uart_buffer[9]; //IDENT_HIGH_BYTE = uart_buffer[10]; //IDENT_LOW_BYTE = uart_buffer[11]; //if (uart_buffer[12] & 0x01) adress_aenderung_sperren = TRUE; profibus_send_CMD(SC, 0, SAP_OFFSET, &uart_buffer[0], 0); break; case SAP_GLOBAL_CONTROL: // Global Control Request (SSAP 62 -> DSAP 58) // Siehe Felser 8/2009 Kap. 4.6.2 // Wenn "Clear Data" high, dann SPS CPU auf "Stop" if (uart_buffer[9] & CLEAR_DATA_) { LED_ERROR_AN; // Status "SPS nicht bereit" } else { LED_ERROR_AUS; // Status "SPS OK" } // Gruppe berechnen for (cnt = 0; uart_buffer[10] != 0; cnt++) uart_buffer[10]>>=1; // Wenn Befehl fuer uns ist if (cnt == group) { if (uart_buffer[9] & UNFREEZE_) { // FREEZE Zustand loeschen } else if (uart_buffer[9] & UNSYNC_) { // SYNC Zustand loeschen } else if (uart_buffer[9] & FREEZE_) { // Eingaenge nicht mehr neu einlesen } else if (uart_buffer[9] & SYNC_) { // Ausgaenge nur bei SYNC Befehl setzen } } break; case SAP_SLAVE_DIAGNOSIS: // Get Diagnostics Request (SSAP 62 -> DSAP 60) // Siehe Felser 8/2009 Kap. 4.5.2 // Nach dem Erhalt der Diagnose wechselt der DP-Slave vom Zustand // "Power on Reset" (POR) in den Zustand "Wait Parameter" (WPRM) // Am Ende der Initialisierung (Zustand "Data Exchange" (DXCHG)) // sendet der Master ein zweites mal ein Diagnostics Request um die // korrekte Konfiguration zu pruefen if (function_code == (REQUEST_ + FCB_ + SRD_HIGH)) { // Erste Diagnose Anfrage //uart_buffer[4] = master_addr; // Ziel Master (mit SAP Offset) //uart_buffer[5] = slave_addr + SAP_OFFSET; // Quelle Slave (mit SAP Offset) //uart_buffer[6] = SLAVE_DATA; uart_buffer[7] = SSAP_data; // Ziel SAP Master uart_buffer[8] = DSAP_data; // Quelle SAP Slave uart_buffer[9] = STATION_NOT_READY_; // Status 1 uart_buffer[10] = STATUS_2_DEFAULT + PRM_REQ_; // Status 2 uart_buffer[11] = DIAG_SIZE_OK; // Status 3 uart_buffer[12] = MASTER_ADD_DEFAULT; // Adresse Master uart_buffer[13] = IDENT_HIGH_BYTE; // Ident high uart_buffer[14] = IDENT_LOW_BYTE; // Ident low #if (EXT_DIAG_DATA_SIZE > 0) uart_buffer[15] = EXT_DIAG_DATA_SIZE; // Geraetebezogene Diagnose (Anzahl Bytes) for (cnt = 0; cnt < EXT_DIAG_DATA_SIZE; cnt++) { uart_buffer[16+cnt] = Diag_Data[cnt]; } #endif profibus_send_CMD(SD2, DATA_LOW, SAP_OFFSET, &uart_buffer[7], 8 + EXT_DIAG_DATA_SIZE); } else if (function_code == (REQUEST_ + FCV_ + SRD_HIGH) || function_code == (REQUEST_ + FCV_ + FCB_ + SRD_HIGH)) { // Diagnose Anfrage um Daten von Check Config Request zu pruefen //uart_buffer[4] = master_addr; // Ziel Master (mit SAP Offset) //uart_buffer[5] = slave_addr + SAP_OFFSET; // Quelle Slave (mit SAP Offset) //uart_buffer[6] = SLAVE_DATA; uart_buffer[7] = SSAP_data; // Ziel SAP Master uart_buffer[8] = DSAP_data; // Quelle SAP Slave if (diagnose_status == TRUE) uart_buffer[9] = EXT_DIAG_; // Status 1 else uart_buffer[9] = 0x00; uart_buffer[10] = STATUS_2_DEFAULT; // Status 2 uart_buffer[11] = DIAG_SIZE_OK; // Status 3 uart_buffer[12] = master_addr - SAP_OFFSET; // Adresse Master uart_buffer[13] = IDENT_HIGH_BYTE; // Ident high uart_buffer[14] = IDENT_LOW_BYTE; // Ident low #if (EXT_DIAG_DATA_SIZE > 0) uart_buffer[15] = EXT_DIAG_DATA_SIZE; // Geraetebezogene Diagnose (Anzahl Bytes) for (cnt = 0; cnt < EXT_DIAG_DATA_SIZE; cnt++) { uart_buffer[16+cnt] = Diag_Data[cnt]; } #endif profibus_send_CMD(SD2, DATA_LOW, SAP_OFFSET, &uart_buffer[7], 8 + EXT_DIAG_DATA_SIZE); } break; case SAP_SET_PRM: // Set Parameters Request (SSAP 62 -> DSAP 61) // Siehe Felser 8/2009 Kap. 4.3.1 // Nach dem Erhalt der Parameter wechselt der DP-Slave vom Zustand // "Wait Parameter" (WPRM) in den Zustand "Wait Configuration" (WCFG) // Nur Daten fuer unser Geraet akzeptieren if ((uart_buffer[13] == IDENT_HIGH_BYTE) && (uart_buffer[14] == IDENT_LOW_BYTE)) { //uart_buffer[9] = Befehl //uart_buffer[10] = Watchdog 1 //uart_buffer[11] = Watchdog 2 //uart_buffer[12] = Min TSDR //uart_buffer[13] = Ident H //uart_buffer[14] = Ident L //uart_buffer[15] = Gruppe //uart_buffer[16] = User Parameter // Bei DPV1 Unterstuetzung: //uart_buffer[16] = DPV1 Status 1 //uart_buffer[17] = DPV1 Status 2 //uart_buffer[18] = DPV1 Status 3 //uart_buffer[19] = User Parameter // User Parameter groeße = Laenge - DA, SA, FC, DSAP, SSAP, 7 Parameter Bytes Vendor_Data_size = PDU_size - 12; // User Parameter einlesen #if (VENDOR_DATA_SIZE > 0) for (cnt = 0; cnt < Vendor_Data_size; cnt++) Vendor_Data[cnt] = uart_buffer[16+cnt]; #endif // Gruppe einlesen for (group = 0; uart_buffer[15] != 0; group++) uart_buffer[15]>>=1; profibus_send_CMD(SC, 0, SAP_OFFSET, &uart_buffer[0], 0); } break; case SAP_CHK_CFG: // Check Config Request (SSAP 62 -> DSAP 62) // Siehe Felser 8/2009 Kap. 4.4.1 // It did only consider the last module. Changed "=" to "+=" May 04 2010 E.S. // Nach dem Erhalt der Konfiguration wechselt der DP-Slave vom Zustand // "Wait Configuration" (WCFG) in den Zustand "Data Exchange" (DXCHG) // IO Konfiguration: // Kompaktes Format fuer max. 16/32 Byte IO // Spezielles Format fuer max. 64/132 Byte IO // Je nach PDU Datengroesse mehrere Bytes auswerten // LE/LEr - (DA+SA+FC+DSAP+SSAP) = Anzahl Config Bytes // FIXIT: module 1 Byte input + 16 Byte input yields 18 Byte returned to master! Output_Data_size=0; Input_Data_size=0; for (cnt = 0; cnt < uart_buffer[1] - 5; cnt++) { switch (uart_buffer[9+cnt] & CFG_DIRECTION_) { case CFG_INPUT: Input_Data_size += (uart_buffer[9+cnt] & CFG_BYTE_CNT_) + 1; if (uart_buffer[9+cnt] & CFG_WIDTH_ & CFG_WORD) Input_Data_size += Input_Data_size*2; break; case CFG_OUTPUT: Output_Data_size += (uart_buffer[9+cnt] & CFG_BYTE_CNT_) + 1; if (uart_buffer[9+cnt] & CFG_WIDTH_ & CFG_WORD) Output_Data_size += Output_Data_size*2; break; case CFG_INPUT_OUTPUT: Input_Data_size += (uart_buffer[9+cnt] & CFG_BYTE_CNT_) + 1; Output_Data_size += (uart_buffer[9+cnt] & CFG_BYTE_CNT_) + 1; if (uart_buffer[9+cnt] & CFG_WIDTH_ & CFG_WORD) { Input_Data_size += Input_Data_size*2; Output_Data_size += Output_Data_size*2; } break; case CFG_SPECIAL: // Spezielles Format // Herstellerspezifische Bytes vorhanden? if (uart_buffer[9+cnt] & CFG_SP_VENDOR_CNT_) { // Anzahl Herstellerdaten sichern Vendor_Data_size = uart_buffer[9+cnt] & CFG_SP_VENDOR_CNT_; // Anzahl von Gesamtanzahl abziehen uart_buffer[1] -= Vendor_Data_size; } // I/O Daten switch (uart_buffer[9+cnt] & CFG_SP_DIRECTION_) { case CFG_SP_VOID: // Leeres Datenfeld break; case CFG_SP_INPUT: Input_Data_size += (uart_buffer[10+cnt] & CFG_SP_BYTE_CNT_) + 1; if (uart_buffer[10+cnt] & CFG_WIDTH_ & CFG_WORD) Input_Data_size += Input_Data_size*2; cnt++; // Dieses Byte haben wir jetzt schon break; case CFG_SP_OUTPUT: Output_Data_size += (uart_buffer[10+cnt] & CFG_SP_BYTE_CNT_) + 1; if (uart_buffer[10+cnt] & CFG_WIDTH_ & CFG_WORD) Output_Data_size += Output_Data_size*2; cnt++; // Dieses Byte haben wir jetzt schon break; case CFG_SP_INPUT_OPTPUT: // Erst Ausgang... Output_Data_size += (uart_buffer[10+cnt] & CFG_SP_BYTE_CNT_) + 1; if (uart_buffer[10+cnt] & CFG_WIDTH_ & CFG_WORD) Output_Data_size += Output_Data_size*2; // Dann Eingang Input_Data_size += (uart_buffer[11+cnt] & CFG_SP_BYTE_CNT_) + 1; if (uart_buffer[11+cnt] & CFG_WIDTH_ & CFG_WORD) Input_Data_size += Input_Data_size*2; cnt += 2; // Diese Bytes haben wir jetzt schon break; } // Switch Ende break; default: Input_Data_size = 0; Output_Data_size = 0; break; } // Switch Ende } // For Ende if (Vendor_Data_size != 0) { // Auswerten } // Bei Fehler -> CFG_FAULT_ in Diagnose senden // Kurzquittung profibus_send_CMD(SC, 0, SAP_OFFSET, &uart_buffer[0], 0); break; default: // Unbekannter SAP break; } // Switch DSAP_data Ende } // Ziel: Slave Adresse else if (destination_add == slave_addr) { // Status Abfrage if (function_code == (REQUEST_ + FDL_STATUS)) { profibus_send_CMD(SD1, FDL_STATUS_OK, 0, &uart_buffer[0], 0); } // Master sendet Ausgangsdaten und verlangt Eingangsdaten (Send and Request Data) else if (function_code == (REQUEST_ + FCV_ + SRD_HIGH) || function_code == (REQUEST_ + FCV_ + FCB_ + SRD_HIGH)) { // Daten von Master einlesen #if (INPUT_DATA_SIZE > 0) for (cnt = 0; cnt < INPUT_DATA_SIZE; cnt++) { data_in_register[cnt] = uart_buffer[cnt + 7]; new_data=1; } #endif // Daten fuer Master in Buffer schreiben #if (OUTPUT_DATA_SIZE > 0) for (cnt = 0; cnt < OUTPUT_DATA_SIZE; cnt++) { uart_buffer[cnt + 7] = data_out_register[cnt]; } #endif #if (OUTPUT_DATA_SIZE > 0) if (diagnose_status == TRUE) profibus_send_CMD(SD2, DIAGNOSE, 0, &uart_buffer[7], 0); // Diagnose anfordern else profibus_send_CMD(SD2, DATA_LOW, 0, &uart_buffer[7], Input_Data_size); // Daten senden #else if (diagnose_status == TRUE) profibus_send_CMD(SD1, DIAGNOSE, 0, &uart_buffer[7], 0); // Diagnose anfordern else profibus_send_CMD(SC, 0, 0, &uart_buffer[7], 0); // Kurzquittung #endif } } } // Daten gueltig Ende } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /*! * \brief Profibus Telegramm zusammenstellen und senden * \param type Telegrammtyp (SD1, SD2 usw.) * \param function_code Function Code der uebermittelt werden soll * \param sap_offset Wert des SAP-Offset * \param pdu Pointer auf Datenfeld (PDU) * \param length_pdu Laenge der reinen PDU ohne DA, SA oder FC */ void profibus_send_CMD (unsigned char type, unsigned char function_code, unsigned char sap_offset, char *pdu, unsigned char length_pdu) { unsigned char length_data; switch(type) { case SD1: uart_buffer[0] = SD1; uart_buffer[1] = master_addr; uart_buffer[2] = slave_addr + sap_offset; uart_buffer[3] = function_code; uart_buffer[4] = checksum(&uart_buffer[1], 3); uart_buffer[5] = ED; length_data = 6; break; case SD2: uart_buffer[0] = SD2; uart_buffer[1] = length_pdu + 3; // Laenge der PDU inkl. DA, SA und FC uart_buffer[2] = length_pdu + 3; uart_buffer[3] = SD2; uart_buffer[4] = master_addr; uart_buffer[5] = slave_addr + sap_offset; uart_buffer[6] = function_code; // Daten werden vor Aufruf der Funktion schon aufgefuellt uart_buffer[7+length_pdu] = checksum(&uart_buffer[4], length_pdu + 3); uart_buffer[8+length_pdu] = ED; length_data = length_pdu + 9; break; case SD3: uart_buffer[0] = SD3; uart_buffer[1] = master_addr; uart_buffer[2] = slave_addr + sap_offset; uart_buffer[3] = function_code; // Daten werden vor Aufruf der Funktion schon aufgefuellt uart_buffer[9] = checksum(&uart_buffer[4], 8); uart_buffer[10] = ED; length_data = 11; break; case SD4: uart_buffer[0] = SD4; uart_buffer[1] = master_addr; uart_buffer[2] = slave_addr + sap_offset; length_data = 3; break; case SC: uart_buffer[0] = SC; length_data = 1; break; default: break; } profibus_TX(&uart_buffer[0], length_data); } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /*! * \brief Telegramm senden * \param data Pointer auf Datenfeld * \param length Laenge der Daten */ void profibus_TX (char *data, unsigned char length) { RS485_TX_EN; OCR1A = TIMEOUT_MAX_TX_TIME; profibus_status = PROFIBUS_SEND_DATA; uart_byte_cnt = length; // Anzahl zu sendender Bytes uart_transmit_cnt = 0; // Zahler fuer gesendete Bytes // IFG2 |= UCA0TXIFG; // TX Flag setzen // IE2 |= UCA0TXIE; // Enable USCI_A0 TX interrupt UCSRB |= _BV(UDRIE); // <<< AVR Version } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /*! * \brief Checksumme berechnen. Einfaches addieren aller Datenbytes im Telegramm. * \param data Pointer auf Datenfeld * \param length Laenge der Daten * \return Checksumme */ unsigned char checksum(char *data, unsigned char length) { unsigned char csum = 0; while(length--) { csum += data[length]; } return csum; } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /*! * \brief Zieladresse ueberpruefen. Adresse muss mit Slave Adresse oder Broadcast (inkl. SAP Offset) uebereinstimmen * \param destination Zieladresse * \return TRUE wenn Zieladresse unsere ist, FALSE wenn nicht */ unsigned char addmatch (unsigned char destination) { if ((destination != slave_addr) && // Slave (destination != slave_addr + SAP_OFFSET) && // SAP Slave (destination != BROADCAST_ADD) && // Broadcast (destination != BROADCAST_ADD + SAP_OFFSET)) // SAP Broadcast return FALSE; return TRUE; } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /*! * \brief ISR UART Transmit */ SIGNAL(USART_UDRE_vect) { // Alles gesendet? if (uart_transmit_cnt < uart_byte_cnt) { // TX Buffer fuellen UDR = uart_buffer[uart_transmit_cnt++]; } else { // Alles gesendet, Interrupt wieder aus // IE2 &= ~UCA0TXIE; UCSRB &=~ _BV(UDRIE); // AVR, nothing to transmit, disable this interrupt } TCNT1=0; } /////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////// /*! * \brief ISR TIMER0 */ SIGNAL (TIMER1_COMPA_vect) // Timer1 Output Compare 1A. { TIMER1_STOP; // Guard ourselves. switch (profibus_status) { case PROFIBUS_WAIT_SYN: // TSYN abgelaufen profibus_status = PROFIBUS_WAIT_DATA; OCR1A = TIMEOUT_MAX_SDR_TIME; uart_byte_cnt = 0; break; case PROFIBUS_WAIT_DATA: // TSDR abgelaufen aber keine Daten da break; case PROFIBUS_GET_DATA: // TSDR abgelaufen und Daten da profibus_status = PROFIBUS_WAIT_SYN; // We have already waited TIMEOUT_MAX_RX_TIME of bus idle. So subtract that from Tsyn. OCR1A = TIMEOUT_MAX_SYN_TIME-TIMEOUT_MAX_RX_TIME; profibus_RX(); break; case PROFIBUS_SEND_DATA: // Sende-Timeout abgelaufen, wieder auf Empfang gehen profibus_status = PROFIBUS_WAIT_SYN; OCR1A = TIMEOUT_MAX_SYN_TIME; RS485_TX_DIS; // Auf Receive umschalten <<< break; default: break; } OCR1A = 0; PORTD &=~0x04; // << 126)) { slave_addr = DEFAULT_ADD; A_Port_D ^= (1<