搞嵌入式開發難免會使用串口通信 ,現有一個項目需要使用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