Arduino中国 | Flamingo EDA

TAG | 电子积木

Aug/11

13

Arduino 电子积木 霍尔开关

首先科普一下,当一块通有电流的金属或半导体薄片垂直地放在磁场中时,薄片的两端就会产生电位差,这种现象就称为霍尔效应。霍尔开关则是利用霍尔效应的一种传感器,它可以很方便的把磁信号转换成电信号,具有很高的可靠性和灵敏度。

在Arduino上使用霍尔开关比较简单,电路连接上只需要用专用的传感器连接线,将霍尔开关与传感器扩展板上的相应端口连接起来就可以了:

编程的话使用最简单的数字输入函数digitalRead()进行读取就可以了:

int ledPin = 13;
int switchPin = 19;
int value = 0; 

void setup() {
  pinMode(switchPin, INPUT);
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  value = digitalRead(switchPin);
  if (HIGH == value) {
    digitalWrite(ledPin, HIGH);
  } else {
    digitalWrite(ledPin, LOW);
  }
}

使用的时候当霍尔开关靠近磁铁的时候,从Arduino相应引脚读出来的值为高,而当霍尔开关远离磁铁的时候,从Arduino相应引脚读出来的值为低。另外这里介绍的霍尔开关模块属于单极性霍尔元件,所以只对磁铁的南极或者北极有响应,实验的时候如果发现不响应的话可以试着换磁铁的另一极使用噢;-)

 

 

· ·

7段数码管可以用来很方便地显示0-9的数字以及,因此在一些电子制作中经常被使用到。7段数码管顾名思义其实就是由7个LED组成,它们一般按照共阴或者共阳的方式连接在一起,既然原理上这么简单,那么用Arduino来驱动7段数码管的方式自然就不言而喻了:分别将7个LED的引脚连接到Arduino的7个引脚上,然后通过给相应引脚置高或者置低的方式来控制7段数码管上显示出不同的数字来。可是仔细想想你会发现,通常Arduino上只有12个可以使用的数字引脚,如果驱动一个7段数码管就需要占用7个引脚,那驱动两个更者更多7段数码管的话,Arduino的引脚数目马上就告急了!这个时候你就可以考虑我们这里介绍的这个4路7段数码管的电子积木模块了:-)

这个4位7段数码管的模块采用专用的驱动芯片来控制数码管的各个引脚,并且使用I2C总线与Arduino进行连接,也就是说我们只需要用两根数据线就可以用Arduino来控制这一数码管模块了:

连接的方法很简单,只需要用专用的I2C连接线,将这一4位7段数码管与Arduino专用扩展板上的I2C端口连接起来就可以了:

同时注意将模块背面的RS开关设置到ON的位置,使能I2C引脚上的上拉电阻:

测试的时候可以使用如下的代码,该代码可以在数码管上循环显示0-9和a-f,同时还可以控制数码管上的小数点:

#include "Wire.h"  // enable I2C bus

//byte saa1064 = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND)
byte saa1064 = 0x76 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to VCC)

int digits[16]={63, 6, 91, 79, 102, 109, 125,7, 127, 111, 119, 124, 57, 94, 121, 113};
// these are the byte representations of pins required to display each digit 0~9 then A~F

void setup()
{
  Wire.begin(); // start up I2C bus
  delay(500);
  initDisplay();
}

void initDisplay()
// turns on dynamic mode and adjusts segment current to 12mA
{
  Wire.beginTransmission(saa1064);
  Wire.send(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
  Wire.send(B01000111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 12mA segment current
  Wire.endTransmission();
}

void displayDigits()
// show all digits 0~9, A~F on all digits of display
{
  for (int z=0; z<16; z++)
  {
    Wire.beginTransmission(saa1064);
    Wire.send(1); // instruction byte - first digit to control is 1 (right hand side)
    Wire.send(digits[z]); // digit 1 (RHS)
    Wire.send(digits[z]); // digit 2
    Wire.send(digits[z]); // digit 3
    Wire.send(digits[z]); // digit 4 (LHS)
    Wire.endTransmission();
    delay(500);
  }

// now repeat but with decimal point

 for (int z=0; z<16; z++)
  {
    Wire.beginTransmission(saa1064);
    Wire.send(1); // instruction byte - first digit to control is 1 (right hand side)
    Wire.send(digits[z]+128); // digit 1 (RHS)
    Wire.send(digits[z]+128); // digit 2
    Wire.send(digits[z]+128); // digit 3
    Wire.send(digits[z]+128); // digit 4 (LHS)
    Wire.endTransmission();
    delay(500);
  }
}

void clearDisplay()
// clears all digits
{
  Wire.beginTransmission(saa1064);
  Wire.send(1); // instruction byte - first digit to control is 1 (right hand side)
  Wire.send(0); // digit 1 (RHS)
  Wire.send(0); // digit 2
  Wire.send(0); // digit 3
  Wire.send(0); // digit 4 (LHS)
  Wire.endTransmission();
}

void loop()
{
  displayDigits();
  clearDisplay();
  delay(1000);
}

运行结果如下图所示:

由于该模块使用的是I2C接口,因此实际上也允许同时接入多个数码管模块,但必须对模块的地址进行设置。在数码管模块的背面有一个ADDR跳线,正是进行这一地址位设置的:

设置的办法是用铬铁将中间的引脚分别与VCC或者GND端连接起来,如果中间引脚与VCC连接时,相应的地址为0×76:

如果中间引脚与GND连接时,相应的地址为0×70:

这一模块在出厂时中间的引脚即不与VCC连接,也不与GND连接,此时的地址也是0×76。

 

·

FlexiForce传感器由由美国Tekscan公司生产的一种电阻元件,其特点是电导率与负载之间能够呈现高度的线性关系。在完全没有压力的状况下,该传感器所表现出来的电阻值非常大,可 以说基本上是一个开放式电路;但随着压力增加,电阻值开始下降,最后可以达到10千欧或者更低的水平。

FlexiForce压力传感器在实际使用的时候需要搭配相应的放大电路,因此我们设计了这一电子积木,以便在实际工程中能够很方便地运用这一传感器:

同其他电子积木一样,在使用的时候只需要用传感器连接线,将该模块与传感器扩展板连接起来就可以了:

对Arduino来讲,读取该传感器输出的值与其它模拟传感器的做法是一样的,下面是相应的测试代码:

int sensorPin = 5;
int value = 0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  value = analogRead(sensorPin);
  Serial.println(value, DEC);
  delay(10);
}

· ·

RS232串口是我们非常熟悉的一种Arduino与计算机之间进行数据交换的手段,虽然最新的Arduino在设计时都采用了USB转串口的方式,但最初一版的Arduino的确是基于RS232串口的。直到现在,RS232仍然在实际的工程项目中大量用到,不过这种方式有两个致命的弱点:一是传输距离不能太长,二是只能实现两个设备之间的点对点通信。我们这里要介绍的RS485模块正好解决了这两个问题:

RS485串行总线接口标准以差分平衡方式传输信号,具有很强的抗共模干扰的能力,允许一对双绞线上一个发送器驱动多个 负载设备,其理论的通讯距离是1200米,速率高达20Mbps,并可以用在强噪声的环境中正常工作,因此在工业通讯领域中被广泛应用。 当我们在Arduino上使用RS485时,更多的可能是借助其互连功能,将多个Arduino连接成一个RS485网络,来实现各个Arduino之间的通信。

在由RS485构成的多机串行通信系统中,一般采用的都是主从式结构:也就是说主机负责所有的通信协调过程,从机一般被动地接收主机发送过来的数据,或者根据主机的命令向主机反馈相应的数据,各个从机之间一般不进行数据交换,而是通过主机进行中转。之所以采用这种方式是因为于RS485是一种半双工通讯,发送和接收共用同一物理信道,在任一时刻都只允许一台设备处于发送状态。如果在时序上配合不好,就会发生总线冲突,使整个系统的通讯瘫痪,从而无法正常工作。

我们以一个三个结点的网络为例,介绍如何利用RS485串口模块来构建一个RS485网络,该网络中包括一台作为主机使用的Arduino(S)和两台作为从机使用的Arduino(CA, CB)。对于网络中的每一台Arduino来讲,硬件连接上我们都需要一块Arduino专用传感器扩展板、一条COM连接线和一条通用传感器连接线,其中COM连接线用来将RS485串口模块与传感器扩展板上的COM口连接起来,而通用传感器连接线则用来将RS485串口模块上的RE/DE引脚和标号为2的数字I/O口连接起来:

我们之前已经介绍过,RS485网络上的任何时候都只有一台设备处于发送状态,而其它设备则应该全部都处于接收状态。Arduino正是通过对RE/DE引脚的控制,来指定该设备是向RS485网络中发送数据,还是从RS485网络中接收数据。为了说明上的方便,在我们的例子中主设备S一直都处于发送状态,相应的RE/DE引脚一直处于高电平状态;两台从设备CA和CB一直处于接收状态,相应的RE/DE引脚则一直处于低电平状态。

在RS485串口模块上另一个需要引起我们注意的就是标记为“TERM?”的开关,它是用来控制终端电阻的,目的是为了吸收RS485网络中的反射信号,以保证正常传输的信号不受到干扰。不过该电阻只能在RS485总线上的最后一台设备上出现,这也是为什么叫做终端电阻的原因。在我们上面给出的网络拓朴图中不难看出,从机CB是总线上的最后一台设置,因此我们需要在该设备所对应的RS485串口模块上将“TERM?”开关拨到“Y”的位置:

而在主机S和从机CA上该开关都需要设置到与之相反的位置上:

由于是要将三个Arduino连接成一个RS485网络,因此我们还需要将RS485 串口模块两两连接起来,这是通过RS485串口模块上的“485 IN”和“485 THU”两个插座来实现的。具体说来,就是我们需要将前一个RS485串口模块的“485 THU”接口,与下一个RS485串口模块的“485 IN”接口连接起来,连接的时候我们可以使用模拟传感器连接线,或者是普通的杜邦线。对于我们正在构建的三个结点的RS485网络来讲,则是要求主机S的“485 THU”与从机CA的“485 IN”连接起来,然后再将从机CA的“485 THU”与从机CB的“485 IN”连接起来,实验中我们使用的是长度为2米的模拟传感器连接线。下图中从左至右依次为主机S,从机CA和从机CB:

硬件都连接好之后,下面我们就可以为各个Arduino来编写相应的程序了。下面是主机S的代码:

int EN = 2;

void setup()
{
  pinMode(EN, OUTPUT);
  Serial.begin(19200);
}

void loop()
{
// send data 
  digitalWrite(EN, HIGH);//使能发送
  Serial.print('A');
  Serial.print('B');
  delay(1000);
}

从主机S的代码不难看出,虽然采用的是RS485串口协议,但编程时使用的还是普通的串口操作语句。

下面是从机CA所使用的代码:

int ledPin = 13;
int EN = 2;
int val;

void setup()
{
  pinMode(ledPin, OUTPUT);
  pinMode(EN, OUTPUT);
  Serial.begin(19200);
}

void loop()
{
  // receive data
 digitalWrite(EN, LOW);//使能接收
 val = Serial.read();
  if (-1 != val) {
    if ('A' == val) {
      digitalWrite(ledPin, HIGH);
      delay(500);
      digitalWrite(ledPin, LOW);
      delay(500);
    }
 }
}

下面是从机CB所使用的代码:

int ledPin = 13;
int EN = 2;
int val;

void setup()
{
  pinMode(ledPin, OUTPUT);
  pinMode(EN, OUTPUT);
  Serial.begin(19200);
}

void loop()
{
  // receive data
 digitalWrite(EN, LOW);//使能接收
 val = Serial.read();
  if (-1 != val) {
    if ('B' == val) {
      digitalWrite(ledPin, HIGH);
      delay(500);
      digitalWrite(ledPin, LOW);
      delay(500);
    }
 }
}

从代码中不难看出,CA和CB分别响应主机S发送过来的字母A或者B,然后做相应的反应:点亮一个LED灯。在将三台Arduino都分别通电之后,你就可以看到作为主机的CA和CB在接到主机S发过来的命令之后,分别做相应的点灯动作。

最后需要说明的一点是,虽然在我们的例子中演示的是主机S不断向从机CA和CB发送数据的单向通信过程,但其实在RS485网络中也是可以实现双向通信的,只但需要在代码上做更多的工作,并且需要在主机和从面之间根据应用的需要定义一个相应的数据通信协议。

· ·

在Arduino上进行声音的播放可以有几种方案,一种是利用语音芯片对事先存储好的声音数据进行播放,这种方案一般只能进行语音数据的播放,音质比较差,面且播放的时长比较受限;第二种办法是播放MP3文件,但这种方式需要专门的MP3解压芯片;第三种可以借助的办法就是下面要介绍的WAV声音播放模块 ,其做法是将事先录制好的WAV文件存储到SD卡中,Arduino负责从SD卡中读出未经压缩的声音文件,再送到WAV模块中进行播放:

目前这一方案受限于所采用的DAC芯片精度的影响,最高只能做到16bit,22KHz,Mono格式的WAV文件,不过这对于大部分应用场合来讲已经足够了。

使用该模块时需要事先准备好符合我们要求WAV文件,有不少音频转换软件可以完成该任务。下面我们以Audacity(http://audacity.sourceforge.net/)为例,讲述如何讲MP3文件转换成我们所需要的格式。

运行Audacity之后,选择“File”菜单下的“Open”命令,打开需要进行转换的MP3文件:

通常MP3文件都是立体声(Stereo)格式的,我们需要将其分离成两个单独的音轨,这可以通过选择左侧声音文件标题下拉框中的“Split Stereo Track”命令来完成:

为了避免这两个音轨在混音之后音量过大,我们可以分在每个音轨上将增益(Gain)设置成-6dB,修改的方法是拖动相应音轨左侧标有正负号的滑动条:

将下来我们需要从每条音频的标题下拉菜单下选择“Mono”命令,将其转换成单声道格式,注意每一路都需要做这个转换:

现在我们可以选择“Project”菜单下的“Quick Mix”命令来完成单声道文件的混音:

通过上述的步骤,我们已经将立体声(Stereo)格式的声音数据转化成单声道(Mono)格式的声音数据,此外我们还需要设置相应的采样位数和采样频率。对于采样位数的设置,我们可以从每条音频的标题下拉菜单中选择“Set Sample Format”下的“16-bit”命令,将采样位数设置为16位:

而采样频率的设置则可以通过Auda对话框底部的“Project rate”进行设置,我们的WAV播放模块最高只支持22Khz:

所有参数都设置好之后,我们就可以导出所需要的声音文件了。选择“Edit”菜单下的“Preferences…”命令,在打开的对话框中从“Uncompressed Export”下拉框中选择“WAV (Microsoft 16 bit PCM)”后单击“Ok”按钮:

最后选择“File”菜单下的“Export As WAV…”命令,导出我们所需要的WAV文件就可以了,为了确保之后Arduino能够正常处理,该WAV文件需要用字母命名,并且文件名称不能超过8个字节。为了确保产生的文件是正确的,我们还可以在Windows中右键单击该文件后,从弹出的菜单中选择“属性”命令打开属性对话框,然后从“摘要”中检查生产的文件是否符合我们的要求:

生成的文件我们可以通过读卡器将其复制到SD卡中,为了保证读取的正确性,可以考虑先格式化后再将WAV文件复制到SD卡中。注意SD卡应该格式化成FAT格式的,而不要格式化成FAT32格式的。

带有WAV文件的SD卡准备好之后,我们可以用Arduino IDC扩展板将SD卡模块和WAV声音播放模块都连接到Arduino上:

通过Arduino皤放SD卡中存储的WAV文件我们需要用到wavehc库(http://code.google.com/p/wavehc/)。我们测试时使用的是wavehc20101009.zip这个文件,你可以通过wavehc的官方网站下载该文件,也可以通过这个地址直接下载我们在测试时使用的库文件包。

在将下载后的wavhc库解压缩之后,将压缩包中的WaveHC上当复制到Arduino安装目录中的libraries目录下。wavhc库默认使用了Arduino上的2、3、4、5这4个引脚,但 我们的Arduino IDC扩展板上的IDC-6座则是与Arduino的6、7、8、9这4个引脚相连接的,因此我们需要对WaveHC目录下的WavePinDefs.h这个文件进行修改, 相应的对应关系为2->9、3->7、4->6、5->8。你可以手工修改这个文件中以MCP_DAC_开头的那些宏定义,也可以直接下载我们已经修改好的WavePinDefs.h文件

测试时我们使用的是wavhc库压缩包examples目录下的daphc,其实现的功能是不断从SD卡中查找以.WAV为扩展名的WAV文件进行播放,相应的代码为:

/*
 * This example plays every .WAV file it finds on the SD card in a loop
 */
#include "WaveHC.h"
#include "WaveUtil.h"

SdReader card;    // This object holds the information for the card
FatVolume vol;    // This holds the information for the partition on the card
FatReader root;   // This holds the information for the volumes root directory
WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

uint8_t dirLevel; // indent level for file/dir names    (for prettyprinting)
dir_t dirBuf;     // buffer for directory reads

/*
 * Define macro to put error messages in flash memory
 */
#define error(msg) error_P(PSTR(msg))

// Function definitions (we define them here, but the code is below)
void play(FatReader &dir);

//////////////////////////////////// SETUP
void setup()
{
  Serial.begin(9600);           // set up Serial library at 9600 bps for debugging

  putstring_nl("\nWave test!");  // say we woke up!

  putstring("Free RAM: ");       // This can help with debugging, running out of RAM is bad
  Serial.println(FreeRam());

  //  if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
  if (!card.init()) {         //play with 8 MHz spi (default faster!)  
    error("Card init. failed!");  // Something went wrong, lets print out why
  }

  // enable optimize read - some cards may timeout. Disable if you're having problems
  card.partialBlockRead(true);

  // Now we will look for a FAT partition!
  uint8_t part;
  for (part = 0; part < 5; part++) {   // we have up to 5 slots to look in
    if (vol.init(card, part))
      break;                           // we found one, lets bail
  }
  if (part == 5) {                     // if we ended up not finding one  :( 
    error("No valid FAT partition!");  // Something went wrong, lets print out why
  }

  // Lets tell the user about what we found
  putstring("Using partition ");
  Serial.print(part, DEC);
  putstring(", type is FAT");
  Serial.println(vol.fatType(),DEC);     // FAT16 or FAT32?

  // Try to open the root directory
  if (!root.openRoot(vol)) {
    error("Can't open root dir!");      // Something went wrong,
  }

  // Whew! We got past the tough parts.
  putstring_nl("Files found (* = fragmented):");

  // Print out all of the files in all the directories.
  root.ls(LS_R | LS_FLAG_FRAGMENTED);
}

//////////////////////////////////// LOOP
void loop()
{
  root.rewind();
  play(root);
}

/////////////////////////////////// HELPERS
/*
 * print error message and halt
 */
void error_P(const char *str)
{
  PgmPrint("Error: ");
  SerialPrint_P(str);
  sdErrorCheck();
  while(1);
}
/*
 * print error message and halt if SD I/O error, great for debugging!
 */
void sdErrorCheck(void)
{
  if (!card.errorCode()) return;
  PgmPrint("\r\nSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  PgmPrint(", ");
  Serial.println(card.errorData(), HEX);
  while(1);
}
/*
 * play recursively - possible stack overflow if subdirectories too nested
 */
void play(FatReader &dir)
{
  FatReader file;
  while (dir.readDir(dirBuf) > 0) {    // Read every file in the directory one at a time

    // Skip it if not a subdirectory and not a .WAV file
    if (!DIR_IS_SUBDIR(dirBuf)
         && strncmp_P((char *)&dirBuf.name[8], PSTR("WAV"), 3)) {
      continue;
    }

    Serial.println();            // clear out a new line

    for (uint8_t i = 0; i < dirLevel; i++) {
       Serial.print(' ');       // this is for prettyprinting, put spaces in front
    }
    if (!file.open(vol, dirBuf)) {        // open the file in the directory
      error("file.open failed");          // something went wrong
    }

    if (file.isDir()) {                   // check if we opened a new directory
      putstring("Subdir: ");
      printEntryName(dirBuf);
      dirLevel += 2;                      // add more spaces
      // play files in subdirectory
      play(file);                         // recursive!
      dirLevel -= 2;
    }
    else {
      // Aha! we found a file that isnt a directory
      putstring("Playing ");
      printEntryName(dirBuf);              // print it out
      if (!wave.create(file)) {            // Figure out, is it a WAV proper?
        putstring(" Not a valid WAV");     // ok skip it
      } else {
        Serial.println();                  // Hooray it IS a WAV proper!
        wave.play();                       // make some noise!

        uint8_t n = 0;
        while (wave.isplaying) {// playing occurs in interrupts, so we print dots in realtime
          putstring(".");
          if (!(++n % 32))Serial.println();
          delay(100);
        }
        sdErrorCheck();                    // everything OK?
        // if (wave.errors)Serial.println(wave.errors);     // wave decoding errors
      }
    }
  }
}

如果一切正常,此时你应该就能够在WAV声音播放模块上接的耳机或者音箱中听到你存储在SD卡中的WAV文件在播放时的效果了。上述Arduino代码在运行的时候会试图通过串口传输一些调试信息,必要的时候可以打开Arduino的串口来查看相应的原因(比如SD卡中没有正常找到)。

· · ·

Older posts >>

Theme Design by devolux.nh2.me