If you need to create a large STRING (such as STRING[1000000]) or a data object for user logging or other reasons, here are some tips.
General info
- AsBrStr library should be used because AsString library is obsolete.
- For large strings, DO NOT use starting address of the destination variable (like ADR(DestVar)) in string functions (brsstrcat, brsstrcpy). Because if the destination variable is a large string that is already filled with some data, there is always a loop with a huge number of iterations.
- For large string composition USE outputs nxt_adr of AsBrStr functions for the destination variable address. It can be used with either string functions or memory functions.
Examples and best practices
Let’s assume that LargeString is, for example, STRING[1000000] and StringToAdd is some small string that we want to add in different steps, for example, STRING[80].
- I recommend to always reset the memory first when initializing a string so that no data remains there from the past.
// Reset memory
brsmemset(ADR(LargeString), 0, SIZEOF(LargeString));
- Get the address of the string.
- Just get address.
// Get address
NextAddress := ADR(LargeString);
- Get the address of the string and directly write the initial value of the string.
// Get address and initiate value
NextAddress := brsstrcpy(ADR(LargeString), ADR(StringToAdd));
- Adding more data to the large string.
- Add string from variable.
// Add string from variable
NextAddress := brsmemcpy(NextAddress, ADR(StringToAdd), brsstrlen(ADR(StringToAdd)));
OR
NextAddress := brsstrcat(NextAddress, ADR(StringToAdd));
- Add hardcoded string. The brsmemcpy variant is a bit better because SIZEOF is evaluated during compilation, so no extra performance is needed to calculate the length of the string when the program is running. After SIZEOF there must be -1 because it returns size also with null character.
// Add hardcoded string
NextAddress := brsmemcpy(NextAddress, ADR('Hello'), SIZEOF('Hello') - 1);
OR
NextAddress := brsstrcat(NextAddress, ADR('Hello'));
- Add integer/float value. Function brsitoa returns length of its output so it must be added to the NextAddress value.
// Add integer value
NextAddress := NextAddress + brsitoa(IntToAdd, NextAddress);
// Add float value
NextAddress := NextAddress + brsftoa(RealToAdd, NextAddress);
Example on a specific use case
- Logging axis position, speed and temperature and logged user in CSV format every 100 ms. The task is in a cyclic class with a repetition of 100 ms, so you only need to log data every cycle. When the string is full, write to a file and stop logging.
Variables declaration
VAR
State : StateEnum;
FB : FBType;
Axis : AxisType;
Start : BOOL;
StartAddress : UDINT;
NextAddress : UDINT;
LogData : STRING[1000000];
LoggedUser : STRING[20] := 'John';
END_VAR
Data types
TYPE
AxisType : STRUCT
Position : REAL;
Velocity : REAL;
Temperature : REAL;
END_STRUCT;
FBType : STRUCT
FileCreate_0 : FileCreate;
FileWrite_0 : FileWrite;
FileClose_0 : FileClose;
END_STRUCT;
StateEnum :
(
stWAIT,
stLOGGING,
stFILE_CREATE,
stFILE_WRITE,
stFILE_CLOSE
);
END_TYPE
Code
PROGRAM _CYCLIC
CASE State OF
stWAIT: // Wait for commands
// Command to start logging
IF Start THEN
Start := FALSE;
// Reset memory
brsmemset(ADR(LogData), 0, SIZEOF(LogData));
// Get address
StartAddress := NextAddress := ADR(LogData);
// Initiate value (add CSV header)
NextAddress := brsstrcpy(NextAddress, ADR('Position;Velocity;Temperature;LoggedUser'));
State := stLOGGING;
END_IF
stLOGGING: // Logging
// Check if the string is already full (lets say a string is full when it has only 100 characters left)
IF ((NextAddress - StartAddress) > (SIZEOF(LogData) - 100)) THEN
State := stFILE_CREATE;
ELSE
// Add line feed
NextAddress := brsmemcpy(NextAddress, ADR('$n'), SIZEOF('$n') - 1);
// Add position
NextAddress := NextAddress + brsftoa(Axis.Position, NextAddress);
// Add delimiter
NextAddress := brsmemcpy(NextAddress, ADR(';'), SIZEOF(';') - 1);
// Add velocity
NextAddress := NextAddress + brsftoa(Axis.Velocity, NextAddress);
// Add delimiter
NextAddress := brsmemcpy(NextAddress, ADR(';'), SIZEOF(';') - 1);
// Add temperature
NextAddress := NextAddress + brsftoa(Axis.Temperature, NextAddress);
// Add delimiter
NextAddress := brsmemcpy(NextAddress, ADR(';'), SIZEOF(';') - 1);
// Add logged user
NextAddress := brsmemcpy(NextAddress, ADR(LoggedUser), brsstrlen(ADR(LoggedUser)));
END_IF
stFILE_CREATE: // Create file for further write
FB.FileCreate_0.enable := TRUE;
FB.FileCreate_0.pDevice := ADR('USER');
FB.FileCreate_0.pFile := ADR('LogData.csv');
FB.FileCreate_0();
IF (FB.FileCreate_0.status = ERR_OK) THEN
State := stFILE_WRITE;
END_IF
stFILE_WRITE: // Write data to a csv file on user partition
FB.FileWrite_0.enable := TRUE;
FB.FileWrite_0.ident := FB.FileCreate_0.ident;
FB.FileWrite_0.pSrc := StartAddress;
FB.FileWrite_0.offset := 0;
FB.FileWrite_0.len := NextAddress - StartAddress;
FB.FileWrite_0();
IF (FB.FileWrite_0.status = ERR_OK) THEN
State := stFILE_CLOSE;
END_IF
stFILE_CLOSE: // Close file
FB.FileClose_0.enable := TRUE;
FB.FileClose_0.ident := FB.FileCreate_0.ident;
FB.FileClose_0();
IF (FB.FileClose_0.status = ERR_OK) THEN
State := stWAIT;
END_IF
END_CASE
// Simulation of value change
Axis.Position := Axis.Position + 0.1;
Axis.Velocity := Axis.Velocity + 0.2;
Axis.Temperature := Axis.Temperature + 0.3;
END_PROGRAM