用C#一步步写串口通信
更新时间:2024-04-30 02:26:01 阅读量: 综合文库 文档下载
最近在公司让用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?
1. //检查是否含有串口
2. string[] str = SerialPort.GetPortNames(); 3. if (str == null) 4. {
5. MessageBox.Show(\本机没有串口!\ 6. return; 7. } 8.
9. //添加串口项目
10. foreach (string s in System.IO.Ports.SerialPort.GetPortNames()) 11. {//获取有多少个COM口 12. cbSerial.Items.Add(s); 13. } 14.
15. //串口设置默认选择项
16. cbSerial.SelectedIndex = 0; //设置
二、“串口设置”
这面我没代码编程,直接从窗体上按照串口信息设置就行。我们仅设置它们的默认选项,但这里我用到了ini文件,暂时不讲,我们先以下面形式设置默认。
[csharp] view plaincopyprint?
1. cbBaudRate.SelectedIndex = 5; 2. cbDataBits.SelectedIndex = 3; 3. cbStop.SelectedIndex = 0; 4. cbParity.SelectedIndex = 0;
5. radio1.Checked = true; //发送数据的“16进制”单选按钮(这里我忘了改名,现在看着很不舒服!) 6. rbRcvStr.Checked = true;
三、打开串口
在发送信息之前,我们需要根据选中的选项设置串口信息,并设置一些控件的属性,最后将串口打开。
[csharp] view plaincopyprint?
1. private void btnSwitch_Click(object sender, EventArgs e) 2. {
3.
8. //设置串口号
9. string serialName = cbSerial.SelectedItem.ToString(); 10. sp1.PortName = serialName; 11.
12. //设置各“串口设置”
13. string strBaudRate = cbBaudRate.Text; 14. string strDateBits = cbDataBits.Text; 15. string strStopBits = cbStop.Text;
16. Int32 iBaudRate = Convert.ToInt32(strBaudRate); 17. Int32 iDateBits = Convert.ToInt32(strDateBits); 18.
19. sp1.BaudRate = iBaudRate; //波特率 20. sp1.DataBits = iDateBits; //数据位 21. switch (cbStop.Text) //停止位 22. { 23. case \
24. sp1.StopBits = StopBits.One; 25. break; 26. case \
27. sp1.StopBits = StopBits.OnePointFive; 28. break; 29. case \
30. sp1.StopBits = StopBits.Two; 31. break; 32. default:
33. MessageBox.Show(\:参数不正确!\ 34. break;
SerialPort sp1 = new SerialPort(); 35. }
36. switch (cbParity.Text) //校验位 37. {
38. case \无\
39. sp1.Parity = Parity.None; 40. break; 41. case \奇校验\
42. sp1.Parity = Parity.Odd; 43. break; 44. case \偶校验\
45. sp1.Parity = Parity.Even; 46. break; 47. default:
48. MessageBox.Show(\:参数不正确!\ 49. break; 50. } 51.
52. if (sp1.IsOpen == true)//如果打开状态,则先关闭一下 53. {
54. sp1.Close(); 55. }
56. //状态栏设置
57. tsSpNum.Text = \串口号:\ 58. tsBaudRate.Text = \波特率:\ 59. tsDataBits.Text = \数据位:\ 60. tsStopBits.Text = \停止位:\ 61. tsParity.Text = \校验位:\ 62.
63. //设置必要控件不可用 64. cbSerial.Enabled = false; 65. cbBaudRate.Enabled = false; 66. cbDataBits.Enabled = false; 67. cbStop.Enabled = false; 68. cbParity.Enabled = false; 69.
70. sp1.Open(); //打开串口 71. btnSwitch.Text = \关闭串口\ 72. }
73. catch (System.Exception ex) 74. {
75. MessageBox.Show(\ 76. return; 77. } 78. }
79. else 80. {
81. //状态栏设置
82. tsSpNum.Text = \串口号:未指定|\ 83. tsBaudRate.Text = \波特率:未指定|\ 84. tsDataBits.Text = \数据位:未指定|\ 85. tsStopBits.Text = \停止位:未指定|\ 86. tsParity.Text = \校验位:未指定|\ 87. //恢复控件功能 88. //设置必要控件不可用 89. cbSerial.Enabled = true; 90. cbBaudRate.Enabled = true; 91. cbDataBits.Enabled = true; 92. cbStop.Enabled = true; 93. cbParity.Enabled = true; 94.
95. sp1.Close(); //关闭串口 96. btnSwitch.Text = \打开串口\ 97. } 98. }
四、发送信息
因为这里涉及到字符的转换,难点在于,在发送16进制数据时,如何将文本框中的字符数据在内存中以同样的形式表现出来,例如我们输入16进制的“eb 90”显示到内存中,也就是如下形式:
或输入我们想要的任何字节,如上面的“12 34 56 78 90”.
内存中的数据时16进制显示的,而我们输入的数据时字符串,我们需要将字符串转换为对应的16进制数据,然后将这个16进制数据转换为字节数据,用到的主要方法是: Convert.ToInt32 (String, Int32); Convert.ToByte (Int32);
这是我想到的,如果你有好的方法,希望你能告诉我。 下面看代码:
[csharp] view plaincopyprint?
1. private void btnSend_Click(object sender, EventArgs e) 2. {
3. if (!sp1.IsOpen) //如果没打开 4. {
5. MessageBox.Show(\请先打开串口!\ 6. return; 7. } 8.
9. String strSend = txtSend.Text;
10. if (radio1.Checked == true) //“16进制发送” 按钮 11. {
12. //处理数字转换,目的是将输入的字符按空格、“,”等分组,以便发送数据时的方便(此处转的比
较麻烦,有高见者,请指点!) 13. string sendBuf = strSend;
14. string sendnoNull = sendBuf.Trim();
15. string sendNOComma = sendnoNull.Replace(',', ' '); //去掉英文逗号 16. string sendNOComma1 = sendNOComma.Replace(',', ' '); //去掉中文逗号
17. string strSendNoComma2 = sendNOComma1.Replace(\去掉0x 18. strSendNoComma2.Replace(\去掉0X 19. string[] strArray = strSendNoComma2.Split(' '); 20.
21. //strArray数组中会出现“”空字符的情况,影响下面的赋
值操作,故将
22. int byteBufferLength = strArray.Length; 23. for 24. {
25. if (strArray[i]==\ 26. {
27. byteBufferLength--; 28. } 29. } 30.
31. byte[] byteBuffer = new byte[byteBufferLength];
32. int ii = 0;//用于给
rgb(255, 255, 255); \赋值
33. for (int i = 0; i < strArray.Length; i++) //对获取的字符做相加运算 34. { 35.
36. Byte[] bytesOfStr = Encoding.Default.GetBytes(strArray[i]); 37.
38. int decNum = 0; 39. if (strArray[i] == \ 40. { 41. continue; 42. } 43. else 44. {
45. decNum = Convert.ToInt32(strArray[i], 16); //atrArray[i] == 12时,temp == 18 46. } 47.
48. try //防止输错,使其只能输入一个字节的字符,即只能在txtSend里输入 “eb 90”等字符串,不能
输入“123 2345”等超出字节范围的数字 49. {
50. byteBuffer[ii] = Convert.ToByte(decNum); 51. }
52. catch (System.Exception ex) 53. {
54. MessageBox.Show(\字节越界,请逐个字节输入!\ 55. return;
(int
i
=
0;
i
<
style=\
rgb(255,
255,
255);
\
56. } 57. 58. ii++; 59. }
60. sp1.Write(byteBuffer, 0, byteBuffer.Length); 61. }
62. else //以字符串形式发送时 63. {
64. sp1.WriteLine(txtSend.Text); //写入数据 65. } 66. }
五、数据的接收
亮点来了,看到这里,如果你还没吐(可能是我的代码比较拙劣!),那么下面的知识点对你也不成问题。
这里需要用到 委托 的知识,我是搞C/C++出身,刚碰到这个知识点还真有点不适应。为了不偏离主题,关于委托,我仅给出两条比较好的链接,需要的网友可以去加深学习:C#委托、订阅委托事件。
在窗体加载时就订阅上委托是比较好的,所以在Form1_Load中添加以下代码:
[csharp] view plaincopyprint?
1. Control.CheckForIllegalCrossThreadCalls = false; //意图见解释
2. sp1.DataReceived += new SerialDataReceivedEventHandler(sp1_DataReceived); //订阅委托
注意,因为自.net 2.0以后加强了安全机制,,不允许在winform中直接跨线程(事件触发需要产生一个线程处理)访问控件的属性,第一条代码的意图是说在这个类中我们强制不检查跨线程的调用是否合法。处理这种问题的解决方案有很多,具体可参阅以下内容:解决方案。 好了,订阅委托之后,我们就可以处理接收数据的事件了。
[csharp] view plaincopyprint?
1. void sp1_DataReceived(object sender, SerialDataReceivedEventArgs e) 2. {
3. if (sp1.IsOpen) //此处可能没有必要判断是否打开串口,但为了严谨性,我还是加上了 4. {
5. byte[] byteRead = new byte[sp1.BytesToRead]; //BytesToRead:sp1接收的字符个数 6. if (rdSendStr.Checked) //'发送字符串'单选按钮 7. {
8. txtReceive.Text += sp1.ReadLine() + \注意:回车换行必须这样写,单独使用\和\都
不会有效果
9. sp1.DiscardInBuffer(); //清空SerialPort控件的Buffer 10. }
11. else //'发送16进制按钮' 12. { 13. try 14. {
15. Byte[] receivedData = new Byte[sp1.BytesToRead]; //创建接收字节数组 16. sp1.Read(receivedData, 0, receivedData.Length); //读取数据 17. sp1.DiscardInBuffer(); //清空SerialPort控件的Buffer 18. string strRcv = null; 19.
20. for (int i = 0; i < receivedData.Length; i++) //窗体显示 21. { 22.
23. strRcv += receivedData[i].ToString(\进制显示 24. }
25. txtReceive.Text += strRcv + \ 26. }
27. catch (System.Exception ex) 28. {
29. MessageBox.Show(ex.Message, \出错提示\ 30. txtSend.Text = \ 31. } 32. } 33. } 34. else 35. {
36. MessageBox.Show(\请打开某个串口\错误提示\ 37. } 38. }
为了友好和美观,我将当前时间也显示出来,又将显示字体的颜色做了修改:
[csharp] view plaincopyprint?
1.
3. txtReceive.Text += dt.GetDateTimeFormats('f')[0].ToString() + \ 4. txtReceive.SelectAll();
5. txtReceive.SelectionColor = Color.Blue; //改变字体的颜色
做到这里,大部分功能就已实现了,剩下的工作就是些简单的操作设置了,有保存设置、定时发送信息、控制文本框输入内容等。
六、保存设置
这部分相对简单,但当时我没接触过,也花了点时间,现在想想,也不过如此。
保存用户设置用ini文件是个不错的选择,虽然大部分都用注册表实现,但ini文件保存还是有比较广泛的使用。
.ini 文件是Initialization File的缩写,也就是初始化文件。
为了不偏离正题,也不过多说明,可参考相关内容(网上资源都不错,因人而异,就不加链接了)。
使用Inifile读写ini文件,这里我用到了两个主要方法:
[csharp] view plaincopyprint?
1. //读出ini文件
2. a:=inifile.Readstring('节点','关键字',缺省值);// string类型 3. b:=inifile.Readinteger('节点','关键字',缺省值);// integer类型 4. c:=inifile.Readbool('节点','关键字',缺省值);// boolean类型 5. 其中[缺省值]为该INI文件不存在该关键字时返回的缺省值。 6. //写入INI文件:
7. inifile.writestring('节点','关键字',变量或字符串值); 8. inifile.writeinteger('节点','关键字',变量或整型值); 9. inifile.writebool('节点','关键字',变量或True或False);
请看代码:
[csharp] view plaincopyprint?
1. //using 省写了 2. namespace INIFILE 3. {
4. class Profile 5. {
6. public static void LoadProfile() 7. {
8. string strPath = AppDomain.CurrentDomain.BaseDirectory; 9. _file = new IniFile(strPath + \
10. G_BAUDRATE = _file.ReadString(\读数据,下同 11. G_DATABITS = _file.ReadString(\ 12. G_STOP = _file.ReadString(\ 13. G_PARITY = _file.ReadString(\ 14. 15. } 16.
17. public static void SaveProfile() 18. {
19. string strPath = AppDomain.CurrentDomain.BaseDirectory; 20. _file = new IniFile(strPath + \
21. _file.WriteString(\写数据,下同 22. _file.WriteString(\ 23. _file.WriteString(\ 24. _file.WriteString(\ 25. } 26.
27. private static IniFile _file;//内置了一个对象 28.
29. public static string G_BAUDRATE = \给ini文件赋新值,并且影响界面下拉框的显示 30. public static string G_DATABITS = \ 31. public static string G_STOP = \ 32. public static string G_PARITY = \ 33. } 34. }
_file声明成了内置对象,可以方便各函数的调用。 下面是“保存设置”的部分代码:
[csharp] view plaincopyprint?
1. private void btnSave_Click(object sender, EventArgs e) 2. { 3.
4. //设置各“串口设置”
5. string strBaudRate = cbBaudRate.Text; 6. string strDateBits = cbDataBits.Text; 7. string strStopBits = cbStop.Text;
8. Int32 iBaudRate = Convert.ToInt32(strBaudRate); 9. Int32 iDateBits = Convert.ToInt32(strDateBits); 10. 11.
12. Profile.G_BAUDRATE = iBaudRate+\波特率 13. Profile.G_DATABITS = iDateBits+\数据位 14. switch (cbStop.Text) //停止位 15. { 16. case \
17. Profile.G_STOP = \ 18. break; 19. case \
20. Profile.G_STOP = \ 21. break;
22. //防止过多刷屏,下面省写了 23. …… 24. }
25. switch (cbParity.Text) //校验位 26. {
27. case \无\
28. Profile.G_PARITY = \ 29. break; 30. ………… 31. }
32. Profile.SaveProfile(); //保存设置 33. }
读取ini文件主要在加载窗体时执行: INIFILE.Profile.LoadProfile();//加载所有
七、控制文本输入这里倒挺简单,只是注意一点。当我们控制输入非法字符时,可通过控制e.Handed的属性值实现,注意这里的Handed属性是“操作过”的含义,而非“执行此处操作”之意,Handled是过去式,看字面意思,\操作过的=是;\,将这个操作的状态设为已处理过,自然就不会再处理了。具体参见MSDN:Handed
[csharp] view plaincopyprint?
1. private void txtSend_KeyPress(object sender, KeyPressEventArgs e) 2. {
3. if (radio1.Checked== true) 4. {
5. //正则匹配
6. string patten = \b|0x|0X| \\\b”:退格键 7. Regex r = new Regex(patten);
8. Match m = r.Match(e.KeyChar.ToString()); 9. 10.
11. if (m.Success )//&&(txtSend.Text.LastIndexOf(\ 12. {
13. e.Handled = false; 14. } 15. else 16. {
17. e.Handled = true; 18. }
19. }//end of radio1
八、定时发送信息
这边看似很简单,但也有一点需要注意,当定时器生效时,我们要间隔访问“发送”按键的内容,怎么实现?还好MS给我们提供了必要的支持,使用Button的 PerformClick可以轻松做到, PerformClick参见MSDN:PerformClick
[csharp] view plaincopyprint?
1. private void tmSend_Tick(object sender, EventArgs e) 2. {
3. //转换时间间隔
4. string strSecond = txtSecond.Text; 5. try 6. {
7. int isecond = int.Parse(strSecond) * 1000;//Interval以微秒为单位 8. tmSend.Interval = isecond; 9. if (tmSend.Enabled == true) 10. {
11. btnSend.PerformClick(); //产生“发送”的click事件
12. } 13. }
14. catch (System.Exception ex) 15. {
16. MessageBox.Show(\错误的定时输入!\ 17. } 18. 19. }
注意在一些情况下不要忘了让定时器失效,如在取消“定时发送数据\和“关闭串口”时等。
好了,主要内容就是这些,希望以上内容对大家有所帮助,如你有好的想法,还请不吝赐教!
正在阅读:
用C#一步步写串口通信04-30
工作经验交流材料02-17
塔河二号联加油站监理规划05-28
平板电脑作文500字06-14
物品清单(铁路)03-12
高校图书馆满意度调查报告04-19
新农村建设01-10
轻伤害鉴定时间02-24
政治学原理测试题及参考答案04-21
4插值与拟合方法09-25
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- C#
- 串口
- 步步
- 通信
- 初三英语听力训练(九)学习啊学习的啊学习的武器学习的武器
- 《电力拖动自动控制系统》习题解答(前三章)
- java项目开发-- 进销存管理系统1 - 图文
- 工业机械手毕业设计(论文)
- 高炉重力除尘灰铁焦分离回收项目
- 我的老板黄光裕(孙佳勋)
- 抽屉原理及其应用
- 金银岛阅读题2017
- 尔雅文艺复兴:欧洲由衰及盛的转折点
- 高考地理二轮复习专题突破练世界主要气候类型新人教
- 光的干涉习题答案
- 2018年中考语文写景散文阅读理解专项复习试题及答案
- 半导体物理学(刘恩科第七版)半导体物理学课本习题解
- 媒体与广告简要笔记
- 电池设计的基本步骤和基础知识
- 《会声会影》转场考题附答案
- 东财《社会保障概论》随堂随练
- 微观经济学习题(单选)
- 必修2同步巩固练习解析:1-2-2
- 模拟测试题一答案