蓝牙控制小灯泡亮度的实验

这里实现用 Windows x86 平板电脑控制小灯泡亮度。

硬件方面在我们最初实验设备【参考1】的基础上增加一个蓝牙模块(这里建议使用蓝牙的朋友选用 HC06系列的,和HC05的不同,这个系列只有Slave的功能,但是个人感觉HC06更容易搜索连接上,我用HC05的时候每次都需要重新搜索配对设备,但是HC06上不用),用来和Windows平板进行通讯。

image001

代码方面,Arduino使用的程序非常简单,将串口收到的char当作PWM值直接输出。程序使用了2个串口,一个是通常的USB,同PC进行通讯,主要是为了方便Debug;真正工作的是另外一个进行蓝牙通讯的串口。

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

void loop()
{
  char  c;
    while (Serial.available() > 0)  
    {
        c=Serial.read();
        analogWrite(6,c);
        Serial.println(c);
    }
    while (Serial1.available() > 0)  
    {
        c=Serial1.read();
        analogWrite(6,c);
        Serial.println(c);
    }    
}

 

上位机使用的是Delphi 2010,使用控件很简单即可完成编程。

unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, iComponent, iVCLComponent, iCustomComponent, iPositionComponent,
  iScaleComponent, iKnob, iSevenSegmentDisplay, iSevenSegmentBinary,
  iSevenSegmentInteger, StdCtrls, CPortCtl, CPort, Buttons;

type
  TForm2 = class(TForm)
    iKnob1: TiKnob;
    iSevenSegmentInteger1: TiSevenSegmentInteger;
    ComPort1: TComPort;
    Button1: TButton;
    Button2: TButton;
    procedure iKnob1PositionChange(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TForm2.Button1Click(Sender: TObject);
begin
  ComPort1.ShowSetupDialog;
end;

procedure TForm2.Button2Click(Sender: TObject);
begin
  if ComPort1.Connected then
    begin
      ComPort1.Close;
      Button2.Caption:='Connect';
    end
  else
    begin
      ComPort1.Open;
      ComPort1.WriteStr(chr(0));
      Button2.Caption:='Disconnect';
    end
end;

procedure TForm2.FormActivate(Sender: TObject);
begin
  iKnob1.Width:=Form2.Width;
end;

procedure TForm2.iKnob1PositionChange(Sender: TObject);
var
  c:byte;
begin
  c:=trunc(iKnob1.Position);
  iSevenSegmentInteger1.Value:=c;
  if Comport1.Connected then
    begin
      ComPort1.Write(&c,1);
    end;
end;

end.

 

界面
image002

上位机完整代码下载

knobtest

工作的视频:
http://www.tudou.com/programs/view/yE4EHhUFcvU/?resourceId=414535982_06_02_99

最后说点其他的:除了Apple和各式各样的 Anrdoid平板电脑,x86的Windows平板也在崛起。

相比之下,使用Windows平板编程有如下优点:
1. 编程简单。工具方面Delphi VB VC 都是非常成熟的工具,能在普通PC上运行的程序,即可顺利移植到Windows平板上(甚至可以说‘移植’这个词不合适,因为不用任何改动直接放上去即可);
2. 发布简单。从时效性上来说,不需要发布到什么市场,也不需要什么审核,各种方法让对方拿到即可运行;
3. 周边设备多多,比如:各种摇杆方向盘,价格也比Apple专用的低很多;
4. 程序运行非常稳定,除非程序有错误,否则根本不会出现那种莫名其妙的“闪退”;

此外,从我的实践的角度来说,Windows 平板目前还有如下的缺点:
1. Windows本质上是给有鼠标的机器运行的,而不是触摸类的设备。这两者在精度上差别很大,传统的Window的各种控件,默认的调用者也都是鼠标,如果直接用触摸操作起来很困难,也容易误操作。因此,我用普通台式机做平板程序的感受是:你一定要把你的用户当成视力有困难的人,能调大的菜单或者按钮一定要做到最大…….
2. 目前比较缺少Windows x86平板方面的中文资料,在使用板载的各种传感器时,缺少资料

参考:
1. http://www.lab-z.com/mos%E6%8E%A7%E5%88%B6%E5%B0%8F%E7%81%AF%E6%B3%A1%E7%9A%84%E5%AE%9E%E9%AA%8C/ MOS控制小灯泡的实验

达文西的灯

前几天,偶然知道周星驰《国产凌凌漆》英文名称是“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 上,所以必须有用于电平转换的设计。

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

Arduino Pro Micro 打造PPT遥控器

之前,我们介绍过如何用Arduino Uno打造一个PPT遥控器【参考1】,缺点是制作过程复杂,用到的器件较多导致整体稳定性不好。这里介绍使用 Arduino Pro Micro来做一个同样的PPT遥控器。

原理上和之前的并没有多少差别,都是通过模拟USB键盘的方式来进行控制。差别在于Pro Micro是缩小版的 Leonardo ,内部集成了USB Slave控制器,我们不需要再花费精力模拟自己为USB Keyboard.
硬件连接示意图:

ppt1

代码是直接修改自示例“KeyboardMessage”

ppt2

/* 
 Keyboard Button test
 
 For the Arduino Leonardo and Micro.
 
 Sends a text string when a button is pressed.
 
 The circuit:
 * pushbutton attached from pin 2 to +5V
 * 10-kilohm resistor attached from pin 4 to ground
 
 created 24 Oct 2011
 modified 27 Mar 2012
 by Tom Igoe
 
 This example code is in the public domain.
 
 http://www.arduino.cc/en/Tutorial/KeyboardButton
 */

const int buttonPinA = A1;          // input pin for A
const int buttonPinB = A2;          // input pin for B
int previousButtonStateA = HIGH;   // for checking the state of a pushButton
int previousButtonStateB = HIGH;   // for checking the state of a pushButton

void setup() {
  // make the pushButtonA pin an input:
  pinMode(buttonPinA, INPUT);
  // make the pushButtonB pin an input:
  pinMode(buttonPinB, INPUT);
  // initialize control over the keyboard:
  Keyboard.begin();
  delay(10000);
}

void loop() {
  // read the pushbutton:
  int buttonStateA = digitalRead(buttonPinA);
  int buttonStateB = digitalRead(buttonPinB);  
  // if the button state has changed, 
  if ((buttonStateA != previousButtonStateA) 
    // and it's currently pressed:
  && (buttonStateA == HIGH)) {
    // type out a message
    //Keyboard.print("A");
    Keyboard.press(KEY_LEFT_ARROW);
    Keyboard.releaseAll();
  }
  // if the button state has changed, 
  if ((buttonStateB != previousButtonStateB) 
    // and it's currently pressed:
  && (buttonStateB == HIGH)) {
    // type out a message
    //Keyboard.print("B");
    Keyboard.press(KEY_RIGHT_ARROW);
    Keyboard.releaseAll();
  }  
  // save the current button state for comparison next time:
  previousButtonStateA = buttonStateA; 
  previousButtonStateB = buttonStateB;   
}

 

实物图

ppt3

工作的视频

http://www.tudou.com/listplay/mAjgE5Ac_Sg/Fz0piTvQ9Cs.html?resourceId=414535982_06_02_99

参考:

1. http://www.lab-z.com/%E7%94%A8-arduino-%E6%89%93%E9%80%A0ppt%E9%81%A5%E6%8E%A7%E5%99%A8/ 用 Arduino 打造PPT遥控器
2. http://arduino.cc/en/Reference/KeyboardModifiers 键值定义

Arduino 打造一个音量计

根据上一次的LM386的设计【参考1】,以及网上的设计【参考2】,用Arduino做一个音量计。首先,音频从MIC进入,经过LM386的放大后接入到Arduino的模拟输入上,经过 DAC 量化之后显示在 1602上。

电路方面,和【参考1】差别在于我们不再使用喇叭而是直接将放大后的OUT信号接入到 Arduino的A5上。

显示方面,我们使用【参考3】提到的方法来自定义字符充当强度指示。

下面的图片与其说是原理图不如说是连接图更合适

vu1

//www.lab-z.com 
//在 1602 上显示音量的小程序

#include <Wire.h> 
#include "LiquidCrystal_I2C.h"

int value=100;

// custom charaters

LiquidCrystal_I2C lcd(0x27,16,2);

//定义进度块
byte p1[8] = {
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10,
                0x10};

byte p2[8] = {
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18,
                0x18};

byte p3[8] = {
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C,
                0x1C};

byte p4[8] = {
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E,
                0x1E};

byte p5[8] = {
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F,
                0x1F};

void setup() {
               lcd.init();           //初始化LCD
               lcd.backlight();		 //打开背光
				
			//将自定义的字符块发送给LCD
			//P1 是第一个,P2 是第二个,以此类推
                lcd.createChar(0, p1);			 
                lcd.createChar(1, p2);
                lcd.createChar(2, p3);
                lcd.createChar(3, p4);
                lcd.createChar(4, p5);
            //MIC输入放大之后在 A0 输入Arduino    
                pinMode(A0, INPUT);
}

//显示音量强度
//从左到右一共有 5 * 16 =80 点,一共是 80+1=81 个状态
void showprg(int value)
{
        //第一行显示当前VU值
                lcd.setCursor(0,0);
                lcd.print("     VU=");
                lcd.print(value);

                
		//移动光标到第二行
                lcd.setCursor(0,1);

        //显示全黑的块
                for (int i=1;i<value / 5;i++) {
                                lcd.write(4);
                        } //for (int i=1;i<a;i++) 


                // drawing charater's colums
		// 显示除去全黑块之后的零头
                switch (value % 5) {
                  case 0:
                        break;
                  case 1:
                        lcd.write(0);
                        break;
                  case 2:
                        lcd.write(1);
                        break;
                  case 3:
                        lcd.write(2);
                        break;
                  case 4:
                        lcd.write(3);
                        break;
                  } //switch (peace)

	       // 用空格填充剩下的位置
              for (int i =0;i<(16-value / 5);i++) {
                lcd.print(" ");   }
}

void loop()
{
     //输入的是0-1023,用函数将这个值对应到[0,80]上 
        showprg(map(analogRead(A0),0,1023,0,80));
		delay(100);
              
}

 

测试方法,在右边用一个手机播放声音,MIC将音频信号转化为电平信号,经过LM386放大后,通过Arduino A5进行量化,最终显示在LCD1602上。

vu2

工作视频:

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

这个还只是一个模型,很粗糙,对于VU的动态显示范围不大,如果想让人类更容易理解需要考虑将现在的线性显示改为非线性的。

另外,工作时我发现比较奇怪的事情,就是如果静音的时候,会显示UV 大约 35左右,但是如果有动态声音播放的时候反而会出现 16 这样值,不清楚原因。

==============================================================================
另外的另外,研究了一下前面那个口哨开关的模拟输出。下面是电路图,右下角红色框起来的是模拟输出电路部分

vu3

查了一下,发现这种用法是电压跟随器,下图来自【参考4】。

vu4

电压跟随器,顾名思义,是实现输出电压跟随输入电压的变化的一类电子元件。也就是说,电压跟随器的电压放大倍数恒小于且接近1【参考5】。
多少输入就是多少输出…….这也就是为什么这个模块驱动能力很差带不动喇叭的原因。
参考:

1. http://www.lab-z.com/lm386-with-mic/ LM386 with MIC
2.http://www.arduino-hacks.com/arduino-vu-meter-lm386electret-microphone-condenser/ Arduino VU meter – LM386+electret microphone condenser
3. http://www.lab-z.com/1602progressbar/ 用 1602实现进度条
4. http://www.geek-workshop.com/thread-551-1-1.html LM358双运算放大器
5. http://baike.baidu.com/link?url=85VzUTT6n3IrCaAATZSg5jsg_A-zQYFrizj6jqGP7PzRg1aJe2yTddMihqJ7_UgRMa_GxnAYakSmmjM-9nhsz_ 电压跟随器

Step to UEFI (41) —– x64 的 FreqCalc程序

之前的一篇文章《Step to UEFI (9)—-使用RDTSC计算当前CPU 频率》【参考1】给出了一个计算当前CPU频率的方法。不过 Tim 给我留言,他说这篇文章的程序无法在 x64下正常编译:

freq

我猜测原因是因为我的程序使用的内嵌汇编,内嵌汇编无法被X64的编译器正常编译的。关于这个说法可以看【参考2】。

动手在 UDK2014 下面实验,不过在看到描述的问题之前还是要走一段路的。

使用编译命令 build -a X64 -p AppPkg\AppPkg.dsc 得到下面的错误信息

c:\edk\AppPkg\AppPkg.dsc(94): error 000E: File/directory not found in workspace
        c:\edk\PerformancePkg\Library\DxeTscTimerLib\DxeTscTimerLib.inf

 

检查 AppPkg.dsc

修改

[LibraryClasses.X64]
  TimerLib|PerformancePkg/Library/DxeTscTimerLib/DxeTscTimerLib.inf

 

修改为

[LibraryClasses.X64]
  TimerLib|PerformancePkg/Library/TscTimerLib/DxeTscTimerLib.inf

 

再次编译,有下面的错误信息

Processing meta-data .. done!
Building ... c:\edk\MdePkg\Library\BaseLib\BaseLib.inf [X64]
        "C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe" /Foc:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\MdePkg\Library\BaseLib\BaseLib\OUTPUT\.\CheckSum.obj /nologo /c /WX /GS- /W4 /Gs32768 /Gy /D UNICODE /O1ib2 /GL /FIAutoG
en.h /EHs-c- /GR- /GF /Zi /Gm /X /Zc:wchar_t /X /Zc:wchar_t /GL- /Ic:\edk\MdePkg\Library\BaseLib\X64  /Ic:\edk\MdePkg\Library\BaseLib  /Ic:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\MdePkg\Library\BaseLib\BaseLib\DEBUG  /Ic:\edk\MdePkg  /Ic:\edk\M
dePkg\Include  /Ic:\edk\MdePkg\Include\X64 c:\edk\MdePkg\Library\BaseLib\CheckSum.c
'C:\Program' 不是内部或外部命令,也不是可运行的程序或批处理文件。
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe' : return code '0x1' Stop.


build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 

build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 


build...
 : error F002: Failed to build module
        c:\edk\MdePkg\Library\BaseLib\BaseLib.inf [X64, MYTOOLS, DEBUG]

- Failed -
Build end time: 11:22:09, Mar.09 2015
Build total time: 00:00:03

 

尝试在命令行窗口直接运行”C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe” 错误提示是:

C:\EDK>"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe"
系统找不到指定的路径。

 

这是因为目前的编译环境缺少 64位编译器。我使用的是 vs2008 express 版本,默认是没有 64位的cl.exe的。

找到 ‘x86_amd64’ 拷贝到 “C:\Program Files\Microsoft Visual Studio 9.0\VC\bin”

需要的朋友可以在这里下载到 x86_amd64

再次编译,然后就能看到 Tim 说的问题啦

C:\Program Files\Microsoft Visual Studio 9.0\VC\bin

Building ... c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.inf [X64]
        "C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe" /Foc:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\AppPkg\Applications\FreqCalc\FreqCalc\OUTPUT\.\FreqCalc.obj /nologo /c /WX /GS- /W4 /Gs32768 /Gy /D UNICODE /O1ib2 /GL /
FIAutoGen.h /EHs-c- /GR- /GF /Zi /Gm /X /Zc:wchar_t /Ic:\edk\AppPkg\Applications\FreqCalc  /Ic:\edk\Build\AppPkg\DEBUG_MYTOOLS\X64\AppPkg\Applications\FreqCalc\FreqCalc\DEBUG  /Ic:\edk\MdePkg  /Ic:\edk\MdePkg\Include  /Ic:\edk\MdePkg\Includ
e\X64  /Ic:\edk\ShellPkg  /Ic:\edk\ShellPkg\Include  /Ic:\edk\MdeModulePkg  /Ic:\edk\MdeModulePkg\Include c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c
FreqCalc.c
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(15) : error C4235: nonstandard extension used : '__asm' keyword not supported on this architecture
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'mov'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : warning C4550: expression evaluates to a function which is missing an argument list
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'mov' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'dword'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'dword' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'ptr'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2065: 'ptr' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(18) : error C2146: syntax error : missing ';' before identifier 'value'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'eax' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'mov'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'mov' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'dword'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'dword' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2146: syntax error : missing ';' before identifier 'ptr'
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2065: 'ptr' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(19) : error C2109: subscript requires array or pointer type
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(20) : error C2065: 'edx' : undeclared identifier
c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.c(20) : error C2143: syntax error : missing ';' before '}'
NMAKE : fatal error U1077: '"C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\x86_amd64\cl.exe"' : return code '0x2'
Stop.


build...
 : error 7000: Failed to execute command
        C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\nmake.exe /nologo tbuild 


build...
 : error F002: Failed to build module
        c:\edk\AppPkg\Applications\FreqCalc\FreqCalc.inf [X64, MYTOOLS, DEBUG]

- Failed -
Build end time: 11:26:31, Mar.09 2015
Build total time: 00:01:16

 

然后就考虑如何解决,网上搜索了一下,有人遇到同样的问题【参考3】,具体的解决方法在【参考4】

简单的说,就是单独写一个 asm 然后在对应的 Inf中声明一下即可。

针对我遇到的问题,程序如下

FreqCalc.inf

## @file
#  Sample UEFI Application Reference EDKII Module
#
#  This is a sample shell application that will print "UEFI Hello World!" to the 
#  UEFI Console based on PCD setting.
#
#  It demos how to use EDKII PCD mechanism to make code more flexible.
#
#  Copyright (c) 2008 - 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.php
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#
#
##

[Defines]
  INF_VERSION                    = 0x00010005
  BASE_NAME                      = FreqCalc
  FILE_GUID                      = 6987936E-ED34-44db-AE97-2FA5E4ED2216
  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]
  FreqCalc.c
  ReadTsc1.asm

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

[LibraryClasses]
  UefiApplicationEntryPoint
  UefiLib
  PcdLib
  ShellCEntryLib
  ShellLib


[FeaturePcd]


[Pcd]

 

然后在FreqCalc.c 写成下面这样,特别注意要声明一下你用的那个汇编语言中的函数名

//
// FreqCalc.C
//

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

EFI_SYSTEM_TABLE	*gST;
EFI_BOOT_SERVICES  	*gBS;

UINT64
EFIAPI
zReadTsc (
  VOID
);

//
// Entry point function - ShowVersion
//
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{

  UINT64  elsp;

  gST = SystemTable;
  gBS = SystemTable->BootServices;

  elsp=zReadTsc();

  gBS -> Stall(1000000);  
  Print(L"CPU Frequency: %ld \n",zReadTsc() - elsp);

  return EFI_SUCCESS;
}

 

然后还有一个ReadTsc1.asm (其实 uefi 里面提供了一个 x64的 ReadTsc.asm,可以直接使用,这里是为了让读者看的更清楚,取了一个不会重复的名称)

    .code

;------------------------------------------------------------------------------
; UINT64
; EFIAPI
; zReadTsc (
;   VOID
;   );
;------------------------------------------------------------------------------
zReadTsc  PROC
    rdtsc
    shl     rdx, 20h
    or      rax, rdx
    ret
zReadTsc  ENDP

    END

 

接下来就可以正常编译了,生成的 efi 文件会比 x64的大一些。然后我在实体机的 x64 shell下面运行成功。就是说上面的方法是没问题的。

FreqCalc

参考:

1.http://www.lab-z.com/rdtsc/ Step to UEFI (9)—-使用RDTSC计算当前CPU 频率

2.http://stackoverflow.com/questions/1295452/why-does-msvc-not-support-inline-assembly-for-amd64-and-itanium-targets 简单解释,说x64下无法支持 _ASM汇编

3.http://biosren.com/viewthread.php?tid=6822&highlight=%C4%DA%C7%B6%2B%BB%E3%B1%E0 請問如何在EDK2的AppPkg裡面使用asm (有人问同样的问题)

4.http://www.biosren.com/thread-6632-1-1.html 給個UEFI 內嵌彙編的小程序吧? 本文的主要参考,其中介绍了如何写单独写一个ASM文件