之前给别人做东西,用了DFRobot的障碍、遮蔽传感器,感觉非常好用,只是价格比较高,要28.后来在taobao 的“树莓派一号店”买东西,正好看到他家也有,只要18,顺手就带了一个试试。结果发现不好用…….根本没有电平信号出来。找售后,他坚持说每一件出厂前都测试过,
所以,如果你希望你的作品稳定一些最好还是选用DFRobot的产品,相比他们的服务,他们的产品还是很靠谱的。
另外,淘宝买回来的东西一定尽快实验确定是否好用。
前面介绍了 EFI_GRAPHICS_OUTPUT_PROTOCOL 的各种用法,这里介绍一下如何使用这个 Protocol 进行截屏,将屏幕信息保存为 BMP 文件。
原理是:用 GraphicsOutput->Blt 将屏幕信息保存在指定的内存位置,然后加一个 BMP 的文件头,再重新排列一下颜色(BMP是直接颜色BGR排列下来),最后将这块内存保存下来就得到需要的BMP文件了。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/LoadedImage.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
extern EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2;
extern EFI_HANDLE gImageHandle;
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
static EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
#pragma pack(1)
//Copied from C\MdePkg\Include\Protocol\UgaDraw.h
typedef struct {
UINT8 Blue;
UINT8 Green;
UINT8 Red;
UINT8 Reserved;
} EFI_UGA_PIXEL;
/* This should be compatible with EFI_UGA_PIXEL */
typedef struct {
UINT8 b, g, r, a;
} EG_PIXEL;
typedef struct {
CHAR8 CharB;
CHAR8 CharM;
UINT32 Size;
UINT16 Reserved[2];
UINT32 ImageOffset;
UINT32 HeaderSize;
UINT32 PixelWidth;
UINT32 PixelHeight;
UINT16 Planes; // Must be 1
UINT16 BitPerPixel; // 1, 4, 8, or 24
UINT32 CompressionType;
UINT32 ImageSize; // Compressed image size in bytes
UINT32 XPixelsPerMeter;
UINT32 YPixelsPerMeter;
UINT32 NumberOfColors;
UINT32 ImportantColors;
} BMP_IMAGE_HEADER;
typedef struct {
UINTN Width;
UINTN Height;
BOOLEAN HasAlpha;
EG_PIXEL *PixelData;
} EG_IMAGE;
#pragma pack()
//
// Basic image handling
//
EG_IMAGE * egCreateImage(IN UINTN Width, IN UINTN Height, IN BOOLEAN HasAlpha)
{
EG_IMAGE *NewImage;
NewImage = (EG_IMAGE *) AllocatePool(sizeof(EG_IMAGE));
if (NewImage == NULL)
return NULL;
NewImage->PixelData = (EG_PIXEL *) AllocatePool(
Width * Height * sizeof(EG_PIXEL));
if (NewImage->PixelData == NULL) {
FreePool(NewImage);
return NULL;
}
NewImage->Width = Width;
NewImage->Height = Height;
NewImage->HasAlpha = HasAlpha;
return NewImage;
}
//
// Save BMP image
//
VOID egEncodeBMP(IN EG_IMAGE *Image, OUT UINT8 **FileDataReturn,
OUT UINTN *FileDataLengthReturn)
{
BMP_IMAGE_HEADER *BmpHeader;
UINT8 *FileData;
UINTN FileDataLength;
UINT8 *ImagePtr;
UINT8 *ImagePtrBase;
UINTN ImageLineOffset;
EG_PIXEL *PixelPtr;
UINTN x, y;
ImageLineOffset = Image->Width * 3;
if ((ImageLineOffset % 4) != 0)
ImageLineOffset = ImageLineOffset + (4 - (ImageLineOffset % 4));
// allocate buffer for file data
FileDataLength = sizeof(BMP_IMAGE_HEADER) +
Image->Height * ImageLineOffset;
FileData = AllocateZeroPool(FileDataLength);
if (FileData == NULL) {
Print(L"Error allocate %d bytes\n", FileDataLength);
*FileDataReturn = NULL;
*FileDataLengthReturn = 0;
return;
}
// fill header
BmpHeader = (BMP_IMAGE_HEADER *)FileData;
BmpHeader->CharB = 'B';
BmpHeader->CharM = 'M';
BmpHeader->Size = FileDataLength;
BmpHeader->ImageOffset = sizeof(BMP_IMAGE_HEADER);
BmpHeader->HeaderSize = 40;
BmpHeader->PixelWidth = Image->Width;
BmpHeader->PixelHeight = Image->Height;
BmpHeader->Planes = 1;
BmpHeader->BitPerPixel = 24;
BmpHeader->CompressionType = 0;
BmpHeader->XPixelsPerMeter = 0xb13;
BmpHeader->YPixelsPerMeter = 0xb13;
// fill pixel buffer
ImagePtrBase = FileData + BmpHeader->ImageOffset;
for (y = 0; y < Image->Height; y++) {
ImagePtr = ImagePtrBase;
ImagePtrBase += ImageLineOffset;
PixelPtr = Image->PixelData +
(Image->Height - 1 - y) * Image->Width;
for (x = 0; x < Image->Width; x++) {
*ImagePtr++ = PixelPtr->b;
*ImagePtr++ = PixelPtr->g;
*ImagePtr++ = PixelPtr->r;
PixelPtr++;
}
}
*FileDataReturn = FileData;
*FileDataLengthReturn = FileDataLength;
}
VOID egFreeImage(IN EG_IMAGE *Image)
{
if (Image != NULL) {
if (Image->PixelData != NULL)
FreePool(Image->PixelData);
FreePool(Image);
}
}
/**
Function opens and returns a file handle to the root directory of a volume.
@param DeviceHandle A handle for a device
@return A valid file handle or NULL is returned
**/
EFI_FILE_HANDLE
EfiLibOpenRoot (
IN EFI_HANDLE DeviceHandle
)
{
EFI_STATUS Status;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *Volume;
EFI_FILE_HANDLE File;
File = NULL;
//
// File the file system interface to the device
//
Status = gBS->HandleProtocol (
DeviceHandle,
&gEfiSimpleFileSystemProtocolGuid,
(VOID *) &Volume
);
//
// Open the root directory of the volume
//
if (!EFI_ERROR (Status)) {
Status = Volume->OpenVolume (
Volume,
&File
);
}
//
// Done
//
return EFI_ERROR (Status) ? NULL : File;
}
static EFI_GUID ESPGuid = { 0xc12a7328, 0xf81f, 0x11d2,
{ 0xba, 0x4b, 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b } };
static EFI_STATUS egFindESP(OUT EFI_FILE_PROTOCOL *RootDir)
{
EFI_STATUS Status;
return Status;
}
EFI_STATUS egSaveFile(IN CHAR16 *FileName,
IN UINT8 *FileData, IN UINTN FileDataLength)
{
EFI_STATUS Status;
EFI_FILE_HANDLE FileHandle;
UINTN BufferSize;
EFI_FILE_PROTOCOL *Root;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
Status = gBS->LocateProtocol(
&gEfiSimpleFileSystemProtocolGuid,
NULL,
(VOID **)&SimpleFileSystem);
if (EFI_ERROR(Status)) {
Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
return Status;
}
Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &Root);
if (EFI_ERROR(Status)) {
Print(L"OpenVolume error \r\n");
return Status;
}
Status = Root->Open(
Root,
&FileHandle,
FileName,
EFI_FILE_MODE_READ |
EFI_FILE_MODE_WRITE |
EFI_FILE_MODE_CREATE,
0);
if (EFI_ERROR(Status))
{
Print(L"Error Open NULL\n");
return Status;
}
BufferSize = FileDataLength;
Status = FileHandle->Write(FileHandle, &BufferSize, FileData);
FileHandle->Close(FileHandle);
return Status;
}
int
EFIAPI
main (
IN int Argc,
IN char **Argv
)
{
EFI_STATUS Status;
EG_IMAGE *Image;
UINT8 *FileData;
UINTN FileDataLength;
Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid,
NULL, (VOID **) &GraphicsOutput);
if (EFI_ERROR(Status)) {
GraphicsOutput = NULL;
Print(L"Loading Graphics_Output_Protocol error!\n");
return EFI_SUCCESS;
}
// allocate a buffer for the whole screen
Image = egCreateImage(GraphicsOutput->Mode->Info->HorizontalResolution,
GraphicsOutput->Mode->Info->VerticalResolution,
FALSE);
if (Image == NULL) {
Print(L"Error egCreateImage returned NULL\n");
return EFI_SUCCESS;
}
// get full screen image
if (GraphicsOutput != NULL) {
GraphicsOutput->Blt(GraphicsOutput,
(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *) Image->PixelData,
EfiBltVideoToBltBuffer,
0, 0, 0, 0, Image->Width, Image->Height, 0); }
// encode as BMP
egEncodeBMP(Image, &FileData, &FileDataLength);
egFreeImage(Image);
if (FileData == NULL) {
Print(L"Error egEncodeBMP returned NULL\n");
return EFI_SUCCESS;
}
// save to file on the ESP
Status = egSaveFile(L"screenshot.bmp", FileData, FileDataLength);
FreePool(FileData);
if (EFI_ERROR(Status)) {
Print(L"Error egSaveFile: %x\n", Status);
return EFI_SUCCESS;
}
return EFI_SUCCESS;
}
运行结果:
完整代码下载:
特别注意,本文使用的代码来自 https://github.com/chengs/UEFI 再次表示感谢。
参考:
1.http://biosengineer.blogspot.com/2011/09/uefi-screenshot-capture-screen.html UEFI Screenshot (Capture screen)
2.http://kurtqiao.blogspot.com/2013/03/how-to-take-screen-shot-under-uefi-shell.htmlHow to take screen shot under UEFI shell
做了一个很简单的小东西,用的是taobao上君悦智控【参考1】的 “JY901串口9轴加速度计\陀螺仪 MPU6050 姿态角度测量模块 卡尔曼”。用MPU6050可以很容易的获得角速度,但是如果没有算法的支撑,得到的RAW数据根本没法用。我之前试图用Arduino直接做一个能够测量摆动的装置,发现得到的数据非常糟糕。
这个模块上有加速度和陀螺仪加上地磁仪器,还有一个单片机,内部有他们自己设计的算法,处理之后从串口送出结果,就是各种姿态速度信息了。
我用这个模块,直接做了一个原型,通过usb接口供电(上面是电池,下面黑色的是一个usb取电装置,用这个装置只是因为上面有USB母头,可以方便的取电而已)。
大概介绍一下是三部分
演示是在Windows平板电脑上,只要有蓝牙接口的Windows都可以运行
上位机使用Delphi编写,源程序下载
工作的视频
http://www.tudou.com/programs/view/t_cxuhLU2hw/?resourceId=414535982_06_02_99
http://www.tudou.com/programs/view/z2Rq0ND_ARM/?resourceId=414535982_06_02_99
补充一下电路连接,直接把串口用蓝牙送出去,非常简单
参考:
1.http://robotcontrol.taobao.com/
对于USB设备来说,最重要的莫过于描述符。这里介绍如何获得描述符,以及部分描述符的含义。
程序来自http://www.circuitsathome.com/mcu/arduino-usb-host-part-3-descriptors
/* MAX3421E USB Host controller get configuration descriptor */
#include <spi.h>
#include "max3421e.h"
#include "usb.h"
#define LOBYTE(x) ((char*)(&(x)))[0]
#define HIBYTE(x) ((char*)(&(x)))[1]
#define BUFSIZE 256 //buffer size
void setup();
void loop();
MAX3421E Max;
USB Usb;
void setup()
{
byte tmpdata = 0;
Serial.begin( 9600 );
Serial.println("Start");
Max.powerOn();
delay( 200 );
}
void loop()
{
byte rcode;
Max.Task();
Usb.Task();
if( Usb.getUsbTaskState() >= 0x80 ) { //state configuring or higher
rcode = getconfdescr( 1, 0 ); //get configuration descriptor
if( rcode ) {
Serial.println( rcode, HEX );
}
while( 1 ); //stop
}
}
byte getconfdescr( byte addr, byte conf )
{
char buf[ BUFSIZE ];
char* buf_ptr = buf;
byte rcode;
byte descr_length;
byte descr_type;
unsigned int total_length;
rcode = Usb.getConfDescr( addr, 0, 4, conf, buf ); //get total length
LOBYTE( total_length ) = buf[ 2 ];
HIBYTE( total_length ) = buf[ 3 ];
if( total_length > 256 ) { //check if total length is larger than buffer
Serial.println("Total length truncated to 256 bytes");
total_length = 256;
}
rcode = Usb.getConfDescr( addr, 0, total_length, conf, buf ); //get the whole descriptor
while( buf_ptr < buf + total_length ) { //parsing descriptors
descr_length = *( buf_ptr );
descr_type = *( buf_ptr + 1 );
switch( descr_type ) {
case( USB_DESCRIPTOR_CONFIGURATION ):
printconfdescr( buf_ptr );
break;
case( USB_DESCRIPTOR_INTERFACE ):
printintfdescr( buf_ptr );
break;
case( USB_DESCRIPTOR_ENDPOINT ):
printepdescr( buf_ptr );
break;
default:
printunkdescr( buf_ptr );
break;
}//switch( descr_type
buf_ptr = ( buf_ptr + descr_length ); //advance buffer pointer
}//while( buf_ptr <=...
return( 0 );
}
/* prints hex numbers with leading zeroes */
// copyright, Peter H Anderson, Baltimore, MD, Nov, '07
// source: http://www.phanderson.com/arduino/arduino_display.html
void print_hex(int v, int num_places)
{
int mask=0, n, num_nibbles, digit;
for (n=1; n<=num_places; n++) {
mask = (mask << 1) | 0x0001;
}
v = v & mask; // truncate v to specified number of places
num_nibbles = num_places / 4;
if ((num_places % 4) != 0) {
++num_nibbles;
}
do {
digit = ((v >> (num_nibbles-1) * 4)) & 0x0f;
Serial.print(digit, HEX);
}
while(--num_nibbles);
}
/* function to print configuration descriptor */
void printconfdescr( char* descr_ptr )
{
USB_CONFIGURATION_DESCRIPTOR* conf_ptr = ( USB_CONFIGURATION_DESCRIPTOR* )descr_ptr;
Serial.println("Configuration descriptor:");
Serial.print("Total length:\t");
print_hex( conf_ptr->wTotalLength, 16 );
Serial.print("\r\nNum.intf:\t\t");
print_hex( conf_ptr->bNumInterfaces, 8 );
Serial.print("\r\nConf.value:\t");
print_hex( conf_ptr->bConfigurationValue, 8 );
Serial.print("\r\nConf.string:\t");
print_hex( conf_ptr->iConfiguration, 8 );
Serial.print("\r\nAttr.:\t\t");
print_hex( conf_ptr->bmAttributes, 8 );
Serial.print("\r\nMax.pwr:\t\t");
print_hex( conf_ptr->bMaxPower, 8 );
return;
}
/* function to print interface descriptor */
void printintfdescr( char* descr_ptr )
{
USB_INTERFACE_DESCRIPTOR* intf_ptr = ( USB_INTERFACE_DESCRIPTOR* )descr_ptr;
Serial.println("\r\nInterface descriptor:");
Serial.print("Intf.number:\t");
print_hex( intf_ptr->bInterfaceNumber, 8 );
Serial.print("\r\nAlt.:\t\t");
print_hex( intf_ptr->bAlternateSetting, 8 );
Serial.print("\r\nEndpoints:\t\t");
print_hex( intf_ptr->bNumEndpoints, 8 );
Serial.print("\r\nClass:\t\t");
print_hex( intf_ptr->bInterfaceClass, 8 );
Serial.print("\r\nSubclass:\t\t");
print_hex( intf_ptr->bInterfaceSubClass, 8 );
Serial.print("\r\nProtocol:\t\t");
print_hex( intf_ptr->bInterfaceProtocol, 8 );
Serial.print("\r\nIntf.string:\t");
print_hex( intf_ptr->iInterface, 8 );
return;
}
/* function to print endpoint descriptor */
void printepdescr( char* descr_ptr )
{
USB_ENDPOINT_DESCRIPTOR* ep_ptr = ( USB_ENDPOINT_DESCRIPTOR* )descr_ptr;
Serial.println("\r\nEndpoint descriptor:");
Serial.print("Endpoint address:\t");
print_hex( ep_ptr->bEndpointAddress, 8 );
Serial.print("\r\nAttr.:\t\t");
print_hex( ep_ptr->bmAttributes, 8 );
Serial.print("\r\nMax.pkt size:\t");
print_hex( ep_ptr->wMaxPacketSize, 16 );
Serial.print("\r\nPolling interval:\t");
print_hex( ep_ptr->bInterval, 8 );
return;
}
/*function to print unknown descriptor */
void printunkdescr( char* descr_ptr )
{
byte length = *descr_ptr;
byte i;
Serial.println("\r\nUnknown descriptor:");
Serial. print("Length:\t\t");
print_hex( *descr_ptr, 8 );
Serial.print("\r\nType:\t\t");
print_hex( *(descr_ptr + 1 ), 8 );
Serial.print("\r\nContents:\t");
descr_ptr += 2;
for( i = 0; i < length; i++ ) {
print_hex( *descr_ptr, 8 );
descr_ptr++;
}
}
运行结果如下:
手工分析USB的描述符简直就是噩梦…….最好有USB逻辑分析仪【参考1】,抓包解析USB数据非常方便。
多说两句关于【参考1】背后的故事:当年,我在一家国内最大的PC组装企业工作。有一次,加拿大的客户抱怨我们卖出去的机器无法使用他们的一款USB条码枪。我们的机器是 AMD RS780系列的商用机,这个系列的特点是:价格便宜,可以支持很多显示器(如果没有记错的话,用上标准的扩展配件可以同时支持5台显示器的显示,这在当时是很牛x的技术)。只是这个芯片组经常有稀奇古怪的问题,卖出去两三年之后会有客户报告奇怪的问题(销售总是承诺售后保修很长的时间)。比如:台湾邮政的客户惊奇的发现,每天早晨这个机器开机进入桌面之后会出现黑屏闪一次的问题等等。
USB条码枪这个客诉先到前端看了差不多三个月都没有搞定,走流程到全球售后研发这边了。组里正好有一台USB逻辑分析仪,我就拿着实验了一下。最后抓取得结果是这个条码枪和芯片组有兼容性问题,在判断USB设备速度的时候,K J 握手有问题。外国人和中国人在对待问题上差别蛮大,前者通常要求你找到Root Cause,即使时间很长也可以等待,找到之后,如果不是我们的问题,他们也能接受;而后者的要求通常是不管你什么Root Cause, 马上给我个能用的方法就行。他们的逻辑简单得也令人发指,最常见的说法是,我在XXX的机器上看不到现象,那就一定是你们的问题。
只是没想到 Root Cause 找到了,客户也表示接受了,我这边反倒闯了祸。
当时我们刚换了一个Team的主管,BIOS工程师出身,管着下面八九个人。据他所知,在 RS780 芯片组USB配置空间上有个寄存器,可以显示当前某个USB口上是否有USB设备插入。这个寄存器是 RS780芯片组特有的。这个主管坚持非要我去检查这个位是否设置起来。我说,逻辑分析仪结果都抓出来了还看啥啊? 最主要是我太懒了,看问题不一定花时间,但是找机器走流程装系统之类的很麻烦。一番话下去,主管为了不再暴露他对USB的无知,没再说下去,只是脸色变得铁青。从技术的角度来说,我的解释没有办法反驳。好比用示波器看到了波形,知道是硬件问题,Onwer肯定要换成 HW 工程师…….
最后看没看我忘记了,只是后来我在这个主管下面日子不好过了。再后来没办法自己找了个其他部门跑路了,办理转部门的时候这个主管非常“遗憾”的拖了我很久才撒手…….
这个故事告诉我们:
1. AMD 的芯片组不稳定啊!
2. 谦虚谨慎根据情况装孙子很重要,特别是国企类型的企业
3. 懂一半的领导比什么都不懂的更麻烦。
就是这样。
参考:
1.http://www.lab-z.com/wp-content/uploads/2013/02/usbhs.pdf 关于 USB 高速(HighSpeed)和中速(FullSpeed)的区分
EFI_Graphics_Output_Protocol 中的 Blt 可以实现在屏幕上绘制图形的功能。
其中的一个参数 EfiBltVideoFill 可以用来填充整个屏幕的颜色,从而实现清屏的目的。
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/LoadedImage.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
extern EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2;
extern EFI_HANDLE gImageHandle;
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
static EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
//Copied from C\MdePkg\Include\Protocol\UgaDraw.h
typedef struct {
UINT8 Blue;
UINT8 Green;
UINT8 Red;
UINT8 Reserved;
} EFI_UGA_PIXEL;
//
// Drawing to the screen
//
VOID egClearScreen(IN EFI_UGA_PIXEL *FillColor)
{
if (GraphicsOutput != NULL) {
// EFI_GRAPHICS_OUTPUT_BLT_PIXEL and EFI_UGA_PIXEL have the same
// layout, and the header from TianoCore actually defines them
// to be the same type.
GraphicsOutput->Blt(GraphicsOutput, (EFI_GRAPHICS_OUTPUT_BLT_PIXEL *)FillColor, EfiBltVideoFill,
0, 0, 0, 0, GraphicsOutput->Mode->Info->HorizontalResolution,
GraphicsOutput->Mode->Info->VerticalResolution, 0);
}
}
int
EFIAPI
main (
IN int Argc,
IN char **Argv
)
{
EFI_STATUS Status;
EFI_UGA_PIXEL color;
UINTN i;
Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
if (EFI_ERROR(Status)) {
GraphicsOutput = NULL;
Print(L"Loading Graphics_Output_Protocol error!\n");
return EFI_SUCCESS;
}
for (i=0;i<255;i++)
{
color.Blue = i & 0xFF;
color.Green = i & 0xFF;
color.Red = i & 0xFF;
egClearScreen(&color);
gBS->Stall(5000);
}
return EFI_SUCCESS;
}
工作视频:
http://www.tudou.com/programs/view/W6lvKHdeMX8/?resourceId=414535982_06_02_99
完整代码下载
参考:
1. 本文参考 https://github.com/chengs 的代码 在此表示感谢!
这篇文章目标是让你知道你买的USB Host Shield能否正常工作。
我们要运行一段代码来确保板子工作正常。从经验的角度来看,这个非常必要,对于很多卖家来说,板子之间的差别只有进货价格的高低,他们对于质量一无所知。
下面例子中的代码来自 http://www.circuitsathome.com/mcu/arduino-usb-host-part-2-classes
第一个代码是测试SPI通信是否正常
/* MAX3421E USB Host controller SPI test */
/* This sketch tests SPI communication between Arduino and MAX3421E USB host controller */
#include <spi.h>
#include "max3421e.h"
void setup();
void loop();
byte i;
byte j = 0;
byte gpinpol_copy;
MAX3421E Max;
void setup()
{
Serial.begin( 9600 );
Max.powerOn();
delay(200);
}
void loop()
{
gpinpol_copy = Max.regRd( rGPINPOL );
Serial.println("SPI test. Each '.' indicates 64K transferred. Press any key to stop.");
while( Serial.available() == 0 ) {
for( i = 0; i < 255; i++ ) {
Max.regWr( rGPINPOL, i );
if( Max.regRd( rGPINPOL ) != i ) {
Serial.println("SPI transmit/receive mismatch");
}
}//for( i = 0; i < 255; i++
j++;
if( j == 0 ) {
Serial.print(".");
}
}//while( Serial.available() == 0
Max.regWr( rGPINPOL, gpinpol_copy );
Serial.println("\r\nStopped.");
while( 1 ); //stop here
}
运行结果
下面这个代码测试的是 MAX3421E 寄存器是否正常
/* This sketch dumps MAX3421E registers */
#include <spi.h>
#include "max3421e.h"
MAX3421E Max; //MAX3421E instance
/* Regiser names/numbers for MAX3421E register dump */
typedef struct {
const char* name;
char number;
}
REGISTER_OUTPUT;
REGISTER_OUTPUT max_register[] = {
{ "\r\nRCVFIFO:\t", rRCVFIFO },
{ "\r\nSNDFIFO:\t", rSNDFIFO },
{ "\r\nSUDFIFO:\t", rSUDFIFO },
{ "\r\nRCVBC:\t", rRCVBC },
{ "\r\nSNDBC:\t", rSNDBC },
{ "\r\nUSBIRQ:\t", rUSBIRQ },
{ "\r\nUSBIEN:\t", rUSBIEN },
{ "\r\nUSBCTL:\t", rUSBCTL },
{ "\r\nCPUCTL:\t", rCPUCTL },
{ "\r\nPINCTL:\t", rPINCTL },
{ "\r\nREVISION:\t", rREVISION },
{ "\r\nIOPINS1:\t", rIOPINS1 },
{ "\r\nIOPINS2:\t", rIOPINS2 },
{ "\r\nGPINIRQ:\t", rGPINIRQ },
{ "\r\nGPINIEN:\t", rGPINIEN },
{ "\r\nGPINPOL:\t", rGPINPOL },
{ "\r\nHIRQ:\t", rHIRQ },
{ "\r\nHIEN:\t", rHIEN },
{ "\r\nMODE:\t", rMODE },
{ "\r\nPERADDR:\t", rPERADDR },
{ "\r\nHCTL:\t", rHCTL },
{ "\r\nHXFR:\t", rHXFR },
{ "\r\nHRSL:\t", rHRSL }
};
void setup()
{
Serial.begin( 9600 );
Max.powerOn();
}
void loop()
{
unsigned char i;
unsigned char numregs = sizeof( max_register )/sizeof( REGISTER_OUTPUT);
for( i = 0; i < numregs; i++ ) {
Serial.print( max_register[ i ].name);
print_hex( Max.regRd( max_register[ i ].number ), 8 );
}
while(1);
}
/* prints hex numbers with leading zeroes */
// copyright, Peter H Anderson, Baltimore, MD, Nov, '07
// source: http://www.phanderson.com/arduino/arduino_display.html
void print_hex(int v, int num_places)
{
int mask=0, n, num_nibbles, digit;
for (n=1; n<=num_places; n++)
{
mask = (mask << 1) | 0x0001;
}
v = v & mask; // truncate v to specified number of places
num_nibbles = num_places / 4;
if ((num_places % 4) != 0)
{
++num_nibbles;
}
do
{
digit = ((v >> (num_nibbles-1) * 4)) & 0x0f;
Serial.print(digit, HEX);
}
while(--num_nibbles);
}
运行结果
下面这个代码测试的是 USB 当前状态
/* MAX3421E interrupt loop */
#include <spi.h>
#include "max3421e.h"
MAX3421E Max;
byte rcode;
byte vbus_state;
void setup()
{
Serial.begin( 9600 );
Serial.println("Start");
Max.powerOn();
}
void loop()
{
Max.Task();
print_vbus_state();
}
void print_vbus_state( void )
{
char* vbus_states[] = { "Disconnected", "Illegal", "Full speed", "Low speed" };
byte tmpbyte;
static byte last_state = 4;
tmpbyte = Max.getVbusState();
if( tmpbyte != last_state ) {
last_state = tmpbyte;
Serial.println( vbus_states[ tmpbyte ] );
}
return;
}
刚开始没有插任何设备,显示为 Disconnected 状态。之后插入一个USB鼠标,识别为Low Speed设备。拔掉之后再插入两个不同的U盘,因为IC本身不支持High Speed,所以都显示为Full Speed设备。
最后,三个修改后的完整代码可以在这里下载:
经过上述测试,可以确定你的板子没问题。
学习了一下如何获得 Shell 下当前的显示信息。通过 GraphicsOutputProtocol 来完成这个功能。这个 Protocol 在【参考1】 中有描述。
头定义在 \MdePkg\Include\Protocol\GraphicsOutput.h
typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL EFI_GRAPHICS_OUTPUT_PROTOCOL;
///
/// Provides a basic abstraction to set video modes and copy pixels to and from
/// the graphics controller's frame buffer. The linear address of the hardware
/// frame buffer is also exposed so software can write directly to the video hardware.
///
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt;
///
/// Pointer to EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE data.
///
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
};
从名称上来看,这个 Protocol 能够实现的功能是:查询/设置当前显示模式,将屏幕内容和内存互copy等。
这次实验的是查询功能。
查询的结果输出是 EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE 结构体
typedef struct {
///
/// The number of modes supported by QueryMode() and SetMode().
///
UINT32 MaxMode;
///
/// Current Mode of the graphics device. Valid mode numbers are 0 to MaxMode -1.
///
UINT32 Mode;
///
/// Pointer to read-only EFI_GRAPHICS_OUTPUT_MODE_INFORMATION data.
///
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
///
/// Size of Info structure in bytes.
///
UINTN SizeOfInfo;
///
/// Base address of graphics linear frame buffer.
/// Offset zero in FrameBufferBase represents the upper left pixel of the display.
///
EFI_PHYSICAL_ADDRESS FrameBufferBase;
///
/// Amount of frame buffer needed to support the active mode as defined by
/// PixelsPerScanLine xVerticalResolution x PixelElementSize.
///
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
可以看到能够获得当前显示模式,屏幕分辨率和格式信息等。根据上面的信息,编写程序如下
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/LoadedImage.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
extern EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2;
extern EFI_HANDLE gImageHandle;
static EFI_GUID GraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
static EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput = NULL;
int
EFIAPI
main (
IN int Argc,
IN char **Argv
)
{
EFI_STATUS Status;
Status = gBS->LocateProtocol(&GraphicsOutputProtocolGuid, NULL, (VOID **) &GraphicsOutput);
if (EFI_ERROR(Status)) {
GraphicsOutput = NULL;
Print(L"Loading Graphics_Output_Protocol error!\n");
return EFI_SUCCESS;
}
Print(L"Max mode =[%d] \n",GraphicsOutput->Mode->MaxMode);
Print(L"Current mode =[%d] \n",GraphicsOutput->Mode->Mode);
Print(L"Version =[%d] \n",GraphicsOutput->Mode->Info->Version);
Print(L"Screen Width =[%d] \n",GraphicsOutput->Mode->Info->HorizontalResolution);
Print(L"Screen height=[%d] \n",GraphicsOutput->Mode->Info->VerticalResolution);
Print(L"Format =[%d] \n",GraphicsOutput->Mode->Info->PixelFormat);
Print(L"Num of pixel =[%d] \n",GraphicsOutput->Mode->Info->PixelsPerScanLine);
return EFI_SUCCESS;
}
运行结果(还是NT32模拟器中)
完整代码下载:
参考:
1.UEFI Spec 2.4 P488 11.9 Graphics Output Protocol
==========================================================
2025年3月17日 额外同一个功能类似的代码
#include <Uefi.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Protocol/GraphicsOutput.h>
#include <Library/DebugLib.h>
INTN
EFIAPI
ShellAppMain (
IN UINTN Argc,
IN CHAR16 **Argv
)
{
EFI_STATUS Status;
EFI_GRAPHICS_OUTPUT_PROTOCOL *GraphicsOutput;
UINTN HandleCount;
EFI_HANDLE *HandleBuffer;
UINTN Index;
UINT32 ModeIndex;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN SizeOfInfo;
// Locate the handles that support the Graphics Output Protocol
Status = gBS->LocateHandleBuffer(ByProtocol, &gEfiGraphicsOutputProtocolGuid, NULL, &HandleCount, &HandleBuffer);
if (EFI_ERROR(Status)) {
Print(L"Failed to locate handles for Graphics Output Protocol\n");
return Status;
}
// Iterate over all handles
for (Index = 0; Index < HandleCount; Index++) {
// Get the Graphics Output Protocol instance
Status = gBS->HandleProtocol(HandleBuffer[Index], &gEfiGraphicsOutputProtocolGuid, (VOID**)&GraphicsOutput);
if (EFI_ERROR(Status)) {
Print(L"Failed to handle protocol for handle %d\n", Index);
continue;
}
Print(L"Controler [%d]\n", Index);
// Iterate over each mode
for (ModeIndex = 0; ModeIndex < GraphicsOutput->Mode->MaxMode; ModeIndex++) {
// Get mode information
Status = GraphicsOutput->QueryMode(GraphicsOutput, ModeIndex, &SizeOfInfo, &Info);
if (EFI_ERROR(Status)) {
Print(L"Failed to query mode %d\n", ModeIndex);
continue;
}
// Output the resolution and pixel format
Print(L"Mode %d: Resolution: %ux%u, Pixels Per Scanline: %u\n",
ModeIndex,
Info->HorizontalResolution,
Info->VerticalResolution,
Info->PixelsPerScanLine);
// Free the mode information
gBS->FreePool(Info);
}
}
// Free the handle buffer
gBS->FreePool(HandleBuffer);
return EFI_SUCCESS;
}
## @file
# A simple, basic, EDK II native, "hello" application.
#
# Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.<BR>
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
##
[Defines]
INF_VERSION = 0x00010006
BASE_NAME = gm
FILE_GUID = a912f198-2025-0317-b908-b757b806ec83
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 0.1
ENTRY_POINT = ShellCEntryLib
#
# VALID_ARCHITECTURES = IA32 X64
#
[Sources]
GetModeInfo.c
[Packages]
MdePkg/MdePkg.dec
ShellPkg/ShellPkg.dec
[LibraryClasses]
UefiLib
ShellCEntryLib

比如我想从串口得到一个输入,然后再输出到串口上,然后写下面的程序
String comdata="";
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
while (Serial.available() > 0)
{
comdata += Serial.read();
delay(2);
}
if (comdata.length()>1) {Serial.println(comdata); comdata="";}
}
但是惊奇的发现,结果是下面这样。”1″ 对应为 “49”两个字符;“2”对应为 “50”两个字符………
如果想得到想要的结果,需要做一个强制转换
String comdata="";
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
// put your main code here, to run repeatedly:
while (Serial.available() > 0)
{
comdata +=(char)Serial.read();
delay(2);
}
if (comdata.length()>1) {Serial.println(comdata); comdata="";}
}
这样的结果就符合预期
感谢极客工坊Super169这位朋友,他给出的解释:
String class 的特性, 當 operator “+” 之後是 numeric type, 會自動把數值先轉成 字符再加上去.
“1” 的數值就是 49, 由於 Serial.read() 的結果是 int type, 當你用 comdata += Serial.read() 時, comdata 是 String, Serial.read() 是 int, 就會執行 String “+” int 的 operation. 當你輸入第一個字 “1” 時, 就會先把 “1” (int value 為 ASCII 值, 即 49) 轉成 “49”, 然後再連接上去. 之後都是一樣了.
當你加上 (char) 的轉換後, comdata += (char)Serial.read(), 就變成是 String “+” char 的 operation, 這時就不需要任何轉換而直接連上去了.
这个问题的原文可以在 http://www.geek-workshop.com/thread-14981-1-1.html 看到
屏幕显示功能搭配取按键信息的功能可以实现一个简单的菜单,用户可以使用上下键移动后选项,回车确认,ESC退出。
代码本身不复杂
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>
#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
extern EFI_BOOT_SERVICES *gBS;
extern EFI_SYSTEM_TABLE *gST;
extern EFI_RUNTIME_SERVICES *gRT;
extern EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2;
extern EFI_HANDLE gImageHandle;
//
// EFI Scan codes
// copied from \EdkCompatibilityPkg\Foundation\Efi\Protocol\SimpleTextIn\SimpleTextIn.h
//
#define SCAN_NULL 0x0000
#define SCAN_UP 0x0001
#define SCAN_DOWN 0x0002
#define SCAN_ESC 0x0017
#define CHAR_CARRIAGE_RETURN 0x000D
#define POSX 7
#define POSY 3
#define NUM 5
CHAR16 HELPSTR[40]=L"UP/DOWN, ENTER , ESC";
CHAR16 ITEM[NUM][20]= {
L"Item1 MOVSXX",
L"Item2 CPUID",
L"Item3 RETF",
L"Item4 PUSHF",
L"Item5 SUB"
};
int
EFIAPI
main (
IN int Argc,
IN char **Argv
)
{
EFI_INPUT_KEY Key;
EFI_STATUS Status;
int current=0;
int i;
gST -> ConOut ->ClearScreen(gST->ConOut);
ShellPrintEx(POSX-3,POSY-1,L"%N%S",HELPSTR);
ShellPrintEx(POSX,POSY,L"%H%S",ITEM[0]);
for (i=1;i<NUM;i++)
{
ShellPrintEx(POSX,POSY+i,L"%N%S",ITEM[i]);
}
Key.ScanCode=SCAN_NULL;
while (SCAN_ESC!=Key.ScanCode)
{
Status= gST -> ConIn -> ReadKeyStroke(gST->ConIn,&Key);
if (Status == EFI_SUCCESS) {
ShellPrintEx(POSX,POSY+current,L"%N%S",ITEM[current]);
if (SCAN_UP == Key.ScanCode) {current = (current-1+NUM)%NUM;}
if (SCAN_DOWN == Key.ScanCode) {current = (current+1)%NUM;}
ShellPrintEx(POSX,POSY+current,L"%H%S",ITEM[current]);
ShellPrintEx(POSX,POSY+NUM,L"Current[%d] Scancode[%d] UnicodeChar[%x] \n\r",current,Key.ScanCode,Key.UnicodeChar);
}
if (CHAR_CARRIAGE_RETURN == Key.UnicodeChar) {
ShellPrintEx(POSX,POSY+NUM+1,L"You have chosen: %N%S",ITEM[current]);
break;
}
};
return EFI_SUCCESS;
}
运行结果:
工作视频:
http://www.tudou.com/programs/view/R_932wqGuYw/?resourceId=414535982_06_02_99
完整的代码下载:
前几天,偶然知道周星驰《国产凌凌漆》英文名称是“From Beijing with Love”。然后又去重温了一遍。
零零七系列一定要有各种无厘头的道具,比如:要你命3000
还有就是经典的太阳能手电。于是,仿造一个用打火机才能“点亮”的小灯。用到之前的MOS模块,小灯泡,还有就是火焰传感器。
这种传感器的原理是:检测火焰或者波长在760纳米~1100纳米范围内的光源。
代码非常简单,火焰传感器输出接在Arduino的Pin9上。
#define firePin 9
#define lampPin 6
void setup()
{
Serial.begin(9600);
pinMode(lampPin,OUTPUT);
pinMode(firePin, INPUT);
}
void loop()
{
if (0==digitalRead(firePin)) {
for (int i=1;i<128;i++)
{
analogWrite(lampPin,i);
delay(100);
}
delay(5000);
for (int i=128;i>0;i--)
{
analogWrite(lampPin,i);
delay(100);
}
}
}
实现的效果是:当没有火焰的时候绝对不亮,如果用一个打火机晃一下,它就会亮起来。
工作视频:
http://www.tudou.com/programs/view/RtMg4ayv_dA/?resourceId=414535982_06_02_99
艺术源于生活,因为时代的发展,未必高于生活【参考2】。
参考:
1. http://www.lab-z.com/?p=3201 MOS控制小灯泡的实验
2. http://baike.baidu.com/link?url=Ldym-lA1eNVcdMMGXyE-ARW5R2ECOA02w-XbslPob1yitMhbngKTwggrS8HbzXDTPQ8UXz7-TIC7ekCaSKsUwK#1