粒子 发表于 2018-8-6 18:42:08

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.jsAutodesk 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);    }    pixels.show();}
#define BUFFPIXEL 20
void bmpDraw(char *filename, int16_t x, int16_t y) {
File   bmpFile;int      bmpWidth, bmpHeight;   // W+H in pixelsuint8_tbmpDepth;            // Bit depth (currently must be 24)uint32_t bmpImageoffset;      // Start of image data in fileuint32_t rowSize;               // Not always = bmpWidth; may have paddinguint8_tsdbuffer; // pixel buffer (R+G+B per pixel)uint8_tbuffidx = sizeof(sdbuffer); // Current position in sdbufferbooleangoodBmp = false;       // Set to true on valid header parsebooleanflip    = true;      // BMP is stored bottom-to-topint      w, h, row, col, x2, y2, bx1, by1;uint8_tr, 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 cardbmpFile = SD.open(filename);/*if ((bmpFile = SD.open(filename)) == NULL) {    Serial.print(F("File not found"));    return;}*/
// Parse BMP headerif(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;            g = sdbuffer;            r = sdbuffer;            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) = f.read(); // LSB((uint8_t *)&result) = f.read(); // MSBreturn result;}
uint32_t read32(File &f) {uint32_t result;((uint8_t *)&result) = f.read(); // LSB((uint8_t *)&result) = f.read();((uint8_t *)&result) = f.read();((uint8_t *)&result) = f.read(); // MSBreturn 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{      width: 100px;      height: 40px;      background-color: white;      border-color: black;      border-radius: 4px;      margin-left: 30px;}
#LED_change>input: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;}

原文翻译自:https://www.dfrobot.com/blog-1007.html

gray6666 发表于 2018-8-14 10:49:48

好棒。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

gada888 发表于 2018-8-25 22:00:33

好强的教程

发表于 2022-5-10 09:08:38

不错不错。。。。。。。。。。。。。。。。

发表于 2022-5-10 09:17:05

代码要整理一下
页: [1]
查看完整版本: ULTIMATE口香糖机