用C#一步步写串口通信_第1页
用C#一步步写串口通信_第2页
用C#一步步写串口通信_第3页
用C#一步步写串口通信_第4页
用C#一步步写串口通信_第5页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

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

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

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

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

3):定时发送数据。

4):保存串口设置。

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

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

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

顺便,我将所有控件对应的代码名字也附上了,相信对初学者来说,再看下面的代码会轻松很多。控件名字命名的方法是“控件名+作用”的形式,例如“打开串口”的开关按钮,其名字是btnSwitch

(btn就是button的简写了)。我认为这种命名控件的方式比较好,建议大家使用,如果你有好的命名方式,希望你能告诉我!

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

一、获取计算机的COM口总个数,将它们列为控件cbSerial的候选项,并将第一个设为cbSerial的默认选项。这部分是在窗体加载时完成的。请看代码:

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

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?//检查是否含有串口

string[]

str

=

SerialPort.GetPortNames();

if

(str

==

null)

{

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

"Error");

return;

}

//添加串口项目

foreach

(string

s

in

System.IO.Ports.SerialPort.GetPortNames())

{//获取有多少个COM口

cbSerial.Items.Add(s);

}

//串口设置默认选择项

cbSerial.SelectedIndex

=

0;

//设置<span

style="font-size:18px;

"><strong>cbSerial的默认选项</strong></span>

二、“串口设置”

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

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?

cbBaudRate.SelectedIndex

=

5;

cbDataBits.SelectedIndex

=

3;

cbStop.SelectedIndex

=

0;

cbParity.SelectedIndex

=

0;

radio1.Checked

=

true;

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

rbRcvStr.Checked

=

true;

三、打开串口

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

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?

private

void

btnSwitch_Click(object

sender,

EventArgs

e)

{

<span

style="white-space:pre">

</span>//sp1是全局变量。

SerialPort

sp1

=

new

SerialPort();

if

(!sp1.IsOpen)

{

try

{

//设置串口号

string

serialName

=

cbSerial.SelectedItem.ToString();

sp1.PortName

=

serialName;

//设置各“串口设置”

string

strBaudRate

=

cbBaudRate.Text;

string

strDateBits

=

cbDataBits.Text;

string

strStopBits

=

cbStop.Text;

Int32

iBaudRate

=

Convert.ToInt32(strBaudRate);

Int32

iDateBits

=

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.Exception

ex)

{

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进制的“eb90”显示到内存中,也就是如下形式:

或输入我们想要的任何字节,如上面的“1234567890”.

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

(String,Int32);

Convert.ToByte

(Int32);这是我想到的,如果你有好的方法,希望你能告诉我。下面看代码:[csharp]

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?

private

void

btnSend_Click(object

sender,

EventArgs

e)

{

if

(!sp1.IsOpen)

//如果没打开

{

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

"Error");

return;

}

String

strSend

=

txtSend.Text;

if

(radio1.Checked

==

true)

//“16进制发送”

按钮

{

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

string

sendBuf

=

strSend;

string

sendnoNull

=

sendBuf.Trim();

string

sendNOComma

=

sendnoNull.Replace(',',

'

');

//去掉英文逗号

string

sendNOComma1

=

sendNOComma.Replace(',',

'

');

//去掉中文逗号

string

strSendNoComma2

=

sendNOComma1.Replace("0x",

"");

//去掉0x

strSendNoComma2.Replace("0X",

"");

//去掉0X

string[]

strArray

=

strSendNoComma2.Split('

');

<span

style="white-space:pre">

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

style="background-color:

rgb(255,

255,

255);

">byteBufferLength相应减小</span>

int

byteBufferLength

=

strArray.Length;

for

(int

i

=

0;

i

<<span

style="background-color:

rgb(255,

255,

255);

">strArray.Length</span><span

style="background-color:

rgb(255,

255,

255);

">;

i++

)</span>

{

if

(strArray[i]=="")

{

byteBufferLength--;

}

}

byte[]

byteBuffer

=

new

byte[byteBufferLength];

int

ii

=

0;<span

style="white-space:pre">

</span>//用于给<span

style="background-color:

rgb(255,

255,

255);

">byteBuffer赋值</span>

for

(int

i

=

0;

i

<

strArray.Length;

i++)

//对获取的字符做相加运算

{

Byte[]

bytesOfStr

=

Encoding.Default.GetBytes(strArray[i]);

int

decNum

=

0;

if

(strArray[i]

==

"")

{

continue;

}

else

{

decNum

=

Convert.ToInt32(strArray[i],

16);

//atrArray[i]

==

12时,temp

==

18

}

try

//防止输错,使其只能输入一个字节的字符,即只能在txtSend里输入

“eb

90”等字符串,不能输入“123

2345”等超出字节范围的数字

{

byteBuffer[ii]

=

Convert.ToByte(decNum);

}

catch

(System.Exception

ex)

{

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

"Error");

return;

}

ii++;

}

sp1.Write(byteBuffer,

0,

byteBuffer.Length);

}

else

//以字符串形式发送时

{

sp1.WriteLine(txtSend.Text);

//写入数据

}

}

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

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

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

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?Control.CheckForIllegalCrossThreadCalls

=

false;

//意图见解释

sp1.DataReceived

+=

new

SerialDataReceivedEventHandler(sp1_DataReceived);

//订阅委托

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

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

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?void

sp1_DataReceived(object

sender,

SerialDataReceivedEventArgs

e)

{

if

(sp1.IsOpen)

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

{

byte[]

byteRead

=

new

byte[sp1.BytesToRead];

//BytesToRead:sp1接收的字符个数

if

(rdSendStr.Checked)

//'发送字符串'单选按钮

{

txtReceive.Text

+=

sp1.ReadLine()

+

"\r\n";

//注意:回车换行必须这样写,单独使用"\r"和"\n"都不会有效果

sp1.DiscardInBuffer();

//清空SerialPort控件的Buffer

}

else

//'发送16进制按钮'

{

try

{

Byte[]

receivedData

=

new

Byte[sp1.BytesToRead];

//创建接收字节数组

sp1.Read(receivedData,

0,

receivedData.Length);

//读取数据

sp1.DiscardInBuffer();

//清空SerialPort控件的Buffer

string

strRcv

=

null;

for

(int

i

=

0;

i

<

receivedData.Length;

i++)

//窗体显示

{

strRcv

+=

receivedData[i].ToString("X2");

//16进制显示

}

txtReceive.Text

+=

strRcv

+

"\r\n";

}

catch

(System.Exception

ex)

{

MessageBox.Show(ex.Message,

"出错提示");

txtSend.Text

=

"";

}

}

}

else

{

MessageBox.Show("请打开某个串口",

"错误提示");

}

}

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

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?<span

style="white-space:pre">

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

DateTime

dt

=

DateTime.Now;

txtReceive.Text

+=

dt.GetDateTimeFormats('f')[0].ToString()

+

"\r\n";

txtReceive.SelectAll();

txtReceive.SelectionColor

=

Color.Blue;

//改变字体的颜色

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

六、保存设置

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

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

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

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

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

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?//读出ini文件

a:=ini('节点','关键字',缺省值);//

string类型

b:=ini('节点','关键字',缺省值);//

integer类型

c:=ini('节点','关键字',缺省值);//

boolean类型

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

//写入INI文件:

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

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

ini('节点','关键字',变量或True或False);

请看代码:[csharp]

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?//using

省写了

namespace

INIFILE

{

class

Profile

{

public

static

void

LoadProfile()

{

string

strPath

=

AppDomain.CurrentDomain.BaseDirectory;

_file

=

new

Ini

+

"Cfg.ini");

G_BAUDRATE

=

_("CONFIG",

"BaudRate",

"4800");

//读数据,下同

G_DATABITS

=

_("CONFIG",

"DataBits",

"8");

G_STOP

=

_("CONFIG",

"StopBits",

"1");

G_PARITY

=

_("CONFIG",

"Parity",

"NONE");

}

public

static

void

SaveProfile()

{

string

strPath

=

AppDomain.CurrentDomain.BaseDirectory;

_file

=

new

Ini

+

"Cfg.ini");

_("CONFIG",

"BaudRate",

G_BAUDRATE);

//写数据,下同

_("CONFIG",

"DataBits",

G_DATABITS);

_("CONFIG",

"StopBits",

G_STOP);

_("CONFIG",

"G_PARITY",

G_PARITY);

}

private

static

IniFile

_内置了一个对象

public

static

string

G_BAUDRATE

=

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

public

static

string

G_DATABITS

=

"8";

public

static

string

G_STOP

=

"1";

public

static

string

G_PARITY

=

"NONE";

}

}

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

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

\o"viewplain"viewplain\o"copy"copy\o"print"print\o"?"?private

void

btnSave_Click(object

sender,

EventArgs

e)

{

//设置各“串口设置”

string

strBaudRate

=

cbBaudRate.Text;

string

strDateBits

=

cbDataBits.Text;

string

strStopBits

=

cbStop.Text;

Int32

iBaudRate

=

Convert.ToInt32(strBaudRate);

Int32

iDateBits

=

Convert.ToInt32(strDateBits);

Pro

=

iBaudRate+"";

//波特率

Pro

=

iDateBits+"";

//数据位

switch

(cbStop.Text)

//停止位

{

case

"1":

Pro

=

"1";

break;

case

"1.5":

Pro

=

"1.5";

break;

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

……

}

switch

(cbParity.Text)

//校验位

{

case

"无":

Pro

=

"NONE";

break;

…………

}

Pro();

//保存设置

}

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

INI();//加载所有七、控制文本输入这里倒挺简单,只是注意一点。当我们控制输入非法字符时,可通过控制e.Handed的属性值实现,注意这里的Handed属性是“操作过”的含义,而非“执行此处操作”之意,Handled是过去式,看字面意思,"操作过的=是;",将这

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论