智能控制基础应用-C#&Codesys共享内存实现数据高速交互
文章目录
- 智能控制基础应用-C#&Codesys共享内存实现数据高速交互
- 前言
- 一、 Codesys共享内存程序实现
- 二、Python共享内存程序实现
- 2.1 界面说明
- 三、功能测试
- 注意事项
前言
共享内存是一种高效的进程间通信(IPC)机制,允许不同的进程访问同一块内存区域。共享内存本质就是建立一块供协作进程共享的内存区域,多个进程可以透过此共享区域读取或者写入数据来交换信息;
资源下载连接
一、 Codesys共享内存程序实现
1.1 库管理
1 查找库文件的名称
项目中需要使用到共享内存相关的函数,在不清楚这些函数在那个库文件下的情况下,可以通过在库管理工具中进行查找,通过【工具】-【库】,可以打开对应的库文件管理工具。
1.2 查找
在库文件管理工具中,点击查找按钮,可以实现对需要引用的函数或者库的查找,点击查找后,将出现1.3所示。
1.3 函数或库搜索
在查找库的搜索栏内,输入需要应用的函数或者库的名称,比如在本项目中,我们需要用sharedMemory相关的函数,我们直接键入sharedMemory,将出现如下内容,这表明,我们需要用的函数,在SysShm库中存在
1.4 Codesys的库管理器
在项目管理界面,双击库管理器工具,系统将打开库管理器。
1.5 导入库
在库管理器中,点击添加按钮,将打开添加库的界面,在此界面中,输入我们需要的库的关键字,比如sysshm,将过滤相关的内容,选中后,点击确认即可完成导入。
1.2 添加数据结构
在【应用】选项卡点击右键,选择【添加对象】,选择添加数据块[DUT]
选择数据的【类型】为结构-Structure类型,修改名称为str_SharedIn和str_SharedOut点击打开即可。
定义的结构体数据中,增加数据结构声明如下:
共享输入的内存,str_SharedIn的声明:
TYPE str_SharedIn :
STRUCT
bIN:BOOL; //布尔数据
iIN:DINT; //整型数据
fIN:LREAL; //浮点数据
END_STRUCT
END_TYPE
共享的输出内存,str_SharedIn的声明:
TYPE str_SharedOut :
STRUCT
bIN:BOOL; //布尔数据
iIN:DINT; //整型数据
fIN:LREAL; //浮点数据
END_STRUCT
END_TYPE
全局变量定义
在【应用】上点击右键,选择【添加对象】,添加【全局变量列表】,即可实现添加全部变量
需要添加的全局变量为写入的参数和读取的参数。具体定义如下:
VAR_GLOBAL
GetPara: Str_ParaFromHMI;
SetPara: Str_ParaToHMI;
END_VAR
1.2 函数实现
在函数中,对变量格式有特殊定义,因此函数中部分变量的定义如下:
PROGRAM POU_1
VAR
bStart: BOOL:= FALSE;
ReadHandle: RTS_IEC_HANDLE:= RTS_INVALID_HANDLE;
WriteHandle: RTS_IEC_HANDLE:= RTS_INVALID_HANDLE;
ulPhysicalAddressRead: __UXINT:= 0;
ulPhysicalAddressWrite: __UXINT:= 0;
ulSizeRead: __UXINT:= 1024;
ulSizeWrite: __UXINT:= 1024;
ResultRead: ARRAY[0..2] OF RTS_IEC_RESULT; //返回运行错误码
ResultWrite: ARRAY[0..2] OF RTS_IEC_RESULT;
SMRead: __UXINT;
SMWrite: __UXINT;
ulOffsetRead: __UXINT:= 0;
ulOffsetWrite: __UXINT:= 0;
shNameRead: STRING:= 'CODESYS_MEMORY_READ'; //声明共享内存的读取内存名称
shNameWrite: STRING:= 'CODESYS_MEMORY_WRITE'; //声明共享内存的写入内存名称
END_VAR
主函数
//内存初始化
IF NOT bStart THEN
ReadHandle:= SysSharedMemoryCreate(pszName:= shNameRead, ulPhysicalAddress:= ulPhysicalAddressRead, pulSize:= ADR(ulSizeRead), pResult:= ADR(ResultRead[0]));
WriteHandle:= SysSharedMemoryCreate(pszName:= shNameWrite, ulPhysicalAddress:= ulPhysicalAddressWrite, pulSize:= ADR(ulSizeWrite), pResult:= ADR(ResultWrite[0]));
IF RTS_INVALID_HANDLE <> ReadHandle AND RTS_INVALID_HANDLE <> WriteHandle THEN
bStart:= TRUE;
END_IF
END_IF
//读取数据
IF RTS_INVALID_HANDLE <> ReadHandle THEN
SMRead:= SysSharedMemoryRead(
hShm:= ReadHandle, //读取内存的设备句柄
ulOffset:= ulOffsetRead, //读取数据的偏移地址
pbyData:= ADR(Glob_VAR.GetPara), //指向读取数据的缓冲区
ulSize:= SIZEOF(str_SharedIn), //读取数据的字节大小
pResult:= ADR(ResultRead[1])); //返回执行的错误码
END_IF
//写入数据
IF RTS_INVALID_HANDLE <> WriteHandle THEN
SMWrite:= SysSharedMemoryWrite(
hShm:= WriteHandle, //写入内存的设备句柄
ulOffset:= ulOffsetWrite, //写入数据的偏移地址
pbyData:= ADR(Glob_VAR.SetPara), //指向写入数据的缓冲区
ulSize:= SIZEOF(str_SharedOut), //写入数据的字节大小
pResult:= ADR(ResultWrite[2])); //返回执行的错误码
END_IF
任务配置
在项目管理界面,选择【任务配置】-【Task】,系统将弹出【Task】的管理界面
在【Task】管理界面,点击【添加调用】,选择正确的POU程序块,系统即实现runtime和应用程序调用的关联
二、Python共享内存程序实现
2.1 界面说明
此项目中规划的设计界面如下,需要实现两个内容:
- 读取共享内存的数据并在界面中显示
- 将界面中设置的共享内存数据写入到codesys
界面设计函数如下:
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
textbox_iRead = new TextBox();
picbox_bWrite = new Label();
textbox_fRead = new TextBox();
iRead = new Label();
fRead = new Label();
picbox_bRead = new Label();
label1 = new Label();
label2 = new Label();
textbox_iWrite = new TextBox();
textbox_fWrite = new TextBox();
btnWrite = new Button();
label3 = new Label();
bWrite = new Label();
ReadFromCodesys = new Label();
WriteToCodesys = new Label();
SuspendLayout();
//
// textbox_iRead
//
textbox_iRead.Location = new Point(75, 70);
textbox_iRead.Name = "textbox_iRead";
textbox_iRead.Size = new Size(100, 23);
textbox_iRead.TabIndex = 0;
//
// picbox_bWrite
//
picbox_bWrite.AutoSize = true;
picbox_bWrite.BackColor = SystemColors.ButtonShadow;
picbox_bWrite.ForeColor = SystemColors.ControlDark;
picbox_bWrite.Location = new Point(284, 37);
picbox_bWrite.Name = "picbox_bWrite";
picbox_bWrite.Size = new Size(44, 17);
picbox_bWrite.TabIndex = 1;
picbox_bWrite.Text = " ";
//
// textbox_fRead
//
textbox_fRead.Location = new Point(75, 109);
textbox_fRead.Name = "textbox_fRead";
textbox_fRead.Size = new Size(100, 23);
textbox_fRead.TabIndex = 2;
//
// iRead
//
iRead.AutoSize = true;
iRead.Location = new Point(26, 65);
iRead.Name = "iRead";
iRead.Size = new Size(41, 17);
iRead.TabIndex = 3;
iRead.Text = "iRead";
//
// fRead
//
fRead.AutoSize = true;
fRead.Location = new Point(26, 109);
fRead.Name = "fRead";
fRead.Size = new Size(42, 17);
fRead.TabIndex = 3;
fRead.Text = "fRead";
//
// picbox_bRead
//
picbox_bRead.AutoSize = true;
picbox_bRead.BackColor = SystemColors.ButtonShadow;
picbox_bRead.Location = new Point(75, 37);
picbox_bRead.Name = "picbox_bRead";
picbox_bRead.Size = new Size(40, 17);
picbox_bRead.TabIndex = 1;
picbox_bRead.Text = " ";
//
// label1
//
label1.AutoSize = true;
label1.Location = new Point(226, 65);
label1.Name = "label1";
label1.Size = new Size(42, 17);
label1.TabIndex = 3;
label1.Text = "iWrite";
//
// label2
//
label2.AutoSize = true;
label2.Location = new Point(225, 109);
label2.Name = "label2";
label2.Size = new Size(43, 17);
label2.TabIndex = 3;
label2.Text = "fWrite";
//
// textbox_iWrite
//
textbox_iWrite.Location = new Point(284, 70);
textbox_iWrite.Name = "textbox_iWrite";
textbox_iWrite.Size = new Size(100, 23);
textbox_iWrite.TabIndex = 0;
textbox_iWrite.Text = "0";
//
// textbox_fWrite
//
textbox_fWrite.Location = new Point(284, 109);
textbox_fWrite.Name = "textbox_fWrite";
textbox_fWrite.Size = new Size(100, 23);
textbox_fWrite.TabIndex = 2;
textbox_fWrite.Text = "0";
//
// btnWrite
//
btnWrite.Location = new Point(284, 141);
btnWrite.Name = "btnWrite";
btnWrite.Size = new Size(100, 23);
btnWrite.TabIndex = 4;
btnWrite.Text = "Write2Mem";
btnWrite.UseVisualStyleBackColor = true;
btnWrite.Click += btnWrite_Click;
//
// label3
//
label3.AutoSize = true;
label3.Location = new Point(26, 37);
label3.Name = "label3";
label3.Size = new Size(46, 17);
label3.TabIndex = 3;
label3.Text = "bRead";
//
// bWrite
//
bWrite.AutoSize = true;
bWrite.Location = new Point(225, 37);
bWrite.Name = "bWrite";
bWrite.Size = new Size(47, 17);
bWrite.TabIndex = 3;
bWrite.Text = "bWrite";
//
// ReadFromCodesys
//
ReadFromCodesys.AutoSize = true;
ReadFromCodesys.Location = new Point(26, 9);
ReadFromCodesys.Name = "ReadFromCodesys";
ReadFromCodesys.Size = new Size(125, 17);
ReadFromCodesys.TabIndex = 3;
ReadFromCodesys.Text = "Read From Codesys";
//
// WriteToCodesys
//
WriteToCodesys.AutoSize = true;
WriteToCodesys.Location = new Point(226, 9);
WriteToCodesys.Name = "WriteToCodesys";
WriteToCodesys.Size = new Size(111, 17);
WriteToCodesys.TabIndex = 3;
WriteToCodesys.Text = "Write To Codesys";
//
// Form1
//
AutoScaleDimensions = new SizeF(7F, 17F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(401, 176);
Controls.Add(btnWrite);
Controls.Add(label2);
Controls.Add(fRead);
Controls.Add(bWrite);
Controls.Add(label1);
Controls.Add(WriteToCodesys);
Controls.Add(ReadFromCodesys);
Controls.Add(label3);
Controls.Add(iRead);
Controls.Add(textbox_fWrite);
Controls.Add(textbox_fRead);
Controls.Add(picbox_bRead);
Controls.Add(picbox_bWrite);
Controls.Add(textbox_iWrite);
Controls.Add(textbox_iRead);
Name = "Form1";
Text = "C# & Codesys Shared Memory";
Load += Form1_Load;
ResumeLayout(false);
PerformLayout();
}
#endregion
private TextBox textbox_iRead;
private Label picbox_bWrite;
private TextBox textbox_fRead;
private Label iRead;
private Label fRead;
private Label picbox_bRead;
private Label label1;
private Label label2;
private TextBox textbox_iWrite;
private TextBox textbox_fWrite;
private Button btnWrite;
private Label label3;
private Label bWrite;
private Label ReadFromCodesys;
private Label WriteToCodesys;
2.2 库函数声明
本项目中需要使用系统自带的MenoryMappedFiles来实现对共享内存空间的访问,同时,为了实时数据读取,需要使用多线程实现。
using System.IO.MemoryMappedFiles; //引用共享内存命名空间
using System.Threading;
using System.Runtime.InteropServices;
2.3 结构体定义
需要保证C#设置的结构体和codesys中设置的结构体一致。
定义了两个结构体,一个结构体读取codesys值,一个结构体从codesys中获取实际数据。
struct StrFromCodesys
{
internal bool bOut;
internal int iOut;
internal double fOut;
}
struct StrToCodesys
{
internal bool bIn;
internal int iIn;
internal double fIn;
}
2.4 实例化变量
项目需要实例化的内容如下所示:
MemoryMappedFile MMF_Write, MMF_Read;
MemoryMappedViewAccessor AccessorWrite, AccessorRead;
StrFromCodesys ParaFromCodesys;
StrToCodesys ParaToCodesys;
2.5 函数初始化
初始化函数如下,注意其中的变量名需要要Codesys的一致,如“Global\CODESYS_MEMORY_READ”。
private void InitMemory()
{
try
{
MMF_Write = MemoryMappedFile.OpenExisting("Global\\" + "CODESYS_MEMORY_READ", MemoryMappedFileRights.ReadWrite);
AccessorWrite = MMF_Write.CreateViewAccessor(0, Marshal.SizeOf(typeof(StrToCodesys)), MemoryMappedFileAccess.ReadWrite);
MMF_Read = MemoryMappedFile.OpenExisting("Global\\" + "CODESYS_MEMORY_WRITE", MemoryMappedFileRights.ReadWrite);
AccessorRead = MMF_Read.CreateViewAccessor(0, Marshal.SizeOf(typeof(StrFromCodesys)), MemoryMappedFileAccess.ReadWrite);
}
catch (Exception ex)
{
if (MMF_Write == null)
{
MMF_Write = MemoryMappedFile.CreateOrOpen("Global\\CODESYS_MEMORY_READ", 1024);
if (MMF_Write != null)
{
AccessorWrite = MMF_Write.CreateViewAccessor(1, Marshal.SizeOf(typeof(StrToCodesys)), MemoryMappedFileAccess.Write);
}
}
if (MMF_Read == null)
{
MMF_Read = MemoryMappedFile.CreateOrOpen("Global\\CODESYS_MEMORY_WRITE", 1024);
if (MMF_Read != null)
{
AccessorRead = MMF_Read.CreateViewAccessor(0, Marshal.SizeOf(typeof(StrFromCodesys)), MemoryMappedFileAccess.Read);
}
}
MessageBox.Show(ex.Message);
}
}
2.6 数据读取
基于参考网页的运行情况下,可能会出现窗体没有显示的情况,这是由于循环导致的进程杜塞,为了避免这一问题,数据读取采用的后台进程的处理方式,程序如下:
private void ReadData()
{
Thread readThread = new Thread(new ThreadStart(() =>
{
while (true)
{
try
{
Thread.Sleep(5);
AccessorRead.Read(0, out ParaFromCodesys); // 读取 Codesys 的数据
Thread.Sleep(5);
// 使用 Invoke 安全更新 UI
this.Invoke((Action)(() =>
{
if (ParaFromCodesys.bOut)
picbox_bRead.BackColor = Color.Green;
else
picbox_bRead.BackColor = Color.Gray;
textbox_iRead.Text = ParaFromCodesys.iOut.ToString("0");
textbox_fRead.Text = ParaFromCodesys.fOut.ToString("0.000");
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}));
readThread.IsBackground = true; // 设置为后台线程
readThread.Start(); // 启动线程
}
2.7 数据写程序
更新数据写程序中对bool型变量采取的是取反的操作方式,数据来源于实例化的结构体变量,程序如下:
private void btnWriteB()
{
ParaToCodesys.bIn = !ParaToCodesys.bIn;
AccessorWrite.Write(0, ref ParaToCodesys);
if (ParaToCodesys.bIn == true)
{
picbox_bWrite.BackColor = Color.Green;
}
else
{
picbox_bWrite.BackColor = Color.Red;
}
}
2.8 数据赋值和写数据
通过按钮触发数据的更新和数据写入共享内存的操作,对应的代码如下,其中需要特别注意的是浮点数的处理,需要采用双精度转换避免精度误差的出现。
代码实现如下;
private void btnWrite_Click(object sender, EventArgs e)
{
ParaToCodesys.bIn = true;
ParaToCodesys.iIn = int.Parse(textbox_iWrite.Text);
//避免数据精度问题,采用双精度方式转换
ParaToCodesys.fIn = double.Parse(textbox_fWrite.Text);
btnWriteB();
}
三、功能测试
通过运行对应的C#程序,在界面中可以看到对应的数据,点击write2Men按钮,界面的设置值将更新到Codesys共享的内存中,Codesys的数据如下所示。
注意事项
共享内存是一种更加接近底层、更加高效的IPC机制,特别适合大量数据的传输。如果涉及到有性能要求的进程间通信的场景,可以考虑使用共享内存。
但是,为了能够更好地使用共享内存,还需要注意以下事项:
1、数据一致性问题
共享内存并不自动提供同步机制,因此需要通过其他方法,比如multiprocessing.Lock等,来确保数据的一致性,尤其是在多个进程同时读写共享内存的场景中。
2、主动内存管理
在使用共享内存时,需要确保在使用完成后,显式调用close()方法和unlink()方法,从而确保内存资源被正确释放。unlink()方法调用后,会删除共享内存的标识符,使其不可用。
如果没有显式调用相应的方法,可能会导致内存泄露的问题发生。
3、数据结构
由于共享内存更偏向于底层的存储,内部更多的是字节存储,在实际使用中,需要选择恰当的数据结构,对共享内存进行映射,从而更急灵活地使用共享内存。
4、性能考虑
尽管共享内存比其他IPC机制性能更好,但是,如果没有进行正确的使用,可能会导致复杂性和性能的问题。此外,在使用中,需要仔细考虑数据的读写模式和共享内存区域的空间大小。
5、跨平台兼容性
Python的共享内存在不同操作系统上的实现可能会略有不同,所以在移植到其他平台时,需要进行充分的测试,以保证程序能够正常运行。
参考:
Python并发编程:一文搞懂为什么以及如何使用共享内存
共享内存 - C#与CoDeSys通讯