这个模块有供电的问题,用 USB 供电容易出现无法使用的情况。
前几天入手一个 CC3000 WiFi Shield 发现无法使用,经过查找和卖家沟通发现这个是通病,
无论是国外出售的还是国内的都有很大概率出现 USB 供电时无法使用的情况,
因此,有 WIFI 通讯需要的朋友请购买其他型号的。
这个模块有供电的问题,用 USB 供电容易出现无法使用的情况。
前几天入手一个 CC3000 WiFi Shield 发现无法使用,经过查找和卖家沟通发现这个是通病,
无论是国外出售的还是国内的都有很大概率出现 USB 供电时无法使用的情况,
因此,有 WIFI 通讯需要的朋友请购买其他型号的。
之前入手过一个9.9元的小水泵【参考1】,正好这次做实验用上了。他的工作电压最高是12V,我用MOS管 + Arduino的PWM来实现电压的控制。电路图和之前一个控制小灯泡的非常类似【参考2】。
#define PWMPin 10
int n=255;
void setup()
{
Serial.begin(9600);
//该端口需要选择有#号标识的数字口
pinMode(PWMPin,OUTPUT);
}
void loop()
{
char c;
while (Serial.available() > 0)
{
c=Serial.read();
if (‘]’==c)
{
n=n+5;
}
if (‘[‘==c)
{
n=n-5;
}
//这里为了方便,加入一个可以直接开到最大值的字符
if (‘/’==c)
{
n=255;
}
//这里为了方便,加入一个可以直接关闭的字符
if (‘.’==c)
{
n=0;
}
if (n>255) {n=0;}
if (n<0) {n=255;}
analogWrite(PWMPin,n);
Serial.println(n);
}
}
具体的实验内容是:使用一个底部有孔的小盆,证明放水时产生漩涡的方向和地球自转偏向力无关,而和放水时水流方向有关。
参考:
1. http://www.lab-z.com/waterpump/?nsukey=ZI7FePgtjom%2F3mfgzX9EtL5onGMfsUajYidhT5jUCO2t0hFAbtL%2B1LlY6YXFtxT785b4i%2Fe%2BSSjzo0wnFsIu1w%3D%3D 小水泵套件
2. http://www.lab-z.com/mos%E6%8E%A7%E5%88%B6%E5%B0%8F%E7%81%AF%E6%B3%A1%E7%9A%84%E5%AE%9E%E9%AA%8C/ MOS控制小灯泡的实验
一个问题:如何获得 Shell 下面所有 USB 设备的 PID 和 VID ?
首先在网上搜索一下,找到【参考1】,上面使用了 USBIO Protocol。
typedef struct _EFI_USB_IO_PROTOCOL {
EFI_USB_IO_CONTROL_TRANSFER UsbControlTransfer;
EFI_USB_IO_BULK_TRANSFER UsbBulkTransfer;
EFI_USB_IO_ASYNC_INTERRUPT_TRANSFER UsbAsyncInterruptTransfer;
EFI_USB_IO_SYNC_INTERRPUT_TRANSFER UsbSyncInterruptTransfer;
EFI_USB_IO_ISOCHRONOUS_TRANSFER UsbIsochronousTransfer;
EFI_USB_IO_ASYNC_ISOCHRONOUS_TRANSFER UsbAsyncIsochronousTransfer;
EFI_USB_IO_GET_DEVICE_DESCRIPTOR UsbGetDeviceDescriptor;
EFI_USB_IO_GET_CONFIG_DESCRIPTOR UsbGetConfigDescriptor;
EFI_USB_IO_GET_INTERFACE_DESCRIPTOR UsbGetInterfaceDescriptor;
EFI_USB_IO_GET_ENDPOINT_DESCRIPTOR UsbGetEndpointDescriptor;
EFI_USB_IO_GET_STRING_DESCRIPTOR UsbGetStringDescriptor;
EFI_USB_IO_GET_SUPPORTED_LANGUAGES UsbGetSupportedLanguages;
EFI_USB_IO_PORT_RESET UsbPortReset;
} EFI_USB_IO_PROTOCOL;
来自【参考2】。
我们在实体机上用 dh –p USBIO 命令看一下(注意:EDK自带的模拟环境没有设备挂USBIO 这个 Protocol,所以只能用实体机查看)。可以看到 USB设备上都有这个 Protocol,于是,一切都简单了。
我们再把实体机启动到 Windows中,也是同样的顺序,分别是 USB Hub (这是我外接的一个 USB HUB),USB鼠标,USB键盘(这个键盘是一个复合设备,所以会显示为2个),最后是我的 U盘。多说两句对照上面的截图,我们可以看到USB鼠标上附加了一个 SimplePointer Protocol,键盘上有 TxtinEx/Txtin/ConIn Protocol ,U盘上附加了 DiskIo/BlkIo Protocol,后面我们会分别研究一下这些 Protocol 的使用。
每一个USB设备上都有这个 Protocol 所以我们要用 LocateHandleBuffer 和HandleProtocol 这样的组合把所有有这个 Protocol 的设备都取下来,然后调用 UsbIO 中的UsbGetDeviceDescriptor 即可。
弄清楚原理整个程序还是比较简单的,如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/UsbIo.h>
extern EFI_BOOT_SERVICES *gBS;
EFI_GUID gEfiUsbIoProtocolGuid =
{ 0x2B2F68D6, 0x0CD2, 0x44CF,
{ 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};
UINTN GetUSB( )
{
EFI_STATUS Status;
UINTN HandleIndex, HandleCount;
EFI_HANDLE *DevicePathHandleBuffer = NULL;
EFI_USB_IO_PROTOCOL *USBIO;
EFI_USB_DEVICE_DESCRIPTOR DeviceDescriptor;
Status = gBS->LocateHandleBuffer(
ByProtocol,
&gEfiUsbIoProtocolGuid,
NULL,
&HandleCount,
&DevicePathHandleBuffer);
if (EFI_ERROR(Status))
{
Print(L"ERROR : Get USBIO count fail.\n");
return 0;
}
for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++)
{
Status = gBS->HandleProtocol(
DevicePathHandleBuffer[HandleIndex],
&gEfiUsbIoProtocolGuid,
(VOID**)&USBIO);
if (EFI_ERROR(Status))
{
Print(L"ERROR : Open USBIO fail.\n");
gBS->FreePool(DevicePathHandleBuffer);
return 0;
}
Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);
if (EFI_ERROR(Status))
{
Print(L"ERROR : Usb Get Device Descriptor fail.\n");
gBS->FreePool(DevicePathHandleBuffer);
return 0;
}
Print(L"VendorID = %04X, ProductID = %04X\n",
DeviceDescriptor.IdVendor,
DeviceDescriptor.IdProduct);
}
gBS->FreePool(DevicePathHandleBuffer);
return HandleCount;
}
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
GetUSB( );
return EFI_SUCCESS;
}
最终运行结果如下:
完整的代码和程序下载:
GetPIDVID
参考:
1. http://biosren.com/viewthread.php?tid=5925&highlight=usbio 請問如何透過DevicePath獲取對應的device全名?
2. http://wiki.phoenix.com/wiki/index.php/EFI_USB_IO_PROTOCOL EFI USB IO PROTOCOL 同样的,在 UEFI spec中也可以找到上面的定义
前面介绍了USB 鼠标数据的读取,个人感觉最困难的部分就是解析HID Descriptor,不同的鼠标的Descriptor不同,发出来的格式也不同。相比之下,键盘切换到 Boot Protocol 之后问题就简单多了。本文介绍如何将鼠标切换到这个模式下。
我们在之前中断方式获得鼠标改变代码的基础上,这样会使得整体效率比较高。
当 Mouse 工作在 Boot Protocol下面的时候,默认使用下面的 HID Protocol【参考1】

根据资料,编写程序如下:
/* Mouse communication via interrupt endpoint */
/* Assumes EP1 as interrupt IN ep */
#include "max3421e.h"
#include "usb.h"
#define DEVADDR 1
#define CONFVALUE 1
#define EP_MAXPKTSIZE 5
EP_RECORD ep_record[ 2 ]; //endpoint record structure for the mouse
void setup();
void loop();
MAX3421E Max;
USB Usb;
void setup()
{
Serial.begin( 115200 );
Serial.println("Start");
Max.powerOn();
delay( 200 );
}
void loop()
{
byte rcode;
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) {
mouse1_init();
}//if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING...
if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) {
rcode = mouse1_poll();
if( rcode ) {
Serial.print("Mouse Poll Error: ");
Serial.println( rcode, HEX );
}//if( rcode...
}//if( Usb.getUsbTaskState() == USB_STATE_RUNNING...
}
/* Initialize mouse */
void mouse1_init( void )
{
byte rcode = 0; //return code
byte tmpdata;
byte* byte_ptr = &tmpdata;
/**/
ep_record[ 0 ] = *( Usb.getDevTableEntry( 0,0 )); //copy endpoint 0 parameters
ep_record[ 1 ].MaxPktSize = EP_MAXPKTSIZE;
ep_record[ 1 ].sndToggle = bmSNDTOG0;
ep_record[ 1 ].rcvToggle = bmRCVTOG0;
Usb.setDevTableEntry( 1, ep_record );
/* Configure device */
rcode = Usb.setConf( DEVADDR, 0, CONFVALUE );
if( rcode ) {
Serial.print("Error configuring mouse. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}//if( rcode...
rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata );
if( rcode ) {
Serial.print("Set Idle error. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}
rcode = Usb.getIdle( DEVADDR, 0, 0, 0, (char *)byte_ptr );
if( rcode ) {
Serial.print("Get Idle error. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}
Serial.print("Idle Rate: ");
Serial.print(( tmpdata * 4 ), DEC ); //rate is returned in multiples of 4ms
Serial.println(" ms");
tmpdata = 0;
rcode = Usb.setIdle( DEVADDR, 0, 0, 0, tmpdata );
if( rcode ) {
Serial.print("Set Idle error. Return code : ");
Serial.println( rcode, HEX );
while(1); //stop
}
/* Set boot protocol */
rcode = Usb.setProto( DEVADDR, 0, 0, 0 );
if( rcode ) {
Serial.print("Error attempting to configure boot protocol. Return code :");
Serial.println( rcode, HEX );
while( 1 ); //stop
}
Usb.setUsbTaskState( USB_STATE_RUNNING );
return;
}
/* Poll mouse via interrupt endpoint and print result */
/* assumes EP1 as interrupt endpoint */
byte mouse1_poll( void )
{
byte rcode,i;
char buf[ 3 ] = { 0 }; //mouse report buffer
unsigned long int libuf[ sizeof(buf) ];
unsigned long int x;
unsigned long int y;
/* poll mouse */
rcode = Usb.inTransfer( DEVADDR, 1, sizeof(buf), buf, 1 ); //
if( rcode ) { //error
if( rcode == 0x04 ) { //NAK
rcode = 0;
}
return( rcode );
}
for (int i=0;i<sizeof(buf);i++) {
libuf[i]=(buf[i]) & 0xff;
Serial.print(libuf[i],HEX); Serial.print(" ");
}
Serial.println("");
return( rcode );
}
运行结果:
完整的代码下载
特别注意:经过我的实验,不是所有的鼠标都支持 Boot Protocol ,前面实验中用到的Dell 和HP鼠标都无法正常工作,可能是这两种鼠标不支持Boot Protocol 。换一个角度说,如果客户使用 USB 键盘,那么一定要有进入 BIOS Setup的需求,但是客户通常不会有在Setup中使用鼠标的需求,所以鼠标对于Boot Protocol的支持不好也在情理之中。
我发现好用的反倒是一个杂牌鼠标:
参考:
1. HID Firmware Specification 1.11 P71
之前介绍过使用 Arduino 用硬件的方法直接读取 EDID 【参考1】,这里介绍一下如何在 Shell下读取 EDID 信息。
我们使用 EFI_EDID_DISCOVERED_PROTOCOL 这个 Protocol 来获得信息。当然,类似的还可以使用 EFI_EDID_ACTIVE_PROTOCOL,都是没有问题的。
在要获取 EDID 信息之前,最好先使用 “DH -p EdidDiscovered” 命令来确定你当前的系统中存在 Edid 信息。例如:
实现这个功能的代码如下:
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/EdidDiscovered.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
extern EFI_HANDLE gImageHandle;
EFI_GUID gEfiEdidDiscoveredProtocolGuid =
{ 0x1C0C34F6, 0xD380, 0x41FA, { 0xA0, 0x49, 0x8A, 0xD0, 0x6C, 0x1A, 0x66, 0xAA }};
EFI_STATUS GetEDIDInfo()
{
EFI_STATUS Status;
EFI_HANDLE *EDIDHandleBuffer;
UINTN EDIDHandleCount, index, i;
EFI_EDID_DISCOVERED_PROTOCOL * EDID;
Status = gBS->LocateHandleBuffer (ByProtocol,
&gEfiEdidDiscoveredProtocolGuid,
NULL,
&EDIDHandleCount,
&EDIDHandleBuffer);
if (EFI_ERROR (Status))
{
Print(L"ERROR : Can't get EdidDiscoveredProtocol.\n");
return FALSE;
}
Print(L"EDID count = %d\n", EDIDHandleCount);
for(index = 0 ; index < EDIDHandleCount ; index++)
{
Status = gBS->OpenProtocol( EDIDHandleBuffer[index],
&gEfiEdidDiscoveredProtocolGuid,
(VOID**)&EDID,
gImageHandle,
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
if (EFI_ERROR (Status))
{
Print(L"ERROR : Open EDID Protocol failed.\n");
return FALSE;
}
Print(L"%d\n", EDID->SizeOfEdid);
for(i = 0 ; i < 128 ; i++)
{
Print(L"%02X ", EDID->Edid[i]);
if ((i+1) % 16 ==0) {
Print(L"\n"); }
else
if ((i+1) % 8 ==0) { Print(L"- ");}
}
Print(L"\n");
Status = gBS->CloseProtocol(EDIDHandleBuffer[index],
&gEfiEdidDiscoveredProtocolGuid, gImageHandle, NULL);
if (EFI_ERROR (Status))
{
Print(L"ERROR : Close EDID device handle failed.\n");
return FALSE;
}
}
return EFI_SUCCESS;
}
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
GetEDIDInfo();
return EFI_SUCCESS;
}
上述程序运行结果
完整代码下载
比对之前获得的结果(我用的是同一个显示器,可以在【参考1】中看到),可以发现结果是相同的。
如果想程序解析每个值的具体含义,还按照【参考3】给出的例子。
参考:
1.http://www.lab-z.com/arduinoedid/ Arduino 读取显示器 EDID
2.http://biosren.com/thread-5921-1-1.html 求救如何取得EDID?
3.http://blog.fpmurphy.com/2012/09/accessing-edid-information-from-uefi-shell.html Accessing EDID Information From UEFI Shell
之前介绍过如何使用外部加一些元件从而让你的 Uno编程一个USB设备【参考1】,美中不足的地方是:
1.模拟方式实现USB,占用非常多的资源,基本上无法做其他事情;
2.需要外部元件,做起来比较麻烦。
本文根据【参考1】提供的信息,介绍一下如何将Uno通过刷写Firmware的方法编程一个Mouse。
先说一下原理:在标准的Uno上有一个USB转串口的芯片,Atmega 16U2 。通过对其重写Firmware,能够实现其他 USB 功能。重写之后这个芯片对AtMega 328 主芯片的通讯仍然是串口,就是说我们主芯片可以像串口通讯一样,直接发送数据到这个芯片上,这个芯片负责将数据转化为 USB信号发给PC。
在整个开始之前,首先,你必须有一个烧写器。这里推荐UsbTinyIsp,我使用的是 OCROBOT 出品的【参考2】。必须有这个东西,否则你一定会出现搞坏了没办法救回来的情况。
然后就可以使用工具刷新 16U2 的 Firmware。 这里推荐 AvrDude 这个工具,比较好的地方在于,这个工具有自动识别芯片的功能,你可以用它检查接线之类是否正常。选中Arduino-mouse.hex,然后点击Write即可,下方会有信息提示(这种工具本质都是调用AVRDUDE的命令行参数,只是AVRDUDE命令行非常复杂也不直观)。完成之后会出现下面的提示:
avrdude.exe: verifying …
avrdude.exe: 3872 bytes of flash verified
avrdude.exe done. Thank you.
接下来,在Arduino的IDE中打开Mouse_usb_demo.pde。
你需要确定 :
1.Tools->Board->选择的是Arduino Uno
2.Tools->Programmer下面选择的是 UsbTinyISP。
正常编译这个程序,不要选择编译并且上传,单纯选择编译即可。编译后,用File->Upload using Programmer 即完成上传。
再把Uno插在PC上,你会发现设备管理器中多出一个鼠标
PID VID如下所示:
实现的功能是你的鼠标每隔一段会自己转一圈。
完整的代码下载
Arduino-mouse
从这篇介绍也可以看出来,初学者尽量买原版的 Arduino Uno ,不要购买什么极客板等等,虽然会便宜一些,但是扩展性可玩性上下降很多。
参考:
1. http://hunt.net.nz/users/darran/weblog/cca39/Arduino_UNO_Mouse_HID.html Arduino UNO Mouse HID
2.这个东西是必须的,推荐这款有数字签名的驱动,支持Win8 64位,这样你就不必每次跑去关闭数字签名了。购买地址:
https://item.taobao.com/item.htm?spm=a1z10.5-c.w4002-684314961.37.VJHdrg&id=19035872312 。
当然淘宝上还有更便宜,我之前买了一个便宜的,说是无需驱动,结果无法用,非常懊恼……
有一个网友提出一个问题“现在要进行一个0.4us的延时,发现不管怎么调都只能调到15us,根本达不到要求,我用的芯片是MEGA32U4-AU,外部晶振是16MHz,求见解!!!!!”
我试验了一下,最终的程序如下:
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
for (long zdelay=0;zdelay<9; zdelay++) {
__asm__("nop\n\t");
}
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
}
这个程序运行之后的波形如下
可以看到中间的延时差不多有4.1331us(我用游标对齐,右下角显示x=4.1331). 让然这个值中还包括了一个拉GPIO的指令周期,大约会有 62.1ns的影响。此外,如果要求特别精确,在使用时还要考虑周期性中断的影响。这里就不说了…….
下面我们继续实验,尝试找到循环次数和实际delay时间的关系(因为涉及到编译器优化, long int的计算和判断,直接尝试计算机器周期不可行)。
首先尝试zdelay<8,测量结果是3.7013us

根据上述值结合循环简单猜测一下,对于这个循环体,固定部分耗时0.2469us (比如给变量赋初始值),循环部分每次耗时0.4318us
就是 T= 0.2469 + n *0.4318
根据这个计算循环zdelay<5应该是 2.4059us测量结果是2.3722us
加入我们打算delay 100us 根据上述公式应该循环 231次
代码:
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
for (long zdelay=0;zdelay<231; zdelay++) {
__asm__("nop\n\t");
}
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
}
最后,“极客工坊”的 sanyouhi 朋友指出,精确延时可以用写好的库来直接完成【参考1】
#define F_CPU 16000000
#include <util/delay.h>
void setup()
{
DDRB = 0X20;
}
void loop()
{
PORTB = 0X20;
_delay_us(0.4);
PORTB = 0;
}
对应的头文件在 \arduino-1.6.3\hardware\tools\avr\avr\include\util\delay.h 有机会的时候再研究一下。
后记,比较有意思,如果我把代码写为下面的形式
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
PORTB = B100000; //digitalWrite(PinA,HIGH);
for (long zdelay=0;zdelay<17; zdelay++) {
}
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
}
中间delay 的周期和不写 for 循环是相同的,猜测原因是编译器的自动优化,当编译器发现空循环时会自动移除循环代码。
参考:
1.http://www.geek-workshop.com/thread-24982-1-1.html
实验设备是一块 Arduino Uno 16M (是那种没有品牌的兼容版),示波器是 Teledyne Lecory Wave Runner 606Zi 600Mhz 20GS/s。
第一个实验:只使用DigitalWrite 能制造出来的最大频率是多少?
首先试试最通用的 digitalWrite 的方法不断在高低之间反转
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
digitalWrite(PinA,HIGH);
digitalWrite(PinA,LOW);
digitalWrite(PinA,HIGH);
digitalWrite(PinA,LOW);
digitalWrite(PinA,HIGH);
digitalWrite(PinA,LOW);
delay(500);
}
使用示波器抓图如下(下面的所有介绍都是具体解说在上,抓取波形在下):
我们设置的Delay 是500ms, 然后示波器的水平方向每一格也是500ms,垂直方向是电压,当前选择每格2V,因此看起来差不多是5v左右,符合预期。
我选择了了Stop功能,放大波形进行查看。可以看到,最上面波形中黄色竖线实际上是一组波形,就是对应我们的拉高拉低。
示波器有测量功能,直接调用该功能进行测试:可以看到幅度是4.946V,示波器还标记出来具体的测试方法,这在测量一些不是那么“规整”的波形时非常有用。
再用示波器自动测量一下频率:是100kHz。
可以在菜单中选择测试的具体方法(比如,测试频率通用的方法是波形上升沿50%的位置)
再测量周期(其实给出来了频率,周期是可以直接换算出来的)
结论:如果我们用 DigitalWrite拼命上下拉,最高是可以输出100Khz频率的。
之后,我们再试试使用PortB赋值直接拉出来的频率是多少?关于 PortB 指令可以在【参考1】看到。
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
delay(500);
}
看起来下面波形感觉畸变比较严重(这里:解释一下,前面图看起来平滑的原因是采样时间导致的。比如,我的示波器单位时间可以采样500个点,如果我采样1s,放大之后,在1ms范围内只有5个点。如果我直接采样1ms,那么会有500个点来描绘波形,看起来自然“平滑”得多)
测试幅度,会达到5.248v
同样,使用自带功能测试频率:惊人的 7.99590Mhz
因为一个周期里面实际上是有两条指令的(拉上拉下),已经非常接近主芯片的16Mhz了。
根据上面的结果引申的问题:如何同时拉高两个Port?
首先我们尝试一下 DigitalWrite的方法
const int PinA = 13;
const int PinB = 8;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
pinMode(PinB, OUTPUT);
digitalWrite(PinB,LOW);
}
void loop() {
digitalWrite(PinA,HIGH);
digitalWrite(PinB,HIGH);
digitalWrite(PinA,LOW);
digitalWrite(PinB,LOW);
delay(500);
}
下图可以看到,注意我设置两个信号起始电压不同,为了观测方便,所以会一个上一个下

放大之后查看
为了方便观看,我设置他们电压起点相同,可以看出他们差不多有1个水平格子的差别(5us)。对于这个差异可以在【参考2】初步了解一下。
再尝试PortB 直接赋值的方法
const int PinA = 13;
const int PinB = 8;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
pinMode(PinB, OUTPUT);
digitalWrite(PinB,LOW);
}
void loop() {
PORTB = B100001; //digitalWrite(PinA,HIGH); digitalWrite(PinB,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW); digitalWrite(PinB,LOW);
delay(500);
}
再放大查看,纠缠在一起,,波形上的细微差别可能是外围电路导致的。
补记:为了比对,额外实验 DFrobot 的 RomeoBLEV1.0 的板子为了看得清楚,修改程序如下,去掉了 delay
const int PinA = 13;
void setup() {
pinMode(PinA, OUTPUT);
digitalWrite(PinA,LOW);
}
void loop() {
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
PORTB = B100000; //digitalWrite(PinA,HIGH);
PORTB = B000000; //digitalWrite(PinA,LOW);
}
先看大范围的,每组3次上升,每组之间的间隔是void loop() { } 中的代码导致的
振幅上和之前的板子差不多 5.13v左右,实际多测试几次也会出现 5.2v。看起来由品牌的板子和无品牌的板子在这方便没有差别。
参考:
1.关于Port x的说明https://www.arduino.cc/en/Reference/PortManipulation
PORTD maps to Arduino digital pins 0 to 7
DDRD – The Port D Data Direction Register – read/write
PORTD – The Port D Data Register – read/write
PIND – The Port D Input Pins Register – read only
PORTB maps to Arduino digital pins 8 to 13 The two high bits (6 & 7) map to the crystal pins and are not usable
DDRB – The Port B Data Direction Register – read/write
PORTB – The Port B Data Register – read/write
PINB – The Port B Input Pins Register – read only
PORTC maps to Arduino analog pins 0 to 5. Pins 6 & 7 are only accessible on the Arduino Mini
DDRC – The Port C Data Direction Register – read/write
PORTC – The Port C Data Register – read/write
PINC – The Port C Input Pins Register – read only
同样,这篇文章中提到了如何同时拉Pin 的方法
Sometimes you might need to set multiple output pins at exactly the same time. Calling digitalWrite(10,HIGH); followed by digitalWrite(11,HIGH); will cause pin 10 to go HIGH several microseconds before pin 11, which may confuse certain time-sensitive external digital circuits you have hooked up. Alternatively, you could set both pins high at exactly the same moment in time using PORTB |= B1100;
2. Arduino 代码机制 http://blog.csdn.net/pinbodexiaozhu/article/details/42641273
这里介绍一下解压缩 EFI_DECOMPRESS_PROTOCOL 的使用。
首先是 GetInfo 函数【参考1】。通过它我们能够获得压缩文件的一些基本信息,比如:解压后的大小,解压过程需要的临时内存空间的大小。
之后就是具体的解压函数 Decompress 【参考2】
根据上面的信息,编写一个简单的测试程序,首先将压缩格式的文件读取到内存中,再使用 GetInfo 取得必要的信息,最后,根据必须要的信息创建内存 Buffer ,使用 Decompress 解压即可。
代码如下
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Protocol/Decompress.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
int
EFIAPI
main (
IN int Argc,
IN CHAR16 **Argv
)
{
EFI_DECOMPRESS_PROTOCOL *Decompress;
VOID *ImageBuffer=NULL;
UINT32 ImageLength=0;
UINT32 DestinationSize;
UINT8 *Scratch;
UINT32 ScratchSize;
VOID *DecompressedImageBuffer=NULL;
EFI_STATUS Status;
EFI_FILE_HANDLE FileHandle;
EFI_FILE_INFO *FileInfo = NULL;
UINTN ReadSize;
EFI_HANDLE *HandleBuffer=NULL;
Status = gBS->LocateProtocol (&gEfiDecompressProtocolGuid,
NULL, (VOID**)&Decompress);
if (EFI_ERROR (Status)) {
Print(L"Can't find Decompress Protocol! \n");
} else {
//Open the file given by the parameter
Status = ShellOpenFileByName(
Argv[1],
(SHELL_FILE_HANDLE *)&FileHandle,
EFI_FILE_MODE_READ,
0);
if(Status != RETURN_SUCCESS) {
Print(L"OpenFile failed!\n");
return EFI_SUCCESS;
}//if(Status != RETURN_SUCCESS) {
//Get file size
FileInfo = ShellGetFileInfo((SHELL_FILE_HANDLE)FileHandle);
//Allocate a memory buffer
HandleBuffer = AllocateZeroPool((UINTN) FileInfo->FileSize);
if (HandleBuffer == NULL) {
return (SHELL_OUT_OF_RESOURCES); }
ReadSize=(UINTN) FileInfo-> FileSize;
//Load the whole file to the buffer
Status = ShellReadFile(FileHandle,&ReadSize,HandleBuffer);
//Close the source file
ShellCloseFile(&FileHandle);
Status = Decompress->GetInfo (
Decompress,
HandleBuffer,
ReadSize,
&DestinationSize,
&ScratchSize
);
if (!EFI_ERROR (Status)) {
Print(L"[GetInfo] Destination Size %d\n",DestinationSize);
Print(L"[GetInfo] Scratch Size %d\n",ScratchSize);
DecompressedImageBuffer = AllocateZeroPool (DestinationSize);
if (DecompressedImageBuffer != NULL) {
Scratch = AllocateZeroPool (ScratchSize);
if (Scratch != NULL) {
Status = Decompress->Decompress(
Decompress,
HandleBuffer, //Source
ReadSize, //Source Size
DecompressedImageBuffer,//Destination
DestinationSize, //DestinationSize
Scratch,
ScratchSize
);
if (!EFI_ERROR (Status)) {
ImageBuffer = DecompressedImageBuffer;
ImageLength = DestinationSize;
//Create a new file
Status = ShellOpenFileByName(L"decomp.bmp",
(SHELL_FILE_HANDLE *)&FileHandle,
EFI_FILE_MODE_READ |
EFI_FILE_MODE_WRITE|
EFI_FILE_MODE_CREATE,
0);
if(Status != RETURN_SUCCESS) {
Print(L"CreatFile failed [%r]!\n",Status);
return EFI_SUCCESS;
}
Status = ShellWriteFile(FileHandle,
&DestinationSize,
ImageBuffer
);
//Close the source file
ShellCloseFile(&FileHandle);
} //if (!EFI_ERROR (Status)) {
else
{
Print(L"Decompress error [%r] \n",Status);
}
FreePool (Scratch);
} // if (Scratch != NULL) {
} //if (ImageBuffer != NULL) {
} //if (!EFI_ERROR (Status)) {
else {
Print(L"Read compressed file error!\n",ScratchSize);
}
} //if (EFI_ERROR (Status)) {
return EFI_SUCCESS;
}
运行结果
完整程序下载
特别注意:
1.本文测试使用的压缩文件是 UEFI 下面生成的,具体命令是
eficompress testc.bmp compressed.z
我尝试使用 BaseTools 里面的压缩工具,生成的文件格式会出现不兼容,无法正常解压的情况。
2.根据我自己的理解 Scratch Buffer 是解压过程中解压算法使用的内存区域,不同压缩文件对于这片区域的大小要求不同。
参考:
1. UEFI Spec 2.4 P883
2. UEFI Spec 2.4 P885
3. ShellPkg 中的 EfiDecompress 程序是非常好的参考例子。
本文介绍如何使用 Arduino 打造一个设备,能够将你的USB键盘转化为蓝牙键盘。
键盘可以算作PC上最古老的设备了,他的出现使得人类可以用非常简单的方法与电脑进行交互。同样的,由于各种历史原因,键盘也是PC上最复杂,兼容性问题最多的设备之一(类似的还有硬盘,不过从IDE到SATA的进化过程中,标准明确,兼容性问题少多了)。
网上流传着一篇DIY USB键盘转换为无线的文章,非常不幸的是,那篇文章是错误的,很明显的错误是作者认为键盘是单向传输,而实际上传输是双向的。比如,USB每次通讯都需要HOST和SLAVE的参与,即便是PS2键盘的通讯也同样如此。此外,大小写键之类切换是主机端进行控制的。
硬件部分Arduino UNO , USB Host Shield 和 HID 蓝牙芯片。强调一下这里使用的是 HID 蓝牙芯片,并非普通的蓝牙串口透传芯片【特别注意是“蓝牙键盘芯片”也不是“蓝牙条码模块”】。关于这个模块可以参考我在【参考1】中的实验。
硬件连接很简单,USB HOST Shield插在 Arduino上,然后VCC/GND/TX/RX将Arduino 和 HID蓝牙模块连接在一起。

原理:首先,为了通用性和编程简单,我们用USB HOST发送命令把键盘切换到 Boot Protocol 模式下。这样即使不同的键盘,每次发出来的数据也都是统一的格式。然后,我们直接读取缓冲数据就可以解析出按键信息了。最后,将取下来的按键信息(Scan Code)按照HID蓝牙模块的格式要求通过串口送到模块上,主机端就收到了。
上述连接就可以正常工作了,但是为了美观和提高可靠性,我找到之前买的一个面包板Shield。

插好之后就是这样
具体代码:
/* MAX3421E USB Host controller LCD/keyboard demonstration */
//#include <Spi.h>
#include "Max3421e.h"
#include "Usb.h"
/* keyboard data taken from configuration descriptor */
#define KBD_ADDR 1
#define KBD_EP 1
#define KBD_IF 0
#define EP_MAXPKTSIZE 8
#define EP_POLL 0x0a
/**/
//******************************************************************************
// macros to identify special charaters(other than Digits and Alphabets)
//******************************************************************************
#define BANG (0x1E)
#define AT (0x1F)
#define POUND (0x20)
#define DOLLAR (0x21)
#define PERCENT (0x22)
#define CAP (0x23)
#define AND (0x24)
#define STAR (0x25)
#define OPENBKT (0x26)
#define CLOSEBKT (0x27)
#define RETURN (0x28)
#define ESCAPE (0x29)
#define BACKSPACE (0x2A)
#define TAB (0x2B)
#define SPACE (0x2C)
#define HYPHEN (0x2D)
#define EQUAL (0x2E)
#define SQBKTOPEN (0x2F)
#define SQBKTCLOSE (0x30)
#define BACKSLASH (0x31)
#define SEMICOLON (0x33)
#define INVCOMMA (0x34)
#define TILDE (0x35)
#define COMMA (0x36)
#define PERIOD (0x37)
#define FRONTSLASH (0x38)
#define DELETE (0x4c)
/**/
/* Modifier masks. One for both modifiers */
#define SHIFT 0x22
#define CTRL 0x11
#define ALT 0x44
#define GUI 0x88
/**/
/* "Sticky keys */
#define CAPSLOCK (0x39)
#define NUMLOCK (0x53)
#define SCROLLLOCK (0x47)
/* Sticky keys output report bitmasks */
#define bmNUMLOCK 0x01
#define bmCAPSLOCK 0x02
#define bmSCROLLLOCK 0x04
/**/
EP_RECORD ep_record[ 2 ]; //endpoint record structure for the keyboard
char buf[ 8 ] = { 0 }; //keyboard buffer
char old_buf[ 8 ] = { 0 }; //last poll
/* Sticky key state */
bool numLock = false;
bool capsLock = false;
bool scrollLock = false;
bool line = false;
void setup();
void loop();
MAX3421E Max;
USB Usb;
void setup() {
Serial.begin( 9600 );
Serial.println("Start");
Max.powerOn();
delay( 200 );
}
void loop() {
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() == USB_STATE_CONFIGURING ) { //wait for addressing state
kbd_init();
Usb.setUsbTaskState( USB_STATE_RUNNING );
}
if( Usb.getUsbTaskState() == USB_STATE_RUNNING ) { //poll the keyboard
kbd_poll();
}
}
/* Initialize keyboard */
void kbd_init( void )
{
byte rcode = 0; //return code
/**/
/* Initialize data structures */
ep_record[ 0 ] = *( Usb.getDevTableEntry( 0,0 )); //copy endpoint 0 parameters
ep_record[ 1 ].MaxPktSize = EP_MAXPKTSIZE;
ep_record[ 1 ].Interval = EP_POLL;
ep_record[ 1 ].sndToggle = bmSNDTOG0;
ep_record[ 1 ].rcvToggle = bmRCVTOG0;
Usb.setDevTableEntry( 1, ep_record ); //plug kbd.endpoint parameters to devtable
/* Configure device */
rcode = Usb.setConf( KBD_ADDR, 0, 1 );
if( rcode ) {
Serial.print("Error attempting to configure keyboard. Return code :");
Serial.println( rcode, HEX );
while(1); //stop
}
/* Set boot protocol */
rcode = Usb.setProto( KBD_ADDR, 0, 0, 0 );
if( rcode ) {
Serial.print("Error attempting to configure boot protocol. Return code :");
Serial.println( rcode, HEX );
while( 1 ); //stop
}
delay(2000);
Serial.println("Keyboard initialized");
}
/* Poll keyboard and print result */
/* buffer starts at position 2, 0 is modifier key state and 1 is irrelevant */
void kbd_poll( void )
{
char i;
boolean samemark=true;
static char leds = 0;
byte rcode = 0; //return code
/* poll keyboard */
rcode = Usb.inTransfer( KBD_ADDR, KBD_EP, 8, buf );
if( rcode != 0 ) {
return;
}//if ( rcode..
for( i = 2; i < 8; i++ ) {
if( buf[ i ] == 0 ) { //end of non-empty space
break;
}
if( buf_compare( buf[ i ] ) == false ) { //if new key
switch( buf[ i ] ) {
case CAPSLOCK:
capsLock =! capsLock;
leds = ( capsLock ) ? leds |= bmCAPSLOCK : leds &= ~bmCAPSLOCK; // set or clear bit 1 of LED report byte
break;
case NUMLOCK:
numLock =! numLock;
leds = ( numLock ) ? leds |= bmNUMLOCK : leds &= ~bmNUMLOCK; // set or clear bit 0 of LED report byte
break;
case SCROLLLOCK:
scrollLock =! scrollLock;
leds = ( scrollLock ) ? leds |= bmSCROLLLOCK : leds &= ~bmSCROLLLOCK; // set or clear bit 2 of LED report byte
Serial.write(0x0c); //BYTE1
Serial.write(0x00); //BYTE2
Serial.write(0xA1); //BYTE3
Serial.write(0x01); //BYTE4
Serial.write(00); //BYTE5
Serial.write(0x00); //BYTE6
Serial.write(0x1e); //BYTE7
Serial.write(0); //BYTE8
Serial.write(0); //BYTE9
Serial.write(0); //BYTE10
Serial.write(0); //BYTE11
Serial.write(0); //BYTE12
delay(500);
Serial.write(0x0c); //BYTE1
Serial.write(0x00); //BYTE2
Serial.write(0xA1); //BYTE3
Serial.write(0x00); //BYTE4
Serial.write(0); //BYTE5
Serial.write(0x00); //BYTE6
Serial.write(0); //BYTE7
Serial.write(0); //BYTE8
Serial.write(0); //BYTE9
Serial.write(0); //BYTE10
Serial.write(0); //BYTE11
Serial.write(0); //BYTE12
break;
case DELETE:
line = false;
break;
case RETURN:
line =! line;
break;
//default:
//Serial.print(HIDtoA( buf[ i ], buf[ 0 ] ));
// break;
}//switch( buf[ i ...
rcode = Usb.setReport( KBD_ADDR, 0, 1, KBD_IF, 0x02, 0, &leds );
if( rcode ) {
Serial.print("Set report error: ");
Serial.println( rcode, HEX );
}//if( rcode ...
}//if( buf_compare( buf[ i ] ) == false ...
}//for( i = 2...
i=0;
while (i<8)
{
if (old_buf[i]!=buf[i]) { i=12; }
i++;
}
if (i==13) {
// for (i=0;i<8;i++) {
// Serial.print(buf[ i ],HEX);
// Serial.print(']');
// }
// Serial.println(' ');
Serial.write(0x0c); //BYTE1
Serial.write(0x00); //BYTE2
Serial.write(0xA1); //BYTE3
Serial.write(0x01); //BYTE4
//Labz_Debug Serial.write(buf[1]); //BYTE5
Serial.write(buf[0]); //BYTE5 //Labz_Debug
Serial.write(0x00); //BYTE6
Serial.write(buf[2]); //BYTE7
Serial.write(buf[3]); //BYTE8
Serial.write(buf[4]); //BYTE9
Serial.write(buf[5]); //BYTE10
Serial.write(buf[6]); //BYTE11
Serial.write(buf[7]); //BYTE12
}
//Labz_Debug for( i = 2; i < 8; i++ ) { //copy new buffer to old
for( i = 0; i < 8; i++ ) { //copy new buffer to old //Labz_Debug
old_buf[ i ] = buf[ i ];
}
}
/* compare byte against bytes in old buffer */
bool buf_compare( byte data )
{
char i;
for( i = 2; i < 8; i++ ) {
if( old_buf[ i ] == data ) {
return( true );
}
}
return( false );
}
我在处理SCROLLLOCK 键的地方插入了一个测试代码,理论上按下这个键的时候,主机还会收到 1 这个字符,这样是为了测试工作是否正常。
我在 x86 台式机上实测过,工作正常;小米4手机上实测过,工作正常; iPad 上是测过,工作也正常。
在iPad上工作的视频在下面:
完整代码下载
特别注意:
1. 因为我们使用的是最简单的Boot Protocol,所以如果你的键盘上有音量键之类的有可能失效;
2. 我不确定是否所有的键盘都会支持 Boot Protocol ,从之前玩USB鼠标的经验来看,确实有可能;
3. 供电部分没有经过优化,不知道电力消耗如何,不确定一个充电宝能够工作的时间;
最后讲一个小故事:有一次我去实验室,发现他们在折腾键盘。那是一款带着音量控制功能的键盘。系统测试的时候发现,按一下键盘音量键之后,屏幕上显示的音量会跳2格。从原理上说,按下那个键之后,键盘发出特定的Scan Code,系统中还有个专门响应这个Scan Code的程序然后在屏幕上绘制音量指示方块。蛮有意思的一件事情是:很多人认为大公司有操控供应商的能力,供应商在大厂面前会唯唯诺诺,这也是高层会有的想法,问题是底层人员未必吃这一套。每次想起这个事情,我都要想起敏感字关于矛盾的辩证法的论证。这个事情就是双方的下层在不停的扯,更准确的说,是键盘厂商,软件开发商和我们在一起纠缠,键盘厂商说同样的键盘在其他人家用起来没问题,软件开发商说我的软件在之前的机型上一直用,我们的人说,少扯淡,赶紧解决,前后一个多月都没有搞定…….那时候,组里刚买了一个usb逻辑分析仪,我用着感觉很好玩。于是,我就用逻辑分析仪测试了一下键盘,测试的结果是,键盘发出来的 Scan Code没有问题,每次按键都是一个Press一个Release,所以真相肯定是写上位机程序的软件厂商搞错了什么。截图附带着数据包一起丢给三方。这是最底层的传输,如果依然嘴硬,那只能落下笑柄而已。然后很快软件厂商就服软自己去修改了。只是说说我经历的事情,如果非要说出一些道理的话这个故事是为了说明:USB逻辑分析仪很有用……
就是这样.
=========================2017年5月11日更新=========================
有朋友说 Win ,Shift,Alt 都不工作,今天正好有空研究了一下,是我的代码有问题。解析出来的USB 键盘按键信息中 Buf[0] 是这些Key 的标志位,我没有正确Pass给模块。因此,修正下面2处代码,一个是发送,一个是每次保存当前的按键状态的位置:
Serial.write(0x0c); //BYTE1
Serial.write(0x00); //BYTE2
Serial.write(0xA1); //BYTE3
Serial.write(0x01); //BYTE4
Serial.write(buf[0]); //BYTE5
Serial.write(0x00); //BYTE6
Serial.write(buf[2]); //BYTE7
Serial.write(buf[3]); //BYTE8
Serial.write(buf[4]); //BYTE9
Serial.write(buf[5]); //BYTE10
Serial.write(buf[6]); //BYTE11
Serial.write(buf[7]); //BYTE12
}
for( i = 0; i < 8; i++ ) { //copy new buffer to old
old_buf[ i ] = buf[ i ];
}
修改后可以正常工作的代码:
参考:
1. http://www.lab-z.com/btkeyboard/ 蓝牙键盘模块的实验