搞嵌入式开发难免会使用串口通信 ,现有一个项目需要使用C#,借此机会来开发一个串口供大家参考
一、UI布局
数据位、校验位、停止位 均写死,各个按钮的 Name值已经标注未标注的基本没有用
二、接收数据报文
这里我们主要对
从机响应 | 字节数 | 返回的信息 | 备 注 |
从机地址 | 1 | XX | 来自地址为XX的从机 |
功能码 | 1 | 33H | 读取寄存器(0x33 = 51) |
数据字节数 | 1 | 02H | XX字节(2倍数据个数) |
寄存器数据1 | 2 | DAT1 | 传感器参数1数据内容 |
CRC码 | 2 | XXXX | 由从机计算得到CRC码 |
进行分析,不难发现报文一帧发送 7 个16进制的数据
由此我们准备以下代码
using ComHelp;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace UnlockProject
{
public partial class Form1 : Form
{
//被打开的COM
public static SerialPort mySerialPort;
public Form1()
{
CheckForIllegalCrossThreadCalls = false;
//初始化
InitializeComponent();
//加载COM
GetComList();
//配置初始化
InitralConfig();
}
public SerialPort com = new SerialPort();
//定义端口类
private SerialPort ComDevice = new SerialPort();
/// <summary>
/// 从注册表获取系统串口列表
/// </summary>
public string[] GetComList()
{
RegistryKey keyCom = Registry.LocalMachine.OpenSubKey("Hardware\\DeviceMap\\SerialComm");
string[] sSubKeys = keyCom.GetValueNames();
string[] str = new string[sSubKeys.Length];
for (int i = 0; i < sSubKeys.Length; i++)
{
str[i] = (string)keyCom.GetValue(sSubKeys[i]);
}
return str;
}
/// <summary>
/// 配置初始化
/// </summary>
private void InitralConfig()
{
Boolean open = false;
string[] coms = GetComList();
for (int i = 0; i < coms.Length; i++)
{
open = false;
if (com.IsOpen)
{
com.Close();
}
cmbPort.Items.Add(coms[i]);
}
//向ComDevice.DataReceived(是一个事件)注册一个方法Com_DataReceived,当端口类接收到信息时时会自动调用Com_DataReceived方法
ComDevice.DataReceived += new SerialDataReceivedEventHandler(Com_DataReceived);
}
int i = 0;
/// <summary>
/// 一旦ComDevice.DataReceived事件发生,就将从串口接收到的数据显示到接收端对话框
/// </summary>
/// <param name="sender"></param>
/// <param name="sender"></param>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//开辟接收缓冲区
byte[] ReDatas = new byte[ComDevice.BytesToRead];
//从串口读取数据
ComDevice.Read(ReDatas, 0, ReDatas.Length);
//实现数据的解码与显示
AddData(ReDatas);
}
int i_data = 0;//接收位数
List<byte> i_data_count = new List<byte>();//不符合位数的数据暂存
/// <summary>
/// 解码过程
/// </summary>
/// <param name="data">串口通信的数据编码方式因串口而异,需要查询串口相关信息以获取</param>
public void AddData(byte[] data)
{
StringBuilder sb = new StringBuilder();
foreach (var b in data)
{
sb.Append(Convert.ToString(b, 16).PadLeft(2, '0')+" ");
}
i_data += data.Length;//记录位长
if (i_data == 7)//满足位长
{
//追写历史记录的数据
StringBuilder i_data_str = new StringBuilder();
foreach (var item in i_data_count)
{
i_data_str.Append(Convert.ToString(item, 16).PadLeft(2, '0') + " ");
}
//CRC校验值是否正确
List<byte> deCRC = new List<byte>();
foreach (var item in i_data_count)
{
deCRC.Add(item);
}
foreach (var item in data)
{
deCRC.Add(item);
}
//得到校验码
long CRC = new CRCHelp().deCRC(5, deCRC.ToArray());
//得到低位在前高位在后的值
string strCRC = CRC.ToString("x8");
//低位
string low = strCRC.Substring(4, 1) + strCRC.Substring(5, 1);
//高位
string heigt = strCRC.Substring(6, 1) + strCRC.Substring(7, 1);
//校验状态
string crc_flag = "[错误]";
//CRC值与数据进行校验是否正确
if (low == deCRC[5].ToString("x8").ToString().Replace("0", "") && heigt == deCRC[6].ToString("x8").ToString().Replace("0", "")) {
crc_flag = "[正确]";
}
//进行打印
AddContent(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss "+ crc_flag + ":") + i_data_str + sb.ToString() + "\r\n");//.ToUpper()
//重置位长
i_data = 0;
//重置位数据缓存
i_data_count = new List<byte>();
}
else {
//不满足位长时记录缓存数据
foreach (var item in data)
{
i_data_count.Add(item);
}
}
}
List<string> data = new List<string>();
/// <summary>
/// 接收端对话框显示消息
/// </summary>
/// <param name="content"></param>
private void AddContent(string content)
{
BeginInvoke(new MethodInvoker(delegate
{
datalog.AppendText(content);
}));
}
/// <summary>
/// 串口开关
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button_Switch_Click(object sender, EventArgs e)
{
if (cmbPort.Items.Count <= 0)
{
MessageBox.Show("未发现可用串口,请检查硬件设备");
return;
}
if (ComDevice.IsOpen == false)
{
//设置串口相关属性
ComDevice.PortName = cmbPort.Text;//cmbPort.SelectedItem.ToString();
//波特率
ComDevice.BaudRate = Convert.ToInt32(btl.Text);
//校验位
ComDevice.Parity = Parity.None;
//停止位
ComDevice.StopBits = StopBits.One;
//数据位位
ComDevice.DataBits = 8;
try
{
//开启串口
ComDevice.Open();
//while (true)
{
//接收数据
ComDevice.DataReceived += new SerialDataReceivedEventHandler(Com_DataReceived);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "未能成功开启串口", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
else
{
try
{
ComDevice.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "串口关闭错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
cmbPort.Enabled = !ComDevice.IsOpen;
}
/// <summary>
/// 将消息编码并发送
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button_Send_Click(object sender, EventArgs e)
{
//if (datalog.Text.Length > 0)
//{
// datalog.AppendText("\n");
//}
byte[] sendData = null;
//sendData = Encoding.UTF8.GetBytes(datalog.Text);
sendData = Hex16StringToHex16Byte(datalog.Text);
SendData(sendData);
}
/// <summary>
/// 此方法用于将16进制的字符串转换成16进制的字节数组
/// </summary>
/// <param name="_hex16ToString">要转换的16进制的字符串。</param>
public static byte[] Hex16StringToHex16Byte(string _hex16String)
{
//去掉字符串中的空格。
_hex16String = _hex16String.Replace(" ", "");
if (_hex16String.Length / 2 == 0)
{
_hex16String += " ";
}
//声明一个字节数组,其长度等于字符串长度的一半。
byte[] buffer = new byte[_hex16String.Length / 2];
for (int i = 0; i < buffer.Length; i++)
{
//为字节数组的元素赋值。
buffer[i] = Convert.ToByte((_hex16String.Substring(i * 2, 2)), 16);
}
//返回字节数组。
return buffer;
}
/// <summary>
/// 此函数将编码后的消息传递给串口
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public bool SendData(byte[] data)
{
if (ComDevice.IsOpen)
{
try
{
//将消息传递给串口
ComDevice.Write(data, 0, data.Length);
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "发送失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else
{
MessageBox.Show("串口未开启", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return false;
}
/// <summary>
/// 16进制编码
/// </summary>
/// <param name="hexString"></param>
/// <returns></returns>
private byte[] strToHexByte(string hexString)
{
hexString = hexString.Replace(" ", "");
if ((hexString.Length % 2) != 0) hexString += " ";
byte[] returnBytes = new byte[hexString.Length / 2];
for (int i = 0; i < returnBytes.Length; i++)
returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2).Replace(" ", ""), 16);
return returnBytes;
}
private void button2_Click(object sender, EventArgs e)
{
i_data = 0;
i_data_count = new List<byte>();
}
private void button3_Click(object sender, EventArgs e)
{
datalog.Text="";
}
}
}
CRC计算类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UnlockProject
{
public class CRCHelp
{
#region CRC计算
public Int64 deCRC(int length, byte[] data)
{
Int64 CRCtemp = 65535;
int j = 0;
int chr = 0;
int chr1 = 0;
for (int y = 0; y < length; y++)
{
chr = (int)CRCtemp & 255;
chr = chr ^ data[j];
CRCtemp = CRCtemp & 0xff00;
CRCtemp = CRCtemp + chr;
for (int i = 0; i < 8; i++)
{
if ((CRCtemp & 0x01) == 1)
{
CRCtemp = CRCtemp >> 1;
CRCtemp = CRCtemp ^ 0xA001;
}
else
{
CRCtemp = CRCtemp >> 1;
}
}
j += 1;
}
chr = (int)CRCtemp & 0xff;
chr1 = (int)CRCtemp & 0xff00;
CRCtemp = chr << 8 | chr1 >> 8;
return CRCtemp;
}
#endregion
}
}
三、代码详细解释
3.1我们只对关键代码解释:
int i_data = 0;//接收位数
List<byte> i_data_count = new List<byte>();//不符合位数的数据暂存
/// <summary>
/// 解码过程
/// </summary>
/// <param name="data">串口通信的数据编码方式因串口而异,需要查询串口相关信息以获取</param>
public void AddData(byte[] data)
{
StringBuilder sb = new StringBuilder();
foreach (var b in data)
{
sb.Append(Convert.ToString(b, 16).PadLeft(2, '0')+" ");
}
i_data += data.Length;//记录位长
if (i_data == 7)//满足位长
{
//追写历史记录的数据
StringBuilder i_data_str = new StringBuilder();
foreach (var item in i_data_count)
{
i_data_str.Append(Convert.ToString(item, 16).PadLeft(2, '0') + " ");
}
//CRC校验值是否正确
List<byte> deCRC = new List<byte>();
foreach (var item in i_data_count)
{
deCRC.Add(item);
}
foreach (var item in data)
{
deCRC.Add(item);
}
//得到校验码
long CRC = new CRCHelp().deCRC(5, deCRC.ToArray());
//得到低位在前高位在后的值
string strCRC = CRC.ToString("x8");
//低位
string low = strCRC.Substring(4, 1) + strCRC.Substring(5, 1);
//高位
string heigt = strCRC.Substring(6, 1) + strCRC.Substring(7, 1);
//校验状态
string crc_flag = "[错误]";
//CRC值与数据进行校验是否正确
if (low == deCRC[5].ToString("x8").ToString().Replace("0", "") && heigt == deCRC[6].ToString("x8").ToString().Replace("0", "")) {
crc_flag = "[正确]";
}
//进行打印
AddContent(DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss "+ crc_flag + ":") + i_data_str + sb.ToString() + "\r\n");//.ToUpper()
//重置位长
i_data = 0;
//重置位数据缓存
i_data_count = new List<byte>();
}
else {
//不满足位长时记录缓存数据
foreach (var item in data)
{
i_data_count.Add(item);
}
}
}
四、运行结果
五、其他工具
串口通讯助手:SerialPort2.exe
串口仿生助手:Configure Virtual Serial Port Driver