使用 UT395B 实现激光测距-Arduino中文社区 - Powered by Discuz!

Arduino中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 9625|回复: 10

[项目] 使用 UT395B 实现激光测距

[复制链接]
发表于 2019-1-31 12:03 | 显示全部楼层 |阅读模式
国际单位制的长度单位“米”(meter, metre)起源于法国。1790年由法国科学家组成的特别委员会,建议以通过巴黎的地球子午线全长的四千万分之一作为长度单位──米,之后法国国民议会决定采纳了这个计量制度。现在全球通用的国际长度单位米,就是由此规定而来。1799年根据测量结果制成一根铂质原器——铂杆,以此杆两端之间的距离定为1米,并交法国档案局保管,所以也称为“档案米”。这就是最早的米定义,这支米原器一直保存在巴黎档案局里。但是随后的使用中,人们发现温度、湿度、气压还有放置方式都会对这个原器长度产生影响,这导致使用和矫正很不方便。20世纪50年代,随着同位素光谱光源的发展。发现了宽度很窄的氪-86同位素谱线,加上干涉技术的成功,人们终于找到了一种不易毁坏的自然标准,即以光波波长作为长度单位的自然基准。1960年第十一届国际计量大会对米的定义作了如下更改:米的长度等于氪-86原子的2P105d1能级之间跃迁的辐射在真空中波长的1650763.73。这一自然基准,性能稳定,没有变形问题,容易复现,而且具有很高的复现精度70年代以来,随着科学技术的进步,对时间和光速的测定都达到了很高的精确度。因此,198310月在巴黎召开的第十七届国际计量大会上通过了米的新定义:“米是1299792458秒的时间间隔内光在真空中行程的长度”。这样,基于光谱线波长的米的定义就被新的米定义所替代了。
随着时代的发展,很多年前高不可攀的激光距离传感器价格不断走低,激光测距正在逐渐取代传统的皮尺走入寻常百姓家。最近笔者公司有一个检测物体距离的需求,经过研究决定使用激光距离传感器来完成这个要求。淘宝上可以找到一些激光测距模块,但是出于准确性可靠性以及成本的考虑最终选择的是带有USB接口的优利德 UT395B,该型号测试距离范围在0.05-70m,精度可以达到1.5mm±(d*十万分之五),完全可以满足使用需要。

image001.png

和之前的项目一样,这次依然使用USBlyzer 软件进行分析。在USBlyzer的帮助下,抓取设备和UT395B自带软件通讯包,然后逐步分析使用的协议。下图是配套软件的界面,使用Micro USB线连接好设备和PC之后,点击 Read 按钮会打开激光(手册上说这是打开瞄准的功能),对准需要测试的位置之后,再次点击Read即可返回距离值,随后激光被关闭。


image002.png
USBlyzer软件中可以看到UT398B 设备的Descriptor,它使用HID协议,自定义通讯格式。接下来需要使用配套软件进步一抓取通讯数据。具体来说,就是每次点击界面上的Read按钮,配套软件会发送一个命令给设备,然后设备执行完命令后返回对应的结果给上位机。UT395B和之前研究过诸如USB接触式温度计周期性发送消息的设备不同,只有按下配套软件的按钮后才会发送数据,这对于观察和记录数据来说非常方便。最终,看到一次完整的通讯有八笔通讯,四笔是发生在第一次按键之后,另外四笔是发生在第二次按键之后。下图是抓包的结果,其中OUTPUTReport是主机发送给 UT395B的数据,INPUT Report UT395B 发送给主机的数据。


image003.png

image004.png
通过观察,最终总结出来数据的含义如下:
  
序号
  
方向
数据
功能
1
主机à设备
41 54 4B 30 30 31 23 00 00 00 00 41  00 00 00 54 00 00 00 44 00 00 00 00
主机要求打开激光瞄准
2
设备à主机
41 54 4B  30 30 31 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
设备 ECHO
3
主机à设备
41 54 44 30 30 31 23 00 00 00 00 41 00 00 00 54 00 00 00 4B 00 00 00  30
主机要求获得上一笔距离数据
4
设备à主机
41 54 44 00 00 12 A0 00 00 00 35 00 00 00 35  F5 23 00 00 00 00 00 00 00
设备返回上一笔数据
5
主机à设备
41 54 4B 30 30 31 23 00 00 00 00 41  00 00 00 54 00 00 00 44 00 00 00 00
主机要求关闭激光瞄准 ,这个和第一条命令是相同的。就是说当设备激光出于关闭状态时,收到这个命令会打开激光;反之是关闭激光
6
设备à主机
41 54 4B 30 30 31 23 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  00
设备 ECHO
7
主机à设备
41 54 44 30 30 31 23 00 00 00 00 41 00 00 00 54 00 00 00 4B 00 00 00  30
主机要求获得上一笔数据
8
设备à主机
41 54 44 00 00 21 D2 00 00 00 1F 00 00 00 1F  0A 23 00 00 00 00 00 00 00
设备返回数据,就是需要的距离信息

距离信息是通过上庙表格中3号和7号命令获得的,UT395B收到这个命令后返回距离值。例如:4154 44 00 00 3A E8 00 …… 其中的 00 3A E8 就是距离信息。0x3AE8=15080对应显示在屏幕上的距离是1.508米。经过多次实验,实际取得的数据是小数点后4位,最后一位会进行四舍五入,屏幕只显示小数点后3位的数值。在 Windows 下使用 VS2015编写代码验证上述发现,结果如下:

image005.png
这里测试得到的58.152米的结果是在夜间测试远处楼房得到的,白天的时候,光线会湮没在日光中而导致测量失败,从官方文档来看,可以通过加装反光板的方式来增加测试距离。人类的太空探索中同样可以使用这种方式来进行测距。历史上美国登月计划中的阿波罗11号,14号和15号在月面上安放的三台激光反射镜。世界各地的天文台都一直使用这些反射镜测量地月距离,精确度达到厘米级别。

阿波罗15号在月面安放的反射
image007.jpg
来自https://www.nasa.gov/sites/default/files/images/444021main_apollo15_LRRRpart_HI.jpg
关键代码如下:
游客,如果您要查看本帖隐藏内容请回复

调用方法如下:

Switch(hUsb);GetData(hUsb);Sleep(400);
SendData(hUsb);GetData(hUsb);Sleep(400);
Switch(hUsb);GetData(hUsb); Sleep(800);
SendData(hUsb); GetData(hUsb);
Windows下完成对UT395B 的控制之后,可以开始编写Arduino代码,硬件上使用USB Host Shield 来作为 HOST

UT395B.h
[mw_shl_code=arduino,true]#include "hiduniversal.h"

#include "controllerEnums.h"



#define UT395B_VID 0x0483

#define UT395B_PID 0x5751



class UT395B : public HIDUniversal {

public:



        UT395B(USB *p) :

        HIDUniversal(p) { };



        bool connected() {

                return HIDUniversal::isReady() &&

                    HIDUniversal::VID == UT395B_VID &&

                    HIDUniversal:ID == UT395B_PID;

        };



        virtual uint8_t GetAddress() {

                return bAddress;

        };



};

[/mw_shl_code]
UT395.ino
[mw_shl_code=arduino,true]#include "UT395.h"

#include <SPI.h>



USB     Usb;

UT395B  ut395(&Usb);



void setup() {

  Serial.begin(115200);



  if (Usb.Init() == -1) {

    Serial.print(F("\r\nOSC did not start"));

    while (1); // Halt

  }

  Serial.println(F("\r\nUT395B Started"));



  pinMode(2,INPUT_PULLUP);

}



void Switch()

{ uint8_t outBuff[24]={0x41,0x54,0x4B,0x30,0x30,0x31,0x23,0x00,

                       0x00,0x00,0x00,0x41,0x00,0x00,0x00,0x54,

                       0x00,0x00,0x00,0x44,0x00,0x00,0x00,0x00};

  Usb.outTransfer(ut395.GetAddress(), 1, sizeof(outBuff), outBuff);              

  delay(500);

}

void SendData()

{ uint8_t outBuff[24]={0x41,0x54,0x44,0x30,0x30,0x31,0x23,0x00,

                       0x00,0x00,0x00,0x41,0x00,0x00,0x00,0x54,

                       0x00,0x00,0x00,0x44,0x00,0x00,0x00,0x00};

  Usb.outTransfer(ut395.GetAddress(), 1, sizeof(outBuff), outBuff);              

  delay(500);

}



void loop() {

  uint8_t r;

  byte cnt;

  String s="";



  Usb.Task();



  if ((digitalRead(2)==LOW)&&(ut395.connected())) {

       uint8_t inBuff[24];



       Switch();

       r=Usb.inTransfer(ut395.GetAddress(), 2, sizeof(inBuff), inBuff,10);

       SendData();

       Usb.inTransfer(ut395.GetAddress(), 2, sizeof(inBuff), inBuff,10);

       Switch();

       Usb.inTransfer(ut395.GetAddress(), 2, sizeof(inBuff), inBuff,10);

       SendData();

       Usb.inTransfer(ut395.GetAddress(), 2, sizeof(inBuff), inBuff,10);

   

       if (0x44 == inBuff[0x02]) {

             unsigned long d;

             d = ((((unsigned long)inBuff[0x04])  << 16) & 0xFF0000) +

                               ((inBuff[0x05] << 8) & 0xFF00) +

                               (inBuff[0x06] & 0xFF);

         

             //数据是小数点后4位的,我们需要通过对最后一位四舍五入变成3位的

             //第一步:四舍五入最后一位

             if (d%10>4) {d=d+10;}

             d=d / 10;

             //得到小数点后3位

             cnt=0;

             while (cnt<3) {

                s=(d % 10)+s;

                d=d /10;

                cnt++;

             }

             //加入小数点

             s='.'+s;

             //加入小数点前面的整数位

             s=(d % 10)+s;

             d=d /10;



             while (d>0) {

                s=(d % 10)+s;

                d=d /10;

             }

             Serial.println(s);

       }            

       //这里可以输出最终获得结果原始数据

       //for (byte Index=0;Index<sizeof(inBuff);Index++)

       //   {Serial.print(inBuff[Index],HEX); Serial.print(" ");}

       //Serial.println("");

            

  }

}[/mw_shl_code]

image008.png


有了上面的方法,我们可以在PCArduino上随心所欲的设计需要的功能,比如:根据距离和间隔时间获得物体的运行速度等等。

参考文献:
1.      刘荣 圈圈教你玩USB   第1版  北京航空航天大学出版社2009年1月
2.      萧世文 USB 2.0 硬件设计 第2版 清华大学出版社 2006年10月
3.      JanAxelson / 陈乃塘 USB 3.1 完全开发手册 第5版  碁峰资讯股份有限公司  2015年10月
4.      陈吕洲  Arduino程序设计基础 第2版北京航空航天大学出版社 2015年2月

image006.png
发表于 2019-2-1 13:48 | 显示全部楼层
顶一下!真的很不错!学习了!
发表于 2019-9-1 23:51 | 显示全部楼层
非常好的资料,感谢作者!
发表于 2019-11-18 16:06 | 显示全部楼层
这个比较实用
发表于 2020-2-7 15:33 | 显示全部楼层
非常好的资料,感谢作者!
发表于 2020-2-24 16:37 | 显示全部楼层
学习大神作品
发表于 2022-4-20 16:38 | 显示全部楼层
手头有设备,就是不知道怎么用。
 楼主| 发表于 2022-4-20 16:45 | 显示全部楼层
guoweiyi86 发表于 2022-4-20 16:38
手头有设备,就是不知道怎么用。

你可以试试,有 windows代码和arduino 代码
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|Archiver|手机版|Arduino中文社区

GMT+8, 2024-12-30 00:55 , Processed in 0.094965 second(s), 18 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表