Nummerische Daten aus der Speicherkarte auslesen und aufteilen

Ich glaube diese Division/Multiplikation bekommt ein kleiner Atmega auch hin.
Du ermittelst einmal einen Faktor für deine Umrechnung mm --> steps und gibst das dem Atmega. Die Schnittstelle nach außen (zu deinem Excel) ist in mm (oder zehntel, oder hunderstel...). Das extern umrechnen zu wollen und dann doch wieder die Klartext-Information fürs Display mitschicken zu wollen finde ich nicht sehr geschickt.

Außerdem würde ich die Umrechnung in der Maschine machen, damit man auch einfach mal einen Einzeiler-befehl über die Serielle Schnittstelle schicken kann "... 15mm relativ nach rechts..."

Naja, das macht Excel ja von alleine. Ich gebe die mm Fahrstrecke ein, und Excel macht von alleine die Schritte daraus. Und da ich dann beides auf der Karte habe, kann ich es auch beides nutzen. Natürlich kann man die Rechnung auch machen, schon klar. Und ich bleibe bei dem Kreuztisch wohl bei der SD-Karte, weil man die dann auch mitbringen kann, reinstecken und ab geht die Post.

Schönes Wochenende
Franz

Hallo Franz,
ich hab Dir mal was aus dem bisher hier auch von @agmue und @my_xy_projekt geschrieben zusammen gebaut. Es läuft zusammen mit einem Steppermotor. Du wirst also vieles wiedererkennen. Da ich nur einen kleinen stepper 28byj-48 habe findest Du nur eine Achse im Sketch, ist aber leicht erweiterbar.
Nun weiß ich nicht wie Deine Stepper aussehen und wie Du die ansteuern willst. Als kleinster gemeinsamer Nenner hab ich mal die standard "Stepper lib " verwendet. Besser wäre sicher Mobatools. Vernünftig wäre sicher auch eine Methode zu nehmen die nicht blockiert.
Nicht drin ist Referenzfahren. Gestartet wird mit einem Digitaleingang das wird ja sicher letztlich auch so gewollt sein.
Wie bereits angesprochen lass das Positionieren doch die Lib machen und nutze absolutes verfahren. Eventuell überdenkst Du ja auch die Daten Deiner Excell Datei nochmal, Du könntest ja auch mehrere Achsen gleichzeitig benötigen. Wenn Du wie von @agmue und @my_xy_projekt angesprochen später irgendwelche zusätzliche Befehle einbauen willst brauchst Du letztlich so eine Art "Interpreter" der die Zeilen auswertet. Aber Dir geht es ja erst mal darum was zu basteln, und Du kannst es als Vorlage brauchen.
Heinz

#include <SPI.h>
#include <SD.h>
#include <Stepper.h>

#define CS 10

const byte btnpin = 2; // Sartpin
const int stepsPerRevolution = 2040;  // stepper 28byj-48
String tmp;
Stepper myStepper(stepsPerRevolution, 4, 6, 5, 7);
//Stepper myStepper2(stepsPerRevolution, 4, 6, 5, 7); // andere Pins
// Stepper myStepper3(stepsPerRevolution, 4, 6, 5, 7);

bool filestatus;

// format für die Daten
// motor;fahren;richtung;schritte;tempo;weg
int motor;    // Nummer der Achse
int fahren;   // Achse soll gefahren werden
int richtung; // Richtung 0=minus 1= plus
long schritte;// Anzahl der Schritte
int tempo;    // Tempo
float weg;    // Weg

File f;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  pinMode(btnpin, INPUT_PULLUP);

  Serial.print("Initializing SD card...");
  if (!SD.begin(CS)) {
    Serial.println("initialization failed!");
    while (1);
  }
  Serial.println("initialization done.");

  //testen bzw. Refferenz fahren
  myStepper.setSpeed(15); // etwa max für den kleinen Motor
  myStepper.step(2040); // eine Umdrehung

}
void loop() {
  if (!digitalRead(btnpin) && !filestatus) {
    readSD();
  }

  if ( filestatus) {
    // wenn das fahren nicht blockierend ist
    // muss hier noch eine Abfrage rein das die nächste Zeile
    // gelesen werden kann. z.B alle auf Position
    readSD();

    if (motor == 1 && fahren == 1) { // setzen der Parameter switch geht natürlich auch 
      myStepper.setSpeed(tempo);
      myStepper.step(schritte);
    }
    else if (motor == 2 && fahren == 1) {
      // aufrufe für zweiten Motor
      //myStepper2.setSpeed(tempo);
      //myStepper2.step(schritte);
    }
    else if (motor = 3 && fahren == 1) {
      // aufrufe für zweiten Motor
      //myStepper3.setSpeed(tempo);
      //myStepper3.step(schritte);
    }
  }
}

void readSD() {

  if (!filestatus) {
    f = SD.open("DATA.CSV");
    if (!f) {
      Serial.println(" Kann Datei nicht öffnen");
      return;
    }
    Serial.println("Datei geöffnet");
    filestatus = true;
    return;
  }

  if (f.available()) { // eine Zeile auslesen
    tmp = f.readStringUntil('\n');
    Serial.println(tmp);
    anzeigen();
    auswerten();
  }
  else {
    f.close();
    Serial.println("Datei geschlossen");
    filestatus = false;
    fahren = 0; // Variable zurück setzen
  }
}

void anzeigen() {
  Serial.print(F("Motor    ")); Serial.println(motor);
  Serial.print(F("fahren   ")); Serial.println(fahren);
  Serial.print(F("Richtung ")); Serial.println(richtung);
  Serial.print(F("Schritte ")); Serial.println(schritte);
  Serial.print(F("Tempo    ")); Serial.println(tempo);
  Serial.print(F("Weg      ")); Serial.println(weg);

}

void auswerten() {
  char zeile[50];
  snprintf( zeile, sizeof(zeile), "%s", tmp.c_str() );
  motor = atoi(strtok(zeile, ";"));
  fahren = atoi(strtok(NULL, ";"));
  richtung = atoi(strtok(NULL, ";"));
  schritte = atol(strtok(NULL, ";"));
  tempo = atoi(strtok(NULL, ";"));
  tmp = strtok(NULL, ";");
  tmp.replace(",", ".");
  weg = atof(tmp.c_str());
  if (richtung == 0) schritte = schritte * -1;
}

Es gibt auch readBytesUntil() um direkt in ein Array einzulesen. Immer diesen Umweg über die String Klasse braucht man nicht. Und Kopieren geht effizienter mit str(n)cpy() als mit snprintf()

Das mag gehen, solange sichergestellt ist, das jeder Datensatz in die Payload passt.
Sobald Du zweimal durch musst, ist umkopieren mit merken der letzten Position und Merker, das nicht vollständig ist, angesagt.
Das könnte aufwendiger sein, als einfach nur einen puffer charweise voll zu schreiben, bis ein \n kommt...

Im praktisch realen Fall sollte bekannt sein, was maximal vom Sender kommt.
Sowas kann man nicht sinnvoll für alle (un)möglichen Anwendungsfälle konzipieren.
Im Zweifel wird die Message auf 2 Zeilen aufgeteilt.

Gruß Tommy

Mit genau der Folge, das ein Zwischenspeicher her muss, wenn es notwendig ist die Message in Gänze auf Plausibilität prüfen zu müssen.

Gerade weil CNC Zeilenbasiert ist, muss damit gerechnet werden, das es nicht in die payload passt. Womit ein umkopieren und merken unausweichlich ist.

ja, aber nur in Zusammenhang mit einer konkreten Anwendung und dort sind die Längen bekannt und das muss der TO einfach festlegen.

Gruß Tommy

Ich habe das Programm jetzt mal zusammen gezimmert. Ist sicher nicht der letzte Stand, aber es funktioniert erst mal so wie es soll. Sagen wir mal Beta 1.0 :slightly_smiling_face:

Hier ist ein Video auf dem der Kreuztisch dann mal einen Displayausschnitt Fräsen würde. Das sind in etwa die Daten, die wir hier zur Demo hatten. Ich mache die Tage auch noch einen Testlauf mit einer Holzplatte auf dem Kreuztisch und einem Schreibstift als Z-Achse, dass die Fahrwege auf dem Holz, oder nem Blatt Papier aufgezeichnet werden. Dann kann ich die Genauigkeit der Fahrwege besser kontrollieren.

Und hier das Programm. Ich habe jetzt einfach mal aus zwei eines gemacht, die Schönheitsreparaturen kommen noch.

/* 
 *  Kreuztisch Steuerung mit Schrittmototoren erstellt mit der Mobatools Lib. die ich sehr empfehlen kann. 
 *  Die Fahrdaten werden mit Excel eingegeben, auf eine SD-Karte mit Trennung durch Semikolon exportiert,
 *  und vom Arduino wieder ausgelesen. 
 *  Die beötigten Daten in einer Fahrdaten Zeile sind:
 *  Position 1: Motor / Achse 1 - 3 für X, Y, Z
 *  Position 2: Fahren ja / nein. Im Moment immer 1
 *  Position 3: Dir, also rechts / links = 1 - 2
 *  Position 4: Tempo 1 - 1000
 *  Position 5: Schritte 1 - 250000 
 *  Das ist die Version 3.2 als erster Lauffähiger Code. 
*/
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
//---SD Kartenleser-------------------------------
#include <SPI.h>
#include <SD.h>
File myFile;
byte gelesen = 0;
const byte chipSelectPin = 53;
const String filename = "Mappe1.csv";
char zeilenBuffer[30];
char auswerteBuffer[6][8];
byte durchgaenge = 0;
byte automatik = 0;
//------------------------------------------------
#define MAX8BUTTONS // spart Speicher, da nur 8 Taster benötigt werden (saves RAM)
#include <MobaTools.h>
// Pindefinitions - change to your needs
//----------Motor 1-------------------------------
const byte dirPin1       = 5;
const byte stepPin1      = 4;
const byte enaPin1       = 6;
//----------Motor 2-------------------------------
const byte dirPin2       = 9;
const byte stepPin2      = 8;
const byte enaPin2       = 10;
//----------Motor 3-------------------------------
const byte dirPin3       = 44;
const byte stepPin3      = 43;
const byte enaPin3       = 45;
//----------Taster--------------------------------
const byte button1Pin   = A1; //---Motor1 links---
const byte button2Pin   = A2; //---Motor1 rechts--
const byte button3Pin   = A3; //---Motor2 links---
const byte button4Pin   = A4; //---Motor2 rechts--
const byte button5Pin   = A5; //---Motor3 links---
const byte button6Pin   = A6; //---Motor3 rechts--
const byte button7Pin   = A7; //---Fräsen Start---
const byte button8Pin   = A8; // Frei
//----------Tempo_Poti---------------------------------------------------------------
const byte potPin       = A0;   // Poti muss ein analoger Eingang sein
const int STEPS_REVOLUTION = 1600;
//--------Fahrdaten------------------------------------------------------------------
byte motor = 0;               // Achsenauswahl 1-2-3 ist X-Y-Z
byte fahren = 0;              // Motoren bewegen
byte richtung = 0;            // Richtung der Bewegung 1 oder 2
int tempo1 = 0;               // Tempo für Handsteuerung, wird vom Poti eingestellt
int tempo2 = 0;               // Tempo zum Fräsen
int tempo = 0;                // Wird vom Programm zur übergabe benutzt
unsigned long schritte = 0;   // Schritte die der Motor fahren soll
//-----------------------------------------------------------------------------------
//Stepper einrichten ( 1600 Schritte / Umdrehung - 1/4 Microstep )
MoToStepper myStepperX( STEPS_REVOLUTION, STEPDIR );  // 1600 Steps/ Umdrehung
MoToStepper myStepperY( STEPS_REVOLUTION, STEPDIR );  // 1600 Steps/ Umdrehung
MoToStepper myStepperZ( STEPS_REVOLUTION, STEPDIR );  // 1600 Steps/ Umdrehung
// Taster einrichten
// Den Tasternamen die Indizes 0...7 zuordnen
enum { Button1 = 0, Button2, Button3, Button4, Button5, Button6, Button7, Button8 };
// muss als byte definiert sein, damit ein enfaches sizeof funktioniert
const byte buttonPins[] = { button1Pin, button2Pin, button3Pin, button4Pin, button5Pin, button6Pin, button7Pin, button8Pin};
MoToButtons button( buttonPins, sizeof(buttonPins), 20, 500 );

MoToTimebase speedIntervall;    // Zeitinterval zum Auslesen des Speedpotentiometers
// the speed pot ist read only every 'speedintervall' ms

int vspeed = 0;                 //Steppergeschwindigkeit in U/min*10
//==============================================================================================================
void setup()
{
  lcd.begin();
  lcd.backlight();
  Serial.begin(9600);
  //-----------------------------Motor 1----------------------------------------------------
  myStepperX.attach( stepPin1, dirPin1 );
  myStepperX.attachEnable( enaPin1, 10, LOW );        // Enable Pin aktivieren ( LOW=aktiv )
  myStepperX.setSpeed( 200 );
  myStepperX.setRampLen( 100 );                       // Rampenlänge 100 Steps bei 20U/min
  speedIntervall.setBasetime( 100 );                  // 100ms Tickerzeit
  //------------------------------Motor 2-----------------------------------------------------
  myStepperY.attach( stepPin2, dirPin2 );
  myStepperY.attachEnable( enaPin2, 10, LOW );        // Enable Pin aktivieren ( LOW=aktiv )
  myStepperY.setSpeed( 200 );
  myStepperY.setRampLen( 100 );                       // Rampenlänge 100 Steps bei 20U/min
  //-----------------------------Motor 3------------------------------------------------------
  myStepperZ.attach( stepPin3, dirPin3 );
  myStepperZ.attachEnable( enaPin3, 10, LOW );        // Enable Pin aktivieren ( LOW=aktiv )
  myStepperZ.setSpeed( 200 );
  myStepperZ.setRampLen( 100 );                       // Rampenlänge 100 Steps bei 20U/min
  //------------------------------------------------------------------------------------------
  //----------CD_Kartenleser aktivieren-------------------------------------------------------
  pinMode(SS, OUTPUT);
  Serial.begin(9600);
  while (!Serial)
    Serial.print("Initializing SD card...");
  if (!SD.begin(chipSelectPin))
  {
    Serial.println("Initialisierung fehlgeschlagen!");
    while (1);
  }
  //Serial.println("Initialisierung abgeschlossen .");
  //-----------------------------------------------------------------------------------------
}
//=============================================================================================================
void loop() {
  lcd.setCursor (0, 0);
  lcd.print(F("Kreuztisch_Pr._V3.2 "));
  //------SD_Karte öffnen zum Lesen-------------------------------------------------------
  if (automatik == 1)
  {
    myFile = SD.open(filename);
    //Serial.print(filename);
    if (myFile)
    {
      //Serial.println(F(" geöffnet!"));
      while (myFile.available())
      {
        //Serial.print(F("Durchgaenge= "));
        //Serial.println(durchgaenge);
        durchgaenge ++;
        leseZeile();
        ausgabeZeile();
        teileZeile();
        ausgabeWerte();
      }
      // schließe die Datei:
      myFile.close();
      //Serial.println(F(" geschlossen!"));
      //while (1);
      automatik = 0;
      lcd.setCursor (0, 2);
      lcd.print (F("                    "));
      lcd.setCursor (0, 2);
      lcd.print (F(" Karte abgearbeitet "));
    }
    else
    {
      // Wenn die Datei nicht geöffnet wurde, drucke einen Fehler :
      //Serial.print("Fehler beim öffnen von ");
      //Serial.println(filename);
    }
  }
  //--------------------------------------------------------------------------------------
  button.processButtons();          // Taster einlesen und bearbeiten

  // Speed alle 100ms neu einlesen und setzen
  if ( speedIntervall.tick() ) {
    // wird alle 100ms aufgerufen ( Tickerzeit = 100ms im setup() )
    vspeed = map((analogRead(potPin)), 0, 1023, 20, 1800);  //Poti mappen auf 2 ... 180 Umdr/Min
    //min speed =2 and max speed =180 rpm
    tempo1 = vspeed;
    myStepperX.setSpeed( tempo );
    myStepperY.setSpeed( tempo );
    myStepperZ.setSpeed( tempo );
  }
  //----------------------------------------------------------------
  // Drehen Motor1 rechtsrum
  if (button.pressed(Button1) ) {
    //Taster1 gedrückt
    tempo = tempo1;
    myStepperX.rotate( 1 );          // Stepper1 dreht vorwärts
  }
  if ( button.released(Button1) ) {
    //Taster1 losgelassen
    myStepperX.rotate(0);             // Stepper1 stoppt
  }

  //Drehen Motor1 linksrum
  if (button.pressed(Button2) ) {
    //Taster2 gedrückt
    tempo = tempo1;
    myStepperX.rotate( -1 );         // Stepper1 dreht rückwärts
  }
  if ( button.released(Button2) ) {
    //Taster2 losgelassen
    myStepperX.rotate(0);    // Stepper1 stoppt
  }
  //-----------------------------------------------------------------
  // Drehen Motor2 rechtsrum
  if (button.pressed(Button3) ) {
    //Taster1 gedrückt
    tempo = tempo1;
    myStepperY.rotate( 1 );          // Stepper2 dreht vorwärts
  }
  if ( button.released(Button3) ) {
    //Taster1 losgelassen
    myStepperY.rotate(0);             // Stepper2 stoppt
  }

  //Drehen Motor2 linksrum
  if (button.pressed(Button4) ) {
    //Taster2 gedrückt
    tempo = tempo1;
    myStepperY.rotate( -1 );         // Stepper2 dreht rückwärts
  }
  if ( button.released(Button4) ) {
    //Taster2 losgelassen
    myStepperY.rotate(0);            // Stepper2 stoppt
  }
  //-----------------------------------------------------------------
  // Drehen Motor3 rechtsrum
  if (button.pressed(Button5) ) {
    //Taster1 gedrückt
    tempo = tempo1;
    myStepperZ.rotate( 1 );          // Stepper3 dreht vorwärts
  }
  if ( button.released(Button5) ) {
    //Taster1 losgelassen
    myStepperZ.rotate(0);             // Stepper3 stoppt
  }

  //Drehen Motor3 linksrum
  if (button.pressed(Button6) ) {
    //Taster2 gedrückt
    tempo = tempo1;
    myStepperZ.rotate( -1 );         // Stepper3 dreht rückwärts
  }
  if ( button.released(Button6) ) {
    //Taster2 losgelassen
    myStepperZ.rotate(0);            // Stepper3 stoppt
  }
  if (button.pressed(Button7) ) {
    //Taster7 gedrückt
    automatik = 1;
  }
  if ( button.released(Button7) ) {
    //Taster7 losgelassen
  }
  //-----------------------------------------------------------------

}
//=========================================================================================
void leseZeile()
{
  char myChar = '\0';
  static byte bufferPosition = 0;
  if (bufferPosition == 0) memset(zeilenBuffer, '\0', sizeof(zeilenBuffer));
  while (myFile.available() && myChar != '\n')
  {
    myChar = myFile.read();
    if (isPrintable(myChar))
    {
      zeilenBuffer[bufferPosition] = myChar;
      bufferPosition++;
    }
  }
  // Hier ist ZeilenEnde / DateiEnde
  bufferPosition = 0;
}
//=========================================================================================
void ausgabeZeile()
{
  //Serial.print(F("eingelsene Zeile ist: "));
  //Serial.println(zeilenBuffer);
  lcd.setCursor (0, 0);
  lcd.print (F("                    "));
  lcd.setCursor (0, 0);
  lcd.print (zeilenBuffer);
}
//=========================================================================================
void teileZeile()
{
  // zeilenbuffer in auswerteBuffer umschreiben
  memset(auswerteBuffer, '\0', sizeof(auswerteBuffer) / sizeof(auswerteBuffer[0]));
  strcpy (auswerteBuffer[0], strtok(zeilenBuffer, ";"));
  strcpy (auswerteBuffer[1], strtok(NULL, ";"));
  strcpy (auswerteBuffer[2], strtok(NULL, ";"));
  strcpy (auswerteBuffer[3], strtok(NULL, ";"));
  strcpy (auswerteBuffer[4], strtok(NULL, ";"));
  strcpy (auswerteBuffer[5], strtok(NULL, ";"));
  //Serial.println(F("Puffer geteilt!"));
}
//========================================================================================
void ausgabeWerte()
{
  for (byte b = 0; b < sizeof(auswerteBuffer) / sizeof(auswerteBuffer[0]); b++)
  {
    //Serial.print(F("Wert "));
    //Serial.print(b);
    //Serial.print(F(" ist: "));
    //Serial.println(auswerteBuffer[b]);
    motor = atoi(auswerteBuffer[0]);
    fahren = atoi(auswerteBuffer[1]);
    richtung = atoi(auswerteBuffer[2]);
    tempo = atoi(auswerteBuffer[3]);
    myStepperX.setSpeed(tempo);
    myStepperY.setSpeed(tempo);
    myStepperZ.setSpeed(tempo);
    schritte = atoi(auswerteBuffer[4]);

    lcd.setCursor (0, 2);
    lcd.print(F("                    "));
    lcd.setCursor (0, 2);
    lcd.print(motor);
    lcd.print (F("/"));
    lcd.print(fahren);
    lcd.print (F("/"));
    lcd.print(richtung);
    lcd.print (F("/"));
    lcd.print(tempo);
    lcd.print (F("/"));
    lcd.print(schritte);
    //-------------Motor 1-----------------
    if (motor == 1)
    {
      if (richtung == 1)
      {
        myStepperX.doSteps(schritte);
      }
      if (richtung == 2)
      {
        myStepperX.doSteps(-schritte);
      }
      fahren = 0;
      motor = 0;
      richtung = 0;
      schritte = 0;
    }
    //-------------Motor 2-----------------
    if (motor == 2)
    {
      if (richtung == 1)
      {
        myStepperY.doSteps(schritte);
      }
      if (richtung == 2)
      {
        myStepperY.doSteps(-schritte);
      }
      fahren = 0;
      motor = 0;
      richtung = 0;
      schritte = 0;
    }
    //------------Motor 3------------------
    if (motor == 3)
    {
      if (richtung == 1)
      {
        myStepperZ.doSteps(schritte);
      }
      if (richtung == 2)
      {
        myStepperZ.doSteps(-schritte);
      }
      fahren = 0;
      motor = 0;
      richtung = 0;
      schritte = 0;
    }
  }//---------fahren ende-----------------
  while(myStepperX.moving()>0){}
  while(myStepperY.moving()>0){}
  while(myStepperZ.moving()>0){}
}

auch wenns absolut nicht der weg ist den ich gehen würde, wenn sich was bewegt ist das natürlich nett zum anschauen :wink:

was mir auffällt, da macht imho das F-Makro keinen Sinn, ist zu kurz:

lcd.print (F("/"));

ich glaube sparsamer wäre

lcd.write('/');

sollte zumindest den Nullterminator sparen.

verlinkst vieleicht noch die Stepper Treiber die du verwendest?

OK, das wußte ich nicht. Kenne nicht mal den Unterschied. Aber mache ich mir Notizen dazu.

Das mit dem Verlinken mache ich im Thema Fräser, worauf ich jetzt dann wieder zurück komme. Ich mache einen Link dorthin. Und bringe dann dort die Hardware dazu. Das war am Anfang das "Hardware Thema" zur Fräse.

Aber das ganze ist natürlich mehr eine Spielerei, denn bei einem richtigen 3D Frästisch ist man nun mal bei EstlCam. Ich will da was möglichst Sinnvolles tun, und das "Resthirn" damit in Bewegung halten.

Franz

Siehe am Ende der Seite PROGMEM.