你的位置:首页 > ASP.net教程

[ASP.net教程]盈动线性绝对值编码器(光栅尺)的测试记录


这款盈动的线性绝对值编码器型号为: CAPLIN-A-485

其安装好光栅尺后的外型下面这样的.

 工作的时候,信号端子和光栅尺是固定的, 滑头由电机带动.

 

编码器数据由485口主动抛出, IPC可以在适当时机读取.

 

这款产品的通讯格式如下:

 

默认的波特率为9600,每桢起始位为0,数据位8位,停止位1位,没有校验和。
一串完整的数据以“55 AA”或者“AA 55”开头(每传输一次绝
对位置交替一次),跟着4桢为绝对值位置,例如:
“55 AA + XX XX XX XX”->”AA 55+XX XX XX XX”->”55 AA+ XX XX
XX XX”->”AA 55+XX XX XX XX”……..
注:XX为绝对位置数据

 

传输上来的 32 位数据
(1) 低 20 位为 4mm 细分位置, 满量程对应 4mm 长度,最小精
度物理长度是(4mm/4096)。
(2)高 12 位为 4mm 累加位置,其最低位对应的物理长度为4mm.
如下:
‘’ AAAA-AAAA-AAAA’’+’’XXXX-XXXX-XXXX-NNNN-NNNN’’

解析可以直接用’’ AAAA-AAAA-AAAA-XXXX-XXXX-XXXX’高24 位化为十进制,如上个例子化为十进制得 232315,则所对应的绝对位置为(232315/4096)*4mm=226.870mm。

 

笔者通过串口助手采集了一段数据,如下:

AA 55 00 05 19 E3 55 AA 00 05 19 E3 AA 55 00 05 19 E3 55 AA 00 05 19 E3 AA 55 00 05 19 E3 55 AA 00 05 19 E3 AA 55 00 05 19 E3 55 AA 00 05 19 E3 AA 55 

标红字符之间的就是我们要得到的数据.

 

笔者写了一个光栅尺的类,  你只需要适时读取这个类的Curposition属性即可得到当前的光栅尺读数.

 1  class GratingRuler:SerialPort 2   { 3     private Timer t1; 4     private double curPosition; 5     public double CurPosition  6     { 7       get  8       { 9         return curPosition;10       }11     }12     private IList<byte> readBuff;13     public GratingRuler()14     {15       try16       {17         this.PortName = "COM4";18         this.BaudRate = 9600;19         this.DataBits = 8;20         this.StopBits = StopBits.One;21         this.Parity = Parity.None;22         readBuff = new List<byte>();23         this.ReadBufferSize = 1024;24         this.Open();25       }26       catch (Exception e1)27       {28         throw e1;29       }30 31       t1 = new Timer(32         new TimerCallback( readRulerValue),33         null,0,100);34     }35 36     private void readRulerValue(object a)37     {38       double res = 0.0f;39       List<byte> dataBuff = new List<byte>();40       byte[] sbyteAry = new byte[1024];41       var cary = this.Read(sbyteAry, 0, this.ReadBufferSize);42       for (int i = sbyteAry.Length - 1; i > 0; i--)43       {44         if ((sbyteAry[i] == 0xAA && sbyteAry[i - 1] == 0x55) ||45           (sbyteAry[i] == 0x55 && sbyteAry[i - 1] == 0xAA))46         {47           dataBuff.Add(sbyteAry[i - 2]);48           dataBuff.Add(sbyteAry[i - 3]);49           dataBuff.Add(sbyteAry[i - 4]);50           dataBuff.Add(sbyteAry[i - 5]);51           res = getvalue(dataBuff);52           this.DiscardOutBuffer();53           dataBuff.Clear();54           break;55         }56       }57       curPosition = res;58     }59 60     private double getvalue(List<byte> dataBuff)61     {62       Int32 v= BitConverter.ToInt32(dataBuff.ToArray(), 0);63       string s = Convert.ToString(v, 2);64       s = s.PadLeft(32, '0');65       double g12 = Convert.ToInt32(s.Substring(32 - 12, 12), 2);66       double d20 = Convert.ToInt32(s.Substring(8, 12), 2);67       double res1 = g12 * 4 + (d20 / 4069) * 4;68       var ss1 = s.Substring(32 - 24, 24);69       double res2= ((double)(Convert.ToInt32(s.Substring(32-24, 24), 2)) / 4096) * 4;70       return res2;71     }72 73 74   }

注意下面的代码中, 提取了0xAA,0x55或者0x55,0xAA之间的数据, 还需要按计算机内存字节顺序(高位在后,低位在前) 排列一下, 才可以解释为正确正确的32位数字.

如下面代码中按5,4,3,2的顺序重排列了一次.

 if ((sbyteAry[i] == 0xAA && sbyteAry[i - 1] == 0x55) ||          (sbyteAry[i] == 0x55 && sbyteAry[i - 1] == 0xAA))        {          dataBuff.Add(sbyteAry[i - 2]);          dataBuff.Add(sbyteAry[i - 3]);          dataBuff.Add(sbyteAry[i - 4]);          dataBuff.Add(sbyteAry[i - 5]);          res = getvalue(dataBuff);          this.DiscardOutBuffer();          dataBuff.Clear();          break;        }

下面的代码把这4个字节的内容拼成32位数,并且按照此款编码器的通讯格式解析出最终的位置值, 单位mm

private double getvalue(List<byte> dataBuff)    {      Int32 v= BitConverter.ToInt32(dataBuff.ToArray(), 0);      string s = Convert.ToString(v, 2);      s = s.PadLeft(32, '0');      double g12 = Convert.ToInt32(s.Substring(32 - 12, 12), 2);      double d20 = Convert.ToInt32(s.Substring(8, 12), 2);      double res1 = g12 * 4 + (d20 / 4069) * 4;      var ss1 = s.Substring(32 - 24, 24);      double res2= ((double)(Convert.ToInt32(s.Substring(32-24, 24), 2)) / 4096) * 4;      return res2;    }

上面笔者用串口助手抓取的数据为:

00 05 19 E3

按上面的代码, 解析出来位置值为: 326.4716796mm

朋友们可以把这个结果数据代入代码中验证一下, 就能很快明白了.

 

这个类中,有几点经验需要记录一下:

1. 不要在DataReceived事件中取得串口读数, 因为编码器会不断丢数据过来, 这个事件会不断触发.

    正确的做法是在需要的时候, 才去读一下当前串口缓冲区中的数据, 然后立刻丢弃缓冲区中的数据.

2. 不要把"取当前编码器的值" 这个功能设计成一个包含串口读取动作的函数,

     因为调用者可能会在多个线程中同时调这个功能, 或者在循环中高速调用这个功能. 这样会造成串口无法响应这种并行处理或者高速的请求.

    正确的做法是在类中以私有方法按一定速度以单线程读取当前的串口数据. 对外部公开一个属性, 供调用者读取当前编码器的值. 在本类中, 是在一个内部独立的线程中以一定速度持续读取串口数据的, 对外则公开一个属性curPosition

 

对这款产品的使用感觉

1. 滑动读头距离光栅尺的距离, 以及平行关系. 还有光栅尺贴的时候的平整性, 都会影响读数的精准度.  可以说是安装的时候要小心翼翼, 是个细活.

2. 目前见到最好的精度是在小数点后第3位跳动, 也就是um级别. 但是仍然发现在同一位置,有时候有可能跳出一个很小的值来, 然后又恢复正常. 感觉如果是高速运动需要时实反馈时, 这东西有些让人担心. 如果在运动后,静止下来测量走了多远, 还是可以的.

3. 如果你设备冷启动上电后, 有时候你不动一下滑头, 读出来的数值为0, 动一下后, 编码器又"活"过来了, 开始持续有数据发出来.

4. 见过的一个最严重的问题是: 有一天突然发现所有数值被加上了9000, 即昨天还在531的位置, 今天变成了9531了, 就好像是数据被累计了一样. 这个情况是在使用了一个月后突然出现的. 厂家认为我们是程序有问题. 但显然不是, 因为之前跑了那么久都是好的.  后来我发现断掉设备所有的电源后, 再开又好了. 真是不得其解.

 

 

由于测试工程是用于验证另一个项目, 本编码器光栅尺只是其中的一个元件, 所以我就不把原代码发出来了.