CNC Fräse – Elektronik und Software

Die Testschaltung

Zuallererst entwickelte ich eine einfache Testschaltung, um die Fräse grundsätzlich auf ihre Funktion zu prüfen. Die Schaltung besteht nur aus einem Mikrocontroller, drei Motor-Treiberbausteinen (L293D) sowie der Stromversorgung (mit Spannungsregler 7805).

Die Ansteuerung der Schrittmotoren durch einen ATmega8 erfolgte über drei Stück L293D, an denen je ein Motor angeschlossen war. Der Code für den Mikrocontroller ist recht simpel, deshalb lasse ich ihn hier weg. Mit diesen Schemata ist es ein Leichtes eine Schrittmotorsteuerung zu programmieren.

Anhand dieser Schaltung konnte ich also die Schrittmotoren testen und z.B. feststellen, wieviel Umkehrspiel die Achsen haben. Es war ein gerade noch mit bloßem Auge erkennbares Umkehrspiel zu sehen.

Erste Version der Steuerung

Das Konzept der ersten Version sah folgendermaßen aus: Die Fräse wird vollständig von einem Mikrocontroller gesteuert. Um den Programmieraufwand kleiner zu halten, wurden dazu neben drei L293D auch drei L297 eingesetzt, die die Generierung der Motorschritte übernehmen. So ist pro Motor nur noch ein Signal für die Schritte und eines für die Richtung nötig. Die Fräsdaten sind in einem EEPROM gespeichert. Sie werden vorher mittels der Software PonyProg vom Computer auf den EEPROM übertragen. Als EEPROM-Baustein war der 24C256 angedacht.

Zum Übertragen der Daten vom PC zum Speicher bedarf es einer eigenen Schaltung. Diese baute ich nach dem Vorbild auf der Homepage von PonyProg.

Die Schaltung der Fräsensteuerung enthält also den Mikrocontroller, die Motortreiber L297 und L293D, den EEPROM-Baustein 24C256, den Spannungsregler und einen 10fach DIL-Schalter für Einstellmöglichkeiten. Außerdem führte ich eine Reihe Pins an eine Stiftleiste, um damit eventuell einmal ein LCD ansteuern zu können. Damit wurde sie für meine Verhältnisse schon etwas komplexer, das Routen der Leiterbahnen wurde schwieriger und brachte leider recht viele Drahtbrücken.

Um auch schräge Linien fräsen zu können, habe ich den Bresenham-Algorithmus verwendet. Die Implementierung des Algorithmus in C habe ich mehr oder weniger vom Wikipediaartikel übernommen. Den Code des Mikrocontrollers zur Ansteuerung der Motoren sieht man weiter unten.

Es stellte sich jedoch bald heraus, dass die Datenmengen, die bei Fräsprojekten anfallen, weit größer als die Kapazität des EEPROM sind. So blieb es bei einigen geometrischen Testfiguren, deren Fräsdaten im Mikrocontroller gespeichert waren.

Doch schon an dieser Pyramide in Plastik konnte man sehen, wie auch dreidimensionale Dinge schön gefräst werden können.

pyramid of plastic

Gefräste Pyramide

Ein Quellcode mit integrierten Fräsdaten (Zeile 170 bis 223) sah dann z.B. so aus:

/************************************************************************
*                        Fräsensoftware 1.0                             *
*                        Autor: Jacob Seibert                           *
*                        Datum: 22.03.2010                              *
*                        IDE: AVR Studio 4.16                           *
*                                                                       *
*         Mit Bresenham-Algorithmus (abgeleitet von Wikipedia)          *
*************************************************************************/
#include <avr/io.h>
#define F_CPU 1000000UL     /* Quarz mit 1 MHz (intern)*/
#include <util/delay.h>

/*Define-Teil*/
#define _FULLSTEPMODUS 1
#define _ENABLED 1
#define _WAIT_MS 3
#define _X_MOTOR 1
#define _Y_MOTOR 2
#define _Z_MOTOR 0
#define _POSITIV 1
#define _NEGATIV 0

int akt_x = 0, akt_y = 0;

/*Funktion zur Generation eines Schrittes. Parameter: Motor und Richtung*/
void step (uint8_t motor, uint8_t direction, uint16_t count)
{
  uint16_t tmp = count;
  while(tmp!=0)
  {
    tmp--;
    switch(motor)/*Motor wählen*/
	{
      case _Z_MOTOR:
	  if(direction == _POSITIV)/*Richtung wählen*/
	  {
        PORTC |= (1<<PC1);
	  }
	  else
	  {
        PORTC &= ~(1<<PC1);
	  }
      PORTC &= ~(1<<PC0);
	  _delay_ms(_WAIT_MS);
      PORTC |= (1<<PC0);
	  break;

	  case _X_MOTOR:
	  if(direction == _POSITIV)/*Richtung wählen*/
	  {
        PORTB |= (1<<PB1);
	  }
	  else
	  {
        PORTB &= ~(1<<PB1);
	  }
      PORTB &= ~(1<<PB0);
	  _delay_ms(_WAIT_MS);
      PORTB |= (1<<PB0);
	  break;

	  case _Y_MOTOR:
	  if(direction == _POSITIV)/*Richtung wählen*/
	  {
        PORTB |= (1<<PB4);
	  }
	  else
	  {
        PORTB &= ~(1<<PB4);
	  }
      PORTB &= ~(1<<PB3);
	  _delay_ms(_WAIT_MS);
      PORTB |= (1<<PB3); 	}   } }; /*Signum (Mathematik)*/ int sgn (int x) {   return (x > 0) ? 1 : (x < 0) ? -1 : 0;
};

/*--------------------------------------------------------------
 * Bresenham-Algorithmus: Linien auf Raster zeichnen
 *
 * Eingabeparameter:
 *    int xstart, ystart        = Koordinaten des Startpunkts
 *    int xend, yend            = Koordinaten des Endpunkts
 *
 * Ausgabe:
 *    void step()
 *---------------------------------------------------------------
 */
void abs_koo (uint16_t xend, uint16_t yend)
{
    int x, y, t, dx, dy, incx, incy, pdx, pdy, ddx, ddy, es, el, err;
 
/* Entfernung in beiden Dimensionen berechnen */
   dx = xend - akt_x;
   dy = yend - akt_y;
 
/* Vorzeichen des Inkrements bestimmen */
   incx = sgn(dx);
   incy = sgn(dy);
   if(dx<0)
   {
     dx = -dx;
   }
   if(dy<0)    {      dy = -dy;    }   /* feststellen, welche Entfernung größer ist */    if (dx>dy)
   {
      /* x ist schnelle Richtung */
      pdx=incx; 
	  pdy=0;    /* pd. ist Parallelschritt */
      ddx=incx; 
	  ddy=incy; /* dd. ist Diagonalschritt */
      es =dy;   
	  el =dx;   /* Fehlerschritte schnell, langsam */
   } 
   else
   {
      /* y ist schnelle Richtung */
      pdx=0;    
	  pdy=incy; /* pd. ist Parallelschritt */
      ddx=incx; 
	  ddy=incy; /* dd. ist Diagonalschritt */
      es =dx;   
	  el =dy;   /* Fehlerschritte schnell, langsam */
   }
 
/* Initialisierungen vor Schleifenbeginn */
   x = akt_x;
   y = akt_y;
   err = el/2;

/* Pixel berechnen */
   for(t=0; t<el; ++t) /* t zaehlt die Pixel, el ist auch Anzahl */
   {
      /* Aktualisierung Fehlerterm */
      err -= es; 
      if(err<0)       {           /* Fehlerterm wieder positiv (>=0) machen */
          err += el;
          /* Schritt in langsame Richtung, Diagonalschritt */
          x += ddx;
          y += ddy;
          if (xend>akt_x)/*X-Richtung ist positiv*/
          {
		    step(_X_MOTOR, _POSITIV, 1);
          }
		  else/*Y_Richtung ist negativ*/
		  {
            step(_X_MOTOR, _NEGATIV, 1);
		  }
		  if(yend>akt_y)/*Y_Richtung ist positiv*/
		  {
            step(_Y_MOTOR, _POSITIV, 1);
		  }
		  else/*Y_Richtung ist negativ*/
		  {
            step(_Y_MOTOR, _NEGATIV, 1);
		  }
      } 
	  else
      {
          /* Schritt in schnelle Richtung, Parallelschritt */
          x += pdx;
          y += pdy;
          if(dx>dy)/*X ist schnelle Richtung*/
		  {
            if (xend>akt_x)/*X-Richtung ist positiv*/
            {
		      step(_X_MOTOR, _POSITIV, 1);
            }
		    else/*Y_Richtung ist negativ*/
		    {
              step(_X_MOTOR, _NEGATIV, 1);
		    }
		  }
		  else/*Y ist schnelle Richtung*/
		  {
            if(yend>akt_y)/*Y_Richtung ist positiv*/
		    {
              step(_Y_MOTOR, _POSITIV, 1);
		    }
		    else/*Y_Richtung ist negativ*/
		    {
              step(_Y_MOTOR, _NEGATIV, 1);
		    }
		  }
      }
   }
   akt_x = xend;
   akt_y = yend;
};

int main (void)
{
/*Initialisierungen*/
  	DDRC |= (1<<PC1) | (1<<PC2) | (1<<PC0);	/*1 - EN/CW-CCW/CLK*/
  	DDRD |= (1<<PD7);							/*HALF/FULL*/
  	DDRB |= (1<<PB0) | (1<<PB1) | (1<<PB2) | (1<<PB3) | (1<<PB4) | (1<<PB5);/*2/3 - EN/CW-CCW/CLK*/
  	if(_FULLSTEPMODUS == 1)
  	{
    	PORTD &= ~(1<<PD7);						/*FULLstep-Modus*/
  	}
  	else
  	{
    	PORTD |= (1<<PD7);						/*HALFstep-Modus*/
  	}
  	if(_ENABLED == 1)
  	{
    	PORTC |= (1<<PC2);						/*1 - EN set*/
		PORTB |= (1<<PB2) | (1<<PB5);			/*2/3 - EN set*/
  	}
  	else
  	{
    	PORTC &= ~(1<<PC2);						/*1 - EN clear*/
		PORTB &= ~((1<<PB2) | (1<<PB5));		/*2/3 - EN clear*/
  	} 
  //abs_koo(000, 000);
/*Main-Teil*/
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  abs_koo(6528, 356);
  step(_Z_MOTOR, _POSITIV, 2500);//DOWN
  abs_koo(6528, 2494);
  abs_koo(5481, 2494);
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  abs_koo(5481, 2516);
  step(_Z_MOTOR, _POSITIV, 2500);//DOWN
  abs_koo(5259, 2541);
  abs_koo(5047, 2609);
  abs_koo(4853, 2722);
  abs_koo(4688, 2869);
  abs_koo(4556, 3050);
  abs_koo(4466, 3256);
  abs_koo(4419, 3475);
  abs_koo(4419, 3697);
  abs_koo(4466, 3916);
  abs_koo(4556, 4119);
  abs_koo(4688, 4300);
  abs_koo(4853, 4450);
  abs_koo(5047, 4563);
  abs_koo(5259, 4631);
  abs_koo(5481, 4653);
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  abs_koo(5481, 4631);
  step(_Z_MOTOR, _POSITIV, 2500);//DOWN
  abs_koo(6528, 4631);
  abs_koo(6528, 2494);
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  abs_koo(3297, 356);
  step(_Z_MOTOR, _POSITIV, 2500);//DOWN
  abs_koo(3297, 3347);
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  abs_koo(3297, 4203);
  step(_Z_MOTOR, _POSITIV, 2500);//DOWN
  abs_koo(3297, 4631);
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  abs_koo(2203, 3159);
  step(_Z_MOTOR, _POSITIV, 2500);//DOWN
  abs_koo(922, 3159);
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  abs_koo(1588, 4631);
  step(_Z_MOTOR, _POSITIV, 2500);//DOWN
  abs_koo(1588, 972);
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  abs_koo(1588, 972);
  step(_Z_MOTOR, _POSITIV, 2500);//DOWN
  abs_koo(1528, 728);
  abs_koo(1381, 525);
  abs_koo(1169, 394);
  abs_koo(922, 356);
  step(_Z_MOTOR, _NEGATIV, 2500);//UP
  while(1);
}

Zweite Version der Steuerung

Die Fräse musste also mit dem Computer gesteuert werden. Nach einigen Überlegungen, die Software dafür selbst zu schreiben, stieß ich dann auf die Open-Source-Software EMC, mit der Schrittmotoren über die Druckerschnittstelle mit Schritt-/Richtungssignalen angesteuert werden können. Da ich aber nicht eine völlig neue Schaltung entwickeln und die alte verwerfen wollte, überlegte ich mir einen Adapter, den man in die IC-Fassung des Mikrocontrollers stecken konnte. An den Adapter kam das Druckerkabel und wurde entsprechend verkabelt. Rechts im Bild das Druckerkabel zum Computer, links die Anschlüsse zu den Schrittmotoren. Außerdem der (theoretische) Schaltplan ohne die letztlich überflüssigen Komponenten.

Danach testete ich diese Schaltung an meinem PC mit der Ubuntu-EMC-Live-CD. Es funktionierte nach einigem Probieren bei den Einstellungen schon ganz gut. Um aber die Einstellungen dauerhaft zu erhalten musste ich Ubuntu erstmal auf einer meiner Schrottmühlen zum Laufen bringen und installieren. Was sich als gar nicht so einfach erwies. Erst als sich ein etwas dynamischerer (mehr als 800 MHz) Computer neu zu den anderen gesellte und auch noch gleich etwas mehr Arbeitsspeicher dabei hatte, ließ sich Ubuntu breitschlagen… ähh.. installieren.

Die Konfiguration von EMC ist relativ simpel, wenn man den EMC2 Stepconf Wizard benutzt. Es sind nur einige Grundeinstellungen sowie die Pinbelegung des Druckerports anzugeben: (Bilder folgen)

Seitdem Ubuntu nun läuft (es gab mittlerweile auch ein Update), gibt es eigentlich keine Probleme mehr. Was noch nicht funktioniert bzw. noch fehlt ist der Anschluss der Endtaster. Bis jetzt muss ich immer noch selbst aufpassen, dass die Fräse nicht über ihre Grenzen hinaus fährt und sich dabei möglicherweise selbst beschädigt.

Erzeugung des G Codes – Platinen

Mein bevorzugtes Layout-Programm (Target 3001) konnte die Daten fürs Isolations-Fräsen leider nur im HPGL-Format ausgeben (Update Dez. 2011: Jetzt ist auch die Ausgabe als G-Code für EMC möglich!). Um diese Daten mit EMC zu verarbeiten, musste also eine Konvertierungssoftware her, um die Daten in G-Code umzuwandeln. Da ich ein solches für mich geeignetes Programm aber nicht im Internet finden konnte, habe ich mir selbst eines in C++ geschrieben. Näheres dazu gibt es auf der eigenen Seite: HPGL to GCode.

HPGL to G-Code (Screenshot)

Erzeugung des G Codes – Holzlampen

Die Fräsdaten, mit denen ich die „Holzbilder“ gefräst habe, werden direkt mit EMC aus Graustufenbildern generiert. Dazu wird einfach mit Datei → Öffnen ein Graustufenbild importiert. Siehe auch Holzlampen.

2 Kommentare

  1. Hallo Jacob,
    zunächst will ich dir für deine hervorragende Dokumentation deiner selbstgebauten CNC danken. Ich bin schon sehr lange auf der Suche nach einem Weg eine CNC in eigenleistung zu verwirklichen. Dank deiner Schema Darstellung (Kombination aus L297 und L293) habe ich jetzt auch den Schritt gewagt 🙂

    Ich habe mir anhand deines Schemas eine einschichtige Platine geäzt und im Grunde alle Teile außer L293D einen L293NE (max 1A Stromstärke) verwendet.

    Leider habe ich bisher meine (deine) Schaltung mit LinuxCNC nicht an start bekommen.
    Die Signale vom PC kommen sauber am L297 an und werden auch an die entsprechenden Ports vom L293NE weitergegeben. Leider macht der L293 bei einer logischen 1 auf z.Bsp. Pin15 keine +12V auf Pin14.

    Kann es sein dass ich an deinem Schema etwas an den Enabled Pins vom L293 missverstanden habe? Ich erkenne da, dass alle Enabled Pins von (L923) IC1+2+3 zusammen geschaltet sind. Müsste an diese Leitung nicht eine 5V Leitung rangehen ?
    Ich habe meine Schaltung hier hochgeladen: http://1drv.ms/1DS2u4L

    Kannst du mir vielleicht helfen ?

    Viele Grüße aus Hamburg
    Alex

    • Hallo Alex,

      ja du hast vollkommen Recht, die Enable-Eingänge der L293 müssten auf +5V. Du hast also nichts missverstanden sondern in meinem Schaltplan fehlt schlicht etwas. 😉 Ich muss das dringend mal nachbessern.
      Ich hoffe du bekommst deine Schaltung damit zum Laufen.

      Beste Grüße,
      Jacob

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

Bitte beantworte kurz folgende Frage, um zu zeigen, dass du kein Roboter bist: *