之前因为和学长一起参加一个活动,所以自学了一下SQL Server数据库和c#编程。
之前做的是一个CPU卡的项目,我主要负责的是上位机这部分,经过短暂的学习,算是基本完成了这个项目。
那个项目就是用串口和下位机进行通信。
那么今天我们要做的是什么呢?因为我们实验室有个热敏打印机,如果想要支持图片打印,那么肯定需要一个上位机去给这个下位机发送图片数据,然后去打印。最好的方法肯定是编写一个APP,通过手机加载图片,然后发送给下位机去打印,奈何自己能力不够,不会写APP,无奈只好选择用PC端开发一个上位机然后去选择图片。
既然用PC编写上位机,那么和下位机通信也有两种方式,有线和无线。无线的肯定要复杂一点,后续可以再整。怎么简单怎么来,那就先写一个用串口和下位机通信的代码吧!
因为c#中有自带的串口组件,这就给我们编程提供了很多的便利。
像拖拽其他控件一样拖到窗体中。
会在下面显示这个控件。
我这里做的是一个串口助手,根据自己的实际应用搭建界面。
public Form1() { InitializeComponent(); serialPort1.Encoding = Encoding.GetEncoding("GB2312"); // 设置串口的编码 Control.CheckForIllegalCrossThreadCalls = false; // 忽略多线程 }
/* 窗体加载初始化 */ private void Form1_Load(object sender, EventArgs e) { /* 可以在初始化的过程中添加波特率 */ SearchCompleteSerial(serialPort1, cbb_Serial_Port); // 自动扫描找到的端口号 }
/* 调试追踪 */ public void DebugTrack(string str, Color color) { if (this.log.InvokeRequired) { Action<string, Color> method = this.DebugTrack; this.log.Invoke(method, str, color); } else { /* 字符串的长度超过1000 自动清空接收显示界面 */ if (this.log.TextLength > 1000) { this.log.Clear(); // 清空 } this.log.SelectionStart = this.log.Text.Length; this.log.SelectionColor = color; // 选择打印的颜色 /* 在接收数据前面显示时间 可选可不选 */ if (chb_Show_Time.Checked) // 如果显示时间被选中 { /* 在打印出来的信息的前面追加时间字符串 */ this.log.AppendText(DateTime.Now.ToString("【yyyy-MM-dd HH:mm:ss】 ") + str); } else { this.log.AppendText(str); // 不添加时间 直接在后面追加字符串 // this.log.AppendText("aaa"+"\r\n"); // 不添加时间 直接在后面追加字符串 } this.log.ScrollToCaret(); // 将控件内容滚动到当前插入符号位置 不加,光标在最初的位置,加上后,光标滚动到插入位置的最后面 } }/* 调试追踪 */ /* 打印字符串 */ public void log_printf(string str) { DebugTrack(str, Color.Black); // 打印出这个字符串 黑色显示 }
/* 字符串转化为十六进制的字节数据 * str:要转换的字符串 */ private byte[] strToHexBytes(string str) { str = str.Replace(" ", ""); // 把空格去掉 if (str.Length % 2 != 0) { str = str.Insert(str.Length - 1, "0"); } byte[] array = new byte[str.Length / 2]; for (int i = 0; i < str.Length / 2; i++) { array[i] = Convert.ToByte(str.Substring(2 * i, 2), 16); } return array; }
当我们发送十六进制的时候,我们需要确保输入框中输入的字符必须是0-9,A-F a-f,空格或换行这些字符。
可以使用下面这个函数来限定。
/* 发送框文本改变事件 当文本发生改变产生事件 */ private void rich_send_TextChanged(object sender, EventArgs e) { /* 判断当前是不是十六进制发送 如果是 需要判断 */ if (chb_Hex_Tx.Checked) { char[] clist = rich_send.Text.ToCharArray(); String newString = ""; for(int i = 0; i< clist.Length; i++) { int ascii = (int)clist[i]; // 把每一个字符都转化为ascii码 /* 如果这个值在A-F之间 0-9之间 可以用 空格是32 * 回车 13 换行 10 */ if ((ascii >= 48 && ascii < 59) || (ascii >= 97 && ascii < 103) || (ascii >= 65 && ascii < 71) || ascii == 32 || ascii == 10 || ascii == 13) { }else { clist[i] = '\0'; } newString += clist[i].ToString(); } rich_send.Text = newString; // 显示界面上 } }
/* 扫描按键 点击监听事件 */ private void btn_Scan_Click(object sender, EventArgs e) { /* 说明:serialPort1是添加一个组件自动生成的一个对象 */ SearchCompleteSerial(serialPort1, cbb_Serial_Port); // 自动扫描找到的端口号 }
/* 串口自动扫描端口号 */ private void SearchCompleteSerial(SerialPort myport, ComboBox mybox) { /* 先清空端口下拉列表 */ mybox.Items.Clear(); String[] mystring = SerialPort.GetPortNames(); // 获取计算机的端口名的数组 for (int i = 0; i < mystring.Length; i++) mybox.Items.Add(mystring[i]); // 往下拉列表中添加端口号 mybox.Text = mystring[0]; // 默认选中的是第一个端口(小的端口) }
通过这个函数可以打开串口,关闭串口。
/* 打开串口按键 点击监听事件 */ private void btn_OpenSerial_Click(object sender, EventArgs e) { /* 先判断当前串口是打开状态还是关闭状态 */ if (btn_OpenSerial.Text == "打开串口") // 串口还没有打开 需要打开串口 { try { serialPort1.PortName = cbb_Serial_Port.Text; // 端口号 serialPort1.BaudRate = 115200; // 写死 串口通信的波特率 serialPort1.Open(); // 打开串口 btn_OpenSerial.Text = "关闭串口"; log_printf("打开串口"); lab_Device_Connect.Text = "设备已连接"; } catch { MessageBox.Show("端口打开错误,请检查串口", "错误"); } } else // 串口已经打开 需要关闭 { try { serialPort1.Close(); // 关闭串口 btn_OpenSerial.Text = "打开串口"; log_printf("关闭串口"); lab_Device_Connect.Text = "设备已断开"; } catch { MessageBox.Show("关闭串口错误"); } } }/* 打开串口 按键监听*/
/* 发送按键 监听事件 */ private void btn_SendData_Click(object sender, EventArgs e) { byte[] data = new byte[1]; // 定义1个字节 if (serialPort1.IsOpen) // 如果串口是打开的 { if (rich_send.Text != "") // 如果发送了数据 { String dateString = rich_send.Text; // 获取要发送的字符串 /* 判断是否发送新行 */ if(chb_Tx_newLine.Checked) // 发送新行 { dateString += "\r\n"; } if (chb_Hex_Tx.Checked) // 如果发送是十六进制模式 { /* 先对要发送的数据去除掉 空格 */ dateString = dateString.Replace(" ", ""); // 把空格去掉 ///* 把换行符也去掉 */ //dateString = dateString.Replace("\r\n",""); //dateString = dateString.Replace("\r", ""); //dateString = dateString.Replace("\n", ""); // 每两位数据是1个字节 还考虑了发送奇数个字符的情况 for (int i = 0; i < (dateString.Length - dateString.Length % 2) / 2; i++) { // 把发送文本框的数值两个两个的发送,并且转化为16进制表示 data[0] = Convert.ToByte(dateString.Substring(i * 2, 2), 16); serialPort1.Write(data, 0, 1); // 从0开始写入1个字节 } if (dateString.Length % 2 != 0) // 剩下1位数据单独处理 { data[0] = Convert.ToByte(dateString.Substring(dateString.Length - 1, 1), 16); // 单独发送1位 serialPort1.Write(data, 0, 1); // 写入到串口 } txTotalCount += dateString.Length / 2; // 发送数据总长度 程序全局变量 txt_Tx_Count.Text = Convert.ToString(dateString.Length / 2); // 把发送缓冲区的数据的长度用发送长度文本框显示 txt_Tx_TotalCount.Text = Convert.ToString(txTotalCount); // 接收数据的总长度 界面显示 } else // 字符串 { try { serialPort1.WriteLine(dateString); // 以字符模式写数据,把要发送的数据写到串口 txTotalCount += rich_send.Text.Length; // 发送数据总长度 程序全局变量 txt_Tx_Count.Text = Convert.ToString(rich_send.Text.Length); // 把发送缓冲区的数据的长度用发送长度文本框显示 txt_Tx_TotalCount.Text = Convert.ToString(txTotalCount); // 接收数据的总长度 界面显示 } catch { MessageBox.Show("串口数据写入错误"); } } } else { MessageBox.Show("请输入要发送的数据"); } } else // 串口没有打开 提示不能发送数据 { MessageBox.Show("请先打开串口"); } }/* 监听按键事件 */
点击一下串口组件
点击右边的事件,选择数据接收事件
/* 串口数据接收事件 数据接收事件 这个是创建一个串口数据接收事件 要在Designer.cs中注册一下 不然没法调用 */ private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) { if (chb_Hex_Rx.Checked) // 如果接收模式为十六进制 { byte data; data = (byte)serialPort1.ReadByte(); // 读取一个数值 string str = Convert.ToString(data, 16).ToUpper(); // 转化为大写的16进制字符串 log_printf(str); // log_printf("0x" + (str.Length == 1 ? "0" + str : str) + " ");// 如果为1个字节,前面补0, 每个数值后面加空格 } else // 如果接收为字符串 { string str = serialPort1.ReadExisting(); //以字符串方式读 log_printf(str); // 往文本框中添加读出的文本数据 // TebReceiveNum.AppendText(Convert.ToString(str.Length)); /* 显示接收的数据长度 */ txTotalCount += str.Length;// 接收数据的总长度 txt_Rx_Count.Text = Convert.ToString(str.Length);// 把接收到的数据的长度赋值给这个文本框显示 txt_Rx_TotalCount.Text = Convert.ToString(txTotalCount); // 接收到的数据的总长度 界面显示 } }
我是使用两个串口助手进行通信,中间使用的是虚拟串口,大家使用的话可以去下载虚拟串口。
效果不好啊。
到此,测试基本完成。除了接收十六进制效果不好之外,其他的都可以正常使用。
用这个编写的串口发送数据效率很低,如果下位机用帧中断来处理数据的话,会接收不到数据。建议用定时器来判断一帧数据是否接收完成。