Step to UEFI (49) —– 内存驻留程序

之前,我们通过修改Shell的代码,能够实现在右上角不断显示当前时间的功能。但是这个方法过于复杂。于是我请教了一下 HZZZ ,看看他是否有什么好办法。

经过他的研究,还是使用定时器的方式不断触发,困难点在于如果退出时没有对应的 Handle,再次触发会导致问题。解决这个问题的方法是:让程序在内存中重新加载自己,跳进去执行之后,最开始的部分就可以丢弃掉了。

当然,如果你想读懂这个程序,要先读懂前面几篇 《Step to UEFI>> 系列的文章。

#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 CONST UINTN SecondsToNanoSeconds = 500000;

/**
  The callback function for the timer event used to get map.

  @param[in] Event    The event this function is registered to.
  @param[in] Context  The context registered to the event.
**/
VOID
EFIAPI
Timeout (
  IN EFI_EVENT      Event,
  IN VOID           *Context
  )
{
	EFI_TIME   ET;
	UINTN		x;
	UINTN		y;
  

  //Get cursor postion
  x = gST->ConOut->Mode->CursorColumn;
  y = gST->ConOut->Mode->CursorRow;
  //Move cursor to Up-Left 
  gST -> ConOut -> SetCursorPosition(gST -> ConOut,70,0);  
  //Output current time
  gRT->GetTime(&ET, NULL);  
  Print(L"%2d:%2d:%2d",ET.Hour,ET.Minute,ET.Second);
  //Move cursor back
  gST -> ConOut -> SetCursorPosition(gST -> ConOut,x,y);
  
  return ;
}

typedef struct {
  UINTN                       Signature;
  /// Image handle
  EFI_HANDLE                  Handle;   
  /// Image type
  UINTN                       Type;           
  /// If entrypoint has been called
  BOOLEAN                     Started;        
  /// The image's entry point
  EFI_IMAGE_ENTRY_POINT       EntryPoint;     
  /// loaded image protocol
  EFI_LOADED_IMAGE_PROTOCOL   Info; 
} LOADED_IMAGE_PRIVATE_DATA_TEMP;

#define _CR(Record, TYPE, Field)  ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))

#define LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(a) \
          _CR(a, LOADED_IMAGE_PRIVATE_DATA_TEMP, Info)



typedef void (*Fun)();

void function()
{
    EFI_STATUS                Status;
    EFI_HANDLE                TimerOne = NULL;
	
    printf("function called\n");
	
    Status  = gBS->CreateEvent (
                    EVT_NOTIFY_SIGNAL | EVT_TIMER,
                    TPL_CALLBACK,
                    Timeout,
                    NULL,
                    &TimerOne
                    );

    if (EFI_ERROR (Status)) {
        Print(L"Create Event Error! \r\n");
		return ;
    }

    Status = gBS->SetTimer (
                   TimerOne,
                   TimerPeriodic,
                   MultU64x32 (SecondsToNanoSeconds, 1)
                   );

    if (EFI_ERROR (Status)) {
        Print(L"Set Timer Error! \r\n");
		return ;
    }
}


int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{


  EFI_STATUS                         Status = EFI_SUCCESS;
  EFI_LOADED_IMAGE_PROTOCOL          *ImageInfo = NULL;
  EFI_HANDLE                         Handle = 0;
  EFI_GUID                           gEfiLoadedImageProtocolGuid = 
                                     { 0x5B1B31A1, 0x9562, 0x11D2, { 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }};
  LOADED_IMAGE_PRIVATE_DATA_TEMP      *private = NULL;
  Fun                                fun;
  UINTN                              FunOffset;
  UINTN                              FunAddr;

  Status = gBS->HandleProtocol (gImageHandle, &gEfiLoadedImageProtocolGuid, &ImageInfo);
  // function offset in the old image
  FunOffset = (UINTN)function - (UINTN)ImageInfo->ImageBase;

  // load the image in memory again
  Status = gBS->LoadImage(FALSE, gImageHandle, NULL, ImageInfo->ImageBase, (UINTN)ImageInfo->ImageSize, &Handle);  

  // get the newer imageinfo
  Status = gBS->HandleProtocol (Handle, &gEfiLoadedImageProtocolGuid, &ImageInfo);

  private = LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(ImageInfo);
  FunAddr = (UINTN)FunOffset + (UINTN)ImageInfo->ImageBase;
  
  fun = (Fun)((UINTN)FunOffset + (UINTN)ImageInfo->ImageBase);
  // called the newer function in new image,the new image will be always in memory because it will not be free
  fun();
  return EFI_SUCCESS;
}

 

工作的视频,看得出来这样的方法适应性更好

完整代码下载

ShowTime2

Arduino 控制USB设备(1)硬件基础篇

相较而言,如何将Arduino模拟为USB设备是一个比较简单的事情,可以用Arduino Uno加入电阻伪装成USB Keyboard/Mouse 【参考1】; 或者直接使用芯片是ATmega32U4的板子,比如Leonardo ,Pro Micro等。但是如果你想直接让Arduno能够和USB设备进行交互就需要动一番脑筋了。

经过搜索发现一块 USB Host Shield能够完成上面的目标。

特别注意,国内能够购买到的USB Host Shield有下面两种

image001

image002

务必购买下面这种(蓝色板子的)。他们之间芯片都是相同的,用的都是Maxim的MAX3421E芯片,和Arduino用SPI接口通信。差别在于蓝色的那种是有官方网站支持的,用起来会好很多,同时上面多了74AHC125 和 74HCT125【参考2】而红色的是第三方开发出来的。

非常不幸的是,我是在Taobao上佳明电子科技这家购买到红色那块。拿到手开始玩之后惊奇的发现有这样的差别。当然,这也和我在发动之前没有做好功课有很大关系。联系卖家也得不到帮助,没有库,没有电路图,让人郁闷和恼火,冷静下来思考:国内Arduino研发能力很弱,通常都是仿造少有原创,那么买到手的应该也是仿制国外的……功夫不负有心人,找到了他的网站
https://www.sparkfun.com/products/9947
image003

顺藤摸瓜,这才搞清楚它和上面蓝色板子差别不大,对付一下还是能用的。前车之鉴,希望后来人在购买硬件上多留心。

然后 Arduino USB Host Shield 的库支持网站是

http://www.circuitsathome.com/arduino_usb_host_shield_projects

这一系列文章都是以这个网站作为参考,有兴趣的朋友可以直接上去翻看各种资料会有很多收获。下面就可以开始 Arduino 操作USB设备之旅。

提醒:插在Uno上的时候是下面这个样子,Shield上的USB方向和Arduino的USB方向刚好相反,错开了(千万别搞反了)
image004

参考:

1. http://www.lab-z.com/20140101/ 用 Arduino 打造一个自动锁屏装置

2. http://www.circuitsathome.com/mcu/arduino-usb-host-shield-build-log-part-4

上面提到这两个IC 不过具体作用我也没搞明白,看起来对我们使用最常见的Arduino Uno没有影响

image005

======================2021年11月14日 ======================

收集到的2个USB Host Shield 的电路,特别需要注意的地方是:Arduino Uno 引脚是 5V, 而主芯片工作在 3.3V 上,所以必须有用于电平转换的设计。

MOS控制小灯泡的实验

之前一直使用继电器作为开关,存在的问题是:

1. 无法高速开关
2. 开关会有声音

请教硬件工程师Johnson,他推荐我学习一下用MOS管作为开关。一般情况下它比三极管能承受更高的电流和电压,并且它是电压控制,不用像三极管那样先计算电流之类的,更容易计算。【参考1】美中不足的是MOS贵一些。
MOS管长成这个样子【参考2】

image001

前几天我在Taobao上购物,看到店铺里有对应的模块,顺手就买下来了,4.5元【参考3】。

image002

模块和元件长得差别有点大,随手画了下各个引脚

image003

左上角 V+ V- 是输出的正负。右上角 Vin 和 GND 是输入的电源的正负极。我用万用表测量了一下,V+ 和Vin 是通的。下面一排分别是 SIG (控制信号),VCC (控制信号电源),GND(控制信号的地)。我用万用表又测量了一下,右上角的GND和下面的GND是通的。
为了验证MOS所以设计了一个实验,用Arduino的PWM输出来控制 SIG,然后达到控制灯泡亮度的目的。
程序很简单,在我们之前的文章中出场过【参考4】。

int  n=255;
void setup()
{
    Serial.begin(9600);
    pinMode(6,OUTPUT);      //该端口需要选择有#号标识的数字口
}

void loop()
{
  char  c;

    while (Serial.available() > 0)  
    {
        c=Serial.read();
        if (']'==c) 
          {
            n=n+5;
          }
        if ('['==c) 
          {
            n=n-5;
          }
       if (n>255) {n=0;}
       if (n<0) {n=255;}   
       analogWrite(6,n); 
       Serial.println(n);

    }
}

 

最后的硬件连接图

image004

上图中的Arduino是USB单独供电的,和灯泡供电是共地而已。
实物照片:

image005

参考:
1. http://wenku.baidu.com/link?url=aWK28h-3NRQ-wvP5wo2c4Du6XgiVI-Tbe6HzaSb0HSS0Xe_e4-QXsZc6Wry0S8KYWjYcb2NwNJbhG_25jM2Wv62HK6g87Cyvf5iWdeFkaDO 三极管和MOS管的区别
2. http://baike.baidu.com/link?url=u0bt0SHS8wEptv7XUiymPXr-homtOfXSsaBkqPSytPSgavA1f8olhLXO6AkGLCnfaEpABoXNGjRIm5ifMy2nh_ mos管
3. http://dzyj.taobao.com/index.htm?spm=2013.1.w5002-10779758706.2.Xd74xF Arduino电子积木 MOS管场效应管驱动模块
4. http://www.lab-z.com/pwmle/ PWM 控制LED亮度

补充一个MOS的基本用法

23381466_1371446942FPXu

Step to UEFI (48) —– 被加载程序的ENTRYPOINT

之前能获得被加载程序的一些基本信息,但是只是“基本”的信息,比如我们需要 EntryPoint应该怎么办呢?

我在网上搜索了一下无果,请教 HZZZ,他给我的建议是:LOADED_IMAGE_PRIVATE_DATA_TEMP。

可以在 \MdeModulePkg\Core\Dxe\Image\Image.h 中看到这个定义。

typedef struct {
  UINTN                       Signature;
  /// Image handle
  EFI_HANDLE                  Handle;   
  /// Image type
  UINTN                       Type;           
  /// If entrypoint has been called
  BOOLEAN                     Started;        
  /// The image's entry point
  EFI_IMAGE_ENTRY_POINT       EntryPoint;     
  /// loaded image protocol
  EFI_LOADED_IMAGE_PROTOCOL   Info;           
  /// Location in memory
  EFI_PHYSICAL_ADDRESS        ImageBasePage;  
  /// Number of pages
  UINTN                       NumberOfPages;  
  /// Original fixup data
  CHAR8                       *FixupData;     
  /// Tpl of started image
  EFI_TPL                     Tpl;            
  /// Status returned by started image
  EFI_STATUS                  Status;         
  /// Size of ExitData from started image
  UINTN                       ExitDataSize;   
  /// Pointer to exit data from started image
  VOID                        *ExitData;      
  /// Pointer to pool allocation for context save/retore
  VOID                        *JumpBuffer;    
  /// Pointer to buffer for context save/retore
  BASE_LIBRARY_JUMP_BUFFER    *JumpContext;  
  /// Machine type from PE image
  UINT16                      Machine;        
  /// EBC Protocol pointer
  EFI_EBC_PROTOCOL            *Ebc;           
  /// Runtime image list
  EFI_RUNTIME_IMAGE_ENTRY     *RuntimeData;   
  /// Pointer to Loaded Image Device Path Protocl
  EFI_DEVICE_PATH_PROTOCOL    *LoadedImageDevicePath;  
  /// PeCoffLoader ImageContext
  PE_COFF_LOADER_IMAGE_CONTEXT  ImageContext; 

} LOADED_IMAGE_PRIVATE_DATA;

 

根据我的理解,我们之前使用到的 EFI_LOADED_IMAGE_PROTOCOL 只是这个结构体的一部分。我们知道 EFI_LOADED_IMAGE_PROTOCOL 的内存地址,然后可以反推出整个 LOADED_IMAGE_PRIVATE_DATA_TEMP 结构。为了实现这个需要用一个比较有技巧的宏:

#define _CR(Record, TYPE, Field) ((TYPE *) ((CHAR8 *) (Record) – (CHAR8 *) &(((TYPE *) 0)->Field)))

#define LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(a) _CR(a, LOADED_IMAGE_PRIVATE_DATA_TEMP, Info)

(在其他地方也能看到这个宏的,它的作用就是根据一个结构体中已知Field的地址反推出整个结构体的内存地址。充满了C语言让人炫目的技巧。)

简单起见 HZZZ 给我的建议是这个结构体可以只使用一部分,不需要声明全部。

完整的代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

extern EFI_BOOT_SERVICES           	 *gBS;
extern EFI_SYSTEM_TABLE				 *gST;
extern EFI_RUNTIME_SERVICES 		 *gRT;

extern EFI_SHELL_PROTOCOL            *gEfiShellProtocol;
extern EFI_SHELL_ENVIRONMENT2 		 *mEfiShellEnvironment2;

extern EFI_HANDLE					 gImageHandle;

typedef struct {
  UINTN                       Signature;
  /// Image handle
  EFI_HANDLE                  Handle;   
  /// Image type
  UINTN                       Type;           
  /// If entrypoint has been called
  BOOLEAN                     Started;        
  /// The image's entry point
  EFI_IMAGE_ENTRY_POINT       EntryPoint;     
  /// loaded image protocol
  EFI_LOADED_IMAGE_PROTOCOL   Info; 
  /// Location in memory
  EFI_PHYSICAL_ADDRESS        ImageBasePage;    
} LOADED_IMAGE_PRIVATE_DATA_TEMP;

#define _CR(Record, TYPE, Field)  ((TYPE *) ((CHAR8 *) (Record) - (CHAR8 *) &(((TYPE *) 0)->Field)))

#define LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(a) \
          _CR(a, LOADED_IMAGE_PRIVATE_DATA_TEMP, Info)
		  
/**
  GET  DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    return (mEfiShellEnvironment2->NameToPath(DeviceName));
  }

  return (NULL);
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_DEVICE_PATH_PROTOCOL *DevicePath;
  EFI_HANDLE	NewHandle;
  EFI_STATUS	Status;
  LOADED_IMAGE_PRIVATE_DATA_TEMP      *private = NULL;  
  UINTN			ExitDataSizePtr;
  EFI_LOADED_IMAGE_PROTOCOL	*ImageInfo = NULL;
  
  if (Argc!=2) {
		Print(L"Usage: Exec4 FileName\n");
		return EFI_SUCCESS;
  }
  
  Print(L"File [%s]\n",Argv[1]);

  DevicePath=ShellGetDevicePath(Argv[1]);

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    gImageHandle,
    DevicePath,
    NULL,
    0,
    &NewHandle);  

  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during LoadImage [%X]\n",Status);
    return (Status);
  }

  Status = gBS -> HandleProtocol (
						NewHandle,
						&gEfiLoadedImageProtocolGuid,
						&ImageInfo
						);
						
  private = LOADED_IMAGE_PRIVATE_DATA_FROM_THIS(ImageInfo);  

  Print(L"ImageBase in EFI_LOADED_IMAGE_PROTOCOL      [%lX]\n",ImageInfo->ImageBase);
  Print(L"ImageBase in LOADED_IMAGE_PRIVATE_DATA_TEMP [%lX]\n",private->ImageBasePage);
  Print(L"Entry Point [%lX]\n",private->EntryPoint);

  Print(L"================================RUN================================\r\n",Status);
  //
  // now start the image, passing up exit data if the caller requested it
  //
  Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during StartImage [%X]\r\n",Status);
    return (Status);
  }
  Print(L"===============================EXIT================================\r\n",Status);
  
  gBS->UnloadImage (NewHandle);  
  return EFI_SUCCESS;
}

 

程序接收文件名作为参数,显示接收到EFI文件的入口。运行结果

N48

完整代码下载
exec4

本程序示例代码和编译中间文件

Hello2

Hello2build

解决 Warning C4819 的工具

某些情况下,BIOS编译过程中会收到 Warning C4819 的错误信息。

cwarning

Warning C4819:The file contains a character that can ot be represented in the current code page(936). save the file in unicode format to prevent data loss.

中文意思是:该文件包含不能在当前代码页中表示的字符,请将文件保存为Unicode格式,以防止数据丢失。【参考1】

简单的说产生的原因是源代码中有和你当前系统中 codepage 中不兼容无法表示的编码。这只是一个Warning 信息,只是因为 BIOS编译过程打开了 Warning as error 所以会导致编译停止。这样的字符通常处于注释中,程序员情不自禁的用了本地字符导致的。

了解了原因,有下面的解决方法:

1.换用英文版的OS,一劳永逸的方法;
2.在出现问题的文件关闭warning功能;
3.找到出现Warning的文件用NotePad打开,再按照ANSI格式保存一下;

只是上面都比较麻烦,特别是某些时候涉及到有这样问题的程序很多,你无法知道需要重复上面的动作多少次。

于是编写了这个工具用来解决这个问题,具体的原理是:逐个打开源文件,然后转换编码为当前系统的CodePage,比较转换前后,如果结果相同表明没有无法识别的编码字符;如果不同,首先改名原文件做备份,再将转换编码后的文件保存下来。

特别注意:

1.请确保使用这个工具之前你的源文件有备份
2.请确保源文件去掉只读属性

下载:

CCPv1.0

参考:

1.http://www.cnblogs.com/rainbowzc/archive/2009/07/02/1515427.html 不再经受”Warning C4819″的摧残(转)

本文首发于 BIOSren 论坛 http://biosren.com/viewthread.php?tid=7582&rpid=57253&fav=yes&ordertype=0&page=1#pid57253

Arduino “spawn error” 怎么办?

我在 Arduino 1.6.0 上忽然之间遇到 “avr-g++: error: spawn: No such file or directory” 这样的错误。反复安装Arduino之后,这个错误提示反而消失了,出现的错误是

“Show verbose output during compilation”
enabled in File > Preferences.
Arduino: 1.0.6 (Windows 7), Board: “Arduino Uno”

最终打开下面这个位置,勾选之后可以显示编译过程

dbg

发现问题原因在于编译器无法找到 cygwin1.dll

解决方法:将 arduino 的目录加入到 path 下面即可解决

Step to UEFI (47) —– 偏移正确吗?

前面展示了在一个程序中调用另外一个程序的方法,还有加载过程中获取被加载程序的一些基本信息。其中的一个是ImageBase。这里做一个实验来验证上面显示的ImageBase是否正确。

上次的HelloWorld.c代码中我们还加入一条显示UefiMain在内存中的位置。

#include <Uefi.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>


/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.  
  @param[in] SystemTable    A pointer to the EFI System Table.
  
  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"Hello,World! \r\n");
  Print(L"www.lab-z.com \r\n");
  
  Print(L"UefiMain  [%X]",(UINTN)UefiMain);  
  
  return EFI_SUCCESS;
}

 

直接运行编译后的结果如下:
elipc1

可以看到,UefiMain被加载到了0x033E 36C5 的位置。运用之前的知识,我们在
\Build\AppPkg\RELEASE_MYTOOLS\IA32\AppPkg\Applications\HelloWorld\HelloWorld\OUTPUT\
能看到编译过程中生成的HelloWorld.map文件(特别注意:我是用build –a IA32 –p AppPkg\AppPkg.dsc –b RELEASE 来生成Release版本的,其他版本会出现在不同的目录中)。

  Address         Publics by Value              Rva+Base       Lib:Object
 0001:00000490       ??_C@_02PCIJFNDE@?$AN?6?$AA@ 00000690     BasePrintLib:PrintLibInternal.obj
 0001:00000494       ??_C@_01LIIJDEN@?$AN?$AA@  00000694     BasePrintLib:PrintLibInternal.obj
 0001:00000496       __ModuleEntryPoint         00000696 f   UefiApplicationEntryPoint:ApplicationEntryPoint.obj
 0001:000004c5       _UefiMain                  000006c5 f   HelloWorld:HelloWorld.obj
 0001:000004fa       _DebugAssert               000006fa f   BaseDebugLibNull:DebugLib.obj
 0001:000004fb       _DebugAssertEnabled        000006fb f   BaseDebugLibNull:DebugLib.obj

 

实际加载的偏移是 6C5 ( Rva+Base ,具体解释请看【参考1 2 3】)

再使用我们的exec2来加载这个EFI可执行程序

elipc2

因此从结果上看,我们用EFI_LOADED_IMAGE_PROTOCOL 获得的ImageBase是准确的。

参考:

1. http://blog.csdn.net/fantcy/article/details/4474604 PE格式深入浅出之RAV,AV,ImageBase之间的关系
2. http://www.cnblogs.com/lzjsky/archive/2011/09/22/2184942.html PE格式全分析
3. http://blog.sina.com.cn/s/blog_6cc1c52d0100t4wa.html PE文件格式学习笔记

攀藤 G1 PM2.5传感器

本文有更新,请参考 拆了攀登 G1 PM 2.5 传感器 《拆了攀登 G1 PM 2.5 传感器》

最近帮朋友做东西,他要求测试当前环境PM2.5。我首先拿出了珍藏许久的神荣模块。记得当时是90多入手的,当初Taobao上也没几个卖这个模块的,90多是最低价格了。写这篇文章的时候顺手查了一下目前的价格,已经降到了60多,卖家也不少了。很可能是目前出货量大,整体价格都降低下来了。我发现除了线材或者连接器,几乎所有的电子模块都有这样的问题。比方说:MPU6050模块,刚出来的时候居然要五六十,现在只要五六元。

先说一下神荣模块,问题多多,首先是根据他的SPEC,要求连续采集30秒,采集的时候基本上什么也不能做,否则会严重影响精度;另外,他每次计算的数值偏差很大,委婉一点的说法是:能够精确反应当前空气颗粒趋势……对于采集偏差,有资料说可能是因为他是采用发热电阻加热空气来实现流动的,而这样的方式不能确保空气的流通速度,所以结果不是很准确。个人观点:如果你的传感器上没有风扇换气,都是靠不住的。

后来,看到论坛上有人推荐攀藤模块,我在上网考察了一下,感觉上微创联合【参考1】这家很专业,但是不知道为什么他邮费报价很高(25?);最后我在树莓派一号店【参考2】这家买的,155+5元邮费,型号是G1。

简单说一下型号,有 G1 G2 G3,这几个主要差别在于外形尺寸。另外,G1可以输出最近一段测试到的单位个数。

外壳上有一层蓝膜,撕下之后能露出金属外壳(有朋友看照片说这个模块很老,那是因为我照相技术的原因,这个模块应该是2014年10月之后才有的):

image002

可以看到,上面带有一个风扇进行空气交换的。

image004

引脚介绍【参考3】。特别注意,似乎他们他们家的模块G1 G3引脚顺序上有差别?根据你手上的实物进行选择

image006

入手之后就开始实验,连接上最小只需要三根线即可让他工作,分别是VCC (Pin1) GND(Pin2)和TXD(Pin5)。我直接将这个模块和蓝牙透传模块连接起来(9600),PC串口即可获得数据非常方便。

image007

因为手头没有能够进行对照测试的仪器,只是简单测试了一下:我用手堵住进气口,过一段PM2.5值会变为0. 可以看一下工作的视频:

http://www.tudou.com/programs/view/xexkePsAd_c/?resourceId=0_06_02_99

上述代码下载

PM2.5Fix

编译器是 Delphi 2010 + CPort VCL

上面说的都是优点,下面说点缺点,貌似我更喜欢负结果?
1. 可能是因为上面有电机的缘故,所以对于电流要求比较高。产品手册说最大电流为 120ma,官网说最大是200ma,实际启动时非常有可能比这个更大,我试图使用万用表测量,无法让其工作未果。因此,如果给Arduino使用,一定不能用Arduino取电,否则有不可预料的后果;我的解决方法是:USB充电宝先是接到一个 USB母头上取电,然后下来的电给G1,蓝牙供电,再直接进入Arduino Pro Micro的VCC;

2. 接口设计上有问题,我买到的那个接口不是很牢固,经常出现串口取不到数据反查一路才发现接口松动,如果能选用更粗大的排插效果会更好;

3. 整体上没有指示灯,无法判断当前工作状态。对于我来说出现问题时首先要把手放在模块上看看是否有震动来判断是否工作;、
4. 只在两个角落预留螺丝孔,如果整体多几个孔位,使用上会更方便;孔径应该是 M2,我手上的M2螺丝太短,没有办法插进去。

5. 手册提供的数据格式似乎有问题,刚开始我看的是卖家介绍网页,以为是卖家搞错了,后来翻了一下手册,手册也是这样写的:

image010

我实际获得的数据是这样的,每一笔串口数据以“BM” 开头,后面是数据,我实验发现 PM1.0 = Data[4]+(Data[5] shl 8) 才是正确值。如果用 (Data[4] shl 8) + (Data[5]) 计算结果明显不正确。【这个问题是Delphi数组下标从1开始导致的,如果你使用VC即可follow spec】刚开始实验我根据手册写成后面的这种形式,结果7000多,疑似身在帝都了;后来琢磨一下写成前面这种,结果为 32 (室内),感觉才正确;

6. 如同文章开头所说,和其他相比价格还是偏贵;

7. 资料偏少,没有官方资料【参考4】缺少寿命方面的详细数据,让人很难信服。特别是带有机械部分的电子设备,寿命通常会远低于电子部分,缺少数据让人很不放心;万一选用的是国产电机,你更无法判断什么时候会悲剧。

最后的总结:如果你是为了简单的研发目的,或者毕业设计,相比其他PM2.5传感器,这款使用简单,结果看起来很准确,非常值得推荐。但是如果你是为了稳定长期的工作,我认为有待时间检验。

参考:
1. http://shop115958317.taobao.com/index.htm?spm=a1z10.1-c.w5002-9767628871.2.jTEb6C 微创联合
2. http://shop110224467.taobao.com/index.htm?spm=2013.1.w5002-6755541327.2.ZK3XMi 树莓派一号店
3. 攀藤 G1 说明书
4. http://www.plantower.com/ 攀藤科技官网

2015年9月5日 放上来说明书 PantengGx

Step to UEFI (46) —– EFILOADEDIMAGEPROTOCOL的使用

上次介绍了如何在一个程序中直接调用另外的程序,那么在调用过程中是否有机会获得一些加载起来的EFI的信息呢?经过一番搜索,发现EFI_LOADED_IMAGE_PROTOCOL【参考1】。这个protocol的作用就是 “Can be used on any image handle to obtain information about the loaded image.”

elip

从定义上看,我们能够得到加载的Image的一些基本信息。在上次程序的基础上,添加一些代码来实验。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

extern EFI_BOOT_SERVICES           	 *gBS;
extern EFI_SYSTEM_TABLE				 *gST;
extern EFI_RUNTIME_SERVICES 		 *gRT;

extern EFI_SHELL_PROTOCOL            *gEfiShellProtocol;
extern EFI_SHELL_ENVIRONMENT2 		 *mEfiShellEnvironment2;

extern EFI_HANDLE					 gImageHandle;
/**
  GET  DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    return (mEfiShellEnvironment2->NameToPath(DeviceName));
  }

  return (NULL);
}

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_DEVICE_PATH_PROTOCOL 	*DevicePath;
  EFI_HANDLE				NewHandle;
  EFI_STATUS				Status;
  UINTN			ExitDataSizePtr;  
  CHAR16 					*R=L"HelloWorld.efi";
  EFI_LOADED_IMAGE_PROTOCOL	*ImageInfo = NULL;
  
  Print(L"File [%s]\n",R);

  DevicePath=ShellGetDevicePath(R);

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    gImageHandle,
    DevicePath,
    NULL,
    0,
    &NewHandle);  

  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during LoadImage [%X]\n",Status);
    return (Status);
  }

  Status = gBS -> HandleProtocol (
						NewHandle,
						&gEfiLoadedImageProtocolGuid,
						&ImageInfo
						);
  Print(L"ImageBase [%lX]\n",ImageInfo->ImageBase);
  Print(L"ImageSize [%lX]\n",ImageInfo->ImageSize);

  //
  // now start the image, passing up exit data if the caller requested it
  //
  Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during StartImage [%X]\n",Status);
    return (Status);
  }
  
  gBS->UnloadImage (NewHandle);  
  return EFI_SUCCESS;
}

 

特别注意,我们代码中需要使用这个Protocol的GUID,在INF中添加下面的引用即可。

[Protocols]
  gEfiLoadedImageProtocolGuid    

 

运行结果

elip2

可以看到显示的ImageSize就是 HelloWorld.efi的大小。

elip3

实验调用的代码比较特殊,如果直接调用CLIB编写的程序会导致错误。至于具体的原因,后续再进行研究。

实验的 HelloWorld.EFI 的代码在下面

#include <Uefi.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>


/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.  
  @param[in] SystemTable    A pointer to the EFI System Table.
  
  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  Print(L"Hello,World! \r\n");
  Print(L"www.lab-z.com \r\n");
  
  return EFI_SUCCESS;
}

 

对应的INF

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = HelloWorld
  FILE_GUID                      = 6987936E-ED34-44db-AE97-1FA5E4ED2116
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = UefiMain

#
# The following information is for reference only and not required by the build tools.
#
#  VALID_ARCHITECTURES           = IA32 X64 IPF EBC
#

[Sources]
  HelloWorld.c

[Packages]
  MdePkg/MdePkg.dec
  MdeModulePkg/MdeModulePkg.dec

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
  PcdLib

[FeaturePcd]

[Pcd]

 

实验完整代码下载

exec2
参考:

1.UEFI Spec 2.4 P265

Arduino 驱动74HC595 LCD模块

Arduino 驱动8位数码管模块

之前我们使用数码管都是直接使用【参考1】,缺点是电路复杂,占用引脚太多,稍微有点问题就会出现缺笔画的问题(测试的时候务必完整测试,否则很可能遇到你想显示8,结果出现6的状况)。最近入手了下面这个模块,上面通过74HC595芯片驱动LCD,接线很简单。我做了简单的实验,感觉挺方便的。

示例代码

    unsigned char LED_0F[] = 
    {// 0	 1	  2	   3	4	 5	  6	   7	8	 9	  A	   b	C    d	  E    F    -
    	0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x8C,0xBF,0xC6,0xA1,0x86,0xFF,0xbf
    };
    unsigned char LED[8];	//用于LED的8位显示缓存
    int SCLK = 10;
    int RCLK = 9;
    int DIO = 8; //这里定义了那三个脚
    
    void setup ()
    {
      pinMode(SCLK,OUTPUT);
      pinMode(RCLK,OUTPUT);
      pinMode(DIO,OUTPUT); //让三个脚都是输出状态
    }
    void loop()
    {
        LED[0]=1;
    	LED[1]=2;
    	LED[2]=3;
    	LED[3]=4;
        LED[4]=5;
    	LED[5]=6;
    	LED[6]=7;
    	LED[7]=8;
    	while(1)
    	{
    		LED8_Display ();
    	} 
      
    }
    
    void LED8_Display (void)
    {
    	unsigned char *led_table;          // 查表指针
    	unsigned char i;
    	//显示第1位
    	led_table = LED_0F + LED[0];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x01);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第2位
    	led_table = LED_0F + LED[1];
    	i = *led_table;
    	LED_OUT(i);		
    	LED_OUT(0x02);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第3位
    	led_table = LED_0F + LED[2];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x04);	
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第4位
    	led_table = LED_0F + LED[3];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x08);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);

    	//显示第5位
    	led_table = LED_0F + LED[4];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x10);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第6位
    	led_table = LED_0F + LED[5];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x20);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第7位
    	led_table = LED_0F + LED[6];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x40);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);        
    	//显示第8位
    	led_table = LED_0F + LED[7];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x80);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);        
    }
    
    void LED_OUT(unsigned char X)
    {
    	unsigned char i;
    	for(i=8;i>=1;i--)
    	{
    		if (X&0x80) 
                {
                  digitalWrite(DIO,HIGH);
                 }  
                else 
                {
                  digitalWrite(DIO,LOW);
                }
    		X<<=1;
                digitalWrite(SCLK,LOW);
                digitalWrite(SCLK,HIGH);
    	}
    }

 

卖家没有提供相应的代码,上述代码是我根据从其他地方找到的4位LED的代码基础上修改出来的。

工作照片:

595LED

附件是卖家提供的8 LED的资料和其他地方找到的 4 LED 的资料。

4LED

8LEDDS

参考:

1. http://www.lab-z.com/4digitial/ Arduino 四位数码管实验
2. http://www.lab-z.com/usb7seg/ USB 控制七段数码管
3. http://www.lab-z.com/usb-%E6%8E%A7%E5%88%B6%E4%B8%83%E6%AE%B5%E6%95%B0%E7%A0%81%E7%AE%A1ii/ USB 控制七段数码管(II)