今天继续我们的小白教程,老鸟就不要在这浪费时间了😊。
前面一期我们介绍了CODESYS的定时器及触发相关的功能块。这一期主要介绍CODESYS的CAA.File库中的目录和文件读写功能块,主要包括文件路径、名称、大小的获取以及文件的创建、打开、读、写、拷贝和删除功能等。
一、文件库类型简介
文件读写有两种库:CAA File(File Access)库和SysFile库。
1.CAA File(File Access)
CAA File库包含用于访问文件目录和文件的功能块。
对于3.5.17以前的版本,通常是使用CAA File库。由于CAA File库中使用的部分类型定义在另外一个库CAA Types Extern中,因此使用时还需要包含该库。
在3.5.17及以后版本,直接使用File Access即可,如下图所示。
2.SysFile
SysFile属于CODESYS比较底层的库,函数及功能与C语言非常接近。实际上CAA File底层也是调用该库来实现的。
二、CAA.File库介绍
CAA.File库包含用于访问目录和文件的操作。
1.枚举定义
(1)文件属性定义ATTRIB
定义GetAttribute功能块获取的文件属性值。
名称 | 初始值 | 说明 |
ARCHIVE | 0 | 档案文件 |
HIDDEN | 1 | 隐藏文件 |
NORMAL | 2 | 没有设置任何其他属性的文件 |
READONLY | 3 | 只读文件 |
(2)文件访问模式MODE
定义file.Open功能块打开文件的访问模式。
名称 | 初始值 | 说明 |
MWRITE | 0 | 写访问,文件将被覆盖或创建 |
MREAD | 1 | 读取访问,文件将仅打开进行读取 |
MRDWR | 2 | 读取和写入访问,文件将被覆盖或创建 |
MAPPD | 3 | 文件将以WRITE模式打开,但写入的数据将附加在文件末尾 |
MREADPLUS | 4 | 打开具有读/写权限的现有文件。如果文件不存在,则打开失败 |
MWRITEPLUS | 5 | 创建具有读/写权限的新文件。如果文件确实存在,则丢弃内容(与file_MRDWR相同) |
MAPPENDPLUS | 6 | 使用附加(读/写)访问权限打开现有文件。如果文件不存在,“打开”将创建一个新文件 |
(3)错误码定义ERROR
定义在处理CAA_File.library的函数时可能会出现错误值。
名称 | 初始值 | 说明 |
NO_ERROR | 0 | 无错误 |
FIRST_ERROR | 5100 | 错误枚举定义的起始编号 |
TIME_OUT | 5101 | 超过时间限制 |
ABORT | 5102 | xAbort信号激活导致操作终止 |
HANDLE_INVALID | 5103 | 无效文件句柄 |
NOT_EXIST | 5104 | 文件或目录不存在 |
EXIST | 5105 | 文件或目录已经存在 |
NO_MORE_ENTRIES | 5106 | 没有其他条目可用 |
NOT_EMPTY | 5107 | 文件或目录不为空 |
READ_ONLY_CAA | 5108 | 文件或目录写保护 |
WRONG_PARAMETER | 5109 | 参数错误 |
ERROR_UNKNOWN | 5110 | 未知错误 |
WRITE_INCOMPLETE | 5111 | 数据写入不完整 |
FILE_NOT_IMPLEMENTED | 5112 | 功能未实现 |
ASM_CREATEJOB_FAILED | 5113 | AsyncManager的作业创建失败 |
FILE_OPERATION_DENIED | 5114 | ForceFilePath/ForceIecFilePath无法访问(这个错误我也不知道是什么意思…) |
FIRST_MF | 5150 | 制造商错误定义的起始编号 |
LAST_ERROR | 5199 | 错误枚举定义的编号上限 |
说实话,上表中定义的很多错误我用了这么久也没碰到过,常见的应该是标粗的那几个。
另外需要注意的是,以上几个枚举定义都需要通过全局变量名“FILE.xxx”来访问,比如只读文件属性要写为FILE.ATTRIB.READONLY,否则编译时会报标识未定义错误。
2.FILE_DIR_ENTRY结构
保存目录条目或文件的信息。
sEntry:CAA.FILENAME,文件或目录名。
szSize:CAA.SIZE,文件大小。
xDirectory:TRUE为目录, FALSE为文件。
xExclusive:文件访问模式,TRUE为独占访问模式,FALSE为多个实例可以同时访问。
dtLastModification:上次修改的日期和时间,日期时间格式为2023-01-17-11:13:00
注意:使用本结构需要通过FILE.FILE_DIR_ENTRY实现。
3.目录操作功能块
目录操作功能块:
功能块名称 | 功能 | 备注 |
DirOpen | 打开目录 | |
DirClose | 关闭目录 | |
DirCreate | 创建目录 | |
DirList | 读取目录条目 | |
DirCopy | 拷贝目录 | |
DirRemove | 删除目录 | 依赖于操作系统和文件系统 |
DirRename | 重命名目录 |
4.文件操作功能块
文件操作功能块如下表所示:
功能块名称 | 功能 | 备注 |
Open | 打开文件 | |
Read | 读取文件内容 | |
Write | 内容写入文件 | |
Flush | 将缓冲写入文件 | |
Close | 关闭文件 | |
Copy | 复制文件 | |
Rename | 重命名文件 | |
Delete | 删除文件 | 已打开的文件也可删除,依赖于操作系统和文件系统 |
EOF | 检查是否到达文件结尾 | |
GetAttribute | 获取文件属性 | |
GetPos | 返回文件访问的当前偏移位置 | 文件必须通过file.Open打开 |
SetPos | 设置文件访问的当前偏移量 | 文件必须通过file.Open打开 |
GetSize | 返回文件大小 | |
GetTime | 返回上次修改的日期和时间 |
5.功能块主要参数
由于各个功能块的参数和操作模式基本类似,各个功能块的大部分参数都是类似的,这里就不针对每个功能块的参数一一说明。
xExecute:输入,上升沿开始执行,下降沿复位输出。如果在功能块完成其动作之前出现下降沿,则输出以通常的方式操作,并且仅在动作完成或发生错误时复位。在这种情况下,对应的输出值(xDone,xError)在输出端只持续一个周期。
xAbort:输入,TRUE则立即停止操作,并将所有输出置为初始值。
sDirName:输入,待操作目录名称。
sFileName:输入,待操作的文件名称。
eFileMode:输入,文件操作模式,由FILE.MODE定义。
udiTimeOut:输入,定义功能块因超时而中止操作并输出错误消息的时间,单位µs。
hDir:待操作的目录句柄。
hFile:待操作的文件句柄。
pBuffer:读取或写入数据缓冲区的首地址,通过ADR获取。
szBuffer:要读取的字节数。
xOverWrite:输入,TRUE为覆盖已存在的文件或目录,FALSE为报错。
xDone:输出,TRUE为操作成功。
xAborted:输出,TRUE为操作被用户中止。
xEOF:输出,TRUE为达到文件结尾。
xBusy:输出,TRUE为功能块正在执行中。
xError:输出,TRUE为发生错误,功能块终止运行;FALSE为无错误。
eError:输出,错误ID,由ERROR定义。
eFileAttrib:输出,文件属性,由FILE.ATTRIB定义。
uidPos:输出,文件指针偏移位置(相对于文件开头的字节数)。
szSize:输出,文件实际大小,单位为字节。
dtLastModification:上次修改的日期和时间,格式为2023-02-03-16:23:00
三、使用示例
这里需要注意的是,在早期版本的CODESYS官方示例中,CAA.HANDLE、CAA.FILENAME、CAA.SIZE等变量是以CAA_HANDLE、CAA_FILENAME、CAA_SIZE的形式出现的,具体从哪个库版本开始改的,我也记不得了,总之改过来以后使用新版本的库就不会报错了~~。
1.目录操作使用示例
以下为目录操作的示例,其功能是在控制器指定目录下建立新目录,然后对目录进行打开、获取目录属性列表、关闭、拷贝、重命名和删除操作。需要注意的是这些操作需要在实际的控制器上才能执行,仿真模式下会报5113号错误。本次测试使用的控制器是禾川的Q0,使用其它控制器时需要正确指定可进行读写操作的目录位置。
程序变量定义如下:
PROGRAM testDir
VAR
xDirInit: BOOL := FALSE;
uiDirState: UINT := 0;
sDirNewName: CAA.FILENAME:='$$flashfiles$$\TestDirectory';
sDirNextName: CAA.FILENAME:='$$flashfiles$$\NewDirectory';
hDir: CAA.HANDLE;
deNewDirectory: FILE.FILE_DIR_ENTRY;
eError: FILE.ERROR;
fDirCreate: FILE.DirCreate;
fDirOpen: FILE.DirOpen;
fDirClose: FILE.DirClose;
fDirList: FILE.DirList;
fDirCopy: FILE.DirCopy;
fDirRename: FILE.DirRename;
fDirRm: FILE.DirRemove;
END_VAR
程序如下:
IF NOT xDirInit THEN
fDirCreate(xExecute:=FALSE);
fDirClose(xExecute:=FALSE);
fDirList(xExecute:=FALSE);
fDirRm(xExecute:=FALSE);
xDirInit:=TRUE;
uiDirState:=0;
ELSE
CASE uiDirState OF
0: (* 创建新目录 *)
fDirCreate.sDirName:=sDirNewName;
fDirCreate.xParent:=FALSE;
fDirCreate(xExecute:=TRUE);
IF fDirCreate.xDone THEN
uiDirState:=1;
END_IF
IF fDirCreate.xError THEN (* 错误处理*)
eError:=fDirCreate.eError;
;
END_IF
1: (* 打开目录 *)
fDirOpen.sDirName:=sDirNewName;
fDirOpen(xExecute:=TRUE);
IF fDirOpen.xDone THEN
hDir := fDirOpen.hDir;
uiDirState:=2;
END_IF
IF fDirOpen.xError THEN (* 错误处理 *)
eError:=fDirOpen.eError;
;
END_IF
2: (* 获取目录属性列表 *)
fDirList.hDir:=hDir;
fDirList(xExecute:=TRUE);
IF fDirList.xDone THEN
deNewDirectory.sEntry :=fDirList.deDirEntry.sEntry;
deNewDirectory.szSize :=fDirList.deDirEntry.szSize;
deNewDirectory.xDirectory :=fDirList.deDirEntry.xDirectory;
deNewDirectory.xExclusive :=fDirList.deDirEntry.xExclusive;
deNewDirectory.dtLastModification :=fDirList.deDirEntry.dtLastModification;
uiDirState:=3;
END_IF
IF fDirOpen.xError THEN (* 错误处理 *)
eError:=fDirList.eError;
;
END_IF
3: (* 关闭目录 *)
fDirClose.hDir:=hDir;
fDirClose(xExecute:=TRUE);
IF fDirClose.xDone THEN
uiDirState:=4;
END_IF
IF fDirClose.xError THEN (* 错误处理 *)
eError:=fDirClose.eError;
;
END_IF
4: (* 目录拷贝 *)
fDirCopy.sDirNameSource:=sDirNewName;
fDirCopy.sDirNameDest:='$$flashfiles$$\TestDirectory1';
fDirCopy(xExecute:=TRUE);
IF fDirCopy.xDone THEN
uiDirState:=5;
END_IF
IF fDirCopy.xError THEN (* 错误处理 *)
eError:=fDirCopy.eError;
;
END_IF
5: (* 目录重命名 *)
fDirRename.sDirNameOld:=sDirNewName;
fDirRename.sDirNameNew:=sDirNextName;
fDirRename(xExecute:=TRUE);
IF fDirRename.xDone THEN
uiDirState:=6;
END_IF
IF fDirRename.xError THEN (* 错误处理 *)
eError:=fDirRename.eError;
;
END_IF
6: (* 删除目录 *)
fDirRm.sDirName:=sDirNextName;
fDirRm.udiTimeOut:=100000; (* 超时时间 100ms *)
fDirRm.xRecursive:=FALSE;
fDirRm(xExecute:=TRUE);
IF fDirRm.xDone THEN
uiDirState:=7;
END_IF
IF fDirRm.xError THEN (* 错误处理 *)
eError:=fDirRm.eError;
;
END_IF
7: (* 示例结束 *)
;
END_CASE
END_IF
2.文件操作使用示例
以下为文件操作的示例,其功能是在控制器指定目录下建立新文件并将指定文本内容写入文件,然后进行读取、关闭、拷贝、重命名和删除文件操作。需要注意的是这些操作需要在实际的控制器上才能执行,仿真模式下会报错。本次测试使用的控制器是禾川的Q0,使用其它控制器时需要正确指定可进行读写操作的目录位置。
程序变量定义如下:
PROGRAM testFile
VAR
xFileStdInit: BOOL:=FALSE;
uiFileStdState: UINT:=0;
sFileName: CAA.FILENAME:= 'TestFile.txt';
hFile: CAA.HANDLE;
sFileTestString:STRING:='Hello 2023!';
sFileString: STRING:='';
szFileSize1: CAA.SIZE := 0;
szFileSize2: CAA.SIZE := 0;
sFileNewName: CAA.FILENAME:= 'NewFile.txt';
szCopiedFileSize: CAA_SIZE := 0;
eError: FILE.ERROR;
fOpen: FILE.Open;
fWrite: FILE.Write;
fRead: FILE.Read;
fClose: FILE.Close;
fCopy: FILE.Copy;
fRename: FILE.Rename;
fDel: FILE.Delete;
END_VAR
程序如下:
IF NOT xFileStdInit THEN
fOpen(xExecute:=FALSE);
fClose(xExecute:=FALSE);
fWrite(xExecute:=FALSE);
fRead(xExecute:=FALSE);
fDel(xExecute:=FALSE);
fRename(xExecute:=FALSE);
fCopy(xExecute:=FALSE);
xFileStdInit:=TRUE;
uiFileStdState:=0;
ELSE
CASE uiFileStdState OF
0: (* 创建新文件 *)
fOpen.sFileName:=sFileName;
fOpen.eFileMode:=FILE.MODE.MRDWR;
fOpen.xExclusive:=TRUE;
fOpen(xExecute:=TRUE);
IF fOpen.xDone THEN
hFile:=fOpen.hFile;
uiFileStdState:=1;
END_IF
IF fOpen.xError THEN (* 错误处理 *)
eError:=fOpen.eError; //错误号
;
END_IF
1:(* 文本内容写入文件 *)
fWrite.hFile:=hFile;
fWrite.pBuffer:=ADR(sFileTestString);
szFileSize1:=SIZEOF(sFileTestString);
fWrite.szSize:=szFileSize1;
fWrite.udiTimeOut:=100000; (* 100ms Timeout *)
fWrite(xExecute:=TRUE);
IF fWrite.xDone THEN
uiFileStdState:=2;
END_IF
IF fWrite.xError THEN (* 错误处理 *)
eError:=fWrite.eError;
;
END_IF
2:(* 读取文件 - TestFile.txt*)
fRead.hFile:=hFile;
fRead.udiTimeOut:=100000; (* 超时时间 100ms *)
fRead.pBuffer:=ADR(sFileString);
fRead.szBuffer:=255;
fRead(xExecute:=TRUE);
IF fRead.xDone THEN
szFileSize2:=fRead.szSize;
IF szFileSize2 = szFileSize1 THEN
uiFileStdState:=3;
ELSE (* 错误处理 *)
eError:=fRead.eError;
;
END_IF
END_IF
IF fRead.xError THEN (* 错误处理 *)
eError:=fRead.eError;
;
END_IF
3: (* 关闭文件 - TestFile.txt *)
fClose.hFile:=hFile;
fClose(xExecute:=TRUE);
IF fClose.xDone THEN
uiFileStdState:=4;
END_IF
IF fClose.xError THEN (* 错误处理 *)
eError:=fClose.eError;
;
END_IF
4:(* 拷贝 *)
fCopy.sFileNameSource:=sFileName;
fCopy.sFileNameDest:='DestFile.txt';
fCopy.udiTimeOut:=100000; (* 超时时间 100ms *)
fCopy.xOverWrite:=TRUE; (* 覆盖已有文件 *)
fCopy( xExecute:=TRUE);
IF fCopy.xDone THEN
szCopiedFileSize := fCopy.szSize;
uiFileStdState:=5;
END_IF
IF fCopy.xError THEN (* 错误处理 *)
eError:=fCopy.eError;
;
END_IF
5: (* 文件重命名 *)
fRename.sFileNameOld:='DestFile.txt';
fRename.sFileNameNew:=sFileNewName;
fRename( xExecute:=TRUE);
IF fRename.xDone THEN
uiFileStdState:=6;
END_IF
IF fRename.xError THEN (* 错误处理 *)
eError:=fRename.eError;
;
END_IF
6:(* 删除文件 *)
fDel.sFileName:=sFileNewName;
fDel( xExecute:=TRUE);
IF fDel.xDone THEN
uiFileStdState:=7;
END_IF
IF fDel.xError THEN (* 错误处理 *)
eError:=fDel.eError;
;
END_IF
7: (* end of example *)
;
END_CASE
END_IF
从上面的示例可以看出,文件操作实际上是通过状态机的方式进行的,即打开、读取或写入、属性获取、关闭等操作都是每次执行一步,一个操作执行完成后转到下一状态。这个流程在CODESYS中读写文件是比较推荐的,可以避免因某一步骤操作时间过长导致的任务超时。
四、结论
CAA File库的使用其实并不复杂,新手只要弄清楚Open、Read、Write、Close等几个主要功能块的用法,照着上面的示例改一下基本上能够解决大部分的问题。这篇文章本来打算连SysFile库一锅烩的,写着写着发现实在是太长了,还是留着下次再写吧^-^~~
另外说明一下,本文的示例是在CODESYS 3.5.17版本上测试的,如果是比较老的版本上(比如3.5.10)上可能无法正常运行。实际上CODESYS的库也是在不停的改来改去的,新手建议用最新的版本。考古的同学需要自己去翻文档了(这里要吐槽一下,CODESYS在线帮助里面有很多示例代码使用的类型定义也是在远古版本的库里面,放到现在的新版本里面会直接报错……)。
------------------
原创不易,感兴趣的多支持!