7501| 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; } |
© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed