串口调试助手

串口调试助手基于微软的.Net技术与串口设备进行通信,主要用于串口设备调试。软件界面简洁,代码开源。

简介

因项目需要调试串口设备,但是试用了网上众多的串口调试助手后,虽然其标榜“好用”,“免费”,但使用过程中还是发现这些串口助手或多或少都有一些问题:

  • 无法自动识别已有的串口,不支持COM4以上串口。
  • 界面古老,有广告,且界面逻辑不清。
  • 无源代码,无法真正进行调试。

其中有些问题已经严重影响到使用体验,像不支持COM4以上的串口导致根本不能工作。数据发送过程不透明,不知道具体发送的数据是什么。为了不再忍受各种老掉牙的软件,自己随手写一个简单的串口调试助手,主要实现以下功能:

  • 自动查找并列出已有的串口。
  • 界面清爽。
  • 能发送String类型和Hex类型,能够在两者之间相互转换。
  • 可以附加2字节的CRC16校验码到发送内容的结尾,校验码由发送内容计算而来(需要在Hex模式下选中CheckBox)。
  • 当收到数据时可以自动回复发送窗口内容。
  • 代码开源,所有发送过程对用户透明,方便调试。
  • 基于Windows,无附加依赖库。

编程语言使用C#,基于微软.Net Framework 4.0 Client Profile。没有使用其他版本进行测试,因此使用其他版本可能导致运行不正确。如果电脑没有安装,可以点此下载。以下是软件界面截图:

使用说明:

1、在Windows系统中,回车键是以"\r\n"表示,如果在发送文本框中输入了回车键,在String模式下发送的数据将包括 0x0D 0x0A 两个字节。在Hex模式下,将无法输入回车键,如果需要回车,用0x0D 0x0A代替。

2、Hex模式下,直接输入16进制数,输入的格式为: FF-34-56-90

下载地址

CODE:https://github.com/wenhuix/COMDBG,为Visual Studio 2010工程,可以被更高版本的VS打开。

EXE: COMDBG.exe

代码说明

软件采用MVC设计模式,其架构如下图所示:

Model类主要是跟串口相关的一些操作,包括打开、关闭串口,接收和发送串口数据。在该类中,定义了事件,当接收到串口的数据时,如果有函数注册到了该事件上,则该函数就会被调用,并通过预先定义的参数类,将接收到的数据传递过去。该类需要注意以下几点:

(1) 打开串口时,当Handshake设置为None时,需要设置串口的 RtsEnable = true 和 DtrEnable = true。

(2) 根据MSDN上关于SerialPort这个类的表述,只有SerailPort的公共静态类型的成员是线程安全的,而 SerialPort.Read() 和 SerialPort.Write() 并不是静态方法,因此,需要加入同步锁,以防止在读/写串口的过程中被其他线程中断出现读写错误。

(3) 关闭串口时,要在非当前线程下进行,要防止死锁事件的发生。


//申明委托函数和传递参数的类
public delegate void SerialPortEventHandler(Object sender, SerialPortEventArgs e);

public class SerialPortEventArgs : EventArgs
{
	public bool isOpend = false;
	public Byte[] receivedBytes = null;
}

public class ComModel
{
	private SerialPort sp = new SerialPort();
	private Object thisLock = new Object();
	//申明事件
	public event SerialPortEventHandler comReceiveDataEvent = null;
					
	//串口收到数据是通过中断来实现的,因此在打开串口的同时,要注册数据处理函数:
	sp.DataReceived += new SerialDataReceivedEventHandler(DataReceived);

	//当串口收到数据时,会自动调用该函数:
	private void DataReceived(object sender, SerialDataReceivedEventArgs e)
	{
		if (sp.BytesToRead <= 0)
		{
			return;
		}
		//加锁,保证线程安全
		lock (thisLock)
		{
			int len = sp.BytesToRead;
			Byte[] data = new Byte[len];
			try
			{
				sp.Read(data, 0, len);
			}
			catch (System.Exception)
			{
				//catch read exception
			}
			SerialPortEventArgs args = new SerialPortEventArgs();
			args.receivedBytes = data;
			//用事件通知界面
			if (comReceiveDataEvent != null)
			{
				comReceiveDataEvent.Invoke(this, args);
			}
		}
	}
	
	//向串口设备发送数据
	public bool Send(Byte[] bytes)
	{
		...
		try
		{
			sp.Write(bytes, 0, bytes.Length);
			
		}
		catch (System.Exception)
		{
			return false;   //write failed
		}
		return true;        //write successfully
	}
	
	//打开串口
	public void Open()
	{
		...
		if (handshake == "None")
		{
			sp.RtsEnable = true; 
			sp.DtrEnable = true;
		}
		...
	}
	
	//关闭串口
	public void Close()
	{
		Thread closeThread = new Thread(new ThreadStart(CloseSpThread));
		closeThread.Start();
	}

	// Close serial port thread
	private void CloseSpThread()
	{
		...
		try
		{
			sp.Close(); 
		}
		catch (Exception)
		{
			...
		}
		...
	}
}
				

Form类是与界面相关的代码,是Model的观察者,在该类里会实现接口View中更新界面的函数,当该函数注册到Model中并被调用时会更新界面。该类需要注意以下问题:

黄金定律:永远不要在非UI的线程内更新UI部件


public interface IView
{
	void SetController(IController controller);
	...
	//Serial port receive data event
	void ComReceiveDataEvent(Object sender, SerialPortEventArgs e);
	...
}			

public partial class MainForm : Form, IView
{
	private IController controller;
	...
	
	//设置控制器
	public void SetController(IController controller)
	{
		this.controller = controller;
	}

	//收到串口数据后,更新界面
	public void ComReceiveDataEvent(Object sender, SerialPortEventArgs e)
	{
		if (this.InvokeRequired)
		{
			Invoke(new Action<Object, SerialPortEventArgs>(ComReceiveDataEvent), 
				sender, e);
			return;
		}
		receivetbx.AppendText(Encoding.Default.GetString(e.receivedBytes));
	}
	
	//点击发送按钮时,调用Controller发送数据
	private void sendbtn_Click(object sender, EventArgs e)
	{
		String sendText = sendtbx.Text;
		...
		//send String to serial port
		bool flag = controller.SendDataToCom(sendText);
		...
	}
}	
				

Controller类是Form类和Model类的桥梁,在该Controller中,将界面上的更新函数注册到Model中。一般来说Controller比较复杂的一部分,但在这个程序中比较简单,没有数据库相关的操作。


public class IController
{
	ComModel comModel = new ComModel();
	IView view;

	public IController(IView view)
	{
		this.view = view;
		view.SetController(this);
		//将页面上的更新界面函数注册到Model的事件
		comModel.comReceiveDataEvent 
			+= new SerialPortEventHandler(view.ComReceiveDataEvent);
	}		

	//通过Model发送字符到串口
	public bool SendDataToCom(String str)
	{
		if (str != null && str != "")
		{
			return comModel.Send(Encoding.Default.GetBytes(str));
		}
		return true;
	}
}
				
参考文献

[1] Serial Comms in C# for Beginners. CodeProject 2013.

[2] Your first program using MVC pattern with C#/WinForms. CodeProje 2013.


Last update: 2014-11-13