☰
CODESYS读写CSV文件完整方案:从库配置到工程落地
2026/10/1 17:41:16 网站建设 项目流程

干过几年PLC项目的人应该都有这种体会:设备调试阶段最烦的不是逻辑写不对,而是甲方突然甩过来一张Excel表,说“把这些配方导进去”,或者“把这几天的运行数据导出来给我们看看”。你要是拿U盘一个个手动输入,几百个参数能输到怀疑人生;你要是说“我们PLC不支持”,甲方看你的眼神立刻就不对了。

其实大部分时候,这类需求都可以用CSV文件搞定。CSV就是纯文本的表格数据,Excel能直接打开,PLC读写起来也不复杂。CODESYS作为目前市面上主流的IEC 61131-3编程环境,自带的文件操作库完全支持读写CSV,关键是你得知道怎么把这套东西组织得稳妥、高效、不出幺蛾子。

这篇文章我就把自己在实际项目里用CODESYS读写CSV文件的一套完整方案写出来,从思路拆解、库文件准备、代码实现,到调试排查、常见坑点,全部覆盖。适合正在做设备配方管理、数据记录、批次追溯,或者被Excel表格折磨过的人参考。

1. 整体设计与思路拆解

1.1 为什么要用CODESYS读写CSV文件

先把场景想清楚。CODESYS跑在IPC、嵌入式工控机或者软PLC上,最常见的两种数据交互需求:

  • 配方写入:设备切换产品型号时,几十上百个工艺参数要跟随产品变化。这些参数放在CSV里,操作工在触摸屏上选择产品型号,PLC自动读取对应参数并下发到轴、温控器、阀岛等执行机构。
  • 数据导出:设备运行时的温度曲线、产量计数、报警记录,定时写入CSV文件。甲方要数据时,直接U盘拷贝或者通过FTP拉走,用Excel分析。

这两个场景如果用Modbus寄存器或者OPC UA来做,通常需要上位机配合,而且配置麻烦。CSV文件的优势在于:格式足够通用、工具链简单、不依赖第三方协议。你甚至不需要在电脑上装任何专业软件,记事本就能打开检查格式。

CSV中文全称是“逗号分隔值”,它的底层就叫“逗号分隔值文件”,本质就是一个带特定分隔符的文本文件。理解这一点是后面所有操作的基础。

1.2 CODESYS文件系统的基础认知

很多刚从传统PLC(比如三菱、西门子S7-200)转过来的人,第一次接触CODESYS的文件操作时会懵:文件路径怎么这么别扭?文件到底存在哪里?

这是因为CODESYS支持多种运行平台。跑在Windows上的CODESYS Control Win,文件路径就是普通的C:\Users\...;跑在树莓派或者定制Linux系统上的CODESYS Control for Linux,路径就是Linux风格的/home/codesys/...;跑在倍福、汇川等厂家的嵌入式控制器上,路径又可能是/usr/...或者设备厂商自定义的目录。

最保险的做法是用CODESYS提供的SysFilePath库来获取系统标准目录,而不是硬编码路径。在项目早期就养好这个习惯,后面换硬件平台时能省掉大量改代码的时间。

另外要注意一点:CODESYS的文件操作库分为好几层。最底层是SysFile库,直接封装操作系统API,功能最全但用起来繁琐;上面一层是CAA File库(Common Application Architecture,通用应用架构),对底层做了封装,提供了FileOpen、FileRead、FileWrite、FileClose等函数块,用起来顺手很多,也是目前社区里最常用的方案。本文的方案就是基于CAA File库实现的。

1.3 核心难点提前梳理

读写CSV文件,表面上看就是“打开-读/写-关闭”三个动作,实际做起来坑很多。我把它拆成四个核心难点:

  • 文件路径与命名管理:文件放在哪个目录,文件名怎么按日期/批次生成,如何避免重复和冲突。
  • 字符串拆解与组装:写CSV要把数据拼接成带分隔符的字符串,读CSV要把字符串按分隔符拆开,还要处理引号、换行符、浮点数格式等细节。
  • 大文件流量控制:PLC的内存和运行时资源有限,不能像电脑一样一次性把整个文件读入内存,必须流式处理,逐行读写。
  • 容错与异常处理:文件不存在、权限不足、磁盘已满、文件被其他程序占用,这些情况都要处理,否则PLC程序可能直接卡死或崩溃。

把这几个难点控制住,CSV读写这个功能就算真正落地了。

2. 库准备与环境搭建

2.1 需要添加哪些库文件

在CODESYS的库管理器(Library Manager)里,默认工程一般不会自动带上文件操作库,需要手动添加。右键“库管理器”选择“添加库”,然后搜索以下库名称:

  • CAA File:核心文件操作库,提供文件读写相关的函数块。
  • CAA Types:CAA File的依赖库,包含错误码定义等基础类型。
  • OSCAT Basic:非必需,但强烈推荐。这是一个开源基础库,里面的字符串处理函数非常多,做CSV解析时能省很多事。在CODESYS库管理器中搜索“OSCAT”即可找到,或者从GitHub上下载后导入。
  • SysFilePath:用于获取系统标准目录。

如果你使用的CODESYS版本较新,部分库可能已经被合并或改名。实际操作时,添加CAA File库后它会自动带出依赖项,不需要纠结太多。

注意:CAA File和SysFile不要混用。同一个文件操作生命周期内,不要一会用CAA函数块、一会用SysFile底层函数,两者的文件句柄管理方式不同,混用容易出莫名其妙的文件指针错乱问题。

2.2 工程配置与全局变量定义

在PLC_PRG(或者你自定义的主程序)里,先定义好全局用的文件管理变量。我习惯把文件操作相关的变量单独放在一个全局变量列表里,方便统一管理:

VAR_GLOBAL // 文件路径拼接缓冲区 g_sFilePath : STRING(255); g_sFileName : STRING(255); // 写入缓冲区 g_sWriteBuffer : STRING(1000); // 读取缓冲区 g_sReadBuffer : STRING(1000); // 文件操作状态 g_bFileBusy : BOOL; g_eFileError : CAA.FILE_ERROR; END_VAR

这里我把字符串缓冲区设定为1000个字符,实际项目需要根据你的数据规模调整。一次PLC扫描周期内处理1000个字符是完全够用的,如果你要一次写一大块数据,可以拆分成多次循环写入。

另外提一句,CODESYS的STRING类型默认是80个字符,如果你定义一个STRING变量时不给长度,后面赋值超出长度的内容会被静默截断,这是很多莫名其妙的“数据不对”问题的根源。所以所有用于文件读写的字符串变量,定义时务必显式指定长度,比如STRING(255)、STRING(1000)。

3. 写入CSV文件:从基础到封装

3.1 基础写入流程解析

任何文件写入操作都遵循“打开-写入-关闭”三步。在CAA File库中,对应的是FileOpen、FileWrite、FileClose三个函数块。

第一个要点:FileOpen的模式选择。FileOpen函数块有一个umod(模式)输入,本质是一个枚举(CAA.FILE_MODE),常用的是:

  • CAA.FILE_MODE.READ:只读模式。如果文件不存在会报错。
  • CAA.FILE_MODE.WRITE:写入模式。如果文件已存在,会从文件头开始覆盖写入。
  • CAA.FILE_MODE.APPEND:追加模式。如果文件已存在,会从文件末尾继续写入;文件不存在则新建。

这个选择很关键。比如你要生成一个按日期命名的日志文件,每天首次运行时应该用WRITE模式创建新文件,后续追加记录要用APPEND模式。如果是配方文件这种固定文件,每次写的时候想要完全覆盖旧内容,就用WRITE模式。

第二个要点:FileWrite的缓冲区机制。FileWrite函数块一次接收一个字符串变量,写入指定长度。这里有个小坑:如果你传入的是一个STRING类型的完整行内容,长度参数应该用LEN函数计算实际字符串长度,而不是直接传整个缓冲区长度,否则会把字符串末尾的空字符也写进去,导致CSV文件里出现不可见的乱码内容。

我踩过的坑是:第一次写的时候直接传了缓冲区长度,结果用Excel打开CSV文件,每一行后面都多了一长串空白单元格,把表格撑得巨大。排查了半天才发现是字符串尾部的空字符被一起写进了文件。

3.2 带引号转义的完整写入方案

CSV格式有一个容易被忽略的规范:如果某个字段里包含逗号、换行符或双引号,那么这个字段需要用双引号包裹,并且字段内部的双引号需要用两个连续的双引号转义。

举个例子,某个产品的配方名称是A, B "Plus",那么写入CSV时这一列的内容应该是:

"A, B ""Plus"""

如果忽略这一步,Excel打开文件时会把逗号当作分列符,导致表格列错位,数据看着乱七八糟的。所以一个工程级的CSV写入函数块,必须包含引号转义处理。

下面是一个简化版的写入函数块实现思路:

// 函数块:FB_CSV_WriteLine // 输入:sLine 要写入的CSV行(已组装好) // 输入:pFile 文件句柄 // 输出:bDone 本次写入是否完成 IF NOT bBusy THEN bBusy := TRUE; bDone := FALSE; // 先写入一行内容 FileWrite_Instance( hFile := pFile, sData := sLine, udiSize := LEN(sLine), pResult := wResult ); END_IF // 检查写入是否完成 IF FileWrite_Instance.bBusy THEN // 继续等待 ELSE IF FileWrite_Instance.pResult = CAA.FILE_OK THEN // 再写入换行符(CRLF) // 这里再调用一次FileWrite写入ASCII 13和10 bDone := TRUE; ELSE // 错误处理 END_IF bBusy := FALSE; END_IF

注意我强调的“先写入内容,再写入换行符”这个顺序。有些实现方案是把$R$N(CODESYS中表示回车换行的转义字符)直接拼接在行字符串末尾一起写入,理论上也没问题,但分开写更清晰,而且方便你统一控制换行符格式(Windows用的CRLF,Linux有些工具认LF,后面会详细说)。

3.3 生成的日期型文件命名与目录管理

实际项目中,我最常用的文件命名规则是“设备号+日期+用途”,例如:

F01_20250615_Recipe.csv F01_20250615_TemperatureLog.csv

在CODESYS中拼这种带日期的字符串,用系统库里的DT_TO_STRING或者自己把年月日分别转成字符串再拼接。注意DT_TO_STRING出来的格式是带空格的2025-06-15-14:30:25,文件路径最好不要含空格,所以还是自己拼接更可控。

拼接时注意前导零问题:月份和日期是个位数时,需要补零,比如6要变成06。自己写个三位数的判断就行,或者用格式化函数。

目录管理上,我建议把CSV文件统一放在一个子目录下,比如/user/data/csv或者C:\PLC_Data\CSV,不要在根目录下直接撒文件。根目录通常受系统权限保护,而且文件多了以后查找非常痛苦。通过SysFilePath库获取用户目录后,再在后面拼接CSV子目录,然后用CreateDir函数块创建目录,如果返回错误码表示目录已存在,就忽略继续往下走。

4. 读取CSV文件:解析与容错

4.1 读取CSV的核心流程

读取CSV比写入稍微复杂一点,因为你要面对的是未知的、可能不规则的数据。基本流程是:打开文件、逐行读取、解析每一行、转成PLC变量、关闭文件。

CAA File库的FileRead函数块支持指定长度读取。但CSV是变长的行,所以更推荐的做法是:按固定长度分块读取到缓冲区,然后在缓冲区里搜索换行符($N),把换行符之前的内容当作一整行来解析。

这里有一个工程上的取舍:**一次性读一行还是分块读?**如果一行内容很长(比如几百个字符),分块读就需要自己维护一个“跨块拼接”的机制,代码复杂度会上升。我的做法是:把读取缓冲区设成足够大(比如1000字符),用FileRead一次读入,然后在缓冲区里搜换行符。实际应用中,配方文件的单行很少超过500字符,这个方案简单可靠。如果你要读的是大数据量的日志文件,可以改成在WHILE循环里多次读取,每读一次就解析一次。

4.2 字符串拆解与数据类型转换

读取CSV最核心的步骤是把一行字符串按分隔符拆成多个字段。CODESYS官方库没有直接提供Split函数,但OSCAT库里有现成的字符串处理函数,可以大幅减少工作量。

我自己封装了一个FB_CSV_SplitLine函数块,核心思路是两层循环:

  • 外层循环遍历整行字符串,遇到分隔符(逗号或分号)就认为是字段边界。
  • 内层循环处理双引号内的内容。如果当前字符是双引号,就进入“引号模式”,在引号模式内遇到的逗号不作为分隔符,遇到两个连续双引号还原为一个双引号。

解析完成后,每个字段存到一个字符串数组里。之后再根据需求,用STRING_TO_REAL、STRING_TO_INT、STRING_TO_BOOL等转换函数把字符串转成对应类型的PLC变量。

这里有一个特别容易翻车的地方:CODESYS的REAL转字符串默认可能是科学计数法格式,比如1.234568E+003,这种格式如果直接写进CSV,Excel能识别,但某些第三方设备或上位机软件就不一定能正确处理。所以写入CSV时,如果数据都是整数,优先考虑转成整数格式写入;如果是小数,自己做格式化,控制小数点位数,比如拼字符串时用REAL_TO_STRING之后再截取前几位,或者用格式化工序。

4.3 大文件分批读取与状态机设计

如果你的CSV文件有几百上千行数据,PLC不能在一次扫描周期里全部处理完,否则会严重影响扫描周期,变频器、轴控的实时性都会受影响。我的做法是设计一个文本解析状态机,让每一行数据的读取和解析分散在多个扫描周期内完成。

状态机的基本状态包括:

  • 空闲(IDLE):等待触发读取命令。
  • 打开中(OPENING):调用FileOpen,等待完成。
  • 读取中(READING):调用FileRead读一块数据,解析出一行或多行。
  • 行处理中(PROCESSING):对解析出的行做业务处理,比如存到配方结构体。
  • 关闭中(CLOSING):文件读完,调用FileClose。
  • 完成(DONE):整个读取流程结束。

用状态机的好处是:每个扫描周期最多执行一个或两个动作,不会卡循环;而且程序结构清晰,出问题时哪个状态卡住了,在线监控一眼就能看出来。

在状态机流转过程中,务必要注意FileOpen、FileRead、FileClose这些函数块的bBusy输出。这些函数块是异步的,调用后不会立即完成,下一周期才能判断结果。最常见的错误就是在同一个条件里连续调用这几个函数块,导致“上一次操作还没完成,下一次操作已经开始了”,文件句柄冲突,程序直接报错。

5. 高频问题与排查技巧

5.1 常见报错与排查思路

我在项目里碰到的CSV相关报错,基本都能归到下面这几类。整理成表格,方便大家快速对照:

错误现象根本原因排查方法
文件写入后为空,或内容只有一半非阻塞函数块未等待完成就执行下一步,文件未关闭就被中止用状态机或IF判断bBusy,确保写入完成后才能关闭
csv log unsuccessful文件路径不存在,或目录无写入权限检查路径、确认目录存在;用SysFilePath获取用户目录;手动创建一个测试目录验证
打开文件报CAA.FILE_ERR.NO_ACCESS文件被其他程序占用,或当前用户权限不足Windows上用资源管理器确认文件未被Excel打开;Linux下检查文件属主和权限位
中文乱码编码不一致,PLC写入的UTF-8被Excel以ANSI打开在CSV首行写入sep=,或统一使用UTF-8 BOM格式;推荐的方案是写入\xEF\xBB\xBF字节序标记
Excel打开后列错位字段中包含逗号但未做引号转义检查写入函数块是否有引号转义处理,确认双引号包裹逻辑
数字变成科学计数法浮点数直接转字符串导致格式不友好自写格式化函数,保留固定小数位,或先转整数处理

csv log unsuccessful这条我特别说一下。这不是CODESYS特有的报错,而是很多基于CODESYS的设备在导入/导出配方时常见的提示。踩过坑之后我总结出一个通用排查路径:先看路径和文件名,再看权限和占用,最后看缓冲区长度是否够用。这个排查顺序准确率极高。

5.2 缓冲区长度与内存控制技巧

字符串缓冲区长度是个典型的“够用就行,但宁大勿小”的参数。太小会导致数据被截断,太大又浪费内存——PLC的内存和电脑不一样,很多嵌入式控制器的用户内存就几MB,动辄定义几十个STRING(5000)变量是不可接受的。

我的经验值是这样的:

  • 写CSV时,单行缓冲区500字符足够,除非你的行字段非常多。
  • 读CSV时,缓冲区至少设成预期最长行的2倍,防止解析过程中拼接字符串产生溢出。
  • 不要一次把所有CSV数据都读到PLC的数组里,应该边读边处理。比如配方数据,读一行就往目标结构体里填一行,用完即弃。
  • CODESYS的STRING类型在底层是字符数组,大量字符串切片、拼接操作会产生一定的CPU开销。如果一次要处理很多行数据,建议牺牲一点可读性,用MID、FIND等直接操作字符数组,而不是反复用CONCAT。

5.3 一个值得记录的调试案例

之前在做一个锂电池测试设备时,遇到一个诡异的问题:CSV文件在Windows上打开一切正常,但导入到第三方MES系统时,最后一行数据丢失。查了半天发现,CSV文件最后一行写完换行符后,文件就立即被关闭了,但因为写入函数块还没完全flush到磁盘,第三方系统读取时文件尾部数据的缓冲还没落盘。

解决办法是:写完所有数据后,关闭文件前加入一个短暂延时,或者调用文件系统层的flush函数。另外,写CSV时建议每条记录都完整写入后再写下一条,避免大段数据集中在关闭前才flush,这样也能降低数据丢失的概率。

6. 实操心得与项目经验总结

6.1 封装成自己的库文件

CSV读写功能做完一次之后,不要每次都重新写一遍。把FB_CSV_WriteLine、FB_CSV_SplitLine、FB_CSV_ReadLine这些函数块整理到一个自定义库中,下次新项目直接引用即可。

封装时注意几点:输入输出变量全部用结构化变量,函数块内部状态不外泄;错误码要统一,方便上层逻辑统一处理;库文件最好带上版本号,避免多项目混用时的版本混乱。

CODESYS生成库文件也不复杂,在工程中把需要发布的POUs放到一个单独的库工程里,右键编译生成library文件,然后在其他工程里通过“库管理器-添加本地库”导入即可。直接发布的库文件如果不想让使用者看到内部实现,可以勾选“隐藏实现代码”选项,只暴露接口。

6.2 关于编码问题的最终建议

CSV文件的编码问题,是中文本地化项目绕不过去的坎。最稳妥的做法是:在写入CSV文件时,文件头加入UTF-8 BOM(字节序标记,EF BB BF)。这样Excel无论中文版还是英文版,打开时都能正确识别UTF-8编码,不会出现乱码。

写入BOM的方法很简单,在创建文件后、写入第一行数据前,先写入三个字节:16#EF、16#BB、16#BF。CAA File库可以用FileWrite写入一个三个字符的字符串变量来实现。

如果不想让文件带BOM(有些Linux工具会讨厌BOM),那至少保证写入的文件是UTF-8编码,并明确告知最终使用者“请用UTF-8编码打开”。不要用CODESYS默认的STRING写中文,因为PLC里的字符串字面量可能存在老版运行时的ANSI编码问题,最好是从HMI或外部传入中文内容,或者用Unicode库处理。过去调试时踩过的坑告诉我,这一条如果忽略,后期排障成本极高。

6.3 对比其他实现方案的参考

除了CAA File库,CODESYS生态还有其他读写CSV的路径,简单对比一下,方便不同场景选择:

方案优点缺点适用场景
CAA File + 字符串手写解析灵活、无依赖、完全可控代码量稍大,需要自己处理细节绝大多数标准PLC项目
OSCAT字符串处理库 + CAA简化拆分拼接,代码简洁引入第三方库,需要注意许可证做数据导出的记录类设备
plc-recorder等第三方工具配置简单,自动采集变量到CSV需要额外安装、非标准运行时、可定制性低边缘网关、数据采集盒子方案
数据库(如SQLite库)查询强、功能丰富资源占用高、配置复杂需要做条件查询、数据量大的场景

我个人主力方案还是CAA File加自封装函数块。原因很简单:工业项目要的是可控,任何一个环节依赖第三方库,都可能成为后续维护的隐患。

6.4 最后给新手的几点操作建议

如果你刚开始接触CODESYS文件操作,按照下面的顺序练一遍,基本就不会走弯路:

  1. 先用CODESYS Control Win仿真,在Windows环境下写出第一个能落盘的CSV文件,确认路径、权限、编码都没问题,再移植到真实控制器上。仿真环境下调试文件操作,比直接在实体PLC上试快得多。
  2. 每次改动CSV相关代码,先做小样本测试。比如要导入1000条配方,先拿3条测试数据跑通全流程,确认格式正确后再上全量。
  3. 在HMI上做一个简单的文件状态显示页,把最近一次读写操作的结果和错误码显示出来。设备放客户现场后,这个页面能帮你在电话里快速定位问题,省下不少跑现场的时间。
  4. 备份时带上CSV文件。很多控制器支持U盘备份或FTP上传,记得把CSV数据目录一并纳入备份范围,别设备换卡后数据全丢了。

CSV文件读写,说难不难,说简单也不简单,真正做好需要把文件系统、字符串处理、异步调用、异常处理这些东西全部理顺。这套方案我在多个项目里验证过,稳定性和可维护性都经得起考验。下个项目如果再遇到“导个表进来”的需求,你可以放心地说“能弄”,然后直接用这套方案拿下来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询