TAG | 电子积木
如果你的Arduino中要做少量的数据存储,那么使用EEPROM可能是最简单易行的做法了。其实Arduino所使用的芯片ATmega本身就具备一定的EEPROM存储器,只不过数量有限罢了。我们设计的这款外置式EEPROM存储模块使用I2C总线来与Arduino进行连接,并且采用可插拔的芯片,直插式系列,这样在扩展容量时会比较容易,基本上只需要按一个更大容量的EEPROM芯片就可以了。
该存储模块上使用的是AT24C系列的EEPROM芯片,其I2C基地址为0×50,最后三位地址可以根据应用需要进行设置。因此使用时我们首先要对其地址的最后三位进行设置,这是通过4位拨码开关上的A2,A1和A0来完成的,其中A0表示地址的最后一位。拨码开关向上推时相应的位为1,向下推时相应的位为0。也就是说,如果将A2,A1,A0都拨到上方时,相应的地址为0×57;而如果将A2,A1,A0都拨到下方时,相应的地址为0×50。
该存储模块上另外一个需要设置的地方就是开关RS,它是用来设置是否往I2C总线上的SDA和SCL上接入板载的上拉电阻。我们知道,I2C之所以被称为总线,就是因为可以连接多个I2C设备,接照I2C协议的规定,只允许在离控制器最近的一个I2C设备处接上拉电阻。说到这里你可能已经明白了,如果有多个这个的存储模块连接到Arduino的I2C总线上时,我们必须将第一个(离Arduino最近)存储模块的RS开关设置到ON的位置,而其它存储模块上的RS则不能设置到ON的位置:
如果使用的是Arduino专用传感器扩展扩展板V4,注意IIC/COM跳线的位置要设置到IIC这一边,因为此时我们需要用到I2C连接方式:
上述设置完成之后,我们只需要用I2C/COM连接线将EEPROM存储模块连接到传感器扩展板上就完成硬件的连接了:
至于软件部分,Arduino的官方网站上提供了两个不错的链接:I2CEEPROM和TWIPROM,需要的话可以参考。下面是我们测试时所用的代码:
#include <Wire.h> #define EEPROM_ADDR 0x50 // I2C Buss address of 24LC256 256K EEPROM void setup() { Wire.begin(); // join I2C bus (address optional for master) Serial.begin(9600); // TESTS FOR EACH FUNCTION BEGIN HERE Serial.println("Writing Test:"); for (int i=0; i<20; i++){ // loop for first 20 slots i2c_eeprom_write_byte(EEPROM_ADDR,i,i+65); // write address + 65 A or 97 a Serial.print(". "); delay(10); // NEED THIS DELAY! } Serial.println(""); delay(500); Serial.println("Reading Test:"); for (int i=0; i<20; i++){ // loop for first 20 slots Serial.print(i2c_eeprom_read_byte(EEPROM_ADDR, i),BYTE); Serial.print(" "); } // setup for page tests . . . byte PageData[30]; // array that will hold test data for a page byte PageRead[30]; // array that will hold result of data for a page for (int i=0; i<30; i++){ // zero both arrays for next test PageData[i] = 0; PageRead[i] = 0; } Serial.println(""); for (int i=0; i<30; i++) PageData[i] = i+33; // fill up array for next test char 33 = ! Serial.println("Writing Page Test:"); i2c_eeprom_write_page(EEPROM_ADDR, 100, PageData, 28 ); // 28 bytes/page is max Serial.println("Reading Page Test:"); i2c_eeprom_read_buffer( EEPROM_ADDR, 100, PageRead, 28); for (int i=0; i<28; i++){ Serial.print(PageRead[i],BYTE); // display the array read Serial.print(" "); } } void loop() { } void i2c_eeprom_write_byte( int deviceaddress, unsigned int eeaddress, byte data ) { int rdata = data; Wire.beginTransmission(deviceaddress); Wire.send((int)(eeaddress >> 8)); // Address High Byte Wire.send((int)(eeaddress & 0xFF)); // Address Low Byte Wire.send(rdata); Wire.endTransmission(); } // Address is a page address, 6-bit (63). More and end will wrap around // But data can be maximum of 28 bytes, because the Wire library has a buffer of 32 bytes void i2c_eeprom_write_page( int deviceaddress, unsigned int eeaddresspage, byte* data, byte length ) { Wire.beginTransmission(deviceaddress); Wire.send((int)(eeaddresspage >> 8)); // Address High Byte Wire.send((int)(eeaddresspage & 0xFF)); // Address Low Byte byte c; for ( c = 0; c < length; c++) Wire.send(data[c]); Wire.endTransmission(); delay(10); // need some delay } byte i2c_eeprom_read_byte( int deviceaddress, unsigned int eeaddress ) { byte rdata = 0xFF; Wire.beginTransmission(deviceaddress); Wire.send((int)(eeaddress >> 8)); // Address High Byte Wire.send((int)(eeaddress & 0xFF)); // Address Low Byte Wire.endTransmission(); Wire.requestFrom(deviceaddress,1); if (Wire.available()) rdata = Wire.receive(); return rdata; } // should not read more than 28 bytes at a time! void i2c_eeprom_read_buffer( int deviceaddress, unsigned int eeaddress, byte *buffer, int length ) { Wire.beginTransmission(deviceaddress); Wire.send((int)(eeaddress >> 8)); // Address High Byte Wire.send((int)(eeaddress & 0xFF)); // Address Low Byte Wire.endTransmission(); Wire.requestFrom(deviceaddress,length); //int c = 0; for ( int c = 0; c < length; c++ ) if (Wire.available()) buffer[c] = Wire.receive(); }
上述代码运行时的效果如下图所示:
实时钟通常也被称为实时时钟,它能够向电子电路提供日期和时间信息,包括年、月、日、时、分、秒,被广泛应用在需要进行计时的场合中。许多实时钟电路还提供电池供电的方式,这样在发生掉电时仍能准确计时。通常说来,功能稍多一点的实时钟电路还会提供包括警报、看门狗,以及支持高精度要求的校准寄存器等附加功能。
DS1307是DALLAS公司的一款实时种芯片,采用I2C协议与单片机通讯,而Arduino上正好有这一接口,因此连接起来就非常方便了。DS1307中有一个可编程波形输出口,它可以用来驱动LED小灯,或者作为中断来触发某些事件,不过用它去带一些大功率的东西的时候要注意。我们设计的这一款实时钟模块,将Ds1307的I2C接口和可编程波形输出接口SQW都连接出来了,不过一般情况下我们只会用到I2C接口来实现基本的时钟设置/读取功能。需要注意的是,该模块必须先安装上电池才可以正常工作。电池使用的是纽扣电池(型号CR1220),正极朝上:
在电路连接上我们可以使用Arduino专用传感器扩展板V4,不过要将相应的跳线设置到IIC的位置上:
剩下的工作就是用4芯的I2C/COM连接线将传感器扩展板上的专用接口,与实时钟模块上的IIC(I2C其实就是IIC的缩写)端口连接起来了:
硬件连接的工作完成之后,如何在Arduino里对该模块进行编程呢?上网搜索了一下,发现在Arduino上使用DS1307做为时钟芯片的玩家还真不少,而且还封装好了相应的Arduino库,实验时我使用的是Google Code上的这个DS1307库,你也可以在这里下载到我使用的版本。将下载好的压缩文件解压缩到Arduino 0018的libraries目录下后,重新启动Arduino并用它自带的测试程序进行测试:
#include <WProgram.h> #include <Wire.h> #include <DS1307.h> // written by mattt on the Arduino forum and modified by D. Sjunnesson void setup() { Serial.begin(9600); RTC.stop(); RTC.set(DS1307_SEC,1); //set the seconds RTC.set(DS1307_MIN,23); //set the minutes RTC.set(DS1307_HR,12); //set the hours RTC.set(DS1307_DOW,4); //set the day of the week RTC.set(DS1307_DATE,15); //set the date RTC.set(DS1307_MTH,7); //set the month RTC.set(DS1307_YR,10); //set the year RTC.start(); } void loop() { Serial.print(RTC.get(DS1307_HR,true)); //read the hour and also update all the values by pushing in true Serial.print(":"); Serial.print(RTC.get(DS1307_MIN,false));//read minutes without update (false) Serial.print(":"); Serial.print(RTC.get(DS1307_SEC,false));//read seconds Serial.print(" "); // some space for a more happy life Serial.print(RTC.get(DS1307_DATE,false));//read date Serial.print("/"); Serial.print(RTC.get(DS1307_MTH,false));//read month Serial.print("/"); Serial.print(RTC.get(DS1307_YR,false)); //read year Serial.println(); delay(1000); }
程序读起来应该不算困难,基本上就是使用RTC.set来对时钟进行设置,然后就可以通过RTC.get来读取相应的时间信息了,至于时钟怎么维护,那就是 DS1307 的工作了:)
在使用APC220实现Arduino无线数据传输那篇文章里,我们介绍了如何使用APC220模块在PC和Arduino间实现无线数据传输。那时我们使用的是厂家提供的USB适配器。,由于这个适配器只能接APC220模块,而不能起到其它的作用,或多或少觉得有些浪费。更加重要的是,为此我们得提供两个不同的套件,一个给Arduino使用,一个给PC使用,两者之间不能实现互换。
正是基于上面的这些问题,我们针对电子积木系列开发了一款USB转串口的适配器Serial Dongle,使用它就可以通过PC机来连接和使用各种基于串口的电子积木模块了,比如这里介绍的无线数传模块APC220。
APC220模块的使用可以参考使用APC220实现Arduino无线数据传输那篇文章,两者间的唯一区别只在于如何对USB虚拟出来的串口进行设置,以便能够使用厂家提供的设置程序RF-ANET来对APC220的各个参数进行调整。首先我们将USB转串口适配器Serial Dongle连接到PC机上,然后从设备管理器中找到该串口,并打开其属性设置窗口:
点击Advanced按钮打开高级属性设置窗口,我们需要从Com Port Number列表中为Serial Dongle设置一个较小的COM号(如COM5),否则厂家给的设置程序会出现无法打开该串口的错识。另一个需要修改的地方是BM Options中的延时设置需要从16改为更小的值(如8):
现在可以运行厂家给的设置程序了,此时特别需要注意的是操作顺序。应该先接好Serial Dongle,并将Serial Dongle右上角的开关拨到M的位置:
接着运行设置程序RF-ANET,最后再用连接线将APC220模块连接到Serial Dongle上,此时应该可以看到APC220设置程序窗口的最下面会显示Found device,表明APC220模块已经成功找到。如果设置程序没有找到APC220模块,可以试着断开APC220与Serial Dongle的连接,然后再次将APC220模块连接到SerialDongle上,直到设置程序成功找到APC220模块。
当APC220模块成功地被设置程序RF-ANET找到之后,我们就可以按照实际的需要来使用Serial Dongle对APC220模块进行各种参数配置了,而不再依赖厂家提供的USB适配器了;-)
至于随后的使用过程,那就完全一致了。一方面,我们可以将APC220模块通过4芯的串口连接线,与Arduino传感器扩展板相连接,这样Arduino就可以通过串口来操作APC220模块实现无线数据的收发:
另一方面,我们可以通过Serial Dongle连接APC220模块与电脑,并通过电脑的串口读写功能来操作APC220模块实现无线数据的收发:
下图就是利用两个APC220模块实现Arduino与电脑间无线数据传输的连接图:
SD卡是我们经常使用到的一种存储设备,像数码相机,MP3,阅读器,GPS导航仪等都会使用到它。在使用Arduino的时候,如何我们要将一些数据记录下来,就可以使用到SD卡。不过要让SD卡能够同Arduino配合使用可不是一件简单的事情,首先在硬件上我们要解决其与Arduino连接的问题,其次软件上我们还要解决文件的存储问题,以便使用读卡器之样的设备能够很方便地将保存在SD卡中的数据读出。
针对硬件上的需求,我们开发了SD卡模块,它通过SPI接口与Arduino进行通信:
同时,为了方便同Arduino的SPI接口进行连接,提供了相应的IDC扩展板,该扩展板上有一个专门为SPI设计的六芯插座:
电路的连接上则依然沿用了电子积木所一向强调的即插即用方式,我们只需要用6芯的IDC连接线,将IDC扩展板上的SPI插座与SD卡模块上的相应插座连接起来,就完成了电路部分的连接:
回到软件部分,我们的目标是要让SD卡上记录的数据能够在电脑上顺利地读出,因此在向SD卡上记录相应数据的时候需要遵循一定的规定,即文件系统的类型。对于像Arduino这样的小系统来讲,相对简单的FAT16是一个比较不错的选择。
在这里我们要用到读卡器来对SD卡进行相应的处理,将其格式化成FAT文件系统:
Arduino上使用的软件则是基于SparkFun提供的FAT库,但根据硬件差异做了一定的修改,你可以从这里下载到修改过的FAT库。该库需要被解压缩到Arduino的hardware\libraries子目录中。
测试时使用的时FAT库里自带的示例程序:
//Include all the libraries necessary for FAT32 #include <byteordering.h> #include <fat.h> #include <FAT16.h> #include <fat_config.h> #include <partition.h> #include <partition_config.h> #include <sd-reader_config.h> #include <sd_raw.h> #include <sd_raw_config.h> FAT TestFile; //This will be the file we manipulate in the sketch char buffer[512]; //Data will be temporarily stored to this buffer before being written to the file int read_size=0; //Used as an indicator for how many characters are read from the file int count=0; //Miscellaneous variable void setup() { Serial.begin(9600); //Initiate serial communication at 9600 bps TestFile.initialize(); //Initialize the SD card and the FAT file system. Serial.println("Starting..."); TestFile.create_file("Sample.txt"); //Create a file on the SD card named "Read_File_Test.txt" //NOTE: This function will return a 0 value if it was unable to create the file. TestFile.open(); //Now that the file has been created, open it so we can write to it. } void loop() { TestFile.write("This is test data."); //using the write function will always write to the beginning of the file. // Here we add some text to the file. TestFile.close(); //We are done writing to the file for now. Close it for later use. while(1){ TestFile.open(); //Open the file. When the file is opened we will be looking at the beginning of the file. read_size=TestFile.read(buffer); //Read the contents of the file. This will only read the amount of data specified // by the size of 'buffer.' Serial.println(read_size, DEC); //Print the number of characters read by the read function. for(int i=0; iSerial.print(buffer[i], BYTE); //Print out the contents of the buffer. } Serial.println(); sprintf(buffer, "%d", count++); //Now we'll use the buffer to write data back to the file. // Here's we'll only add one value to buffer, the 'count' variable. TestFile.write(buffer); //Write the new buffer to the end of the file TestFile.close(); //Close the file for later use. delay(1000); //Wait one second before repeating the loop. } }
这样Arduino在运行的时候,会不断地向SD卡模块上的Sample.txt文件中写入相应的数据,随后我们同样可以通过读卡器读出该文件中记录的内容。
需要注意的是,由于用来操作SD卡和文件系统的代码相对较多,目前该模块只能运行在带有ATmega328P的Arduino上面。另外,目前市场上可选的SD卡种类繁多,可能不是每种SD卡都能够被很好的兼容,目前我测试过通过的SD卡有:
- SanDisk 2GB SD2 Card
- Apacer 60X 1GB SD Card
测试没有通过的SD卡有:
- SanDisk 16MB SD Card





