物理上,人们使用坎德拉/平方米(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? | 查询命令解释器版本。示例如下: 输入:[SYSTEM:]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 关闭屏幕 输入:DISPLAY OFF 打开屏幕 输入:DISPLAY 1 |
| UNIT:PHOTOMETRIC txt | 选择测量结果显示的单位。可选参数为 LX,FC,CD_M2,FL 输入:UNIT:PHOTOMETRIC LX 实践显示 CD_M2和FL 会报错,提示不支持 |
| PHOTOMETRIC? | 不加参数时显示当前测量结果。 LX: 输入:PHOTOMETRIC? 输出:289 E-02 CD/M2 FC: 403 E-03 FL 1059 E00 FL 1870 E-02 FL |
| RANGE num | 设定测量范围。测量范围和精度相关,具体可在表格后【Note 4】看到 |
| RANGE? | |
| RANGE:AUTO b | 打开或者自动量程。参数 0,1,ON,OFF 输入:RANGE:AUTO ON 无输出 |
| RANGE:AUTO? | 查询当前自动量程是否打开。 输入:RANGE:AUTO 输出:ON |
| ECHo b | 命令回显功能是否启用。参数 0,1,ON,OFF 在回显打开的情况下,设备在收到主机名后会将命令再次返回主机,这样的方便便于主机确认命令是否正确,但是可能会对处理结果早晨麻烦。 例如在回显打开的情况下 输入:MEASURE:PHTOT? 输出:MEASURE:PHTOT? 123E00 LX 在回显关闭的情况下 输入:MEASURE:PHTOT? 输出:123E00 LX |
| DISPLAY:BACKLIGHT b | 打开或者关闭显示屏幕背光。参数 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
测量范围和精度
=
从上面的的知识,我们可以制作一个带有 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 支持,自带的例子涵盖了 HID CDC MSD 以及 Audio,可以非常方便的用在自己的项目中。美中不足的是价格较高(300元左右),如果你的项目对于价格不敏感,或者需要快速实现 USB 相关功能,不妨考虑一下这款开发板。
完整代码如下