今日尝试使用C# Winform写一个上位机软件控制 SYN6288语音模块
这里不讲什么基本原理(或者讲的比较略简),直接讲实现了就......
文章提供测试代码讲解、测试效果图、整体测试工程下载
目录
控件的摆放:
SYN6288介绍:
代码编程:
对16进制发送长串的处理:
对中文语句发送的处理:
将字符串按照GB2312编码进行编码检查:
GB2312转HEX:
构建数据包:
整体代码贴出:
测试视频:
整体测试工程下载:
网上查阅资料贴出:
控件的摆放:
如图摆放控件:主要有Button、label、picturebox、listview、check、serialport、imaginelist
SYN6288介绍:
代码编程:
对16进制发送长串的处理:
条件判断:首先,它通过一个
else
语句块来确定当前是处于“16进制发送”模式(这通常是通过某个界面元素,如复选框checkBox2
的选中状态来控制的,但在这段代码中并没有直接显示这个条件)。异常处理(外层):使用了一个
try-catch
块来捕获并处理在尝试发送16进制数据时可能发生的任何异常。这个外层try-catch
块主要是用来捕获由数据转换(即,将字符串转换为字节数组)过程中可能发生的异常,比如如果输入字符串包含无法转换为16进制字节的字符(尽管在这个特定的实现中,通过移除空格和转换为大写,以及检查字符串长度为偶数,已经减少了这种可能性)。字符串处理:
- 从
textBox1
中获取用户输入的字符串,并使用Replace(" ", "").ToUpper()
方法移除所有空格并将字符串转换为大写。这是为了确保输入数据的一致性,因为空格和大小写差异在16进制表示中是有意义的。- 检查处理后的字符串长度是否为偶数。因为每两个16进制字符代表一个字节,所以字符串长度必须是偶数才能正确转换为字节数组。如果不是偶数,则记录一条日志消息(通过调用
myaddlog
函数)并返回,不执行发送操作。数据转换:
- 创建一个字节数组
data
,其大小等于处理后的字符串长度除以2(因为每两个字符代表一个字节)。- 使用一个
for
循环遍历处理后的字符串,每次迭代处理两个字符,并使用Convert.ToByte
方法将它们从16进制字符串转换为字节,然后存储在data
数组中。发送数据:
- 在内层的
try-catch
块中,尝试使用serialPort1.Write
方法将data
数组发送到串口。如果发送成功,则记录一条“16进制数据发送成功”的日志消息。- 如果在发送过程中发生异常(例如,串口已关闭或硬件问题),则捕获该异常,并记录一条包含异常消息的日志。
数据转换错误处理:
- 外层的
catch
块(这里没有指定异常类型,因此会捕获所有类型的异常)用于处理数据转换过程中可能发生的任何错误(尽管在这个特定的实现中,由于前面的字符串处理和数据转换逻辑,这种错误的可能性很小)。如果发生这种错误,则记录一条“数据转换错误,请输入16进制数”的日志消息。然而,需要注意的是,由于这个catch
块紧跟在字符串处理和数据转换代码之后,并且没有更具体的异常类型指定,它实际上可能会捕获到任何在try
块中发生的异常,而不仅仅是数据转换错误。
//16进制发送:else //数据模式{try //如果此时用户输入字符串中含有非法字符(字母,汉字,符号等等,try,catch块可以捕捉并提示){string hexString = textBox1.Text.Replace(" ", "").ToUpper(); // 移除空格并转换为大写 if (hexString.Length % 2 != 0){myaddlog(1, "输入的16进制数据长度必须为偶数!");// MessageBox.Show("输入的16进制数据长度必须为偶数!");return;}byte[] data = new byte[hexString.Length / 2];for (int i = 0; i < hexString.Length; i += 2){data[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);}try{serialPort1.Write(data, 0, data.Length);myaddlog(0, "16进制数据发送成功");}catch (Exception ex){myaddlog(1, "串口数据写入错误: " + ex.Message);}}catch{myaddlog(1, "数据转换错误,请输入16进制数");}}
对中文语句发送的处理:
在C#中,
SerialPort.Write
方法实际上并不直接接受十六进制字符串作为输入。它接受一个字节数组(byte[]
)或者一个字符串(但字符串会被按照当前编码转换为字节序列发送,这通常不是我们想要的,特别是当想发送特定的十六进制数据时)。因此,如果想要以十六进制形式发送数据(即
packet
字符串所表示的数据),需要先将这个十六进制字符串转换为一个字节数组。其中以下函数在文章之后有单独定义与解释:
GB2312转HEX:
public static string StringToHexString(string input, Encoding encoding)
将字符串按照GB2312编码进行编码检查:
public static bool IsStringEncodableInGB2312(string input)
构建数据包:
public string BuildPacket()
if (textBox1.Text != ""){//如果不是16进制发送if (!checkBox2.Checked){try{string text = textBox1.Text;isencodable= IsStringEncodableInGB2312(text);if (isencodable == false){myaddlog(1, "你输入的不是GB2312中文编码格式");return;}else{Encoding gb2312 = Encoding.GetEncoding("GB2312"); // 获取GB2312编码(注意:在某些系统上可能需要使用GBK)hexString1 = StringToHexString(text, gb2312);//MessageBox.Show(hexString1);string packet = BuildPacket();// 将十六进制字符串转换为字节数组 byte[] dataToSend = StringToByteArray(packet);//MessageBox.Show(packet); //取消这行注释可以看到发送的数据包原始数据serialPort1.Write(dataToSend, 0, dataToSend.Length);myaddlog(0, "GB2312语音数据发送成功");}//serialPort1.Write(textBox1.Text);myaddlog(0, "单条发送成功");//serialPort1.WriteLine(); //字符串写入}catch{myaddlog(1, "串口数据写入错误");}
将字符串按照GB2312编码进行编码检查:
public static bool IsStringEncodableInGB2312(string input)
// 尝试将字符串按照GB2312编码进行编码,并检查是否成功 public static bool IsStringEncodableInGB2312(string input){try{// 注意:这里使用GBK作为替代,因为GBK是GB2312的超集 // 如果你确定只需要GB2312中的字符,并且你的环境支持GB2312,也可以尝试使用Encoding.GetEncoding("GB2312") Encoding.GetEncoding("GBK").GetBytes(input);return true; // 如果没有抛出异常,则认为字符串可以被GB2312(或GBK)编码表示 }catch (EncoderFallbackException){// 如果在编码过程中遇到了无法用GBK表示的字符,则会抛出此异常 return false; // 字符串包含无法被GB2312(或GBK)编码表示的字符 }catch (ArgumentException){// 如果指定的编码名称无效,则会抛出此异常 // 注意:在.NET Core或.NET 5/6/7等较新版本中,直接使用"GB2312"可能会抛出此异常 // 在这种情况下,你应该使用"GBK"作为替代,或者确保你的环境支持GB2312编码 return false; // 编码名称无效,无法进行检查 }}
GB2312转HEX:
public static string StringToHexString(string input, Encoding encoding)
//GB2312转HEX// 将GB2312(或GBK)编码的字符串转换为16进制字符串 public static string StringToHexString(string input, Encoding encoding){if (input == null) throw new ArgumentNullException(nameof(input));if (encoding == null) throw new ArgumentNullException(nameof(encoding));byte[] bytes = encoding.GetBytes(input);StringBuilder hex = new StringBuilder(bytes.Length * 2);foreach (byte b in bytes){hex.AppendFormat("{0:x2}", b);}return hex.ToString();}
构建数据包:
// 构建并返回完整的十六进制数据包字符串 public string BuildPacket(){// 计算数据区长度(字节数),注意hexString1的长度 不需要除以2 //int dataLength = hexString1.Length / 2;//int dataLength = hexString1.Length+3;// 将hexString1转换为字节数组 byte[] dataBytes = StringToByteArray(hexString1);// 数据区长度(字节) int dataLengthBytes = dataBytes.Length+3;// 数据区长度的高位和低位(十六进制) // 注意:如果数据区长度小于0x100,高位总是0 byte lengthHigh = (byte)(dataLengthBytes >> 8);byte lengthLow = (byte)(dataLengthBytes & 0xFF);// 数据区长度的高位和低位(十六进制) //byte lengthHigh = (byte)(dataLength >> 8); // 对于小于0x100的长度,这个值总是0 //byte lengthLow = (byte)(dataLength & 0xFF);// 拼接整个数据包 StringBuilder packet = new StringBuilder();// 帧头 packet.AppendFormat("{0:X2}", 0xFD);// 数据区长度(高位和低位) packet.AppendFormat("{0:X2}{1:X2}", lengthHigh, lengthLow);// 命令字和命令参数 packet.AppendFormat("{0:X2}{1:X2}", commandWord, commandParam);// 数据区(直接从hexString1获取,无需再次转换) packet.Append(hexString1);// 计算异或校验值 byte xorChecksum = CalculateXorChecksum(packet.ToString().Substring(2)); // 跳过帧头 xorChecksum ^= 0xfd; //异或帧头// 计算异或校验值(不包括帧头、长度、命令字和命令参数) // 注意:这里我们使用dataBytes来计算校验,而不是packet的字符串表示 //byte xorChecksum = CalculateXorChecksum(dataBytes);// 添加异或校验值到数据包末尾 packet.AppendFormat("{0:X2}", xorChecksum);return packet.ToString();}// 计算异或校验值(从给定的字符串起始位置开始,不包括帧头) private byte CalculateXorChecksum(string hexData){byte checksum = 0;for (int i = 0; i < hexData.Length; i += 2){// 将每两个十六进制字符转换为一个字节,并计算异或 checksum ^= Convert.ToByte(hexData.Substring(i, 2), 16);}return checksum;}// 辅助方法:将十六进制字符串转换为字节数组static byte[] StringToByteArray(string hex){return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();}
整体代码贴出:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;namespace SYN6288_Control
{//info 表示报警级别 ,log 表示报警信息public delegate void AddLog(int info, string log);public partial class Form1 : Form{//创建这个窗体的addlog ,需要绑定一个实际方法private AddLog myaddlog;bool Form1_FClosing = false;//用于防止二次Form1_FormClosing()事件发生的string formattedLogMessage; //用于临时拼接字符串bool OPEN_SERIAL_flag = false;//打开串口标志 false:未打开string hexString1; //转化GB2312用// 命令字和命令参数 private byte commandWord = 0x01;private byte commandParam = 0x00;public Form1(){InitializeComponent();this.Load += Form1_Load;myaddlog = this.AddLog;//绑定方法serialPort1.Encoding = Encoding.GetEncoding("GB2312"); //串口接收编码Control.CheckForIllegalCrossThreadCalls = false;}//表单初始化private void Form1_Load(object sender, EventArgs e){设置第一列的宽度=整个宽度 减去 第0页宽度lstInfo.Columns[1].Width = lstInfo.ClientSize.Width - lstInfo.Columns[0].Width;for (int i = 1; i < 10; i++)//初始化串口 号下拉框内容{comboBox4.Items.Add("COM" + i.ToString()); //添加串口}for (int H = 0; H < 5; H++)//初始化串口 波特率下拉框内容{switch (H){case 0: comboBox5.Items.Add("2400"); break;case 1: comboBox5.Items.Add("4800"); break;case 2: comboBox5.Items.Add("9600"); break;case 3: comboBox5.Items.Add("115200"); break;}}//停止位 下拉框内容for (int j = 0; j < 3; j++){switch (j){case 0: comboBox7.Items.Add("1"); break;case 1: comboBox7.Items.Add("1.5"); break;case 2: comboBox7.Items.Add("2"); break;}}comboBox4.Text = "COM1";//端口下拉框初始值comboBox5.Text = "9600";//波特率下拉框初始值comboBox7.Text = "1";//停止位comboBox6.Text = "8";//数据位serialPort1.Close(); //关闭串行端口连接}//写入日志委托方法//创建委托private void AddLog(int info, string Log){if (!lstInfo.InvokeRequired){//创建ListViewItem ,将时间与info放进去ListViewItem lst = new ListViewItem(" " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), info);lst.SubItems.Add(Log);lstInfo.Items.Insert(0, lst);}else{Invoke(new Action(() =>{ListViewItem lst = new ListViewItem(" " + DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), info);lst.SubItems.Add(Log);lstInfo.Items.Insert(0, lst);}));}}//串口测试发送private void button7_Click(object sender, EventArgs e){byte[] Data = new byte[1]; //单字节发数据 bool isencodable = false; //检查GB2312用if (serialPort1.IsOpen){if (textBox1.Text != ""){//如果不是16进制发送if (!checkBox2.Checked){try{string text = textBox1.Text;isencodable= IsStringEncodableInGB2312(text);if (isencodable == false){myaddlog(1, "你输入的不是GB2312中文编码格式");return;}else{Encoding gb2312 = Encoding.GetEncoding("GB2312"); // 获取GB2312编码(注意:在某些系统上可能需要使用GBK)hexString1 = StringToHexString(text, gb2312);//MessageBox.Show(hexString1);string packet = BuildPacket();// 将十六进制字符串转换为字节数组 byte[] dataToSend = StringToByteArray(packet);//MessageBox.Show(packet); //取消这行注释可以看到发送的数据包原始数据serialPort1.Write(dataToSend, 0, dataToSend.Length);myaddlog(0, "GB2312语音数据发送成功");}//serialPort1.Write(textBox1.Text);myaddlog(0, "单条发送成功");//serialPort1.WriteLine(); //字符串写入}catch{myaddlog(1, "串口数据写入错误");}}//16进制发送:else //数据模式{try //如果此时用户输入字符串中含有非法字符(字母,汉字,符号等等,try,catch块可以捕捉并提示){string hexString = textBox1.Text.Replace(" ", "").ToUpper(); // 移除空格并转换为大写 if (hexString.Length % 2 != 0){myaddlog(1, "输入的16进制数据长度必须为偶数!");// MessageBox.Show("输入的16进制数据长度必须为偶数!");return;}byte[] data = new byte[hexString.Length / 2];for (int i = 0; i < hexString.Length; i += 2){data[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);}try{serialPort1.Write(data, 0, data.Length);myaddlog(0, "16进制数据发送成功");}catch (Exception ex){myaddlog(1, "串口数据写入错误: " + ex.Message);}}catch{myaddlog(1, "数据转换错误,请输入16进制数");}}}}else if (serialPort1.IsOpen==false){myaddlog(1, "错误警告: 端口无设备连接");}}//GB2312转HEX// 将GB2312(或GBK)编码的字符串转换为16进制字符串 public static string StringToHexString(string input, Encoding encoding){if (input == null) throw new ArgumentNullException(nameof(input));if (encoding == null) throw new ArgumentNullException(nameof(encoding));byte[] bytes = encoding.GetBytes(input);StringBuilder hex = new StringBuilder(bytes.Length * 2);foreach (byte b in bytes){hex.AppendFormat("{0:x2}", b);}return hex.ToString();}// 尝试将字符串按照GB2312编码进行编码,并检查是否成功 public static bool IsStringEncodableInGB2312(string input){try{// 注意:这里使用GBK作为替代,因为GBK是GB2312的超集 // 如果你确定只需要GB2312中的字符,并且你的环境支持GB2312,也可以尝试使用Encoding.GetEncoding("GB2312") Encoding.GetEncoding("GBK").GetBytes(input);return true; // 如果没有抛出异常,则认为字符串可以被GB2312(或GBK)编码表示 }catch (EncoderFallbackException){// 如果在编码过程中遇到了无法用GBK表示的字符,则会抛出此异常 return false; // 字符串包含无法被GB2312(或GBK)编码表示的字符 }catch (ArgumentException){// 如果指定的编码名称无效,则会抛出此异常 // 注意:在.NET Core或.NET 5/6/7等较新版本中,直接使用"GB2312"可能会抛出此异常 // 在这种情况下,你应该使用"GBK"作为替代,或者确保你的环境支持GB2312编码 return false; // 编码名称无效,无法进行检查 }}// 构建并返回完整的十六进制数据包字符串 public string BuildPacket(){// 计算数据区长度(字节数),注意hexString1的长度 不需要除以2 //int dataLength = hexString1.Length / 2;//int dataLength = hexString1.Length+3;// 将hexString1转换为字节数组 byte[] dataBytes = StringToByteArray(hexString1);// 数据区长度(字节) int dataLengthBytes = dataBytes.Length+3;// 数据区长度的高位和低位(十六进制) // 注意:如果数据区长度小于0x100,高位总是0 byte lengthHigh = (byte)(dataLengthBytes >> 8);byte lengthLow = (byte)(dataLengthBytes & 0xFF);// 数据区长度的高位和低位(十六进制) //byte lengthHigh = (byte)(dataLength >> 8); // 对于小于0x100的长度,这个值总是0 //byte lengthLow = (byte)(dataLength & 0xFF);// 拼接整个数据包 StringBuilder packet = new StringBuilder();// 帧头 packet.AppendFormat("{0:X2}", 0xFD);// 数据区长度(高位和低位) packet.AppendFormat("{0:X2}{1:X2}", lengthHigh, lengthLow);// 命令字和命令参数 packet.AppendFormat("{0:X2}{1:X2}", commandWord, commandParam);// 数据区(直接从hexString1获取,无需再次转换) packet.Append(hexString1);// 计算异或校验值 byte xorChecksum = CalculateXorChecksum(packet.ToString().Substring(2)); // 跳过帧头 xorChecksum ^= 0xfd; //异或帧头// 计算异或校验值(不包括帧头、长度、命令字和命令参数) // 注意:这里我们使用dataBytes来计算校验,而不是packet的字符串表示 //byte xorChecksum = CalculateXorChecksum(dataBytes);// 添加异或校验值到数据包末尾 packet.AppendFormat("{0:X2}", xorChecksum);return packet.ToString();}// 计算异或校验值(从给定的字符串起始位置开始,不包括帧头) private byte CalculateXorChecksum(string hexData){byte checksum = 0;for (int i = 0; i < hexData.Length; i += 2){// 将每两个十六进制字符转换为一个字节,并计算异或 checksum ^= Convert.ToByte(hexData.Substring(i, 2), 16);}return checksum;}// 辅助方法:将十六进制字符串转换为字节数组static byte[] StringToByteArray(string hex){return Enumerable.Range(0, hex.Length).Where(x => x % 2 == 0).Select(x => Convert.ToByte(hex.Substring(x, 2), 16)).ToArray();}//串口接收//一个接收数据事件获取串口发送来的数据private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e){//处理事件这块可以加上延时确保不定数的数据可以全部收到缓冲后,才去读缓冲内容--单位: 毫秒Thread.Sleep(50);//如果16进制转换没被勾选if (!checkBox1.Checked){myaddlog(0, serialPort1.ReadExisting());myaddlog(0, "串口消息接收回传:");}//如果16进制转换被勾选了else{try{//定义缓冲区数组大小为串口缓冲区数据的字节数//因为串口事件触发时有可能收到不止一个字节byte[] data = new byte[serialPort1.BytesToRead];serialPort1.Read(data, 0, data.Length);foreach (byte Member in data) //遍历用法{string str = Convert.ToString(Member, 16).ToUpper();formattedLogMessage = string.Format("0x" + (str.Length == 1 ? "0" + str : str) + " ");myaddlog(0, formattedLogMessage);}myaddlog(0, "串口消息接收回传:");}catch { }}}private void label2_Click(object sender, EventArgs e) { }private void label1_Click(object sender, EventArgs e) { }private void pictureBox1_Click(object sender, EventArgs e){}//清除日志区private void button8_Click(object sender, EventArgs e){lstInfo.Items.Clear(); //清除日志listview 的内容MessageBox.Show("已成功清除日志区", "清除接收区");}//打开/关闭串口private void button6_Click_1(object sender, EventArgs e){if (OPEN_SERIAL_flag == false){try{serialPort1.PortName = comboBox4.Text;//设置端口号serialPort1.BaudRate = Convert.ToInt32(comboBox5.Text);//设置端口波特率serialPort1.StopBits = (StopBits)Convert.ToInt32(comboBox7.Text);//设置停止位serialPort1.DataBits = Convert.ToInt32(comboBox6.Text);//设置数据位serialPort1.ReceivedBytesThreshold = 1;serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);serialPort1.Open(); //打开串口OPEN_SERIAL_flag = true; //标记打开了串口myaddlog(0, "当前串口有设备连接,串口已成功打开");button6.Text = "关闭串口";}catch{myaddlog(1, "错误警告: 端口无设备连接");button6.Text = "打开串口";}}else if (OPEN_SERIAL_flag == true){try{serialPort1.Close(); //关闭串口 myaddlog(0, "已关闭串口 ");OPEN_SERIAL_flag = false;button6.Text = "打开串口";}catch { }}}}
}
测试视频:
发送数据区的[v15]是调节音量大小的,数字范围是0~16,数字越大,音量越大!
打包的整体测试工程压缩包 里的测试软件路径如下
打开这个SYN6288_Control.exe程序就是我写好的上位机了
SYN6288语音模块_Winform上位机控制软件
整体测试工程下载:
https://download.csdn.net/download/qq_64257614/89616214
网上查阅资料贴出:
STM32 使用SYN6288语音模块-CSDN博客
STM32传感器外设集--语音模块(SYN6288)_syn6288语音模块-CSDN博客