目的
记录串口调试的遇到的一些问题以及相应的解决方法
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删掉
删掉这行代码之后串口接收到的数据就正常了