| 
 
 9299| 4
 
 | 
ULTIMATE口香糖机 | 

| 
 本帖最后由 粒子 于 2018-8-6 18:47 编辑  LED、WiFi、自动弹出、LCD屏幕都备齐了。 这款口香糖球机能与客户通过网页互动。 ![]() 硬件部件 2.8英寸TFT触摸屏,带4MB闪存,用于Arduino和medde  Teensy 3.5  ESP8266 Thing - 开发板  WS2812 LED灯带  白色街机按钮 用于3D打印机的混合式步进电机  用于Theremino系统的步进电机的驱动器DRV8825  软件应用程序和在线服务 Node.js   Autodesk Fusion 360  Arduino IDE  手工工具和制造机器  3D 打印机(通用)  烙铁(通用)  数控雕刻机  竖锯  设想 Ultimate是什么?无限RGB? 一个很酷的LCD触摸屏怎么样? 甚至是一些完全不必要的WiFi功能?所有这些都在一台口香糖球机里,怎么样? ![]() ![]() 设计  像往常一样,几乎所有比制作一些简单连接和基本盒子更复杂的东西都需要在 Fusion 360 中进行设计。 我开始草拟我希望机器看起来像什么。 它需要很高,有足够的空间容纳所有的电子设备,也能支撑12磅口香糖球的重量。 ![]() ![]() 于是我试着做了一个简单而优雅的弹出机构。 它一次只能弹出一个口香糖球,不会被卡住,不要让一个以上的口香糖球从它转动的地方掉下来。 我意识到我所需要的只是一个有4个孔的简单轮子,并且弹出孔的顶部将具有盖子,以防止多余的口香糖球掉落。 ![]() ![]() 设计完成后,我导出了所有可3D打印零件和生成的刀具路径, 用于在外壳上进行数控雕刻。 外壳和制造 我从收集口香糖球胶球机腿的尺寸开始,然后在一张巨大的胶合板上勾画出来。 然后我拿起竖锯,锯出四条腿。 我还用数控雕刻机,用胶合板锯出主外壳。 ![]() ![]() 然后,我在所有的东西上钻孔,把它涂成红色。 ![]() ![]() 将LED灯带粘在底板上,这样它就可以在机器下面的支架上发出很好的光。 ![]() 网页  为了让用户与口香糖球机进行交互,需要有一个简单的界面。 我选择了创建一个简单的网页,让用户弹出口香糖球,并改变 LED 的颜色。 动作发生后,网页通过 AJAX 将数据发布到自定义 Node.js web服务器。 ![]() Web服务器  我需要一个web服务器来充当网页用户和口香糖球机之间的中介。 因此,我决定使用Node.js发送和接收数据。 用户发送POST请求以控制LED颜色和弹出。然后,ESP8266发送GET请求以获取机器的状态。 如果有人不断点击“弹出”会发生什么?服务器跟踪点击“弹出”按钮的所有IP,并阻止他们两次弹出。 电子器件  TFT屏幕需要很大的处理能力来驱动,所以我不得不选择快速且功能强大的主板,引导我使用Teensy 3.5。 但现在你可能会想:“Teensy如何使用WiFi?”这是很难解决的问题。 我需要让Teensy 侦听本地服务器,以了解用户所做的更改。 然后,我突然脑洞大开,只用ESP8266来检查服务器,在通过串口与Teensy通信,这让事情变得容易多了。 ![]() ![]() ![]() 软件  Teensy运行一个简单的脚本,首先从SD卡加载图像,并在屏幕上显示。 然后检查串行数据,看看是否需要改变LED的颜色或弹出。 使用方法  使用口香糖球机非常简单:只需转到网页并单击“弹出”按钮。 或者跟简单些,按一下上方的按钮,就能够到你应得的奖品。 ![]() 代码 1.Teensy CodeC/C++ #define TFT_CS 2 #define TFT_DC 5 #define SD_CS   BUILTIN_SDCARD #define DIR_PIN 16 #define STEP_PIN 17 #define LED_PIN 20 #define BUTTON_PIN 30 #define NUMPIXELS 20 #define RPM 60 #define MOTOR_STEPS 200 #define MICROSTEPS 1 //#define rxPin 36 //#define txPin 37 #include <SPI.h> #include <SD.h> #include <Adafruit_ILI9341.h> #include <Adafruit_GFX.h> #include <Adafruit_NeoPixel.h> #include <Arduino.h> #include "BasicStepperDriver.h" //#include <SoftwareSerial.h> Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); int LEDColors[] = {pixels.Color(255,0,0),pixels.Color(0,255,0),pixels.Color(0,0,255),pixels.Color(200,0,200),pixels.Color(0,0,0)}; BasicStepperDriver stepper(MOTOR_STEPS, DIR_PIN, STEP_PIN); //SoftwareSerial Serial4 = SoftwareSerial(rxPin, txPin); bool button_pressed = false; Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); void setup(){     //pinMode(rxPin, INPUT);     //pinMode(txPin, OUTPUT);     Serial.begin(9600);     Serial4.begin(9600);     delay(500);     SPI.setMOSI(7);     SPI.setSCK(14);     if(SD.begin(SD_CS)){       Serial.println("Success");       File entry = SD.open("MAINPAGE.BMP");       if(entry != NULL){       Serial.println(entry.name());       }       entry.close();   }   stepper.begin(RPM, MICROSTEPS);   tft.begin();   pixels.begin();   tft.setRotation(1);   pixels.setBrightness(75);   pinMode(BUTTON_PIN,INPUT_PULLUP);   attachInterrupt(BUTTON_PIN,set_button,FALLING);   tft.setTextColor(0x0000);   tft.fillScreen(0xFFFF);   tft.setCursor(0,0);   tft.print("HI");   setAllPixels(0);   //displayDispensing();   delay(3000);   loadMainPage(); } elapsedMillis timer1; void loop(){     if(button_pressed){         button_pressed = false;         dispense();     }     if(Serial4.available()){         String cmd = Serial4.readStringUntil(',');         Serial.print(cmd);         Serial.print(',');         if(cmd=="LED"){             String color = Serial4.readStringUntil('\n');             Serial.println(color);             if(color=="RED\r"){                 setAllPixels(0);             }             else if(color=="GREEN\r"){                 setAllPixels(1);             }             else if(color=="BLUE\r"){                 setAllPixels(2);             }             else if(color=="PURPLE\r"){                 setAllPixels(3);             }             else if(color=="BLACK\r"){                 setAllPixels(4);             }         }         else if(cmd=="DISPENSE"){             String cmd2 = Serial4.readStringUntil('\n');             Serial.println(cmd2);             if(cmd2=="true\r"){                 if(timer1>5000){                 //displayDispensing();                 dispense();                 }             }         }     } } void loadMainPage(){     tft.fillScreen(0xFFFF);     bmpDraw("MAINPAGE.BMP",0,0); } void displayDispensing(){     tft.fillScreen(0xFFFF);     tft.setTextSize(3);     tft.setCursor(90,30);     tft.print("Dispensing \n");     tft.setCursor(100,80);     tft.print("gumball!");     delay(3000);     loadMainPage(); } void dispense(){     Serial.println("Dispensing");     displayDispensing();     stepper.move(259);     delay(3000);     loadMainPage(); } void set_button(){     button_pressed = true;     delay(200); } void setAllPixels(int colorNum){     Serial.print("Setting pixels to ");Serial.println(colorNum);     for(int i=0;i<NUMPIXELS;i++){         pixels.setPixelColor(i, LEDColors[colorNum]);     }     pixels.show(); } #define BUFFPIXEL 20 void bmpDraw(char *filename, int16_t x, int16_t y) {   File     bmpFile;   int      bmpWidth, bmpHeight;   // W+H in pixels   uint8_t  bmpDepth;              // Bit depth (currently must be 24)   uint32_t bmpImageoffset;        // Start of image data in file   uint32_t rowSize;               // Not always = bmpWidth; may have padding   uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)   uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer   boolean  goodBmp = false;       // Set to true on valid header parse   boolean  flip    = true;        // BMP is stored bottom-to-top   int      w, h, row, col, x2, y2, bx1, by1;   uint8_t  r, g, b;   uint32_t pos = 0, startTime = millis();   if((x >= tft.width()) || (y >= tft.height())) return;   Serial.println();   Serial.print(F("Loading image '"));   Serial.print(filename);   Serial.println('\'');   // Open requested file on SD card   bmpFile = SD.open(filename);   /*if ((bmpFile = SD.open(filename)) == NULL) {     Serial.print(F("File not found"));     return;   }*/   // Parse BMP header   if(read16(bmpFile) == 0x4D42) { // BMP signature     Serial.print(F("File size: ")); Serial.println(read32(bmpFile));     (void)read32(bmpFile); // Read & ignore creator bytes     bmpImageoffset = read32(bmpFile); // Start of image data     Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);     // Read DIB header     Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));     bmpWidth  = read32(bmpFile);     bmpHeight = read32(bmpFile);     if(read16(bmpFile) == 1) { // # planes -- must be '1'       bmpDepth = read16(bmpFile); // bits per pixel       Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);       if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed         goodBmp = true; // Supported BMP format -- proceed!         Serial.print(F("Image size: "));         Serial.print(bmpWidth);         Serial.print('x');         Serial.println(bmpHeight);         // BMP rows are padded (if needed) to 4-byte boundary         rowSize = (bmpWidth * 3 + 3) & ~3;         // If bmpHeight is negative, image is in top-down order.         // This is not canon but has been observed in the wild.         if(bmpHeight < 0) {           bmpHeight = -bmpHeight;           flip      = false;         }         // Crop area to be loaded         x2 = x + bmpWidth  - 1; // Lower-right corner         y2 = y + bmpHeight - 1;         if((x2 >= 0) && (y2 >= 0)) { // On screen?           w = bmpWidth; // Width/height of section to load/display           h = bmpHeight;           bx1 = by1 = 0; // UL coordinate in BMP file           if(x < 0) { // Clip left             bx1 = -x;             x   = 0;             w   = x2 + 1;           }           if(y < 0) { // Clip top             by1 = -y;             y   = 0;             h   = y2 + 1;           }           if(x2 >= tft.width())  w = tft.width()  - x; // Clip right           if(y2 >= tft.height()) h = tft.height() - y; // Clip bottom           // Set TFT address window to clipped image bounds           tft.startWrite(); // Requires start/end transaction now           tft.setAddrWindow(x, y, w, h);           for (row=0; row<h; row++) { // For each scanline...             // Seek to start of scan line.  It might seem labor-             // intensive to be doing this on every line, but this             // method covers a lot of gritty details like cropping             // and scanline padding.  Also, the seek only takes             // place if the file position actually needs to change             // (avoids a lot of cluster math in SD library).             if(flip) // Bitmap is stored bottom-to-top order (normal BMP)               pos = bmpImageoffset + (bmpHeight - 1 - (row + by1)) * rowSize;             else     // Bitmap is stored top-to-bottom               pos = bmpImageoffset + (row + by1) * rowSize;             pos += bx1 * 3; // Factor in starting column (bx1)             if(bmpFile.position() != pos) { // Need seek?               tft.endWrite(); // End TFT transaction               bmpFile.seek(pos);               buffidx = sizeof(sdbuffer); // Force buffer reload               tft.startWrite(); // Start new TFT transaction             }             for (col=0; col<w; col++) { // For each pixel...               // Time to read more pixel data?               if (buffidx >= sizeof(sdbuffer)) { // Indeed                 tft.endWrite(); // End TFT transaction                 bmpFile.read(sdbuffer, sizeof(sdbuffer));                 buffidx = 0; // Set index to beginning                 tft.startWrite(); // Start new TFT transaction               }               // Convert pixel from BMP to TFT format, push to display               b = sdbuffer[buffidx++];               g = sdbuffer[buffidx++];               r = sdbuffer[buffidx++];               tft.writePixel(tft.color565(r,g,b));             } // end pixel           } // end scanline           tft.endWrite(); // End last TFT transaction         } // end onscreen         Serial.print(F("Loaded in "));         Serial.print(millis() - startTime);         Serial.println(" ms");       } // end goodBmp     }   }   bmpFile.close();   if(!goodBmp) Serial.println(F("BMP format not recognized.")); } uint16_t read16(File &f) {   uint16_t result;   ((uint8_t *)&result)[0] = f.read(); // LSB   ((uint8_t *)&result)[1] = f.read(); // MSB   return result; } uint32_t read32(File &f) {   uint32_t result;   ((uint8_t *)&result)[0] = f.read(); // LSB   ((uint8_t *)&result)[1] = f.read();   ((uint8_t *)&result)[2] = f.read();   ((uint8_t *)&result)[3] = f.read(); // MSB   return result; } 2. ESP8266 CodeC/C++ #include <Arduino.h> #include <ESP8266WiFi.h> #include <ESP8266WiFiMulti.h> #include <ESP8266HTTPClient.h> ESP8266WiFiMulti WiFiMulti; void setup(){     Serial.begin(9600);     WiFiMulti.addAP("SSID", "PSK"); } void loop(){     if((WiFiMulti.run() == WL_CONNECTED)){         HTTPClient http;         http.begin("http://local_ip (change these values):3010/status/led");         int httpCode = http.GET();         String payload = http.getString();         if(payload){             Serial.print("LED,");             Serial.println(payload);         }         http.end();         http.begin("http://local_ip:3010/status/dispense");         httpCode = http.GET();         payload = http.getString();         if(payload){             Serial.print("DISPENSE,");             Serial.println(payload);             if(payload=="true"){                 delay(4000);             }         }         http.end();         delay(1000);     } } 3.Node JS Server Code var express = require('express'); var myParser = require('body-parser'); var app = express(); const cors = require('cors'); var latestColor = "RED"; var dispense_active = false; var usedIPs = []; const whitelist = ['::ffff:local_ip'] app.use(myParser.json({extended: true})); app.use(cors()); app.options('*',cors()); app.post("/gumball", function(request, response){     console.log(request.body);     response.send("OK, 200");     if(typeof request.body.LED !=='undefined'){         latestColor = request.body.LED;     }      if(typeof request.body.DISPENSE !=='undefined'){         dispense_active = request.body.DISPENSE;         if(dispense_active = true){             if(usedIPs.indexOf(request.ip)==-1){             usedIPs.push(request.ip);             console.log(usedIPs);             }             else if(whitelist.indexOf(request.ip)>-1){                 dispense_active = true;             }             else{                 dispense_active = false;             }         }     } }); app.get('/status/dispense',function(req,res){     res.send(dispense_active);     dispense_active = false; }); app.get('/status/led',function(req,res){     res.send(latestColor); }); app.listen(3010); 4.Webpage main HTML <html>         <head>         <title>IoT Gumball Machine</title>         <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js">         </script>         <link rel="stylesheet" type="text/css" href="style.css">         </head> <body>         <script>                 function changeLED(color){                         console.log(color.toUpperCase());                         var colorName = color.toUpperCase();                         var obj = {"LED": colorName};                         $.ajax('http://local_ip:3010/gumball',{                                 data: JSON.stringify(obj),                                 contentType: 'application/json',                                 type: 'POST'                         });                 }                 function dispense(){                         console.log("Dispensing");                         var obj = {"DISPENSE": true};                         $.ajax('http://local_ip (change these values):3010/gumball',{                                 data: JSON.stringify(obj),                                 contentType: 'application/json',                                 type: 'POST'                         });                 }                 function changeColor(color){ document.getElementById("color_list").style.color = color;                         console.log(color);                 }         </script>         <div class="centered">         <form id="LED_change">                 <select id="color_list" name="color_list">                 <option class="sRed" value="red">Red</option>                 <option class="sGreen" value="green">Green</option>                 <option class="sBlue" value="blue">Blue</option>                 <option class="sPurple" value="purple">Purple</option>                 <option class="sOff" value="black">Off</option>                 </select>                 <input type=submit value="Change color">         </form>         <button id="dButton">Dispense gumball</button>         </div> </body> </html> 5. Webpage CSS #dButton{         width: 200px;         height: 100px;         font-size: 18px;         color: black;         background-color: white;         border-color: lightgray;         border-radius: 18px;         margin:30px 10px; } #dButton:hover{         cursor: pointer; } select{         width: 100px;         font-size: 18px; } #LED_change>input[type=submit]{         width: 100px;         height: 40px;         background-color: white;         border-color: black;         border-radius: 4px;         margin-left: 30px; } #LED_change>input[type=submit]:hover{         cursor: pointer; } .centered{         position: fixed;         top: 40%;         left: 40%; } .sRed{         color: red; } .sGreen{         color: green; } .sBlue{         color: blue; } .sPurple{         color: purple; } .sOff{         color: black; } body{         font-family: Verdana;         font-size: 20px; }  | 
编辑选择奖
                          
 沪公网安备31011502402448© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed