WinFrom 串口通訊的簡單使用【包括RCR校驗】

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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章