临近公司放假,也好在年前对这次项目的一项总结(包括代码)。刚分配到这份项目时,我认为自己可以完成这次的项目,但是在项目的开发过程中,还是遇到许多的问题,总的来说,分为以下几点:
1.前期对项目的背景不是很清楚(我一开始以为只要拿到项目就开始Coding,完成项目,其实不是这样);
2.文档写的不够完善(只写了需求分析、概要设计、没有数据库设计,因为文件都是存放在XML文档里);
3.对使用的技术不会(操作RS232串口,以前在学校从来没学过这些,当然,也不需要学习这些。本人认为,自己主动去学和学校所教最大的区别在于主动是有意识的去学,而学校所教是被动的);
4.与老板(客户)的沟通不够,客户那边一有新的需求,就马上换成新的需求,然后换来换去,弄的心情很不爽,特别是一个项目需要分成好几种类型,比如,四川省的我要这种,福建省的我要那种,北京市的又是另外的,而且还是同时更新。所以改的比较痛苦,也很纠结。(现在已改用OO的方法);
5.最重要的是我自己对技术看的太轻松了,我认为技术会了就是会了,其实太天真了~ ~(其实是看个人的心态拉,我认为自己还Ok)。
所以,我一直在改变、进步!好了,下面是项目中一个串口操作类重构后的Code。
这里我专门写了一个类来处理对串口的操作,主要是write、read,其中有用到包括delegate、event、lock、多线程。
首先,实例化串行端口,有端口名称 波特率 奇偶校验位 数据位 停止位这些。然后在Program实例化这个串口操作类。
SettingClass sc=new SettingClass();
public class SettingClass
{
/// <summary>
/// 实例化串行端口资源 端口名称 波特率 奇偶校验位 数据位 停止位.
/// </summary>
public static SerialPort serPort = null;
//声明委托
public delegate void InsertData(byte[] ByData);
//声明事件
public static event InsertData GetData;
写个构造函数,来处理串口对象的初始化
/// <summary>
/// 构造函数,处理串口对象的初始化.
/// </summary>
public SettingClass()
{
try
{
serPort = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
serPort.DataReceived += new SerialDataReceivedEventHandler(serPort_DataReceived);
serPort.Open();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("未发现到串口COM1,请检查.!\n" + ex.Message, "错误警告");
}
}
我们还可以写个带参的构造函数,用来后续选择端口的初始化
/// <summary>
/// 后续可以选择COM端口初始化
/// </summary>
/// <param name="portName"></param>
/// <param name="baudRate"></param>
/// <param name="parity"></param>
/// <param name="dataBits"></param>
/// <param name="stopBits"></param>
public SettingClass(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits)
{
try
{
serPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
serPort.DataReceived += new SerialDataReceivedEventHandler(serPort_DataReceived);
serPort.Open();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("未发现到串口COM1,请检查.!\n" + ex.Message, "错误警告");
}
}
在操作serialport的时候,如果需要实行实时的监听来自设备的数据包,那么,在SerialPort类中有DataReceived事件,当串口的读缓存有数据到达时则触发DataReceived事件。(这个在上一篇文章中有,这里只是提一下)
/// <summary>
/// 处理来自设备的数据.事件.
/// </summary>
/// 缓存数组
List<byte> buffer_list = new List<byte>();
private void serPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
//获取缓冲区的字节个数.
int intToRead = serPort.BytesToRead;
//声明临时数组存储串口数据.
byte[] byteData = new byte[intToRead];
//读取来自缓冲区的数据.
serPort.Read(byteData, 0, byteData.Length);
byte[] bytedata = new byte[byteData.Length];
//把接收到的数据保存到缓存里
for (int i = 0; i < byteData.Length; i++)
{
bytedata[i] += byteData[i];
buffer_list.Add(bytedata[i]);
}
lock (_syncLock)
{
// 开启新线程
Thread th = new Thread(new ThreadStart(ReturnBytaData));
th.Start();
//if (th.IsAlive)
//{
// th.Abort();
//}
}
}
catch { }
}
private static readonly object _syncLock = new object();
在上面这个事件里面,可以看到我声明了一个缓存数组来接收数据,作用是:如果设备发包只发了一个、或者多个、又或者1个半,因为包是由Code+Length+Data+CRC组成,接收的时候怕只接收到了Code+Length,后面的没接收到,那么这个buffer的用处就来了,可以把前面的包和后面的包拼接成一个完整的包,然后传输到form的接收函数里。设备发的数据包不可能只发一个Code+Length,后面在发一个Code+Length,规则协议和TCP传输原理一样。
下面的函数是用来处理从设备接收到的数据包,最后只返回Code和Data,返回Code是因为我一个界面上有不同的命令,但是会有相同的参数,视情况而定。在这个函数中,也处理了
buffer缓存,对设备的数据包进行校验。
note:下面代码第19行 ,返回的是set ok,返回的是成功的数据包格式,需要进行处理。
1 /// <summary>
2 /// 校验从设备得到的数据包
3 /// </summary>
4 /// <param name="listbytedata">缓存数组</param>
5 /// <returns>去除length+crc的数据包</returns>
6 public void ReturnBytaData()
7 {
8 try
9 {
10 //查询缓存是否还存在完整数据包
11 while (buffer_list.Count > 4)
12 {
13 //缓存里第一个数据包
14 int bytelength = buffer_list[1];
15
16 //声明需要返回的数据包
17 byte[] returndata = new byte[bytelength - 1];
18
19 if (bytelength != 101)
20 {
21 //把缓存内完整的数据包遍历到字节数组
22 byte[] returnbytedata = new byte[bytelength + 2];
23 for (int i = 0; i < buffer_list[1] + 2; i++)
24 {
25 returnbytedata[i] += buffer_list[i];
26 }
27
28 //判断数据包的数据是否真实
29 if (bytelength == returnbytedata.Length - 2)
30 {
31 //得到高八位、低八位
32 byte j1 = returnbytedata[returnbytedata.Length - 2];
33 byte j2 = returnbytedata[returnbytedata.Length - 1];
34
35 int sum = 0;
36
37 //校验高八位、低八位.
38 for (int j = 0; j < returnbytedata.Length - 2; j++)
39 {
40 sum += returnbytedata[j];
41 }
42 byte g = (byte)(sum >> 8);
43 byte d = (byte)(sum);
44 if (j1 == g && j2 == d)
45 {
46 //得到data data=-crc,-code,-length
47 int datalength = returnbytedata.Length - 4;
48 byte[] byteData = new byte[datalength];
49 Array.Copy(returnbytedata, 2, byteData, 0, datalength);
50
51 //得到code
52 byte[] bytecode = new byte[1];
53 Array.Copy(returnbytedata, 0, bytecode, 0, 1);
54
55 //返回code+data
56 for (int i = 0; i < bytecode.Length; i++)
57 {
58 returndata[i] += bytecode[i];
59 }
60 for (int n = 0; n < datalength; n++)
61 {
62 returndata[n + 1] += byteData[n];
63 }
64
65 buffer_list.RemoveRange(0, returnbytedata.Length);
66
67 if (GetData != null)
68 {
69 //绑定到事件
70 GetData(returndata);
71 }
72 }
73 else
74 {
75 buffer_list.RemoveRange(0, returnbytedata[1] + 2);
76 return;
77 }
78 }
79 else
80 {
81 return;
82 }
83 }
84 else
85 {
86 buffer_list.RemoveRange(0, buffer_list.Count);
87 }
88 }
89 }
90 catch { }
91 }
最后面的是接收来自form传过来的data.
/// <summary>
/// 处理各个窗体传来的数据.各窗体只需要传送命令+数据即可.
/// </summary>
/// <param name="by">接收到的数据.</param>
public static void InsertserPortData(byte[] Data)
{
try
{
//因为各窗体发来的数据不包括CRC校验,所以长度+2.
byte[] byteText = new byte[Data.Length + 2];
int sum = 0;
for (int i = 0; i < Data.Length; i++)
{
byteText[i] += Convert.ToByte(Data[i]);
}
//遍历sum的累加和,判断高八位和低八位.
for (int n = 0; n < byteText.Length; n++)
{
sum += byteText[n];
}
//高八位.
byte g = (byte)(sum >> 8);
//低八位.
byte d = (byte)(sum);
//把检验加到字节数组中.
byteText[byteText.Length - 2] += g;
byteText[byteText.Length - 1] += d;
//写入串口.
serPort.Write(byteText, 0, byteText.Length);
}
catch { }
}
最后,在需要接收数据包的form里面注册下这个事件。
SettingClass.GetData += new SettingClass.InsertData(SettingClass_GetData);
下面的ByData是数据包(去除Length+CRC).
void SettingClass_GetData(byte[] ByData)
{
//处理接收到的数据包,显示到界面里...
}
这个项目对我的意义重大,我会一直对它进行重构下去。下次应该就是用OO的方法来写文章了。这个项目是我今年毕业以来独立负责的第一个项目。所以,帮助真的很大,在这里我非常感谢我的公司,相信我,给了我锻炼的机会,也很感谢我的同事FHW、LB、FBY、RJB等。