今日尝试使用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博客