前言
最近在写C# 串口通讯,顺便总结一下。C# 串口通讯已经被微软封装好了,可以直接使用。
相关资料
设计思路
因为串口通讯的延迟性,我们希望将其封装成一个Task 线程。通过异步来控制收发。其实就两个方法。发送和接收。
发送比较简单,因为发送是不需要等待延迟的。
接收是异步,需要异步等待,等收到数据了才能接收数据。
代码封装
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace HY_SokectClient.SocketManager
{
/// <summary>
/// 串口服务类
/// </summary>
public class SimChangeService
{
private SerialPort serialPort;
public string[] serialProtArray;
private string name = "Sim卡切换设备:";
/// <summary>
/// 接受的数据
/// </summary>
private string receiveMsg = "";
/// <summary>
/// 最大超时时间
/// </summary>
private int waitTime = 10 * 1000;
/// <summary>
/// 线程阻塞
/// </summary>
private ManualResetEvent manualResetEvent = new ManualResetEvent(true);
// 消息打印的委托,可以是控制台,也可以是Winfrom/WPF窗口
private Action<string> ShowMsg;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="PortName">打开端口</param>
/// <param name="waitTime">每包数据等待时间</param>
public SimChangeService(string PortName, int waitTime,Action<string> action)
{
ShowMsg = action;
this.waitTime = waitTime;
//这个参数根据实际的情况设置
serialPort = new SerialPort()
{
PortName = "COM7",
BaudRate = 9600,
Parity = Parity.None,
DataBits = 8,
StopBits = StopBits.Two,
};
//获取当前机器所有串口
serialProtArray = SerialPort.GetPortNames();
//打开串口
serialPort.Open();
//读取最大超时时间
serialPort.ReadTimeout = waitTime;
//收到数据的回调
serialPort.DataReceived += SerialPort_DataReceived;
ShowMsg("找到本机已有的串口");
ShowMsg(JsonConvert.SerializeObject(serialProtArray));
}
//专门用于线程暂停的函数,用于阻塞读取线程
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//ShowMsg("取消线程暂停");
manualResetEvent.Set();
}
/// <summary>
/// 接受数据
/// </summary>
/// <param name="timeout">超时时间</param>
/// <returns></returns>
private async Task<string> Recive(int timeout)
{
var isOutTime = false;
//这个是超时计数,查看接收数据时间是否超时
//开启两个任务,一个是休眠时间,一个是线程阻塞
await Task.WhenAny(Task.Run(async () =>
{
await Task.Delay(timeout);
isOutTime = true;
}), Task.Run(() =>
{
manualResetEvent.WaitOne();
}));
if (isOutTime)
{
throw new Exception("已超时");
}
//ReadExisting函数会清空暂存区所有数据,如果你的数据是多次拼接,需要自己主动拼接。默认是Ascll的字符串数据。可以自己去更改
var res = serialPort.ReadExisting();
//每次读完数据,就阻塞自己。只能由接收数据事件放开阻塞
manualResetEvent.Reset();
ShowMsg("返回命令:" + res);
return res;
}
//发送函数,没什么好说的
private void Send(string msg)
{
ShowMsg($"{
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}。发送命令:" + msg);
//ShowMsg("线程阻塞");
//主动阻塞读取线程,只能由接收数据事件放开阻塞
manualResetEvent.Reset();
//串口发送数据
serialPort.Write(msg);
}
}
}
简单使用
/// <summary>
/// Sim卡切换
/// </summary>
/// <param name="index"></param>
public async Task ChangeSimNo(int index)
{
int row = (index / 12);
string col = Convert.ToInt32(index % 12).ToString("X1");
var msg = $"AT+S{
row}{
col}";
SimNo = -1;
Send(msg);
try
{
var res = await Recive(waitTime);
ShowMsg(res);
res = await Recive(waitTime);
//如何处理串口数据,需要根据实际逻辑。我这里是第二包收到OK就是接受成功了。
if (res.Contains("OK"))
{
SimNo = index;
ShowMsg($"Sim[{
index}]卡切换成功!");
}
else
{
ShowMsg($"Sim[{
index}]卡接受报文错误,应为OK!");
}
}
catch (Exception ex)
{
throw new Exception("等待OK超时");
}
}