目的
记录串口调试的遇到的一些问题以及相应的解决方法
1.串口定义:串口是计算机与其他硬件传输数据的通道,在计算机与外设通信时起到重要作用
2.串口通信的基础知识
- C#中的串口通信类
C#使用串口通信类是SerialPort(),该类使用方法是
new 一个 SerialPort对象
为SerialPort对象准备参数
_serialPort = new SerialPort(portName);_serialPort.BaudRate = bauRate;_serialPort.Parity = parity;_serialPort.DataBits = dataBits;_serialPort.StopBits = stopBits;_serialPort.Handshake = Handshake.None;_serialPort.ReadTimeout = 500;_serialPort.WriteTimeout = 500;
接下来就是打开串口并且绑定事件
try{ErrorMessage = "";_serialPort.Open();if (_serialPort.IsOpen){_serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);return true;}else{Log.Debug($"Truth_Power.SerialPortHelper.Open().Return [{false.ToString()}]");return false;}}catch(Exception ex){ErrorMessage = ex.Message;Log.Error($"Truth_Power.SerialPortHelper.Open().Error [{ex.Message}]");return false;}
2.从串口读取数据
串口的性质不同于网络,网络发一个HTTP会返回一个完整的数据。例如请求一个网页,服务端就会返回完整的网页代码。
但是串口不同,串口不一定一次返回完整的数据,可能会分批次返回
例如我已知串口返回来的数据是10个字节但是第一次触发数据接收事件时可能返回5个,第二次再触发事件时返回剩下的5个。这样串口需要一个数据合并的代码,实例代码如下:
private void DataReceivedHandler(object sender,SerialDataReceivedEventArgs args){//端口缓冲区字节数,本次数据到达的字节数int n = _serialPort.BytesToRead;// Console.WriteLine(n);//将端口缓冲区数据存入字节数组byte[] byteRev = new byte[n];_serialPort.Read(byteRev, 0, n);//将字节数组存入程序缓冲区string data = BitConverter.ToString(byteRev);// RecieveBuffer += data; if (IsRead){ReadBuffer += "-"+data; //拼接返回的数据}//if(ReadBuffer.Length == 20) //如果数据长度合格,则可以发出事件 这个事件不应该在回调中发出而是应该在writeRead函数中发出//{// ReceivedDataFromPort.Invoke(command, byteRev, n); //读出完整数据后再发出事件//}}
public bool WriteAndRead(byte[] sendBytes, out string recieved, int timeOut = 1000, int stepTime = 20){IsRead = true;ReadBuffer = "";//公共变量recieved = "";//clearInput(); //清空串口if (Write(sendBytes)){int times = timeOut / stepTime;for (int i = 0; i < times; i++){recieved = ReadBuffer; //获取截止到当前串口返回的数据Thread.Sleep(stepTime); //这个延时一定要放在recived = ReadBuffer之后,如果20ms后ReadBuffer被更新,那么received!=ReadBuffer,这时就不会跳出循环if (recieved == "") //如果这次读到是空先跳过,等下次再读{continue;}if (recieved == ReadBuffer) //当某次达到满数据后可以直接跳出循。跳出后判断i的值,如果i达到times那么说明超时;如果小于times那么没有超时{break;}}clearOutput();IsRead = false;return true;}IsRead = false;Log.Debug($"Truth_Power.SerialPortHelper.WriteAndRead().Return [{false.ToString()}]");return false;}
3.C#跨线程访问
C#跨线程访问需要特殊的处理
private void sendDataThread(){string fileName = "testdata.txt";StreamWriter sr;sr = new StreamWriter(fileName);const int length = 10; ;//发送与返回的字节长度int timeOut = 1000; //超时时间int stepTime = 20; //时间间隔int n = 0; //当前接收的数据长度string hexString;//现在的问题就是数据没有写入string dataRev;byte[] dataRecv = new byte[length];while (true){if (killed) //如果需要结束线程,那么用break跳出循环{sr.Close();break;}lock (Lock_Port){this.Dispatcher.Invoke(new Action(delegate{//你想要做的操作 ControlMode mode = ((MainWindowViewModel)this.DataContext).CurrentSelectedMode;int electric = ((MainWindowViewModel)this.DataContext).Electricity;int magetic = ((MainWindowViewModel)this.DataContext).Magnetic;int voltage = ((MainWindowViewModel)this.DataContext).Voltage;byte[] command;List<byte> data = new List<byte>();switch (mode){case ControlMode.Voltage:SerialPortCommand.SetVoltage(voltage, Sign.PositiveSign, out command);//这里可能要先清空_serialPortControl.command = command;_serialPortControl.Write(command.ToArray());_serialPortControl.WriteAndRead(command, out dataRev);timestamp = DateTime.Now.ToString();hexString = BitConverter.ToString(command);timestamp = DateTime.Now.ToString();sr.WriteLine("接收时间");sr.WriteLine(timestamp);sr.WriteLine("发送数据");sr.WriteLine(hexString);// hexString = BitConverter.ToString(receive);sr.WriteLine("接收数据");sr.WriteLine(dataRev);sr.WriteLine("接收数据长度");sr.WriteLine(dataRev.Length.ToString());sr.WriteLine("\n");break;case ControlMode.Magnetic:SerialPortCommand.SetMagetic(magetic);break;case ControlMode.Electricity:SerialPortCommand.SetElectric(electric, Sign.PositiveSign, out command);_serialPortControl.command = command;_serialPortControl.WriteAndRead(command, out dataRev);timestamp = DateTime.Now.ToString();hexString = BitConverter.ToString(command);timestamp = DateTime.Now.ToString();sr.WriteLine("接收时间");sr.WriteLine(timestamp);sr.WriteLine("发送数据");sr.WriteLine(hexString);// hexString = BitConverter.ToString(receive);sr.WriteLine("接收数据");sr.WriteLine(dataRev);sr.WriteLine("接收数据长度");sr.WriteLine(dataRev.Length.ToString());sr.WriteLine("\n");break;}}));}Thread.Sleep(1000); //延时1秒,保证电源有时间响应}
4.串口指令生成
写串口实质上是向串口写入数据
数据本质上一串字节型数据,一般有固定的格式。帧头-命令字-数据部分-帧尾,值得关注就是数据部分。
以这个图片为例,取出32位Int型数据的某八位可以用>>(移位)和&(且)
如果取出高第二个字节
(byte)((dataLength >> 16) & 0xFF)
如果取出Int型数据的低8位
(byte)(dataLength & 0xFF)
5.关于combox selectedChange事件 运行程序立即执行的问题
解决这个bug需要在切换事件中判断串口是否打开,如果未打开则事件立即返回。代码如下:
//选中的改变之后,根据当前的选中值更新//核心获取改变之后的值if (!_isOpenForPort){MessageBox.Show("串口未打开");return;}
6.返回重复数据的bug的原因
换成新的WriteAndRead函数后没有把原来的函数Write删掉
删掉这行代码之后串口接收到的数据就正常了