Zuerst wollte ich es nur noch einmal hochladen, und zu diesem Zweck bekam ich die Quellcodes für das Grbl CNC-Projekt. Aber die Neugier überwand und ich stürzte mich in das Studium dieser Quellen ...
sie bilden eine sehr einfache und logische, aber
Eigentlich ist die Idee einer Steuerung für eine CNC-Maschine recht einfach und interessant. Es gibt mehrere Verarbeitungsthreads - einer liest Daten (gcode) und analysiert sie, der zweite wandelt Befehle in Ausführungsblöcke um und der dritte (Stepper) führt diese Blöcke tatsächlich aus. Dieser dritte Strom wird diskutiert.
Der Stepper behandelt eine Liste einzelner Befehle der Form - mache (X, Y, Z) Schritte für alle drei (mindestens) Schrittmotoren und für eine bestimmte Zeit und in eine bestimmte Richtung (nun, das ist so einfach). Ich muss sagen, dass ein Schrittmotor mit seinem Treiber ganz einfach zu steuern ist - Sie stellen die Drehrichtung ein (0 oder 1) und dann versucht der Motor, einen Schritt durch ein positives Eingangsdifferential (0 -> 1) zu machen (und es gibt normalerweise 200 Schritte pro Umdrehung). Die Daten sind bereits vorbereitet, sodass Sie die 3 Ganzzahlen nur irgendwie mit der angegebenen Zeit korrelieren müssen.
Im Original verwendete der Autor den atmega328p-Controller, aber praktisch ohne Änderungen lässt sich alles leicht auf den Arm übertragen (z. B. stm32). Der Algorithmus selbst kann jedoch nur Fragen aufwerfen.
Einerseits wird ein sehr perfekter Bresenham-Algorithmus verwendet, oder vielmehr seine Version der adaptiven mehrachsigen Schrittglättung. Andererseits ist alles irgendwie kompliziert und vor allem hängen die Laufruhe des Schrittmotors und die Genauigkeit des Routers direkt von der Genauigkeit der Steuersignale ab. In diesem Fall ist dies auf die Frequenz zurückzuführen, mit der der Timer arbeitet, und auf die Interrupt-Verarbeitungszeit - und dies ergibt bestenfalls nicht mehr als 40-50 kHz und normalerweise sogar weniger -, dh die Genauigkeit der Steuerungseinstellung beträgt 20-50 Mikrosekunden.
Es ist jedoch ziemlich offensichtlich, dass wir, wenn wir einen Befehl aus dem Puffer verarbeiten, nur die Momente der Signalumschaltung am Ausgangsport und diese Momente berechnen und die Umschaltung vornehmen müssen.
Da ich überlegt habe, auf cortex-m umzusteigen (genauer gesagt auf stm32h750, das ich sehr liebe und das sehr billig geworden ist), kann eine solche Aufgabe überhaupt gelöst werden, ohne die CPU einzubeziehen, sondern nur mit zwei DMA-Kanälen und einem 32-Bit-Zähler.
Die Idee ist sehr einfach. Lassen Sie einen Kanal beim Zählerüberlauf neue Daten in den Port schreiben, und der zweite Kanal schreibt einen neuen maximalen Zählerwert (dies ist beim allerersten Taktzyklus des Zählers sinnvoll). Um den Befehl aus der Liste zu verarbeiten, müssen Sie sich auf ein Array von Änderungswerten für den Port und die Zeitüberschreitungen zwischen ihnen vorbereiten.
Es wird sich so etwas herausstellen.
Interrupt-Behandlung - Umschalten auf einen neuen Puffer (doppelte Pufferung).
#define MAX_PGM 32
typedef struct _pgm_buffer {
uint32_t data[MAX_PGM];
uint32_t delta[MAX_PGM];
} pgm_buffer;
pgm_buffer buf[2];
uint32_t current_buf = 1;
uint32_t flags = 0;
void program_down(DMA_HandleTypeDef *_hdma) {
TIM2->CR1 &= ~TIM_CR1_CEN;
if ((flags & BUF_RUNNING) == 0)
return;
current_buf ^= 1;
DMA1_Channel5->CCR &= ~1;
DMA1_Channel2->CCR &= ~1;
DMA1_Channel5->CNDTR = MAX_PGM;
DMA1_Channel2->CNDTR = MAX_PGM;
DMA1_Channel5->CMAR = (uint32_t) (buf[current_buf].delta);
DMA1_Channel2->CMAR = (uint32_t) (buf[current_buf].data);
DMA1_Channel5->CCR |= 1;
DMA1_Channel2->CCR |= 1;
TIM2->CNT = 0;
TIM2->ARR = 8;
TIM2->EGR |= TIM_EGR_UG;
TIM2->CR1 |= TIM_CR1_CEN;
}
Sie können folgendermaßen initiieren:
HAL_DMA_RegisterCallback(&hdma_tim2_up, HAL_DMA_XFER_CPLT_CB_ID,
program_down);
HAL_DMA_Start_IT(&hdma_tim2_up, buf, &GPIOA->BSRR, MAX_PGM);
DMA1_Channel5->CCR &= ~1;
DMA1_Channel5->CPAR = &TIM2->ARR;
DMA1_Channel5->CCR |= 1;
TIM2->CCR1 = 1;
TIM2->DIER |= TIM_DIER_UDE | TIM_DIER_CC1DE;
flags |= BUF_RUNNING;
Nun, der Anfang ist:
program_down(NULL);
Was tut es? Berechnen wir am Beispiel des gleichen stm32h750. Der Timer (TIM2) arbeitet dort mit einer Frequenz von 200 MHz, die minimale Latenz beträgt zwei Taktzyklen, aber DMA kann keine Daten schneller als 50 MHz senden, dh zwischen zwei Befehlen zum Umschalten des Ports können Sie (unter Berücksichtigung der möglichen Busbelegung) 40 ns (25 MHz) setzen - das ist 1000 mal besser als die ursprüngliche Implementierung!
Auf der anderen Seite beträgt die Portbreite 16 Bit, sodass Sie 8 Schrittmotoren gleichzeitig steuern können, anstatt 3
In diesem Fall verursacht das Ausfüllen der tatsächlichen Daten keine Probleme (mit dieser und jener Auflösung!) - einfache lineare Interpolation für jeden Motor separat mit durch Kombinieren (zur Optimierung) von Ereignissen näher als 40 ns.
Die tatsächlichen Schlussfolgerungen.
In der Werkstatt gibt es eine fertige CNC-Maschine mit einer Größe von 1,2 x 0,8 Metern mit Motoren und Treibern, aber ohne Steuerung. Sieht so aus, als müssten wir den Job beenden und versuchen, wie episch er sein wird. Wenn ich das tue, werde ich definitiv eine Fortsetzung schreiben. In der Zwischenzeit verstehe ich nicht, warum die Controller dies auf atmega tun und sie quietschen auf allen 3D-Druckern und CNC-Routern bei diesen groben Interrupts ...
Und natürlich können Sie mit der Leistung von Cortex-M7 eine reibungslosere Flugbahnsteuerung mit allen Einschränkungen implementieren , aber das ist ein ganz anderer Artikel.
PS Anscheinend ist es notwendig, ein hypothetisches Beispiel zu geben, warum es so wichtig ist, eine so kurze Zeit zu haben.
Nehmen wir an, die Maschine muss sich in X um 100 mm und in Y um 11 mm bewegen, und die Software hat alles in Abschnitte mit Beschleunigung und gleichmäßiger Bewegung unterteilt. Es gibt viele Abschnitte mit 100 Schritten in 11 Schritten, die mit maximaler Geschwindigkeit durchlaufen werden. Dies entspricht 10 kHz. Nun, 10 Schritte in Y passen genau in 100 Schritte in X, aber bis zum 11. können Probleme auftreten - sie können übersprungen werden, da sie eine Verdoppelung der Frequenz verursachen. Infolgedessen beträgt die Bewegung 100 mm in X und 10 bis 11 mm in Y. Dies gilt für lineare Bewegungen, bei denen sogar die Einschränkungen für zulässige Beschleunigungen und Geschwindigkeiten einfach sind. Und wenn es im Zickzack gemacht wird? Nun, zum Beispiel wird eine Fläche von 100 mal 110 mm in 10 Durchgängen gewischt - dann vermissen wir im Allgemeinen sehr viel ... Der
vorgeschlagene Algorithmus wird verwendet, um diesen Fehler zu beseitigen, und überhaupt nicht für Supermühlen usw.