C#实现简单串口通信
串口通信(Serial Communications)是指外设和计算机间通过数据信号线、地线等按位(bit)进行传输数据的一种通信方式,属于串行通信方式,能够实现远距离通信,长度可达1200米。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
串口通信的接口标准有很多,有 RS-232C、RS-232、RS-422A、RS-485 等。常用的就是 RS-232 和 RS-485。串口通信使用的大多都是 DB9 接口,如下图。
1 载波检测(DCD) 2 接受数据(RXD) 3 发出数据(TXD) 4 数据终端准备好(DTR) 5 信号地线(SG) 6 数据准备好(DSR) 7 请求发送(RTS) 8 清除发送(CTS) 9 振铃指示(RI)
这里我们以 RS-232 接口进行演示。
1、数据包格式定为(10bytes):
帧头(0xAA,0x55),命令字(1byte),地址位(2bytes),数据位(2bytes),校验位(1byte,和校验),帧尾(0xFE,0xFE)
地址位和数据位都是高位在前。
数据封装方法:
//数据打包 private byte[] DataPackage(byte cmd, int addr, int data) { byte[] package = new byte[10]; package[0] = 0xAA;//帧头 package[1] = 0x55; package[2] = cmd;//命令字 byte[] dataaddr = IntToByteArray(addr); package[3] = dataaddr[0];//地址高字节 package[4] = dataaddr[1];//地址低字节 byte[] value = IntToByteArray(data); package[5] = value[0];//数据高字节 package[6] = value[1];//数据低字节 package[7] = CheckSum(package);//校验位 package[8] = 0xFE;//帧尾 package[9] = 0xFE; return package; } //将int转换成2位数组 private static byte[] IntToByteArray(int value) { int hvalue = (value >> 8) & 0xFF; int lValue = value & 0xFF; byte[] arr = new byte[] { (byte)hvalue, (byte)lValue }; return arr; } //得到和校验码 private byte CheckSum(byte[] package) { byte checksum = 0; for (int i = 0; i < package.Length; i++) { checksum += package[i]; } return checksum; }
2、串口调用封装类CommHelper.cs
internal class CommHelper { //委托 public delegate void EventHandle(byte[] readBuffer); public event EventHandle DataReceived; public SerialPort serialPort; private Thread thread; volatile bool _keepReading; public CommHelper() { serialPort = new SerialPort(); thread = null; _keepReading = false; serialPort.ReadTimeout = -1; serialPort.WriteTimeout = 1000; } //获取串口打开状态 public bool IsOpen { get { return serialPort.IsOpen; } } //开始读取 private void StartReading() { if (!_keepReading) { _keepReading = true; thread = new Thread(new ThreadStart(ReadPort)); thread.Start(); } } //停止读取 private void StopReading() { if (_keepReading) { _keepReading = false; thread.Join(); thread = null; } } //读数据 private void ReadPort() { while (_keepReading) { if (serialPort.IsOpen) { int count = serialPort.BytesToRead; if (count > 0) { byte[] readBuffer = new byte[count]; try { Application.DoEvents(); serialPort.Read(readBuffer, 0, count); DataReceived?.Invoke(readBuffer); Thread.Sleep(100); } catch (TimeoutException) { } } } } } //写数据 public void WritePort(byte[] send, int offSet, int count) { if (IsOpen) { serialPort.Write(send, offSet, count); } } //打开 public void Open() { Close(); try { serialPort.Open(); serialPort.RtsEnable = true; if (serialPort.IsOpen) { StartReading(); } else { MessageBox.Show("串口打开失败!"); } } catch { } } //关闭 public void Close() { StopReading(); serialPort.Close(); } }
3、调用(模拟测试读写硬件版本号)
//地址存储信息定义 private static int HARD_VERSION = 0; //硬件版本号 //命令定义 private static byte RD_HV = 0; //读硬件版本 private static byte WR_HV = 1; //写硬件版本 private CommHelper comm = new CommHelper(); private bool GetCorrect = false; //用来标识是否已经接收到返回值 private List<byte> buffer = new List<byte>(1024);//默认分配1M内存,限制不能超过 private byte[] binary_data = new byte[10];//指定格式的某个完整数据 //串口初始化 private void InitComm() { comm.serialPort.PortName = "COM1"; comm.serialPort.BaudRate = 19200; try { comm.Open(); } catch (Exception ex) { MessageBox.Show(ex.Message); } if (comm.IsOpen) { comm.DataReceived += new CommHelper.EventHandle(comm_DataReceived); } } //数据接收后处理 private void comm_DataReceived(byte[] readBuffer) { try { buffer.AddRange(readBuffer);//将数据放到缓存中 //完整性判断 while (buffer.Count >= 10) { //查找数据头 if (buffer[0] == 0xAA && buffer[1] == 0x55) { //和校验 byte checksum = 0; for (int i = 0; i < 7; i++) { checksum += buffer[i]; } if (checksum != buffer[7]) { buffer.RemoveRange(0, 10);//如果校验失败,从缓存中删除这一包数据 continue; } buffer.CopyTo(0, binary_data, 0, 10);//复制一条完整的数据到具体的数据缓存 //读取硬件版本号 if (binary_data[2] == RD_HV && ByteArrayToInt(binary_data[3], binary_data[4]) == HARD_VERSION)//命令字为读RD_HV,地址HARD_VERSION { GetCorrect = true; string data = ByteArrayToString(binary_data[5], binary_data[6]); //这里可以处理数据更新界面展示 } buffer.RemoveRange(0, 10); } else { GetCorrect = false; buffer.RemoveAt(0);//如果数据开始不是头,则删除数据 } } } catch { } } //将2位数组转换成字符串 private static string ByteArrayToString(byte arr1, byte arr2) { int value = ByteArrayToInt(arr1, arr2); string str = Convert.ToString(value, 10); return str; } //将2位数组转换成10进制数 private static int ByteArrayToInt(byte arr1, byte arr2) { byte[] arr = new byte[] { arr1, arr2 }; int value = (int)((arr[0] & 0xFF) << 8) | (arr[1] & 0xFF); return value; } //读取硬件版本号 private void ReadHV() { GetCorrect = false; try { comm.WritePort(DataPackage(RD_HV, HARD_VERSION, 0), 0, 10);//发送RD_E2P命令,地址HARD_VERSION Delay(100); if (GetCorrect == true) { MessageBox.Show("硬件版本号读取成功!"); } else { MessageBox.Show("硬件版本号读取失败!"); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } //写入硬件版本号 private void WriteHV() { if (tbVersion.Text.Length == 4 && isLegal(tbVersion.Text))//限定只能写入4个字符且输入合法 { try { int buf = Convert.ToInt32(tbVersion.Text); GetCorrect = false; comm.WritePort(DataPackage(WR_HV, HARD_VERSION, buf), 0, 10);//发送WR_HV命令,地址HARD_VERSION Delay(100); if (GetCorrect == true) { MessageBox.Show("硬件版本号写入成功!"); } else { MessageBox.Show("硬件版本号写入失败!"); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } else { MessageBox.Show("硬件版本号数据格式错误!请重新写入!"); } } //判断输入是否合法,必须是数字 private const string PATTERN = @"^[0-9]*$"; private bool isLegal(string hex) { return System.Text.RegularExpressions.Regex.IsMatch(hex, PATTERN); } //延时函数 [DllImport("kernel32.dll")] static extern uint GetTickCount(); static void Delay(uint ms) { uint start = GetTickCount(); while (GetTickCount() - start < ms) { Application.DoEvents(); } }
上一篇:C#中按引用传递与按值传递的区别,以及ref与out关键字的用法详解
栏 目:.NET代码
下一篇:深入理解C#指针之美
本文标题:C#实现简单串口通信
本文地址:http://www.codeinn.net/misctech/205751.html