本文是使用Arduino制作有趣电子产品项目的【后篇】。在前篇中,我们从构建电路进行到加速度计测试,在本文中,我们将完成该项目硬件的制作。这个独特的电子制作创意来自艺术家平原真,他的作品主要以“事物之间的关系”为主题。平原先生也是日本大阪艺术大学的副教授,他使用电脑和电子元器件制作了许多媒体艺术作品。近年来,他主要研究如何使用木材和石头等天然材料制作3D作品。那么,现在让我们进入“用Arduino制作的太阳能电池板供电数字养殖箱”项目的【后篇】吧。
硬件
步骤介绍
接下来,我们进行外壳组装和电子元器件安装。步骤如下:
1.用激光加工机切割材料
2.组装主体
3.LED接线及安装
4.安装太阳能电池板
步骤1:用激光加工机切割材料
材料是用激光加工机切割出来的。外壳是3mm厚的椴木板。从左边起依次为:LED面、太阳能电池板面、两个侧面和LED底座。LED底座上的刻线用于在粘贴LED时使其对齐。
可能很难看清楚,其实在下图的右上角有一个1mm厚的半透明亚克力板。它用于帮助扩散来自全彩串行LED灯带的光。
请从以下链接中下载切割用的数据。需要按照红线进行切割,按照黑线刻画标记。在实际作业时,请根据您使用的激光加工机来调整具体设置。
>> CutData.ai
步骤2:组装主体
在两枚侧面板的边缘涂上木工胶,然后将这四块板和LED以及太阳能电池板粘贴在一起。粘合后立即夹紧并固定到位。如果您担心粘合的强度不够,可以用一小块木头贴在粘合位置的拐角内侧进行加固。
步骤3:LED接线及安装
将全彩LED灯带分割成5条独立灯带。电极中间可以用钳子剪开。将跳线保留在临时接线时的位置。
将切割好的全彩LED灯带对准刻痕粘贴到底座上。此时,请注意在临时接线时所连接的跳线应在左上角的位置,且电极方向与蓝色箭头方向一致。
各个全彩LED灯带的电极之间通过硬跳线进行连接。电极之间的内侧距离约为11mm,中间距离约为17mm,外侧距离约为23mm。左上方连接跳线的LED编号为0,其余LED编号按照连接顺序递增,因此右下方LED编号为24。
在这种状态下,戳一下面包板上的跳线,确认其是否牢固。然后将LED示例程序([Adafruit NeoPixel]> [Simple])中NUPIXELS的值从16改为25并写入。如果LED都亮绿灯,就表示没有问题。
↓
步骤4:安装LED底座和面包板
依次叠加椴木板(LED表面)、亚克力板、LED底座,然后用螺丝将这四部分固定到位。连接着全彩LED灯带的跳线应从板面之间走线到内侧。垫片使用非导电垫圈(M2)。由于垫圈越厚越光的扩散面积越大,因此请根据自己的需要将间隙距离调整到约0.5mm至1mm之间。
将面包板安装在LED底座的中央。面包板的背面也有双面胶带,所以将离型纸撕下并将面包板贴上即可。请注意,如果您在安装后尝试移除面包板,那么里面的引脚将会脱落。将连接了全彩LED灯带的跳线分别插入面包板的电源、GND和6个引脚。
步骤5:用3D打印机制作太阳能电池板托架
用于固定太阳能电池板的托架可以通过3D打印机制作。从下面的链接页面中下载STL文件,并根据您的3D打印机情况进行设置,层压参数设置为粗糙一点的值也没关系。请打印两个相同形状的托架。
>>SolarPanelFixture.stl
首先,用螺丝将一个托架固定在太阳能电池板顶部的内侧。3D打印机打印出来的托架上有一个未切割的2mm的孔,请用M2平头螺丝钻孔并将其固定。
接下来,在将太阳能电池板插入上侧托架的同时,用螺丝固定下侧托架。
步骤6:硬件制作完成!
将太阳能电池板的DC插头插入面包板上的DC插座,到这里,所有的硬件制作就已经完成了。
然后,为了在Arduino Pro Mini 328中写入草图,请移除下侧的托架,并拆下太阳能电池板。
草图
整体步骤
写入草图相关的所有步骤如下:
1.安装库文件
2.以二维数组方式管理LED
3.种草
4.创建Animal结构体并管理多种动物
5.让动物数量或种类在加速度计有反应时增加
安装库文件
我们将使用Adafruit提供的Neo Pixel和加速度计LIS3DH的库。另外,还会用到定期调用指定函数的名为[MsTimer2]的库。从Arduino IDE菜单中选择[Sketch]> [Include Library]> [Manage Library]来打开库管理器。在搜索字段中输入[MsTimer2],然后从显示出来的选项中进行选择并安装。
以矩阵方式管理LED
LED灯带的编号是从0到24的连续编号,但为了更容易表达动植物的位置,可以换为用横纵坐标来表示。我们将LED编号存储在二维数组中。
... int ledMatrix[WIDTH][HEIGHT];//用来存储LED配置和编号的二维数组 ... ledMatrix[0][0] = 0;// 编号0为x=0, y=0 ledMatrix[1][0] = 9;// 编号9为x=1, y=0 ledMatrix[2][0] = 10;// 编号10为x=2, y=0 ledMatrix[3][0] = 19;// 编号19为x=3, y=0 ledMatrix[4][0] = 20;// 编号10为x=4, y=0 ...
种草
创建一个5 x 5的二维数组,该数组用于保存草的生长情况以及每帧中在随机位置种草。
... int grassMatrix[WIDTH][HEIGHT];//用来存储草的生长情况的二维数组 ... int rndX = random(WIDTH); int rndY = random(HEIGHT); grassMatrix[rndX][rndY] += random(GRASS_GROWTH_POTENTIAL + 1); //草在随机位置生长 ...
养动物
定义一个结构体来处理和保存动物的参数。动物信息通过个Animal结构体数组进行管理。Animal结构体有生死标志,如果动物没有死,那么它会移动和觅食。接下来,让动物向长势最好的草移动。当进入草地部分时,动物开始进食,草的生长值置0,相应的值被添加到动物的体能值中。同时,每一帧都会消耗一定的体能,如果体能降到0,就代表动物死亡。
... struct Animal { float x; float y; int life; bool isDead; RGB color; }; ... Animal animals[ANIMAL_MAX_NUM];//用来存储动物信息的数组 ... for ( int i = 0; i < ANIMAL_MAX_NUM; i++) { if ( !animals[i].isDead ) { moveMaxGrass(i);//向长势最好的草移动 //吃掉所在地的草,体能恢复 animals[i].life += grassMatrix[int(animals[i].x)][int(animals[i].y)]; grassMatrix[int(animals[i].x)][int(animals[i].y)] = 0; //体能减少 animals[i].life -= ANIMAL_DECREASE; //体能值变为0即代表死亡 if ( animals[i].life <= 0 ) { animals[i].life = 0; animals[i].isDead = true; } ...
让动物数量或种类增加
有两种方法可以增加动物。一种是当敲击作品主体、检测到一定程度的振动时物种会增加。这种情况下,一种随机颜色的新动物将会出现在画面中间。
... // 只要加速度计有反应,动物种类就会增加 if ( bornFlag ) { bornFlag = false; bornAnimal(-1); } ...
另一种是当动物吃草并且体能达到上限时同种动物数量会增加。此时动物的体能将会减半,并增加一只相同颜色的动物数量。
... //体能达到上限,同种动物数量增加 if ( animals[i].life > MAX_BRIGHTNESS ) { animals[i].life = MAX_BRIGHTNESS / 2; bornAnimal(i); } ...
整体草图
#define WIDTH 5//横向LED的数量 #define HEIGHT 5//纵向LED的数量 #define LED_PIN 6//LED灯带的信号线 #define INTERVAL 750//画面切换间隔 #define MAX_BRIGHTNESS 32//动植物的最大亮度 #define ANIMAL_MAX_NUM 10//动物的最大数量 #define ANIMAL_DECREASE 1//动物的体能减少 #define GRASS_GROWTH_NUM 3//草同时生长的位置 #define GRASS_GROWTH_POTENTIAL 2//草的生长速度 #define KNOCK_THRESHOLD 5000//用来判断敲击的阈值 #include <MsTimer2.h> #include <Adafruit_NeoPixel.h> #include <Wire.h> #include <Adafruit_LIS3DH.h> #include <Adafruit_Sensor.h> struct RGB { int r; int g; int b; }; struct Animal { float x; float y; int life; bool isDead; RGB color; }; // LED Adafruit_NeoPixel pixels;//控制LED的对象 int ledMatrix[WIDTH][HEIGHT];//用来存储LED的配置和编号的二维数组 int grassMatrix[WIDTH][HEIGHT];//用来存储草的生长情况的二维数组 Animal animals[ANIMAL_MAX_NUM];//用来存储动物信息的数组 //加速度计 Adafruit_LIS3DH lis = Adafruit_LIS3DH();//控制加速度计的对象 long px, py, pz;//1帧前的加速度 boolean bornFlag;//在下一帧生物是否诞生的标志 void setup() { Serial.begin( 9600 ); randomSeed( analogRead(0) ); // TIMER ------------------------- MsTimer2::set(INTERVAL, update);//定期调用函数update的定时器 MsTimer2::start(); // NEOPIXEL -------------------- pixels = Adafruit_NeoPixel(WIDTH * HEIGHT, LED_PIN, NEO_GRB + NEO_KHZ800);//生成NeoPixel对象 pixels.begin(); // ACC SENSOR ------------------------ if (! lis.begin(0x18)) { while (1); } lis.setRange(LIS3DH_RANGE_2_G);//设置加速度计的敏感度。2, 4, 8, 16G // LED MATRIX ------------------------- //将编号0~24的LED转换为X、Y坐标 ledMatrix[0][0] = 0;//编号0的LED为x=0, y=0 ledMatrix[1][0] = 9;//编号9的LED为x=1, y=0 ledMatrix[2][0] = 10; ledMatrix[3][0] = 19; ledMatrix[4][0] = 20; ledMatrix[0][1] = 1; ledMatrix[1][1] = 8; ledMatrix[2][1] = 11; ledMatrix[3][1] = 18; ledMatrix[4][1] = 21; ledMatrix[0][2] = 2; ledMatrix[1][2] = 7; ledMatrix[2][2] = 12; ledMatrix[3][2] = 17; ledMatrix[4][2] = 22; ledMatrix[0][3] = 3; ledMatrix[1][3] = 6; ledMatrix[2][3] = 13; ledMatrix[3][3] = 16; ledMatrix[4][3] = 23; ledMatrix[0][4] = 4; ledMatrix[1][4] = 5; ledMatrix[2][4] = 14; ledMatrix[3][4] = 15; ledMatrix[4][4] = 24; reset(); } void loop() { checkKnock();//判断是否受到敲击 delay(1); } void update() { // GRASS ---------------------------------------- grawGrass();//草长高 // ANIMAL ---------------------------------------- for ( int i = 0; i < ANIMAL_MAX_NUM; i++) { if ( !animals[i].isDead ) { moveMaxGrass(i);//向长势最好的草移动 //吃掉所在地的草,体能恢复 animals[i].life += grassMatrix[int(animals[i].x)][int(animals[i].y)]; grassMatrix[int(animals[i].x)][int(animals[i].y)] = 0; //体能减少 animals[i].life -= ANIMAL_DECREASE; //体能变为0即代表死亡 if ( animals[i].life <= 0 ) { animals[i].life = 0; a nimals[i].isDead = true; } //体能值达到上限,数量增加 if ( animals[i].life > MAX_BRIGHTNESS ) { animals[i].life = MAX_BRIGHTNESS / 2; bornAnimal(i); } } } // KNOCK ---------------------------------------- //只要加速度计有反应,动物就会增加 if ( bornFlag ) { bornFlag = false; bornAnimal(-1); } // OUTPUT ---------------------------------------- setLed();//设置LED的值使之发光 } void reset() { // ANIMAL ------------------------------------- for ( int i = 0; i < ANIMAL_MAX_NUM; i++) { animals[i].x = random(WIDTH); animals[i].y = random(HEIGHT); animals[i].life = 10; animals[i].color.r = random(0, 10); animals[i].color.g = random(0, 10); animals[i].color.b = random(0, 10); animals[i].isDead = true; } // GRASS ------------------------------------- for ( int y = 0; y < HEIGHT; y++) { for ( int x = 0; x < WIDTH; x++) { grassMatrix[x][y] = 0; } } // LED ------------------------------------- pixels.clear(); pixels.show(); } void grawGrass() { for ( int i = 0; i < GRASS_GROWTH_NUM; i++) { int rndX = random(WIDTH); int rndY = random(HEIGHT); grassMatrix[rndX][rndY] += random(GRASS_GROWTH_POTENTIAL + 1); //草在随机位置生长 if ( grassMatrix[rndX][rndY] > MAX_BRIGHTNESS ) grassMatrix[rndX][rndY] = MAX_BRIGHTNESS; } } void moveMaxGrass( int _id ) { // 查找长势最好的草的位置 int maxGrass = 0; int maxGrassX = 0; int maxGrassY = 0; for ( int y = 0; y < HEIGHT; y++) { for ( int x = 0; x < WIDTH; x++) { if ( grassMatrix[x][y] > maxGrass ) { maxGrass = grassMatrix[x][y]; maxGrassX = x; maxGrassY = y; } } } // 对目标进行比较并接近目标 int nX = animals[_id].x; int nY = animals[_id].y; if ( (maxGrassX - animals[_id].x) > 0 ) { nX += 1; } else if ( (maxGrassX - animals[_id].x) < 0 ) { nX -= 1; } if ( (maxGrassY - animals[_id].y) > 0 ) { nY += 1; } else if ( (maxGrassY - animals[_id].y) < 0 ) { nY -= 1; } //如果移动目标位置有活着的动物,则不移动 for ( int i = 0; i < ANIMAL_MAX_NUM; i++ ) { if ( animals[i].x == nX && animals[i].y == nY )//XY两者均一致 { if ( !animals[i].isDead )//活着 { if ( i != _id) //不是自己 { return;//结束函数 } } } } animals[_id].x = nX; animals[_id].y = nY; } void bornAnimal(int _parent) { int x; int y; int r; int g; int b; //查找空帧 for ( int i = 0; i < ANIMAL_MAX_NUM; i++) { if ( animals[i].isDead ) { if ( _parent < 0 )//因振动而诞生时 { x = WIDTH / 2; y = HEIGHT / 2; r = random(0, 15); g = random(0, 15); b = random(0, 15); } else {//上一代尚存时 x = animals[ _parent ].x; y = animals[ _parent ].y; r = animals[ _parent ].color.r; g = animals[ _parent ].color.g; b = animals[ _parent ].color.b; } animals[i].x = x; animals[i].y = y; animals[i].life = 10; animals[i].color.r = r; animals[i].color.g = g; animals[i].color.b = b; animals[i].isDead = false; break;//退出循环 } } } void checkKnock() { lis.read();//读取传感器的值 long delta = abs(px - lis.x) + abs(py - lis.y) + abs(pz - lis.z);//各轴的变化量合计 if ( delta > KNOCK_THRESHOLD )//变化量大的话 { bornFlag = true; } px = lis.x; py = lis.y; pz = lis.z; } void setLed() { RGB rgbMatrix[WIDTH][HEIGHT]; // GRASS -------------------------------------- for ( int y = 0; y < HEIGHT; y++) { for ( int x = 0; x < WIDTH; x++) { //rgbMatrix的初始化 rgbMatrix[x][y].r = 0; rgbMatrix[x][y].g = 0; rgbMatrix[x][y].b = 0; rgbMatrix[x][y].g += grassMatrix[x][y];//增加草的颜色 } } // ANIMAL -------------------------------------- for ( int i = 0; i < ANIMAL_MAX_NUM; i++) { if ( !animals[i].isDead ) { //增加动物的颜色 rgbMatrix[int(animals[i].x)][int(animals[i].y)].r += animals[i].color.r; rgbMatrix[int(animals[i].x)][int(animals[i].y)].g += animals[i].color.g; rgbMatrix[int(animals[i].x)][int(animals[i].y)].b += animals[i].color.b; } } // SET DATA ------------------------------------ for ( int y = 0; y < HEIGHT; y++) { for ( int x = 0; x < WIDTH; x++) { //将rgbMatrix的颜色设置为LED的颜色 pixels.setPixelColor( ledMatrix[x][y], pixels.Color(rgbMatrix[x][y].r, rgbMatrix[x][y].g, rgbMatrix[x][y].b ) ); } } pixels.show(); }
总结
大家辛苦了!作品完成了!如果您将太阳能电池板朝向太阳,我相信光点会开始移动。当代表动物的光点开始移动寻找食物时,看起来确实像一个生物。那么,您是否有兴趣通过重写种草方式和动物的移动逻辑来创建自己的原创生态系统呢?
到这里,通过【前篇】和【后篇】这两部分的介绍,我们了解了如何使用Arduino制作由太阳能电池板供电的数字养殖箱。通过本项目的制作,如果能让大家感受到电子制作的乐趣,我将倍感荣幸。谢谢大家!
本系列连载的内容
前篇:用Arduino制作的太阳能电池板供电数字养殖箱
后篇:用Arduino制作的太阳能电池板供电数字养殖箱(本文)