永远盛开的郁金香
机械郁金香雕塑,只需轻轻触摸即可绽放,可以发出五彩斑斓的光亮。
硬件材料
1 * Arduino Nano R3
1 * SG90舵机
1 * TTP223触摸模块
1 * 1毫米黄铜线
1 * 2毫米黄铜管
1 * 0.3mm绝缘铜线
7 * WS2812 5050 LED灯
30 * 白色贴片1206 LED
故事
永恒的花朵,永恒的爱。一个完美的不只是情人节可以送给你所爱的人的礼物。这款郁金香只需轻轻触摸即可绽放出任何颜色。它的六个花瓣将慢慢打开并点亮。当花瓣关闭时,它们会产生令人难以置信的环境光线和叶子图案。
如何实现
在我告诉你如何创造这种美之前,先简单地谈谈它是如何运作的。郁金香由6个花瓣组成,每个花瓣有5个贴片白色LED。每个花瓣都与一个Arduino输出引脚相连。花蕊隐藏了7个RGBLED。从电气的角度来说,整个花是负电压的,花瓣中的静脉是正电压。花瓣通过推杆打开,推杆一直沿着茎杆向底座延伸。推杆由小型舵机构的运动控制。阀杆侧面的小叶片连接到底座内的TTP223触摸传感器,并创建一个电容式触摸板。木质底座包含舵机,触摸IC和ArduinoNano。让我们自己构建一个吧!
我没有告诉你材料的任何尺寸和花朵的确切形状。我认为每朵花都应该是独一无二的。
花瓣(4小时)
让我们从最令人满意的部分开始- 开花的花瓣。
你需要的第一件事是开花的模板。我用石膏浇注成管模具。干燥后,我把它塑造成郁金香花。你也可以3D打印它,但我没有3D打印机。石膏很棒,因为它很容易加工,铅笔可以在上面画画。单瓣是模板表面的1/4,所以最后当它们有6个时,它们会略微重叠,形成郁金香花朵的精确外观。我用铅笔在石膏表面画出花瓣形状。当我对花瓣形状感到满意时,我用刀子将它雕刻成石膏,以帮助我在焊接时将杆固定到位。
花瓣由1毫米的黄铜棒组成,一直围绕着形状。花瓣内部是5个1206白色LED和来自同一根线的“静脉”结构。首先,创建圆周线,花时间弯曲它。切下一小段管子并将其放在电线的底部平坦部分- 这将是铰链花瓣将四处移动。焊丝末端焊接在一起,确保不要用焊料填充管子。它需要自由移动。用LED和静脉结构完成花瓣。看我做一个花瓣。对我感到羞耻,这是我制作这首花的唯一镜头。
现在再做5个。所有花瓣都需要完全相同。这真的很重要。否则,它们在关闭时不会像一个漂亮的郁金香形状,甚至可能卡住。
开花(1小时)
是时候将所有花瓣焊接在一起了。花的基部是六角形- 6个花瓣。花瓣通过铰链固定在六边形上。然而,六边形略微不规则。我失败很多次。花瓣需要相互重叠,如果六边形是规则的,则不允许。三个花瓣靠近中心(内花瓣),另外三个花瓣稍微偏移(外花瓣)。为此,我创建了一个模板并将其打印在纸上。红色形状是由两个嵌套的正六边形组成的最终不规则六边形。将所有铰链(管道)焊接到六边形上。从模板中心出来的光线将帮助你将花瓣焊接到正确的位置。花瓣的中心需要跟随从六边形中心出来的射线。最后,当你关闭花瓣时,你将拥有最后的花朵形状。
制作阀杆和推杆(1小时)
首先完成了开花内部的机制,然后添加了一个杆和推杆。它为制作增添了许多痛苦。有一次,我几乎想把它丢弃,永不回头。花瓣的移动是由1毫米黄铜推杆在黄铜管内自由移动而产生的。花瓣通过一个非常小的六边形与推杆连接,每侧有2mm长的铰链- 推杆头。六边形垂直地坐在推杆上,借助穿过六边形中心的小杆。这是制表师的工作。
为了做推杆头,我做了第二个模板。首先,弯曲电线以形成微小的六边形。然后切下2毫米长的小管并将它们放在电线上。焊接电线以完成六边形形状。再次确保不要将管道焊接到电线上。继续将焊丝焊接到六边形的中心。并通过垂直于推杆头中心焊接1毫米线完成杆。
将黄铜管切成所需长度。而现在是第二个关键部分。茎需要与花的六角形基部完全垂直,需要在其正中心,并且需要有足够的空间让推杆头向下移动以关闭花瓣。首先,我将两根电线焊接到阀杆的一端以形成V形膨胀。这些将是连接杆与六边形基座顶点的6根线中的2根。
因此将花朵倒置并将V形膨胀物焊接到六角形底座的两个相对顶点。检查形状。如果你把杆放在杆内,它需要在花的中心出来。花点时间在这里完美。这真的是一个至关重要的部分。当你使用它时,在六边形顶部的其余部分和杆顶部之间焊接4根导线。确保不要焊接管道内的孔!
花瓣机制(6小时)
我差点把它丢进垃圾桶里。你要钉它!对于这一部分,我受到蒸汽机及其杆,活塞和飞轮的启发。
用茎将花固定在向上的位置。将推杆穿过阀杆,头部朝上。垂直于推杆底端焊接短管,并用1mm黄铜线制成一个小连杆,用于连接舵机和推杆(见上图)。应该能够使用舵机上下推动推杆。连接你的Arduino尝试一下。在开始焊接花瓣和推杆头之间的连杆之前,需要使用Arduino调整舵机机芯。编写代码,以便在推杆头端与开花六角形底座齐平的最顶部位置上下移动推杆。在最低位置,它位于开花V形底座的底部。为使推杆运动尽可能柔软,请使用最靠近舵机臂中心的孔。舵机需要旋转更长但步长更短。当推杆向上移动时,推动连杆和花瓣向下。随着它向下移动它连接杆和花瓣被关闭。
花瓣目前缺少一个关键部件- 花瓣针。这个铰链使花瓣移动。铰链位于一个杆上(见下图),该杆与平行于其底座的每个花瓣焊接在一起。花瓣销需要位于花瓣表面之上,以便在动画中看到它完全打开。用管子的铰链制作一个这样的棒,并将其焊接到第一个花瓣上。这将需要大量试验杆的大小及其与花瓣基部的距离,以允许推杆完全打开和关闭花瓣。使用试错法。将杆焊接到合适的位置,并在推杆销和花瓣销之间添加连杆。如果你的推杆位于上部位置而你的花瓣处于完全打开的位置,推杆销和花瓣销之间的空间就是连杆的长度。
现在尝试上下推动推杆,看看发生了什么。如果它没有任何摩擦工作正常,花瓣可以关闭和打开你完成(与第一个花瓣)!如果没有,请尝试不同长度的连杆或杆的不同位置。为了完成开花,在剩下的5个花瓣上复制相同的条和连杆。当开花开放时,3个外花瓣需要稍低,以允许它们在关闭时适当地重叠内花瓣。最后,你应该能够关闭并打开花朵。不要惊慌如果你没有做第一次尝试的完美形状。它只意味着所有的花瓣都不完全一样。可能需要进行大量的微调才能创造出完美的形状- 连杆的长度和杆的位置略有不同。
花蕊(1小时)
在花开的里面,我放了7个LED从内部发光。这些LED只需要控制一根DATA线,并且可以菊花链式连接。我把它们中的6个焊接在两个小六边形之间(当然是另一个模板)。下六边形是地线,上半部分是正电压。将适当的NeoPixels引线焊接到这些六角环上。这些LED放置在45度角下,照射到侧面。为了使其更好,将第七个LED放入上六角的中心。最后但并非最不重要的是连接DATAIN和OUT导致创建菊花链。
这种结构需要两条线向下到达基座- VCC和DATA。地面取自花架。将一根0.3的绝缘铜线焊接到上环用于VCC,第二根焊接到菊花链中的第一个LED用于DATA。这些电线最后会到达基座。使它们至少是茎长度的3倍。在焊接之前,这些电线的末端需要从它们的透明绝缘层中释放出来。热量不会破坏它。用刀子去除绝缘层。你现在可以测试LED以确保它们正常工作。用这些铜线温和。如果你不小心剥去绝缘材料以外的其他地方,可能会发生短路!
将雌蕊结构放在花的中心。稍微偏离开花六角形底座,为花瓣连杆留出足够的空间。对我来说,它比开花六边形高1厘米。用黄铜棒连接所有顶点,形成坚固的结构。花完成了!现在测试看看花瓣是否仍能自由移动。
扎根(2小时)
花瓣和LED都需要电源线才能发光。整个花卉雕塑将是一个地面,但RGBLED有6个花瓣和2个线,需要连接到基座上的Arduino。为此,带有透明绝缘的0.3毫米细铜线将缠绕在管杆上。用于LED的两条线已经完成。将另外6个焊接到铰链附近的每个花瓣上的松散静脉线上,并使线穿过茎下的花开结构。确保不要以锐角弯曲这些电线,它会很容易断裂。
现在将所有电线收集在阀杆管的上端附近,并使用捆扎带固定它们。不要过度拧紧,让电线穿过它。现在很好地组织开花内的所有电线。确保花瓣可以自由移动,推杆也不会与导线碰撞。完成了吗?现在拧紧捆扎带。
电线现在无法控制地绕着阀杆运行。你需要耐心地慢慢地将它们包裹在茎干上。紧密而均匀。我花了至少一个小时才完成这一步。当你在杆的末端时,放置另一条捆扎带以固定那里的电线并使用透明的强力胶将它们固定在那里。确保不要用推杆密封管道!
最后一根线缺失是一个地线。将另一根铜线焊接到阀杆的底端。你应该最终从花中出来9根电线。现在明智的做法是将所有电线连接到Arduino并测试是否没有短路并且所有LED都亮起。
花盆(2小时)
我希望这朵花能够从人造花盆中长出来,这也将隐藏所有电子产品。我用了一块木头把它加工成一个4厘米高,直径9厘米的圆筒。我没有车床,因此我使用圆锯切割原始形状,然后使用压力钻作为临时车床。然后我用手动铣刀雕刻了一个2.5厘米深,直径7厘米的开口,以适应舵机,ArduinoNano和触摸传感器IC。在底部,还有一个小开口,可以精确地安装ArduinoNano USB端口,以便能够从侧面连接USB线。
如果你有花盆,可以在花朵生长的地方用电线钻一个直径为花茎的孔- 可能在中心。尽量适合你的花。电线要小心。如果你以锐角弯曲它们,它们就会断裂。最后,我还从底座内部添加了一个大孔,为舵机臂和连杆腾出更多空间。你可以做你喜欢的任何形状的花盆,请记住你需要适应所有的电子设备。
触摸叶垫(1小时)
机械郁金香需要某种互动元素,使人能够开花。我选择了TTP223触摸传感器。但是哪里放一个触控板?我决定在茎的侧面添加一点叶子,既可以使花更自然,也可以作为电容式触摸板。触摸时会触发TTP223传感器并告诉Arduino打开花朵。当你完成这么复杂的雕塑时,这对你来说将是一块蛋糕。使用与花瓣相同的技术省略LED。我也为自己创建了一个模板。在杆孔旁边的基座上钻一个小孔,将叶子固定在那里。
如果你不想或不能使用电容式触摸传感器,可以在底座上添加一个普通的按钮。它会做同样的工作。
把它放在一起(2小时)
这是组装的最后一步!你紧张吗?将花茎再次插入基座的孔中。现在,这是重要的一步。在切割前测量两次!打开盛开的花朵。并切断从阀杆出来的推杆的末端与阀杆齐平。现在,当你再次关闭花朵时,推杆应从茎杆中拔出。垂直于推杆焊接短管。这将是连杆与舵机臂的铰链。当你放开花和杆时,它应该完全打开,因为管道也会作为停止。
你现在可以将杆粘在底座上。确保阀杆管端与底座内侧齐平,以便为舵机臂留出尽可能多的空间。我再次使用过强力胶水。确保不要将推杆与阀杆粘在一起。这会破坏你的工作!
接下来,胶水在叶垫上。在你将铜线焊接到它之前,能够将叶垫连接到TTP223触摸传感器。
将花倒置。雕塑周围要小心,不要现在打破它!这将是多么浪费!首先,将舵机放入最终位置。它的手臂应该已经从试验台准备好了。只需找到舵机臂在底座内自由移动并将连杆连接到推杆的最佳位置。为了将舵机系统固定到位,我使用了一块金属板和两个螺钉。我希望在舵机故障或放置错误的情况下保持灵活性。但如果你有信心可以粘上它。
如果你有TTP223模块,请将电线焊接到原始TTP223模块触摸板(组件所在的另一侧)的叶垫上。你需要刮擦保护性丝绸面膜以暴露铜层。粘贴触摸模块。
进入基座的最后一个组件是ArduinoNano本身作为郁金香的大脑。将它放入底座的开口中,以便它可以连接到计算机并与所有其他组件连接:
伺服数据线 ⭢D9
TTP223触摸传感器数据 ⭢D2(利用中断)
新像素数据 ⭢A0(任何输出引脚都可以)
花瓣 ⭢D3,D4,D5,D6,D10,D11(任何输出引脚都可以)
花地线 ⭢GND
VCC线 ⭢5V
TTP223触摸传感器GND ⭢GND
TTP223触摸传感器VCC ⭢5V
伺服GND ⭢GND
伺服VCC ⭢5V
编码(1小时)
#include
#include #include "SoftPWM.h" #define NEOPIXEL_PIN A0 #define TOUCH_SENSOR_PIN 2 #define SERVO_PIN 9 //#define SERVO_OPEN 1750 #define SERVO_OPEN 1650 #define SERVO_SAFE_MIDDLE 1000 #define SERVO_CLOSED 775 #define RED 0 #define GREEN 1 #define BLUE 2 float currentRGB[] = {0, 0, 0}; float changeRGB[] = {0, 0, 0}; byte newRGB[] = {0, 0, 0}; #define MODE_SLEEPING 0 #define MODE_BLOOM 3 #define MODE_BLOOMING 4 #define MODE_BLOOMED 5 #define MODE_FADE 6 #define MODE_FADING 7 #define MODE_FADED 8 #define MODE_FALLINGASLEEP 9 #define MODE_RAINBOW 90 byte mode = MODE_FADED; byte petalPins[] = {3, 4, 5, 6, 10, 11}; Adafruit_NeoPixel pixels = Adafruit_NeoPixel(7, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ400); Adafruit_TiCoServo servo; int servoChange = 1; // open int servoPosition = SERVO_SAFE_MIDDLE; void setup() { Serial.begin(115200); pixels.begin(); servo.attach(SERVO_PIN, SERVO_CLOSED, SERVO_OPEN); pinMode(TOUCH_SENSOR_PIN, INPUT); attachInterrupt(digitalPinToInterrupt(TOUCH_SENSOR_PIN), _touchISR, RISING); randomSeed(analogRead(A7)); SoftPWMBegin(); pixelsUnifiedColor(pixels.Color(0, 0, 0)); //pixelsUnifiedColor(pixels.Color(255, 70, 0)); prepareCrossFade(140, 70, 0, 140); servo.write(servoPosition); } int counter = 0; byte speed = 15; void loop() { boolean done = true; switch (mode) { case MODE_BLOOM: prepareCrossFadeBloom(500); changeMode(MODE_BLOOMING); break; case MODE_BLOOMING: done = crossFade() && done; done = openPetals() && done; done = petalsBloom(counter) && done; if (done) { changeMode(MODE_BLOOMED); } break; case MODE_FADE: //prepareCrossFade(0, 0, 0, 800); changeMode(MODE_FADING); break; case MODE_FADING: done = crossFade() && done; done = closePetals() && done; done = petalsFade(counter) && done; if (done) { changeMode(MODE_FADED); } break; case MODE_FADED: //prepareCrossFade(140, 70, 0, 140); changeMode(MODE_FALLINGASLEEP); break; case MODE_FALLINGASLEEP: done = crossFade() && done; done = closePetals() && done; if (done) { changeMode(MODE_SLEEPING); } break; case MODE_RAINBOW: rainbow(counter); break; } counter++; delay(speed); } void changeMode(byte newMode) { if (mode != newMode) { mode = newMode; counter = 0; } } void _touchISR() { if (mode == MODE_SLEEPING) { changeMode(MODE_BLOOM); } else if (mode == MODE_BLOOMED) { changeMode(MODE_FADE); } } // petals animations boolean petalsBloom(int j) { if (j < 250) { return false; // delay } if (j > 750) { return true; } int val = (j - 250) / 2; for (int i = 0; i < 6; i++) { SoftPWMSet(petalPins[i], val); } return false; } boolean petalsFade(int j) { if (j > 510) { return true; } for (int i = 0; i < 6; i++) { SoftPWMSet(petalPins[i], (510 - j) / 2); } return false; } // animations void prepareCrossFadeBloom(unsigned int duration) { byte color = random(0, 5); switch (color) { case 0: // white prepareCrossFade(140, 140, 140, duration); break; case 1: // red prepareCrossFade(140, 5, 0, duration); break; case 2: // blue prepareCrossFade(30, 70, 170, duration); break; case 3: // pink prepareCrossFade(140, 0, 70, duration); break; case 4: // orange prepareCrossFade(255, 70, 0, duration); break; } } void rainbow(int j) { uint16_t i; byte num = pixels.numPixels() - 1; pixels.setPixelColor(pixels.numPixels() - 1, 100, 100, 100); for (i = 0; i < num; i++) { pixels.setPixelColor(i, colorWheel(((i * 256 / num) + j) & 255)); } pixels.show(); } // servo function boolean openPetals() { if (servoPosition >= SERVO_OPEN) { return true; } servoPosition ++; servo.write(servoPosition); return false; } boolean closePetals() { if (servoPosition <= SERVO_CLOSED) { return true; } servoPosition --; servo.write(servoPosition); return false; } // utility function void pixelsUnifiedColor(uint32_t color) { for (unsigned int i = 0; i < pixels.numPixels(); i++) { pixels.setPixelColor(i, color); } pixels.show(); } void prepareCrossFade(byte red, byte green, byte blue, unsigned int duration) { float rchange = red - currentRGB[RED]; float gchange = green - currentRGB[GREEN]; float bchange = blue - currentRGB[BLUE]; changeRGB[RED] = rchange / (float) duration; changeRGB[GREEN] = gchange / (float) duration; changeRGB[BLUE] = bchange / (float) duration; newRGB[RED] = red; newRGB[GREEN] = green; newRGB[BLUE] = blue; Serial.print(newRGB[RED]); Serial.print(" "); Serial.print(newRGB[GREEN]); Serial.print(" "); Serial.print(newRGB[BLUE]); Serial.print(" ("); Serial.print(changeRGB[RED]); Serial.print(" "); Serial.print(changeRGB[GREEN]); Serial.print(" "); Serial.print(changeRGB[BLUE]); Serial.println(")"); } boolean crossFade() { if (currentRGB[RED] == newRGB[RED] && currentRGB[GREEN] == newRGB[GREEN] && currentRGB[BLUE] == newRGB[BLUE]) { return true; } for (byte i = 0; i < 3; i++) { if (changeRGB[i] > 0 && currentRGB[i] < newRGB[i]) { currentRGB[i] = currentRGB[i] + changeRGB[i]; } else if (changeRGB[i] < 0 && currentRGB[i] > newRGB[i]) { currentRGB[i] = currentRGB[i] + changeRGB[i]; } else { currentRGB[i] = newRGB[i]; } } pixelsUnifiedColor(pixels.Color(currentRGB[RED], currentRGB[GREEN], currentRGB[BLUE])); /* Serial.print(currentRGB[RED]); Serial.print(" "); Serial.print(currentRGB[GREEN]); Serial.print(" "); Serial.print(currentRGB[BLUE]); Serial.println(); */ return false; } uint32_t colorWheel(byte wheelPos) { // Input a value 0 to 255 to get a color value. // The colours are a transition r - g - b - back to r. wheelPos = 255 - wheelPos; if (wheelPos < 85) { return pixels.Color(255 - wheelPos * 3, 0, wheelPos * 3); } if (wheelPos < 170) { wheelPos -= 85; return pixels.Color(0, wheelPos * 3, 255 - wheelPos * 3); } wheelPos -= 170; return pixels.Color(wheelPos * 3, 255 - wheelPos * 3, 0); }
代码有点乱,可以留言,我发送arduino的ino文件也可。