/*
Zeile 183 und 184 auskommentieren wenn eprom Loeschen !!!!!!!!!!!!
* Bed is built with standard hardware (square aluminuim profile 16X16, expanded metal sheet, bearings MR105ZZ (5X10X4),
* M5 nuts and threaded) alourd 3D printed vitamins.
* A Nema17 stepper motorize it assisted with a closed GT2 timing belt (620 Tooth / 1240mm ) and pulley,
* assuming synchronous moves on 4 corners.
*
* An homing switch allows to take a reference at the botomm bed position.
*
* A simple eletronics have to be built alround an arduino (here an Arduino Micro, Sparkfun Pro micro, Arduino Leonardo) with :
* - A stepper motor controller of any kind (here TB6600) connected on pins 4(step), 5(dir) and 6 (enable)
* - A homing switch connected between pin 7 and ground.
* - An SSD1306 Oled (128X32 mini) connected on pins 2(SDA) and 3(CLK)
* - A panel rotary encoder with pushbutton connected on pins 0(RX), 1(TX) pour les 2 entrées A et B, et l'entrée 8 (button)
*
****************************************************************************************************************
* Make
****************************************************************************************************************
*
****************************************************************************************************************
* Needs
****************************************************************************************************************
* - AccelStepper library (Mike McCauley): available thru Arduino library manager
* - Adafruit SSD1306 library : available thru Arduino library manager
* - Adafruit GFX library : available thru Arduino library manager
* - Encoder Library ( PaulStoffregen) : available thru Arduino library manager
* - EzButton : available thru Arduino library manager
*
*
****************************************************************************************************************
* Interface
****************************************************************************************************************
* At boot, splash screen is displayed for 2 seconds with software version.
* Then firmware do homing (moving bed to the bottom while Home swich is not activated), while displaying "Origine".
* When homed, bed rise up setting.fBedOffset, displays "Machine a zero" for 1 second, clears screen and cut motor.
*
* Initial display is main menu. It shows 2 lines. A short press to encoder allows to switch active parameter.
* Parameter can be changed by rotating encoder. Value '-' is counter clockwise, value '+' is clockwise.
* - "Cut" parameter is to determine if cutting (Oui) or engraving (Non)
* - "Epais" parameter is to setup material tickness. 1 encoder step = 0.1mm.
* -------------
* | Cut: Oui |
* | Epais:05.0 |
* -------------
*
* A long press to panel encoder allows to go to setup menu.
* There are 2 lines, each with a different parameter. A short press allow to move between parameters just like on main menu.
* Parameter can be changed by rotating encoder.
* - "Decal" is to set an offset from bed to bottom of material to cut. This is useful when user wants to recut
* or engrave an already made/assembled plywood box to make an additionnal hole for exemple
* - "ZeroB" is to setup the bed offset from home to setup focal zero (2'' = 50.8mm from lens and bed surface)
* -------------
* | Decal:00.0 |
* | ZeroB:08.0 |
* -------------
* A long press quit settings screen, save all parameters them in flash memory and bring user back to main menu.
* Please note the 4 parameters are saved, but only when exiting settings screen to preserve flash write memory cycles.
*
* It is difficult to exactly measure the expected 50.8mm distance between lens and bed's top, because lens is enclosed in aluminium.
* Experiment is the best way to find this setting. First, you have to set Cut to "No", "Epais" and "Decal" to zero.
* Adjust ZeroB to have an approx distance of 45mm from lens theorical position and bed (with a ruler)
* Trace a vertical line with the laser, move laser head right 1mm and set ZeroB to 1mm down and repeat the operation 10 times
* You'll have a pattern of vertical lines more and less thick. The tickest designates the right altitude for setting bed zero.
* This will have to be done only one time, unless you'll change laser head, change lens for an other focal (ex: 1.5'')
* or change drastically optic calibration.
*
****************************************************************************************************************
* Note:
****************************************************************************************************************
* Here developped using ATMEGA32U4 powered arduino (mini, leonardo, pro micro, etc.).
* if you want to use other Arduino, pins have to be adapted accordingly, especially for encoder and SSD1306 libraries.
* - See pin interrupts constraints for encoder on https://www.pjrc.com/teensy/td_libs_Encoder.html
* - Connect SDA / SCK to right pins.
*
****************************************************************************************************************
*/
#include <AccelStepper.h> // To manager stepper with acceleration
#include <SPI.h> // needed by SSD1306
#include <Wire.h> // needed by SSD1306
#include <Adafruit_GFX.h> // Adafruit graphic library needed by SSD1306
#include <Adafruit_SSD1306.h> // LCD SSD1306 management
#include <Encoder.h> // Panel encoder management from Paul Stoffregen
#include <ezButton.h> // Button management
// 128x32 SSD1306 Oled display connected to pin 2 (SDA) and 3 (SCL)
#define DISPLAY // define to support SSD1306 display
#define SCREEN_WIDTH 128 // OLED display width,in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define MAXBED 38 // Max bed travel
#define MAXTICK 10 // Max material tickness
/* Motor settings for bed :
* Motor : Wantai 42BYGHW609 1.7A (https://fr.aliexpress.com/premium/42BYGHW609.html)
* Driver = TB6600 (https://www.banggood.com/TB6600-Upgraded-Stepper-Motor-Driver-Controller-for-4A-940V-TTL-32-Micro-Step-2-or-4-Phase-of-4257-Stepper-Motor-3D-Printer-CNC-Part-p-1453122.html)
* Steps (1,2,3) = 0ff, On, Off = 1/8 step
* Power (4,5,6) = On,On, Off) = 1.5A nominal / 1.7 Peak
*
* 200 * 8 = 1600 step / turn.
* Screw M5 x 0.8
* 1 turn = 0.8 mm = 1600 steps
* 1mm = (1600/0.8)*1 = 2000 steps
* 0.1mm = 200 steps
*/
// Stepper connections
const int Step = 4; // Step interface
const int Dir = 5; // Dir interface
const int Ena = 6; // Enable stepper motor
// Stepper constants
const int stepPerMm = 2000;
// Home switch
const int Home = 7; // home switch
// Encoder connections
const int Enc1 = 0; // See https://www.pjrc.com/teensy/td_libs_Encoder.html
const int Enc2 = 1; // 2,3 are i2c, and 0,1 Serial1
// encoder button managemnet
const int buttonPin = 8;
const int SHORT_PRESS_TIME = 1000; // 1000 milliseconds
const int LONG_PRESS_TIME = 2000; // 1000 milliseconds
// Variables
// Encoder position
long oldTicks;
long newTicks;
long Position = 0; // Current motor position (steps)
// button vars
unsigned long pressedTime = 0;
unsigned long releasedTime = 0;
bool isPressing = false;
bool isLongDetected = false;
// bed settings vars
typedef struct {
float fBedOffset; // Default offset from switch to bed focal zero (mm)
float fMaterialOffset; // Default offset from bed to surface to cut's bottom (mm)
bool bCut; // Cut y/n (boolean)
float fTickness; // Material tickness (mm). Used to compute real offset.
// For engraving (bCut=0), focal point is set to material surface
// Focal point = fBedOffset + fMaterialOffset + fTickness
// For cutting (bCut=1), focal point is is in the middle of the material
// Focal point = fBedOffset + fMaterialOffset + fTickness/2
} settingsType;
settingsType settings ;
// State vars for menu management
bool setupMenu = false; // true if in setup menu
bool inputMaterialOffset = false; // in setup, true if BedOffset input in progress
bool inputBedOffset = false; // in setup, true if BedOffset input in progress
bool mainMenu = true; // true if in main menu
bool inputCut = true ; // in main, true if Cut Y/N input in progress
bool inputTickness = false ; // in main, true if Tickness of material input in progress
// Library objects instantiation
AccelStepper Bed(AccelStepper::DRIVER, Step, Dir);
#ifdef DISPLAY
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#endif
Encoder myEnc(Enc1, Enc2);
ezButton button(buttonPin); // create ezButton object that attach to pin 7;
void defaultSettings()
{
settings = {8.0, 0.0, true, 5.0};
}
void writeSettings()
// be aware of eeprom longevity : only 10000 write cycles..
{
eeprom_write_block((const void*)&settings, (void*)0, sizeof(settingsType));
}
void readSettings()
{
eeprom_read_block((void*)&settings, (void*)0, sizeof(settingsType));
// Uncomment on first launch, to init settings. Don't forget to recomment and reflash after...
// defaultSettings();
// writeSettings();
}
void displaySettings()
{
Serial.println(settings.fBedOffset,1);
Serial.println(settings.fMaterialOffset,1);
Serial.println(settings.bCut,1);
Serial.println(settings.fTickness,1);
}
void home()
{
#ifdef DISPLAY
display.clearDisplay();
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("Homeing.."));
display.display();
#endif
// keep slowly
Bed.setMaxSpeed(2000);
Bed.setSpeed(2000);
Bed.AccelStepper::enableOutputs();
// Loop going to zero (each step is 0.01mm)
while (digitalRead(Home)) {
Bed.moveTo(Position);
Bed.runSpeedToPosition();
Position -= stepPerMm/10; // (precision: 0.1mm)
}
// homed !
Bed.setCurrentPosition(0);
// Compute new position based on saved settings (previously loaded in init)
Position = (settings.fBedOffset + settings.fMaterialOffset - settings.fTickness)* stepPerMm ;
// if cut, move 1/2 Tickess in material
if ( settings.bCut ) Position+= (settings.fTickness/2)* stepPerMm;
#ifdef DISPLAY
display.clearDisplay();
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("Rising up"));
display.setCursor(0, 16);
display.print((float)Position / stepPerMm,1);
display.println(F(" mm"));
display.display();
#endif
// Rise up xx mm, to setup focal zero
// Max seems to be alround 4300
Bed.AccelStepper::enableOutputs();
Bed.setMaxSpeed(4500);
Bed.setSpeed(4500);
// Move to new coordinates
Bed.moveTo((long)Position);
Bed.runToPosition();
Bed.AccelStepper::disableOutputs();
#ifdef DISPLAY
display.clearDisplay();
display.setTextSize(1); // Draw 2X-scale text
display.setCursor(0, 0);
display.println(F("Machine"));
display.setCursor(0, 16);
display.println(F("bei null."));
display.display();
#endif
// Shutoff motor
Bed.AccelStepper::disableOutputs();
delay (2000);
display.clearDisplay();
display.display();
}
void goingSetupMenu() {
mainMenu = false ;
inputCut = false ;
inputTickness = false;
setupMenu = true ;
inputMaterialOffset = true ;
inputBedOffset = false ;
refreshDisplay();
}
void goingMainMenu() {
mainMenu = true ;
inputCut = true ;
inputTickness = false;
setupMenu = false ;
inputMaterialOffset = false ;
inputBedOffset = false ;
refreshDisplay();
}
void refreshDisplay() {
#ifdef DISPLAY
display.clearDisplay();
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
if (mainMenu) {
display.print("Schneiden: ");
if (inputCut) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
if ( settings.bCut ) display.print("Ja"); else display.print("Nein");
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 16);
display.print("Materialdicke: ");
if (inputTickness) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.print(settings.fTickness,1);
display.setTextColor(SSD1306_WHITE);
display.display(); // Show initial text
#endif
}
if (setupMenu) {
#ifdef DISPLAY
display.print("Versatz: ");
if (inputMaterialOffset) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.print(settings.fMaterialOffset,1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 16);
display.print("Focus-Linse: ");
if (inputBedOffset) display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.print(settings.fBedOffset,1);
display.setTextColor(SSD1306_WHITE);
display.display(); // Show initial text
#endif
}
}
void buttonManagement() {
if(button.isPressed()){
pressedTime = millis();
isPressing = true;
isLongDetected = false;
}
if(button.isReleased()) {
isPressing = false;
releasedTime = millis();
long pressDuration = releasedTime - pressedTime;
if( pressDuration < SHORT_PRESS_TIME ) {
// Short press : state flag management
if (setupMenu) {
if (inputBedOffset) {
inputBedOffset = false;
inputMaterialOffset = true;
}
else {
inputBedOffset = true;
inputMaterialOffset = false;
}
}
if (mainMenu) {
if (inputCut) {
inputCut = false;
inputTickness = true;
}
else {
inputCut = true;
inputTickness = false;
}
}
refreshDisplay();
}
}
if(isPressing == true && isLongDetected == false) {
long pressDuration = millis() - pressedTime;
if( pressDuration > LONG_PRESS_TIME ) {
// Long press
if (setupMenu) { // if already in setup...
writeSettings(); // Save settings in flash
Bed.AccelStepper::enableOutputs(); // switch on stepper
// Compute new position
Position = (settings.fBedOffset + settings.fMaterialOffset - settings.fTickness) * stepPerMm;
// if cut, move 1/2 Tickess in material
if ( settings.bCut ) Position+= (settings.fTickness/2) * stepPerMm;
// Move to new coordinates
Bed.moveTo((long)Position);
goingMainMenu(); // Return to main.
}
else { // we're in main. Going setup.
goingSetupMenu();
}
isLongDetected = true;
}
}
}
void setup()
{
Serial.begin(9600);
readSettings(); // read settings inflash
#ifdef DISPLAY
// Init display
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextSize(1); // Draw 2X-scale text
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
display.setCursor(0, 0);
display.print(F("K40 V1.0"));
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 16);
display.print(F("Juergen 2024"));
display.display();
delay (2000);
#endif
// init homing pin
pinMode(Home, INPUT_PULLUP);
// Init button
button.setDebounceTime(50); // set debounce time to 50 milliseconds
// init bed motor
Bed.setEnablePin(Ena); // Set enable pin
Bed.setPinsInverted(false,false,false);
Bed.setAcceleration(10000);
// home motor
home();
// reset encoder position
myEnc.write(0);
// read current (should be zero) and set old
newTicks = myEnc.read()/4;
oldTicks = newTicks;
goingMainMenu();
}
void loop()
{
button.loop(); // MUST call the loop() function first to properly manage buttons.
buttonManagement(); // to manage short and long encoder button press
newTicks = myEnc.read()/4;
if (newTicks != oldTicks) {
myEnc.write(0); // reset encoder
// manage vars
if (setupMenu) {
if (inputMaterialOffset) { // Default offset from bed to surface to cut's bottom (mm)
settings.fMaterialOffset+=newTicks*0.1;
if (settings.fMaterialOffset<0) settings.fMaterialOffset=0;
if (settings.fMaterialOffset>MAXBED) settings.fMaterialOffset=MAXBED;
}
if (inputBedOffset) { // Default offset from switch to bed focal zero (mm)
settings.fBedOffset+=newTicks*0.1;
if (settings.fBedOffset<0) settings.fBedOffset=0;
if (settings.fBedOffset>MAXBED) settings.fBedOffset=MAXBED;
}
}
if (mainMenu) {
if (inputCut) { // Cut y/n (boolean)
if (newTicks<0) settings.bCut=false;
else settings.bCut=true;
}
if (inputTickness) { // Material tickness (mm). Used to compute real offset.
settings.fTickness+=newTicks*0.1;
if (settings.fTickness<0) settings.fTickness=0;
if (settings.fTickness>MAXTICK) settings.fTickness=MAXTICK;
}
}
refreshDisplay();
// Motor move
Bed.AccelStepper::enableOutputs(); // switch on stepper
// Compute new position
Position = (settings.fBedOffset + settings.fMaterialOffset - settings.fTickness) * stepPerMm;
// if cut, move 1/2 Tickess in material
if ( settings.bCut ) Position+= (settings.fTickness/2) * stepPerMm;
// Move to new coordinates
Bed.moveTo((long)Position);
// displaySettings();
}
if (!Bed.isRunning()) // Cut motor power is position reached.
Bed.AccelStepper::disableOutputs();
Bed.run(); // MUST also be called for movements management...
}