用C#一步步写串口通信.docx
用C#一步步写串口通信我们来看具体的实现步骤。 公司要求实现以下几个功能: 1):实现两台计算机之前的串口通信,以16进制形式和字符串两种形式传送和接收。 2):根据需要设置串口通信的必要参数。 3):定时发送数据。 4):保存串口设置。 看着好像挺复杂,其实都是纸老虎,一戳就破,前提是你敢去戳。我尽量讲的详细一些,争取说到每个知识点。 在编写程序前,需要将你要测试的COM口短接,就是收发信息都在本地计算机,短接的方式是将COM口的2、3号针接起来。COM口各针的具体作用,度娘是这么说的:COM口。记住2、3针连接一定要连接牢固,我就是因为接触不良,导致本身就不通,白白花掉了一大半天时间调试代码。 下面给出主要的操作界面,如下: 顺便,我将所有控件对应的代码名字也附上了,相信对初学者来说,再看下面的代码会轻松很多。控件名字命名的方法是“控件名+作用”的形式,例如“打开串口”的开关按钮,其名字是btnSwitch 。我认为这种命名控件的方式比较好,建议大家使用,如果你有好的命名方式,希望你能告诉我! 下面我们将各个功能按照从主到次的顺序逐个实现。 一、获取计算机的COM口总个数,将它们列为控件cbSerial的候选项,并将第一个设为cbSerial的默认选项。 这部分是在窗体加载时完成的。请看代码: csharp view plaincopyprint? 1. /检查是否含有串口 2. string str = SerialPort.GetPortNames; 3. if (str = null) 4. 5. MessageBox.Show("本机没有串口!", "Error"); 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; /<span style="font-size:18px; "><strong>cbSerial</strong></span> 的设默认选置项二、“串口设置” 这面我没代码编程,直接从窗体上按照串口信息设置就行。我们仅设置它们的默认选项,但这里我用到了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. <span style="white-space:pre"> </span>/sp1量。 SerialPort sp1 = new SerialPort; 是全局变4. if (!sp1.IsOpen) 5. 6. try 7. 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 "1": 24. sp1.StopBits = StopBits.One; 25. break; 26. case "1.5": 27. sp1.StopBits = StopBits.OnePointFive; 28. break; 29. case "2": 30. sp1.StopBits = StopBits.Two; 31. break; 32. default: 33. MessageBox.Show("Error:参数不正确!", "Error"); 34. break; 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("Error:参数不正确!", "Error"); 49. break; 50. 51. 52. if (sp1.IsOpen = true)/如果打开状态,则先关闭一下 53. 54. sp1.Close; 55. 56. /状态栏设置 57. tsSpNum.Text = "串口号:" + sp1.PortName + "|" 58. tsBaudRate.Text = "波特率:" + sp1.BaudRate + "|" 59. tsDataBits.Text = "数据位:" + sp1.DataBits + "|" 60. tsStopBits.Text = "停止位:" + sp1.StopBits + "|" 61. tsParity.Text = "校验位:" + sp1.Parity + "|" 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("Error:" + ex.Message, "Error"); 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("请先打开串口!", "Error"); 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", ""); /去掉0x 18. strSendNoComma2.Replace("0X", ""); /去掉0X 19. string strArray = strSendNoComma2.Split(' '); 20. 21. <span style="white-space:pre"> </span>/strArray数组中会出现“”空字符的情况,影响下面的赋值操作,故将<span style="background-color: rgb(255, 255, 255); ">byteBufferLength相应减小</span> 22. int byteBufferLength = strArray.Length; 23. 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> 24. 25. if (strArrayi="") 26. 27. byteBufferLength-; 28. 29. 30. 31. byte byteBuffer = new bytebyteBufferLength; 32. int ii = 0;<span style="white-space:pre"> </span>/用于给<span style="background-color: rgb(255, 255, 255); ">byteBuffer</span> 赋值33. for (int i = 0; i < strArray.Length; i+) /对获取的字符做相加运算 34. 35. 36. Byte bytesOfStr = Encoding.Default.GetBytes(strArrayi); 37. 38. int decNum = 0; 39. if (strArrayi = "") 40. 41. continue; 42. 43. else 44. 45. decNum = Convert.ToInt32(strArrayi, 16); /atrArrayi = 12时,temp = 18 46. 47. 48. try /防止输错,使其只能输入一个字节的字符,即只能在txtSend里输入 “eb 90”等字符串,不能输入“123 2345”等超出字节范围的数字 49. 50. byteBufferii = Convert.ToByte(decNum); 51. 52. catch (System.Exception ex) 53. 54. MessageBox.Show("字节越界,请逐个字节输入!", "Error"); 55. return; 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 bytesp1.BytesToRead; /BytesToRead:sp1接收的字符个数 6. if (rdSendStr.Checked) /'发送字符串'单选按钮 7. 8. txtReceive.Text += sp1.ReadLine + "rn" /注意:回车换行必须这样写,单独使用"r"和"n"都不会有效果 9. sp1.DiscardInBuffer; /清空SerialPort控件的Buffer 10. 11. else /'发送16进制按钮' 12. 13. try 14. 15. Byte receivedData = new Bytesp1.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 += receivedDatai.ToString("X2"); /16进制显示 24. 25. txtReceive.Text += strRcv + "rn" 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. <span style="white-space:pre"> </span>/输出当前时间 2. DateTime dt = DateTime.Now; 3. txtReceive.Text += dt.GetDateTimeFormats('f')0.ToString + "rn" 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 + "Cfg.ini"); 10. G_BAUDRATE = _file.ReadString("CONFIG", "BaudRate", "4800"); /读数据,下同 11. G_DATABITS = _file.ReadString("CONFIG", "DataBits", "8"); 12. G_STOP = _file.ReadString("CONFIG", "StopBits", "1"); 13. G_PARITY = _file.ReadString("CONFIG", "Parity", "NONE"); 14. 15. 16. 17. public static void SaveProfile 18. 19. string strPath = AppDomain.CurrentDomain.BaseDirectory; 20. _file = new IniFile(strPath + "Cfg.ini"); 21. _file.WriteString("CONFIG", "BaudRate", G_BAUDRATE); /写数据,下同 22. _file.WriteString("CONFIG", "DataBits", G_DATABITS); 23. _file.WriteString("CONFIG", "StopBits", G_STOP); 24. _file.WriteString("CONFIG", "G_PARITY", G_PARITY); 25. 26. 27. private static IniFile _file;/内置了一个对象 28. 29. public static string G_BAUDRATE = "1200"/给ini文件赋新值,并且影响界面下拉框的显示 30. public static string G_DATABITS = "8" 31. public static string G_STOP = "1" 32. public static string G_PARITY = "NONE" 33. 34. _file声明成了内置对象,可以方便各函数的调用。 下面是“保存设置”的部分代码: csharp view plaincopyprint? 1. private void btnSave_Click(object sender, EventArgs e) 2. 3. 4. /设置各“串口设置” 5. string strBaudRate = c