Arduino 控制USB设备(2)硬件测试篇

这篇文章目标是让你知道你买的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
}

 

运行结果

usbspitest1

下面这个代码测试的是 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);
 
}

 

运行结果

usbspitest2

下面这个代码测试的是 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设备。

usbspitest3

最后,三个修改后的完整代码可以在这里下载:

usb2

经过上述测试,可以确定你的板子没问题。

Step to UEFI (51) —– EFI_Graphics_Output_Protocol获得基本信息

学习了一下如何获得 Shell 下当前的显示信息。通过 GraphicsOutputProtocol 来完成这个功能。这个 Protocol 在【参考1】 中有描述。

Capture

头定义在 \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模拟器中)

gopinfo

完整代码下载:

GFXTest

参考:

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.&lt;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

Arduino String的问题

比如我想从串口得到一个输入,然后再输出到串口上,然后写下面的程序

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() &gt; 0)
{
comdata += Serial.read();
delay(2);
}
if (comdata.length()&gt;1) {Serial.println(comdata); comdata="";}

}

 

但是惊奇的发现,结果是下面这样。”1″ 对应为 “49”两个字符;“2”对应为 “50”两个字符………

dd

如果想得到想要的结果,需要做一个强制转换

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() &gt; 0)
{

comdata +=(char)Serial.read();
delay(2);
}
if (comdata.length()&gt;1) {Serial.println(comdata); comdata="";}

}

 

这样的结果就符合预期

ee

感谢极客工坊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 看到

Step to UEFI (50) —– 实现一个简单的菜单功能

屏幕显示功能搭配取按键信息的功能可以实现一个简单的菜单,用户可以使用上下键移动后选项,回车确认,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;
}

 

运行结果:

smenu

工作视频:

http://www.tudou.com/programs/view/R_932wqGuYw/?resourceId=414535982_06_02_99

完整的代码下载:

Menu

达文西的灯

前几天,偶然知道周星驰《国产凌凌漆》英文名称是“From Beijing with Love”。然后又去重温了一遍。

image001

零零七系列一定要有各种无厘头的道具,比如:要你命3000

image003

还有就是经典的太阳能手电。于是,仿造一个用打火机才能“点亮”的小灯。用到之前的MOS模块,小灯泡,还有就是火焰传感器。

image006

这种传感器的原理是:检测火焰或者波长在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

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

攀藤 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

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)

推荐绘制电路图的软件 TinyCAD

之前我尝试用 Fritzing 绘制短路图一段时间。这个软件有直观方便的特点。问题是大多数我用到的原件他的库中没有,如果自己绘制的话非常复杂,修改元件管脚也异常麻烦。

http://sourceforge.net/projects/tinycad/

tinycada

用这个很容易就绘制一个 Arduino Pro Micro的电路

tinycadb

对应文件可以在这里下载
Arduino-Pro-Micro