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_ 电压跟随器

实验 DS1307 RTC 模块

之前入手过一个 DS1307 模块,但是一直没有实验,今天拿出来玩玩。

实验之前先研究硬件,上面有一个电池,上面写的是 Rechargable ,拆下电池用万用表测试了一下,没有电。不过后来连接USB之后由拔下来稍微放置了一点时间再插上,发现计时仍在继续。应该是充进去一点电了。

1

I2C 接口的,接线很简单【参考1】:

ds1307con

接下来测试程序,当然为了简单起见还是直接用库。但是不知道为什么连续找了几个库编译都无法完成。最终,找到一个好用的库【参考2】

编写简单的测试程序如下

#include <Wire.h>
#include "DS1307A.h"
DS1307A ds = DS1307A(2000);
DS1307A_RAM ram;
void setup()
{     
    Serial.begin(9600);                //init serial

    //ds.setDate(2014,10,1);
    //ds.setTime(20,17,40);
    //ds.setWeek(MONDAY);

}  
void loop()   
{ 
    Time t = ds.getTime();

    Serial.print( t.year);
    Serial.print(".");     
    Serial.print(t.month);     
    Serial.print(".");     
    Serial.print(t.date);     
    Serial.print("  ");     
    Serial.print(t.hour);     
    Serial.print(":");     
    Serial.print(t.minute);     
    Serial.print(":");     
    Serial.print(t.second);     

    Serial.println(); 

    //Serial.print(ds.getDateString("YMD",'-'));
    //Serial.print(" ");
    //Serial.println(ds.getTimeString("HMS",':'));    

    delay(1000);

}

 

运行结果如下

DS1307

代码和测试程序在此 DS1307

参考:

1.http://www.geek-workshop.com/forum.php?mod=viewthread&tid=207&extra=&highlight=ds1307&page=4 arduino学习笔记27 - DS1307 RTC时钟芯片与DS18B20数字温度传感器实验 (特别提醒,这篇文章中给出的库编译无法通过)

2.http://www.geek-workshop.com/thread-2317-1-1.html 自己封装的arduino1.0.1时钟库,使用DS1307芯片
你可以在这里下载文章中提到的库 DS1307A

====================================================================================================================================================================
2015/04/24 特别注意:这个模块对电压比较敏感,特别如果你是 Pro Micro之类的板子,VCC出来的电压可能是 4.6V ,这时候是无法正常工作的,会出现各种稀奇古怪的问题。

用 1602实现进度条

在 https://www.electronicsblog.net/arduino-lcd-horizontal-progress-bar-using-custom-characters/ 这里发现比较有趣的代码:用1602LCD 实现一个进度条。根据文章指引,我也试验了一下。

弄明白了原理,程序非常简单:

//https://www.electronicsblog.net/
//Arduino LCD horizontal progress bar using custom characters
#include <Wire.h> 
#include "LiquidCrystal_I2C.h"

#define lenght 16.0

double percent=100.0;
unsigned char b;
unsigned int peace;
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);
}

void loop()
{
                //设置光标在左上角
                lcd.setCursor(0, 0);

                percent = value/1024.0*100.0;

		//当超过100%的时候自动校正为 100%
		if (percent>100) {percent=1;value=0;}

                lcd.print("     ");
                lcd.print(percent);
                lcd.print(" % ");

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

                double a=lenght/100*percent;

                // drawing black rectangles on LCD
		// 显示全黑块。
                if (a>=1) {
                    for (int i=1;i<a;i++) {
                                lcd.write(4);
                                b=i;
                        } //for (int i=1;i<a;i++) 
                    a=a-b;
                }
                else {b=0;}

                peace=a*5;

                // drawing charater's colums
		// 显示除去全黑块之后的零头
                switch (peace) {
                  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)

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

		//递增
                value=value+10;				
		delay(300);

  }

 

连接

1602pg

大图

1602pg2

代码下载 pb1602

================================================================================
2015年3月13日 特别注意:自定义字符一次不能超过8个,如果需要自定义很多个,可以用动态的方法进行切换,参考 http://www.geek-workshop.com/thread-5190-1-1.html 1602自定义字符的另一种思路,实现超过8种自定义字符的显示

编译和使用 SpbTestTool

SpbTestTool 是 Windows WDK Sample 中的一个代码【参考1】,页面上介绍如下

The SpbTestTool sample serves two purposes. First, it demonstrates how to open a handle to the SPB controller, use the SPB interface from a KMDF driver, and employ GPIO passive-level interrupts. Second, it implements a set of commands for communicating with a peripheral device to aid in debugging.

程序本身实际上是两部分:一个是SYS的Demo Code (通过I2C访问设备的驱动)。这个SYS使用了 I2C 的资源以及GPIO;另一个是Application,演示如何通过应用程序调用驱动来完成通讯。

编译环境是 VS2013+WDK (8.1的),这也是MS推荐开发Win8.1 Driver使用的。正常情况下,安装完成之后,直接打开 MS 的Sample就可以直接编译成功,不需要额外的设置。如果无法编译,请检查VS2013和WDK 的安装顺序,需要先装VS再装WDK。具体环境的搭建住在这里就不再赘述了。

测试编译正常之后,就可以打开 SpbTestTool 这个工程文件了。

需要修改SpbTestTool.inx文件,这里面给出了编译后生成的INF文件中的 ACPI ID,需要和你BIOS中的一致。默认值是 SpbTestTool。如果你在ASL中真的使用这个,会发现BIOS的编译都无法通过。

labz1

修改了上面的文件后,每次编译生成的inf文件就是下面这个样子

labz2

同时,你BIOS中的ASL要写成下面的样子

Device(LABZ)
{
	Name(_ADR, 0x0)
	Name(_HID, "LABZ0001")
	Name(_CID, "LABZ0001")
	Name(_UID, 0x1)

	Method(_STA, 0x0, NotSerialized)
		{
			return(0x0f)
		}
			
	Method(_CRS, 0x0, NotSerialized)
		{
			Name(ZBUF,ResourceTemplate () {
					I2CSerialBus(0x50,          //SlaveAddress: bus address
						,                       //SlaveMode: default to ControllerInitiated
						400000,                 //ConnectionSpeed: in Hz
						,						//Addressing Mode: default to 7 bit
						"\\_SB.I2C3",           //ResourceSource: I2C bus controller name
						,                       //Descriptor Name: creates name for offset of resource descriptor
						)  //VendorData
					GpioInt(LEVEL,  ActiveLow, Exclusive, PullDown, 0, "\\_SB.GPO2", ) {6}//SAR INT  (GPIO INT)
				})
			Return (ZBUF)
		}
}// Device LABZ

 

编译时还需要在菜单 Build -> Configuration Manager 下,选择 Win8.1 Release。具体原因后面说。

Untitled3

编译完成后,把所有的东西一股脑 copy 到U盘上。

编译BIOS,之后将生成的BIOS刷新到被测机上。启动进入系统之后,设备管理中应该会出现我们在ASL中设置的这个设备,ID是我们刚才设置的 LABZ0001.

Capture3

下面是安装驱动。因为我们的驱动没有签名,所以要保证已经关闭签名验证之类的设置。

Capture4

安装后的样子

Capture5

Capture6

驱动的编译和安装到此已经结束,接下来就可以进行测试了。

测试需要有硬件对应,我这里选择的是一款I2C接口的EEPROM

eeprom

引脚对应连接到板子上的I2C即可,需要注意的是,可以选择 1.8V或者3.3V 供电都可以,差别在于如果选择前者的话,通讯速度上只能选择100KHz,选择后者之后速度可以选择 400KHz,这个数据来自 AT24C256 的DataSheet【参考 2】。

编译后的可执行文件在下面这个位置

Capture8

因为是VS2013编译的,所以在运行的时候需要对应的DLL库的支持,如果你的Win8.1没有的话,请到网上下载 vcredist_x86.exe

rb2013

如果你看到下面的画面,那么是因为你没有选择 Release Build

capa

运行之后,程序是命令行方式。使用 h 可以看到帮助。

我们的目标是:在EEPROM中,从 0 开始保存 1 2 3 这三个值,

首先,要打开设备,命令是 open

open

然后是写入命令可以写为 write {0 0 1 2 3} (前面 0 0 是一个16位的地址 0x0000. 不要按照MS的说明写,它程序不支持 01 02 这样的写法)

接下来,是读EEPROM的命令 writeread {0 0 } 3 (意思是从 0 0 开始连续读3个)

Captured

因为EEPROM断电不会丢失数据,写入之后,你再关机重启,重新使用这个应用程序用命令读取,仍然能看到你写入的值。

参考:

1. Dev Center - Hardware > Samples > Windows Driver Kit (WDK) 8.1 Samples > SpbTestTool
http://code.msdn.microsoft.com/windowshardware/SpbTestTool-adda6d71
(另外,在完整的 WDK8.1 的Sample Package中,也有 SpbTestTool 这个程序,只是页面单独下载的要比完整Package中的新一些。建议直接下载单独页面提供的。)

备份一个: SpbTestTool

2. AT24C256 的 Datasheet at24c256