FUNCTION_BLOCK "FB_BarcodeReaderCognex" TITLE = FB_BarcodeReader { S7_Optimized_Access := 'TRUE' } AUTHOR : 'e.yueksel' FAMILY : BarcodeReader VERSION : 0.1 //Zusammensetzung aus FB_CameraCognex_Continuous und FB_StationScanner_WithCont //Optimierte Variante (z.B. Schrittkette zusammengefasst) // //Dieser Baustein funktioniert nur mit Cognex Readern (z.B. DM260, DM280) und dem Cognex Profinet Interface und Triggermodus: Kontinuierlich (Continuous)! VAR_INPUT stInSettings : "UDT_SettingsBarcodeReader"; xInBorner : Bool; // NIU xInStartReading : Bool; // Starte Lesevorgang xInHandshakeDone : Bool; // Lesevorgang beendet Handshake rein xInErrQuit : Bool; // Fehler rücksetzen xInReset : Bool; // Baustein zurücksetzen xInHWError : Bool; // Baugruppen Fehler END_VAR VAR_OUTPUT xOutReadingDone : Bool; // Lesevorgang abgeschlossen / Handshake raus xOutReadyForNewScan : Bool; // Reader ist bereit für Leseanforderung nOutBarcode : LInt; // Letzter gelesener Barcode sOutBarcode : String[20]; // Letzter gelesener Barcode (als Zeichenkette) wOutErrorWord : Word; // Fehlercode xOutCodeGood : Bool; // NIU xOutCodeBad : Bool; // NIU END_VAR VAR_IN_OUT stInOutInterface : "stCognexInterface"; stInOutControlUnitCabinet : "UDT_MainState"; stInOutHMI : "UDT_BarcodeReader"; END_VAR VAR xStartReading : Bool; xTrigger : Bool; xToggleBitFlg : Bool; nBarcode : LInt; sBarcodeString : String[20] := 'none'; GETIO_PART {InstructionName := 'GETIO_PART'; LibVersion := '1.2'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : GETIO_PART; SETIO_PART {InstructionName := 'SETIO_PART'; LibVersion := '1.2'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : SETIO_PART; stCtrlBitsIn : Struct D0_DeviceReady : Bool; D1_SystemReady : Bool; D2_GoodRead : Bool; D3_NoRead : Bool; D4_ExtOut1 : Bool; D5_ExtOut2 : Bool; D6_Result1 : Bool; D7_Result2 : Bool; D8_ExtIn1 : Bool; D9_ExtIn2 : Bool; D10_Sensor1 : Bool; D11_Sensor2 : Bool; D12_Res : Bool; D13_Res : Bool; D14_Res : Bool; D15_Toggle : Bool; END_STRUCT; nState : Int; nLastStep { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int; arStepHistory : Array[0..10] of Struct StepNum : Int; TimeStamp {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL; END_STRUCT; nResultID : Int; nResultCode : Int; nResultExtended : Int; "64Byte_IN_Data" { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Array[0..63] of Byte; tOnMonitoring {InstructionName := 'TON_TIME'; LibVersion := '1.0'} : TON_TIME; stHMI : "stHMI_BRCognexDM"; fbMonitoringCamRuntimeFail : "FB_Monitoring"; fbMonitoringCamGeneralFault : "FB_Monitoring"; stStatistics : Struct nReads : UDInt; nGoodRead : UDInt; nBadRead : UDInt; nGoodReadBarcodeChanged : UDInt; nBadReadBarcodeChanged : UDInt; tLastBarcodeRecv {InstructionName := 'DTL'; LibVersion := '1.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DTL; tLastNoReadRecv {InstructionName := 'DTL'; LibVersion := '1.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DTL; tLastReset {InstructionName := 'DTL'; LibVersion := '1.0'; ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : DTL; END_STRUCT; END_VAR VAR RETAIN xReInit : Bool; END_VAR VAR tPLCTime {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL; xCodeGood : Bool; xCodeBad : Bool; nBarcodeTarget : DInt; sBarcodeTarget : String; stTest : Struct nGoodRead : Int; nBadRead : Int; nGoodReadBarcodeChanged : Int; nBadReadBarcodeChanged : Int; END_STRUCT; END_VAR VAR_TEMP tReadIO_Status : DWord; tReadIO_Error : Bool; tWriteIO_Status_01 : DWord; tWriteIO_Error_01 : Bool; t_BC_iO : Bool; t_BC_niO : Bool; t_Format : Word; t_Status : Int; t_Time : Int; xTemp : Bool; nTempRetVal : Int; tTempLocalTime {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL; END_VAR VAR CONSTANT "E_DeviceState.eInit1" : Int := 0; "E_DeviceState.eInit2" : Int := 100; "E_DeviceState.eNoJob" : Int := 200; "E_DeviceState.eWaitReaderReady" : Int := 300; "E_DeviceState.eWaitTriggerAck" : Int := 400; "E_DeviceState.eGetResult" : Int := 500; "E_DeviceState.eClearResult" : Int := 600; "E_DeviceState.eOutput" : Int := 700; "E_DeviceState.eHandshake" : Int := 800; END_VAR BEGIN REGION Block Header //================================================================================= // Schönenberger Systeme GmbH / (c)Copyright 2025 //--------------------------------------------------------------------------------- // Title: FB_BarcodeReaderCognex // Comment/Function: Cognex Barcode Reader // Library/Family: BaseSystemLib/PROFINET/Devices // Author: E.Yüksel // Tested with: CPU 1515F-2 PN FW:V3.0 // Engineering: TIA Portal V20 // Restrictions: This function block ONLY works with Cognex Dataman Readers and PROFINET protocol // Currently ONLY designed for Cognex trigger mode: Continuous // Requirements: PLC (S7-1500) //--------------------------------------------------------------------------------- // Change log table: // Version | Date | Expert in charge | Changes applied //----------|------------|------------------------|-------------------------------- // 01.00.00 | 25.04.2024 | Yüksel, Molina | First released version // 02.00.00 | 20.11.2024 | Yüksel | Prepare for library integration // 03.00.00 | 31.01.2025 | Yüksel | Refactoring, no functionality changes //================================================================================= END_REGION REGION Info (* Errors: 00 Internal Error / Stepfailure 01 Results available before trigger 02 Runtime failure 03 Barcode ASCI failure 04 Res 4 05 ReadIO_Error 06 Res 6 07 Res 7 08 Res 8 09 Res 9 10 Res 10 11 Res 11 12 Res 12 13 Res 13 14 Camera not ready / General Fault 15 Modul reset *) END_REGION REGION Read local plc time // Lokale CPU Zeit auslesen #nTempRetVal := RD_LOC_T(#tTempLocalTime); END_REGION REGION INIT REGION First Scan IF "FirstScan" THEN #nState := #"E_DeviceState.eInit1"; END_IF; END_REGION REGION Reset statistics IF NOT #xReInit AND NOT "FirstScan" AND #tTempLocalTime.YEAR > 1970 THEN #stStatistics.nReads := 0; #stStatistics.nGoodRead := 0; #stStatistics.nBadRead := 0; #stStatistics.nGoodReadBarcodeChanged := 0; #stStatistics.nBadReadBarcodeChanged := 0; #stStatistics.tLastBarcodeRecv := DTL#1970-01-01-00:00:00; #stStatistics.tLastNoReadRecv := DTL#1970-01-01-00:00:00; #stStatistics.tLastReset := DTL#1970-01-01-00:00:00; #xReInit := TRUE; #stStatistics.tLastReset := #tTempLocalTime; END_IF; IF #stInOutHMI.xResetStatistics THEN #stStatistics.nReads := 0; #stStatistics.nGoodRead := 0; #stStatistics.nBadRead := 0; #stStatistics.nGoodReadBarcodeChanged := 0; #stStatistics.nBadReadBarcodeChanged := 0; #stStatistics.tLastBarcodeRecv := DTL#1970-01-01-00:00:00; #stStatistics.tLastNoReadRecv := DTL#1970-01-01-00:00:00; #stStatistics.tLastReset := DTL#1970-01-01-00:00:00; #stStatistics.tLastReset := #tTempLocalTime; #stInOutHMI.xResetStatistics := FALSE; END_IF; END_REGION END_REGION REGION ErrorQuitt IF #xInErrQuit THEN #wOutErrorWord := 0; END_IF; END_REGION REGION reset step sequence IF #xInReset THEN #nState := #"E_DeviceState.eInit1"; #wOutErrorWord := 0; #wOutErrorWord.%X15 := TRUE; END_IF; END_REGION REGION Err check camera ready #wOutErrorWord.%X14 := #stInOutInterface.#xInCamGeneralFault; END_REGION REGION stepsequence CASE #nState OF #"E_DeviceState.eInit1": REGION STEP 000 - INIT 1 #xOutReadyForNewScan := FALSE; #xCodeBad := FALSE; #xCodeGood := FALSE; #xOutReadingDone := FALSE; #stInOutInterface.xOutCamTriggerEnable := FALSE; #stInOutInterface.xOutCamTrigger := FALSE; #stInOutInterface.xOutCamResultsAck := FALSE; #nOutBarcode := #nBarcode := 0; #sOutBarcode := #sBarcodeString := ''; #wOutErrorWord := 0; // no Error #nState := #"E_DeviceState.eInit2"; //Lade aktuelle Schrittnummer END_REGION #"E_DeviceState.eInit2": REGION STEP 100 - INIT 2 #xOutReadyForNewScan := FALSE; #xCodeBad := FALSE; #xCodeGood := FALSE; #xOutReadingDone := FALSE; #stInOutInterface.xOutCamTriggerEnable := FALSE; #stInOutInterface.xOutCamTrigger := FALSE; #stInOutInterface.xOutCamResultsAck := TRUE; // Reader Ergebnisse quittieren #nOutBarcode := #nBarcode := 0; #sOutBarcode := #sBarcodeString := ''; //Transition IF NOT #stInOutInterface.xInCamResultsAvailable THEN #nState := #"E_DeviceState.eNoJob"; //Lade aktuelle Schrittnummer END_IF; END_REGION #"E_DeviceState.eNoJob": REGION STEP 200 - IDLE #xCodeBad := FALSE; #xCodeGood := FALSE; IF #stInOutInterface.xInCamResultsAvailable OR #xInErrQuit THEN #stInOutInterface.xOutCamResultsAck := TRUE; ELSE #stInOutInterface.xOutCamResultsAck := FALSE; END_IF; //#stInOutInterface.xOutCamResultsAck := #xInErrQuit; #stInOutInterface.xOutCamTriggerEnable := FALSE; #stInOutInterface.xOutCamTrigger := FALSE; #xOutReadingDone := FALSE; #nBarcode := #nOutBarcode := 0; #xOutReadyForNewScan := TRUE; //Transition IF #xInStartReading THEN // Leseanforderung #nState := #"E_DeviceState.eWaitReaderReady"; //Lade aktuelle Schrittnummer END_IF; END_REGION #"E_DeviceState.eWaitReaderReady": REGION STEP 300 - WAIT READER READY #xOutReadyForNewScan := FALSE; #stInOutInterface.xOutCamTriggerEnable := TRUE; // Freigabe an Barcode Reader #stInOutInterface.xOutCamResultsAck := FALSE; #wOutErrorWord.%X1 := #stInOutInterface.xInCamResultsAvailable; // Fehler wenn es schon Ergebnisse gibt //Transition IF #stInOutInterface.xInCamTriggerReady THEN // Barcode Reader meldet bereit #stInOutInterface.xOutCamTrigger := TRUE; // Barcode Reader triggern #nState := #"E_DeviceState.eWaitTriggerAck"; END_IF; END_REGION #"E_DeviceState.eWaitTriggerAck": REGION STEP 400 - WAIT TRIGGER ACK #xOutReadyForNewScan := FALSE; #stInOutInterface.xOutCamTrigger := TRUE; //Transition IF #stInOutInterface.xInCamTriggerAck THEN #nState := #"E_DeviceState.eGetResult"; END_IF; END_REGION #"E_DeviceState.eGetResult": REGION STEP 500 - GET RESULT // INFO: // Beim Continous-Trigger (Modus der am Barcode Reader eingestellt wird) muss das Trigger-Signal im Gegensatz zum Single-Trigger immer high bleiben. // Der Reader macht kontinuierlich Bilder und hört erst auf, wenn etwas gelesen wurde oder man den Trigger wegnimmt (z.B. nach Timeout) // #stInOutInterface.xOutCamTrigger := #xInStartReading; // Auf Ergebnis warten... IF #stInOutInterface.xInCamResultsAvailable THEN // Ergebnis ist da #stInOutInterface.xOutCamTrigger := FALSE; // Trigger wegnehmen REGION Read data #GETIO_PART(ID := #stInOutInterface.hwInCamInspectionResults, //Einlesen IN 64 Byte Daten OFFSET := 0, LEN := 64, STATUS => #tReadIO_Status, ERROR => #wOutErrorWord.%X5, INPUTS := #"64Byte_IN_Data"); Chars_TO_Strg(Chars := #"64Byte_IN_Data", pChars := 8, //Zeichen ab 8.Byte InData Cnt := 14, //10 Zeichen Strg => #sBarcodeString); END_REGION REGION Evaluation (+ optional check Ascii) // ok - ASCI Zeichenprüfung 0 = 48 ... 9 = 57 IF NOT #stInSettings.xActivateCheckASCI OR // next Step wenn ASCI Prüfung nicht aktiv (#"64Byte_IN_Data"[08] >= 48 AND //Prüfung 1. Zeichen auf Zahlen größer/gleich 0 #"64Byte_IN_Data"[08] <= 57 AND //Prüfung 1. Zeichen auf Zahlen kleiner/gleich 9 #"64Byte_IN_Data"[15] >= 48 AND //Prüfung 10. Zeichen auf Zahlen kleiner/gleich 0 #"64Byte_IN_Data"[15] <= 57) //Prüfung 10. Zeichen auf Zahlen kleiner/gleich 9 THEN REGION SUCCESS #stStatistics.tLastBarcodeRecv := #tTempLocalTime; #stStatistics.nReads += 1; #stStatistics.nGoodRead += 1; #xCodeGood := TRUE; #wOutErrorWord.%X3 := FALSE; REGION Convert to Integer STRG_VAL(IN := #sBarcodeString, //Barcode nach DWord FORMAT := w#16#0, P := 1, //Ab Zeichen 1 bis zu Ungültigem wert umwandeln // OUT => #nBarcode); END_REGION END_REGION ELSE REGION FAIL / BAD #stStatistics.nReads += 1; #stStatistics.nBadRead += 1; #stStatistics.tLastNoReadRecv := #tTempLocalTime; #wOutErrorWord.%X3 := TRUE; // Fehler ASCI Prüfung! zb NoRead von Cam #xCodeBad := TRUE; #nBarcode := -1; #sBarcodeString := 'NO READ'; // NEU END_REGION END_IF; END_REGION REGION Acknowledge & Transition //Transition #stInOutInterface.xOutCamResultsAck := TRUE; #nState := #"E_DeviceState.eClearResult"; END_REGION END_IF; END_REGION #"E_DeviceState.eClearResult": REGION STEP 600 - CLEAR RESULT #stInOutInterface.xOutCamResultsAck := TRUE; //Leseergebnis quittieren //Transition IF NOT #stInOutInterface.xInCamResultsAvailable // Auf Handshake warten THEN #nState := #"E_DeviceState.eOutput"; // Nächster Schritt END_IF; END_REGION #"E_DeviceState.eOutput": REGION STEP 700 - WRITE OUTPUT #xOutReadyForNewScan := FALSE; #stInOutInterface.xOutCamTriggerEnable := FALSE; // Freigabe wegnehmen #stInOutInterface.xOutCamTrigger := FALSE; #nOutBarcode := #nBarcode; #sOutBarcode := #sBarcodeString; #xOutReadingDone := TRUE; //Transition #nState := #"E_DeviceState.eHandshake"; END_REGION #"E_DeviceState.eHandshake": REGION STEP 800 - HANDSHAKE #xOutReadyForNewScan := FALSE; #xOutReadingDone := TRUE; #stInOutInterface.xOutCamTriggerEnable := FALSE; // Freigabe wegnehmen #stInOutInterface.xOutCamTrigger := FALSE; IF #xInHandshakeDone THEN // Handshake #nState := #"E_DeviceState.eNoJob"; END_IF; END_REGION ELSE //keine gültige Schrittnummer vorhanden #wOutErrorWord.%X0 := true; END_CASE; REGION Step history IF #nState <> #nLastStep THEN #t_Status := MOVE_BLK_VARIANT(SRC := #arStepHistory, COUNT := 10, SRC_INDEX := 0, DEST_INDEX := 1, DEST => #arStepHistory); #t_Status := RD_LOC_T(#arStepHistory[0].TimeStamp); //Zeitstempel #arStepHistory[0].StepNum := #nState; #nLastStep := #nState; END_IF; END_REGION END_REGION REGION Output #xOutCodeGood := #xCodeGood; #xOutCodeBad := #xCodeBad; END_REGION REGION Runtime monitoring IF #tOnMonitoring.Q THEN // TIMEOUT #stStatistics.nReads += 1; #stStatistics.nBadRead += 1; #stStatistics.tLastNoReadRecv := #tTempLocalTime; #xCodeBad := TRUE; #wOutErrorWord.%X2 := TRUE; // Runtime failure #nBarcode := -1; #sBarcodeString := 'NO-READ'; #nState := #"E_DeviceState.eOutput"; END_IF; END_REGION REGION HMI / Visu #stHMI.In16.%X0 := #stInOutInterface.xInCamTriggerReady; #stHMI.In16.%X1 := #stInOutInterface.xInCamTriggerAck; #stHMI.In16.%X2 := #stInOutInterface.xInCamResultsAvailable; #stHMI.In16.%X3 := #stInOutInterface.xInCamGeneralFault; #stHMI.BarcodeString := #sBarcodeString; #stHMI.Barcode := #nBarcode; #stHMI.ResultID := #nResultID; #stHMI.ErrorWord := #wOutErrorWord; IF #stInOutInterface.xInCamGeneralFault OR #xInHWError THEN // Kamera nicht bereit #stInOutHMI.bStatus := 2; // ERROR ELSE CASE #nState OF #"E_DeviceState.eInit1", #"E_DeviceState.eInit2": #stInOutHMI.bStatus := 0; // NOT_AVAILABLE #"E_DeviceState.eNoJob", #"E_DeviceState.eWaitReaderReady", #"E_DeviceState.eWaitTriggerAck", #"E_DeviceState.eGetResult", #"E_DeviceState.eClearResult", #"E_DeviceState.eOutput", #"E_DeviceState.eHandshake": #stInOutHMI.bStatus := 1; // AVAILABLE ELSE //Fallback #stInOutHMI.bStatus := 0; // NOT AVAILABLE END_CASE; END_IF; #stInOutHMI.nMode := 2; // Continuous #stInOutHMI.stBRInterface := #stHMI; #stInOutHMI.nNumberScans := #stStatistics.nReads; #stInOutHMI.nNumberNoReads := #stStatistics.nBadRead; #stInOutHMI.nNoReadPercentage := REAL_TO_USINT((UDINT_TO_REAL(#stStatistics.nBadRead) / UDINT_TO_REAL(#stStatistics.nReads)) * 100); #stInOutHMI.tLastBarcodeRecv := #stStatistics.tLastBarcodeRecv; #stInOutHMI.tLastNoReadRecv := #stStatistics.tLastNoReadRecv; #stInOutHMI.tLastReset := #stStatistics.tLastReset; END_REGION REGION Timer #tOnMonitoring(IN := #stInOutInterface.xOutCamTriggerEnable, PT := #stInSettings.tTimeout); END_REGION REGION Monitoring #fbMonitoringCamRuntimeFail.stSettings.nErrorType := 2; #fbMonitoringCamRuntimeFail.stSettings.xErrLedIfMachineIsOff := TRUE; #fbMonitoringCamRuntimeFail.stSettings.xSelfQuit := TRUE; #fbMonitoringCamRuntimeFail(sInFctnName := CONCAT_STRING(IN1:='Barcode Reader: ', IN2:= #stInOutHMI.sLocation), sInPrefix := #stInOutHMI.sUnit, sInSuffix := #stInOutHMI.sIpAdress, InOutMachineState := #stInOutControlUnitCabinet, xInQuitError := #stInOutControlUnitCabinet.xQuitError, xInSignal := NOT #tOnMonitoring.Q, sInAlarmMessage := 'Laufzeitfehler', wInTextListId_Function := 1, wInTextListId_Message := 1); #fbMonitoringCamGeneralFault.stSettings.nErrorType := 1; #fbMonitoringCamGeneralFault.stSettings.xErrLedIfMachineIsOff := TRUE; #fbMonitoringCamGeneralFault.stSettings.xSelfQuit := TRUE; #fbMonitoringCamGeneralFault(sInFctnName := CONCAT_STRING(IN1:='Barcode Reader: ', IN2:= #stInOutHMI.sLocation), sInPrefix := #stInOutHMI.sUnit, sInSuffix := #stInOutHMI.sIpAdress, InOutMachineState := #stInOutControlUnitCabinet, xInQuitError := #stInOutControlUnitCabinet.xQuitError, xInSignal := NOT #stInOutInterface.xInCamGeneralFault AND NOT #xInHWError, sInAlarmMessage := 'Barcode Reader ist nicht bereit.', wInTextListId_Function := 1, wInTextListId_Message := 1); END_REGION REGION Event Log // Pro Event wird ein Aufruf benötigt //TODO END_REGION END_FUNCTION_BLOCK