Leonrado Uno 转接板和修改后的USB Host库

有时候,我们希望在 Leonrado 上使用 Uno 的Shield,但是会发现两者的引脚并不兼容。比如: uno 上的 SPI 位置和 leonrado 不同。

这种情况下就可以使用下面的转接板。
a2u

主要是调整了 i2c 和 spi 使得转接后的能够兼容设计给 uno 的sheild.

a2u2

比如,我直接的应用就是让 USB Host Shield 可以在 Leonrado 上工作。同时,设计上考虑到国内基本上买不到合适的长脚母座,所以使用了母座外加排针的方式, 堆叠之后的样式如下:

a2u3

电路板下载 zLeo2UnoShieldv1.0

之后,我实验编译使用Leonrado 自带USB Keyboard功能的时候遇到奇怪的问题,始终提示没有加入include “keyboard.h”。经过一番研究发现,出现这个问题的原因是:IDE定义USB keyboard的时候定义了HID Class,而USBHost 库在解析USB设备的时候同样定义了一个HID Class,二者存在冲突。
对此,需要修改避免HID Class 的冲突。我选择修改 USB Host库,因为这个毕竟是第三方库。

最终将库中的 HID Class全部修改为 NewHID ,这样我们可以在 Leonrado 同时使用 USB Host 和 模拟键盘鼠标了。

附件是修改之后的版本,亲测有效:

USB_Host_Shield_Library_2.0_Modified

从源代码到 FFS 文件

本文会以 BdsDxe.ffs 的生成为例,介绍一下从 EFI 到 FFS的编译过程。
所有的实验都是建立在UDK2015 NT32Pkg的基础上。
首先,要保证NT32能够正常编译运行。之后,在 build目录中取得生成的BIOS文件:nt32.fd。使用 fmmt –v nt32.fd 查看一下这个BIOS的组成。可以看到其中只有一个 FV , 然后有 BdsDxe的ffs。我们可以在\Build\NT32IA32\DEBUG_VS2013\IA32\MdeModulePkg\Universal\BdsDxe\BdsDxe\OUTPUT\ 目录下,找到编译生成的 BdsDxe.efi。 在 \Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe可以看到文件名为6D33944A-EC75-4855-A54D-809C75241F6C.ffs 的文件。就是说我们前面看到的BdsDxe .efi,最终得到了一个ffs文件。
ffs1

ffs2

下面就针对这个过程逐一进行分析。
1. *.PE32 文件的生成。具体的生成命令可以在在6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.1.1.pe32.txt中看到:
GenSec -s EFI_SECTION_PE32 -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.1.1.pe32 c:\udk2015\Build\NT32IA32\DEBUG_VS2013\IA32\MdeModulePkg\Universal\BdsDxe\BdsDxe\OUTPUT\BdsDxe.efi
使用工具比较一下生成前后的文件,可以看到只有头部存在一点差异。
ffs2
再查看 GenSec 的源代码,当指定为 EFI_SECTION_PE32 的时候,头的内容是 EFI_SECTION_PE32(0x10)+Length。所以和之前的EFI 相比,增加的是 0x10 和0x13004 (EFI文件的长度)
2. GUIDED文件的生成,在6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.guided.txt 可以看到, 使用的是 GenSec 工具,作为输入的是 PE32, UI和 VER文件。
GenSec -s EFI_SECTION_GUID_DEFINED –sectionalign 1 –sectionalign 1 –sectionalign 1 -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.guided c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.1.1.pe32 c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.2.ui c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.3.ver
3. 目前我尚不知道 *.UI文件是如何生成的,希望有懂的朋友指导一下,谢谢!
4. 从6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.3.ver.txt 文件可以得知*.VER 是这样生成的
GenSec -s EFI_SECTION_VERSION -n 1.0 -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.3.ver

5. 从6D33944A-EC75-4855-A54D-809C75241F6CSEC2.com.txt 文件可以得知 .COM 的生成方式如下(COM 并非DOS下面的可执行文件,而是 COMPRESS的缩写)。经过了这步,因为压缩的原因,生成的结果比压缩之前小了很多,比较起来也会发现“面目全非”。
GenSec -s EFI_SECTION_COMPRESSION -c PI_STD -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.com c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.1.guided
6. 从6D33944A-EC75-4855-A54D-809C75241F6C.ffs.txt 可以看到 FFS是如何生成的:
GenFfs -t EFI_FV_FILETYPE_DRIVER -g 6D33944A-EC75-4855-A54D-809C75241F6C -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6C.ffs -i c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC1.1.dpx -i c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.com
7. 从6D33944A-EC75-4855-A54D-809C75241F6CSEC1.1.dpx.txt,可以看到 dpx 文件的生成过程
GenSec -s EFI_SECTION_DXE_DEPEX -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC1.1.dpx c:\udk2015\Build\NT32IA32\DEBUG_VS2013\IA32\MdeModulePkg\Universal\BdsDxe\BdsDxe\OUTPUT\BdsDxe.depex

8. 接下来是最后一步,使用COM 和 DPX 生成 FFS 文件,从6D33944A-EC75-4855-A54D-809C75241F6C.ffs.txt 文件可以看到操作动作
GenFfs -t EFI_FV_FILETYPE_DRIVER -g 6D33944A-EC75-4855-A54D-809C75241F6C -o c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6C.ffs -i c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC1.1.dpx -i c:\udk2015\Build\NT32IA32\DEBUG_VS2013\FV\Ffs\6D33944A-EC75-4855-A54D-809C75241F6CBdsDxe\6D33944A-EC75-4855-A54D-809C75241F6CSEC2.com

最后生成的 FFS 和 COM 差别很小
ffs3

了解了整体的BIOS结构,做很多事情,比如:编写无需重新编译代码,直接修改ROM的工具等等。本文只是简单的对于FFS的生成进行了研究,希望这篇文章对于需要了解EDK2 Firmware 结构的朋友有所帮助。

示波器查看串口通讯的波形

虽然使用了串口很久,但是一直没有深入研究波形。最近看了一篇介绍【参考1】,随手用示波器抓了一下波形。
下面是资料中提到的例子。在无数据传输时,串口总线上应该是高电平。当有数据传输时,首先会拉低一个时钟周期,这是 Start Bit。之后是有用的数据,长度是根据双方约定好的。最后可以跟着一个校验位(也可以没用),最终用一个拉高的时钟周期表示传输完成,这是Stop Bit。然后总线继续拉高处于Idle状态。
u1
下面示波器抓取的实际波形,双方的通讯设置如下:
u2
发送0xAA
u3

发送 0x55
u4

最后附上一个常用的对 0x3F8 初始化的代码段

#define PORT 0x3f8 /* COM1 */

void init_serial() {
outb(PORT + 1, 0x00); // Disable all interrupts
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
outb(PORT + 0, 0x01); // Set divisor to 1 (lo byte) 115200 baud
outb(PORT + 1, 0x00); // (hi byte)
outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
}

参考:
1.http://www.unm.edu/~zbaker/ece238/slides/UART.pdf

让 Leonorade的键盘有“输出”能力

Arduino Leonarado 和其他型号相比,最大的特点是可以方便的将自身模拟为USB键盘和鼠标。从USB总线的角度来说,数据通讯本身是双向的。从整体角度来说键盘只是输入设备,并没有输出的能力,但是如果仔细观察会发现键盘上有三个指示灯,分别是:NumLock,ScrollLock和CapsLock。这三个指示灯作用如下:
1. Num Lock 是副键盘中数字键盘的开关。在这个键对应的键盘指示灯关闭的情况下,小键盘的按键用来移动光标(上下左右、行首、行尾等等),在这个键对应的键盘指示打开的情况下,即锁定数字键,小键盘的按键用来输入数字;
2. Caps Lock 是大小写锁定键,在这个键对应的键盘指示灯关闭的情况下,键盘输入的字母都是小写,否则键盘输入都为大写;
3. Scroll lock (滚动锁定键)最初是用来设计为DOS下自动滚动屏幕的,但是进入Windows出现图形化的界面之后,这个按键就没有用途了,只是为了兼容等等考虑在通用键盘设计上仍然有所保留。
如果你的电脑有两个以上的键盘,还能观察到一个有趣的现象:在一个键盘上按下上述三个键中的某一个,那么其他的键盘状态也会跟着发生变化。原理上来说,是Windows会将收到的切换信息再“广播”出去,保证所有的键盘状态都是同步的。我用USB逻辑分析仪抓包,当在其他键盘上按下NumLock时,系统会向每一个USB键盘发出广播。比如:下面系统中有3个键盘,当键盘1按下 NumLock 后,系统还会通知全部三个键盘“NumLock状态改变”的消息。
kb1

我们无需关心Windows中消息的格式,对于USB来说,USBHost (Windows的PC),会送出一个 SET Package来通知 USB Device(USB 键盘)。下图是我用USB逻辑分析仪抓包的结果:

kb2

进一步展看查看协议,是发送了 07 给USB 键盘
kb3

上面的原理可以帮助我们在Arduino Leonarado上实现。简单起见,我们的目标是在ArduinoLeonarado 上装上三个LED,让这三个LED和我的 USB键盘上三个LED实现同步。原生的ArduinoIDE 并没有设计这个功能,因此,需要对源代码进行修改:
第一个需要修改的地方是 \arduino\libraries\Keyboard\src\Keyboard.cpp 中的 USBKeyboard的HID 描述符(Descriptor)。增加了下面斜体字表示的部分:
0x95, 0x08, // REPORT_COUNT (8)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//ZT_DEBUG
[i] 0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x05, 0x08, // USAGE_PAGE (LEDs)
0x19, 0x01, // USAGE_MINIMUM (1)
0x29, 0x05, // USAGE_MAXIMUM (5)
0x91, 0x02, // OUTPUT (Data,Var,Abs) // LED report
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x01, // OUTPUT (Constant) // padding[/i]
//ZT_DEBUG

0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x65, // LOGICAL_MAXIMUM (101)
0x05, 0x07, // USAGE_PAGE (Keyboard)

当然,具体作用请参考USB HID协议对照解读。简单的解释:修改之前的描述符只有INPUT部分,就是键盘告诉系统“我只能发出数据”;增加部分的作用是键盘告诉系统能够接收HOST过来的状态信息(USB协议中,INPUT指的是设备对主机的方向,OUTPUT指的是主机对设备)。我们的修改是加入“我还能接收”的能力。如果不声明这样的能力,系统不会将数据发送过来,具体实验中,一些USB接口的小键盘上面的LED不会跟随主机状态变化。
下面需要做的就是实际处理数据了。1.6.X 系列的代码和之前的差别很大,对于键盘这部分是分开在 HID 和Keyboard 的类中。

Keyboard 只有输出的代码,通过调用 HID 类来进行发送的处理。因此,我们需要在HID 类中开一个接口。
重新定义如下:

处理部分, 在\arduino\hardware\arduino\avr\libraries\HID\src\HID.cpp 中的 boolHID_::setup(USBSetup& setup)函数中,当我们收到 Set_Report 就是系统发过来的关于LED的设置,我们取下来即可。

对于 HID 开一个接口,直接返回即可

uint8_t HID_::getLedStatus(void)
{
returnled;
}

我们给用户的接口是 Keyboard 类,我们要在上面田间一个读取的接口,

在这只文件中声明 \arduino\libraries\Keyboard\src\Keyboard.h

在s\arduino\libraries\Keyboard\src\Keyboard.cpp 文件中实现

编写一个例子,使用 Arduino 同步显示当前键盘的LED状态:

D5 最左侧灯,D6中间灯,D7 最右侧灯。

#include <Keyboard.h>

uint8_t old=0xFF,n=0xFF;
     
void setup() {
  pinMode(A0, INPUT_PULLUP);
  pinMode(5,OUTPUT);
  pinMode(6,OUTPUT);
  pinMode(7,OUTPUT);

  Keyboard.begin();

  Serial.begin(9600);
  while (digitalRead(A0) == HIGH) {
    // do nothing until pin 2 goes low
    delay(500);
  }
}
  
void loop() {

  n=Keyboard.getLedStatus();
  if (n!=old) {
     Serial.println(n);
     old=n;
     if (0!=(n&1) +) {
          digitalWrite(5,HIGH);
      }
     else {
          digitalWrite(5,LOW);
      }
     if (0!=(n&2)) {
          digitalWrite(6,HIGH);
      }
     else {
          digitalWrite(6,LOW);
      }
     if (0!=(n&4)) {
          digitalWrite(7,HIGH);
      }
     else {
          digitalWrite(7,LOW);
      }
  }
  delay(100);
}

 

kb4
工作的视频

Lenorade的按键值

对于 Leonardo 来说,最大的特点就是可以直接模拟USB键盘鼠标。

一般来说,当我们需要发送某一个按键的时候可以直接使用Keyboard.print(‘a’)这样的方式,或者在Keyboard.h 中找到定义好的键值,比如:#define KEY_F1 0xC2。

我最近遇到一个问题:这个文件中定义的键值我无法在其他资料上找到,让人非常恼火,例如:没有一份资料上说 CAPS LOCK值为0xC1,但是使用Keyboard.h中定义的 #define KEY_CAPS_LOCK 0xC1 确实是好用的。最后才想起来应该去看一下对应的代码(开源的优点)。最终,找到的代码如下:

size_t Keyboard_::press(uint8_t k)
{
       uint8_t i;
       if (k >= 136) {                     // it's a non-printing key (not a modifier)
              k = k - 136;
       } else if (k >= 128) {    // it's a modifier key
              _keyReport.modifiers |= (1<<(k-128));
              k = 0;
       } else {                        // it's a printing key
              k = pgm_read_byte(_asciimap + k);
              if (!k) {
                     setWriteError();
                     return 0;
              }
              if (k & 0x80) {                                          // it's a capital letter or other character reached with shift
                     _keyReport.modifiers |= 0x02;  // the left shift modifier
                     k &= 0x7F;
              }
       }
      
       // Add k to the key report only if it's not already present
       // and if there is an empty slot.
       if (_keyReport.keys[0] != k && _keyReport.keys[1] != k &&
              _keyReport.keys[2] != k && _keyReport.keys[3] != k &&
              _keyReport.keys[4] != k && _keyReport.keys[5] != k) {
             
              for (i=0; i<6; i++) {
                     if (_keyReport.keys[i] == 0x00) {
                            _keyReport.keys[i] = k;
                            break;
                     }
              }
              if (i == 6) {
                     setWriteError();
                     return 0;
              }     
       }
       sendReport(&_keyReport);
       return 1;
}

 

就是说,当我们使用KEY_CAPS_LOCK ==0xC1,会先有一个 193-136=57的动作,最后发送出去的实际上是 57(0x39),在USB HID Usage Tables 有下面的定义:

同样的文档中我们可以查到 Scroll Lock == 71(0x47) Num Lock == 83 (0x53)。于是,定义如下:

#define KEY_SCROLL_LOCK 0xCF
#define KEY_NUM_LOCK 0xDB

最终,我们编写一个代码,能让键盘上的三个LED逐次亮灭

#include "Keyboard.h"
 
#define KEY_SCROLL_LOCK   0xCF
#define KEY_NUM_LOCK   0xDB
 
void setup() {
  pinMode(A0, INPUT_PULLUP);
  Keyboard.begin();
  while (digitalRead(A0) == HIGH) {
    // do nothing until pin 2 goes low
    delay(500);
  }
 
}
 
void loop() {
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
  Keyboard.write(KEY_NUM_LOCK);
  Keyboard.releaseAll();
  delay(1000);
  
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000); 
  Keyboard.write(KEY_CAPS_LOCK);
  Keyboard.releaseAll();
  delay(1000);
 
  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000); 
  Keyboard.write(KEY_SCROLL_LOCK);
  Keyboard.releaseAll();
  delay(1000);
 
}

 

WHQL USB Exposed Port System Test

在 Windows 10 的 WHQL 测试中【参考1】,有一个称为“USB Exposed Port System Test”的项目。这个项目的测试目的是测试当前系统中对外的 USB 3.0接口和2.0接口的配对情况。比如,一个USB3.0 的端口构成如下,实际上可以看作是 2.0的信号 加上 3.0的信号组成的。而在实际设计上, USB2.0 和 3.0 的线并不是配对的,比如 USB 2.0 的 Port 1 可以和 USB 3.0 的Port 2共同组成这个端口。这对于 Chipset 来说是知道的,但是 Windows 并不知道这样的配对方式。

image001

image002

因此,通知 Windows 组合方式的任务就落在了 BIOS身上,更准确的说是通过 ASL 来完成这一工作的。具体来说是通过_UPC和_PLD这两个 Method 完成的【参考2】。具体设定完成之后,可以直接在被测机器上用 USBView进行检查(注意要用支持 USB 3.0的版本)。这个测试要求将全部“暴露”给客户的USB端口上插满USB Hub。 当USB Hub 插入后,会同时在 USB 2.0和3.0 的 Root Port 下出现设备。这样系统就可以得知 ACPI 的Report和真实的硬件能否对应上。
当然,上面并不是本文的重点,对于BIOS工程师来说最困难的bug并不是BIOS本身,而是并非BIOS却要求BIOS工程师来解的问题。
对于这个测试来说,真正的难点在于:需要使用USB IF 认证过的USB Hub来进行测试。起初我们使用了普通的USB Hub,结果始终无法通过测试,最后多方打听,终于找到了支持这个测试的Hub。
京东有售 http://item.jd.com/983704.html#product-detail ,显然价格比普通的USB 3.0 Hub要高出一截(当然,如果你再看一下测试 WHQL使用的 WindowsToGo 的U盘,还会觉得这个是良心价格)。

image003

截至目前(2017/04/22),我还不清楚这种认证过的Hub和普通的Hub有什么差别(错误提示和 container ID 有关系,我查看了资料也没有弄清楚是什么问题)。

亲测有效,希望对进行 WHQL 测试的朋友有所帮助。

参考:
1. https://msdn.microsoft.com/en-us/library/windows/hardware/jj123655(v=vs.85).aspx
2. https://msdn.microsoft.com/windows/hardware/drivers/install/using-acpi-to-configure-usb-ports-on-a-computer#

访问 PCIE 空间的两种方法

本文根据 Intel 文档【参考1】写成。
访问 PCIE 设备配置空间有2种方法: IO Port(0xCF8/0xCFC) 和 MMIO。前者是最传统的方式,能够访问 256Bytes的,很多扩展出来的超过256Bytes的就无能为力了;后者是比较新的方法。
先说一下第一种方法:
image002

比如,我们要访问南桥,Bus:0,Dev:1F,Func:0. 手工计算如下:

Capture

访问时,写为 dport(0xcf8,0x8000F800),写入之后再读取一次dport(0xcfc) 就是结果本例中是 Bus0/Dev 1F/Func 0/ Reg 0 ,PCI设备的 DID、VID
用代码表示就是:
address = BIT31 | ((Bus & 0xFF) << 16) | ((Dev & 0x1F) << 11) | ((Fun & 0x7) << 8) | (Reg & 0xfffffffc); 第二种方法: PCI 的寄存器会映射到内存中,系统中最多有256个Bus, 每个Bus最多有32个Device, 每个Device最多可以有4096个bytes的寄存器. 因此,整个映射最多占用256MB。下面这个图解释这个事情: image003

这么大的位置,是放置在4G以下的位置。一般会以 0xE000 0000为基地址。保险起见,最好查一下你代码中是否有重新定义。或者直接去看 0xE000 0000是否为一个PCI Header。

image004

还是以访问南桥为例子,手工计算如下:
Memory Address = PCI Express* Configuration Space Base Address + (Bus Number x 100000h) + (Device Number x 8000h) + (Function Number x 1000h) +
(Register Offset)

Bus:0,Dev:1F,Func:0 的地址在: 0xE000 0000 + 1Fh x 8000h = 0xE00F8000

参考:
1. http://www.docin.com/p-525417787.html 321090 Accessing PCI Express* Configuration Registers Using Intel® Chipsets

Sideband 的测试工具(Shell版,无源码)

有时候,我们需要在Shell 下访问 sideband总线上的一些寄存器,这个工具就是为了这个目标编写的。
使用方法:
Sbitestx64 –r 或者 –w 表示 读写,然后后面是设备 PID,接下来是要访问的寄存器,最后如果是要写入再跟着要写入的数值即可,下面是一个读取的例子:

SBI

需要特别注意的两点
1. 所有的参数都是十六进制
2. 如果遇到无法访问的情况很大可能是因为你的 Sideband总线已经锁死了。这是chipset的一项安全机制。最简单的打开办法是在 Setup 中找到 sideband unlock 的选项。或者直接让BIOS工程师帮忙build一个去掉加锁动作的BIOS。
sbi2

我已经使用一段时间了,挺好用的。

下载: sbitestX64

DFRobot SIM808 Shield 的 GPS 功能

人类对于世界的了解的越多,能够掌握的工具和方法就越多。
曾经读过方舟子写的《相对论有没有用?》【参考1】,让我大吃一惊的是日常用到的GPS就是相对论使用的典型:
“GPS是靠美国空军发射的24颗GPS卫星来定位的(此外还有几颗备用卫星),每颗卫星上都携带着原子钟,它们计时极为准确,误差不超过十万亿分之一,即每天的误差不超过10纳秒(1纳秒等于10亿分之一秒),并不停地发射无线电信号报告时间和轨道位置。这些GPS卫星在空中的位置是精心安排好的,任何时候在地球上的任何地点至少都能见到其中的4颗。GPS导航仪通过比较从4颗GPS卫星发射来的时间信号的差异,计算出所在的位置。
GPS卫星以每小时14000千米的速度绕地球飞行。根据狭义相对论,当物体运动时,时间会变慢,运动速度越快,时间就越慢。因此在地球上看GPS卫星,它们携带的时钟要走得比较慢,用狭义相对论的公式可以计算出,每天慢大约7微秒。
GPS卫星位于距离地面大约2万千米的太空中。根据广义相对论,物质质量的存在会造成时空的弯曲,质量越大,距离越近,就弯曲得越厉害,时间则会越慢。受地球质量的影响,在地球表面的时空要比GPS卫星所在的时空更加弯曲,这样,从地球上看,GPS卫星上的时钟就要走得比较快,用广义相对论的公式可以计算出,每天快大约45微秒。
在同时考虑了狭义相对论和广义相对论后,GPS卫星时钟每天还要快上大约38微秒,这似乎微不足道,但是如果我们考虑到GPS系统必须达到的时间精度是纳秒级的,这个误差就非常可观了(38微秒等于38000纳秒)。如果不校正的话,GPS系统每天将会累积大约10千米的定位误差,是没有用的。为此,在GPS卫星发射前,要先把其时钟的走动频率调慢100亿分之4.465,把10.23兆赫调为10.22999999543兆赫。此外,GPS卫星的运行轨道并非完美的圆形,与地面的距离和运行速度会有所变化,如果轨道偏心率为0.02,时间就会有46纳秒的误差。由于地球的自转,GPS导航仪在地球表面上的位移也会产生误差,例如当GPS导航仪在赤道上,而GPS卫星在地平线上时,由于位移产生的误差将会达到133纳秒。GPS导航仪在定位时还必须根据相对论进行计算纠正这些误差。

我手中的 DFRobot的SIM808 Shield支持GPS功能,这次我们就实验一下这个功能,GPS取得当前的时间和经纬度之后,结果显示在1602液晶上。使用到的硬件包括:
1. Arduino Uno 1块
2. DFRobot 的SIM808 Shield 1块
3. 18650 电池组 2块
4. 1602 LCD 一块
5. 亚克力切割的外壳 一对

特别注意的是,必须使用外接电源,USB端口供电不足以驱动模块,特别注意:一定要接上 GPS 天线,否则无法收到信号。

硬件准备妥当之后,先实验直接发送 AT 命令,这个实验不需要SIM卡:
1. 板上开关放置于3号位置
2. 下载 blink 程序(需要不占用 的程序)
3. 外接电源(我用的是2节18650,目前有7.5v左右)
4. 板上开关放置于2号位置
5. 用IDE自带的串口工具,输入 AT 可以看到自动回复 OK,这说明模块本身是正常的
6. 输入AT+CGNSPWR=1命令(打开GPS电源)AT+CGNSTST=1命令(开始从串口接收GPS数据)
7. 下面就可以看到获得的GPS数据了
GPS1
8. 结束的时候,使用AT+CGNSPWR=0命令关断GPS电源。
GPS2
我们在地图上看一下这个位置,很准

GPS3

软件方面需要下载DFRobot_SIM808的库,在https://github.com/DFRobot/DFRobot_SIM808。本文末尾提供了打包好的库文件。可以使用 Sketch->Include library->Add .zip library直接添加。
代码如下:

#include <LiquidCrystal_I2C.h>
#include <DFRobot_sim808.h>
  
DFRobot_SIM808 sim808(&Serial);
  
// Set the LCD address to 0x3F for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x3f, 16, 2);
  
void setup() {
    Serial.begin(9600);
  
    // initialize the LCD
    lcd.begin();
  
    // Turn on the blacklight and print a message.
    lcd.backlight();
   
    //************* Turn on the GPS power************
    if( sim808.attachGPS())
      {
        Serial.println("Open the GPS power success");
        lcd.print("power success");
      }
    else
      {    
        Serial.println("Open the GPS power failure");
        lcd.print("power failure");        
      }
}
  
void loop() {
     char s[20];    
     //************** Get GPS data *******************
     if (sim808.getGPS()) {
      Serial.print(sim808.GPSdata.hour);
      Serial.print(":");
      Serial.print(sim808.GPSdata.minute);
      Serial.print(":");
      Serial.println(sim808.GPSdata.second);
      Serial.print("latitude :");
      Serial.println(sim808.GPSdata.lat);
      Serial.print("longitude :");
      Serial.println(sim808.GPSdata.lon);
  
      lcd.clear(); 
      lcd.print("GPS     bbdebbbbbbbhhh             b        "); 
      lcd.print(sim808.GPSdata.hour);  
      lcd.print(":"); 
      lcd.print(sim808.GPSdata.minute); 
      lcd.print(":"); 
      lcd.print(sim808.GPSdata.second);
       
  
      lcd.setCursor(0,1);
      lcd.print("(");
      dtostrf(sim808.GPSdata.lat,3,2,s);
      lcd.print(s);
      lcd.print(",");
      dtostrf(sim808.GPSdata.lon,3,2,s);
      lcd.print(s);
      lcd.print(")");
  
      //************* Turn off the GPS power ************
      sim808.detachGPS();
    }
  
  }

 

最终运行结果:
GPS4
GPS5
最后说点关于GPS 好玩的事情:
第一件事情:我在昆山打工的时候,有一天瞎溜达,走到一座大桥下,看到上面铭牌上刻着经纬度。那是很久以前的事情,久远到google还没有被封,手机还没有GPS 功能。我很好奇的记下了经纬度。回去宿舍在googlemap上查找这个经纬度,非常疑惑的看着坐标飞到了太平洋中。朱多年来心中疑惑一直没有解开:这样的标注是为了“乱了敌人锻炼了群众”吗?
第二件事情: 1983年9月1日清晨(UTC时间为8月31日傍晚),大韩航空007号班机进入苏联领空,遭苏联空军Su-15拦截机击落于库页岛西南方的公海。误入的原因是机长操作失误,没有切换到正确的导航模式【参考2】。这件事情之后美国宣布开放部份的GPS功能给民间使用。
参考:
1. http://view.news.qq.com/a/20090422/000029.htm 方舟子:相对论有没有用?
借用臧克家的一句话来描述方舟子“有的人死了,他还活着;有的人或者,他已经敏感字了”。
2. http://baike.baidu.com/item/%E5%A4%A7%E9%9F%A9%E8%88%AA%E7%A9%BA007%E5%8F%B7%E7%8F%AD%E6%9C%BA%E7%A9%BA%E9%9A%BE大韩航空007号班机空难
如果非说苏联伟大的话,那么伟大的原因一定是它一次次将人类从自己的魔爪下解救出来。
DFRobot_SIM808-master

DFRobot Leonardo & Xbee R3 做个锁屏装置

上次活动拿到了DFRobot Leonardo & Xbee R3 控制器,一如既往的用料,还有标志性的颜色:

LOCK1
想起来还有上次赠送的徽章,想了下,做个锁屏的装置吧。使用徽章作为锁屏的触发。比如,起身离开的时候,轻触徽章即可锁住屏幕。很早之前我用模拟USB的方法做过一次【参考1】,有这个想法的缘由很简单:
上大学的时候,当团支书,负责同学交入Party申请书之类的事情(很值得骄傲的是作为熟读历史经常思考的年轻人,我从来没有写过申请书)。
LOCK2
想入Party有一关是“群众评议”,就是看看是否有反对的意见。当时我和班级上的同学说“一个好人,进入Party是追求进步,是好事,有机会让他改造好;一个坏人,进入Party,也是好事情,能让群众队伍更纯洁。大家都不要反对哈”。我们班级在这一关从来没有过什么问题。后来有一次,我去帮同学交入Party申请书,顺便讨价还价一下(我记得有规定是交了之后必须间隔一段才能进入正式的程序,为此我会和辅导员讨价还价一番,让申请书落款的时间提前几个月)。辅导员不在,我就稍等了一下。期间无聊,顺便翻翻其他班级的评议,结果让我大吃一惊,真有班级同学特别认真的反对某个人的,罗列了很多条意见。正在我看在兴头上,辅导员看到急忙过来将东西收了起来,半开玩笑的批评另外帮忙的女同学,说这样的东西怎么能让这种人看到………所以信息安全非常重要。时至今日,写在纸上的东西越来越少,电脑上的内容越来越多。当你起身去上厕所或者喝水的时候,是否可曾担心屏幕上的信息被人有意或者无意的看到?或者你外出办事,是否担心有人悄悄操作你的电脑?解决这个问题最简单的办法就是给Windows设置一个密码,然后在离开的时候按下 WinKey+L 。
因此,我们可以使用Arduino 模拟这个过程,按下锁屏键。
硬件上除了 Leonardo板子,还要用到徽章,还有一个导线,导线一端插入 A0口,一段接在徽章后面。徽章正面是绝缘的,需要处理一下,我用锡箔包裹了一下,导电部分留在边缘。

LOCK3
代码是根据【参考2】【参考3】改编的。

//Leonardo to MakeyMake
//http://www.alsrobot.cn/article-91.html
  
#include <Keyboard.h>
  
#define MOD_GUI_LEFT       0x83              // Win 按键
  
int InPut0 = 0;                               //触控输入值初始化
int TouchedValue = 200; //临界比较值,需要根据你的材质摆放进行测试
  
void setup()
{
  Serial.begin(9600);
  pinMode(A0, INPUT);   //使用A0接受输入
  
  pinMode(9, INPUT_PULLUP);    //为了防止程序错误干扰下次烧写,这里设计了一个开关
  while (digitalRead(9)==HIGH) {}; //只有当对应的 D9 接地才会继续
  
  Keyboard.begin();
}
void loop()
{
 InPut0 = analogRead(A0); //检测A0的输入              
 Serial.println(InPut0);  //Debug用
  if(InPut0 <= TouchedValue ) //检测按键并去抖动
  {
    delay(20);
    
    InPut0 = analogRead(A0); //检测A0的输入  
    if(InPut0 <=TouchedValue)
    {
      Serial.println("Get pressed"); //有按键
                      
      Keyboard.press(MOD_GUI_LEFT); //按下 Win Key
      Keyboard.press('l');          //按下 L
      //delay(200);
      Keyboard.releaseAll();        //抬起全部按键
  
    }
  }
    
  delay(1000);   //1秒检查一次是否有按键
}

 

最终的样子,经过测试工作正常:

LOCK4

有几个需要注意的地方:
第一, 不要把徽章放在导电的物体上,这样会造成很大的干扰;
第二, 细心的朋友可能注意到,设计上我还在D9短接到地,这样的设计是为了防止编写键盘鼠标程序时,影响上位机的代码,比如,程序除了错误,会一直向电脑上输入字符。加上这个设计,相当于加入了一个硬件开关;
第三, 代码中用来判断当前是否有触摸的那个数值是实际测量得出的,需要根据具体实现来选定。
参考:

1. http://www.lab-z.com/20140101/
1. http://www.geek-workshop.com/thread-1192-1-1.html Arduino学习笔记A12 – 自制Makey模拟触摸键盘
2. http://www.alsrobot.cn/article-91.html 【创客学堂】Arduino改做MakeyMakey玩