关键词:SerialPort,RS485,ModbusRTU,delegate(委托),BeginInvoke,
本文使用VS2015,.net版本是4--因为考虑要在XP生产用机器上使用。
最初,公司需要做RS485的IAP程序用来给设备升级(原来有相关升级程序,是使用USB口的串口直接IAP代码,那个例程我也看过,比modbusRTU的要简单的很多,当然,我没测试过,只是简单看了一下,感觉比用modbusRTU逻辑上一样,但代码要少的多),但是原来的代码时第三方做的,在时间和可控性上有点延误,于是决定我们自己搞一个。于是有了下文。
本文是RS485的modbusRTU基本通信,IAP升级相关的会在以后更新。
c#我不熟啊,可以说基本不会,于是我开始了网上搜索过程,从优快云下载了几个代码(花了不少),然后分析了一顿代码,对IAP和串口升级代码都有了一定理解,而一直没找到相关可用的RS485 modbusRTU IAP代码,modbusRTU用了有一段时间了,那就干脆用c#来搞一下RS485通信吧。
用以上找到的代码看了一下,都是使用NModbus,NModbus4,NModbus.Serial来搞,而这个程序包在使用上基本都是从站地址,指令,数据,CRC这样的组合方式,在基本的modbusRTU来说足够用了,但是如果用一些基于RS485的特殊指令就有点困难了(也许我没查到相关方法),于是决定直接用SerialPort来写。
代码上用了很多其他人写的函数方法,毕竟通用性强,而且我懒,还有何必重复造。
整体思路:
打开程序,自动扫描设备,并按照默认参数设置相关参数,点击“打开串口”按钮直接按默认参数打开连接,
设置参数:添加菜单,可以设置串口参数,并在关闭窗口的时候把参数传回Form1的相关参数,
打开串口后,点击“发送”按钮,可以发送文本框中的指令(为什么用文本字符串?因为很多指令都是现成的,我就是想直接发送,而不要再拆分到NModbus的参数中,这样不香吗,我认为很香,而且这样的限制几乎没有,我们有很多自定义的指令可以使用,如果限定在那写modbusRTU指令中,特殊指令就没法操作了),从机方面收到相关指令就会返回相应数据。
以上是主要流程,再来补充一下细节:
1,指令的HEX转码,因为指令是ASCII码文本,需要转换为HEX字节集才能正常发送到从机并被正确理解和返回数据,直接上代码,拿去用
//转换成HEX格式数据,即16进制数据
public byte[] HexStringToBytes(string hs)
{
string[] strArr = hs.Trim().Split(' ');
byte[] b = new byte[strArr.Length];
if (!sending) { return b; }
//逐个字符变为16进制字节数据
for (int i = 0; i < strArr.Length; i++)
{
b[i] = Convert.ToByte(strArr[i], 16);
}
//按照指定编码将字节数组变为字符串
comlength = strArr.Length;
return b;
}
2,SerialPort的码率,必须和从机一致,在本机使用虚拟串口可以不用限制,我的本机测试是这样。
3,异步收到数据的处理,
声明委托和串口参数:
public delegate void MyDelegate(string s);
public string portName;//连接参数,串口名
public int baudRate;//连接参数,波特率
public Parity parity;//连接参数,奇偶校验
public int dataBits;//连接参数,数据位
public StopBits stopBits;//连接参数,停止位
可以看到,parity和stopBits是专用类型,需要处理,其他几个参数直接使用即可。
设置串口:
switch (f2parity)//奇偶校验索引
{
case 0:
parity = Parity.Odd;
break;
case 1:
parity = Parity.Even;
break;
case 2:
parity = Parity.None;
break;
default:
break;
}
// dataBits 数据位 已经赋值,Form2返回也会赋值
switch (f2stopBits)//停止位
{
case 0:
stopBits = StopBits.One;
break;
case 1:
stopBits = StopBits.Two;
break;
default:
break;
}
comport = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
//收到返回数据的事件处理绑定函数
comport.DataReceived += new SerialDataReceivedEventHandler(DataReceived_Simplea);
发送数据:
comport.Open();
comport.Write(HexStringToBytes(textBox1.Text), 0, comlength);
comport.Write(HexStringToBytes(textBox1.Text), 0, comlength);
Write三个参数分别是转换位HEX字节集的函数调用返回数据,开始发送的位置,发送长度
相应串口收到数据事件的代码:
public void DataReceived_Simplea(object sender, SerialDataReceivedEventArgs e)
{
MyDelegate mydele1 = new MyDelegate(xianshitextbox2);
MyDelegate mydele2 = new MyDelegate(xianshitextbox3);
shoudaocishu ++;
textBox3.BeginInvoke(mydele2, shoudaocishu.ToString());
DateTime dt = DateTime.Now;
//16进制发送数据还原
int i_InNum;//输入缓冲区中字节数
i_InNum = comport.BytesToRead;
Byte[] ReceivedData = new Byte[comport.BytesToRead];//创建接收字节数组
comport.Read(ReceivedData, 0, ReceivedData.Length);//读取接收的数据
String RecvDataText = null;
for (int i = 0; i < ReceivedData.Length; i++)
{
RecvDataText += (ReceivedData[i].ToString("X2") + "");
}
textBox3.BeginInvoke(mydele1, RecvDataText);//使用delegate委托更新textbox2
//丢弃接收缓冲区数据
comport.DiscardInBuffer();
}
委托函数:
public void xianshitextbox2(string s)
{
textBox2.Text += s + "\r\n";
}
public void xianshitextbox3(string s)
{
textBox3.Text = s;
}
如果在设置textBox.text的时候直接使用textBox.text = 数据 会报线程安全错误,所以需要做以上代码中的处理:
声明委托,然后实例化委托,然后给相关控件执行BeginInvoke(不推荐用Invoke,会造成阻塞),
途中是和从机通信返回的数据,数据正确不用较真,只是模拟数据。
题外话:再来说说从机的使用----
还是另起一个文章说说我的从机使用吧。