系统采用之前的系统:
相关的链接为
https://blog.csdn.net/u011854789/article/details/51895014
https://blog.csdn.net/weixinhum/article/details/53684151
http://www.cnblogs.com/Traveller-Lee/p/6940221.html(主要参考)
(一)WPF工程做上位机与彩屏(或单片机)进行串口通信、解决彩屏(或单片机)只能发送信息不能接受信息问题。
我是在上位机的接收彩屏的信息状态下,收到异常信息:
超时原因,是因为使用了SerialPort.ReadTimeout 方法和 SerialPortObj.ReadLine()方法。其中,SerialPort.ReadTimeout方法是设定读取的时间间隔。此外, SerialPortObj.ReadLine()表示读取接收缓存区的字节,如未在SerialPort.ReadTimeout设定的时间间隔内收到信息,则抛出异常。
那么,SerialPort为什么要自带这些方法呢?是多余了吗?显然不是,它是有着其他用途的。我们往后再讨论SerialPort.ReadTimeout 方法和 SerialPort.ReadLine()方法的应用场景。
现在,我们回过头来解决正常的就接收问题,我只要能够任何时间段、任何连接状态下,只要能够正常接收东西即可。我采用的方法是
总之,我们在这里采用超时方法ReadLine()。附上代码,该代码已经能够与大彩屏正常收发。
MainWindow.xaml:
MainWindow.xaml.cs:
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using System.IO.Ports;
using System.Collections.Generic;
namespace PortChat
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow
{
List protocolsList = new List();
#region 包头包尾指令
//列表
byte[] bytHomeUserListConstantData = new byte[] { 0xEE, 0xB1, 0x5B, 0x00, 0x01, 0x00, 0x1B, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
byte[] bytStudyOptionalProtocolListConstantData = new byte[] { 0xEE, 0xB1, 0x5B, 0x00, 0x02, 0x00, 0x05, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
byte[] bytStudySelectedBodyListConstantData = new byte[] { 0xEE, 0xB1, 0x5B, 0x00, 0x02, 0x00, 0x22, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
//高压
byte[] bytStudyExposeKvConstantData = new byte[] { 0xEE, 0xB1, 0x10, 0x00, 0x02, 0x00, 0x2F, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
//mA
byte[] bytStudyExposemAConstantData = new byte[] { 0xEE, 0xB1, 0x10, 0x00, 0x02, 0x00, 0x31, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
//ms
byte[] bytStudyExposemsConstantData = new byte[] { 0xEE, 0xB1, 0x10, 0x00, 0x02, 0x00, 0x35, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
//mAs
byte[] bytStudExposemAsConstantData = new byte[] { 0xEE, 0xB1, 0x10, 0x00, 0x02, 0x00, 0x38, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
#endregion
//总包
byte[] bytListDataSend = new byte[0];
byte[] bytStudyOptionalProtocolListData = new byte[0];
byte[] bytStudyOptionalBodyListData = new byte[0];
byte[] bytStudyExposeKvData = new byte[0];
byte[] bytStudyExposemAData = new byte[0];
byte[] bytStudyExposemsData = new byte[0];
byte[] bytStudExposemAsData = new byte[0];
//获取可选体位列表数据数据并发送
string strProtocolName1 = "协议1";
string strProtocolNum1 = "名称1";
string strProtocolName2 = "协议2";
string strProtocolNum2 = "名称2";
string strProtocolName3 = "--协faf议2";
string strProtocolNum3 = "-77名称faf2";
//串口
private static readonly SerialPort SerialPortObj = new SerialPort();
string _recievedData = null;
public MainWindow()
{
InitializeComponent();
}
///
/// 串口连接
///
///
///
private void Connect_Click(object sender, RoutedEventArgs e)
{
if ((BtnConnect.Content as string) == "连接")
{
//端口 默认COM3
SerialPortObj.PortName = CbbPortName.Text;
//波特率 默认9600
SerialPortObj.BaudRate = int.Parse(CbbBaudRate.Text);
//奇偶校验 默认None
SerialPortObj.Parity = (Parity)Enum.Parse(typeof(Parity), CbbPortParity.Text, true);
//数据位 默认8
SerialPortObj.DataBits = int.Parse(TbDataBits.Text);
//停止位 默认1
SerialPortObj.StopBits = (StopBits)Enum.Parse(typeof(StopBits), CbbStopBits.Text, true);
//握手方式 默认None
SerialPortObj.Handshake = (Handshake)Enum.Parse(typeof(Handshake), CbbHandshake.Text, true);
//SerialPortObj.ReceivedBytesThreshold = 1;
//SerialPortObj.ReadTimeout = 500;//超过ReadTimeout未接收到,则抛出异常。
//SerialPortObj.WriteTimeout = 500;
if (!SerialPortObj.IsOpen)
{
SerialPortObj.Open();
}
BtnConnect.Content = "断开";
BtnConnect.Background = Brushes.Green;
//委托、调用数据接收方法
SerialPortObj.DataReceived += Recieve;
}
else
{
try // just in case serial port is not open could also be acheved using if(serial.IsOpen)
{
SerialPortObj.Close();
BtnConnect.Content = "连接";
BtnConnect.Background = Brushes.White;
}
catch (Exception)
{
// ignored
}
}
}
private delegate void UpdateUiTextDelegate(string text);
private void Recieve(object sender, SerialDataReceivedEventArgs e)
{
// ReadLine方法要判定 读超时。因此,不用改函数,也不用设置ReadTimeout了。
//_recievedData = SerialPortObj.ReadLine();
//开辟接收缓冲区
byte[] ReDatas = new byte[SerialPortObj.BytesToRead];
SerialPortObj.Read(ReDatas, 0, ReDatas.Length); //从串口读取数据
_recievedData = System.Text.Encoding.Default.GetString(ReDatas);
//实现数据的解码与显示
//AddData(ReDatas);
//调用委托,将字符信息显示在TextBox控件上。
Dispatcher.Invoke(DispatcherPriority.Send, new UpdateUiTextDelegate(WriteDate), _recievedData);
}
private void WriteDate(string text)
{
TbReceive.Text += _recievedData;
}
///
/// 发送指令
///
///
///
private void SendData_Click(object sender, RoutedEventArgs e)
{
//添加列表信息
protocolsList.Add(new PatientsAndProtocolsList(strProtocolName1, strProtocolNum1));
protocolsList.Add(new PatientsAndProtocolsList(strProtocolName2, strProtocolNum2));
protocolsList.Add(new PatientsAndProtocolsList(strProtocolName3, strProtocolNum3));
//病人检查记录发送
bytListDataSend = GetStringToHex(protocolsList, bytHomeUserListConstantData);
SerialCmdSend(bytListDataSend);
TbSend.Text = System.Text.Encoding.Default.GetString(bytListDataSend);
//可选体位发送
bytListDataSend = GetStringToHex(protocolsList, bytStudyOptionalProtocolListConstantData);
SerialCmdSend(bytListDataSend);
//已选体位发送
bytListDataSend = GetStringToHex(protocolsList, bytStudySelectedBodyListConstantData);
SerialCmdSend(bytListDataSend);
}
///
/// 字符转16进制,16进制保存到字节
///
///
///
///
private byte[] GetStringToHex( List protocolsList, byte[] bytHeadTailPackageVar)
{
//总长度
//int iTotalPackageLong = 0;
////包头、包尾长度
int iHeadPackageLong = 7;
int iTailPackageLong = 4;
////列表行:高八位
//int iRowHighSize = 0x00;
////列表行:低八位
//int iRowLowSize = 0x02;
////列表行:高八位
//int iColumnHighSize = 0;//每一行的每列字节数都不固定
////列表行:低八位
//int iColumnLowSize = 0;//每一行的每列字节数都不固定
byte[] bytTotalPackageVar = new byte[500];
int protocolCount = 0;
//拷贝包头:7个固定的字节
Array.ConstrainedCopy(bytHeadTailPackageVar, 0, bytTotalPackageVar, 0, iHeadPackageLong);
#region 确定总行数,及其对应的字节
Int16 iRowsNum = 0;//总行数
iRowsNum = (Int16)protocolsList.Count;
byte[] byt_iRowsNum = new byte[2];
byt_iRowsNum = BitConverter.GetBytes(iRowsNum);//一般是两个字节
Array.ConstrainedCopy(byt_iRowsNum, 1, bytTotalPackageVar, iHeadPackageLong, 1);//byt_iRowsNum的高八位,放到缓存区的低字节区。才符合大彩屏的指令接收要求。
Array.ConstrainedCopy(byt_iRowsNum, 0, bytTotalPackageVar, (iHeadPackageLong + 1), 1);//byt_iRowsNum的低八位,放到缓存区的高字节区。
#endregion
#region 每行的字符内容,及其对应的字节数目、字节内容
int iTotalPackageStartIndex = iHeadPackageLong + byt_iRowsNum.Length;
while (protocolCount < protocolsList.Count)
{
string strProtocolNumVar = null;
string strProtocolNameVar = null;
string strEachRowContent = null;
Int16 iEachRowByteNum = 0;//每行的字节数目
byte[] byt_strEachRowContent = new byte[0];
byte[] byt_iEachRowByteNum = new byte[0];
#region 确定每行的字符内容,及其对应的字节数目、字节内容
//字符
strProtocolNumVar = protocolsList[protocolCount].StrPatientNumOrProtocolNum;
strProtocolNameVar = protocolsList[protocolCount].StrPatientNameOrProtocolName;
strEachRowContent = strProtocolNumVar + ";" + strProtocolNameVar + ";";//必须加上分号。
//内容
byt_strEachRowContent = System.Text.Encoding.Default.GetBytes(strEachRowContent); //每行的字节内容
iEachRowByteNum = (Int16)byt_strEachRowContent.Length;
byt_iEachRowByteNum = BitConverter.GetBytes(iEachRowByteNum);//每行的字节数目,将数目转成字节
//拷贝
Array.ConstrainedCopy(byt_iEachRowByteNum, 1, bytTotalPackageVar, iTotalPackageStartIndex, 1);//byt_iEachRowByteNum的高八位,放到缓存区的低字节区。才符合大彩屏的指令接收要求。
Array.ConstrainedCopy(byt_iEachRowByteNum, 0, bytTotalPackageVar, (iTotalPackageStartIndex + 1), 1);//byt_iEachRowByteNum的低八位,放到缓存区的高字节区。
Array.ConstrainedCopy(byt_strEachRowContent, 0, bytTotalPackageVar, (iTotalPackageStartIndex + byt_iEachRowByteNum.Length), byt_strEachRowContent.Length);
//下标索引
iTotalPackageStartIndex = iTotalPackageStartIndex + byt_iEachRowByteNum.Length + byt_strEachRowContent.Length;
#endregion
protocolCount++;
}
#endregion
//拷贝包尾:4个固定的字节
Array.ConstrainedCopy(bytHeadTailPackageVar, iHeadPackageLong, bytTotalPackageVar, iTotalPackageStartIndex, iTailPackageLong);
return bytTotalPackageVar;
}
///
/// 串口发送函数
///
///
private void SerialCmdSend(byte[] data)
{
//若是串口没打开,跳出函数 SerialCmdSend
if (!SerialPortObj.IsOpen) return;
try
{
//将待发送的内容写到缓冲区
SerialPortObj.Write(data, 0, data.Length);
}
catch (Exception ex)
{
TbSend.Text = "Failed to SEND" + data + "\n" + ex + "\n";
}
}
}
}
PatientsAndProtocolsList.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PortChat
{
//该集合可以装用户集合或列表集合
class PatientsAndProtocolsList
{
public string strPatientNumOrProtocolNum;
public string strPatientNameOrProtocolName;
public string strPatientSex;
public string strPatientAge;
public string strPatientBody;
public string strStudyDateTime;
public string StrPatientNumOrProtocolNum
{
get
{
return strPatientNumOrProtocolNum;
}
set
{
strPatientNumOrProtocolNum = value;
}
}
public string StrPatientNameOrProtocolName
{
get
{
return strPatientNameOrProtocolName;
}
set
{
strPatientNameOrProtocolName = value;
}
}
public string StrPatientSex
{
get
{
return strPatientSex;
}
set
{
strPatientSex = value;
}
}
public string StrPatientAge
{
get
{
return strPatientAge;
}
set
{
strPatientAge = value;
}
}
public string StrPatientBody
{
get
{
return strPatientBody;
}
set
{
strPatientBody = value;
}
}
public string StrStudyDateTime
{
get
{
return strStudyDateTime;
}
set
{
strStudyDateTime = value;
}
}
public PatientsAndProtocolsList() { }
///
/// 保存协议信息
///
///
///
public PatientsAndProtocolsList(string strProtocolNum, string strProtocolName)
{
this.strPatientNumOrProtocolNum = strProtocolNum;
this.strPatientNameOrProtocolName = strProtocolName;
}
///
/// 保存病人信息:5个参数
///
///
///
///
///
///
public PatientsAndProtocolsList(string strPatientNum, string strPatientName, string strPatientSex, string strPatientAge, string strPatientBody)
{
this.strPatientNumOrProtocolNum = strPatientNum;
this.strPatientNameOrProtocolName = strPatientName;
this.strPatientSex = strPatientSex;
this.strPatientAge = strPatientAge;
this.strPatientBody = strPatientBody;
}
///
/// 保存病人信息:6个参数
///
///
///
///
///
///
///
public PatientsAndProtocolsList(string strPatientNum, string strPatientName, string strPatientSex, string strPatientAge, string strPatientBody, string studyDateTime)
{
this.strPatientNumOrProtocolNum = strPatientNum;
this.strPatientNameOrProtocolName = strPatientName;
this.strPatientSex = strPatientSex;
this.strPatientAge = strPatientAge;
this.strPatientBody = strPatientBody;
this.strPatientBody = studyDateTime;
}
}
}
下面讨论ReadTimeout方法的具体应用场景。
首先,这些总结或方法是我百度的,我没有实测过。收发往往放到不同的线程中执行。百度了好久,网上也么有说ReadTimeout的应用场景。
双方通讯时,一般都需要定义通讯协议,即使最简单的通过串口发送文本聊天的程序。
通常是在当一方按下回车时,将其所数据的文本连同换行符发给另一方。在这个通讯事例中,协议桢是通过换行符界定的,每一桢数据都被换行符隔开,这样就很容易识别出通讯双发发送的信息。
在以上的例子中,可以用WriteLine()来发送数据,用ReadLine()来读取数据。WriteLine发送完数据后,会将换行符作为数据也发送给对方。ReadLine()读取数据时,直至遇到一个换行符,然后返回一个字符串代表一行信息。换行符可以通过SerialPort 的属性NewLine来设置。一般地,Windows将CrLn作为换行符,而在Linux下,换行符则只用一个Ln表示。
ReadLine()方法是阻塞的,直至遇到一个换行符后返回。在读取数据时,如果一直没有遇到换行符,那么在等待ReadTimeout时间后,抛出一个TimeoutException。默认情况下,ReadTimeout为InfiniteTimeout。这样,ReadLine一直处于阻塞状态,直至有新一行数据到达(这段话可以完美说明阻塞问题)。
WriteLine()方法也是阻塞的,如果另一方不能及时接收数据,就会引起TimeoutException异常。
由于ReadLine()和WriteLine()方法都是阻塞式的,在程序使用SerialPort 进行串口通讯时,一般应该把读写操作交由其他线程处理,避免因为阻塞而导致程序不响应。
ReadTimeOut的意思是从有数据开始,读取的时间超过这个设置的值,则引发异常,比如是某些驱动级原因导致读取时间过长导致的,并不是说ReadByte,ReadTo,ReadLine的超时时间,这些方法本身是一定会同步阻塞等结果的,你可以先判断一下,比如
if(serial.BytesToRead>0) b = serial.ReadByte();
或是用
serial.ReadExisting()
读取到一个缓存,再处理替代ReadLine或ReadTo。
替代都是发生在你的信源(对你程序的发送方)可能会正常情况的缺失\r\n或你指定字符的情况。
好了,到这里为止,超时问题可以完美的被解决了。以上叙述可以总结为:
1、收发往往放在不同的线程,以防止被阻塞的。本文的接收采用事件方式,发送在主线程。
2、你可以不用ReadLine方法,用以下方法就能实现正常的接收:
byte[] ReDatas = new byte[SerialPortObj.BytesToRead];
SerialPortObj.Read(ReDatas, 0, ReDatas.Length); //从串口读取数据
3、当然,若你要用ReadLine方法,最好是将ReadTimeout设置为无穷大,并且能够识别 换行符。这样你能一直等待接收信息了,不会超时抛出异常了。