Arduino - Zwei Schrittmotore an einem 74HC595 Shiftregister via SPI

| abgelegt unter: , ,
Wenn man mit dem Arduino Schrittmotore betreiben will, kann man das Stepper()-Objekt nutzen. Dummerweise kann man davon nur wenige Objekte in einem Arduino verwenden. Es kam mir der Gedanke, dass sich die Pulse für 2 Motore auch als Low-Byte und High-Byte beschreiben lassen und ein 8-Bit-Shift-Register so zwei Schrittmotore bepulsen kann.

Spannend ist dieser Ansatz auch, weil der SPI-Bus mit Systemtakt laufen kann. Das sind beim Arduino UNO satte 16MHz.

 

DigitalWriteFast

Die schlechte Nachricht vorab: Der ATMega ist in Zusammenhang mit der Arduino IDE kein besonders schneller Schalter. Das digitalWrite-Kommando braucht einfach viel zu lange. Zum Glück hat sich schon mal jemand damit beschäftigt und eine andere Bibliothek namens DigitalWriteFast geschrieben, die die Kosten für einen Schaltvorgang erheblich reduziert. Zwar könnte man auch mit der nativen Syntax minimal schneller schalten, doch die Lesbarkeit, Verständlichkeit und Anpassbarkeit des Sketches würde sinken. Ich bevorzuge Code, den ich möglichst schnell erfassen kann.

 

Der Aufbau

Bei diesem Aufbau werden insgesamt 4 Pins des Arduino belegt. Mit einem zusätzlichen Pin können aber zwei weitere Motore integriert werden.

 

 

 

Ausserdem fällt dem geschulten Auge sicher auf, dass man die Motore natürlich nicht direkt an das Shift-Register hängen kann, da pro Pin am 74HC595 nur etwa 40mA zur Verfügung stehen. Viel zu wenig für einen Stepper. Daher gehört zwischen das Register und die Motore je eine Doppel-H-Brücke (z.B. L298N), die als Signalverstärker fungiert.

 

Der Sketch

Zuerst importiert man die benötigten Bibliotheken.

#include <SPI.h>
#include <digitalWriteFast.h>

Anschließend definiert man die Schrittmodi. Hier: Vollschritt und Halbschritt. Man kann auch die entsprechenden Dezimalzahlen in das Array schreiben, aber so finde ich das fast übersichtlicher. Es müssen für Low- und High-Byte entsprechend Arrays angelegt werden.

byte MotorAFull[4] = {0b10000000,0b00100000,0b01000000,0b00010000};
byte MotorBFull[4] = {0b00001000,0b00000010,0b00000100,0b00000001};
byte MotorAHalf[8] = {0b10000000,0b10100000,0b00100000,0b01100000,0b01000000,0b01010000,0b00010000,0b10010000};
byte MotorBHalf[8] = {0b00001000,0b00001010,0b00000010,0b00000110,0b00000100,0b00000101,0b00000001,0b00001001};

Nun noch ein paar Variablen, die Zur Laufzeit gebraucht werden.

byte StepMode = 0;                // 0 Full, 1 Half
byte MotorDir[2] = { 0, 0 };      // Motor A,B (0 back,1 forward)
byte MotorIndex[2] = { 0, 0 };    // start position (see MotorXFull|Half)
byte Motor;
const byte outregister = 2; // Latch pin
byte oreg[2] = {0,0};
byte dir;
byte ind

 In der setup()-Funktion wird das SPI-Objekt initialisiert und konfiguriert. Der Wert in SPI.setClockDivider ist ein Teiler des Systemtaktes. Im Beispiel also 16/1 = 16MHz. Nicht alle Geräte mit SPI vertragen diese Geschwindigkeit. Die 74HC595 kommen aber damit zurecht. Im Zweifel hilft ein Blick in Dokumentation des entsprechenden Gerätes.

void setup() {
  pinMode(outregister, OUTPUT);
  SPI.begin();
  SPI.setClockDivider(1);  // 16MHz!
}

 In main loop ist eigentlich auch nichts besonderes los

void loop() {

  for (byte Motor = 0; Motor<Motors;Motor++) {
      dir = MotorDir[Motor];
      ind = MotorIndex[Motor];
      oreg[Motor] = 0;
       
      switch(dir) {
        case 0:ind++;break;
        case 1:ind--;break;
      }
       
      switch(StepMode) {
        case 0: switch(ind) { case  4: ind = 0;break; case -1: ind = 3;break;} break;       
        case 1: switch(ind) { case  8: ind = 0;break; case -1: ind = 7;break;} break;      
      }

       MotorIndex[Motor] = ind;
     
       switch(Motor) {
         case 0:
           switch(StepMode) { case 0: oreg[Motor] = MotorAFull[ind];break; case 1: oreg[Motor] = MotorAHalf[ind];break; } break;         
         case 1:
           switch(StepMode) { case 0: oreg[Motor] = MotorBFull[ind];break; case 1: oreg[Motor] = MotorBHalf[ind];break; } break;
         case 2:
           switch(StepMode) { case 0: oreg[Motor] = MotorAFull[ind];break; case 1: oreg[Motor] = MotorAHalf[ind];break; } break;
         case 3:
           switch(StepMode) { case 0: oreg[Motor] = MotorBFull[ind];break; case 1: oreg[Motor] = MotorBHalf[ind];break; } break;
       }            
   }

SPI.transfer( oreg[0] + oreg[1] );
digitalWriteFast(outregister, LOW);
digitalWriteFast(outrgister, HIGH);
 
   delay(speed);
}

Dieser Code kann immerhin schon zwei Motore steuern. Wobei die Geschwindigkeit der beiden Motore nicht verschieden sein kann, die Richtung aber schon. Als kleines Testprogramm reicht es allemal.

 

Höher, Schneller, Weiter

Bei dem hier beschriebenen Ansatz fungiert der Arduino als Pulsgeber. Nach meiner Erfahrung macht er das auch sehr gut. Wenn man die Genauigkeit noch Steigern möchte, kann man z.B. einen ATMega in Verbindung mit einem Quarzoszillator verwenden. Wenn es nicht auf die Leistungsaufnahme ankommt, kann man auch übertakten. Einerseits natürlich durch die Veränderung des Taktmultiplikators über die FuseBits des ATMega, andererseits durch die Verwendung entsprechend schneller externer Taktgeber. Ein ATMega auf interner Clock kommt hier ins Straucheln. Vor allem erhöht der Takt auch die Schaltgeschwindigkeit des Latch Pins.