zoologist 发表于 2021-1-26 12:27:51

改造一个 USB 亮度计

物理上,人们使用坎德拉/平方米(cd/m2)作为单位,单位投影面积上的发光强度。比如,我们可以使用这个单位来比较不同发光二极管之间的性能差别。在我们日常使用的笔记本电脑上,功耗排在第二位的屏幕,而其中屏幕的功率和亮度有着直接的关系。为此,实验室购买了一个MAVO-MONITORUSB亮度计。它是德国 GMC-Instruments 集团旗下的 GOSSEN Foto-und Lichtmesstechnik Gmbh 有限公司生产的,这是一家专注于官学测量产品研发生产的公司,这款亮度计市场零售价格在1500美元左右。每次测试的时候,需要先将测试头贴在屏幕上,然后在设备的屏幕上度数,然后需要将数值抄写在纸上。这样做很不方便,因此我尝试将其改造为半自动的记录设备。
设备提供了一个 USB 端口,从数据手册上了解到,这款设备是通过内置的 FDTI 芯片来实现USB对串口的转接。之前我们介绍过 USB HID设备的读取,相较而言串口和 HID相比,从编程角度来说前者更加方便和通用,资料更加丰富,无论什么编程语言都会支持这一传统设备的编程。但是从客户使用的角度来说,USB串口很可能需要用户安装驱动,虽然Win10内置了很多USB串口芯片的驱动,但是仍然有一些不支持,在使用过程中如果客户使用 Ghost 精简版也会碰到很多问题。而HID设备只需要Application 即可,驱动完全内置。串口通讯需要设置通讯参数,对于这款设备使用的参数为“9600BPS/1 Start bit/ 7 Data bits/ 2 Stopbits/ Even parity/ no Flow control”。接下来用串口工具试验串口设定如下:

*RST恢复设备参数为默认值。运行后设备会重启,但不会影响已存储值。默认设定为:打开自动量程,启用按键输入,屏幕显示打开,存储数据不变,标准采样率(2秒一次),系统时间不受影响。
*IDN?查询设备信息,返回值包括制造商,型号,序列号,硬件版本和Firmware版本。示例如下:输入:*IDN输出:GOSSEN,M 504,31123,02,V 2.04
VERSION?查询命令解释器版本。示例如下:输入:VERSION?输出:VERSION            V 1.01(2004)
BEEPER num当后面跟有数字作为参数时,将数字设置为发声时长;没有参数时直接发声。示例如下:设定每次发生时长3秒输入:BEEPER 3 发声一次(如果运行了上面的设定,那么会产生3秒的声音)输入: BEEPER ON
KEYBOARD b启用或者关闭按键输入功能。示例如下:关闭按键输入: KEYBOARD OFF
打开按键输入: KEYBOARD ON
TIME?读取自从上一次校准之后经过的时间示例如下:输入: TIME?输出:00055,02,21
DISPLAY b打开或者关闭屏幕。可选参数为0,1,ON,OFF关闭屏幕输入:DISPLAYOFF打开屏幕输入:DISPLAY1
UNIT:PHOTOMETRICtxt选择测量结果显示的单位。可选参数为LX,FC,CD_M2,FL输入:UNIT:PHOTOMETRIC LX实践显示 CD_M2和FL会报错,提示不支持
PHOTOMETRIC?不加参数时显示当前测量结果。LX:输入:PHOTOMETRIC?输出:289E-02 CD/M2FC:403 E-03 FL1059 E00 FL1870 E-02 FL
RANGE num设定测量范围。测量范围和精度相关,具体可在表格后【Note4】看到
RANGE?查询当前的测量范围输入:RANGE? 输出:1
RANGE:AUTO b打开或者自动量程。参数0,1,ON,OFF 输入:RANGE:AUTOON无输出
RANGE:AUTO?查询当前自动量程是否打开。输入:RANGE:AUTO输出:ON
ECHo b命令回显功能是否启用。参数0,1,ON,OFF在回显打开的情况下,设备在收到主机名后会将命令再次返回主机,这样的方便便于主机确认命令是否正确,但是可能会对处理结果早晨麻烦。例如在回显打开的情况下输入:MEASURE:PHTOT?输出:MEASURE:PHTOT?123E00 LX在回显关闭的情况下输入:MEASURE:PHTOT?输出:123E00LX
DISPLAY:BACKLIGHTb打开或者关闭显示屏幕背光。参数0,1,ON,OFF打开背光输入:DISPLAY:BACKLIGHT ON关闭背光输入:DISPLAY:BACKLIGHT OFF
MEMory:CLEar清除保存的数据。输入:MEMORY:CLEAR输出:100,100
MEMory:FREE?查询当前的存储空间剩余量。输入:MEMORY:CLEAR输出:100,100

MEMory:DATA?查询已经保存的数据。输入:MEMORY:DATA输出:EMPTY

注意:1.上述命令大小写敏感;2.输入命令需要以回车作为结尾;3.上述部分命令有缩写方式,比如:查询当前测量结果的命令“PHOtometric?”可以简写为“PHOT?”,具体建议以手册为准,这里皆使用完整命令。4.命令必须以回车结尾。以 “*IDN”命令为例 ,串口实际发送的十六进制数值如下,需要特别注意结尾处的 0D 0A. 2A 49 44 4E 3F 0D 0A 测量范围和精度=

测量范围Candela/m2(cd/m2)测量范围Candela/m2(ft)分辨率cd/m2分辨率


亮度I0.01...19.990.001… 1.9990.010.001
II0.1...199.90.01…   19.990.10.01
III1 …   19990.1…   199.910.1
IV10…    199901…         1999101

从上面的的知识,我们可以制作一个带有 USB Host 的设备,通过串口命令来控制这个设备,在需要的时候将设备数值发送给 PC 上。经过比较,最终选择了 Teensy 3.6开发板。 这款开发板是PJRC推出的高性能开发板,最高支持180Mhz 的主频。此外接口丰富,自带一个 USB Device 此外还有一个 USB Host,二者可以同时使用。
因此,我们使用板载 USB Host 来连接这个USB亮度计,然后将信息从 USB Device 发送给 PC。为了使用方便,我们使用一个按键进行触发,按下之后USBHost 发送询问命令给设备取得当前读数, USB Device 将自身模拟成一个键盘,直接将读数发给PC。电路设计如下:
可以看到,外围没有元件,只有连接器,U2是 USB 母头,H3 是一个排针用于连接按键。PCB 设计如下:
预览如下:
拿到手的PCB 如下:
下面就是程序设计的过程了,完整代码如下:#include "USBHost_t36.h"



// 通讯参数要求: 9600,Data Bits: 7, Parity:Even, Stop Bits:2

#define USBHOST_SERIAL_7E2 0x1207

// 按键间隔(在间隔以内再次发生的按键都会被忽略)

#define KEYDELAY 2000



// 设置使用 A1 Pin 触发发送

#define PRESSKEY A1

// 发送标志 true-发送 false-不发送

boolean Pressed=false;

// 按键触发时间

unsigned long Elsp;



char *cmdECHOOFF = "ECHO 0"; //关闭命令回显

char *cmdSETUNIT = "UNIT:PHOTOMETRIC LX"; //设定照度返回单位

char *cmdREAD    = "MEASURE:PHOTO?";//读取照度

char *cmdVERSION = "VERSION?";//显示版本





USBHost myusb;

USBSerial userial(myusb);



void setup() {

Serial.begin(115200);

// while (!Serial && (millis() < 5000)) ; // wait for Arduino Serial Monitor



// 设置使用 PRESSKEY 触发中断

pinMode(PRESSKEY,INPUT_PULLUP);

attachInterrupt(digitalPinToInterrupt(PRESSKEY), KeyAssert, RISING);

Serial.println("Starting....");

myusb.begin();

userial.begin((uint32_t) 9600,(uint32_t) USBHOST_SERIAL_7E2);

// 打开键盘

Keyboard.begin();

//开始后需要等待设备准备好

delay(5000);

}



byte Status=0;

String revData="";

String HumanReadable="";



void KeyAssert() {

Pressed=true;

}



// 将设备的科学计数法转为常用的表示,结果在 ToStr 中

void SciToStr(String s) {

/*分析设备返回的结果,得到有效数字和小数点位置*/

// 有效数字

int long validNum;

// 小数点位置,就是 E 后面的数值

int zeroPostion;

String sSub;



// Step1. 找到第一个空格,空格之前的就是有效数字

sSub=s.substring(0,s.indexOf(' '));

validNum=sSub.toInt();   



// Step2. 将第一个空格之后的字符串切割到另外的字符串中

sSub=s.substring(s.indexOf(' ')+1,s.length());



//Step3. 处理指数位

if (sSub.indexOf('-')!=-1) {

      // 有负号,切割从负号到下一个空格

      sSub=sSub.substring(sSub.indexOf('-')+1,sSub.indexOf(' '));

      zeroPostion=-sSub.toInt();

    }

else {

      // 没有负号

      sSub=sSub.substring(sSub.indexOf('E')+1,sSub.indexOf(' '));

      zeroPostion=sSub.toInt();

}

/*分析结束*/



/*根据前面结果,将字符串转为人类易读的字符串*/

   String tmp;



// 如果小数位置为正或者为0,只需要在有效数字末尾添加 0 即可

if (zeroPostion>=0) {

    HumanReadable=String (validNum);

    for (byte i=0;i<zeroPostion;i++) {

      HumanReadable=HumanReadable+'0';

    }

    return;

}



// 如果小数位置小于0 有两种情况:

// 1.在左侧添加组成 0.XYZ 或者 0.0XYZ

// 2.在数字中间加入小数点 X.YZ

if (zeroPostion<0) {

    tmp=(String)validNum;

    // 处理情况 1

    if (abs(zeroPostion)>=tmp.length()) {

      HumanReadable="0.";

      for (byte i=0;i<abs(zeroPostion)-tmp.length();i++) {

      HumanReadable=HumanReadable+"0";

      }

      HumanReadable=HumanReadable+String(validNum);

      return;

    }

    else {

      // 处理情况 2

      HumanReadable=tmp;

      tmp=HumanReadable.substring(0,HumanReadable.length()-abs(zeroPostion))+'.'+HumanReadable.substring(HumanReadable.length()-abs(zeroPostion));

      HumanReadable=tmp;

      return;

    }

}   

}



void loop() {

    myusb.Task();



char c;





if (Status==0) {

      userial.println(cmdECHOOFF);

      Serial.println("Send EchoOff");

      delay(500);

      Serial.println("Send SETUNIT");

      userial.println(cmdSETUNIT);

      delay(500);

      Status=1;

      //if (userial.available()) {Status=1;}

}



if (Status==1) {

          userial.println(cmdREAD);

          //Serial.println("Read");

          delay(100);

          while (userial.available()) {

                c=userial.read();

                //Serial.write(c);

                // 有可能收到 0

                if (c==0) {continue;}

                revData.concat((String)c);

                if (c==0x0a) {Status=2;}

                delay(2);

          }

    }



if (Status==2) {

      //Serial.print(">"); Serial.print(revData); Serial.println("<");

      SciToStr(revData);

      Serial.println(HumanReadable);

      if ((Pressed)&(millis()>Elsp)) {

          Pressed=false;

          Keyboard.print(HumanReadable);

          Keyboard.press(KEY_DOWN);

          Keyboard.release(KEY_DOWN);

          Elsp=millis()+KEYDELAY;

      }

   

      revData="";

      Status=1;

}

}简单的流程介绍:1.对设备发送 ECHO OFF 的命令,设置返回值单位2. 如果有按键按下,那么设置起来标志信息3. 轮询对设备发送读取当前亮度的命令4. 如果按键标志位设置起来,那么就将上一次查询到的当前亮度通过USB接口发送给 PC

Teensy 3.6 开发板提供了非常强大的 USB 支持,自带的例子涵盖了 HIDCDC MSD 以及 Audio,可以非常方便的用在自己的项目中。美中不足的是价格较高(300元左右),如果你的项目对于价格不敏感,或者需要快速实现 USB 相关功能,不妨考虑一下这款开发板。

zoologist 发表于 2021-1-26 12:27:52

电路图和 PCB 如下

完整代码如下

发表于 2021-1-26 21:43:13

zoologist 发表于 2021-1-26 12:27
电路图和 PCB 如下

完整代码如下

好的收到

发表于 2021-1-26 22:20:28

zoologist 发表于 2021-1-26 12:27
电路图和 PCB 如下

完整代码如下

请问,PCB是用立创EDA设计的吗?

zoologist 发表于 2021-1-27 08:26:24

诩 发表于 2021-1-26 22:20
请问,PCB是用立创EDA设计的吗?

是的,立创这个比较方面,我开始用 eagle 后面都是立创 Eda了

DFHkLLPkvkd 发表于 2021-1-27 16:14:28

66666666666666666666666666666666666

发表于 2021-1-27 17:19:46

zoologist 发表于 2021-1-27 08:26
是的,立创这个比较方面,我开始用 eagle 后面都是立创 Eda了

好的

DFHkLLPkvkd 发表于 2021-1-31 15:25:00

#在这里快速回复#谢谢啦
页: [1]
查看完整版本: 改造一个 USB 亮度计