Leonardo改装为USB读卡器

前面介绍了如何用 Leonardo 实现SD卡的读写,这次介绍如何实现将其改装为 USB SD 卡读卡器。

经过搜索在 http://elasticsheep.com/2010/04/teensy2-usb-mass-storage-with-an-sd-card/  给出了一个代码。结合上面提到的硬件可以完成功能(CS Pin 直接接地,此外其他连接相同)。

使用 C:\WinAVR-20100110\进行编译:

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC>cd\
C:\>cd C:\WinAVR-20100110\SDCardReader
C:\WinAVR-20100110\SDCardReader>setenv.bat
C:\WinAVR-20100110\SDCardReader>PATH=C:\WinAVR-20100110\bin;C:\WinAVR-20100110\utils\bin;C:\Windows\System32\wbem
C:\WinAVR-20100110\SDCardReader>cd "LowLevelMassStorage+SD"
C:\WinAVR-20100110\SDCardReader\LowLevelMassStorage+SD>make
-------- begin --------
avr-gcc (WinAVR 20100110) 4.3.3
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


Size before:
AVR Memory Usage
----------------
Device: atmega32u4
Program:    9094 bytes (27.8% Full)
(.text + .data + .bootloader)
Data:        712 bytes (27.8% Full)
(.data + .bss + .noinit)
Checking for invalid events...
---- Compile Time Library Options ----
USB_DEVICE_ONLY
FIXED_CONTROL_ENDPOINT_SIZE=8
FIXED_NUM_CONFIGURATIONS=1
USE_FLASH_DESCRIPTORS
USE_STATIC_OPTIONS=(USB_DEVICE_OPT_FULLSPEED | USB_OPT_REG_ENABLED | USB_OPT_AUTO_PLL)
INTERRUPT_CONTROL_ENDPOINT
--------------------------------------
--------- Target Information ---------
AVR Model: atmega32u4
Board: USER
Clock: 16000000Hz CPU, 16000000Hz Master
--------------------------------------
Size after:
AVR Memory Usage
----------------
Device: atmega32u4

Program:    9094 bytes (27.8% Full)
(.text + .data + .bootloader)
Data:        712 bytes (27.8% Full)
(.data + .bss + .noinit)
-------- end --------

编译之后生成 MassStorage.hex 文件,我使用Arduino的上传方式进行烧写,烧写之前按下 reset  button 然后马上运行,特别注意根据你自己的实际情况选择COM,我这里是 COM7 (意思是按下 Reset 之后出现的 Bootloader 的COM号):

D:\arduino-1.8.4\hardware\tools\avr\bin\avrdude -v -Cd:\arduino-1.8.4\hardware\tools\avr\etc\avrdude.conf -patmega32u4 -cavr109 -PCOM7 -b57600 -D -V -Uflash:w:./MassStorage.hex:i

烧写之后就可以正常工作了。

写入速度

读取速度能快一点

完整的代码和Lufa 下载(根据我的测试只有特定的 Lufa版本LUFA_091223才能通过编译,所以放在一起了,另外,为了减小体积我删除了 Lufa 里面的 Demo)。

Step to UEFI (89) 内存访问

初学 Watcom C的时候遇到一个问题“如何访问指定的内存”。当时为了这个问题花费了不少功夫,最后才发现直接用指针就可以进行访问,因为过于简单以至于网上都没有人问过…….
最近看 UEFI 编程,同样也遇到了这个问题,我去查找了 mm命令的source code,看到了它使用了PCI Root Bridge I/O Protocol 这个Protocol,然后就去研究之。同样越研究越迷糊,最终发现虽然这个Protocol提供了内存访问函数,但是本质上依然使用指针来直接访问。出于保护模式下的内存,任何其他方法都有“脱了裤子放屁-----多此一举”之嫌。
参考 mm 源程序,很快写出代码:

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

#include <Library/MemoryAllocationLib.h>
#include  <Protocol/DeviceIo.h>

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

typedef enum {
  EfiPciWidthUint8,
  EfiPciWidthUint16,
  EfiPciWidthUint32,
  EfiPciWidthUint64
} DUMMY;

VOID
ReadMem (
  EFI_IO_WIDTH  Width,
  UINT64        Address,
  UINTN         Size,
  VOID          *Buffer
  )
{
  do {
    if (Width == EfiPciWidthUint8) {
      *(UINT8 *) Buffer = *(UINT8 *) (UINTN) Address;
      Address -= 1;
    } else if (Width == EfiPciWidthUint16) {
      *(UINT16 *) Buffer = *(UINT16 *) (UINTN) Address;
      Address -= 2;
    } else if (Width == EfiPciWidthUint32) {
      *(UINT32 *) Buffer = *(UINT32 *) (UINTN) Address;
      Address -= 4;
    } else if (Width == EfiPciWidthUint64) {
      *(UINT64 *) Buffer = *(UINT64 *) (UINTN) Address;
      Address -= 8;
    } else {
      Print(L"Can't read memory at %X",Width);
      break;
    }
    //
    //
    //
    Size--;
  } while (Size > 0);
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	CHAR8		*Mem1;
	UINT32		Buffer;

	Mem1=AllocatePool(4);
	*(Mem1+0)='L'; 
	*(Mem1+1)='A';
	*(Mem1+2)='B';
	*(Mem1+3)='Z';
	Print(L"Memory Address: %X\n",Mem1);
	Print(L"%X\n",*Mem1);
	Print(L"%X\n",*(Mem1+1));
	Print(L"%X\n",*(Mem1+2));
	Print(L"%X\n",*(Mem1+3));
	
	ReadMem(EfiPciWidthUint32, (UINT64)Mem1, 1, &Buffer);
	
	Print(L"Read[%X]=%X\n",Mem1,Buffer);
	
	FreePool(Mem1);
	
  return EFI_SUCCESS;
}

 

在NT32模拟器中实验时发现,模拟环境中不是所有的内存空间都是可以直接访问的。稍有不慎就会得到错误信息,模拟器也会随之崩溃。于是,代码是创建一个4 Bytes长的内存空间,写入一些字符,然后再 ReadMem 读取出来,这样做能够保证访问的内存是可以被正常操作的。

运行结果:

ma

参考的 mm.c 来自EfiShell 1.06\Shell\mm\mm.c。
mm

本文提到的完整代码下载
ReadMEM

UDK2015 + Windows 7

UDK2015 出来一段时间了,之前的文章也介绍过【参考1】。只是有一个严重的问题: UDK2015 下面 C编写的工具是高版本的 Visual Studio 编译的,并且没有设置对于 XP 的兼容,于是 XP 下面无法直接使用这些工具。一种解决的办法是重新编译用来build的工具,另外一种就是更换你的操作系统为 Windows 7。

周末花了一点时间在虚拟机中安装了一个 Windows7 然后配置了 Vs2008和 UDK2015 ,于是目前可以做到虚拟机下面的 UDK2015 的编译了。有需要的朋友可以直接下载,安装 Virtual Box 之后就可以直接使用。

链接:http://pan.baidu.com/s/1jIfdzhk 密码:x04z

2016/06/10 补充

今天尝试在上面安装 EADK 发现直接编译不过,后来比较了一下文件发现 UDK2015 ShellPkg中的一些Pkg被删掉了,AppPkg.dsc 中有用到。我尝试从 UDk2014 中直接比较补充了那几个 Pkg,编译能够通过。不过这样的话,建议有需要的朋友同时安装 UDK2014。

参考:
1.http://www.lab-z.com/udk2015/ UDK2015来了

Step to UEFI (87) EFI_UNICODE_COLLATION_PROTOCOL

查看UEFI下的大小写转换函数的时候,偶然发现了EFI_UNICODE_COLLATION_PROTOCOL【参考1】提供了几个有意思的函数。

u1

具体的头文件定义在 \MdePkg\Include\Protocol\UnicodeCollation.h

///
/// The EFI_UNICODE_COLLATION_PROTOCOL is used to perform case-insensitive 
/// comparisons of strings. 
///
struct _EFI_UNICODE_COLLATION_PROTOCOL {
  EFI_UNICODE_COLLATION_STRICOLL    StriColl;   
  EFI_UNICODE_COLLATION_METAIMATCH  MetaiMatch;
  EFI_UNICODE_COLLATION_STRLWR      StrLwr;
  EFI_UNICODE_COLLATION_STRUPR      StrUpr;

  //
  // for supporting fat volumes
  //
  EFI_UNICODE_COLLATION_FATTOSTR    FatToStr;
  EFI_UNICODE_COLLATION_STRTOFAT    StrToFat;
  
  ///
  /// A Null-terminated ASCII string array that contains one or more language codes.
  /// When this field is used for UnicodeCollation2, it is specified in RFC 4646 format.
  /// When it is used for UnicodeCollation, it is specified in ISO 639-2 format.
  ///
  CHAR8                             *SupportedLanguages;
};

 

根据介绍,大概的介绍一些功能(如果你发现有错误,欢迎eMail指出)
EFI_UNICODE_COLLATION_STRICOLL StriColl; //大小写不敏感的比较函数
EFI_UNICODE_COLLATION_METAIMATCH MetaiMatch; //正则表达式匹配
EFI_UNICODE_COLLATION_STRLWR StrLwr; //字符串转小写
EFI_UNICODE_COLLATION_STRUPR StrUpr; //字符串转大写
EFI_UNICODE_COLLATION_FATTOSTR FatToStr; //8.3格式的OEM定义字符文件名转String
EFI_UNICODE_COLLATION_STRTOFAT StrToFat; //String转8.3格式的OEM定义字符
CHAR8 *SupportedLanguages; //列出当前系统支持的语言代码

之后,根据上面的介绍,编写一个测试例子:

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

#include <Protocol/UnicodeCollation.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_STATUS	Status;
	EFI_UNICODE_COLLATION_PROTOCOL *mUnicodeCollation;
	CHAR16	*TestStr=L"wWw.LaB-z.cOm";
	CHAR16  *Pattern1=L"w*";
	CHAR16  *Pattern2=L"*z.c*";
	CHAR16  *Pattern3=L"c*";
	
	Status = gBS->LocateProtocol(
				&gEfiUnicodeCollation2ProtocolGuid, 
				NULL, 
				&mUnicodeCollation);
    if (EFI_ERROR (Status)) {
      Print(L"Can't Locate Protocol\n");
      return Status;
    }
	
	mUnicodeCollation->StrLwr(mUnicodeCollation,TestStr);
	Print(L"%s\n",TestStr);
	
	mUnicodeCollation->StrUpr(mUnicodeCollation,TestStr);
	Print(L"%s\n",TestStr);

	Print(L"%d\n",(mUnicodeCollation->
					MetaiMatch(mUnicodeCollation,
					   TestStr,
					   Pattern1)));

	Print(L"%d\n",(mUnicodeCollation->
					MetaiMatch(mUnicodeCollation,
					   TestStr,
					   Pattern2)));

	Print(L"%d\n",(mUnicodeCollation->
					MetaiMatch(mUnicodeCollation,
					   TestStr,
					   Pattern3)));					   
  return EFI_SUCCESS;
}

 

运行结果:
u2

其中测试了大小写转换不必细说,多说两句关于正则表达式的用法:

	CHAR16	*TestStr=L"wWw.LaB-z.cOm";
	CHAR16  *Pattern1=L"w*";
	CHAR16  *Pattern2=L"*z.c*";
	CHAR16  *Pattern3=L"c*";

 

其中 “*” 表示匹配一个或者任意多个字符, Pattern1 表示的是“以w开头的字符串”;Pattern2 表示的是“中间含有 z.c 字符的字符串”;Pattern3 表示的是“以c开头的字符串”。最终运行结果如下:

完整的代码下载:
UnicTest

参考:
1. UEFI 2.4 P592

Step to UEFI (86) StartImage 加载CLib程序的解决方法

前面【参考1】提到了 StartImage 加载 CLib 编写Application 出错的原因,这篇文章介绍如何解决这个问题。
根据原因来看是因为找不到提供 Parameters 的Protocol,那么我们在调用之前给被加载的Application 装上需要的Protocol即可。安装 Protocol 需要用到 InstallProtocollInterface,具体定义如下【参考2】:
image001

欲安装的 Protocol 实例则是从加载程序(Exec6)上面取下来的。

没有多少人愿意看大篇幅的代码,我这里列下最关键的部分:

首先,取出当前的 Shell Interface, 不同的环境下还可以使用 Shell Parameter Protocol , NT32 环境下只支持前者

//如果你在实体机上发现有问题,那么可以考虑这段代码的问题
  Status = gBS->OpenProtocol(gImageHandle,
                             &gEfiShellInterfaceGuid,
                             (VOID **)&EfiShellInterface,
                             gImageHandle,
                             NULL,
                             EFI_OPEN_PROTOCOL_GET_PROTOCOL
                            );
  if (EFI_ERROR(Status)) {
	Print(L"Shell Parameters Protocol not Found!\r\n",Status);	
    return (Status);	
  } 
//之后,将取下来的 Protocol 安装给被加载的 Application
  Status = gBS->InstallProtocolInterface (
            &NewHandle,
            &gEfiShellInterfaceGuid,
            EFI_NATIVE_INTERFACE,
            EfiShellInterface
           ); 
  if (EFI_ERROR(Status)) {
	Print(L"Protocol Interface Installed fail!\r\n",Status);
	return (Status);
  } 	

 

最后,安装之后不能忘记 Uninstall,还要调用一下,特别注意第一个参数传递的不是指针。

image003

运行结果,可以看出 Hello1和 Hello2都可以被正常加载运行:

image005

看到这里,这篇文章就可以结束了,下面列出 Exec6 的代码:

#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;
    EFI_SHELL_INTERFACE           *EfiShellInterface=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);
							  
  Status = gBS->OpenProtocol(gImageHandle,
                             &gEfiShellInterfaceGuid,
                             (VOID **)&EfiShellInterface,
                             gImageHandle,
                             NULL,
                             EFI_OPEN_PROTOCOL_GET_PROTOCOL
                            );
  if (EFI_ERROR(Status)) {
	Print(L"Shell Parameters Protocol not Found!\r\n",Status);	
    return (Status);	
  } 

  Status = gBS->InstallProtocolInterface (
            &NewHandle,
            &gEfiShellInterfaceGuid,
            EFI_NATIVE_INTERFACE,
            EfiShellInterface
           ); 
  if (EFI_ERROR(Status)) {
	Print(L"Protocol Interface Installed fail!\r\n",Status);
	return (Status);
  } 		   
 
  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);

  Status = gBS->UninstallProtocolInterface (
            NewHandle,
            &gEfiShellInterfaceGuid,
            EfiShellInterface
           ); 
  if (EFI_ERROR(Status)) {
	Print(L"Protocol Interface Uninstalled fail!\r\n",Status);
	return (Status);
  } 
  
  gBS->UnloadImage (NewHandle);  

  Print(L"NewHandle [%lX]\n",NewHandle);  
  
  return EFI_SUCCESS;
}

 

完整代码下载:
exec6

至此,终于回答了 StartImage 执行Application 的问题,如果你发现本文有任何问题欢迎给我留言,或者你有什么其他问题,同样可以给我发 e-Mail。

就是这样。

参考:
1. http://www.lab-z.com/stu85/ StartImage CLib
2. Uefi Spec 2.4 P153

如何在Processing中导入Opencv库

国内玩 Processing 的就很少,玩 OpenCv的更少,有人问到了这里我抽空研究了一下。

首先,要下载OpenCv for Processing,官方网站是https://github.com/atduskgreg/opencv-processing。我是在这个页面下载的https://github.com/atduskgreg/opencv-processing/releases 0.5.2 的版本。
下载之后直接打开是这样的:

image001

然后,还是和之前文章【参考1】提到的安装方式相同,检查 Preferences 设定,特别提醒,目录不可以有空格或者中文。

image003

之后,打开d:\prcdir\libraries 目录,把前面的全部内容都丢进去

image005
再次打开 processing ,library目录下出现 OpenCv 即正确。

image007

可以直接运行这个库自带的各种例子。
我上传了本文提到的库到 baidu云上,有需要的朋友可以下载,如果有问题也可以直接给我发eMail。
链接:http://pan.baidu.com/s/1jHSPV74 密码:hanc
参考:
1. http://www.lab-z.com/promodel/ Processing导入模型

介绍一个MP3模块

之前入手了一个 MP3 播放模块(名称是 Arduino TTL串口语音模块 Mini Voice MP3语音音乐播放器),自带 SD 卡槽还有小喇叭。

image002

image004

image006

指令:
1.播放 play,0001,$
2.播放/暂停 pap,$,pla,$
3.停止播放 stop,$
4.上一曲 previous,$
5.下一曲 next,$
6.音量加 vol+,$
7.音量减 vol-,$
8.音量大小 vol,A,$ (从 vol,1,$到vol,F,$ 十六个级别)
9复位模块 reset,$
10.波特率设置 baud,9600,$ (支持 1200,2400,4800,7200,9600,14400,19200,38400,57600,115200 十种)
测试程序:

boolean pause=true;

void setup() {
  Serial.begin(9600);
  Serial1.begin(9600);
  while (!Serial) 
    {;}
}

void loop() {
  Serial.println("(0) Play 0001");
  Serial.println("(1) Play 0002");
  Serial.println("(2) Play/Pause");
  Serial.println("(3) Stop");
  Serial.println("(4) Previuos");  
  Serial.println("(5) Next"); 
  Serial.println("(6) Volume +");  
  Serial.println("(7) Volume -");  
  Serial.println("(9) Reset");  
  Serial.println("");

  while (!Serial.available()) {;}

  switch (Serial.parseInt())
  {
      case 0:  Serial1.print("play,0001,$");      
      case 1:  Serial1.print("play,0002,$"); break;
      case 2:  if (pause==true) {
                    Serial1.print("pla,$");
                   }
               else {
                    Serial1.print("pap,$");
                   }
               pause=!pause;     
                break;
      case 3:  Serial1.print("stop,$");break;
      case 4:  Serial1.print("previous,$");break;
      case 5:  Serial1.print("next,$");break;
      case 6:  Serial1.print("vol+,$");break;
      case 7:  Serial1.print("vol-,$");break;
      case 9:  Serial1.print("reset,$");break;  
      default: Serial1.println("Menu item does not exist.");
  }
}

 

运行结果

image008

另外,模块上面带有USB接口,然后可以将 SD 卡插在上面,用USB线直接连接到电脑上即可充当读卡器。测试中我将 MP3分别命名为 0001.MP3 0002.MP3 0003.MP3。
image010

参考:
1. https://item.taobao.com/item.htm?spm=a1z09.2.0.0.iedIEI&id=45599999495&_u=ckf8s90790 Arduino TTL串口语音模块 Mini Voice MP3语音音乐播放器

Step to UEFI ----- TIPs

每次编译 Application 之后生成的 efi 文件都在

\Build\AppPkg\DEBUG_MYTOOLS\IA32\AppPkg\Applications\APPNAME\APPNAME 这样的目录中,每次需要手工 copy 到虚拟出来的目录下,这样比较麻烦。

经过研究发现可以在 \AppPkg\AppPkg.dsc 中做如下修改

#OUTPUT_DIRECTORY = Build/AppPkg
OUTPUT_DIRECTORY = Build/NT32IA32

这样,每次最后生成 efi 文件之后就会有一个自动 copy 的动作。

tips

processing中如何实现淡入一幅图像

简单的说就是加载图像,在显示的时候使用tint(gray,alpha) 不断调整 alpha 让他越来越不透明。

例子如下:

PImage img;
int i=0; 

void setup() {
size(640, 360);
img = loadImage("moonwalk.jpg"); // Load an image into the program 
}

void draw() { 
  tint(255, i++); // Display at half opacity
  image(img, 0, 0);
  if (i==40) {
    delay(5000);
    exit();
  }
  delay(200);
}

完整例子下载:

Transparency

用示波器“看” arduino (3) --- PWM

Arduino 上面有模拟到数字(ADC)的采样功能但是没有数字到模拟的输出(DAC),在要求不是特别高的情况下 PWM 充当这一角色。

首先需要知道的是:不是所有的Pin都可以用来输出PWM,根据【参考1】,在Uno上的 3 5 6 9 10 和11才可以用来输出PWM.

image001

编写一个简单的程序来调整占空比

int  n=255;
const int PWMPin=6;

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 (n>255) {n=0;}
       if (n<0) {n=255;}   
       analogWrite(PWMPin,n); 
       Serial.println(n);

    }
}

我们使用 DFRobot 出品的 RoMeoBLE V1.0 作为实验器材。

占空比 250 时 , 250/255=98.039% ,测量值符合预期

image006

占空比 200 时 ,200/255=78.431% ,测量值符合预期

image003

占空比为0 时是一条低电平的直线,占空比255是一条高电平的直线,这里就不贴上来了。

不过特别注意到测量出来的频率是976.5Hz, 我更换了一个普通的UNO 结果还是 976HZ。这和很多资料中提到的 490Hz不同,经过研究,在【参考2】上找到了介绍,原来 D5 D6 的默认频率和其他的不同。换成 Pin6 修改上面的程序,看到的就是 490Hz了。

image004

同样,根据【参考2】改一下频率,可以看到当前是 31.36884 KHz

int  n=255;
const int PWMPin=9;

void setup()
{
    Serial.begin(9600);
    TCCR1B = TCCR1B & B11111000 | B00000001;    // set timer 1 divisor to 1 for PWM frequency of 31372.55 Hz    
    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 (n>255) {n=0;}
       if (n<0) {n=255;}   
       analogWrite(PWMPin,n); 
       Serial.println(n);

    }
}

调整占空比为 245 时的样子

image005

放大一点看看具体波形

image006

参考:
1. http://www.diyleyuan.com/index.php?m=content&c=index&a=show&catid=29&id=616
2. https://arduino-info.wikispaces.com/Arduino-PWM-Frequency
pwma
3. http://playground.arduino.cc/Code/PwmFrequency 关于调整频率官方的介绍
4. https://www.arduino.cc/en/Tutorial/SecretsOfArduinoPWM Secrets of Arduino PWM
http://www.diy-robots.com/?p=852 上面文章的翻译
http://www.diy-robots.com/?p=814 上面文章的翻译