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
好棒。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 好强的教程 不错不错。。。。。。。。。。。。。。。。 代码要整理一下
页:
[1]