用C#一步步写串口通信 串口通信程序 c语言

最近在公司让用C#写一个串口调试的工具,要求向串口中输入16进制数据或字符串。下面我将这次遇到的问题和解决方法奉献出来,目的是和同行交流,回馈网友们提供的帮助,也是为了自己对知识加深一下巩固。

我们来看具体的实现步骤。

公司要求实现以下几个功能:

1):实现两台计算机之前的串口通信,以16进制形式和字符串两种形式传送和接收。

2):根据需要设置串口通信的必要参数。

3):定时发送数据。

4):保存串口设置。

看着好像挺复杂,其实都是纸老虎,一戳就破,前提是你敢去戳。我尽量讲的详细一些,争取说到每个知识点。

在编写程序前,需要将你要测试的COM口短接,就是收发信息都在本地计算机,短接的方式是将COM口的2、3号针接起来。COM口各针的具体作用,度娘是这么说的:COM口。记住2、3针连接一定要连接牢固,我就是因为接触不良,导致本身就不通,白白花掉了一大半天时间调试代码。

下面给出主要的操作界面,如下:



顺便,我将所有控件对应的代码名字也附上了,相信对初学者来说,再看下面的代码会轻松很多。控件名字命名的方法是“控件名+作用”的形式,例如“打开串口”的开关按钮,其名字是btnSwitch (btn就是button的简写了)。我认为这种命名控件的方式比较好,建议大家使用,如果你有好的命名方式,希望你能告诉我!

下面我们将各个功能按照从主到次的顺序逐个实现。(我分块给出代码实现,详细代码见链接:《C#串口通信工具》)

一、获取计算机的COM口总个数,将它们列为控件cbSerial的候选项,并将第一个设为cbSerial的默认选项。

这部分是在窗体加载时完成的。请看代码:

(很多信息代码的注释里讲的很清楚,我就不赘述了。)

[csharp] view plaincopyprint?

//检查是否含有串口

string[]str=SerialPort.GetPortNames();

if(str==null)

{

MessageBox.Show("本机没有串口!","Error");

return;

}

//添加串口项目

foreach(stringsinSystem.IO.Ports.SerialPort.GetPortNames())

{//获取有多少个COM口

cbSerial.Items.Add(s);

}

//串口设置默认选择项

用C#一步步写串口通信 串口通信程序 c语言
cbSerial.SelectedIndex=0;//设置<span><strong>cbSerial的默认选项</strong></span>

二、“串口设置”

这面我没代码编程,直接从窗体上按照串口信息设置就行。我们仅设置它们的默认选项,但这里我用到了ini文件,暂时不讲,我们先以下面形式设置默认。

[csharp] view plaincopyprint?

cbBaudRate.SelectedIndex=5;

cbDataBits.SelectedIndex=3;

cbStop.SelectedIndex=0;

cbParity.SelectedIndex=0;

radio1.Checked=true;//发送数据的“16进制”单选按钮(这里我忘了改名,现在看着很不舒服!)

rbRcvStr.Checked=true;

三、打开串口

在发送信息之前,我们需要根据选中的选项设置串口信息,并设置一些控件的属性,最后将串口打开。

[csharp] view plaincopyprint?

privatevoidbtnSwitch_Click(objectsender,EventArgse)

{

<span></span>//sp1是全局变量。SerialPortsp1=newSerialPort();

if(!sp1.IsOpen)

{

try

{

//设置串口号

stringserialName=cbSerial.SelectedItem.ToString();

sp1.PortName=serialName;

//设置各“串口设置”

stringstrBaudRate=cbBaudRate.Text;

stringstrDateBits=cbDataBits.Text;

stringstrStopBits=cbStop.Text;

Int32iBaudRate=Convert.ToInt32(strBaudRate);

Int32iDateBits=Convert.ToInt32(strDateBits);

sp1.BaudRate=iBaudRate;//波特率

sp1.DataBits=iDateBits;//数据位

switch(cbStop.Text)//停止位

{

case"1":

sp1.StopBits=StopBits.One;

break;

case"1.5":

sp1.StopBits=StopBits.OnePointFive;

break;

case"2":

sp1.StopBits=StopBits.Two;

break;

default:

MessageBox.Show("Error:参数不正确!","Error");

break;

}

switch(cbParity.Text)//校验位

{

case"无":

sp1.Parity=Parity.None;

break;

case"奇校验":

sp1.Parity=Parity.Odd;

break;

case"偶校验":

sp1.Parity=Parity.Even;

break;

default:

MessageBox.Show("Error:参数不正确!","Error");

break;

}

if(sp1.IsOpen==true)//如果打开状态,则先关闭一下

{

sp1.Close();

}

//状态栏设置

tsSpNum.Text="串口号:"+sp1.PortName+"|";

tsBaudRate.Text="波特率:"+sp1.BaudRate+"|";

tsDataBits.Text="数据位:"+sp1.DataBits+"|";

tsStopBits.Text="停止位:"+sp1.StopBits+"|";

tsParity.Text="校验位:"+sp1.Parity+"|";

//设置必要控件不可用

cbSerial.Enabled=false;

cbBaudRate.Enabled=false;

cbDataBits.Enabled=false;

cbStop.Enabled=false;

cbParity.Enabled=false;

sp1.Open();//打开串口

btnSwitch.Text="关闭串口";

}

catch(System.Exceptionex)

{

MessageBox.Show("Error:"+ex.Message,"Error");

return;

}

}

else

{

//状态栏设置

tsSpNum.Text="串口号:未指定|";

tsBaudRate.Text="波特率:未指定|";

tsDataBits.Text="数据位:未指定|";

tsStopBits.Text="停止位:未指定|";

tsParity.Text="校验位:未指定|";

//恢复控件功能

//设置必要控件不可用

cbSerial.Enabled=true;

cbBaudRate.Enabled=true;

cbDataBits.Enabled=true;

cbStop.Enabled=true;

cbParity.Enabled=true;

sp1.Close();//关闭串口

btnSwitch.Text="打开串口";

}

}

四、发送信息

因为这里涉及到字符的转换,难点在于,在发送16进制数据时,如何将文本框中的字符数据在内存中以同样的形式表现出来,例如我们输入16进制的“eb 90”显示到内存中,也就是如下形式:

  

或输入我们想要的任何字节,如上面的“12 34 56 78 90”.

内存中的数据时16进制显示的,而我们输入的数据时字符串,我们需要将字符串转换为对应的16进制数据,然后将这个16进制数据转换为字节数据,用到的主要方法是:

Convert.ToInt32 (String, Int32);

Convert.ToByte (Int32);

这是我想到的,如果你有好的方法,希望你能告诉我。

下面看代码:

[csharp] view plaincopyprint?

privatevoidbtnSend_Click(objectsender,EventArgse)

{

if(!sp1.IsOpen)//如果没打开

{

MessageBox.Show("请先打开串口!","Error");

return;

}

StringstrSend=txtSend.Text;

if(radio1.Checked==true)//“16进制发送”按钮

{

//处理数字转换,目的是将输入的字符按空格、“,”等分组,以便发送数据时的方便(此处转的比较麻烦,有高见者,请指点!)

stringsendBuf=strSend;

stringsendnoNull=sendBuf.Trim();

stringsendNOComma=sendnoNull.Replace(',','');//去掉英文逗号

stringsendNOComma1=sendNOComma.Replace(',','');//去掉中文逗号

stringstrSendNoComma2=sendNOComma1.Replace("0x","");//去掉0x

strSendNoComma2.Replace("0X","");//去掉0X

string[]strArray=strSendNoComma2.Split('');

<span></span>//strArray数组中会出现“”空字符的情况,影响下面的赋值操作,故将<span>byteBufferLength相应减小</span>

intbyteBufferLength=strArray.Length;

for(inti=0;i<<span>strArray.Length</span><span>;i++)</span>

{

if(strArray[i]=="")

{

byteBufferLength--;

}

}

byte[]byteBuffer=newbyte[byteBufferLength];

intii=0;<span></span>//用于给<span>byteBuffer赋值</span>

for(inti=0;i<strArray.Length;i++)//对获取的字符做相加运算

{

Byte[]bytesOfStr=Encoding.Default.GetBytes(strArray[i]);

intdecNum=0;

if(strArray[i]=="")

{

continue;

}

else

{

decNum=Convert.ToInt32(strArray[i],16);//atrArray[i]==12时,temp==18

}

try//防止输错,使其只能输入一个字节的字符,即只能在txtSend里输入“eb90”等字符串,不能输入“1232345”等超出字节范围的数字

{

byteBuffer[ii]=Convert.ToByte(decNum);

}

catch(System.Exceptionex)

{

MessageBox.Show("字节越界,请逐个字节输入!","Error");

return;

}

ii++;

}

sp1.Write(byteBuffer,0,byteBuffer.Length);

}

else//以字符串形式发送时

{

sp1.WriteLine(txtSend.Text);//写入数据

}

}

五、数据的接收

亮点来了,看到这里,如果你还没吐(可能是我的代码比较拙劣!),那么下面的知识点对你也不成问题。

这里需要用到 委托 的知识,我是搞C/C++出身,刚碰到这个知识点还真有点不适应。为了不偏离主题,关于委托,我仅给出两条比较好的链接,需要的网友可以去加深学习:C#委托、订阅委托事件。

在窗体加载时就订阅上委托是比较好的,所以在Form1_Load中添加以下代码:

[csharp] view plaincopyprint?

Control.CheckForIllegalCrossThreadCalls=false;//意图见解释

sp1.DataReceived+=newSerialDataReceivedEventHandler(sp1_DataReceived);//订阅委托

注意,因为自.net 2.0以后加强了安全机制,,不允许在winform中直接跨线程(事件触发需要产生一个线程处理)访问控件的属性,第一条代码的意图是说在这个类中我们强制不检查跨线程的调用是否合法。处理这种问题的解决方案有很多,具体可参阅以下内容:解决方案。

好了,订阅委托之后,我们就可以处理接收数据的事件了。

[csharp] view plaincopyprint?

voidsp1_DataReceived(objectsender,SerialDataReceivedEventArgse)

{

if(sp1.IsOpen)//此处可能没有必要判断是否打开串口,但为了严谨性,我还是加上了

{

byte[]byteRead=newbyte[sp1.BytesToRead];//BytesToRead:sp1接收的字符个数

if(rdSendStr.Checked)//'发送字符串'单选按钮

{

txtReceive.Text+=sp1.ReadLine()+"rn";//注意:回车换行必须这样写,单独使用"r"和"n"都不会有效果

sp1.DiscardInBuffer();//清空SerialPort控件的Buffer

}

else//'发送16进制按钮'

{

try

{

Byte[]receivedData=newByte[sp1.BytesToRead];//创建接收字节数组

sp1.Read(receivedData,0,receivedData.Length);//读取数据

sp1.DiscardInBuffer();//清空SerialPort控件的Buffer

stringstrRcv=null;

for(inti=0;i<receivedData.Length;i++)//窗体显示

{

strRcv+=receivedData[i].ToString("X2");//16进制显示

}

txtReceive.Text+=strRcv+"rn";

}

catch(System.Exceptionex)

{

MessageBox.Show(ex.Message,"出错提示");

txtSend.Text="";

}

}

}

else

{

MessageBox.Show("请打开某个串口","错误提示");

}

}

为了友好和美观,我将当前时间也显示出来,又将显示字体的颜色做了修改:

[csharp] view plaincopyprint?

<span></span>//输出当前时间

DateTimedt=DateTime.Now;

txtReceive.Text+=dt.GetDateTimeFormats('f')[0].ToString()+"rn";

txtReceive.SelectAll();

txtReceive.SelectionColor=Color.Blue;//改变字体的颜色

做到这里,大部分功能就已实现了,剩下的工作就是些简单的操作设置了,有保存设置、定时发送信息、控制文本框输入内容等。

六、保存设置

这部分相对简单,但当时我没接触过,也花了点时间,现在想想,也不过如此。

保存用户设置用ini文件是个不错的选择,虽然大部分都用注册表实现,但ini文件保存还是有比较广泛的使用。

.ini 文件是Initialization File的缩写,也就是初始化文件。

为了不偏离正题,也不过多说明,可参考相关内容(网上资源都不错,因人而异,就不加链接了)。

使用Inifile读写ini文件,这里我用到了两个主要方法:

[csharp] view plaincopyprint?

//读出ini文件

a:=inifile.Readstring('节点','关键字',缺省值);//string类型

b:=inifile.Readinteger('节点','关键字',缺省值);//integer类型

c:=inifile.Readbool('节点','关键字',缺省值);//boolean类型

其中[缺省值]为该INI文件不存在该关键字时返回的缺省值。

//写入INI文件:

inifile.writestring('节点','关键字',变量或字符串值);

inifile.writeinteger('节点','关键字',变量或整型值);

inifile.writebool('节点','关键字',变量或True或False);

请看代码:

[csharp] view plaincopyprint?

//using省写了

namespaceINIFILE

{

classProfile

{

publicstaticvoidLoadProfile()

{

stringstrPath=AppDomain.CurrentDomain.BaseDirectory;

_file=newIniFile(strPath+"Cfg.ini");

G_BAUDRATE=_file.ReadString("CONFIG","BaudRate","4800");//读数据,下同

G_DATABITS=_file.ReadString("CONFIG","DataBits","8");

G_STOP=_file.ReadString("CONFIG","StopBits","1");

G_PARITY=_file.ReadString("CONFIG","Parity","NONE");

}

publicstaticvoidSaveProfile()

{

stringstrPath=AppDomain.CurrentDomain.BaseDirectory;

_file=newIniFile(strPath+"Cfg.ini");

_file.WriteString("CONFIG","BaudRate",G_BAUDRATE);//写数据,下同

_file.WriteString("CONFIG","DataBits",G_DATABITS);

_file.WriteString("CONFIG","StopBits",G_STOP);

_file.WriteString("CONFIG","G_PARITY",G_PARITY);

}

privatestaticIniFile_file;//内置了一个对象

publicstaticstringG_BAUDRATE="1200";//给ini文件赋新值,并且影响界面下拉框的显示

publicstaticstringG_DATABITS="8";

publicstaticstringG_STOP="1";

publicstaticstringG_PARITY="NONE";

}

}

_file声明成了内置对象,可以方便各函数的调用。

下面是“保存设置”的部分代码:

[csharp] view plaincopyprint?

privatevoidbtnSave_Click(objectsender,EventArgse)

{

//设置各“串口设置”

stringstrBaudRate=cbBaudRate.Text;

stringstrDateBits=cbDataBits.Text;

stringstrStopBits=cbStop.Text;

Int32iBaudRate=Convert.ToInt32(strBaudRate);

Int32iDateBits=Convert.ToInt32(strDateBits);

Profile.G_BAUDRATE=iBaudRate+"";//波特率

Profile.G_DATABITS=iDateBits+"";//数据位

switch(cbStop.Text)//停止位

{

case"1":

Profile.G_STOP="1";

break;

case"1.5":

Profile.G_STOP="1.5";

break;

//防止过多刷屏,下面省写了

……

}

switch(cbParity.Text)//校验位

{

case"无":

Profile.G_PARITY="NONE";

break;

…………

}

Profile.SaveProfile();//保存设置

}

读取ini文件主要在加载窗体时执行:

INIFILE.Profile.LoadProfile();//加载所有

七、控制文本输入这里倒挺简单,只是注意一点。当我们控制输入非法字符时,可通过控制e.Handed的属性值实现,注意这里的Handed属性是“操作过”的含义,而非“执行此处操作”之意,Handled是过去式,看字面意思,"操作过的=是;",将这个操作的状态设为已处理过,自然就不会再处理了。具体参见MSDN:Handed

[csharp] view plaincopyprint?

privatevoidtxtSend_KeyPress(objectsender,KeyPressEventArgse)

{

if(radio1.Checked==true)

{

//正则匹配

stringpatten="[0-9a-fA-F]|b|0x|0X|";//“b”:退格键

Regexr=newRegex(patten);

Matchm=r.Match(e.KeyChar.ToString());

if(m.Success)//&&(txtSend.Text.LastIndexOf("")!=txtSend.Text.Length-1))

{

e.Handled=false;

}

else

{

e.Handled=true;

}

}//endofradio1

八、定时发送信息

这边看似很简单,但也有一点需要注意,当定时器生效时,我们要间隔访问“发送”按键的内容,怎么实现?还好MS给我们提供了必要的支持,使用Button的 PerformClick可以轻松做到, PerformClick参见MSDN:PerformClick

[csharp] view plaincopyprint?

privatevoidtmSend_Tick(objectsender,EventArgse)

{

//转换时间间隔

stringstrSecond=txtSecond.Text;

try

{

intisecond=int.Parse(strSecond)*1000;//Interval以微秒为单位

tmSend.Interval=isecond;

if(tmSend.Enabled==true)

{

btnSend.PerformClick();//产生“发送”的click事件

}

}

catch(System.Exceptionex)

{

MessageBox.Show("错误的定时输入!","Error");

}

}

注意在一些情况下不要忘了让定时器失效,如在取消“定时发送数据"和“关闭串口”时等。

好了,主要内容就是这些,希望以上内容对大家有所帮助,如你有好的想法,还请不吝赐教!

  

爱华网本文地址 » http://www.413yy.cn/a/25101013/169038.html

更多阅读

团支书用英语怎么说写 团支书用英语怎么说

团支书用英语怎么说写?团支书用英语翻译是什么?团支书用英语怎么说?团支书用英语怎么写?团支书用英语怎么说写 League Secretary“我当过班里的团支书,学习委员,和英语协会的副会长” I used to act as the secre

一撇一捺写人生 一撇一捺是什么字

人字两笔,一撇一捺,简单易写,但一撇一捺书写人生,就没那么容易了,有一笔写不好,人生就不完整。一撇写父母,一捺写儿女。父母赐予你生命,把你扶养成人,恩重如山,尽儿女孝心,天经地义;儿女带给你快乐,把他扶养成人,不求回报,让儿女过得好,是父母的心愿

C语言实现ICMP协议Ping命令 c语言写ping命令

大部分人用ping命令只是作为查看另一个系统的网络连接是否正常的一种简单方法。在这篇文章中,作者将介绍如何用C语言编写一个模拟ping命令功能的程序。ping命令是用来查看网络上另一个主机系统的网络连接是否正常的一个工具。ping命

声明:《用C#一步步写串口通信 串口通信程序 c语言》为网友比我糟吗分享!如侵犯到您的合法权益请联系我们删除