1088 lines
53 KiB
Markdown
1088 lines
53 KiB
Markdown
***
|
|
|
|
Ho creato l'FB2120 per leggere direttamente i valori inviati dai masselli UR62 o UR29 utilizzando il protocollo ADAM tramite un gateway 485 a Ethernet. Ho scelto TCP sul gateway trasparente 485-Ethernet per migliorare la resilienza dei dati. Il protocollo TCP gestisce automaticamente le ritrasmissioni in caso di perdita di un frame, mantenendo l'ordine dei dati. Questa gestione è ideale per la velocità richiesta, poiché Ethernet opera a velocità molto superiori e permette di evitare la perdita di trame in caso di collisioni.
|
|
|
|
Il sistema di riconnessione gestisce diversi timeout per ristabilire la connessione in varie situazioni. Il valore di TimeBetweenFramesMs è stato creato per misurare la velocità con cui i Masselli inviano i dati. Altri contatori monitorano diversi tipi di errori per valutare l'affidabilità del sistema.
|
|
|
|
Per potere testare il sistema ho creato un simulatore che riesce a creare via 485 attraverso il gateway le stesse protocollo ADAM e ho testato sul ADAM che ci ha prestato Masselli e funziona correttamente. Una volta collaudata la simulazione dei dati digitali ho creato un server TCP che permette simulare il funzionamento e dal PLC prendere i valori como se ci fosse il Masseli colegato. Con questo setup ho potuto simulare diversi problemi che possono accadere nella comunicazione tra il PLC e il Gateway. Quando arrivi il misuratore dorerebbe essere funzionante.
|
|
|
|
Ho utilizzato un Gateway Waveshare WIFI 485/Ethernet ([Waveshare Industrial Grade Serial Server RS232/485 to WiFi And Ethernet, Modbus Gateway, MQTT Gateway, Metal Case, Wail-Mount and Rail-Mount Support : Amazon.it: Informatica](https://www.amazon.it/dp/B0CFXX5SKS?ref=ppx_yo2ov_dt_b_fed_asin_title) ), ma in realtà si può usare qualsiasi gateway 485 a Ethernet che diventa un server TCP trasparente. Sul lato PLC, è sufficiente un'istanza di FB/DB per misuratore, utilizzando solo le FB Siemens TCON e TRCV, già impiegate per la FB1. Non è stato necessario utilizzare SFC extra o modificare la configurazione hardware, poiché TCON gestisce la comunicazione senza bisogno di creare il link con NetPro. Per semplicità, ho inserito l'IP del server direttamente nel codice per rendere la chiamata alla FB più pulita.
|
|
|
|
La FB fornisce stati per gestire problemi di comunicazione e calcola il Brix e la corrente ricevuta digitalmente. Utilizzando questo sistema, si evita la doppia conversione DAC-ADC, riducendo la perdita di risoluzione; questo include la perdita del 25% di risoluzione per il discorso che il ADAM lavora a 0..20mA e solo se utilizzano 4..20mA. La FB legge i valori digitali direttamente dall'uscita del Masselli, senza perdite, e può essere usata in parallelo al ADAM come opzione. Il flusso via 485 può essere letto sia dal ADAM che dal Gateway contemporaneamente, sfruttando la comunicazione unidirezionale.
|
|
|
|
Video da i test: [Microsoft OneDrive](https://1drv.ms/v/c/129c0e590bbc3a9e/EedcDsWnAwFKkzfMdsEloHoBJoapLe0CjJq9dpG5ch6qzw?e=UpMASB)
|
|
Link dal software S7 utlizato per Test: [Microsoft OneDrive](https://1drv.ms/u/c/129c0e590bbc3a9e/ETpiLUQCwNRLn4rQFx8ygkcBDFsQ6HRxXy7o0FGd15FBcA?e=5fld6z)
|
|
|
|
***
|
|
|
|
#### La chiamata della FB sarebbe:
|
|
|
|
![[Pasted image 20250530095053.png]]
|
|
|
|
![[Pasted image 20250530095111.png]]
|
|
# Documentazione del Blocco Funzione: MaselliTCP
|
|
|
|
---
|
|
|
|
**Versione:** 1.8 **Descrizione:** Questa FB è progettata per leggere dati da un sensore Maselli tramite una connessione TCP/IP in modo continuo e ottimizzato. Il sensore invia frame di dati con un formato fisso di 12 byte: `#XX12.345CKCR` via RS485, questi dati vengono catturati con un gateway Waveshare che funziona da server TCP trasparente. L'FB gestisce la connessione, la ricezione ottimizzata tramite **burst reading**, il parsing dei dati con validazione ID e checksum, e il calcolo del valore Brix.
|
|
|
|
## Principali Miglioramenti v1.8 verso v1.3
|
|
|
|
- **Burst Reading**: Sistema di lettura ottimizzato che riduce il carico di ciclo PLC alternando fasi di lettura intensiva e pause
|
|
- **Controllo ID Dispositivo**: Validazione opzionale dell'ID del dispositivo Adam (posizioni 1-2 del frame)
|
|
- **Validazione Checksum**: Controllo checksum configurabile per garantire l'integrità dei dati
|
|
- **Buffer Ottimizzato**: Buffer di ricezione ridotto da 128 a 23 byte per maggiore efficienza
|
|
- **Organizzazione Dati Strutturata**: Uso di strutture per configurazione, statistiche, timing e diagnostica
|
|
- **Gestione Errori Avanzata**: Sistema di codici errore con priorità (0-12, maggiore = più critico)
|
|
- **Tabelle FIFO**: Storia opzionale degli ultimi 10 valori per debug (newest=[0], oldest=[9])
|
|
|
|
## Funzionamento di Base
|
|
|
|
L'FB opera attraverso una macchina a stati principale e una macchina a stati di lettura ottimizzata:
|
|
|
|
### Stati Principali (iState):
|
|
|
|
1. **IDLE (0):** Attesa abilitazione con ritardo anti-riconnessione rapida
|
|
2. **CONNECT (1):** Preparazione richiesta connessione TCP
|
|
3. **WAIT CONNECT (2):** Tentativo di connessione con timeout configurabile
|
|
4. **READING (3):** Lettura continua con sistema burst reading ottimizzato
|
|
5. **DISCONNECT (4):** Chiusura controllata della connessione
|
|
|
|
### Stati di Lettura Ottimizzata (iReadingState):
|
|
|
|
- **ACTIVE_READING (0):** Lettura intensiva durante finestra di ricezione dati
|
|
- **COMPLETED (1):** Frame valido processato, calcolo timing per frame successivo
|
|
- **PAUSED (2):** Pausa calcolata tra frame per ridurre carico ciclo PLC
|
|
|
|
Il sistema **burst reading** ottimizza i tempi di ciclo alternando:
|
|
|
|
- Fasi di lettura intensiva quando sono attesi i dati
|
|
- Pause calcolate per ridurre il carico computazionale
|
|
- Fase di inizializzazione per sincronizzazione con il sensore
|
|
|
|
## Parametri di Ingresso (VAR_INPUT)
|
|
|
|
|Nome|Tipo|Valore Predefinito|Descrizione|
|
|
|---|---|---|---|
|
|
|`xEnable`|`BOOL`||Abilita (`TRUE`) o disabilita (`FALSE`) la comunicazione con il sensore|
|
|
|`aRemoteIP`|`ARRAY[1..4] OF BYTE`|`[10,1,33,100]`|Indirizzo IP del server Gateway Maselli|
|
|
|`iRemotePort`|`INT`|`8899`|Porta TCP del server Gateway Maselli|
|
|
|`rBrixMax`|`REAL`|`80.0`|Valore Brix massimo che corrisponde a 20mA del segnale|
|
|
|`wConnectionID`|`WORD`|`W#16#10`|ID univoco per la connessione TCP (configurato nell'hardware del PLC)|
|
|
|`xEnableChecksum`|`BOOL`|`TRUE`|**NUOVO**: Abilita validazione checksum dei frame ricevuti|
|
|
|`rFramesPerSecond`|`REAL`|`3.0`|**NUOVO**: Frame attesi per secondo (per calcolo timing ottimizzato)|
|
|
|`iConnTimeoutSec`|`INT`|`10`|**NUOVO**: Timeout connessione in secondi|
|
|
|`iDataTimeoutSec`|`INT`|`30`|**NUOVO**: Timeout ricezione dati in secondi|
|
|
|`iRetryDelaySec`|`INT`|`2`|**NUOVO**: Ritardo tra tentativi di riconnessione|
|
|
|`xEnableFifoDebug`|`BOOL`|`FALSE`|**NUOVO**: Abilita tabelle FIFO per debug (ultimi 10 valori)|
|
|
|`xDisableBurstRead`|`BOOL`|`FALSE`|**NUOVO**: Disabilita burst reading (TRCV sempre attivo)|
|
|
|`iDeviceID`|`INT`|`0`|**NUOVO**: ID dispositivo da validare (0=disabilitato, 1-99=abilitato)|
|
|
|
|
## Parametri di Uscita (VAR_OUTPUT)
|
|
|
|
|Nome|Tipo|Descrizione|
|
|
|---|---|---|
|
|
|`xConnected`|`BOOL`|Indica lo stato della connessione TCP|
|
|
|`xDataValid`|`BOOL`|Impulso quando un nuovo set di dati validi è stato ricevuto e parsato|
|
|
|`xError`|`BOOL`|`TRUE` se si è verificato un errore|
|
|
|`wErrorCode`|`WORD`|Codice di errore legacy (per compatibilità)|
|
|
|`iErrorCode`|`INT`|**NUOVO**: Codice errore strutturato (0=OK, maggiore=più critico)|
|
|
|`rCurrentMA`|`REAL`|Valore di corrente in milliampere letto dal sensore|
|
|
|`rBrixValue`|`REAL`|Valore Brix calcolato|
|
|
|`xInitPhaseFinish`|`BOOL`|**NUOVO**: Flag completamento fase inizializzazione|
|
|
|`diTimeBetweenFramesMs`|`DINT`|Tempo trascorso tra gli ultimi due frame validi|
|
|
|`diReadingState`|`INT`|**NUOVO**: Stato corrente del sistema di lettura (0=Attivo, 1=Completato, 2=Pausa)|
|
|
|
|
## Strutture Dati Organizzate
|
|
|
|
### Configurazione (stConfig)
|
|
|
|
Parametri di configurazione consolidati per migliore organizzazione dei dati del DB.
|
|
|
|
### Statistiche (stStats)
|
|
|
|
- `diFrameCount`: Frame totali ricevuti
|
|
- `diErrorFrameCount`: Frame con errori di formato
|
|
- `diSuccessfulReconCount`: Riconnessioni riuscite
|
|
- `diChecksumErrorCount`: **NUOVO**: Errori checksum
|
|
- `diLostFramesCount`: **NUOVO**: Frame persi stimati
|
|
- `iConsecutiveBadFrames`: **NUOVO**: Frame consecutivi errati
|
|
|
|
### Timing (stTiming)
|
|
|
|
- Calcolo automatico intervalli frame basato su `rFramesPerSecond`
|
|
- Ottimizzazione dinamica del timing di lettura
|
|
- Rilevamento jitter e variazioni timing
|
|
|
|
### Diagnostica (stDiag)
|
|
|
|
Stati e informazioni di debug consolidate per facilitare la manutenzione.
|
|
|
|
## Codici Errore Strutturati (iErrorCode)
|
|
|
|
**Priorità crescente (0 = nessun errore, 12 = più critico):**
|
|
|
|
|Codice|Descrizione|
|
|
|---|---|
|
|
|0|Nessun errore|
|
|
|1|Errore checksum|
|
|
|2|Formato frame non valido|
|
|
|3|Caratteri non numerici|
|
|
|4|Dati fuori range (4-20mA)|
|
|
|5|Frame errati consecutivi multipli|
|
|
|6|Frame persi rilevati|
|
|
|7|Buffer ricezione TRCV troppo piccolo|
|
|
|8|Errore comunicazione TRCV|
|
|
|9|Timeout ricezione dati|
|
|
|10|Timeout connessione|
|
|
|11|Connessione TCP fallita|
|
|
|12|Connessione TCP terminata|
|
|
|
|
## Tabelle FIFO (Debug Opzionale)
|
|
|
|
Quando `xEnableFifoDebug = TRUE`:
|
|
|
|
- `arBrixHistory[0..9]`: Ultimi 10 valori Brix ([0]=più recente, [9]=più vecchio)
|
|
- `auiTimeHistory[0..9]`: Ultimi 10 timestamp ([0]=più recente, [9]=più vecchio)
|
|
|
|
## Note Aggiuntive
|
|
|
|
---
|
|
|
|
- **Buffer Ottimizzato**: Buffer di ricezione ridotto da 128 a 23 byte per maggiore efficienza memoria
|
|
- **Burst Reading**: Riduce significativamente il carico di ciclo PLC alternando lettura intensiva e pause calcolate
|
|
- **Validazione ID**: Controllo opzionale dell'ID dispositivo Adam nelle posizioni 1-2 del frame
|
|
- **Checksum**: Validazione hex checksum configurabile per garantire integrità dati
|
|
- **Timing Adattivo**: Calcolo automatico degli intervalli di lettura basato sulla frequenza frame configurata
|
|
- **Rilevamento Frame Persi**: Stima automatica dei frame persi basata su analisi timing
|
|
- **Fase Inizializzazione**: Gestione ottimizzata della sincronizzazione iniziale con il sensore
|
|
- **Reset Contatori**: I contatori si resettano automaticamente per prevenire overflow
|
|
|
|
### Source:
|
|
***
|
|
```pascal
|
|
//******************************************************************************
|
|
// Function Block: FB_MaselliTCP
|
|
// Description: Enhanced Maselli sensor TCP reader with optimized buffer and FIFO data
|
|
// Frame format: #XX12.345CKCR (12 fixed bytes)
|
|
//
|
|
// | Field | Start (1 char) | Adam address (2 chars) | Brix or Temperature value in mA (XX.YYY: 6 chars) | Checksum (2 chars) | Carriage return (1 char) |
|
|
// | :-----: | :------------: | :--------------------: | :-----------------------------------------------: | :----------------: | :----------------------: |
|
|
// | Example | # | 01 | 02.145 | CkCk | Cr |
|
|
//
|
|
// Version: 1.8 - Enhanced with structured data and optimized buffer management
|
|
// Platform: Siemens S7-300
|
|
//
|
|
// IMPROVEMENTS v1.8:
|
|
// - Structured data organization for better DB readability
|
|
// - Optimized buffer size (23 bytes instead of 128)
|
|
// - Enhanced reading state machine with initialization phase
|
|
// - Ordered FIFO tables for last 10 readings (newest at [0], oldest at [9])
|
|
// - Optional FIFO debug mode (disabled by default)
|
|
// - Improved synchronization logic with xInitPhaseFinish flag
|
|
// - Limited diAccumCycleMs accumulation only during iState = 3
|
|
//
|
|
// READING STATES:
|
|
// 0 = ACTIVE_READING - Receiving data burst, read intensively
|
|
// 1 = COMPLETED - Finished reading a valid frame, return to paused
|
|
// 2 = PAUSED - Not reading, waiting for next frame window
|
|
//
|
|
// FIFO TABLES (when xEnableFifoDebug = TRUE):
|
|
// arBrixHistory[0] = newest Brix value, arBrixHistory[9] = oldest
|
|
// auiTimeHistory[0] = newest timestamp, auiTimeHistory[9] = oldest
|
|
//
|
|
// ERROR CODES (iErrorCode) - Higher numbers = Higher priority:
|
|
// 0 = No error, 1 = Checksum error, 2 = Invalid frame format
|
|
// 3 = Invalid digit characters, 4 = Data out of range (4-20mA)
|
|
// 5 = Multiple consecutive bad frames, 6 = Lost frames detected
|
|
// 7 = TRCV receive buffer too small, 8 = TRCV communication error
|
|
// 9 = Data reception timeout, 10 = Connection timeout
|
|
// 11 = TCP connection failed, 12 = TCP connection terminated
|
|
//******************************************************************************
|
|
|
|
FUNCTION_BLOCK MaselliTCP
|
|
TITLE = 'Maselli TCP Reader - Enhanced Buffer Management'
|
|
VERSION : '1.8'
|
|
|
|
VAR_INPUT
|
|
xEnable : BOOL; // Enable communication
|
|
aRemoteIP : ARRAY[1..4] OF BYTE := [10,1,33,100]; // Remote server IP
|
|
iRemotePort : INT := 8899; // Server port
|
|
rBrixMax : REAL := 80.0; // Maximum Brix value at 20mA
|
|
wConnectionID : WORD := W#16#10; // TCP Connection ID
|
|
xEnableChecksum : BOOL := TRUE; // Enable checksum validation
|
|
rFramesPerSecond : REAL := 3.0; // Expected frames per second
|
|
iConnTimeoutSec : INT := 10; // Connection timeout in seconds
|
|
iDataTimeoutSec : INT := 30; // Data reception timeout in seconds
|
|
iRetryDelaySec : INT := 2; // Delay between reconnection attempts
|
|
xEnableFifoDebug : BOOL := FALSE; // Enable FIFO tables update (debug only)
|
|
xDisableBurstRead : BOOL := FALSE; // Disable burst reading (TRCV always active in reading state if TRUE)
|
|
iDeviceID : INT := 0; // Device ID to validate (0 = disabled, 1-99 = enabled). Checks aFrame[1] and aFrame[2].
|
|
END_VAR
|
|
|
|
VAR_OUTPUT
|
|
xConnected : BOOL; // Connection status
|
|
xDataValid : BOOL; // Pulse when valid data is available
|
|
xError : BOOL; // Active error
|
|
wErrorCode : WORD; // Legacy error code (for compatibility)
|
|
iErrorCode : INT; // Structured error code (0=OK, higher=more critical)
|
|
rCurrentMA : REAL; // Current mA value
|
|
rBrixValue : REAL; // Current Brix value
|
|
xInitPhaseFinish : BOOL; // Initialization phase completed flag
|
|
|
|
// Current frame timing
|
|
diTimeBetweenFramesMs : DINT; // Time elapsed between last two valid frames
|
|
diReadingState : INT; // Current reading state for diagnostics
|
|
END_VAR
|
|
|
|
// ============================================================================
|
|
// CONTROL VARIABLES
|
|
// ============================================================================
|
|
|
|
VAR // State Machine Control
|
|
iState : INT := 0; // Main state: 0=Idle, 1=Connect, 2=WaitConnect, 3=Reading, 4=Disconnect
|
|
iReadingState : INT := 0; // Reading state: 0=Active, 1=Completed, 2=Paused
|
|
iCurrentErrorCode : INT := 0; // Current error being evaluated
|
|
diAccumCycleMs : DINT := 0; // Accumulated cycle time (only in iState=3)
|
|
iConsecutiveGoodFrames : INT := 0; // Counter for consecutive structurally good frames
|
|
xFirstScan : BOOL := TRUE;
|
|
diInitPhaseTimerMs : DINT := 0; // Timer for initialization phase duration
|
|
END_VAR
|
|
|
|
VAR // Timing Control
|
|
tCurrentTime : TIME; // Current system time
|
|
tLastTime : TIME; // Previous time
|
|
diElapsedMs : DINT; // Elapsed time in ms
|
|
xTrcvTimeToCall : BOOL := TRUE; // TRCV call control
|
|
END_VAR
|
|
|
|
VAR // FIFO Tables - Last 10 readings (ordered: [0]=newest, [9]=oldest)
|
|
arBrixHistory : ARRAY[0..9] OF REAL; // Last 10 Brix values (newest first)
|
|
auiTimeHistory : ARRAY[0..9] OF INT; // Last 10 timestamps in ms (newest first)
|
|
END_VAR
|
|
|
|
// ============================================================================
|
|
// STRUCTURED DATA ORGANIZATION
|
|
// ============================================================================
|
|
|
|
VAR // Configuration Structure
|
|
stConfig : STRUCT
|
|
rFramesPerSecond : REAL := 3.0; // Expected frames per second
|
|
iConnTimeoutSec : INT := 10; // Connection timeout
|
|
iDataTimeoutSec : INT := 30; // Data timeout
|
|
iRetryDelaySec : INT := 2; // Retry delay
|
|
rBrixMax : REAL := 80.0; // Maximum Brix at 20mA
|
|
xEnableChecksum : BOOL := TRUE; // Checksum validation
|
|
iDeviceID : INT := 0; // Device ID to validate
|
|
xDisableBurstRead : BOOL; // Disable burst reading
|
|
END_STRUCT;
|
|
END_VAR
|
|
|
|
VAR // Statistics Structure
|
|
stStats : STRUCT
|
|
diFrameCount : DINT; // Total received frames
|
|
diErrorFrameCount : DINT; // Frames with format errors
|
|
diSuccessfulReconCount : DINT; // Successful reconnections
|
|
diChecksumErrorCount : DINT; // Checksum errors
|
|
diLostFramesCount : DINT; // Estimated lost frames
|
|
iConsecutiveBadFrames : INT; // Consecutive bad frames counter
|
|
END_STRUCT;
|
|
END_VAR
|
|
|
|
VAR // Timing Structure
|
|
stTiming : STRUCT
|
|
diFrameIntervalMs : DINT := 333; // Auto-calculated frame interval
|
|
iPreReadOffsetMs : INT := 50; // Pre-read offset
|
|
iMaxActiveReadingMs : INT := 200; // Max active reading time
|
|
diEstFrameMs : DINT := 1000; // Estimated frame interval
|
|
diNextFrameMs : DINT := 0; // Next expected frame time
|
|
diReadingStartMs : DINT := 0; // Reading start time
|
|
diMinFrameIntervalMs : DINT := DINT#999999; // Minimum observed interval
|
|
diMaxFrameIntervalMs : DINT := 0; // Maximum observed interval
|
|
diFrameJitterMs : DINT := 0; // Frame jitter
|
|
END_STRUCT;
|
|
END_VAR
|
|
|
|
VAR // Diagnostics Structure
|
|
stDiag : STRUCT
|
|
diState : INT; // Current state for debug
|
|
diTconStatus : WORD; // TCON status
|
|
diTrcvStatus : WORD; // TRCV status
|
|
diNextExpectedMs : DINT; // Next expected frame time
|
|
diCalcFrameIntervalMs : DINT; // Calculated frame interval
|
|
iCalcPreReadOffsetMs : INT; // Calculated pre-read offset
|
|
iCalcMaxActiveMs : INT; // Calculated max active time
|
|
rAvgCurrentMA : REAL; // Moving average of current
|
|
END_STRUCT;
|
|
END_VAR
|
|
|
|
// ============================================================================
|
|
// CONNECTION PARAMETERS
|
|
// ============================================================================
|
|
|
|
VAR // TCP Connection Parameters
|
|
stConnParams : STRUCT
|
|
block_length : WORD := W#16#40;
|
|
id : WORD;
|
|
connection_type : BYTE := B#16#11; // TCP
|
|
active_est : BOOL := TRUE; // Active client
|
|
local_device_id : BYTE := B#16#2; // Integrated PN
|
|
local_tsap_id_len : BYTE := B#16#0;
|
|
rem_subnet_id_len : BYTE := B#16#0;
|
|
rem_staddr_len : BYTE := B#16#4;
|
|
rem_tsap_id_len : BYTE := B#16#2;
|
|
next_staddr_len : BYTE := B#16#0;
|
|
local_tsap_id : ARRAY[1..16] OF BYTE;
|
|
rem_subnet_id : ARRAY[1..6] OF BYTE;
|
|
rem_staddr : ARRAY[1..6] OF BYTE;
|
|
rem_tsap_id : ARRAY[1..16] OF BYTE;
|
|
next_staddr : ARRAY[1..6] OF BYTE;
|
|
spare : WORD := W#16#0;
|
|
END_STRUCT;
|
|
END_VAR
|
|
|
|
// ============================================================================
|
|
// FUNCTION BLOCK INSTANCES AND INTERNAL DATA (Bottom of DB)
|
|
// ============================================================================
|
|
|
|
VAR // Frame Processing
|
|
i : INT;
|
|
j : INT;
|
|
iStartPos : INT;
|
|
aFrame : ARRAY[0..11] OF BYTE; // Current frame
|
|
iChecksumSum : INT;
|
|
bCalculatedChecksum : BYTE;
|
|
bReceivedChecksum : BYTE;
|
|
END_VAR
|
|
|
|
VAR // Connection Control
|
|
xTdisconReq : BOOL;
|
|
xTconReq : BOOL;
|
|
xTconReqOld : BOOL;
|
|
xTconPulse : BOOL;
|
|
iCyclesToWait : INT;
|
|
END_VAR
|
|
|
|
VAR // Timer Instances
|
|
tonConnTimeout : TON; // Connection timeout
|
|
tonRetryDelay : TON; // Retry delay
|
|
tonNoDataTimeout: TON; // No data timeout
|
|
tonDisconTimeout: TON; // Disconnect timeout
|
|
END_VAR
|
|
|
|
VAR // Communication Block Instances
|
|
TCON_DB : TCON; // TCP connection
|
|
TRCV_DB : TRCV; // TCP receive
|
|
TDISCON_DB : TDISCON; // TCP disconnect
|
|
END_VAR
|
|
|
|
VAR // Optimized Reception Buffer (23 bytes instead of 128)
|
|
aRxBuffer : ARRAY[0..22] OF BYTE; // Optimized buffer size
|
|
END_VAR
|
|
|
|
VAR // Buffer Management Structure
|
|
stBuffer : STRUCT
|
|
aCircBuffer : ARRAY[0..255] OF BYTE; // Circular buffer
|
|
iWritePtr : INT := 0; // Write pointer
|
|
iReadPtr : INT := 0; // Read pointer
|
|
iDataInBuffer : INT := 0; // Data in buffer
|
|
iRxLength : INT; // Last received length
|
|
END_STRUCT;
|
|
END_VAR
|
|
|
|
|
|
VAR_TEMP
|
|
tempByte : BYTE;
|
|
tempInt : INT;
|
|
tempIntValue : INT;
|
|
tempWord : WORD;
|
|
tempReal : REAL;
|
|
tempTime : TIME;
|
|
iAvailable : INT;
|
|
iBytesToRead : INT;
|
|
tempActualIntervalMs : DINT; // Stores actual interval for current frame processing cycle
|
|
tempReceivedID_Char1 : BYTE;
|
|
tempDiffDint : DINT; // Temporary DINT for offset adjustment calculation
|
|
tempReceivedID_Char2 : BYTE;
|
|
tempReceivedID_Val : INT;
|
|
END_VAR
|
|
|
|
BEGIN
|
|
|
|
// --- Read system time ---
|
|
tCurrentTime := TIME_TCK();
|
|
|
|
// --- Initialization on first scan ---
|
|
IF xFirstScan THEN
|
|
xFirstScan := FALSE;
|
|
tLastTime := tCurrentTime;
|
|
|
|
// Initialize configuration from inputs
|
|
stConfig.rFramesPerSecond := rFramesPerSecond;
|
|
stConfig.iConnTimeoutSec := iConnTimeoutSec;
|
|
stConfig.iDataTimeoutSec := iDataTimeoutSec;
|
|
stConfig.iRetryDelaySec := iRetryDelaySec;
|
|
stConfig.rBrixMax := rBrixMax;
|
|
stConfig.xEnableChecksum := xEnableChecksum;
|
|
stConfig.iDeviceID := iDeviceID;
|
|
stConfig.xDisableBurstRead := xDisableBurstRead;
|
|
|
|
// Clear connection parameters arrays
|
|
FOR i := 1 TO 16 DO
|
|
stConnParams.local_tsap_id[i] := B#16#00;
|
|
stConnParams.rem_tsap_id[i] := B#16#00;
|
|
END_FOR;
|
|
FOR i := 1 TO 6 DO
|
|
stConnParams.rem_subnet_id[i] := B#16#00;
|
|
stConnParams.rem_staddr[i] := B#16#00;
|
|
stConnParams.next_staddr[i] := B#16#00;
|
|
END_FOR;
|
|
|
|
// Configure connection parameters
|
|
stConnParams.id := wConnectionID;
|
|
stConnParams.rem_staddr[1] := aRemoteIP[1];
|
|
stConnParams.rem_staddr[2] := aRemoteIP[2];
|
|
stConnParams.rem_staddr[3] := aRemoteIP[3];
|
|
stConnParams.rem_staddr[4] := aRemoteIP[4];
|
|
|
|
// Remote port configuration
|
|
tempWord := INT_TO_WORD(iRemotePort);
|
|
stConnParams.rem_tsap_id[1] := WORD_TO_BYTE(SHR(IN:= tempWord, N:= 8));
|
|
stConnParams.rem_tsap_id[2] := WORD_TO_BYTE(tempWord AND W#16#FF);
|
|
|
|
// Reset all control variables
|
|
xTconReq := FALSE;
|
|
xTconReqOld := FALSE;
|
|
iCyclesToWait := 0;
|
|
stBuffer.iWritePtr := 0;
|
|
stBuffer.iReadPtr := 0;
|
|
stBuffer.iDataInBuffer := 0;
|
|
|
|
// Reset statistics
|
|
stStats.diFrameCount := 0;
|
|
stStats.diErrorFrameCount := 0;
|
|
stStats.diSuccessfulReconCount := 0;
|
|
stStats.diChecksumErrorCount := 0;
|
|
stStats.diLostFramesCount := 0;
|
|
stStats.iConsecutiveBadFrames := 0;
|
|
|
|
// Initialize timing parameters
|
|
IF stConfig.rFramesPerSecond > 0.1 THEN
|
|
stTiming.diFrameIntervalMs := REAL_TO_DINT(1000.0 / stConfig.rFramesPerSecond);
|
|
stTiming.iPreReadOffsetMs := DINT_TO_INT(stTiming.diFrameIntervalMs / 6);
|
|
stTiming.iMaxActiveReadingMs := DINT_TO_INT(stTiming.diFrameIntervalMs / 3);
|
|
IF stTiming.iPreReadOffsetMs < 20 THEN stTiming.iPreReadOffsetMs := 20; END_IF;
|
|
IF stTiming.iMaxActiveReadingMs < 50 THEN stTiming.iMaxActiveReadingMs := 50; END_IF;
|
|
ELSE
|
|
stTiming.diFrameIntervalMs := 1000;
|
|
stTiming.iPreReadOffsetMs := 50;
|
|
stTiming.iMaxActiveReadingMs := 200;
|
|
END_IF;
|
|
|
|
stTiming.diEstFrameMs := stTiming.diFrameIntervalMs;
|
|
stTiming.diNextFrameMs := 0;
|
|
stTiming.diReadingStartMs := 0;
|
|
|
|
// Initialize FIFO tables (ordered arrays)
|
|
FOR i := 0 TO 9 DO
|
|
arBrixHistory[i] := 0.0;
|
|
auiTimeHistory[i] := 0;
|
|
END_FOR;
|
|
|
|
// Initialize other variables
|
|
diTimeBetweenFramesMs := 0;
|
|
diAccumCycleMs := 0;
|
|
iReadingState := 0; // Start in ACTIVE_READING
|
|
xTrcvTimeToCall := TRUE;
|
|
iCurrentErrorCode := 0;
|
|
iConsecutiveGoodFrames := 0;
|
|
diInitPhaseTimerMs := 0;
|
|
xInitPhaseFinish := FALSE;
|
|
END_IF;
|
|
|
|
// Calculate elapsed time since last call
|
|
IF tCurrentTime >= tLastTime THEN
|
|
diElapsedMs := TIME_TO_DINT(tCurrentTime - tLastTime);
|
|
if diElapsedMs > 1000 THEN
|
|
diElapsedMs := 1000; // Cap at 1 second
|
|
END_IF;
|
|
ELSE
|
|
diElapsedMs := 1; // Handle wrap-around
|
|
END_IF;
|
|
tLastTime := tCurrentTime;
|
|
|
|
// --- Diagnostic outputs ---
|
|
stDiag.diState := iState;
|
|
stDiag.diTconStatus := TCON_DB.STATUS;
|
|
stDiag.diTrcvStatus := TRCV_DB.STATUS;
|
|
diReadingState := iReadingState;
|
|
stDiag.diNextExpectedMs := stTiming.diNextFrameMs;
|
|
stDiag.diCalcFrameIntervalMs := stTiming.diFrameIntervalMs;
|
|
stDiag.iCalcPreReadOffsetMs := stTiming.iPreReadOffsetMs;
|
|
stDiag.iCalcMaxActiveMs := stTiming.iMaxActiveReadingMs;
|
|
|
|
// --- Error code evaluation (higher number = higher priority) ---
|
|
iCurrentErrorCode := 0;
|
|
|
|
// Priority 1: Checksum errors
|
|
IF stStats.diChecksumErrorCount > 0 AND diAccumCycleMs < 5000 THEN
|
|
iCurrentErrorCode := 1;
|
|
END_IF;
|
|
|
|
// Priority 2: Frame format errors
|
|
IF stStats.diErrorFrameCount > 0 AND diAccumCycleMs < 5000 THEN
|
|
iCurrentErrorCode := 2;
|
|
END_IF;
|
|
|
|
// Priority 4: Data out of range
|
|
IF rCurrentMA > 0.0 AND (rCurrentMA < 4.0 OR rCurrentMA > 20.0) AND
|
|
diAccumCycleMs < 2000 THEN
|
|
iCurrentErrorCode := 4;
|
|
END_IF;
|
|
|
|
// Priority 5: Multiple consecutive bad frames
|
|
IF stStats.iConsecutiveBadFrames >= 5 THEN
|
|
iCurrentErrorCode := 5;
|
|
END_IF;
|
|
|
|
// Priority 6: Lost frames
|
|
IF stStats.diLostFramesCount > 0 AND stTiming.diEstFrameMs > 0 AND
|
|
diAccumCycleMs > (stTiming.diEstFrameMs * 2) THEN
|
|
iCurrentErrorCode := 6;
|
|
END_IF;
|
|
|
|
// Priority 7-8: TRCV errors
|
|
IF TRCV_DB.ERROR THEN
|
|
IF TRCV_DB.STATUS = W#16#8088 THEN
|
|
iCurrentErrorCode := 7;
|
|
ELSE
|
|
iCurrentErrorCode := 8;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
// Priority 9: Data timeout
|
|
IF tonNoDataTimeout.Q THEN
|
|
iCurrentErrorCode := 9;
|
|
END_IF;
|
|
|
|
// Priority 10: Connection timeout
|
|
IF tonConnTimeout.Q THEN
|
|
iCurrentErrorCode := 10;
|
|
END_IF;
|
|
|
|
// Priority 11: Connection failed
|
|
IF TCON_DB.ERROR THEN
|
|
iCurrentErrorCode := 11;
|
|
END_IF;
|
|
|
|
// Priority 12: Connection terminated
|
|
IF (TRCV_DB.STATUS = W#16#80A3) OR (TRCV_DB.STATUS = W#16#80C4) THEN
|
|
iCurrentErrorCode := 12;
|
|
END_IF;
|
|
|
|
// Set final error outputs
|
|
iErrorCode := iCurrentErrorCode;
|
|
xError := (iCurrentErrorCode > 0);
|
|
|
|
// --- Reset pulse outputs ---
|
|
xDataValid := FALSE;
|
|
|
|
// --- Pulse control for TCON ---
|
|
xTconPulse := xTconReq AND NOT xTconReqOld;
|
|
xTconReqOld := xTconReq;
|
|
|
|
// Enhanced Reading State Machine with Initialization Phase
|
|
IF iState <> 3 THEN // Only run reading states when in READING main state
|
|
iReadingState := 0; // Reset reading state
|
|
xInitPhaseFinish := FALSE; // Reset initialization phase finish
|
|
diAccumCycleMs := 0; // Reset cycle accumulator
|
|
diInitPhaseTimerMs := 0; // Reset init phase timer
|
|
iConsecutiveGoodFrames := 0; // Reset counter when not in active reading cycle
|
|
stTiming.diEstFrameMs := stTiming.diFrameIntervalMs;
|
|
ELSE // iState = 3: READING
|
|
|
|
IF stStats.iConsecutiveBadFrames <= 5 THEN
|
|
diAccumCycleMs := diAccumCycleMs + diElapsedMs;
|
|
END_IF;
|
|
|
|
IF NOT xInitPhaseFinish THEN
|
|
diInitPhaseTimerMs := diInitPhaseTimerMs + diElapsedMs;
|
|
END_IF;
|
|
|
|
CASE iReadingState OF
|
|
0: // ACTIVE_READING - Receiving data burst, read intensively
|
|
xTrcvTimeToCall := TRUE; // Read every cycle
|
|
|
|
// During initialization phase, skip PAUSED state
|
|
IF xInitPhaseFinish THEN
|
|
// Normal operation - can transition to PAUSED
|
|
IF (stTiming.diNextFrameMs - diAccumCycleMs) > INT_TO_DINT(stTiming.iMaxActiveReadingMs) THEN
|
|
iReadingState := 2; // Go to PAUSED
|
|
xTrcvTimeToCall := FALSE;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
1: // COMPLETED - Finished reading a valid frame
|
|
xTrcvTimeToCall := FALSE;
|
|
|
|
// Capture the actual time elapsed for this frame cycle before resetting diAccumCycleMs
|
|
tempActualIntervalMs := diAccumCycleMs;
|
|
|
|
// --- Time Between Frames Calculation ---
|
|
// Only update diTimeBetweenFramesMs if the current and previous frames were good.
|
|
IF iConsecutiveGoodFrames >= 2 THEN
|
|
diTimeBetweenFramesMs := tempActualIntervalMs;
|
|
END_IF;
|
|
// Always reset diAccumCycleMs for the next interval measurement.
|
|
diAccumCycleMs := 0;
|
|
|
|
// Update estimated frame interval (stTiming.diEstFrameMs)
|
|
IF NOT xInitPhaseFinish THEN
|
|
// During initialization, if we have a fresh reliable interval, use it directly.
|
|
IF iConsecutiveGoodFrames >= 2 AND diTimeBetweenFramesMs > 0 THEN
|
|
stTiming.diEstFrameMs := diTimeBetweenFramesMs;
|
|
END_IF;
|
|
// ELSE stTiming.diEstFrameMs remains its initial value (from config)
|
|
ELSE
|
|
// After initialization, smooth the estimate if diTimeBetweenFramesMs is valid.
|
|
IF diTimeBetweenFramesMs > 0 THEN
|
|
stTiming.diEstFrameMs := (stTiming.diEstFrameMs * 3 + diTimeBetweenFramesMs) / 4; // Smoothing
|
|
ELSE
|
|
// If diTimeBetweenFramesMs is not positive (e.g. never set, or problem), revert to default.
|
|
stTiming.diEstFrameMs := stTiming.diFrameIntervalMs;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
// Set next expected frame time
|
|
stTiming.diNextFrameMs := stTiming.diEstFrameMs;
|
|
|
|
// --- Dynamically adjust iPreReadOffsetMs (User Optimization) ---
|
|
// Adjust only after initialization and with stable timing data.
|
|
// Limits for iPreReadOffsetMs: 5ms to 50ms.
|
|
IF xInitPhaseFinish AND (iConsecutiveGoodFrames >= 2) THEN
|
|
// tempDiffDint = Estimated Interval for Next Frame - Actual Interval of Last Frame
|
|
tempDiffDint := stTiming.diNextFrameMs - diTimeBetweenFramesMs;
|
|
|
|
IF tempDiffDint < INT_TO_DINT(stTiming.iPreReadOffsetMs) THEN
|
|
IF stTiming.iPreReadOffsetMs > 5 THEN
|
|
stTiming.iPreReadOffsetMs := stTiming.iPreReadOffsetMs - 1;
|
|
END_IF;
|
|
ELSIF tempDiffDint > INT_TO_DINT(stTiming.iPreReadOffsetMs) THEN
|
|
IF stTiming.iPreReadOffsetMs < 50 THEN
|
|
stTiming.iPreReadOffsetMs := stTiming.iPreReadOffsetMs + 1;
|
|
END_IF;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
// Detect lost frames based on the *actual* interval for the just-completed frame cycle
|
|
// compared to the current estimate of frame interval.
|
|
IF stTiming.diEstFrameMs > 0 AND tempActualIntervalMs > (stTiming.diEstFrameMs * 3 / 2) THEN
|
|
tempInt := DINT_TO_INT(tempActualIntervalMs / stTiming.diEstFrameMs) - 1;
|
|
IF tempInt > 0 THEN
|
|
stStats.diLostFramesCount := stStats.diLostFramesCount + INT_TO_DINT(tempInt);
|
|
END_IF;
|
|
END_IF;
|
|
iReadingState := 2;
|
|
|
|
2: // PAUSED - Not reading, waiting for next frame window
|
|
// Ensure iConsecutiveGoodFrames is reset if we are paused for a long time or before starting a new read attempt.
|
|
// This is mainly handled by the iState <> 3 check, but can be reinforced if needed.
|
|
IF NOT xInitPhaseFinish OR stConfig.xDisableBurstRead THEN
|
|
iReadingState := 0;
|
|
ELSE // Only used when xInitPhaseFinish = TRUE
|
|
IF (stTiming.diNextFrameMs - diAccumCycleMs) <= INT_TO_DINT(stTiming.iPreReadOffsetMs) THEN
|
|
iReadingState := 0; // Go to ACTIVE_READING
|
|
stTiming.diReadingStartMs := diAccumCycleMs;
|
|
ELSE
|
|
xTrcvTimeToCall := FALSE;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
END_CASE;
|
|
END_IF;
|
|
|
|
// --- Main State Machine ---
|
|
CASE iState OF
|
|
0: // IDLE
|
|
xConnected := FALSE;
|
|
wErrorCode := W#16#0000;
|
|
|
|
// Retry delay
|
|
tempTime := DINT_TO_TIME(INT_TO_DINT(stConfig.iRetryDelaySec) * 1000);
|
|
tonRetryDelay(IN := TRUE, PT := tempTime);
|
|
|
|
IF xEnable AND tonRetryDelay.Q THEN
|
|
tonRetryDelay(IN := FALSE);
|
|
|
|
// Intentar desconectar primero por seguridad
|
|
xTdisconReq := TRUE;
|
|
TDISCON_DB(REQ := xTdisconReq, ID := wConnectionID);
|
|
|
|
// Resetear variables de control
|
|
xTconReq := FALSE;
|
|
xTconReqOld := FALSE;
|
|
iCyclesToWait := 0;
|
|
stBuffer.iWritePtr := 0;
|
|
stBuffer.iReadPtr := 0;
|
|
stBuffer.iDataInBuffer := 0;
|
|
iState := 1;
|
|
ELSIF NOT xEnable THEN
|
|
tonRetryDelay(IN := FALSE);
|
|
xTdisconReq := FALSE;
|
|
TDISCON_DB(REQ := FALSE, ID := wConnectionID);
|
|
END_IF;
|
|
|
|
1: // CONNECT - Prepare connection
|
|
iCyclesToWait := iCyclesToWait + 1;
|
|
IF iCyclesToWait >= 2 THEN
|
|
iCyclesToWait := 0;
|
|
xTconReq := TRUE;
|
|
xTconReqOld := FALSE; // ← AGREGAR ESTA LÍNEA
|
|
iState := 2;
|
|
END_IF;
|
|
|
|
2: // WAIT CONNECT - Wait for connection
|
|
TCON_DB(
|
|
REQ := xTconReq, // xTconPulse,
|
|
ID := wConnectionID,
|
|
CONNECT := stConnParams
|
|
);
|
|
|
|
tempTime := DINT_TO_TIME(INT_TO_DINT(stConfig.iConnTimeoutSec) * 1000);
|
|
tonConnTimeout(IN := TRUE, PT := tempTime);
|
|
tonNoDataTimeout(IN := FALSE);
|
|
|
|
IF TCON_DB.DONE THEN
|
|
xTconReq := FALSE;
|
|
xConnected := TRUE;
|
|
tonConnTimeout(IN := FALSE);
|
|
stStats.diSuccessfulReconCount := stStats.diSuccessfulReconCount + 1;
|
|
|
|
// Start initialization phase
|
|
diAccumCycleMs := 0; // Reset cycle accumulator when entering reading state
|
|
iReadingState := 0; // Start in ACTIVE_READING
|
|
diInitPhaseTimerMs := 0; // Reset init phase timer
|
|
stTiming.diReadingStartMs := 0;
|
|
|
|
iState := 3; // Go to continuous reading
|
|
ELSIF TCON_DB.ERROR THEN
|
|
IF TCON_DB.STATUS = W#16#80A3 THEN
|
|
// Conexión ya existe - ir a disconnect primero
|
|
xTconReq := FALSE;
|
|
xTconReqOld := FALSE;
|
|
iState := 4;
|
|
ELSE
|
|
xTconReq := FALSE;
|
|
xTconReqOld := FALSE;
|
|
wErrorCode := TCON_DB.STATUS;
|
|
tonConnTimeout(IN := FALSE);
|
|
iState := 4;
|
|
END_IF;
|
|
ELSIF tonConnTimeout.Q THEN
|
|
xTconReq := FALSE;
|
|
xTconReqOld := FALSE;
|
|
wErrorCode := W#16#8001;
|
|
tonConnTimeout(IN := FALSE);
|
|
iState := 4;
|
|
END_IF;
|
|
|
|
IF NOT xEnable THEN
|
|
xTconReq := FALSE;
|
|
tonConnTimeout(IN := FALSE);
|
|
iState := 4;
|
|
END_IF;
|
|
|
|
3: // READING - Enhanced reading with initialization phase
|
|
|
|
// Call TRCV based on reading state
|
|
IF xTrcvTimeToCall THEN
|
|
TRCV_DB(
|
|
EN_R := TRUE,
|
|
ID := wConnectionID,
|
|
LEN := 0, // Ad-hoc mode
|
|
DATA := aRxBuffer // Optimized 23-byte buffer
|
|
);
|
|
ELSE
|
|
TRCV_DB(
|
|
EN_R := FALSE,
|
|
ID := wConnectionID,
|
|
LEN := 0,
|
|
DATA := aRxBuffer
|
|
);
|
|
END_IF;
|
|
|
|
// Process TRCV result
|
|
IF TRCV_DB.NDR THEN
|
|
stBuffer.iRxLength := TRCV_DB.RCVD_LEN;
|
|
tonNoDataTimeout(IN := FALSE);
|
|
|
|
// Copy data to circular buffer
|
|
FOR i := 0 TO (stBuffer.iRxLength - 1) DO
|
|
stBuffer.aCircBuffer[stBuffer.iWritePtr] := aRxBuffer[i];
|
|
stBuffer.iWritePtr := (stBuffer.iWritePtr + 1) MOD 256;
|
|
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer + 1;
|
|
IF stBuffer.iDataInBuffer > 256 THEN
|
|
stBuffer.iDataInBuffer := 256;
|
|
stBuffer.iReadPtr := (stBuffer.iReadPtr + 1) MOD 256;
|
|
END_IF;
|
|
END_FOR;
|
|
|
|
// --- Process frames in circular buffer ---
|
|
WHILE stBuffer.iDataInBuffer >= 12 DO
|
|
// Find first '#'
|
|
iStartPos := -1;
|
|
FOR j := 0 TO (stBuffer.iDataInBuffer - 1) DO
|
|
tempInt := (stBuffer.iReadPtr + j) MOD 256;
|
|
IF stBuffer.aCircBuffer[tempInt] = B#16#23 THEN
|
|
iStartPos := j;
|
|
EXIT;
|
|
END_IF;
|
|
END_FOR;
|
|
|
|
IF iStartPos >= 0 THEN
|
|
// Discard bytes before '#'
|
|
IF iStartPos > 0 THEN
|
|
stBuffer.iReadPtr := (stBuffer.iReadPtr + iStartPos) MOD 256;
|
|
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - iStartPos;
|
|
END_IF;
|
|
|
|
IF stBuffer.iDataInBuffer >= 12 THEN
|
|
// Check format: '.' at position 5
|
|
IF stBuffer.aCircBuffer[(stBuffer.iReadPtr + 5) MOD 256] = B#16#2E THEN
|
|
// Copy frame
|
|
FOR i := 0 TO 11 DO
|
|
aFrame[i] := stBuffer.aCircBuffer[(stBuffer.iReadPtr + i) MOD 256];
|
|
END_FOR;
|
|
|
|
// --- Validate Device ID (if enabled) ---
|
|
// Adam address is in aFrame[1] and aFrame[2]
|
|
IF stConfig.iDeviceID > 0 AND stConfig.iDeviceID <= 99 THEN
|
|
tempReceivedID_Char1 := aFrame[1];
|
|
tempReceivedID_Char2 := aFrame[2];
|
|
|
|
// Check if ID characters are digits
|
|
IF (BYTE_TO_INT(tempReceivedID_Char1) >= 16#30 AND BYTE_TO_INT(tempReceivedID_Char1) <= 16#39) AND
|
|
(BYTE_TO_INT(tempReceivedID_Char2) >= 16#30 AND BYTE_TO_INT(tempReceivedID_Char2) <= 16#39) THEN
|
|
|
|
tempReceivedID_Val := (BYTE_TO_INT(tempReceivedID_Char1) - 16#30) * 10 +
|
|
(BYTE_TO_INT(tempReceivedID_Char2) - 16#30);
|
|
|
|
IF tempReceivedID_Val <> stConfig.iDeviceID THEN
|
|
// ID Mismatch
|
|
stStats.diErrorFrameCount := stStats.diErrorFrameCount + 1;
|
|
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
|
|
iConsecutiveGoodFrames := 0;
|
|
stBuffer.iReadPtr := (stBuffer.iReadPtr + 12) MOD 256;
|
|
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - 12;
|
|
CONTINUE;
|
|
END_IF;
|
|
ELSE
|
|
// Invalid characters for ID in frame
|
|
stStats.diErrorFrameCount := stStats.diErrorFrameCount + 1;
|
|
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
|
|
iConsecutiveGoodFrames := 0;
|
|
stBuffer.iReadPtr := (stBuffer.iReadPtr + 12) MOD 256;
|
|
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - 12;
|
|
CONTINUE;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
// Validate digit characters
|
|
IF (BYTE_TO_INT(aFrame[3]) >= 16#30 AND BYTE_TO_INT(aFrame[3]) <= 16#39) AND
|
|
(BYTE_TO_INT(aFrame[4]) >= 16#30 AND BYTE_TO_INT(aFrame[4]) <= 16#39) AND
|
|
(BYTE_TO_INT(aFrame[6]) >= 16#30 AND BYTE_TO_INT(aFrame[6]) <= 16#39) AND
|
|
(BYTE_TO_INT(aFrame[7]) >= 16#30 AND BYTE_TO_INT(aFrame[7]) <= 16#39) AND
|
|
(BYTE_TO_INT(aFrame[8]) >= 16#30 AND BYTE_TO_INT(aFrame[8]) <= 16#39) THEN
|
|
|
|
// Checksum validation
|
|
IF stConfig.xEnableChecksum THEN
|
|
iChecksumSum := 0;
|
|
FOR i := 0 TO 8 DO
|
|
iChecksumSum := iChecksumSum + BYTE_TO_INT(aFrame[i]);
|
|
END_FOR;
|
|
bCalculatedChecksum := INT_TO_BYTE(iChecksumSum MOD 256);
|
|
|
|
// Extract received checksum
|
|
tempInt := BYTE_TO_INT(aFrame[9]);
|
|
IF tempInt >= 16#30 AND tempInt <= 16#39 THEN
|
|
tempInt := tempInt - 16#30;
|
|
ELSIF tempInt >= 16#41 AND tempInt <= 16#46 THEN
|
|
tempInt := tempInt - 16#37;
|
|
ELSIF tempInt >= 16#61 AND tempInt <= 16#66 THEN
|
|
tempInt := tempInt - 16#57;
|
|
ELSE
|
|
tempInt := 0;
|
|
END_IF;
|
|
bReceivedChecksum := INT_TO_BYTE(tempInt * 16);
|
|
|
|
tempInt := BYTE_TO_INT(aFrame[10]);
|
|
IF tempInt >= 16#30 AND tempInt <= 16#39 THEN
|
|
tempInt := tempInt - 16#30;
|
|
ELSIF tempInt >= 16#41 AND tempInt <= 16#46 THEN
|
|
tempInt := tempInt - 16#37;
|
|
ELSIF tempInt >= 16#61 AND tempInt <= 16#66 THEN
|
|
tempInt := tempInt - 16#57;
|
|
ELSE
|
|
tempInt := 0;
|
|
END_IF;
|
|
bReceivedChecksum := bReceivedChecksum OR INT_TO_BYTE(tempInt);
|
|
|
|
IF bCalculatedChecksum <> bReceivedChecksum THEN
|
|
stStats.diChecksumErrorCount := stStats.diChecksumErrorCount + 1;
|
|
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
|
|
iConsecutiveGoodFrames := 0; // Reset consecutive good frames counter
|
|
stBuffer.iReadPtr := (stBuffer.iReadPtr + 12) MOD 256;
|
|
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - 12;
|
|
CONTINUE;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
// Parse valid frame
|
|
// At this point, frame is structurally good (format, digits, checksum OK or disabled)
|
|
iConsecutiveGoodFrames := iConsecutiveGoodFrames + 1;
|
|
IF iConsecutiveGoodFrames > 2 THEN // Cap at 2 for "last two frames" logic
|
|
iConsecutiveGoodFrames := 2;
|
|
END_IF;
|
|
stStats.iConsecutiveBadFrames := 0; // Reset consecutive bad *structural* frames counter
|
|
tempIntValue := 0;
|
|
tempInt := BYTE_TO_INT(aFrame[3]);
|
|
tempIntValue := (tempInt - 16#30) * 10;
|
|
tempInt := BYTE_TO_INT(aFrame[4]);
|
|
tempIntValue := tempIntValue + (tempInt - 16#30);
|
|
rCurrentMA := INT_TO_REAL(tempIntValue);
|
|
|
|
tempInt := BYTE_TO_INT(aFrame[6]);
|
|
rCurrentMA := rCurrentMA + INT_TO_REAL(tempInt - 16#30) / 10.0;
|
|
tempInt := BYTE_TO_INT(aFrame[7]);
|
|
rCurrentMA := rCurrentMA + INT_TO_REAL(tempInt - 16#30) / 100.0;
|
|
tempInt := BYTE_TO_INT(aFrame[8]);
|
|
rCurrentMA := rCurrentMA + INT_TO_REAL(tempInt - 16#30) / 1000.0;
|
|
|
|
// Calculate Brix
|
|
IF (rCurrentMA >= 4.0) AND (rCurrentMA <= 20.0) THEN
|
|
rBrixValue := (rCurrentMA - 4.0) * stConfig.rBrixMax / 16.0;
|
|
|
|
// *** UPDATE FIFO TABLES (if enabled) ***
|
|
IF xEnableFifoDebug THEN
|
|
// Shift all elements up (newest at [0], oldest discarded at [9])
|
|
FOR tempInt := 9 TO 1 BY -1 DO
|
|
arBrixHistory[tempInt] := arBrixHistory[tempInt - 1];
|
|
auiTimeHistory[tempInt] := auiTimeHistory[tempInt - 1];
|
|
END_FOR;
|
|
// Insert new values at [0]
|
|
arBrixHistory[0] := rBrixValue;
|
|
auiTimeHistory[0] := DINT_TO_INT(diAccumCycleMs MOD 32767); // Convert to UINT
|
|
END_IF;
|
|
|
|
// Set data valid pulse
|
|
xDataValid := TRUE;
|
|
|
|
// Check if initialization phase should finish
|
|
IF NOT xInitPhaseFinish AND diInitPhaseTimerMs > (stTiming.diFrameIntervalMs * 2) THEN
|
|
IF stBuffer.iRxLength = 12 THEN
|
|
xInitPhaseFinish := TRUE; // Synchronized and clean buffer
|
|
END_IF;
|
|
END_IF;
|
|
|
|
// Update moving average
|
|
IF stDiag.rAvgCurrentMA = 0.0 THEN
|
|
stDiag.rAvgCurrentMA := rCurrentMA;
|
|
ELSE
|
|
stDiag.rAvgCurrentMA := stDiag.rAvgCurrentMA * 0.9 + rCurrentMA * 0.1;
|
|
END_IF;
|
|
|
|
// stStats.iConsecutiveBadFrames already reset as frame is structurally good
|
|
END_IF;
|
|
|
|
// Frame processing complete - transition to COMPLETED
|
|
iReadingState := 1;
|
|
|
|
// Increment frame counter
|
|
stStats.diFrameCount := stStats.diFrameCount + 1;
|
|
IF stStats.diFrameCount >= DINT#2000000000 THEN
|
|
stStats.diFrameCount := 0;
|
|
END_IF;
|
|
|
|
ELSE // Invalid digits
|
|
stStats.diErrorFrameCount := stStats.diErrorFrameCount + 1;
|
|
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
|
|
iConsecutiveGoodFrames := 0; // Reset consecutive good frames counter
|
|
END_IF;
|
|
|
|
ELSE // Invalid format (no '.' at position 5)
|
|
stStats.diErrorFrameCount := stStats.diErrorFrameCount + 1;
|
|
stStats.iConsecutiveBadFrames := stStats.iConsecutiveBadFrames + 1;
|
|
iConsecutiveGoodFrames := 0; // Reset consecutive good frames counter
|
|
END_IF;
|
|
|
|
// Discard processed frame
|
|
stBuffer.iReadPtr := (stBuffer.iReadPtr + 12) MOD 256;
|
|
stBuffer.iDataInBuffer := stBuffer.iDataInBuffer - 12;
|
|
|
|
ELSE // Not enough bytes for full frame
|
|
EXIT;
|
|
END_IF;
|
|
|
|
ELSE // No '#' found
|
|
stBuffer.iReadPtr := (stBuffer.iReadPtr + stBuffer.iDataInBuffer) MOD 256;
|
|
stBuffer.iDataInBuffer := 0;
|
|
EXIT;
|
|
END_IF;
|
|
END_WHILE;
|
|
|
|
ELSIF TRCV_DB.ERROR THEN
|
|
IF (TRCV_DB.STATUS = W#16#80A3) OR (TRCV_DB.STATUS = W#16#80C4) THEN
|
|
xConnected := FALSE;
|
|
TCON_DB(REQ := FALSE, ID := wConnectionID, CONNECT := stConnParams);
|
|
TRCV_DB(EN_R := FALSE, ID := wConnectionID, LEN := 0, DATA := aRxBuffer);
|
|
IF xTdisconReq THEN
|
|
xTdisconReq := FALSE;
|
|
TDISCON_DB(REQ := FALSE, ID := wConnectionID);
|
|
END_IF;
|
|
tonNoDataTimeout(IN := FALSE);
|
|
iState := 0;
|
|
ELSIF TRCV_DB.STATUS = W#16#8088 THEN
|
|
tonNoDataTimeout(IN := FALSE);
|
|
ELSE
|
|
wErrorCode := TRCV_DB.STATUS;
|
|
xConnected := FALSE;
|
|
iState := 4;
|
|
END_IF;
|
|
END_IF;
|
|
|
|
// Data timeout check - limited by diAccumCycleMs max = iDataTimeoutSec
|
|
tempTime := DINT_TO_TIME(INT_TO_DINT(stConfig.iDataTimeoutSec) * 1000);
|
|
tonNoDataTimeout(IN := TRUE, PT := tempTime);
|
|
IF tonNoDataTimeout.Q THEN
|
|
wErrorCode := W#16#8002;
|
|
iState := 4;
|
|
END_IF;
|
|
|
|
IF NOT xEnable THEN
|
|
iState := 4;
|
|
END_IF;
|
|
|
|
4: // DISCONNECT
|
|
xTdisconReq := TRUE;
|
|
TDISCON_DB(
|
|
REQ := xTdisconReq,
|
|
ID := wConnectionID
|
|
);
|
|
|
|
tonDisconTimeout(IN := xTdisconReq, PT := T#2S);
|
|
|
|
IF TDISCON_DB.DONE OR TDISCON_DB.ERROR OR tonDisconTimeout.Q THEN
|
|
xTdisconReq := FALSE;
|
|
TDISCON_DB(REQ := xTdisconReq, ID := wConnectionID);
|
|
xConnected := FALSE;
|
|
tonDisconTimeout(IN := FALSE);
|
|
TCON_DB(REQ := FALSE, ID := wConnectionID, CONNECT := stConnParams);
|
|
TRCV_DB(EN_R := FALSE, ID := wConnectionID, LEN := 0, DATA := aRxBuffer);
|
|
iState := 0;
|
|
END_IF;
|
|
|
|
END_CASE;
|
|
|
|
END_FUNCTION_BLOCK
|
|
|
|
``` |