My MSLA printer finally works. It prints! "It actually prints! And surprisingly, without summoning a demon or tripping the circuit breaker... yet. This is my most complete prototype of MSLA printer I’ve made so far. And it only costs 40 dollars. That’s roughly the price of a fancy pizza, but instead of calories, you get the smell of resin and potential UV burns. Check the part list below! The PCB for today's project looks awesome and works great.
by: ELECTRONOOBS on 2026-07-23
It has everything. And I’m sharing everything for free with you guys so get the PCB, the code and all the files from below.
The PCB looks awesome and the good thing is that is has a connection for everything. The entire brain of this printer will be on this cool PCB made by PCBWAY in the most professional way. Thanks PCBWAY! Go to PCBWAY.com and order your PCBs in just a couple of minutes.

Download my schematic as well. Follow all these values and components and solder everything. The only thing I’ve changed was to add a buck converter instead of an LDO for 5V because I prefer my components cool, not medium-rare. "I went with the ESP32 S3 because I have a serious 'more pins is better' addiction. It’s basically the same price, so why not have extra pins to accidentally short-circuit later?

To save money this time I’ve made an all in one PCB. It has everything except a coffee maker. It has its own power supply, power regulation, the ESP32 S3, OLED display, the printing display, buttons for control, the SD reader, RGB LEDs, MOSFET or SSR power driver for the UV light, step motor driver, endstop switch, extra ports, and a buzzer to scream at me when I mess up. The PCB is of 100 by 100mm and is made to sandwich together with the PCB of the display I will be using

Ok, now we assemble the case. These are the 3D files. It has a bunch of parts but everything was well designed to fit in place perfectly, add the needed support but also look cool. The TFT screen is made to go directly on the top plate of the printer. Try to be gentle here, if you drop it, you'll be back on AliExpress faster than you can say 'shipping costs?
The code is quite long. Make sure you install the ESP32 S3 Dev module boards, install the needed libraries, flip the UART switch on the PCB and upload the code.
#include <SPI.h>
#include <SD.h>
#include <TFT_eSPI.h>
#include <PNGdec.h>
#include <unzipLIB.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "FastAccelStepper.h"
#include <Adafruit_NeoPixel.h>
#include <Preferences.h>
Preferences pref;
#define Z_EN_PIN 38
#define Z_STEP_PIN 18
#define Z_DIR_PIN 21
#define Z_ENDSTOP 48
#define UV_MOSFET 41
#define BUZZER 45
#define WS2812_PIN 42
#define BTN_UP 20
#define BTN_OK 19
#define BTN_DWN 47
#define OLED_SDA 39
#define OLED_SCL 40
#define SD_CS 10
#define SD_MOSI 11
#define SD_SCK 12
#define SD_MISO 13
#define RX_NEXTION 0
#define TX_NEXTION 9
Adafruit_NeoPixel led(1, WS2812_PIN, NEO_GRB + NEO_KHZ800);
// --- Fixed Variables ---
int capaActual = 0;
int totalCapas = 0;
String SelectedFile = "";
String listaArchivos[20]; // Máximo 20 archivos zip
String listaMenu1[3] = {"PRINT","CONFIG","MOVE"};
String listaCancel[3] = {"CANCEL","RESUME","CONFIG"};
String listaMove[7] = {"Up 100mm","Up 10mm","Up 1mm","Down 100mm","Down 10mm","Down 1mm", "Go Back"};
String listaConfig[7] = {"Beep ON","Step Speed","Step Accel","Layer Height","Exposion Time","Set Default", "Go Back"};
int totalArchivos = 0;
int indiceMenu = 0;
char Timebuffer[10];
// --- Variables used in code ---
unsigned long tiempoInicioExposicion = 0;
unsigned long prevLayerTime = 0;
unsigned long elapsedTime = 0;
unsigned long startTime = 0;
unsigned long refreshTimePrev = 0;
unsigned long prevPauseTime = 0;
volatile unsigned long ultimaInterrupcion = 0;
long ledfade = 0;
int menu_level = 1;
long stepsToMake = 0;
long remainingDistance = 0;
int filescroll = 0;
float HeightZ = 0;
int selectedConfig = 0;
int selectedDigit = 0;
int selectedDigits = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
// --- Variables de Estado ---
bool printing = false;
bool BTN_UP_State = true;
bool BTN_DWN_State = true;
bool BTN_OK_State = true;
bool BTN_OK_PRESSED = false;
bool PUSHED = false;
bool UNPUSHED = true;
bool homed = false;
bool enPausa = false;
bool imagePrepared = false;
bool luzUvEncendida = false;
bool luzUvApagada = false;
bool rising = false;
bool rising_slow = false;
bool lowering = false;
bool lowering_slow = false;
bool moving = false;
//volatile bool homing = false;
volatile bool homingFinished = false;
volatile int faseHoming = 0;
bool manualHoming = false;
// --- Constant Variables Defined by Printer---
const int debounceTime = 250; // Interrupt debounce in ms (ex: 250ms = 0.25s)
const long tExposicion_Min = 1000; // Min UV exposure time in ms (ex: 1000ms = 1s)
const long tExposicion_Max = 50000; // Max UV exposure time in ms (ex: 1000ms = 1s)
const int stepperSpeed_Max = 45000; // Max speed in steps/s
const int stepperSpeed_Min = 100; // Min speed in steps/s
const int stepperAccel_Max = 12000; // Max acceleration in steps/s^2
const int stepperAccel_Min = 100; // Min acceleration in steps/s^2
const int layerHeight_Max = 500; // In micrometers (ex: 50um=0.05mm)
const int layerHeight_Min = 20; // In micrometers (ex: 50um=0.05mm)
const float HeightZ_Max = 300; // Maximum Z height in mm
const float stepsPerRot = 200.0; // Nema 17 makes 1.8°/step so we need 200 steps for 360°
const float mmPerRot = 2.0; // how much mm the lead screw makse for 360°
const int microstepping = 32; // How you configured the TMC2225 driver
// Automatic calculation for steps/mm
float stepsPerMM = (stepsPerRot * microstepping) / mmPerRot;
bool UV_ON = LOW; // SSR turns on with a low signal
bool UV_OFF= HIGH; // SSR turns off with a high signal
#define NOTE_C5 523
#define NOTE_E5 659
#define NOTE_G5 784
#define NOTE_C6 1047
#define NOTE_A5 880
// --- User Variables ---
long tExposicion = 10000; // 10 segundos, unfortunelly RBG LCDs block 70% of UV light :(
long tExposicionFL = tExposicion*5; // 50 segundos for the first 4-5 layers
float layerHeight = 50; //Printed layer thickness in mm
int stepperAccel = 800; //Default stepper acceleration
int stepperSpeed = 2000; //Default stepper speed
float layerChangeDistance = 5; //Up and down distance on layer change in mm
bool beep_active = true; //Buzzer active or not
// --- Configuración Movimiento ---
FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepperZ = NULL;
// --- Configuración Pantallas ---
TFT_eSPI tft = TFT_eSPI();
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
PNG png;
UNZIP zip;
// --- Callbacks SD (Indispensables para unzipLIB) ---
File zFile;
void * myOpen(const char *fn, int32_t *s) { zFile = SD.open(fn); if (!zFile) return NULL; *s = (int32_t)zFile.size(); return (void *)&zFile; }
void myClose(void *p) { if (zFile) zFile.close(); }
int32_t myRead(void *p, uint8_t *b, int32_t l) { return zFile.read(b, l); }
int32_t mySeek(void *p, int32_t pos, int type) { if (type == SEEK_SET) return zFile.seek(pos); return zFile.seek(zFile.position() + pos); }
// Colores predefinidos (R, G, B)
void colorLED(uint8_t r, uint8_t g, uint8_t b) {
led.setPixelColor(0, led.Color(r, g, b));
led.show();
}
void listarZips() {
File root = SD.open("/");
totalArchivos = 0;
while (true) {
File entry = root.openNextFile();
if (!entry) break;
String name = entry.name();
if (name.endsWith(".zip") || name.endsWith(".ZIP")) {
listaArchivos[totalArchivos] = "/" + name;
totalArchivos++;
}
entry.close();
if (totalArchivos >= 20) break;
}
root.close();
}
void scrollMenu() {
oled.clearDisplay();
oled.setTextSize(2);
oled.setCursor(0, 0);
// Definimos cuántas líneas caben (ejemplo: 5 líneas de archivos tras el encabezado)
const int lineasMaximas = 3;
// Calculamos el inicio de la ventana de visualización
int inicioVentana = 0;
if (indiceMenu >= lineasMaximas) {
inicioVentana = indiceMenu - lineasMaximas + 1;
}
// Solo imprimimos los archivos que caben en la ventana
for (int i = inicioVentana; i < 3 && i < (inicioVentana + lineasMaximas); i++) {
if (i == indiceMenu) oled.print("> ");
else oled.print(" ");
String nombreMostrar = listaMenu1[i];
// Truncar si es muy largo (como hicimos antes)
if (nombreMostrar.length() > 18) {
nombreMostrar = nombreMostrar.substring(0, 15) + "...";
}
oled.println(nombreMostrar);
}
// Opcional: Dibujar una pequeña flecha si hay más archivos abajo
if (inicioVentana + lineasMaximas < totalCapas) {
oled.setCursor(120, 56); oled.print("v");
}
oled.display();
}
void scrollConfig(){
oled.clearDisplay();
oled.setTextSize(1);
oled.setCursor(0, 0);
const int lineasMaximas = 7;
// Calculamos el inicio de la ventana de visualización
int inicioVentana = 0;
if (indiceMenu >= lineasMaximas) {
inicioVentana = indiceMenu - lineasMaximas + 1;
}
// Solo imprimimos los archivos que caben en la ventana
for (int i = inicioVentana; i < 7 && i < (inicioVentana + lineasMaximas); i++) {
if (i == indiceMenu) oled.print("> ");
else oled.print(" ");
String nombreMostrar = listaConfig[i];
// Truncar si es muy largo (como hicimos antes)
if (nombreMostrar.length() > 18) {
nombreMostrar = nombreMostrar.substring(0, 15) + "...";
}
oled.println(nombreMostrar);
}
oled.display();
}
void scrollMove(){
oled.clearDisplay();
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.print("Height: ");
oled.print(HeightZ);
oled.println(" mm");
const int lineasMaximas = 7;
// Calculamos el inicio de la ventana de visualización
int inicioVentana = 0;
if (indiceMenu >= lineasMaximas) {
inicioVentana = indiceMenu - lineasMaximas + 1;
}
// Solo imprimimos los archivos que caben en la ventana
for (int i = inicioVentana; i < 7 && i < (inicioVentana + lineasMaximas); i++) {
if (i == indiceMenu) oled.print("> ");
else oled.print(" ");
String nombreMostrar = listaMove[i];
// Truncar si es muy largo (como hicimos antes)
if (nombreMostrar.length() > 18) {
nombreMostrar = nombreMostrar.substring(0, 15) + "...";
}
oled.println(nombreMostrar);
}
oled.display();
}
void setConfig(){
if(selectedConfig == 0){
oled.clearDisplay();
oled.setCursor(0, 0);
oled.setTextSize(1);
oled.setTextColor(BLACK,WHITE);
oled.println("STEP SPEED (steps)");
oled.setCursor(0, 20);
oled.setTextSize(2);
for(int digit=selectedDigits-1; digit>=0; digit--){
if(digit == selectedDigit){
oled.setTextColor(BLACK,WHITE);
}
else{
oled.setTextColor(WHITE);
}
int curDigit = (stepperSpeed / (int)pow(10, digit)) % 10;
oled.print(curDigit);
}
}
else if(selectedConfig == 1){
oled.clearDisplay();
oled.setCursor(0, 0);
oled.setTextSize(1);
oled.setTextColor(BLACK,WHITE);
oled.println("STEP ACCEL (steps)");
oled.setCursor(0, 20);
oled.setTextSize(2);
for(int digit=selectedDigits-1; digit>=0; digit--){
if(digit == selectedDigit){
oled.setTextColor(BLACK,WHITE);
}
else{
oled.setTextColor(WHITE);
}
int curDigit = (stepperAccel / (int)pow(10, digit)) % 10;
oled.print(curDigit);
}
}
else if(selectedConfig == 2){
oled.clearDisplay();
oled.setCursor(0, 0);
oled.setTextSize(1);
oled.setTextColor(BLACK,WHITE);
oled.println("LAYER HEIGHT (um)");
oled.setCursor(0, 20);
oled.setTextSize(2);
for(int digit=selectedDigits-1; digit>=0; digit--){
if(digit == selectedDigit){
oled.setTextColor(BLACK,WHITE);
}
else{
oled.setTextColor(WHITE);
}
int curDigit = (int(layerHeight) / (int)pow(10, digit)) % 10;
oled.print(curDigit);
}
}
else if(selectedConfig == 3){
oled.clearDisplay();
oled.setCursor(0, 0);
oled.setTextSize(1);
oled.setTextColor(BLACK,WHITE);
oled.println("EXPOSURE TIME (ms)");
oled.setCursor(0, 20);
oled.setTextSize(2);
for(int digit=selectedDigits-1; digit>=0; digit--){
if(digit == selectedDigit){
oled.setTextColor(BLACK,WHITE);
}
else{
oled.setTextColor(WHITE);
}
int curDigit = (tExposicion / (int)pow(10, digit)) % 10;
oled.print(curDigit);
}
}
oled.display();
}
void showPause(){
oled.clearDisplay();
oled.setCursor(0, 0);
oled.setTextSize(2);
oled.setTextColor(BLACK,WHITE);
oled.println(" PAUSE ");
oled.setTextSize(1);
oled.setTextColor(WHITE);
// Definimos cuántas líneas caben (ejemplo: 5 líneas de archivos tras el encabezado)
const int lineasMaximas = 5;
// Calculamos el inicio de la ventana de visualización
int inicioVentana = 0;
if (indiceMenu >= lineasMaximas) {
inicioVentana = indiceMenu - lineasMaximas + 1;
}
// Solo imprimimos los archivos que caben en la ventana
for (int i = inicioVentana; i < 3 && i < (inicioVentana + lineasMaximas); i++) {
if (i == indiceMenu) oled.print("> ");
else oled.print(" ");
String nombreMostrar = listaCancel[i];
// Truncar si es muy largo (como hicimos antes)
if (nombreMostrar.length() > 18) {
nombreMostrar = nombreMostrar.substring(0, 15) + "...";
}
oled.println(nombreMostrar);
}
// Opcional: Dibujar una pequeña flecha si hay más archivos abajo
if (inicioVentana + lineasMaximas < totalCapas) {
oled.setCursor(120, 56); oled.print("v");
}
oled.display();
}
void scrollFiles() {
oled.clearDisplay();
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.println("SELECCIONAR ARCHIVO:");
oled.println("--------------------");
// Definimos cuántas líneas caben (ejemplo: 5 líneas de archivos tras el encabezado)
const int lineasMaximas = 5;
// Calculamos el inicio de la ventana de visualización
int inicioVentana = 0;
if (indiceMenu >= lineasMaximas) {
inicioVentana = indiceMenu - lineasMaximas + 1;
}
// Solo imprimimos los archivos que caben en la ventana
for (int i = inicioVentana; i < totalArchivos && i < (inicioVentana + lineasMaximas); i++) {
if (i == indiceMenu) oled.print("> ");
else oled.print(" ");
String nombreMostrar = listaArchivos[i].substring(1);
// Truncar si es muy largo (como hicimos antes)
if (nombreMostrar.length() > 18) {
nombreMostrar = nombreMostrar.substring(0, 15) + "...";
}
oled.println(nombreMostrar);
}
// Opcional: Dibujar una pequeña flecha si hay más archivos abajo
if (inicioVentana + lineasMaximas < totalCapas) {
oled.setCursor(120, 56); oled.print("v");
}
oled.display();
}
// --- Función de Dibujo para el LCD ---
int pngDraw(PNGDRAW *pDraw) {
uint16_t lineBuffer[800];
png.getLineAsRGB565(pDraw, lineBuffer, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
tft.pushImage(0, pDraw->y, pDraw->iWidth, 1, lineBuffer);
return 1;
}
// --- Funciones de Audio No Bloqueantes ---
void beepSound(int freq, int dur) {
tone(BUZZER, freq, dur);
}
void melodySound(int tipo) {
if (tipo == 1) { // MELODÍA ÉXITO (Homing OK / Inicio)
int notas[] = {NOTE_C5, NOTE_E5, NOTE_G5, NOTE_C6};
int duraciones[] = {100, 100, 100, 200};
for (int i = 0; i < 4; i++) {
tone(BUZZER, notas[i], duraciones[i]);
delay(duraciones[i] + 20); // Pequeña pausa entre notas
}
}
else if (tipo == 2) { // MELODÍA FINAL (Impresión terminada)
int notas[] = {NOTE_G5, NOTE_A5, NOTE_G5, NOTE_E5, NOTE_C5};
int duraciones[] = {150, 150, 150, 150, 400};
for (int i = 0; i < 5; i++) {
tone(BUZZER, notas[i], duraciones[i]);
delay(duraciones[i] + 30);
}
}
noTone(BUZZER); // Asegurar que el pin quede en silencio
}
// --- Gestión de Movimiento con Aceleración ---
void homingZ() {
if(faseHoming == 0){
Serial.println("Starting new homing process...");
homed = false; //Reset homed variable
faseHoming = 1; //Activate homing for core 1
homingFinished = false; //Reset homingFinished variable
}
}
void contarCapas() {
totalCapas = 0;
// 1. Cerramos CUALQUIER rastro del sistema de archivos
zFile.close();
// Quitar espacios en blanco que puedan haberse colado
SelectedFile.trim();
// Verificación Crítica: ¿La SD responde bajo UART?
File test = SD.open(SelectedFile.c_str());
if (!test) {
Serial.printf("CRÍTICO: El sistema de archivos no puede abrir [%s]\n", SelectedFile.c_str());
return;
}
Serial.printf("Archivo detectado físicamente. Tamaño: %d\n", test.size());
test.close();
// 3. Intento de apertura con la librería
int rc = zip.openZIP(SelectedFile.c_str(), myOpen, myClose, myRead, mySeek);
if (rc == UNZ_OK) {
zip.gotoFirstFile();
do { totalCapas++; } while (zip.gotoNextFile() == UNZ_OK);
zip.closeZIP();
Serial.printf("Éxito: %d capas.\n", totalCapas);
} else {
Serial.printf("Error unzipLIB RC: %d\n", rc);
}
}
void IRAM_ATTR isr_BTN_OK() {
unsigned long tiempoActual = millis();
if (tiempoActual - ultimaInterrupcion > debounceTime) {
BTN_OK_PRESSED = true;
ultimaInterrupcion = tiempoActual;
}
}
// --- Definición de la Tarea del Motor ---
TaskHandle_t MotorTask;
void IRAM_ATTR MotorLoop(void * pvParameters) {
for (;;) {
switch (faseHoming) {
case 1: // FASE 1: BAJADA RÁPIDA
if (digitalRead(Z_ENDSTOP) == HIGH) {
stepperZ->setSpeedInHz(stepperSpeed);
stepperZ->setAcceleration(stepperAccel);
stepperZ->runBackward();
}
else {
stepperZ->forceStop();
faseHoming = 2; // Pasar a subida
}
break;
case 2: // FASE 2: SUBIDA DE ESCAPE (2mm)
if (!stepperZ->isRunning()) { // Esperar a que frene la fase 1
stepperZ->setSpeedInHz(stepperSpeed/3);
stepperZ->setAcceleration(stepperAccel/3);
stepperZ->move(1*stepsPerMM); //Go up 1mm
faseHoming = 10; // Estado intermedio para esperar a que termine de subir
}
break;
case 10: // ESPERAR QUE TERMINE DE SUBIR
if (!stepperZ->isRunning()) {
faseHoming = 3; // Ahora sí, bajada lenta
}
break;
case 3: // FASE 3: BAJADA LENTA (TOQUE DE PRECISIÓN)
if (digitalRead(Z_ENDSTOP) == HIGH) {
stepperZ->setSpeedInHz(stepperAccel/5); // Muy lento
stepperZ->setAcceleration(stepperAccel/5);
stepperZ->runBackward();
}
else {
stepperZ->forceStop();
stepperZ->setCurrentPosition(0); // ¡CERO REAL!
faseHoming = 0; // Volver a reposo
homingFinished = true;
}
break;
}
// vTaskDelay(1) es más estable para el Core 1 que yield()
vTaskDelay(1);
}
}
void setup() {
Serial.begin(115200);
while(!Serial && millis() < 3000); // Esperar Serial en ESP32-S3
Serial.println("\n[SISTEMA MSLA INICIADO]");
// Pines de Control
pinMode(Z_EN_PIN, OUTPUT);
pinMode(Z_ENDSTOP, INPUT_PULLUP);
pinMode(UV_MOSFET, OUTPUT);
pinMode(BUZZER, OUTPUT);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_OK, INPUT_PULLUP);
pinMode(BTN_DWN, INPUT_PULLUP);
digitalWrite(Z_EN_PIN, LOW); // Activar TMC2208
digitalWrite(UV_MOSFET, UV_OFF); // UV Apagado
pref.begin("msla_settings", true); // Modo lectura
tExposicion = pref.getLong("expo", tExposicion);
stepperAccel = pref.getInt("accel", stepperAccel);
stepperSpeed = pref.getInt("speed", stepperSpeed);
layerHeight = pref.getFloat("height", layerHeight);
beep_active = pref.getBool("beep", beep_active);
tExposicionFL = tExposicion*5;
pref.end();
// Inicializa la comunicación con Nextion en los pines 9 y 0
// Por defecto Nextion viene a 9600 baudios
Serial1.begin(9600, SERIAL_8N1, RX_NEXTION, TX_NEXTION);
Serial1.setTimeout(50);
Serial.println("Conexión con Nextion iniciada en pines 9 (RX) y 0 (TX)");
delay(10);
attachInterrupt(digitalPinToInterrupt(BTN_OK), isr_BTN_OK, FALLING);
// Pantalla OLED
Wire.begin(OLED_SDA, OLED_SCL);
Wire.setClock(400000); // 400kHz (High Speed)
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
oled.clearDisplay();
oled.setTextColor(WHITE);
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.print("booting up...");
oled.display();
engine.init();
stepperZ = engine.stepperConnectToPin(Z_STEP_PIN);
if (stepperZ) {
stepperZ->setDirectionPin(Z_DIR_PIN);
stepperZ->setEnablePin(Z_EN_PIN);
stepperZ->setAutoEnable(false);
}
// SD y Pantalla LCD
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
delay(300);
if (!SD.begin(SD_CS)) {
oled.println("No SD inserted!");
oled.display();
beepSound(2000, 50);
Serial.println("CRÍTICO: SD NO DETECTADA.");
delay(1000);
}
else {
melodySound(1);
Serial.println("SD Detectada!");
delay(100);
}
scrollMenu();
tft.begin();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
listarZips();
// 5. CREAR LA TAREA (Al final, cuando todo está listo)
xTaskCreatePinnedToCore(
MotorLoop,
"MotorTask",
10000,
NULL,
1, // Prioridad
&MotorTask,
1 // Core 1
);
}//End of void SETUP
void loop() {
if (Serial1.available()) {
// 1. Miramos el primer byte sin sacarlo del buffer
byte primerByte = Serial1.peek();
// 2. Si el byte es 0x65 (Evento Touch) o 0xFF (Terminador)
// o cualquier código de estado de Nextion (menor a un carácter 'a' o '0')
if (primerByte == 0x65 || primerByte == 0xFF || primerByte < 32) {
// Es un código de sistema, lo descartamos
Serial1.read();
return; // Salimos del loop para esperar el texto real
}
if(beep_active){
beepSound(1000,50);
}
// Leemos hasta el salto de línea
String mensaje = Serial1.readStringUntil('\n');
Serial.println(mensaje);
mensaje.trim(); // Quitamos espacios o caracteres invisibles
// List All files
if (mensaje.startsWith("listfiles:")) {
listarZips();
int pos = mensaje.indexOf(':');
String page = mensaje.substring(pos + 1);
int pageNumber = page.toInt();
//Set the PagesAmount variable on the Nextion
int pagesamount = (totalArchivos + 5) / 6;
String toSend = "PagesAmount.val=" + String(pagesamount);
Serial1.print(toSend);
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
if(pagesamount>(pageNumber+1)){
Serial1.print("vis b7,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else{
Serial1.print("vis b7,0");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
if((pageNumber+1)>=pagesamount){
Serial1.print("vis b7,0");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
if(pageNumber>0){
Serial1.print("vis b6,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else{
Serial1.print("vis b6,0");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
//List each file
int firstfile = pageNumber*6;
int lastfile = firstfile;
if((totalArchivos-firstfile) > pageNumber*6){
lastfile = firstfile+6;
}
else{
lastfile = totalArchivos;
}
if(totalArchivos>6){
for(int i=firstfile; i<lastfile; i++){
//Show the button
String tosend = "vis b" + String(i-(pageNumber*6)) + ",1";
Serial1.print(tosend);
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
//Set button text to the file name without "/"
tosend = "b" + String(i-(pageNumber*6)) + ".txt=\""+listaArchivos[i].substring(1)+"\"";
Serial1.print(tosend);
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
//Set file ID inside listaArchivos[] list
tosend = "fileID" + String(i-(pageNumber*6)) + ".val="+String(i);
Serial1.print(tosend);
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
}
else if(totalArchivos>0){
for(int i=0; i<totalArchivos; i++){
//Show the button
String tosend = "vis b" + String(i) + ",1";
Serial1.print(tosend);
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
//Set button text to the file name without "/"
tosend = "b" + String(i) + ".txt=\""+listaArchivos[i].substring(1)+"\"";
Serial1.print(tosend);
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
//Set file ID inside listaArchivos[] list
tosend = "fileID" + String(i) + ".val="+String(i);
Serial1.print(tosend);
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
}
else{
//Show button 0
Serial1.print("vis b0,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
//Disable it so it can't be clicked
Serial1.print("tsw b0,0");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
//Display text that there are no files on the SD card
Serial1.print("b0.txt=\"NO FILES OR SD NOT FOUND\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
}
// START NEW PRINT
if (mensaje.startsWith("startprint:")) {
// Extraemos lo que hay después de los dos puntos ":"
int pos = mensaje.indexOf(':');
String seletedFile = mensaje.substring(pos + 1);
int seletedFileID = seletedFile.toInt();
//
if(!printing){
Serial1.print("tStatus.txt=\"PRINTSTARTED\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page page4");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
SelectedFile = listaArchivos[seletedFileID];
contarCapas();
delay(10);
oled.clearDisplay();
oled.setCursor(0,0);
oled.println("Loading files...");
oled.display();
Serial.print("Selected file: ");
Serial.print(SelectedFile);
Serial.print(" with ");
Serial.print(totalCapas);
Serial.println(" layers");
float printHeight = totalCapas * (layerHeight/1000);
if(printHeight > HeightZ_Max){
Serial.println("Error: The object height is bigger than the maximum printer height");
oled.clearDisplay();
oled.setCursor(0,0);
oled.println("ERROR");
oled.println("Object too tall!");
oled.display();
Serial1.print("page 11");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page11.t0.txt=\"Error: The object height is bigger than the maximum printer height\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm0.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
beepSound(1000, 500);
delay(1500);
Serial.println("go to print menu");
scrollFiles();
}
else{
menu_level = 99;
capaActual = 0;
homed = false;
faseHoming = 0; //Stop any homing process
homingFinished = false;
enPausa = false;
colorLED(127, 0, 0);
printing = true;
}
delay(10);
}
}
// STOP THE PRINT
if (mensaje.startsWith("stopprint")) {
if(printing){
Serial1.print("page 11");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page11.t0.txt=\"PRINT CANCELED\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial.println("Print canceled from Nextion display");
oled.clearDisplay();
oled.setCursor(0,0);
oled.println("Nextion CANCELED");
oled.display();
beepSound(1000,500);
digitalWrite(Z_EN_PIN, HIGH); //Deactivate Motor
digitalWrite(UV_MOSFET, UV_OFF); // !!! Turn off UV just in case!!!
stepperZ->stopMove();
printing = false;
enPausa = false;
moving = false;
faseHoming = 0; //Stop any homing process
homed = false;
homingFinished = false;
delay(1000);
menu_level = 1;
indiceMenu = 0;
scrollMenu();
colorLED(0, 0, 0);
melodySound(2);
}
}
// PAUSE THE PRINT
if (mensaje.startsWith("pause")) {
if(enPausa){
enPausa = false;
}
else{
enPausa = true;
}
refreshNextion();
}
// Load the config data
if (mensaje.startsWith("loadconfig:")) {
// Extraemos lo que hay después de los dos puntos ":"
int pos = mensaje.indexOf(':');
String menufrom = mensaje.substring(pos + 1);
int menufromID = menufrom.toInt();
if(beep_active){
Serial1.print("BEEP.val=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b0.txt=\"TURN OFF\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tsw b0,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
beepSound(1000,50);
}
else{
Serial1.print("BEEP.val=0");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b0.txt=\"TURN ON\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b0.bco=1024");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tsw b0,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
Serial1.print("config1.val="+ String(tExposicion));
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b1.txt=\""+ String(tExposicion) + "ms\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tsw b1,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("config2.val="+ String(stepperSpeed));
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b2.txt=\""+ String(stepperSpeed) + "\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tsw b2,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("config3.val="+ String(stepperAccel));
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b3.txt=\""+ String(stepperAccel) + "\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tsw b3,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("config4.val="+ String(layerHeight));
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b4.txt=\""+ String(int(layerHeight)) + "um\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tsw b4,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
// SET Beep
if (mensaje.startsWith("togglebeep")) {
if(beep_active){
beep_active=false;
Serial1.print("b0.txt=\"TURN ON\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b0.bco=1024");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putBool("beep", false);
pref.end();
}
else{
beep_active=true;
Serial1.print("b0.txt=\"TURN OFF\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("b0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putBool("beep", true);
pref.end();
}
}
// SET EXPOSURE
if (mensaje.startsWith("setexp:")) {
// Extraemos lo que hay después de los dos puntos ":"
int pos = mensaje.indexOf(':');
String expValue = mensaje.substring(pos + 1);
int exposureValue = expValue.toInt();
if(exposureValue<tExposicion_Min){
Serial1.print("tStatus.txt=\"RANGE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.txt=\"TOO LOW\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else if(exposureValue>tExposicion_Max){
Serial1.print("tStatus.txt=\"RANGE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.txt=\"TOO HIGH\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else{
Serial1.print("tStatus.txt=\"SAVED\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page page6");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
tExposicion = exposureValue;
tExposicionFL = tExposicion*5;
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putLong("expo", tExposicion);
pref.end();
}
}
// SET SPEED
if (mensaje.startsWith("setspeed:")) {
// Extraemos lo que hay después de los dos puntos ":"
int pos = mensaje.indexOf(':');
String speedValueStr = mensaje.substring(pos + 1);
int speedValue = speedValueStr.toInt();
if(speedValue<stepperSpeed_Min){
Serial1.print("tStatus.txt=\"RANGE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.txt=\"TOO LOW\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else if(speedValue>stepperSpeed_Max){
Serial1.print("tStatus.txt=\"RANGE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.txt=\"TOO HIGH\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else{
Serial1.print("tStatus.txt=\"SAVED\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page page6");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
stepperSpeed = speedValue;
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putInt("speed", stepperSpeed);
pref.end();
}
}
// SET ACCELERATION
if (mensaje.startsWith("setaccel:")) {
// Extraemos lo que hay después de los dos puntos ":"
int pos = mensaje.indexOf(':');
String accelValueStr = mensaje.substring(pos + 1);
int accelValue = accelValueStr.toInt();
if(accelValue<stepperAccel_Min){
Serial1.print("tStatus.txt=\"RANGE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.txt=\"TOO LOW\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else if(accelValue>stepperAccel_Max){
Serial1.print("tStatus.txt=\"RANGE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.txt=\"TOO HIGH\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else{
Serial1.print("tStatus.txt=\"SAVED\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page page6");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
stepperAccel = accelValue;
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putInt("accel", stepperAccel);
pref.end();
}
}
// SET LAYER HEIGHT
if (mensaje.startsWith("setheight:")) {
// Extraemos lo que hay después de los dos puntos ":"
int pos = mensaje.indexOf(':');
String heightValueStr = mensaje.substring(pos + 1);
float heightValue = heightValueStr.toInt();
if(heightValue<layerHeight_Min){
Serial1.print("tStatus.txt=\"RANGE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.txt=\"TOO LOW\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else if(heightValue>layerHeight_Max){
Serial1.print("tStatus.txt=\"RANGE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.txt=\"TOO HIGH\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("t0.bco=62187");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else{
Serial1.print("tStatus.txt=\"SAVED\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page page6");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
layerHeight = heightValue;
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putFloat("height", layerHeight);
pref.end();
}
}
// Move
if (mensaje.startsWith("move:")) {
// Extraemos lo que hay después de los dos puntos ":"
int pos = mensaje.indexOf(':');
String movevalue = mensaje.substring(pos + 1);
float movemm = movevalue.toFloat();
if(movemm > 0){
moveZ(abs(movemm), true, stepperSpeed, stepperAccel);
Serial.print("Moving upwards ");
Serial.print(movemm);
Serial.println (" mm");
}
else{
moveZ(abs(movemm), false, stepperSpeed, stepperAccel);
Serial.print("Moving downwards ");
Serial.print(movemm);
Serial.println (" mm");
}
Serial1.print("page9.t3.txt=\"" + String(HeightZ,1) + "mm\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page9.status.txt=\"ok\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
// Home
if (mensaje.startsWith("home")) {
if(!moving && !printing && faseHoming == 0){
Serial1.print("page9.status.txt=\"ok\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
manualHoming = true;
homingZ();
}
else{
Serial1.print("page9.status.txt=\"moving\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
}
// Halt
if (mensaje.startsWith("halt")) {
if(moving || faseHoming != 0){
digitalWrite(Z_EN_PIN, HIGH); //Deactivate Motor
digitalWrite(UV_MOSFET, UV_OFF); // !!! Turn off UV just in case!!!
stepperZ->stopMove();
moving = false;
faseHoming = 0; //Stop any homing process
homed = false;
homingFinished = false;
}
Serial1.print("page9.status.txt=\"ok\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
// Enter moving menu so set the height t3 text tab in Nextion
if (mensaje.startsWith("height")) {
Serial1.print("page9.t3.txt=\"" + String(HeightZ,1) + "mm\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page9.status.txt=\"ok\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
}//End of Serial1.available
////////////////////////////UP BUTTON//////////////////////////
///////////////////////////////////////////////////////////////
if(!digitalRead(BTN_UP) && BTN_UP_State){
BTN_UP_State = PUSHED;
if(beep_active){
beepSound(1000, 50);
}
if(menu_level == 1){ //Main Menu
indiceMenu = (indiceMenu - 1 + 3) % 3;
scrollMenu();
}
else if(menu_level == 2){ //Select File To Print
indiceMenu = 0;
menu_level = 1; //Go back to main menu
scrollMenu();
}
else if(menu_level == 3){ //Main Menu
indiceMenu = (indiceMenu - 1 + 7) % 7;
scrollMove();
}
else if(menu_level == 4){ //Main Menu
indiceMenu = (indiceMenu - 1 + 7) % 7;
scrollConfig();
}
else if(menu_level == 5){
int increment = round(pow(10, selectedDigit));
if(selectedConfig == 0){
stepperSpeed = stepperSpeed + increment;
if(stepperSpeed > stepperSpeed_Max){
stepperSpeed = stepperSpeed_Max;
}
}
else if(selectedConfig == 1){
stepperAccel = stepperAccel + increment;
if(stepperAccel > stepperAccel_Max){
stepperAccel = stepperAccel_Max;
}
}
else if(selectedConfig == 2){
layerHeight = (layerHeight + increment);
if(layerHeight > layerHeight_Max){
layerHeight = layerHeight_Max;
}
}
else if(selectedConfig == 3){
tExposicion = tExposicion + increment;
if(tExposicion > tExposicion_Max){
tExposicion = tExposicion_Max;
}
}
setConfig();
}
else if(menu_level == 99){//Printing
if(enPausa){
indiceMenu = (indiceMenu - 1 + 3) % 3;
showPause();
}
}
Serial.println("UP button pressed");
delay(200); // Debounce
}
else if(digitalRead(BTN_UP) && !BTN_UP_State){
BTN_UP_State = UNPUSHED;
Serial.println("UP button released");
}
////////////////////////////UP BUTTON//////////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////DOWN BUTTON/////////////////////////
///////////////////////////////////////////////////////////////
if(!digitalRead(BTN_DWN) && BTN_DWN_State){
BTN_DWN_State = PUSHED;
if(beep_active){
beepSound(1000, 50);
}
if(menu_level == 1){//Main Menu
indiceMenu = (indiceMenu + 1) % 3;
scrollMenu();
}
else if(menu_level == 2){//Select File To Print
indiceMenu = (indiceMenu + 1) % totalArchivos;
scrollFiles();
}
else if(menu_level == 3){//Select File To Print
indiceMenu = (indiceMenu + 1) % 7;
scrollMove();
}
else if(menu_level == 4){//Select File To Print
indiceMenu = (indiceMenu + 1) % 7;
scrollConfig();
}
else if(menu_level == 5){ //Main Menu
int decrement = round(pow(10, selectedDigit));
if(selectedConfig == 0){
stepperSpeed = stepperSpeed-decrement;
if(stepperSpeed < stepperSpeed_Min){
stepperSpeed = stepperSpeed_Min;
}
}
else if(selectedConfig == 1){
stepperAccel = stepperAccel-decrement;
if(stepperAccel < stepperAccel_Min){
stepperAccel = stepperAccel_Min;
}
}
else if(selectedConfig == 2){
layerHeight = (layerHeight - decrement);
if(layerHeight < layerHeight_Min){
layerHeight = layerHeight_Min;
}
}
else if(selectedConfig == 3){
tExposicion = tExposicion - decrement;
if(tExposicion < tExposicion_Min){
tExposicion = tExposicion_Min;
}
}
setConfig();
}
else if(menu_level == 99){//Printing
if(enPausa){
indiceMenu = (indiceMenu + 1) % 3;
showPause();
}
}
Serial.println("DOWN button pressed");
delay(200); // Debounce
}
else if(digitalRead(BTN_DWN) && !BTN_DWN_State){
BTN_DWN_State = UNPUSHED;
Serial.println("DOWN button released");
}
///////////////////////////DOWN BUTTON/////////////////////////
///////////////////////////////////////////////////////////////
////////////////////////////OK BUTTON//////////////////////////
///////////////////////////////////////////////////////////////
if(BTN_OK_PRESSED){
if(beep_active){
beepSound(1000, 50);
}
BTN_OK_PRESSED = false;
BTN_OK_State = PUSHED;
if(menu_level == 1){//Main Menu
if(indiceMenu == 0){
SD.end();
delay(10);
if (!SD.begin(SD_CS)) {
Serial.println("Insert SD card");
oled.clearDisplay();
oled.setTextSize(1);
oled.setCursor(0, 10);
oled.println("POR FAVOR...");
oled.println("INSERTE SD");
oled.display();
beepSound(2000, 100);
delay(1500); // Pausa para que el usuario lea
scrollMenu();
}
else {
Serial.println("go to print menu");
menu_level = 2;
indiceMenu = 0;
listarZips();
scrollFiles();
}
}
else if(indiceMenu == 1){
Serial.println("go to config menu");
menu_level = 4; //Go to mconfig menu
indiceMenu = 0;
scrollConfig();
}
else if(indiceMenu == 2){
Serial.println("go to move menu");
menu_level = 3; //Go to move menu
scrollMove();
}
}//End of menu_level = 1
else if(menu_level == 2){//Print Menu
SelectedFile = listaArchivos[indiceMenu];
contarCapas();
delay(10);
oled.clearDisplay();
oled.setCursor(0,0);
oled.println("Loading files...");
oled.display();
Serial.print("Selected file: ");
Serial.print(SelectedFile);
Serial.print(" with ");
Serial.print(totalCapas);
Serial.println(" layers");
float printHeight = totalCapas * (layerHeight/1000);
if(printHeight > HeightZ_Max){
Serial.println("Error: The object height is bigger than the maximum printer height");
oled.clearDisplay();
oled.setCursor(0,0);
oled.println("ERROR");
oled.println("Object too tall!");
oled.display();
beepSound(1000, 500);
delay(1500);
Serial.println("go to print menu");
scrollFiles();
}
else{
menu_level = 99;
capaActual = 0;
homed = false;
enPausa = false;
colorLED(127, 0, 0);
printing = true;
}
delay(10);
}//End of menu_level = 2
else if(menu_level == 3){//Move Menu
if(!moving){
if(indiceMenu == 0){
Serial.println("Move Up 100mm");
moveZ(100, true, stepperSpeed, stepperAccel);
}
else if(indiceMenu == 1){
Serial.println("Move Up 10mm");
moveZ(10, true, stepperSpeed, stepperAccel);
}
else if(indiceMenu == 2){
Serial.println("Move Up 1mm");
moveZ(1, true, stepperSpeed, stepperAccel);
}
else if(indiceMenu == 3){
Serial.println("Move Down 100mm");
moveZ(100, false, stepperSpeed, stepperAccel);
}
else if(indiceMenu == 4){
Serial.println("Move Down 10mm");
moveZ(10, false, stepperSpeed, stepperAccel);
}
else if(indiceMenu == 5){
Serial.println("Move Down 1mm");
moveZ(1, false, stepperSpeed, stepperAccel);
}
}
if(indiceMenu == 6){
Serial.println("Go Back");
digitalWrite(Z_EN_PIN, HIGH); // Desactivate Motor
menu_level = 1;
indiceMenu = 2;
scrollMenu();
}
}//End of menu_level = 3
else if(menu_level == 4){//Config Menu
if(!printing && !moving){
if(indiceMenu == 0){
if(beep_active){
beep_active = false;
listaConfig[0] = "Beep OFF";
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putBool("beep", false);
pref.end();
}
else{
beep_active = true;
listaConfig[0] = "Beep ON";
beepSound(1000, 50);
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putBool("beep", true);
pref.end();
}
scrollConfig();
}
else if(indiceMenu == 1){
menu_level = 5;
selectedConfig = 0;
selectedDigits = (stepperSpeed == 0) ? 1 : (int)log10(abs(stepperSpeed)) + 1;
selectedDigit = 0;
setConfig();
}
else if(indiceMenu == 2){
menu_level = 5;
selectedConfig = 1;
selectedDigits = (stepperAccel == 0) ? 1 : (int)log10(abs(stepperAccel)) + 1;
selectedDigit = 0;
setConfig();
}
else if(indiceMenu == 3){
menu_level = 5;
selectedConfig = 2;
selectedDigits = (layerHeight == 0) ? 1 : (int)log10(abs(layerHeight)) + 1;
selectedDigit = 0;
setConfig();
}
else if(indiceMenu == 4){
menu_level = 5;
selectedConfig = 3;
selectedDigits = (tExposicion == 0) ? 1 : (int)log10(abs(tExposicion)) + 1;
selectedDigit = 0;
setConfig();
}
else if(indiceMenu == 5){
}
else if(indiceMenu == 6){
Serial.println("Go Back");
menu_level = 1;
indiceMenu = 1;
scrollMenu();
}
}
}//End of menu_level = 4
else if(menu_level == 5){//Set Selected Config
selectedDigit++;
if(selectedDigit > (selectedDigits-1)){
Serial.println("New Config Stored");
menu_level = 4;
scrollConfig();
beepSound(1000,500);
selectedDigit = 0;
if(selectedConfig == 0){
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putInt("speed", stepperSpeed);
pref.end();
}
else if(selectedConfig == 1){
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putInt("accel", stepperAccel);
pref.end();
}
else if(selectedConfig == 2){
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putFloat("height", layerHeight);
pref.end();
}
else if(selectedConfig == 3){
pref.begin("msla_settings", false); // "msla_recovery" es el nombre del espacio
pref.putLong("expo", tExposicion);
pref.end();
}
}
else{
setConfig();
}
}//End of menu_level = 5
else if(menu_level == 99){//Printing
if(enPausa){
if(indiceMenu == 0){
Serial.println("Print Canceled from OLED pause menu");
oled.clearDisplay();
oled.setCursor(0,0);
oled.println("User CANCELED");
oled.display();
digitalWrite(Z_EN_PIN, HIGH); //Deactivate Motor
digitalWrite(UV_MOSFET, UV_OFF); // !!! Turn off UV just in case!!!
stepperZ->stopMove();
printing = false;
enPausa = false;
moving = false;
faseHoming = 0; //Stop any homing process
homed = false;
homingFinished = false;
delay(1000);
menu_level = 1;
indiceMenu = 0;
scrollMenu();
colorLED(0, 0, 0);
melodySound(2);
}
else if(indiceMenu == 1){
Serial.println("Resume Print");
enPausa = false;
indiceMenu = 0;
colorLED(0, 0, 0);
}
else if(indiceMenu == 2){
Serial.println("Config");
}
}
else{
enPausa = true;
prevPauseTime = millis();
indiceMenu = 0;
showPause();
}
}
delay(200); // Debounce
Serial.println("OK button pressed");
}
else if(digitalRead(BTN_OK) && !BTN_OK_State){
BTN_OK_State = UNPUSHED;
Serial.println("OK button released");
}
////////////////////////////OK BUTTON//////////////////////////
//////////////////////////////////////////////////////////////
if(printing){
digitalWrite(Z_EN_PIN, LOW); // Encender el motor
// 1. Home the machine
if(!homed && faseHoming == 0 && !homingFinished){
homingZ();
oled.clearDisplay(); oled.setTextSize(1); oled.setCursor(0,0); oled.println("HOMING Z..."); oled.display();
Serial1.print("page4.t5.txt=\"Homing...\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("vis t5,1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else if(!homed && homingFinished){
oled.clearDisplay(); oled.setTextSize(1); oled.setCursor(0,0); oled.println("HOME"); oled.display();
Serial1.print("page4.t5.txt=\"HOME\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("vis t5,0");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
homed = true;
homingFinished = false;
HeightZ=0;
melodySound(1);
startTime = millis();
}
// 2. Printer is homed so start the print process
else if(homed){
if (capaActual < totalCapas) {
elapsedTime = (millis() - startTime)/1000;
// 3. Refresh the OLED and Nextion each 900ms
if(millis() - refreshTimePrev >= 900){
refreshTimePrev = millis();
hours = elapsedTime/3600;
minutes = (elapsedTime % 3600) / 60;
seconds = elapsedTime % 60;
if(!enPausa){
refreshOLED();
refreshNextion();
}
}
// 4. Process the next picture
if(!imagePrepared){
capaActual++; //Increase layer by 1
HeightZ = HeightZ + (layerHeight/1000); //Calculate he height depending on the layer and layer height
prepareNextImage(capaActual); // Carga el PNG del ZIP
imagePrepared = true; //Indicate that the next picture is prepared
prevLayerTime = millis(); //Reset timer of previous layer
Serial.println("New Layer Image Loaded");
}
// 5. Turn on UV light. First 5 layers have 6 times more exposure
int localtExposicion;
if(capaActual < 6){ //First 5 layers
localtExposicion = tExposicionFL;
}
else{
localtExposicion = tExposicion;
}
if (millis() - prevLayerTime >= 500) { // Wait 0.5s for stability and turn ON UV
if (!luzUvEncendida){
digitalWrite(UV_MOSFET, UV_ON); // !!! TURN ON UV !!!
colorLED(0, 0, 127); // Change RNG led to BLUE (indicating UV on)
luzUvEncendida = true;
Serial.println("UV Light Turned On");
}
}
// 6.1 When exposure time passed, turn OFF UV light and start rising
if (millis() - prevLayerTime >= (500+localtExposicion)) { // Wait for exposure time
if (!luzUvApagada){
digitalWrite(UV_MOSFET, UV_OFF); // !!! TURN OFF UV !!!
tft.fillScreen(TFT_BLACK); // IMPORTANT: Make LCD all BLACK
colorLED(127, 0, 0); // Change RNG led to RED (indicating UV off)
luzUvApagada = true;
// SLOW RISE: To protect the FEP film (ventouse effect)
stepperZ->setSpeedInHz(3200);
stepperZ->setAcceleration(1600);
stepperZ->move(2*stepsPerMM); //Go up 2mm very slow (FEP peel)
rising_slow = true;
Serial.println("UV Light Switched OFF, rising VERY slow for 2mm");
}
}
// 6.2 Still rising but at normal speed after 2mm
if(rising_slow){
if(!(stepperZ->isRunning())){
rising_slow = false;
rising = true;
stepperZ->setSpeedInHz(stepperSpeed); //Normal Speed
stepperZ->setAcceleration(stepperAccel);
stepperZ->move((layerChangeDistance-2) * stepsPerMM); //Move up layerChangeDistance-2mm made before
Serial.println("Z axis reached top on peel movement");
}
}
// 7. Get back to position - layerHeight for the next layer
if(rising){
if(!(stepperZ->isRunning())){
rising = false;
lowering = true;
stepperZ->setSpeedInHz(stepperSpeed);
stepperZ->setAcceleration(stepperAccel);
stepperZ->move( -((layerChangeDistance-layerHeight/1000) * stepsPerMM) ); // Move back down but less than "layerHeight"
Serial.println("Z axis reached top");
}
}
// 8. When lowering process is over, reset all variables for next layer
if(lowering){
remainingDistance = abs(stepperZ->targetPos() - stepperZ->getCurrentPosition());
if (remainingDistance < 1000 && !lowering_slow) {
//Lower the speed near the LCD
stepperZ->setSpeedInHz(1600); // 0.5 mm/s
stepperZ->setAcceleration(800);
stepperZ->applySpeedAcceleration(); // Indicate to hardware that new speed was applied
lowering_slow = true;
}
if(!(stepperZ->isRunning())){
lowering = false;
lowering_slow = false;
imagePrepared = false;
luzUvEncendida = false;
luzUvApagada = false;
prevLayerTime = millis();
Serial.println("Pass to next layer");
}
}
}//End of capaActual < totalCapas
else {
colorLED(0, 127, 0);
finalizarImpresion();
}
// Si estamos en pausa, avisar en el OLED
if (enPausa) {
if(millis()-prevPauseTime > 7){
prevPauseTime = prevPauseTime + 7;
ledfade = ledfade + 1;
if (ledfade < 254){
colorLED(ledfade, ledfade, ledfade);
}
else{
colorLED(508-ledfade, 508-ledfade, 508-ledfade);
}
if (ledfade > 508){
ledfade = 0;
}
}
}
}//End of homed = 1
}//End of printing
else if(moving){
if(!(stepperZ->isRunning())){
moving = false;
scrollMove();
}
}//End of normal moving
else if(homingFinished && manualHoming){
manualHoming = false;
homingFinished = false;
homed = true;
faseHoming = 0; //Stop any homing process
HeightZ = 0;
oled.clearDisplay(); oled.setTextSize(1); oled.setCursor(0,0); oled.println("HOME"); oled.display();
Serial1.print("page9.t2.txt=\"HOME\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page9.t2.bco=9256");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page9.t2.pco=0");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page9.tm2.en=1");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
melodySound(1);
}
}//END VOID LOOP
void prepareNextImage(int CapaParaImprimir){
// 1. Cargar y Proyectar
char nombre[16]; sprintf(nombre, "%d.png", CapaParaImprimir);
if (zip.openZIP(SelectedFile.c_str(), myOpen, myClose, myRead, mySeek) == UNZ_OK) {
if (zip.locateFile(nombre) == UNZ_OK) {
unz_file_info fi; zip.getFileInfo(&fi, NULL, 0, NULL, 0, NULL, 0);
uint8_t *p = (uint8_t *)malloc(fi.uncompressed_size);
if (p) {
zip.openCurrentFile(); zip.readCurrentFile(p, fi.uncompressed_size); zip.closeCurrentFile();
if (png.openRAM(p, (int)fi.uncompressed_size, pngDraw) == PNG_SUCCESS) {
tft.startWrite(); png.decode(NULL, 0); tft.endWrite();
png.close();
}
free(p);
}
}
zip.closeZIP();
}
}
void finalizarImpresion(){
printing = false;
digitalWrite(UV_MOSFET, UV_OFF); // !!! Make sure to TURN OFF UV !!!
oled.clearDisplay();
oled.setCursor(0,20);
oled.println("PRINT COMPLETE");
oled.println("Press any key");
oled.display();
sprintf(Timebuffer, "%02d:%02d:%02d", hours, minutes, seconds);
Serial1.print("page 12");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page12.t0.txt=\"Print finished in "+ String(Timebuffer) + "\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
digitalWrite(Z_EN_PIN, HIGH); // Apagar motor
melodySound(1);
//Reset some variables just in case
menu_level = 1;
indiceMenu = 0;
homingFinished = false;
faseHoming = 0;
manualHoming = false;
imagePrepared = false;
delay(10);
float mmAbove = ((HeightZ_Max - HeightZ) - 15); //Go up, 15mm of margin
if(mmAbove>0){ //Only go up if there is still room above
moveZ(abs(mmAbove), true, stepperSpeed, stepperAccel);
}
}
void refreshNextion(){
Serial1.print("page4.t0.txt=\"Printing: "+ SelectedFile + "\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
sprintf(Timebuffer, "%02d:%02d:%02d", hours, minutes, seconds);
Serial1.print("page4.t2.txt=\""+ String(Timebuffer) + "\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page4.t4.txt=\"Height "+ String(HeightZ,2) + "mm\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page4.t1.txt=\"Layer "+ String(capaActual) + "/" + String(totalCapas) + "\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
int percentage = (capaActual*100)/totalCapas;
Serial1.print("page4.j0.val="+ String(percentage));
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
Serial1.print("page4.t3.txt=\""+ String(percentage) + "%\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
if(enPausa){
Serial1.print("page4.b1.txt=\"RESUME\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
//Set Color to green
Serial1.print("page4.b1.bco=1024");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else{
Serial1.print("page4.b1.txt=\"PAUSE\"");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
//Set Color to yellow
Serial1.print("page4.b1.bco=54944");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
if(luzUvEncendida && !luzUvApagada){
Serial1.print("page4.p0.pic=8");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
else if(luzUvApagada){
Serial1.print("page4.p0.pic=7");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
if(homed){
Serial1.print("vis t5,0");
Serial1.write(0xff); Serial1.write(0xff); Serial1.write(0xff);
}
}
void refreshOLED(){
sprintf(Timebuffer, "%02d:%02d:%02d", hours, minutes, seconds);
String fileprinting = "Printing " + SelectedFile.substring(1);
filescroll++;
if(filescroll > (fileprinting.length()-21)){
filescroll = 0;
}
if(fileprinting.length() > 21){
fileprinting = fileprinting.substring(filescroll, filescroll+21);
}
oled.clearDisplay();
oled.setCursor(0,0);
oled.println(fileprinting);
oled.printf("Layer: %d / %d \n", capaActual, totalCapas);
oled.print("Height: ");
oled.print(HeightZ);
oled.println(" mm");
oled.setTextSize(2);
oled.println(Timebuffer);
oled.setTextSize(1);
if(luzUvEncendida && !luzUvApagada){
oled.println("UV light ON");
}
else if(luzUvApagada){
oled.println("UV light OFF");
}
oled.println(remainingDistance);
oled.display();
}
void moveZ(float mm, bool upwards, int speed, int accel) {
if(!printing && !moving && faseHoming == 0){
moving = true;
long steps = mm * stepsPerMM;
digitalWrite(Z_EN_PIN, LOW); // Encender el motor
if (upwards) {
// Going up
if(HeightZ + mm > HeightZ_Max){
Serial.println("Error: Maximum height reached");
beepSound(1000,500);
}
else{
HeightZ = HeightZ + mm;
//stepperZ.setMaxSpeed(speed); // Velocidad baja
stepperZ->setSpeedInHz(speed);
//stepperZ.setAcceleration(accel); // Rampa muy suave
stepperZ->setAcceleration(accel);
//stepperZ.move(upwards ? steps : -steps);
stepperZ->move(upwards ? steps : -steps);
}
}
else {
// Going down
if(HeightZ - mm < 0){
Serial.println("Error: Minimum height reached");
beepSound(1000,500);
}
else{
HeightZ = HeightZ - mm;
//stepperZ.setMaxSpeed(speed); // Velocidad baja
stepperZ->setSpeedInHz(speed);
//stepperZ.setAcceleration(accel); // Rampa muy suave
stepperZ->setAcceleration(accel);
//stepperZ.move(upwards ? steps : -steps);
stepperZ->move(upwards ? steps : -steps);
}
}
}
else{
Serial.println("Error: Already moving");
}
}
Here’s a list with the improvements respect the previous versions:
Leave a comment
Please login in order to comment.