#define emulator
#define sketchname "Nano_Stepper_1"
/************************************************************************************
Steuerung für einen elektrisch höhenverstellbaren Tischeinsatz für einen
K40-Lasercutter. Der Tisch mit Mechanik stammt von Martin.
Als Stepper-Motor ist ein NEMA 17HS4023 verbaut.
***********************************************************************************
Autor : Reinhold Wenzl, August 2022
Board : Arduino Nano - HW Interrupt-Pins: nur Pin 2 und 3
Prozessor : ATmega328P (Old Bootloader)
Display : SSD1306, I2C, SDA an A4, SCL an A5
Motordriver : DRV8825
***********************************************************************************
http://www.airspayce.com/mikem/arduino/AccelStepper/index.html
http://www.airspayce.com/mikem/arduino/AccelStepper/AccelStepper_8h_source.html
https://github.com/greiman/SSD1306Ascii
https://github.com/Stutchbury/EncoderButton
https://randomnerdtutorials.com/guide-for-oled-display-with-arduino
+--------------------------+ +---------------+
| 0 | | 0 font 12x16 |
| 8 | | 16 123456789# |
| 16 font 6x8 | | 32 1 |
| 24 | | 48 123467890 |
| 32 123456789#123456789#1 | | 0246802468 |
| 40 | +---------------+
| 48 1111 |
| 56 1123344566778990012 |
| 062840628406284062840 |
+--------------------------+
************************************************************************************/
//#define Debug
#define EncoderAccelerate // verwenden
#include <AccelStepper.h> // Motordriver DRV8825
#include "SSD1306AsciiWire.h"
#include <EncoderButton.h> // RotaryEncoder mit Key
#include <EEPROM.h>
#define EncCK 2 // Rotary Encoder Clock (HW-Interrupt)
#define EncDT 3 // Rotary Encoder Data
#define EncKEY 4 // Rotary Encoder Key
#define LS 5 // Gabellichtschranke
#define DIR 6 // DIR am DRV8825
#define STEP 7 // STEP am DRV8825
#define ENABLEPIN 8 // ENABLE am DRV8825
#define SW_ERSATZ 12 // Rotary Encoder SW für Simulator
#define motorInterfaceType 1
/*-----------------------------------------------------------------------------------
faktor an den Motor anpassen
für 0.1mm pro step bei einem Motor mit 200 steps/U und 4 microsteps
-----------------------------------------------------------------------------------*/
float faktor = 10.0;
/*-----------------------------------------------------------------------------------
maxSteps - ist der Wert für den gesamten Fahrweg von der Auslösung der
Lichtschranke bis zur oberen NULL Position, wo der Laser seinen
Brennpunkt hat. Wird manuell im setup eingestellt.
gapSteps - Korrekturwert für die untere Pos im Drehgeber, damit der Tisch im
Normalbetrieb die Lichtschranke nicht auslösen kann. Je höher der Wert,
umso größer der Abstand zur Lichtschranke.
Beide Werte werden vor dem kalibrieren im Pisplay unten eingeblendet.
-----------------------------------------------------------------------------------*/
int maxSteps; // zB 3580 für 35.8mm bei faktor 10
int gapSteps = 50;
int maxPos; // max. mögliche Pos im Diplay
//-----------------------------------------------------------------------------------
bool dreifachklick = false;
bool kalibriert = false; // true wenn kalibriert = Tisch in NULL Pos
bool initialisiert = false;
char buf[21];
bool drehen = false; // Motor bewegen gesperrt
bool LSunterbrochen = LOW; // HIGH = Lichtschranke unterbrochen
int stopSteps; // Merker für Stop bei LS Unterbrechung
int neuePos = 0;
int gemerktePos; // Buffer für aktuelle Encoder Position
int steps; // Merker für Motorschritte
byte kennzeichen;
//-----------------------------------------------------------------------------------
AccelStepper stepper = AccelStepper(motorInterfaceType, STEP, DIR);
EncoderButton rotary(EncCK, EncDT, EncKEY);
SSD1306AsciiWire oled;
/*-----------------------------------------------------------------------------------
----- S E T U P -----
-----------------------------------------------------------------------------------*/
void setup()
{
Serial.begin(9600);
Wire.begin();
Wire.setClock(400000L);
oled.begin(&Adafruit128x64, 0x3C);
oled.setFont(System5x7);
oled.clear();
// ----- Displaytest
for (int i = 0; i < 7; i++)
{
oled.println("0....5....0....5....0");
}
delay(2000);
// end ----- Displaytest
pinMode(SW_ERSATZ, INPUT_PULLUP);
pinMode(LS, INPUT);
pinMode(LED_BUILTIN, OUTPUT); // OnBoard LED Pin
digitalWrite(LED_BUILTIN, LOW); // LED aus
pinMode(ENABLEPIN, OUTPUT); // geht zum Treiber EN Pin
digitalWrite(ENABLEPIN, HIGH);
// --- RotaryEncoder-Events verlinken
rotary.setClickHandler(onButtonClicked);
rotary.setLongPressHandler(onButtonLongPress, false); // true = repeat the long click
rotary.setEncoderHandler(onRotaryEncoder);
#ifdef EncoderAccelerate
rotary.setRateLimit(200);
// Bei 200 ms ist das Inkrement() > 1, wenn der Encoders mehr als 5 Impulse/s liefert
#endif
stepper.setMaxSpeed(800.0); // Geschwindigkeit steps/min
stepper.setAcceleration(400.0); // Beschleunigung/Verzögerung steps/min
/*-----------------------------------------------------------------------------------
setup - NULL Pos aus EEPROM holen
-----------------------------------------------------------------------------------
Wenn in EEPROM Adresse 0 als Wert 0xEE enthalten ist, dann maxSteps aus
EEPROM Adresse 1/2 lesen. Ansonsten ManuellerBetrieb zur konfig aufrufen.
-----------------------------------------------------------------------------------*/
#ifdef emulator
kennzeichen = 0xEE;
maxSteps = 3080;
maxPos = (maxSteps - gapSteps) / faktor; // max. mögliche Pos für Diplay
#else
kennzeichen = EEPROM.read(0);
if (kennzeichen == 0xEE)
{
maxSteps = EEPROMReadInt(1);
maxPos = (maxSteps - gapSteps) / faktor; // max. mögliche Pos für Diplay
} else {
maxSteps = 3000; // default, wenn nicht gespeichert ist
clickToStart(); // auf start-click warten
ManuellerBetrieb();
}
#endif emulator
/*-----------------------------------------------------------------------------------
Tisch kalibrieren
-----------------------------------------------------------------------------------*/
if (!kalibriert) {
oled.clear();
bottomDisplaySetupValues();
oled.set2X(); oled.setCursor(0, 0); oled.println("triple");
oled.println("click to "); oled.print("calibrate");
while (!kalibriert) {
Encoder_Update();
if (dreifachklick) {
dreifachklick = false;
kalibrieren();
}
}
}
/*-----------------------------------------------------------------------------------
OLED SETUP-Anzeige
-----------------------------------------------------------------------------------*/
oled.clear();
oled.set2X();
oled.setCursor(0, 0); oled.print("Pos: 0.0");
oled.set1X();
oled.setCursor(0, 3); //Zeile 4
oled.print("in mm");
oled.setCursor(0, 6); //Zeile 6
oled.print(sketchname);
bottomDisplaySetupValues();
}
/*-----------------------------------------------------------------------------------
----- ENDE SETUP -----
-----------------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------
----- L O O P -----
-----------------------------------------------------------------------------------*/
void loop()
{
/* --- WICHTIG ---
------------------------------------------------------
Pos Werte sind 1/10mm. steps Werte sind Motorschritte
------------------------------------------------------ */
/* Lichtschranke abfragen - HIGH wenn unterbrochen
In loop() muss LS eigentlich immer frei sein, da ja in der Subroutine
MotorBewegen() bei unterbrochener LS der Tisch 5mm hoch fährt.
Also liegt bei false ein mechanischer Fehler vor.
Im OLED ERROR blinken lassen */
LSunterbrochen = digitalRead(LS);
if (LSunterbrochen == HIGH) { // LS unterbrochen
error();
} else { // LS frei
Encoder_Update(); // alle Ecoder events bearbeiten
if (drehen == true) {
drehen = false; // damit nur 1x gedreht wird
if (neuePos != gemerktePos) {
gemerktePos = neuePos; // neue Pos merken
steps = neuePos * faktor; // pos2steps für Motor umwandeln
Encoder_Sperren();
MotorBewegen(); // zur steps Position bewegen
Encoder_Freigeben();
gemerktePos = steps / faktor; // steps2pos für Encoder und Anzeige
rotary.resetPosition(gemerktePos); // Encoder Pos korrigirren
posAnzeigeAktualisieren(gemerktePos); // Anzeige korrigirren
}
}
}
}
/*-----------------------------------------------------------------------------------
----- Ende LOOP -----
-----------------------------------------------------------------------------------*/
/*-----------------------------------------------------------------------------------
MotorBewegen
Zur steps Position bewegen. Bei Unterbrechung der Lichtschranke sofortiger Stop.
Encoder, Key1 und Key2 sind gesperrt.
-----------------------------------------------------------------------------------*/
void MotorBewegen()
{
Motorlauf();
/*-----------------------------------------------------------------------------------
LS Unterbrechung behandeln
-----------------------------------------------------------------------------------*/
if (LSunterbrochen == HIGH) { // LS unterbrochen
LSunterbrochen = LOW; // gleich wieder auf frei setzen
/* Tisch ca. 5mm hochfahren
Und zwar auf eine genaue 10er-Stelle, damit nachher bei steps/10
keine Ungenauigkeit für die Anzeige entsteht
*/
steps = int(stopSteps / 10) * 10 - 500;
if (steps < 0) steps = 0;
if (steps > maxSteps) steps = maxSteps;
//gemerktePos = steps / faktor; //steps2pos für Anzeige umwandeln
stepper.setCurrentPosition(stopSteps);
delay(300);
stepper.moveTo(steps);
digitalWrite(ENABLEPIN, LOW);
while (stepper.currentPosition() != steps) {
//=====================
stepper.run();
//=====================
}
digitalWrite(ENABLEPIN, HIGH);
// alternativ
//stepper.runToNewPosition(steps);
}
}
/*-----------------------------------------------------------------------------------
Motorlauf
Zur steps Position bewegen. Bei Unterbrechung der Lichtschranke sofortiger Stop.
-----------------------------------------------------------------------------------*/
void Motorlauf()
{
stepper.moveTo(steps);
digitalWrite(ENABLEPIN, LOW);
while (stepper.currentPosition() != steps) {
LSunterbrochen = digitalRead(LS);
if (LSunterbrochen == HIGH) { // LS unterbrochen
stopSteps = stepper.currentPosition(); // die Stop Pos speichern
stepper.setCurrentPosition(0); // reset pos to 0
stepper.moveTo(stopSteps); // counter auf stopSteps stellen
break;
} else { // LS frei
if (stepper.distanceToGo() == 0) {
stepper.setCurrentPosition(0); // reset pos to 0
stepper.moveTo(steps); // Drivercounter auf steps stellen
break;
}
//=====================
stepper.run();
//=====================
}
}
digitalWrite(ENABLEPIN, HIGH);
}
/*-----------------------------------------------------------------------------------
ERROR
Motor abschalten, "ERROR" im Display pulsieren lassen
-----------------------------------------------------------------------------------*/
void error()
{
digitalWrite(ENABLEPIN, HIGH);
oled.clear();
oled.set2X();
while (true) {
oled.setCursor(0, 2);
oled.clearToEOL();
delay(500);
oled.print(" E R R O R");
delay(500);
}
}
/*-----------------------------------------------------------------------------------
clickTo Start - warte auf einen Tastendruck
-----------------------------------------------------------------------------------*/
void clickToStart()
{
oled.clear();
oled.set2X();
oled.setCursor(12, 2); oled.print("click to ");
oled.setCursor(36, 4); oled.print("start");
// 1-fach Klick //
while (rotary.clickCount() != 1) {
Encoder_Update();
}
oled.clear();
}
/*-----------------------------------------------------------------------------------
bottomDisplaySetupValues - setup Werte im Display ganz unten anzeigen
m: im EEPROM gespeicherter maxSteps Wert
g: verringert den maxSteps Wert, damit der Tisch nicht in die Lichtschranke fährt
p: maxPos, der höchste zulässige Pos-Wert
-----------------------------------------------------------------------------------*/
void bottomDisplaySetupValues()
{
oled.set1X();
oled.setCursor(0, 7);
oled.print("m:"); oled.print(maxSteps);
oled.print(" g:"); oled.print(gapSteps);
oled.print(" p:"); oled.print(maxPos * 0.1, 1);
}
/*-----------------------------------------------------------------------------------
timeLoop ersetzt delay durch millis
zB für 20ms: timeLoop(millis(), 20);
-----------------------------------------------------------------------------------*/
void timeLoop (long int startMillis, long int interval)
{
while (millis() - startMillis < interval) {}
}