# How to log data to CSV on CC100 SD Card

**URL:** <https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328>\
**Category:** CODESYS\
**Tags:** cc100, ipc, csv, datalog, files, libraries, pfc\
**Created:** [October 2, 2025, 1:03am UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328 "2025-10-02T01:03:49Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![WagoKurt](https://sea2.discourse-cdn.com/flex016/user_avatar/www.wago.community/wagokurt/32/11_2.png) [@WagoKurt](https://www.wago.community/u/WagoKurt)\
**Post date:** [October 2, 2025, 1:03am UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/1 "2025-10-02T01:03:49Z")

</div>

This simple example makes use of Coesys 3.5 Libraries, SysFile, SysTypes and SysTimeRtc to datalog every second to a CSV file on an SD Card. This will create a new file when the xRunning variable becomes TRUE. The format of the file used is: Recipe\_Name\_YYYY-MM-DD\_HH-MM-SS.csv

```auto
PROGRAM Datalog
VAR
   xRunning : BOOL;
   tTimer : TON; // Timer for 1-second interval
   hFile : RTS_IEC_HANDLE; // File handle
   sFileName : STRING(255); // Dynamic file path
   bFileOpen : BOOL := FALSE; //File open status
   bWriteHeader : BOOL := TRUE; //Flag to write CSV header
   bSetFileName : BOOL := TRUE; //Flag to set filename on xRunning rising edge
   dtTimestamp : DATE_AND_TIME; //RTC timestamp
   sBuffer : STRING(1024); //Buffer for CSV line
   iResultOpen : DINT; //RTS_IEC_RESULT
   iResultWrite : DINT; //RTS_IEC_RESULT
   iResultClose : DINT; //RTS_IEC_RESULT

   // Timestamp variables
   diRtcSec : DWORD; 
   Year : STRING;
   Month : STRING;
   Day : STRING;
   Hour : STRING;
   Minute : STRING;
   Second : STRING; 
   dtStr : STRING(30);
   pos : INT;

   // Example Process Variables 
   sRecipe_Name : STRING := 'MyTest';
   wRPM : WORD := 250; 
   rDegreesF : REAL := 23.4; 
   rPressurePSI : REAL := 100.4;  

   // Install SysFile (3.5.17.0+), SysTypes (3.5.17.0+) & SysTimeRtc (3.5.17.0+) 
   // in Library Manager. 
   // Tested on CC100 FW 28 Codesys 3.5.17.0 on Oct 1 2025
END_VAR

```

```auto
(* Initialize timer *)
tTimer(IN := xRunning AND NOT tTimer.Q , PT := T#1S);

(* Detect rising edge of xRunning to set filename *)
IF xRunning AND bSetFileName THEN
   IF sRecipe_Name <> '' THEN 		
      sFileName := CONCAT('/media/sd/', sRecipe_Name); 	
      sFileName := CONCAT(sFileName, '_'); 
   ELSE 		
      sFileName := CONCAT('/media/sd/', 'NoRecipe_'); 
   END_IF  

   diRtcSec := SysTimeRtcGet(pResult := Result); 
   dtTimestamp := UDINT_TO_DT(diRtcSec); 
   dtStr := DT_TO_STRING(dtTimestamp); // Convert DT to string 
   pos := FIND(dtStr, '#') + 1; // Position after 'DT#': 4 
   Year := MID(dtStr,4, pos); // 'DT#2025-10-01-16:48:00' 
   Month := MID(dtStr,2, pos + 5); // '10' 
   Day := MID(dtStr,2, pos + 8); // '01' 
   pos := FIND(dtStr, '-') + 1;// Extract (HH:MM:SS part, after the '-') 
   Hour := MID(dtStr,2, pos); // '16' 
   Minute := MID(dtStr,2, pos + 3); // '48' 
   Second := MID(dtStr,2, pos + 6); // '00'  	
   sFileName := CONCAT(sFileName, Year); 	
   sFileName := CONCAT(sFileName, '-'); 	
   sFileName := CONCAT(sFileName, Month); 	
   sFileName := CONCAT(sFileName, '-'); 	
   sFileName := CONCAT(sFileName, Day); 	
   sFileName := CONCAT(sFileName, '_'); 	
   sFileName := CONCAT(sFileName, Hour); 	
   sFileName := CONCAT(sFileName, '-'); 	
   sFileName := CONCAT(sFileName, Minute); 	
   sFileName := CONCAT(sFileName, '-'); 	
   sFileName := CONCAT(sFileName, Second); 	
   sFileName := CONCAT(sFileName, '.csv');  
   // Format filename: /media/sd/RecipeName_YYYY-MM-DD_HH-MM-SS.csv 	
   bSetFileName := FALSE; // Prevent resetting filename 
END_IF

(* Reset filename flag when xRunning goes FALSE *)
IF NOT xRunning THEN
   bSetFileName := TRUE;
   bWriteHeader := TRUE;
END_IF

IF xRunning AND tTimer.Q THEN

   (* Open file if not already open *)
   IF NOT bFileOpen THEN
      hFile := SysFileOpen(sFileName, 1, ADR(iResultOpen));
      IF iResultOpen = 0 THEN
         bFileOpen := TRUE;
      END_IF
   END_IF

   IF bFileOpen THEN     

      (* Write header if needed *)     
      IF bWriteHeader THEN         	
         sBuffer := 'Timestamp,RPM,Degrees,Pressure'; // EDIT THIS SECTION
         sBuffer := CONCAT(sBuffer, '$R$L'); // CR LF         
         SysFileWrite(hFile, ADR(sBuffer), LEN(sBuffer), ADR(iResultWrite));         
         bWriteHeader := FALSE;     
      END_IF         

      (* Format data row *)     
      sBuffer := CONCAT(DT_TO_STRING(dtTimestamp), ','); // EDIT THIS SECTION  
      sBuffer := CONCAT(sBuffer, WORD_TO_STRING(wRPM)); 
      sBuffer := CONCAT(sBuffer, ',');     
      sBuffer := CONCAT(sBuffer, REAL_TO_STRING(rDegreesF)); 
      sBuffer := CONCAT(sBuffer, ',');     
      sBuffer := CONCAT(sBuffer, REAL_TO_STRING(rPressurePSI)); 
      sBuffer := CONCAT(sBuffer, '$R$L'); // CR LF          

      (* Write to file *)     
      SysFileWrite(hFile, ADR(sBuffer), LEN(sBuffer), ADR(iResultWrite)); 
   END_IF 
END_IF 

(* Close file when not running )
IF NOT xRunning AND bFileOpen THEN
   SysFileClose(hFile);
   bFileOpen := FALSE;
   bWriteHeader := TRUE; // Reset header flag for next file open 
END_IF 

```

---

<div class="post-metadata">

**Author:** ![JF777](https://avatars.discourse-cdn.com/v4/letter/j/6de8d8/32.png) [@JF777](https://www.wago.community/u/JF777)\
**Post date:** [October 28, 2025, 2:10pm UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/2 "2025-10-28T14:10:05Z")

</div>

I wanted to test this program on my PFC 200. But I get an error message on this line:

diRtcSec := SysTimeRtcGet(pResult := Result);

Apparently Result is not defined. What needs to be done?

---

<div class="post-metadata">

**Author:** ![Thruser](https://avatars.discourse-cdn.com/v4/letter/t/d26b3c/32.png) [@Thruser](https://www.wago.community/u/Thruser)\
**Post date:** [October 29, 2025, 7:49am UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/3 "2025-10-29T07:49:49Z")

</div>

Hi,

Not tested, but try:

add to declaration part

```auto
iResultTime : DINT; //RTS_IEC_RESULT

```

change to in programm part

```auto
diRtcSec := SysTimeRtcGet(pResult := adr(iResultTime));

```

But also check WagoAppDatalogger library. There is also an example available.

---

<div class="post-metadata">

**Author:** ![JF777](https://avatars.discourse-cdn.com/v4/letter/j/6de8d8/32.png) [@JF777](https://www.wago.community/u/JF777)\
**Post date:** [October 29, 2025, 10:51am UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/4 "2025-10-29T10:51:46Z")

</div>

I don`t fully understand what this command is doing? I have changed according to your instructions, but got the following error:  
_VAR\_IN\_OUT- beziehungsweise REFERENCE-Parameter ‘pResult’ von ‘SysTimeRtcGet’ benötigt eine Variable mit Schreibzugriff als Eingang_

---

<div class="post-metadata">

**Author:** ![Thruser](https://avatars.discourse-cdn.com/v4/letter/t/d26b3c/32.png) [@Thruser](https://www.wago.community/u/Thruser)\
**Post date:** [October 29, 2025, 12:26pm UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/5 "2025-10-29T12:26:05Z")

</div>

OK, pResult is differently handled here.than in the sysfile function.

```auto
udiResultTime : RTS_IEC_RESULT;

```

```auto
diRtcSec := SysTimeRtcGet(pResult := udiResultTime);

```

---

<div class="post-metadata">

**Author:** ![damienexail](https://avatars.discourse-cdn.com/v4/letter/d/b2d939/32.png) [@damienexail](https://www.wago.community/u/damienexail)\
**Post date:** [July 29, 2026, 8:54am UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/6 "2026-07-29T08:54:55Z")

</div>

> [@WagoKurt](#):
>
> ```auto
> ADR(sBuffer), LEN(sBuffer), ADR(iResultWrite));         
> bWriteHeader := FALSE;     
> END_IF         
> 
> (* Format data row *)     
> sBuffer := CONCAT(DT_TO_STRING(dtTimestamp), ','); // EDIT THIS SECTION  
> sBuffer := CONCAT(sBuffer, WORD_TO_STRING(wRPM)); 
> sBuffer := CONCAT(sBuf
> 
> ```

I like a lot to use the PLC as a datalogger.

But CSV is quite heavy =\> 123;45.7458 means 12 octets.

Is it possible to generate directly a binary file that translate the word 123 and the real 45.7458 to 6 octets only?

---

<div class="post-metadata">

**Author:** ![WagoIcard](https://sea2.discourse-cdn.com/flex016/user_avatar/www.wago.community/wagoicard/32/1508_2.png) [@WagoIcard](https://www.wago.community/u/WagoIcard)\
**Post date:** [August 3, 2026, 12:25am UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/7 "2026-08-03T00:25:31Z")

</div>

First in english we don’t use octets but bytes.

Next to Word to string should only convert the available data:  
“123” should become 16#31,16#32,16#33 (3 byte)  
The problem come to the real value,  
If you only want 3 byte you will have “45.” (In that case only 2 byte is ok) or you need a 4th byte “45.7”

You can test this function:

```auto
FUNCTION FuLreal_to_nString : STRING
(* This function convert a LREAL into a STRING.*)
VAR_INPUT
	lrValue : LREAL;
	usiPres : USINT := 3;
END_VAR
VAR
	_lrValue	: LREAL;
	_uliHelp	: ULINT;
	_sHelp : STRING;
END_VAR

```

```auto
(* LIMIT usiPres between 0 and 7 *)
usiPres := LIMIT(0, usiPres, 7);
_lrValue := lrValue;
IF (_lrValue = 0) THEN
	_sHelp := '0';
	IF (usiPres > 0) THEN
		// _sHelp := Standard.CONCAT(_sHelp, '.');
		WagoAppString.StrAppend(sBuffer := _sHelp, udiSize := SIZEOF(_sHelp), sAppendee := '.');
		FOR _uliHelp := 1 TO usiPres BY 1 DO
			// _sHelp := Standard.CONCAT(_sHelp, '0');
			WagoAppString.StrAppend(sBuffer := _sHelp, udiSize := SIZEOF(_sHelp), sAppendee := '0');
		END_FOR;
	END_IF;
ELSE
	(* round the rVar to bPres digits *)
	_uliHelp := TO_ULINT(ABS(_lrValue) * EXP(usiPres * LN(10)));
	(* convert _uliHelp to string *)
	_sHelp := TO_STRING(_uliHelp);
	(* add missing "0"s *)
	WHILE (Standard.LEN(_sHelp) < usiPres) DO
		// _sHelp := Standard.CONCAT('0', _sHelp);
		WagoAppString.StrAppend(sBuffer := _sHelp, udiSize := SIZEOF(_sHelp), sAppendee := '0');
	END_WHILE
	(* add the separator *)
	IF (usiPres > 0) THEN
		//_sHelp := Standard.INSERT(_sHelp, '.', Standard.LEN(_sHelp) - bPres);
		WagoAppString.StrInsert(sBuffer := _sHelp, udiSize := SIZEOF(_sHelp), udiPos := TO_UDINT(Standard.LEN(_sHelp) - usiPres) + 1, sInsert := '.');
	END_IF;
	(* add leading 0 if needed *)
	IF (-1 < _lrValue) AND (_lrValue < 1) THEN
		// _sHelp := Standard.CONCAT('0', _sHelp);
		WagoAppString.StrAppend(sBuffer := _sHelp, udiSize := SIZEOF(_sHelp), sAppendee := '0');
	END_IF;
	(* add a minus sign if rVar is negative *)
	IF (_lrValue < 0) THEN
		// _sHelp := Standard.CONCAT('-', _sHelp);
		WagoAppString.StrAppend(sBuffer := _sHelp, udiSize := SIZEOF(_sHelp), sAppendee := '-');
	END_IF;
END_IF;
(* we copy the _sHelp in the output *)
FuLreal_to_nString := _sHelp;

```

usiPres is the number of data after the . (can be from 0: 46 to 7: 45.7458000)  
The function should return the closest data possible or the full value.

---

<div class="post-metadata">

**Author:** ![damienexail](https://avatars.discourse-cdn.com/v4/letter/d/b2d939/32.png) [@damienexail](https://www.wago.community/u/damienexail)\
**Post date:** [August 18, 2026, 2:36pm UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/8 "2026-08-18T14:36:08Z")

</div>

Sorry for the octet 🙂

Thanks for the code. I’m going to do more about this subject.

---

<div class="post-metadata">

**Author:** ![damienexail](https://avatars.discourse-cdn.com/v4/letter/d/b2d939/32.png) [@damienexail](https://www.wago.community/u/damienexail)\
**Post date:** [October 2, 2026, 12:46pm UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/9 "2026-10-02T12:46:45Z")

</div>

Hello,  
A little problem, LEN function is limited to max value 255 . We can’t use for String(1024).

---

<div class="post-metadata">

**Author:** ![WagoIcard](https://sea2.discourse-cdn.com/flex016/user_avatar/www.wago.community/wagoicard/32/1508_2.png) [@WagoIcard](https://www.wago.community/u/WagoIcard)\
**Post date:** [October 4, 2026, 11:53pm UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/10 "2026-10-04T23:53:15Z")

</div>

since when in a REAL or LREAL you have more than 255 character ?

---

<div class="post-metadata">

**Author:** ![damienexail](https://avatars.discourse-cdn.com/v4/letter/d/b2d939/32.png) [@damienexail](https://www.wago.community/u/damienexail)\
**Post date:** [October 5, 2026, 6:08am UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/11 "2026-10-05T06:08:46Z")

</div>

(\* Format data row \*)  
sBuffer := CONCAT(DT\_TO\_STRING(dtTimestamp), ‘,’); \*// EDIT THIS SECTION \*  
sBuffer := CONCAT(sBuffer, WORD\_TO\_STRING(wRPM));  
sBuffer := CONCAT(sBuffer, ‘,’);  
sBuffer := CONCAT(sBuffer, REAL\_TO\_STRING(rDegreesF));  
sBuffer := CONCAT(sBuffer, ‘,’);  
sBuffer := CONCAT(sBuffer, REAL\_TO\_STRING(rPressurePSI));  
sBuffer := CONCAT(sBuffer, ‘$R$L’); _// CR LF_

```
  (\* Write to file \*)     
  SysFileWrite(hFile, ADR(sBuffer), LEN(sBuffer), ADR(iResultWrite));

```

I don’t speak about Real or LREAL 🙂  
In the example above, there are only three measures, so we are well below the LEN() function’s limit of 255.  
However, if we use more data, the LEN() function becomes limiting.  
That’s a shame because SysFileWrite consumes cycle time.

---

<div class="post-metadata">

**Author:** ![WagoIcard](https://sea2.discourse-cdn.com/flex016/user_avatar/www.wago.community/wagoicard/32/1508_2.png) [@WagoIcard](https://www.wago.community/u/WagoIcard)\
**Post date:** [October 5, 2026, 6:23am UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/12 "2026-10-05T06:23:24Z")

</div>

Yes yes i thought you talked about the snippet i send.

In that case look inside the WagoAppString, you have function that bypass this limitation.

---

<div class="post-metadata">

**Author:** ![damienexail](https://avatars.discourse-cdn.com/v4/letter/d/b2d939/32.png) [@damienexail](https://www.wago.community/u/damienexail)\
**Post date:** [October 5, 2026, 12:13pm UTC](https://www.wago.community/t/how-to-log-data-to-csv-on-cc100-sd-card/2328/13 "2026-10-05T12:13:45Z")

</div>

FYI, Lina from Wago France advise me to use StrLenA(ADR(sBuffer));
