Step to UEFI (15) —– 命令行参数 Again

前面介绍过 UEFI 下获得命令行参数的方法。这次尝试用 C Lib 来实现。

需要获取的参数直接用下面的代码进行输出

for (i=0;i<Argc; i++)
{
printf(“Arg[%d]: %s\n”,i,Argv[i]);
}

惊奇的发现,只能输出每个参数的第一个字母,一下子蒙了。琢磨了好长时间,忽然想起来这个应该是 unicode导致的。正常情况下

“abc”这样的ascii在unicode中会被存为 “97 00 98 0 99 0”,遇到 00 printf 自动就给截断了

顺手查了一下 \StdLib\Include\stdio.h ,其中提到了这个事情

If an l length modifier is present, the argument shall be a
pointer to the initial element of an array of wchar_t type. Wide
characters from the array are converted to multibyte characters
(each as if by a call to the wcrtomb function, with the conversion
state described by an mbstate_t object initialized to zero before
the first wide character is converted) up to and including a
terminating null wide character. The resulting multibyte characters
are written up to (but not including) the terminating null
character (byte). If no precision is specified, the array shall
contain a null wide character. If a precision is specified, no more
than that many bytes are written (including shift sequences, if
any), and the array shall contain a null wide character if, to
equal the multibyte character sequence length given by the
precision, the function would need to access a wide character one
past the end of the array. In no case is a partial multibyte
character written.

那我再试试

for (i=0;i<Argc; i++)
{
printf(“Arg[%d]: %ls\n”,i,Argv[i]);
}

结果依旧。不知道为什么了,如果有了解的朋友可以给我写邮件告诉我。

好在我们还有 Print 和 wprintf 可以绕过去。最后写了一个程序,主要代码如下

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

#include  <stdio.h>
#include <wchar.h>
/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  int i;
  printf("You have input %d args\n",Argc);

  printf("\nExp1. If we use \" printf(\"Arg[%%d]: %%s\\n\",i,Argv[i]);\n");
  for (i=0;i<Argc; i++)  
   {
	printf("Arg[%d]: %s\n",i,Argv[i]);
   }
  
  printf("\nExp2. If we use \" printf(\"Arg[%%d]: %%ls\\n\",i,Argv[i]);\n");
  for (i=0;i<Argc; i++)  
   {
	printf("Arg[%d]: %ls\n",i,Argv[i]);
   }
   
  printf("\nExp3. If we use \" Print(L\"Arg[%%d]: %%s\\n\",i,Argv[i]);\n");
  for (i=0;i<Argc; i++)  
   {
	Print(L"Arg[%d]: %s\n",i,Argv[i]);
   }

  printf("\nExp4. If we use \" wprintf(L\"Arg[%%d]: %%s\\n\",i,Argv[i]);\n");
  for (i=0;i<Argc; i++)  
   {
	wprintf(L"Arg[%d]: %ls\n",i,Argv[i]);
   }   
  return EFI_SUCCESS;
}

 

最终的运行结果

argv

代码下载:Main (和前面几篇文章一样,请用 AppPkg 编译)

Step to UEFI (14) —– EADK clock()

除了前面介绍的tm struct 和 time_t定义,标准C的库还有clock_t的定义。相比前面两者,这个是一个更加单纯的“计时”定义,前两者更像是日期时间的定义。

对于 clock_t ,在 EadkPkg_A2\StdLib\Include\time.h 有如下定义

/** An arithmetic type capable of representing values returned by clock(); **/
#ifdef _EFI_CLOCK_T
  typedef _EFI_CLOCK_T  clock_t;
  #undef _EFI_CLOCK_T
#endif

 

在 StdLib\Include\sys\EfiCdefs.h 有下面的定义

#define _EFI_CLOCK_T      UINT64

 

和 clock_t 配合的还有CLOCKS_PER_SEC ,定义了一秒中的Clock数

EadkPkg_A2\StdLib\Include\time.h

#define CLOCKS_PER_SEC  __getCPS()

 

这个函数的具体实现在 EadkPkg_A2\StdLib\LibC\Time\Time.c

clock_t
EFIAPI
__getCPS(void)
{
  return gMD->ClocksPerSecond;
}

 

函数 clock() 的作用是取得从程序开始运行的处理器时钟数。特别需要注意:在NT32模拟环境中和实际环境中,这个函数是不同的。之前提到过,如果想在NT32环境中正常使用C Library,必须在AppPkg.dsc中做一些修改

[LibraryClasses.IA32]
  #TimerLib|PerformancePkg/Library/DxeTscTimerLib/DxeTscTimerLib.inf
  ## Comment out the above line and un-comment the line below for running under Nt32 emulation.
  TimerLib|MdePkg/Library/BaseTimerLibNullTemplate/BaseTimerLibNullTemplate.inf

 

当然,修改之后,能在NT32环境中运行了,但是无法取得clock值。输出始终为 0。

clock1

为了验证,我尝试在实际环境下测试。首先是要将上面的设定恢复原装。恢复完之后无法通过编译,需要修改成下面的样子(下面ORG给出的是原来的路径)。

[LibraryClasses.IA32]
  #ORG TimerLib|PerformancePkg/Library/DxeTscTimerLib/DxeTscTimerLib.inf
  TimerLib|PerformancePkg/Library/TscTimerLib/DxeTscTimerLib.inf
  ## Comment out the above line and un-comment the line below for running under Nt32 emulation.
  #TimerLib|MdePkg/Library/BaseTimerLibNullTemplate/BaseTimerLibNullTemplate.inf

 

之后,我用WinISO做了一个 ISO镜像,将main.efi放在iso文件中,直接挂接到virtualbox的虚拟机中(4.3.10 r93012),发现运行之后无任何输出.

clock2

拷贝到实际机器上运行,看起来结果正常:

clock

这次实验使用的代码如下,依然是从 Demo 的Main.c中修改而来

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
//#include  <Uefi.h>
//#include  <Library/UefiLib.h>
//#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <time.h>

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  int i;
  printf("Sizeof Clock_T %d\n",sizeof(clock_t));

  printf("CLOCKS_PER_SEC %d\n",CLOCKS_PER_SEC);

  printf("Start %d\n",clock());

  for (i=1;i<0xFFFF; i++)  
   {
   }

  printf("End %d\n",clock());

  return EFI_SUCCESS;
}

 

依旧是使用 AppPkg 的环境进行编译。

代码下载

Main

编译的可以在实际机器上运行的EFI文件

Main_Actual

根据上面的 CLOCKS_PER_SEC 我们同样能做出来一个测算当前CPU频率的程序。同时我们也完全可以写出一个 delay 函数(我在C 标准库中查看了一下,惊奇的发现这个函数并非标准函数……)

为了总结关于时间的库函数,总结下面这个图表

time lib

参考:
1.http://ganquan.info/standard-c/function/clock

红外无线温度计

前一段,给Arduino做了一个外壳。材料是有机玻璃再加上 3mm 的铜柱螺丝和螺母的组合,做出来效果还不错。最明显的就是放到论坛上会有很多人关注和讨论。相比OpenCV统计骰子点数的装置,这个盒子的人气高很多。【参考1】

淘宝上有更专业的Arduino外壳,只是价格比较贵。有兴趣的朋友可以像我这样,买一块有机玻璃,再买一把各种螺钉螺母的。

shellarduino

果壳网近期有个手机装置设计大赛,翻翻箱子看看手上有什么能够用起来的东西。最后决定做一个“无线温度计”。除了上面的外壳,还有如下材料:

Arduino Uno
TN901 红外温度传感器
蓝牙模块

简单介绍一下 TN901 ,实物如图(实际我使用的比下面的长一截,多出来一段电路板,不知道是不是考虑到太短了用起来容易焊接不亮,或者容易头重脚轻)

tn901

上图从左到右分别是 A:测试 G:地 C:时钟 D:数据 V:电源【参考】。从另外一个方向看过去,顺序刚好颠倒。用的时候先看一下电路板上哪里是 A 哪里是 V 即可确认方向。刚开始的时候我就接反了,不工作,吓得我一身冷汗(这个小东西要 150元,是我一个月购买材料budget。开发品就是贵啊。相比之下,淘宝上还有50元的测温枪,不同的传感器而已)
论坛上有人之前做过【参考1】,有现成的库可以使用【参考2】,方便啊!
连接上蛮简单,VCC供电(建议选 5V),GND接好之后,剩下三根线,根据库初始化时的提示分别将 Data CLK ACK 连接到 Pin 13 12 11。连接好之后,在串口就能看到数据了。

下面再引入蓝牙模块。入手的是专门Arduino 使用的。简单的说Arduino蓝牙模块=单板+底板。而我刚开始入手的是单板。入手单板之后有点傻眼:小是够小,但是没引出来需要的Pin脚。最后还是买了一个专门 Arduino 使用的了事。

有专门的 TN901的库,因此 Arduino 程序编写要简单的多。

#include <Arduino.h> 
#include <Wire.h>
#include "TN901.h"  //引用库文件
TN901 tn;           //实 例 化
void setup()
{
    Serial.begin(9600);
    tn.Init(13,12,11);  //初 始 化 data clk ack
}
void loop()
{
   tn.Read();
   SerialValue();
   delay(2000);
}

void SerialValue()
{
   String s;
   s=">O"+String(tn.OT, DEC)+"E"+String(tn.ET,DEC)+"<"; 
   Serial.println(s);
   //Serial.print(tn.OT, DEC);
   //Serial.print("ET");
   //Serial.print(tn.ET, DEC);
}

 

出来的两个温度分别是目标温度(tn.OT)和当前的环境温度(tn.ET)。上面的程序将结果放在一个下面这样 >Oxx.xxEyy.yy< 的字符串中发送到串口上。再通过串口蓝牙,我们的电脑就能够收到结果了。

dPsth1-sLLasZ1cLCQ3rMfmbswWAUts6clUKxhVVkLTRAQAAZwIAAFBO

gzpTD5eLLIU02MC-PJLVKXZdGj_FxCdSvfSsH1W16H6QBwAAIAoAAEpQ

供电部分使用的是一个USB充电宝,内部是一节 16580 电池。体积适中,价格也不贵。

FkSGBq2NsJR46BrDWXQG6geid3rNpRCUh42Eeq5dNSqAAgAA4AEAAEpQ

电脑端,使用Delphi 设计了一个简单的界面,用类似任务管理器的界面来展示当前的和部分的历史数据

x4k4h5l-_l7mJqz3KqnSmvqPMmoxITAwhYTGsStQ874BBgAAhAMAAFBO

为了方便起见同时还使用了 CPort VCL 来作为串口通讯。但是个人感觉不是很稳定,因此有机会还是直接使用 API 爱进行通讯比较好。程序下载 IRTemp。Arduino相关库下载:tn9

最后还拍摄了一个应用的演示视频。

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

参考:
1. http://www.geek-workshop.com/thread-10726-1-1.html用有机玻璃做个arduino外壳

2. http://www.geek-workshop.com/thread-8727-1-1.html DIY红外非接触式温度计

3. http://www.geek-workshop.com/thread-1884-1-1.html如何自己编写Arduino支持的C++类库

4. http://www.zytemp.com/products/tn901.asp Tn9 usermanual

libusb(1)

最近看了一下 libusb 这个开源库(Win32)。然后发现LibUsbDotNet给出了一个USB速度测试Demo,包括软件和Firmware部分。对我来说,最感兴趣的还是 USB Firmware,于是乎研究了一番。他的代码在 \LibUsbDotNet\Src\Benchmark\Firmware 下面。从文档中来看,代码基于PIC的 USB 单片机 Demo 板的,又搜了一下对应开发板的价格…….然后我们谈一下其他的吧…….

PIC系列的单片机是 MicroChip 公司开发的系列产品,覆盖范围非常广。考察下来的结论是:PIC属于那种非常适合公司开发的单片机,是那种有钱所有都有的(开发板,Debug,编辑器,编译器一条龙都能下来),美中不足的就是需要钱,烧写都需要专门的工具(也不得不说他的工具非常强大)。但是对于我这样的,很多时候只是三分钟热血看一眼而已,每月预算有限的爱好者只是看看吧。

然后就开始阅读代码了。但是很奇怪的是,代码似乎不完整。又下载了一个 MPLAB的编译器。经过一个晚上的研究发现:缺少USB Stack。又去找这些东西,最后发现MicroChip会将用到的代码 Publish出来。之后我下载了一个最新的,补充到 LibUsbDotNet 给出的代码中,编译之后发现不通过。查找了一下,应该是我下载的库版本太新,而LibUsbDotNet Firmware用的是老库导致的…….最后用的是 MCHP_App_Lib_v2010_10_19_Installer.zip 。即可正常编译,虽然无法运行,但是理论上不缺代码了。

编译结果:

Capture

编译的Source Code,有板子的朋友可以烧一下看看是否正常

PICUSB

OpenCV在捕获的摄像头上写字

这个实验和之前的画圆的类似,先捕捉图像,然后再上面输出文字。

#include "stdafx.h"
#include "highgui.h"
#include "cv.h"
#include "stdio.h"
#include <ctype.h>
#include <time.h>

int main(int argc,char ** argv)
{
	time_t	t;
	char showMsg[256];

	//the font variable    
    CvFont font; 

       CvCapture *capture = 0;

	   //从摄像头读取
       capture = cvCaptureFromCAM(0);

 
       if(!capture)
       {
              fprintf(stderr,"Could not initialize capturing...\n");
              return -1;
       }
 
       cvNamedWindow("Laplacian",1);
 
       //循环捕捉,直到用户按键跳出循环体
       for(;;)
       {
              IplImage * frame =0;
             
              //抓起一祯
              frame = cvQueryFrame(capture);
 
              if(!frame)
                     break;

	time(&t);
	strftime(showMsg, sizeof(showMsg), "%H%M%S", localtime(&t) ); //格式化输出.
   
    // 初始化字体   
    cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX, 4.5,1,0,2);//初始化字体,准备写到图片上的   
    // cvPoint 为起笔的x,y坐标   
    cvPutText(frame,showMsg,cvPoint(300,100),&font,CV_RGB(255,0,0));//在图片中输出字符  

              cvShowImage("Laplacian",frame);
 
              if(cvWaitKey(10)>0)
                     break;
       }
       cvReleaseCapture(&capture);
       cvDestroyWindow("Laplacian");
       return 0;
}

 

campaint

参考:

1.http://blog.csdn.net/jiadelin/article/details/2916329 strftime()函数用法
2.http://blog.csdn.net/jinshengtao/article/details/20549221 利用OpenCV给图像添加标注
3.http://blog.csdn.net/ycc892009/article/details/6516528 opencv-在图像上显示字符(不包括中文)

Arduino接收串口字符的问题

随手写了一个小程序,目标是串口收到:‘b’关闭 Pin 13 的LED;当收到 ‘s’ 是打开 LED.

程序如下,很奇怪的现象是:板子上 PIN 13 的LED一直是熄灭的。有兴趣的朋友在看答案之前可以先自己琢磨一下。

#define LED_PIN 13

void setup()
{
    Serial.begin(9600);
    pinMode(LED_PIN, OUTPUT);  
}

void loop()
{
    while (Serial.available() > 0)  
    {
        if ('b'==Serial.read();) 
          {
            digitalWrite(LED_PIN, LOW);
          }
        if ('s'==Serial.read();) 
          {
            digitalWrite(LED_PIN, HIGH);            
          }
    }
}

 

1022186_12845416716A28

真相只有一个,当 Serial Port Available之后,如果你取走了字符,下一个判断就是空了,所以第二个条件永远无法满足……

#define LED_PIN 13

void setup()
{
    Serial.begin(9600);
    pinMode(LED_PIN, OUTPUT);  
}

void loop()
{
  char  c;
  
    while (Serial.available() > 0)  
    {
        c=Serial.read();
        if ('b'==c) 
          {
            digitalWrite(LED_PIN, LOW);
          }
        if ('s'==c) 
          {
            digitalWrite(LED_PIN, HIGH);            
          }
    }
}

 

Step to UEFI (13) —– EADK struct tm

EADK 中的时间,还有一种定义(更准确的说是C标准库中的定义),可以在 \EadkPkg_A2\StdLib\Include\time.h 看到

/** A structure holding the components of a calendar time, called the
    broken-down time.  The first nine (9) members are as mandated by the
    C95 standard.  Additional fields have been added for EFI support.
**/
struct tm {
  int     tm_year;      // years since 1900
  int     tm_mon;       // months since January ?[0, 11]
  int     tm_mday;      // day of the month ?[1, 31]
  int     tm_hour;      // hours since midnight ?[0, 23]
  int     tm_min;       // minutes after the hour ?[0, 59]
  int     tm_sec;       // seconds after the minute ?[0, 60]
  int     tm_wday;      // days since Sunday ?[0, 6]
  int     tm_yday;      // days since January 1 ?[0, 365]
  int     tm_isdst;     // Daylight Saving Time flag
  int     tm_zoneoff;   // EFI TimeZone offset, -1440 to 1440 or 2047
  int     tm_daylight;  // EFI Daylight flags
  UINT32  tm_Nano;      // EFI Nanosecond value
};

 

有两个函数 localtime 和 mktime 可以将时间在 time_t 和 struct tm之间进行转换。

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
//#include  <Uefi.h>
//#include  <Library/UefiLib.h>
//#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <time.h>

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{

  time_t t,y;
  struct tm *tb;
  
  time(&t);

  printf("%ld\n",t);

  tb=localtime(&t);
  
  printf("localtime: %s",asctime(tb));

  y=mktime(tb); 
  
  printf("mktime   : %s",ctime(&y));
  
  return 0;
}

 

运行结果

time2

参考:

1.http://ganquan.info/standard-c/function/localtime
2.http://ganquan.info/standard-c/function/mktime

OpenCV 的cvCircle函数

写了一个小程序,获得摄像头输出之后直接在上面画图。

cvCircle(CvArr* img, CvPoint center, int radius, CvScalar color, int thickness=1, int lineType=8, int shift=0)

img为图像指针,单通道多通道都行,不需要特殊要求
center为画圆的圆心坐标
radius为圆的半径
color为设定圆的颜色,比如用CV_RGB(255, 0,0)设置为红色

thickness为设置圆线条的粗细,值越大则线条越粗,为负数则是填充效果 【参考1】

#include "stdafx.h"
#include "highgui.h"
#include "cv.h"
#include "stdio.h"
#include <ctype.h>
 
int main(int argc,char ** argv)
{
		int	r=0;

       CvCapture *capture = 0;

	   //从摄像头读取
       capture = cvCaptureFromCAM(0);

 
       if(!capture)
       {
              fprintf(stderr,"Could not initialize capturing...\n");
              return -1;
       }
 
       cvNamedWindow("Laplacian",1);
 
       //循环捕捉,直到用户按键跳出循环体
       for(;;)
       {
              IplImage * frame =0;
             
              //抓起一祯
              frame = cvQueryFrame(capture);
 
              if(!frame)
                     break;

			  r>20?r=0:r++;


			  //画一个圈  
			  cvCircle(frame,cvPoint(frame->width / 2,frame->height /2 ), 45+abs(10-r), CV_RGB(150, 100,100), 4, 8, 0);

              cvShowImage("Laplacian",frame);
 
              if(cvWaitKey(10)>0)
                     break;
       }
       cvReleaseCapture(&capture);
       cvDestroyWindow("Laplacian");
       return 0;
}

 

campaint

参考:
1.http://blog.163.com/yuhua_kui/blog/static/9679964420141104385570/ OpenCV cvCircle函数
2.http://wiki.opencv.org.cn/index.php/Cxcore%E7%BB%98%E5%9B%BE%E5%87%BD%E6%95%B0 Cxcore绘图函数

如何在XP上部署OpenCV编写的程序

本文使用环境:

1. XP 下,VS2008
2. 只适合你用Release编译出来的结果,不适合Debug版本(因为我测试Debug版本在XP上始终无法成功,所以删除了所有相关的 *d.dll)
3. 只是简单的测试之类的(如果你想更专业,可以只分发用到的DLL;或者重新编译OpenCV生成 Lib,通过静态编译将内容添加到你的EXE中)

步骤:

1.按照Debug编译,然后在工作机上测试OK
2.将生成的 Release 目录下的全部内容拷贝到dll的目录下 OpenCVXPPackage
3.如果目标机之前没有安装过 VS2008 的支持包,需要先安装一次vcredist2008sp1
4.之后在目标机上运行生成的 EXE 即可

注意:
如果出现 “应用程序正常初始化(0xc0000135)失败。请单击‘确定’,终止应用程序”,请安装 Microsoft .NET Framework 2.0

vcredist2008sp1:
vcredist2008sp1

OpenCVXPPackage(其中包含了一个编译好的测试应用程序):
OpenCVXPPackage

应用程序运行结果:
Capture

带有自动提示功能的手机座

起因:有网友抱怨,因为陪着孩子在家玩,漏接了公司的重要电话,被罚款500(如果真的是人民币的话,我相信他一定乐于看到这个产品)

看到这个需求,我忽然想是否可以用 Arduino来打造一个能够提醒的盒子呢? 当然,解决方法还有很多,严小姐听了我的目标之后第一个反应是为什么不换一个山寨机,大屏幕啊有没有,超长待机啊有没有,八个喇叭啊有没有,只要499啊有没有。我回答有很多人喜欢老物件有感情而已。她又在问为什么,终于被我意味深长的眼神说服了。

下面就开始动手实做了。从触发的角度来说最稳妥的办法还是安装APP,Android和iOS都是可行的,只是…….默默的轻抚了一下自己的 Nokia E72,放弃了这个方案。翻翻元件箱,里面有振动开关,那就凑合着用吧。提示方面,最近入手了一个具有录音放音功能的模块ISD1820,使用非常简单,GPIO足以搞定(多说一句,上面有麦克风和一个录音按钮,按下这个按钮对着它高歌一曲即记录在芯片中。时间比较短,只能记录10秒)。选用了 8×8 LED模块作为提示灯光,它在之前的《电子骰子系列》登场过。

原理上很简单,就是检测连续振动确定是振铃–>触发装置,每隔10秒提示一次–>如果有人按下Stop按钮就停止提示。

用到的配件

IMG_2553

用面包板来验证电路

IMG_2502

 

侧面有一个喇叭,一个按钮

IMG_2517

 

正面俯视,有一个 8×8点阵

 

IMG_2518

 

振动传感器

IMG_2550

 

内部线路看起来有点复杂

IMG_2551

 

另外一边做出一个洞,给 Arduino供电

 

IMG_2516

 

#include "LedControl.h"

const int ShockTestPin = 4; //Pin for vibration sensor
const int AlartPin = 5;     //LED Alert
const int StopPin = 6;      //Stop button
int sta=0;

//pin 12 is connected to the DataIn
//pin 11 is connected to the CLK
//pin 10 is connected to LOAD
//We have only a single MAX72XX.
LedControl lc=LedControl(12,11,10,1);

void showface() {
/* here is the data for the characters */
byte face[8]={
B00000100,
B00100010,
B01000110,
B01000000,
B01000000,
B01000110,
B00100010,
B00000100};

lc.setRow(0,0,face[0]);
lc.setRow(0,1,face[1]);
lc.setRow(0,2,face[2]);
lc.setRow(0,3,face[3]);
lc.setRow(0,4,face[4]);
lc.setRow(0,5,face[5]);
lc.setRow(0,6,face[6]);
lc.setRow(0,7,face[7]);

}

void setup() {

  Serial.begin(9600);

  pinMode(ShockTestPin, INPUT);   
  pinMode(AlartPin,OUTPUT);
  digitalWrite(AlartPin,LOW),
  pinMode(StopPin,INPUT);  //Stop pin for input

  //The MAX72XX is in power-saving mode on startup,
  //we have to do a wakeup call

  lc.shutdown(0,false);
  //Set the brightness to a medium values
  lc.setIntensity(0,8);
  //and clear the display
  lc.clearDisplay(0);

} // End of Setup

void loop() {
 long int start;
  
  if (0==sta) {       //Status One: Test status
     boolean shock=false;
     
     start=millis();
     while (millis()-start<=1000L) { //Test vibration for 1 second 
        if (LOW==digitalRead(ShockTestPin)) {  //If there is virbration signal
          shock=true;                          //Mark true
          break;
        } //if (LOW==digitalRead(ShockTestPin))
     }   
     if (false==shock) {sta=0; goto Sta1End;}    //If in the one seconds there is no vibration, it should break
     delay(500);
     
     start=millis();
     while (millis()-start<=1000L) { //Test vibration for 1 second 
        if (LOW==digitalRead(ShockTestPin)) {  //If there is virbration signal
          shock=true;                          //Mark true
          break;
        } //if (LOW==digitalRead(ShockTestPin))
     }   
     if (false==shock) {sta=0; goto Sta1End;}    //If in the one seconds there is no vibration, it should break
     delay(500);

     start=millis();
     while (millis()-start<=1000L) { //Test vibration for 1 second 
        if (LOW==digitalRead(ShockTestPin)) {  //If there is virbration signal
          shock=true;                          //Mark true
          break;
        } //if (LOW==digitalRead(ShockTestPin))
     }   
     if (false==shock) {sta=0; goto Sta1End;}    //If in the one seconds there is no vibration, it should break
     delay(500);
    
     //It means we have test vibration in 3 seconds 
     sta=1;
  Sta1End:
      ;  
  }
  
  if (1==sta) {
      //Play the voice that we have record
      digitalWrite(AlartPin,LOW);
      digitalWrite(AlartPin,HIGH);
      delay(100);
      digitalWrite(AlartPin,LOW);
      
      //Delay 10 seconds
      start=millis();
     
      while (millis()-start<=15000L)
      {
        if (LOW==digitalRead(StopPin)) { //We will stop the playing ,if we test the stop button is pressed
          sta=0;
          lc.clearDisplay(0);   
          break;
        }
        if (0==(millis()-start)/1000L % 2) {
           showface(); 
        }
       else
        {
            lc.clearDisplay(0);          
        } 
      }//while (millis()-start<=15000L)

  }//if (1==sta)
}

 

具体的使用方法拍了一个简单的视频,本人倾力献

最后放两张靓照

IMG_2544

IMG_2547

这只是很粗糙的原型,如果真想做出来实用的还有很长的路要走。比如:让检测更灵敏更可靠,或者干脆使用手机的声音作为触发条件。提示音量再大一些。或者再加上充电功能就更加实用,也给需要一直插着让Arduino供电找到更好的借口。

然后严小姐的问题是:如果忽然有很强的振动,误触发了怎么办,如果家里确实没有人它又一直在叫怎么办,如果隔壁在装修,有振动有噪音怎么办?好吧,如果你肯再品准500+以上的预算,让我买块蓝牙开发板,我相信一定不是问题。哦,对了还要换一个支持Android的手机才好啊。