一、串口助手是什么?
通过电脑串口(包括USB口)收发数据并且显示的应用软件,一般用于电脑与嵌入式系统的通讯,借助于它来调试串口通讯或者系统的运行状态。也可以用于采集其他系统的数据,用于观察系统的运行情况。
简单的串口助手只能用来接收可发送串口数据,但是当你学会了如何开发串口助手,就可以根据通信协议开发上位机控制硬件和查看硬件的状态,先介绍如何开发一个简单的串口助手。
二、开发环境
- Visual Stdio
- C#
开发串口助手会用到Winform的相关知识,想要学习Winform的相关知识可以点击链接学习:http://c.biancheng.net/csharp/winform/
三、开发步骤
1. 串口参数
要连接上串口让并实现串口通信我们首先要搜索并选择需要进行通信的串口号,然后设置好波特率、数据位、停止位和检验位,串口号、波特率、数据位、停止位和检验位相当于打开特定串口的简单的密码,设置好之后就能打开串口进行通信。
名称 | 作用 | 常用数值 |
---|---|---|
波特率 | 串口通信的速率(发送和接收各数据位的间隔时间) | 4800 9600 14400 19200 38400 56000 57600 115200 128000 256000 |
数据位 | 表示一组数据实际包含的数据位数 | 5 6 7 8 |
停止位 | 停止位在最后,用以标志一个字符传送的结束,表示数据帧之间的间隔 | 1 1.5 2 None |
校验位 | 用于数据验证 | Even Odd None |
数据位概述图 | ||
2.UI设计
一个串口助手界面主要由参数设置、接收框、发送框三个部分以及一些控制按钮组成,参数设置使用ComboBox组件实现,接收和发送数据框使用RichTextBox组件实现,按钮使用Button组件实现,还有串口可以使用自带的SerialPort组件。
3.创建工程
(1)点击新建项目->选择Windows窗体应用->点击下一步->填写好项目名称和什么位置->点击创建
(2)实现UI
创建好项目后左边是工具栏,可以直接拖拉组件,里面有我们要用到的Label、ComboBox、RichTextBox、Button组件,可以根据字母排序查找需要的组件。右边是用来设置组件参数,按F4可以出现该框,可以设置组件名称、文件内容、颜色、大小、位置、字体、图标等参数,点击闪电标志里面可以设置绑定事件。
注意UI实现的过程中要设置好组件的Name,下面是我设置的一些Name参数
组件 | Name |
---|---|
ComboBox | comboBox_PortNames comboBox_BaudRate comboBox_DataBit comboBox_StopBit comboBox_Parity |
RichTextBox | richTextBox_Receive richTextBox_Send |
Button | button_Serach button_OpenOrClose button_CLEAR button_Send |
串口波特率、数据位、停止位和校验位参数可以预先设置好,这里给出波特率参数设置,其他参数设置的方法一样
SerialPort组件使用方法
基本实现好的UI(审美不在线),大家可以自行优化
点击启动的运行效果
(3)功能代码实现
代码要实现的功能主要有:
- 1.初始化函数
编写初始化函数时要在Form1.Designer.cs文件的Windows 窗体设计器生成的代码里面添加this.Load += new System.EventHandler(this.Form1_Load);
如下图所示
/**************************************************************************/#region 初始化窗体private void Form1_Load(object sender, EventArgs e) //加载界面程序{try{string[] str = SerialPort.GetPortNames(); //获取连接到电脑的串口号并存进数组comboBox_PortNames.Items.Clear(); //清除串口号下拉框的内容comboBox_PortNames.Items.AddRange(str); //将串口号添加到下拉框if (str.Length > 0){comboBox_PortNames.SelectedIndex = 0; //设置ComboBox框的初始值comboBox_BaudRate.SelectedIndex = 7;comboBox_DataBit.SelectedIndex = 3;comboBox_StopBit.SelectedIndex = 1;comboBox_Parity.SelectedIndex = 2;}else{MessageBox.Show("当前无串口连接!");}}catch{MessageBox.Show("无串口设备!/r/n请检查是否连接设备!/r/n请检查设备驱动!");}}#endregion/**************************************************************************/
- 2.搜索串口
/**************************************************************************/#region 搜索串口(与初始化函数一样)private void button_Serach_Click(object sender, EventArgs e){try{string[] str = SerialPort.GetPortNames();comboBox_PortNames.Items.Clear();comboBox_PortNames.Items.AddRange(str);if (str.Length > 0){comboBox_PortNames.SelectedIndex = 0;comboBox_BaudRate.SelectedIndex = 7;comboBox_DataBit.SelectedIndex = 3;comboBox_StopBit.SelectedIndex = 1;comboBox_Parity.SelectedIndex = 2;}else{MessageBox.Show("当前无串口连接!");}}catch{MessageBox.Show("无串口设备!/r/n请检查是否连接设备!/r/n请检查设备驱动!");}}#endregion/**************************************************************************/
- 3.打开/关闭串口
/**************************************************************************/#region 打开串口private void button_OpenOrClose_Click(object sender, EventArgs e){if (!serialPort1.IsOpen){if (comboBox_PortNames.SelectedItem == null){MessageBox.Show("请选择正确的串口", "提示");return;}//设置串口参数serialPort1.PortName = comboBox_PortNames.Text.ToString(); //serialPort1是serialPort组件的Name serialPort1.BaudRate = Convert.ToInt32(comboBox_BaudRate.SelectedItem.ToString());serialPort1.DataBits = Convert.ToInt32(comboBox_DataBit.SelectedItem.ToString());//设置停止位if (comboBox_StopBit.Text == "One"){serialPort1.StopBits = StopBits.One;} else if (comboBox_StopBit.Text == "Two"){serialPort1.StopBits = StopBits.Two;} else if (comboBox_StopBit.Text == "OnePointFive"){serialPort1.StopBits = StopBits.OnePointFive;} else if (comboBox_StopBit.Text == "None"){serialPort1.StopBits = StopBits.None;}//设置奇偶校验位if (comboBox_Parity.Text == "Odd"){serialPort1.Parity = Parity.Odd;}else if (comboBox_Parity.Text == "Even"){serialPort1.Parity = Parity.Even;}else if (comboBox_Parity.Text == "None"){serialPort1.Parity = Parity.None;}try{//禁止操作组件comboBox_PortNames.Enabled = false;comboBox_BaudRate.Enabled = false;comboBox_DataBit.Enabled = false;comboBox_StopBit.Enabled = false;comboBox_Parity.Enabled = false;button_Serach.Enabled = false;serialPort1.Open(); //设置完参数后打开串口button_OpenOrClose.Text = "Close"; //更改Open按钮文本内容}catch{MessageBox.Show("串口打开失败!");}serialPort1.DataReceived += new SerialDataReceivedEventHandler(SerialDataReceive); //打开串口后绑定数据接收}else if (button_OpenOrClose.Text == "Close") {try{//允许操作组件comboBox_PortNames.Enabled = true;comboBox_BaudRate.Enabled = true;comboBox_DataBit.Enabled = true;comboBox_StopBit.Enabled = true;comboBox_Parity.Enabled = true;button_Serach.Enabled = true;serialPort1.DiscardInBuffer(); //清除缓冲区的数据serialPort1.Close();button_OpenOrClose.Text = "Open"; //更改Close按钮文本内容}catch{MessageBox.Show("串口打开失败!");}}}#endregion/**************************************************************************/
- 4.接收数据
/**************************************************************************/#region 接收串口数据/*开辟缓存区 根据具体协议内容获取一帧数据进行接收中断处理*///变量List<byte> sp_buffer = new List<byte>(4096); //串口缓存区int sp_buffer_max = 4096; //串口缓存区最大缓存字节数private void SerialDataReceive(object sender, SerialDataReceivedEventArgs e){if (serialPort1.IsOpen == false){serialPort1.Close();return;}int Byte_len = serialPort1.BytesToRead; //读取缓存的数据长度byte[] Rc_byte = new byte[Byte_len];serialPort1.Read(Rc_byte,0,Byte_len); //将缓存数据存储进字节数组里面if (sp_buffer.Count > sp_buffer_max) //缓存超过字节数 先丢弃前面的字节 sp_buffer.RemoveRange(0,sp_buffer_max); //丢弃前面的字节0到sp_buffer_maxsp_buffer.AddRange(Rc_byte); //存入缓存区byte[] ruffer = new byte[9192]; //用来存放缓冲区的数据流//对数据流进行筛选,缓冲区每一组数据个数大于4则为我们想要的数据流if (sp_buffer.Count > 4) {sp_buffer.CopyTo(0,ruffer,0,sp_buffer.Count);Task.Run(()=>printf_data(ruffer, sp_buffer.Count, 1)); //打印数据流}}#endregion/**************************************************************************/
- 5.打印数据流
/**************************************************************************/#region 打印数据流void printf_data(byte[] Frame, int Length, int T_R) //打印串口数据{Int16 i_len;StringBuilder s = new StringBuilder();if (T_R == 0)s.Append("发送:");elses.Append("接收:");for (i_len = 0; i_len < Length; i_len++) //打印字符串{s.Append(Frame[i_len].ToString("X2"));s.Append(" ");}s.Append("[" + DateTime.Now.ToString("HH:mm:ss fff") + "]");s.Append("\r\n");string str_show = s.ToString();MethodInvoker mi = new MethodInvoker(() =>{if (richTextBox_ReceiveBox.Lines.Count() > 20)richTextBox_ReceiveBox.Clear();richTextBox_ReceiveBox.AppendText(str_show);});BeginInvoke(mi);/*textBox_com_data.Focus(); //获取焦点textBox_com_data.Select(textBox_com_data.TextLength, 0);//光标textBox_com_data.ScrollToCaret(); //滚动条*/}#endregion/**************************************************************************/
- 6.发送数据
/**************************************************************************/#region 发送数据private void button_Send_Click(object sender, EventArgs e){if (serialPort1.IsOpen){string[] sendbuff = richTextBox1.Text.Split(); //分割输入的字符串,判断有多少个字节需要发送int Buff_Len = sendbuff.Length;byte[] buff = new byte[Buff_Len];for (int i = 0; i < sendbuff.Length; i++){buff[i] = byte.Parse(sendbuff[i], System.Globalization.NumberStyles.HexNumber); //格式化字符串为十六进制数值}try{serialPort1.Write(buff, 0, buff.Length); //写数据Task.Run(()=>printf_data(buff, Buff_Len, 0));}catch{MessageBox.Show("发送失败!!");}}else {MessageBox.Show("串口未打开!");}}#endregion/**************************************************************************/
- 7.清除接收框
/**************************************************************************/#region 清除输出框private void Clear_Click(object sender, EventArgs e){richTextBox_ReceiveBox.Clear(); //清楚数据接收框数据}#endregion/**************************************************************************/
功能函数写好后绑定按钮和函数
(4)全部代码整合
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.IO;namespace Serial
{public partial class Form1 : Form{public Form1(){InitializeComponent();}/**************************************************************************/#region 初始化窗体private void Form1_Load(object sender, EventArgs e) //加载界面程序{try{string[] str = SerialPort.GetPortNames(); //获取连接到电脑的串口号并存进数组comboBox_PortNames.Items.Clear(); //清除串口号下拉框的内容comboBox_PortNames.Items.AddRange(str); //将串口号添加到下拉框if (str.Length > 0){comboBox_PortNames.SelectedIndex = 0; //设置ComboBox框的初始值comboBox_BaudRate.SelectedIndex = 7;comboBox_DataBit.SelectedIndex = 3;comboBox_StopBit.SelectedIndex = 0;comboBox_Parity.SelectedIndex = 2;}else{MessageBox.Show("当前无串口连接!");}}catch{MessageBox.Show("无串口设备!/r/n请检查是否连接设备!/r/n请检查设备驱动!");}}#endregion/**************************************************************************//**************************************************************************/#region 搜索串口(与初始化函数一样)private void button_Serach_Click(object sender, EventArgs e){try{string[] str = SerialPort.GetPortNames();comboBox_PortNames.Items.Clear();comboBox_PortNames.Items.AddRange(str);if (str.Length > 0){comboBox_PortNames.SelectedIndex = 0;comboBox_BaudRate.SelectedIndex = 7;comboBox_DataBit.SelectedIndex = 3;comboBox_StopBit.SelectedIndex = 0;comboBox_Parity.SelectedIndex = 2;}else{MessageBox.Show("当前无串口连接!");}}catch{MessageBox.Show("无串口设备!/r/n请检查是否连接设备!/r/n请检查设备驱动!");}}#endregion/**************************************************************************//**************************************************************************/#region 打开串口private void button_OpenOrClose_Click(object sender, EventArgs e){if (!serialPort1.IsOpen){if (comboBox_PortNames.SelectedItem == null){MessageBox.Show("请选择正确的串口", "提示");return;}//设置串口参数serialPort1.PortName = comboBox_PortNames.Text.ToString(); //serialPort1是serialPort组件的Name serialPort1.BaudRate = Convert.ToInt32(comboBox_BaudRate.SelectedItem.ToString());serialPort1.DataBits = Convert.ToInt32(comboBox_DataBit.SelectedItem.ToString());//设置停止位if (comboBox_StopBit.Text == "One"){serialPort1.StopBits = StopBits.One;}else if (comboBox_StopBit.Text == "Two"){serialPort1.StopBits = StopBits.Two;}else if (comboBox_StopBit.Text == "OnePointFive"){serialPort1.StopBits = StopBits.OnePointFive;}else if (comboBox_StopBit.Text == "None"){serialPort1.StopBits = StopBits.None;}//设置奇偶校验位if (comboBox_Parity.Text == "Odd"){serialPort1.Parity = Parity.Odd;}else if (comboBox_Parity.Text == "Even"){serialPort1.Parity = Parity.Even;}else if (comboBox_Parity.Text == "None"){serialPort1.Parity = Parity.None;}try{//禁止操作组件comboBox_PortNames.Enabled = false;comboBox_BaudRate.Enabled = false;comboBox_DataBit.Enabled = false;comboBox_StopBit.Enabled = false;comboBox_Parity.Enabled = false;button_Serach.Enabled = false;serialPort1.Open(); //设置完参数后打开串口button_OpenOrClose.Text = "Close"; //更改Open按钮文本内容}catch{MessageBox.Show("串口打开失败!");}serialPort1.DataReceived += new SerialDataReceivedEventHandler(SerialDataReceive); //打开串口后绑定数据接收}else if (button_OpenOrClose.Text == "Close"){try{//允许操作组件comboBox_PortNames.Enabled = true;comboBox_BaudRate.Enabled = true;comboBox_DataBit.Enabled = true;comboBox_StopBit.Enabled = true;comboBox_Parity.Enabled = true;button_Serach.Enabled = true;serialPort1.DiscardInBuffer(); //清除缓冲区的数据serialPort1.Close();button_OpenOrClose.Text = "Open"; //更改Close按钮文本内容}catch{MessageBox.Show("串口打开失败!");}}}#endregion/**************************************************************************//**************************************************************************/#region 接收串口数据/*开辟缓存区 根据具体协议内容获取一帧数据进行接收中断处理*///变量List<byte> sp_buffer = new List<byte>(4096); //串口缓存区int sp_buffer_max = 4096; //串口缓存区最大缓存字节数private void SerialDataReceive(object sender, SerialDataReceivedEventArgs e){if (serialPort1.IsOpen == false){serialPort1.Close();return;}int Byte_len = serialPort1.BytesToRead; //读取缓存的数据长度byte[] Rc_byte = new byte[Byte_len];serialPort1.Read(Rc_byte, 0, Byte_len); //将缓存数据存储进字节数组里面if (sp_buffer.Count > sp_buffer_max) //缓存超过字节数 先丢弃前面的字节 sp_buffer.RemoveRange(0, sp_buffer_max); //丢弃前面的字节0到sp_buffer_maxsp_buffer.AddRange(Rc_byte); //存入缓存区byte[] ruffer = new byte[9192]; //用来存放缓冲区的数据流//对数据流进行筛选,缓冲区每一组数据个数大于4则为我们想要的数据流if (sp_buffer.Count > 4){sp_buffer.CopyTo(0, ruffer, 0, sp_buffer.Count);Task.Run(() => printf_data(ruffer, sp_buffer.Count, 1)); //打印数据流}}#endregion/**************************************************************************//**************************************************************************/#region 打印数据流void printf_data(byte[] Frame, int Length, int T_R) //打印串口数据{Int16 i_len;StringBuilder s = new StringBuilder();if (T_R == 0)s.Append("发送:");elses.Append("接收:");for (i_len = 0; i_len < Length; i_len++) //打印字符串{s.Append(Frame[i_len].ToString("X2"));s.Append(" ");}s.Append("[" + DateTime.Now.ToString("HH:mm:ss fff") + "]");s.Append("\r\n");string str_show = s.ToString();MethodInvoker mi = new MethodInvoker(() =>{if (richTextBox_ReceiveBox.Lines.Count() > 20)richTextBox_ReceiveBox.Clear();richTextBox_ReceiveBox.AppendText(str_show);});BeginInvoke(mi);/*textBox_com_data.Focus(); //获取焦点textBox_com_data.Select(textBox_com_data.TextLength, 0);//光标textBox_com_data.ScrollToCaret(); //滚动条*/}#endregion/**************************************************************************//**************************************************************************/#region 发送数据private void button_Send_Click(object sender, EventArgs e){if (serialPort1.IsOpen){string[] sendbuff = richTextBox_Send.Text.Split(); //分割输入的字符串,判断有多少个字节需要发送int Buff_Len = sendbuff.Length;byte[] buff = new byte[Buff_Len];for (int i = 0; i < sendbuff.Length; i++){buff[i] = byte.Parse(sendbuff[i], System.Globalization.NumberStyles.HexNumber); //格式化字符串为十六进制数值}try{serialPort1.Write(buff, 0, buff.Length); //写数据Task.Run(() => printf_data(buff, Buff_Len, 0));}catch{MessageBox.Show("发送失败!!");}}else{MessageBox.Show("串口未打开!");}}#endregion/**************************************************************************//**************************************************************************/#region 清除输出框private void Clear_Click(object sender, EventArgs e){richTextBox_ReceiveBox.Clear(); //清楚数据接收框数据}#endregion/**************************************************************************/}
}
(5)实现效果
到这我们就完整地做一个简单的串口助手了,我们可以进行二次开发,添加按钮绑定特定的的指令,每次可以通过点击按钮控制硬件,或者对接收到的数据流进行解析并显示出来,这样就能够做出项目需要的上位机了。