In den beiden vorherigen Teilen habe ich darüber gesprochen, wie ich eine grafische Benutzeroberfläche erstellt, einen Schrittmotor gesteuert und die Arbeit mit Dateien auf einem USB-Stick organisiert habe.
Heute werde ich über den Druckprozess, die Ausgabe der gedruckten Ebenen auf dem Hervorhebungsbildschirm und die verbleibenden, nicht so wesentlichen Dinge
schreiben : 4. Ausgabe der Bilder der Ebenen auf die Hervorhebungsanzeige.
5. Alles, wie die Steuerung von Beleuchtung und Lüftern, das Laden und Speichern von Einstellungen usw.
6. Zusätzliche Funktionen für Komfort und Bequemlichkeit.
- Teil 1: 1. Benutzeroberfläche.
- Teil 2: 2. Arbeiten mit dem Dateisystem auf einem USB-Stick. 3. Schrittmotorsteuerung für Plattformbewegung.
- 3: 4. . 5. , .. 6. .
4.
4.1 -
Wie hat es ein Mikrocontroller ohne spezielle Peripheriegeräte geschafft, das Bild auf einer hochauflösenden Matrix mit einer Geschwindigkeit von 74 Millionen Pixel pro Sekunde (Auflösung 2560 x 1440, 20 Bilder pro Sekunde) über die MIPI-Schnittstelle zu aktualisieren? Antwort: Verwenden eines FPGA mit einem angeschlossenen 16-MB-SDRAM und zwei MIPI-Schnittstellenchips - SSD2828. Zwei Mikroschaltungen sind sinnvoll, da die Anzeige logisch in zwei Hälften unterteilt ist, von denen jede über einen eigenen Kanal bedient wird, sodass zwei Anzeigen in einer erhalten werden.
Das Bild für die Anzeige wird in einer von 4 SDRAM-Bänken gespeichert. Der FPGA-Chip ist für die Wartung des SDRAM und die Ausgabe des Bildes von diesem an die SSD2828 verantwortlich. FPGA generiert vertikale und horizontale Synchronisationssignale für SSD2828 und Laufwerke
kontinuierlicher Strom von Farbwerten für Pixel über 24 Zeilen (8R 8G 8B) in jede der SSD2828. Die Bildrate beträgt ungefähr 20 Hz.
Das FPGA ist über eine serielle Schnittstelle (SPI) mit dem Mikrocontroller verbunden, über die der Mikrocontroller ein Bild übertragen kann. Es wird in Paketen übertragen, von denen jede eine Zeile des Bildes enthält (Zeilen werden entlang der kurzen Seite der Anzeige gezählt - 1440 Pixel). Zusätzlich zu diesen Daten enthält das Paket auch die SDRAM-Banknummer, die Zeilennummer und die Prüfsumme - CRC16. Das FPGA empfängt dieses Paket, überprüft die Prüfsumme und speichert die Daten im entsprechenden SDRAM-Bereich, wenn alles in Ordnung ist. Wenn die CRC nicht übereinstimmt, legt das FPGA an einem seiner ebenfalls mit dem Mikrocontroller verbundenen Pins ein Signal frei, wonach der Mikrocontroller versteht, dass die Daten nicht normal angekommen sind und das Senden wiederholen können. Für ein vollständiges Bild muss der Mikrocontroller 2560 solcher Pakete an das FPGA senden.
Die Bilddaten innerhalb des Pakets werden im Bitformat dargestellt: 1 - Pixel leuchtet, 0 - Pixel ist dunkel. Leider schließt dies die Möglichkeit der Organisation von Graustufenunschärfen an den Rändern der gedruckten Ebenen vollständig aus - Anti-Aliasing. Um diese Art der Unschärfe zu organisieren, muss die Konfiguration (Firmware) des FPGA neu geschrieben werden, für die ich noch nicht bereit bin. Zu lange und nicht sehr lange habe ich mit FPGA gearbeitet, ich muss praktisch alles neu beherrschen.
Zusätzlich zu Datenpaketen kann der Mikrocontroller auch einen Steuerbefehl senden, in dem angegeben wird, von welcher SDRAM-Bank Daten zur Anzeige gelesen und die Bildausgabe aktiviert / deaktiviert werden sollen.
Die SSD2828-Chips sind auch über SPI mit dem Mikrocontroller verbunden. Dies ist erforderlich, um ihre Register beim Einschalten zu konfigurieren, sie in den Ruhezustand oder in den aktiven Modus zu versetzen.
Zwischen dem Mikrocontroller und dem FPGA / SSD2828 befinden sich mehrere weitere Leitungen - das Reset-Signal und die Chip Select-Signale für jede der Mikroschaltungen.
Im Allgemeinen ist dieses Arbeitsschema meiner Meinung nach alles andere als optimal. Zum Beispiel wäre es logischer, das FPGA über eine parallele externe Speicherschnittstelle mit dem Mikrocontroller zu verbinden. Daten würden viel schneller übertragen als über SPI mit einer Frequenzgrenze von 20 MHz (wenn die Frequenz steigt, empfängt das FPGA keine Daten mehr normal). Außerdem ist das Rücksetzsignal nicht mit dem physischen FPGA-Rücksetzeingang verbunden, sondern führt als normales Logiksignal keinen Hardware-Reset durch. Und dies war auch ein grausamer Witz, auf den weiter unten eingegangen wird.
Ich habe das alles herausgefunden, indem ich die Quellcodes des Herstellers verstanden habe. Ich habe die Funktionen der Arbeit mit FPGA so wie sie sind aus dem Quellcode übertragen. Ich habe immer noch nicht vollständig verstanden, wie das alles funktioniert. Glücklicherweise haben die Chinesen ihren Code so weit auskommentiert (auf Chinesisch), dass sie ihn ohne große Schwierigkeiten herausfinden können.
4.2 Lesen von Ebenen aus einer Druckdatei
Ok, wir haben die Ausgabe des fertigen Bildes mehr oder weniger herausgefunden. Jetzt werde ich Ihnen ein wenig darüber erzählen, wie diese Bilder aus zum Drucken vorbereiteten Dateien extrahiert werden. Die Dateien .pws, .photons, .photon, .cbddlp sind im Wesentlichen eine Reihe von Ebenenbildern. Dieses Format stammt meines Wissens von der chinesischen Firma Chitu, die auf die Idee kam, Karten mit einer solchen Schaltung herzustellen (Mikrocontroller - FPGA - SDRAM - SSD2828). Angenommen, Sie möchten ein Modell mit einer Höhe von 30 mm mit jeder Schichtstärke von 0,05 mm drucken. Das Slicer-Programm schneidet dieses Modell in Schichten der angegebenen Dicke und bildet für jede von ihnen das Bild.
Somit werden 30 / 0,05 = 600 Bilder mit einer Auflösung von 1440 × 2560 erhalten. Diese Bilder werden in eine Ausgabedatei gepackt, der Header mit allen Parametern wird dort eingegeben und eine solche Datei wird bereits an den Drucker gesendet. Ebenenbilder sind 1 Bit tief und werden vom RLE-Algorithmus byteweise komprimiert, wobei das höchstwertige Bit den Farbwert und die sieben niedrigstwertigen Bits die Anzahl der Wiederholungen angeben. Mit dieser Methode können Sie das Ebenenbild von 460 KB auf ca. 30-50 komprimieren. Der Drucker liest die komprimierte Schicht, dekomprimiert sie und sendet sie zeilenweise an das FPGA.
Beim Hersteller geschieht dies wie folgt:
- — 1, 1, 0. , (1440), .
- , 1440 (180 ).
- FPGA .
Dies ist die dreistufige Methode der Chinesen. Wie sich herausstellte, wurde dies getan, damit das Bild der Ebene in reduzierter Form auf der Benutzeroberfläche angezeigt werden konnte und dem Benutzer zeigte, was gedruckt wird. Dieses Bild wird nur aus dem Bytearray gebildet. Obwohl nicht klar ist, was daran gehindert hat, es sofort aus den decodierten Bits zu bilden. Und was die Bildung einer Bitmap für die Übertragung auf FPGA im selben Zyklus verhinderte, ist ebenfalls unklar.
Jetzt verwende ich die gleiche Methode, wenn auch optimiert. Um zu verdeutlichen, was die Optimierung war, muss ich noch einen Punkt klarstellen. Die Daten für die Anzeigezeile sind keine festen Nutzdaten. In der Mitte befinden sich einige zusätzliche "nicht funktionierende" Pixel, da zwei Display-Controller auf der kurzen Seite verbunden sind und jeder von ihnen 24 "nicht funktionierende" Pixel an den Rändern aufweist. Somit bestehen die tatsächlich übertragenen Daten für eine Zeile des Bildes aus 3 Teilen: Daten für die erste Hälfte (erste Steuerung), dazwischenliegende "nicht arbeitende" 48 Pixel, Daten für die zweite Hälfte (zweite Steuerung).
Als die Chinesen das Byte-Array innerhalb der Schleife bildeten, überprüften sie, ob das Ende der ersten Hälfte erreicht war. Wenn nicht, wurde der Wert mit dem Zeiger * p geschrieben, sonst mit dem Zeiger * (p + 48) . Diese Überprüfung für jeden der 1440-Werte und sogar die Änderung des Zeigers für die Hälfte von ihnen trug eindeutig nicht zur Geschwindigkeit der Schleife bei. Ich habe diese eine Schleife in zwei separate aufgeteilt - in der ersten wird die erste Hälfte des Arrays gefüllt, nach dieser Schleife wird der Zeiger um 48 erhöht und die zweite Schleife beginnt für die zweite Hälfte des Arrays. In der Originalversion wurde die Ebene in 1,9 Sekunden gelesen und angezeigt. Allein diese Änderung reduzierte die Lese- und Ausgabezeit auf 1,2 Sekunden.
Eine weitere Änderung betraf die Datenübertragung zum FPGA. In den Originalquellen geschieht dies über DMA, aber nach dem Start der Übertragung über DMA wartet die Funktion auf ihren Abschluss und beginnt erst danach, eine neue Zeile des Bildes zu dekodieren und zu bilden. Ich habe diese Erwartung entfernt, damit die nächste Zeile generiert wird, während die Daten aus der vorherigen Zeile übertragen werden. Dies reduzierte die Zeit um weitere 0,3 Sekunden auf 0,9 pro Schicht. Und dies ist beim Kompilieren ohne Optimierung. Wenn Sie mit vollständiger Optimierung kompilieren, verringert sich die Zeit auf etwa 0,53 Sekunden, was bereits durchaus akzeptabel ist. Von diesen 0,53 Sekunden dauert die Berechnung von CRC16 etwa 0,22 Sekunden und die Erstellung einer Bitmap aus einem Byte-Array vor der Übertragung etwa 0,19 Sekunden. Die Übertragung aller Leitungen zum FPGA selbst dauert jedoch etwa 0,4 Sekunden und damit höchstwahrscheinlichEs kann nichts getan werden - alles hängt hier von der Begrenzung der für FPGA maximal zulässigen SPI-Frequenz ab.
Wenn ich selbst die FPGA-Konfiguration schreiben würde, könnten wir ihr die RLE-Dekomprimierung geben, und dies könnte die Ausgabe der Schicht um eine Größenordnung beschleunigen, aber wie wird das gemacht ...
Und ja, ich wollte über den Pfosten schreiben, der mit der Tatsache verbunden ist, dass das FPGA nicht durch Hardware auf ein Rücksetzsignal vom Mikrocontroller zurückgesetzt wird. Als ich bereits gelernt hatte, wie man Bilder von Ebenen anzeigt, habe ich den Druckvorgang selbst abgeschlossen und bin auf einen unverständlichen Fehler gestoßen - einmal von 5 bis 10 wurde der Druck mit einer vollständig beleuchteten Anzeige gestartet. Ich sehe im Debugger, dass die Schichten korrekt gelesen werden, die Daten nach Bedarf an das FPGA gesendet werden und das FPGA die Richtigkeit des CRC bestätigt. Das heißt, alles funktioniert und anstatt eine Ebene zu zeichnen, eine vollständig weiße Anzeige. Das FPGA oder die SSD2828 sind eindeutig schuld. Ich habe die Initialisierung von SSD2828 noch einmal überprüft - alles ist in Ordnung, alle Register in ihnen werden mit den erforderlichen Werten initialisiert, dies kann beim Kontrolllesen der Werte von ihnen gesehen werden. Dann habe ich schon mit einem Oszilloskop in die Platine gegriffen. Und ich fand heraus, dass das FPGA bei einem solchen Fehler keine Daten in das SDRAM schreibt. WIR signalisieren,Schreiben erlaubt, steht an der Stelle in der inaktiven Ebene verwurzelt. Und ich hätte wahrscheinlich lange mit dieser Panne gekämpft, wenn nicht ein Freund mir geraten hätte, dem FPGA einen expliziten Befehl zum Ausschalten der Bildausgabe vor dem Zurücksetzen zu geben, damit zum Zeitpunkt des Zurücksetzens garantiert keine Anrufe von FPGA an SDRAM erfolgen. Ich habe es versucht und es hat funktioniert! Dieser Fehler zeigte sich nie wieder. Am Ende kamen wir zu dem Schluss, dass der IP-Core des SDRAM-Controllers im FPGA nicht ganz korrekt implementiert wurde, das Zurücksetzen und Initialisieren des SDRAM-Controllers nicht in allen Fällen normal erfolgt. Etwas stört das korrekte Zurücksetzen, wenn zu diesem Zeitpunkt auf die Daten im SDRAM zugegriffen wird. So…Wer riet, vor dem Zurücksetzen zu versuchen, dem FPGA einen expliziten Befehl zum Ausschalten der Bildausgabe zu geben, damit zum Zeitpunkt des Zurücksetzens garantiert keine Anrufe vom FPGA an das SDRAM erfolgen. Ich habe es versucht und es hat funktioniert! Dieser Fehler zeigte sich nie wieder. Am Ende kamen wir zu dem Schluss, dass der IP-Core des SDRAM-Controllers im FPGA nicht ganz korrekt implementiert ist, das Zurücksetzen und Initialisieren des SDRAM-Controllers nicht in allen Fällen normal erfolgt. Etwas stört das korrekte Zurücksetzen, wenn zu diesem Zeitpunkt auf die Daten im SDRAM zugegriffen wird. So…Wer hat geraten, vor dem Zurücksetzen zu versuchen, dem FPGA einen expliziten Befehl zum Ausschalten der Bildausgabe zu geben, damit zum Zeitpunkt des Zurücksetzens garantiert keine Anrufe vom FPGA an das SDRAM erfolgen. Ich habe es versucht und es hat funktioniert! Dieser Fehler zeigte sich nie wieder. Am Ende kamen wir zu dem Schluss, dass der IP-Core des SDRAM-Controllers im FPGA nicht ganz korrekt implementiert wurde, das Zurücksetzen und Initialisieren des SDRAM-Controllers nicht in allen Fällen normal erfolgt. Etwas stört das korrekte Zurücksetzen, wenn zu diesem Zeitpunkt auf die Daten im SDRAM zugegriffen wird. So…Da der IP-Core des SDRAM-Controllers im FPGA nicht richtig implementiert ist, funktioniert das Zurücksetzen und Initialisieren des SDRAM-Controllers nicht in allen Fällen normal. Etwas stört das korrekte Zurücksetzen, wenn zu diesem Zeitpunkt auf die Daten im SDRAM zugegriffen wird. So…Da der IP-Core des SDRAM-Controllers im FPGA nicht richtig implementiert ist, funktioniert das Zurücksetzen und Initialisieren des SDRAM-Controllers nicht in allen Fällen normal. Etwas verhindert das korrekte Zurücksetzen, wenn zu diesem Zeitpunkt auf die Daten im SDRAM zugegriffen wird. So…
4.3 Benutzeroberfläche beim Drucken von Dateien
Nachdem der Benutzer die Datei ausgewählt und mit dem Drucken begonnen hat, wird der folgende Bildschirm angezeigt:
Dies ist ein ziemlich normaler Bildschirm für solche Fotopolymerdrucker.
Der größte Bereich des Bildschirms wird vom Bild der aktuell belichteten Ebene belegt.
Die Anzeige dieses Bildes ist mit der Hintergrundbeleuchtung synchronisiert. Wenn die Hintergrundbeleuchtung eingeschaltet ist, wird das Bild angezeigt. Wenn die Hintergrundbeleuchtung ausgeschaltet ist, wird das Bild gelöscht. Das Bild wird wie bei der UV-Anzeige entlang der kurzen Seite des Bildes erzeugt. Ich habe mich nicht mit Zeigern entlang der Linienversätze dieses Bildes beeilt, aber kurz vor dem Anzeigen gebe ich dem Anzeigesteuergerät einen Befehl zum Ändern der Ausgaberichtung für die gegossenen Daten, d. H. Der Bereich dieses Bildes ist auf der Seite "gedreht".
Nachfolgend finden Sie Informationen zum Druckfortschritt - die verstrichene und geschätzte Druckzeit, die aktuelle Ebene und die Gesamtzahl der Ebenen sowie einen Fortschrittsbalken mit Prozentsätzen rechts davon. Ich möchte auch die aktuelle Höhe in Millimetern nach der Anzahl der Schichten hinzufügen, nur um zu sein.
Rechts befinden sich die Schaltflächen Pause, Einstellungen und Interrupt. Wenn Sie die Pause in der Firmware drücken, wird das Pausenflag gesetzt und das weitere Verhalten hängt vom aktuellen Status des Druckers ab. Wenn die Plattform für die nächste Ebene ausfällt oder die Belichtung bereits gestartet wurde, schließt die Firmware die Belichtung ab und hebt die Plattform erst danach auf die in den Einstellungen festgelegte Pausenhöhe an, wo sie wartet, bis der Benutzer auf die Schaltfläche "Weiter" klickt:
Das Anheben der Plattform für eine Pause erfolgt zuerst mit der in den Dateiparametern angegebenen Geschwindigkeit, und nach der in denselben Parametern angegebenen Höhe erhöht sich die Geschwindigkeit.
Wenn der Druckvorgang unterbrochen wird, wird ein Fenster angezeigt, in dem diese Aktion bestätigt wird. Erst nach der Bestätigung wird der Druckvorgang gestoppt und die Plattform wird auf die maximale Achsenhöhe angehoben. Die Hubgeschwindigkeit sowie während der Pause sind variabel - zuerst langsam, um die Schicht vom Film zu lösen, und dann bis zum Maximum.
Die Schaltfläche "Einstellungen" ist noch nicht funktionsfähig. Wenn Sie jedoch darauf klicken, wird der Benutzer zu einem Bildschirm mit Druckparametern weitergeleitet, die geändert werden können - Belichtungszeit der Schicht, Höhe und Hubgeschwindigkeit usw. Im Moment beende ich es. Es besteht auch die Idee, die Möglichkeit zu geben, die geänderten Parameter wieder in der gedruckten Datei zu speichern.
5. Alles, wie die Steuerung von Beleuchtung und Lüftern, das Laden und Speichern von Einstellungen usw.
Die Karte verfügt über 3 Hochleistungs-MOSFET-Ausgänge - einen für UV-LEDs zur Beleuchtung und zwei für Lüfter (z. B. Kühlung der Beleuchtungsdioden und Kühlung des Displays). Hier gibt es nichts Interessantes - die Ausgänge des Mikrocontrollers sind mit den Gates dieser Transistoren verbunden und ihre Steuerung ist so einfach wie das Blinken einer LED. Für eine hohe Genauigkeit der Belichtungszeit wird diese im Hauptzyklus über die Funktion zum Einstellen der Betriebszeit eingeschaltet:
UVLED_TimerOn(l_info.light_time * 1000);
void UVLED_TimerOn(uint32_t time)
{
uvled_timer = time;
UVLED_On();
}
Und es schaltet sich nach dem Millisekunden-Interrupt des Timers aus, wenn der Hintergrundbeleuchtungszähler Null erreicht:
...
if (uvled_timer && uvled_timer != TIMER_DISABLE)
{
uvled_timer--;
if (uvled_timer == 0)
UVLED_Off();
}
...
5.1 Einstellungen, Laden aus Datei und Speichern im EEPROM
Die Einstellungen werden im integrierten EEPROM at24c16 gespeichert. Im Gegensatz zum Speichern von Ressourcen in einem großen Flash-Speicher ist hier alles einfach: Für jeden gespeicherten Datentyp ist der Adressversatz im EEPROM fest codiert. Insgesamt sind drei Blöcke darin gespeichert: Z-Achseneinstellungen, allgemeine Systemeinstellungen (Sprache, Ton usw.) und Zähler für die Betriebszeit der Hauptdruckerkomponenten - Beleuchtung, Anzeige und Lüfter.
Die gespeicherten Blockstrukturen enthalten die aktuelle Firmware-Version und eine primitive Prüfsumme - nur die 16-Bit-Summe der Werte aller Bytes im Block. Beim Lesen der Einstellungen aus dem EPROM wird die CRC überprüft. Wenn sie nicht der tatsächlichen entspricht, werden den Parametern dieses Blocks Standardwerte zugewiesen, eine neue CRC berechnet und der Block im EPROM anstelle der alten gespeichert. Wenn der Leseblock nicht mit der aktuellen Version übereinstimmt, sollte er auf die aktuelle Version aktualisiert werden und wird in einer neuen Form anstelle der alten gespeichert. Dies wurde noch nicht implementiert, wird aber in Zukunft durchgeführt, um die Firmware ordnungsgemäß zu aktualisieren.
Einige Einstellungen können über die Benutzeroberfläche geändert werden, die meisten können jedoch nur durch Laden einer Konfigurationsdatei geändert werden. Hier habe ich meine Gewohnheiten nicht geändert und meinen eigenen Parser für solche Dateien geschrieben.
Die Struktur einer solchen Datei ist Standard: Parametername + Gleichheitszeichen + Parameterwert. Eine Zeile - ein Parameter. Leerzeichen und Tabulatoren am Zeilenanfang und zwischen dem Gleichheitszeichen sowie dem Namen und Wert werden ignoriert. Leerzeilen und Zeilen, die mit dem Hash-Zeichen "#" beginnen, werden ebenfalls ignoriert. Dieses Zeichen definiert Zeilen mit Kommentaren. Der Fall von Buchstaben in den Namen von Parametern und Abschnitten spielt keine Rolle.
Zusätzlich zu den Parametern enthält die Datei auch Abschnitte, deren Namen in eckigen Klammern stehen. Nach dem gefundenen Abschnittsnamen erwartet der Parser, dass nur die zu diesem Abschnitt gehörenden Parameter weiter gehen, bis ein anderer Abschnittsname gefunden wird. Ehrlich gesagt weiß ich nicht, warum ich diese Abschnitte eingeführt habe. Als ich das tat, hatte ich eine Art Gedanken, der mit ihnen verbunden war, aber jetzt kann ich mich nicht mehr daran erinnern.
Um Vergleiche des Namens des gelesenen Parameters mit vordefinierten Namen zu verkürzen, wird zuerst der erste Buchstabe des gelesenen Namens analysiert und dann nur die Namen verglichen, die mit diesem Buchstaben beginnen.
Inhalt der Konfigurationsdatei
# Stepper motor Z axis settings
[ZMotor]
# .
# : 0 1. : 1.
# .
invert_dir = 1
# .
# : -1 1. : -1.
# -1,
# , . 1
# .
home_direction = -1
# Z . ,
# 0, - .
home_pos = 0.0
# .
# : -32000.0 32000.0.
# : -3.0
# .
# , .
min_pos = -3.0
# .
# : -32000.0 32000.0.
# : 180.0
# .
# , .
max_pos = 180.0
# .
# : 0 1. : 1.
# ,
# 1, - 0.
min_endstop_inverting = 1
# .
# : 0 1. : 1.
# ,
# 1, - 0.
max_endstop_inverting = 1
# 1 .
steps_per_mm = 1600
# ,
# , /. : 6.0.
homing_feedrate_fast = 6.0
# ,
# , /. : 1.0.
homing_feedrate_slow = 1.0
# , /2.
acceleration = 0.7
# , /.
feedrate = 5.0
# ( ,
# ..), /2.
travel_acceleration = 25.0
# ( ,
# ..), /. 30
# ,
# 5 /.
travel_feedrate = 25.0
# , .
current_vref = 800.0
# , .
current_hold_vref = 300.0
# ,
# . . 0
# .
hold_time = 30.0
# ,
# . .
# hold_time. 0 .
# , .
off_time = 10.0
# General settings
[General]
# (0.001 )
# .
# : 0 15000. : 700 (0.7 ).
buzzer_msg_duration = 700
# (0.001 )
# , .
# : 0 15000. : 70 (0.07 ).
buzzer_touch_duration = 70
# 180 .
# .
# : 0 1. : 0.
rotate_display = 0
# , .
# LCD-. -
# .
# : 0 15000. : 10. 0 .
screensaver_time = 10
Wenn eine solche Datei (mit der Erweiterung .acfg) in der Liste der Dateien ausgewählt ist, fragt die Firmware, ob der Benutzer die Einstellungen aus dieser Datei herunterladen und anwenden möchte, und beginnt nach Bestätigung mit dem Parsen dieser Datei.
Wenn ein Fehler gefunden wird, wird eine Meldung mit dem Fehlertyp und der Zeilennummer angezeigt. Folgende Fehler werden behandelt:
- unbekannter Partitionsname
- unbekannter Parametername
- Ungültiger Parameterwert - Wenn beispielsweise versucht wird, einem numerischen Parameter einen Textwert zuzuweisen
Wenn jemand interessiert ist - hier ist ein vollständiges Blatt der drei Hauptfunktionen des Parsers
void _cfg_GetParamName(char *src, char *dest, uint16_t maxlen)
{
if (src == NULL || dest == NULL)
return;
char *string = src;
// skip spaces
while (*string != 0 && maxlen > 0 && (*string == ' ' || *string == '\t' || *string == '\r'))
{
string++;
maxlen--;
}
// until first space symbol
while (maxlen > 0 && *string != 0 && *string != ' ' && *string != '\t' && *string != '\r' && *string != '\n' && *string != '=')
{
*dest = *string;
dest++;
string++;
maxlen--;
}
if (maxlen == 0)
dest--;
*dest = 0;
return;
}
//==============================================================================
void _cfg_GetParamValue(char *src, PARAM_VALUE *val)
{
val->type = PARAMVAL_NONE;
val->float_val = 0;
val->int_val = 0;
val->uint_val = 0;
val->char_val = (char*)"";
if (src == NULL)
return;
if (val == NULL)
return;
char *string = src;
// search '='
while (*string > 0 && *string != '=')
string++;
if (*string == 0)
return;
// skip '='
string++;
// skip spaces
while (*string != 0 && (*string == ' ' || *string == '\t' || *string == '\r'))
string++;
if (*string == 0)
return;
// check param if it numeric
if ((*string > 47 && *string < 58) || *string == '.' || (*string == '-' && (*(string+1) > 47 && *(string+1) < 58) || *(string+1) == '.'))
{
val->type = PARAMVAL_NUMERIC;
val->float_val = (float)atof(string);
val->int_val = atoi(string);
val->uint_val = strtoul(string, NULL, 10);
}
else
{
val->type = PARAMVAL_STRING;
val->char_val = string;
}
return;
}
//==============================================================================
void CFG_LoadFromFile(void *par1, void *par2)
{
sprintf(msg, LANG_GetString(LSTR_MSG_CFGFILE_LOADING), cfgCFileName);
TGUI_MessageBoxWait(LANG_GetString(LSTR_WAIT), msg);
UTF8ToUnicode_Str(cfgTFileName, cfgCFileName, sizeof(cfgTFileName)/2);
if (f_open(&ufile, cfgTFileName, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
if (tguiActiveScreen == (TG_SCREEN*)&tguiMsgBox)
tguiActiveScreen = (TG_SCREEN*)((TG_MSGBOX*)tguiActiveScreen)->prevscreen;
TGUI_MessageBoxOk(LANG_GetString(LSTR_ERROR), LANG_GetString(LSTR_MSG_FILE_OPEN_ERROR));
BUZZ_TimerOn(cfgConfig.buzzer_msg);
return;
}
uint16_t cnt = 0;
uint32_t readed = 0, totalreaded = 0;
char *string = msg;
char lexem[128];
PARAM_VALUE pval;
CFGREAD_STATE rdstate = CFGR_GENERAL;
int16_t numstr = 0;
while (1)
{
// read one string
cnt = 0;
readed = 0;
string = msg;
while (cnt < sizeof(msg))
{
if (f_read(&ufile, string, 1, &readed) != FR_OK || readed == 0 || *string == '\n')
{
*string = 0;
break;
}
cnt++;
string++;
totalreaded += readed;
}
if (cnt == sizeof(msg))
{
string--;
*string = 0;
}
numstr++;
string = msg;
// trim spaces/tabs at begin and end
strtrim(string);
// if string is empty
if (*string == 0)
{
// if end of file
if (readed == 0)
break;
else
continue;
}
// skip comments
if (*string == '#')
continue;
// upper all letters
strupper_utf(string);
// get parameter name
_cfg_GetParamName(string, lexem, sizeof(lexem));
// check if here section name
if (*lexem == '[')
{
if (strcmp(lexem, (char*)"[ZMOTOR]") == 0)
{
rdstate = CFGR_ZMOTOR;
continue;
}
else if (strcmp(lexem, (char*)"[GENERAL]") == 0)
{
rdstate = CFGR_GENERAL;
continue;
}
else
{
rdstate = CFGR_ERROR;
string = LANG_GetString(LSTR_MSG_UNKNOWN_SECTNAME_IN_CFG);
sprintf(msg, string, numstr);
break;
}
}
// get parameter value
_cfg_GetParamValue(string, &pval);
if (pval.type == PARAMVAL_NONE)
{
rdstate = CFGR_ERROR;
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
// check and setup parameter
switch (rdstate)
{
case CFGR_ZMOTOR:
rdstate = CFGR_ERROR;
if (*lexem == 'A')
{
if (strcmp(lexem, (char*)"ACCELERATION") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.float_val < 0.1)
pval.float_val = 0.1;
cfgzMotor.acceleration = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
} else
if (*lexem == 'C')
{
if (strcmp(lexem, (char*)"CURRENT_HOLD_VREF") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val < 100)
pval.uint_val = 100;
if (pval.uint_val > 1000)
pval.uint_val = 1000;
cfgzMotor.current_hold_vref = pval.uint_val;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"CURRENT_VREF") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val < 100)
pval.uint_val = 100;
if (pval.uint_val > 1000)
pval.uint_val = 1000;
cfgzMotor.current_vref = pval.uint_val;
rdstate = CFGR_ZMOTOR;
break;
}
} else
if (*lexem == 'F')
{
if (strcmp(lexem, (char*)"FEEDRATE") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.float_val < 0.1)
pval.float_val = 0.1;
if (pval.float_val > 40)
pval.float_val = 40;
cfgzMotor.feedrate = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
} else
if (*lexem == 'H')
{
if (strcmp(lexem, (char*)"HOLD_TIME") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val == 0)
pval.uint_val = TIMER_DISABLE;
else if (pval.uint_val > 100000)
pval.uint_val = 100000;
cfgzMotor.hold_time = pval.uint_val * 1000;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"HOME_DIRECTION") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.int_val != -1.0 && pval.int_val != 1.0)
pval.int_val = -1;
cfgzMotor.home_dir = pval.int_val;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"HOME_POS") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
cfgzMotor.home_pos = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"HOMING_FEEDRATE_FAST") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.float_val < 0.1)
pval.float_val = 0.1;
if (pval.float_val > 40)
pval.float_val = 40;
cfgzMotor.homing_feedrate_fast = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"HOMING_FEEDRATE_SLOW") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.float_val < 0.1)
pval.float_val = 0.1;
if (pval.float_val > 40)
pval.float_val = 40;
cfgzMotor.homing_feedrate_slow = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
} else
if (*lexem == 'I')
{
if (strcmp(lexem, (char*)"INVERT_DIR") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.int_val < 0 || pval.int_val > 1)
pval.int_val = 1;
cfgzMotor.invert_dir = pval.int_val;
rdstate = CFGR_ZMOTOR;
break;
}
} else
if (*lexem == 'M')
{
if (strcmp(lexem, (char*)"MAX_ENDSTOP_INVERTING") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.int_val < 0 || pval.int_val > 1)
pval.int_val = 1;
cfgzMotor.max_endstop_inverting = pval.int_val;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"MAX_POS") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
cfgzMotor.max_pos = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"MIN_ENDSTOP_INVERTING") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.int_val < 0 || pval.int_val > 1)
pval.int_val = 1;
cfgzMotor.min_endstop_inverting = pval.int_val;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"MIN_POS") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
cfgzMotor.min_pos = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
} else
if (*lexem == 'O')
{
if (strcmp(lexem, (char*)"OFF_TIME") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val > 100000)
pval.uint_val = 100000;
else if (pval.uint_val < cfgzMotor.hold_time)
pval.uint_val = cfgzMotor.hold_time + 1000;
else if (pval.uint_val == 0)
pval.uint_val = TIMER_DISABLE;
cfgzMotor.off_time = pval.int_val * 60000;
rdstate = CFGR_ZMOTOR;
break;
}
} else
if (*lexem == 'S')
{
if (strcmp(lexem, (char*)"STEPS_PER_MM") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val < 1)
pval.uint_val = 1;
if (pval.uint_val > 200000)
pval.uint_val = 200000;
cfgzMotor.steps_per_mm = pval.uint_val;
rdstate = CFGR_ZMOTOR;
break;
}
} else
if (*lexem == 'T')
{
if (strcmp(lexem, (char*)"TRAVEL_ACCELERATION") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.float_val < 0.1)
pval.float_val = 0.1;
cfgzMotor.travel_acceleration = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
if (strcmp(lexem, (char*)"TRAVEL_FEEDRATE") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.float_val < 0.1)
pval.float_val = 0.1;
cfgzMotor.travel_feedrate = pval.float_val;
rdstate = CFGR_ZMOTOR;
break;
}
}
string = LANG_GetString(LSTR_MSG_UNKNOWN_PARAMNAME_IN_CFG);
sprintf(msg, string, numstr);
break;
case CFGR_GENERAL:
rdstate = CFGR_ERROR;
if (*lexem == 'B')
{
if (strcmp(lexem, (char*)"BUZZER_MSG_DURATION") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val > 15000)
pval.uint_val = 15000;
cfgConfig.buzzer_msg = pval.uint_val;
rdstate = CFGR_GENERAL;
break;
}
if (strcmp(lexem, (char*)"BUZZER_TOUCH_DURATION") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val > 15000)
pval.uint_val = 15000;
cfgConfig.buzzer_touch = pval.uint_val;
rdstate = CFGR_GENERAL;
break;
}
} else
if (*lexem == 'R')
{
if (strcmp(lexem, (char*)"ROTATE_DISPLAY") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val > 0)
{
cfgConfig.display_rotate = 1;
LCD_WriteCmd(0x0036);
LCD_WriteRAM(0x0078);
}
else
{
cfgConfig.display_rotate = 0;
LCD_WriteCmd(0x0036);
LCD_WriteRAM(0x00B8);
}
rdstate = CFGR_GENERAL;
break;
}
} else
if (*lexem == 'S')
{
if (strcmp(lexem, (char*)"SCREENSAVER_TIME") == 0)
{
if (pval.type != PARAMVAL_NUMERIC)
{
string = LANG_GetString(LSTR_MSG_INVALID_PARAMVAL_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (pval.uint_val > 15000)
cfgConfig.screensaver_time = 15000 * 60000;
else if (pval.uint_val == 0)
pval.uint_val = TIMER_DISABLE;
else
cfgConfig.screensaver_time = pval.uint_val * 60000;
rdstate = CFGR_GENERAL;
break;
}
}
string = LANG_GetString(LSTR_MSG_UNKNOWN_PARAMNAME_IN_CFG);
sprintf(msg, string, numstr);
break;
}
if (rdstate == CFGR_ERROR)
break;
}
f_close(&ufile);
if (tguiActiveScreen == (TG_SCREEN*)&tguiMsgBox)
{
tguiActiveScreen = (TG_SCREEN*)((TG_MSGBOX*)tguiActiveScreen)->prevscreen;
}
if (rdstate == CFGR_ERROR)
{
TGUI_MessageBoxOk(LANG_GetString(LSTR_ERROR), msg);
BUZZ_TimerOn(cfgConfig.buzzer_msg);
}
else
{
CFG_SaveMotor();
CFG_SaveConfig();
TGUI_MessageBoxOk(LANG_GetString(LSTR_COMPLETED), LANG_GetString(LSTR_MSG_CFGFILE_LOADED));
}
}
//==============================================================================
Nach erfolgreichem Parsen der Datei werden die neuen Einstellungen sofort angewendet und im EPROM gespeichert.
Die Betriebsstundenzähler für Druckerkomponenten werden im EPROM nur aktualisiert, wenn die Datei gedruckt oder unterbrochen wird.
6. Zusätzliche Funktionen für Komfort und Bequemlichkeit
6.1 Uhr mit Kalender
Nun, nur um es zu schaffen. Warum Güte verschwenden - eine im Mikrocontroller integrierte autonome Echtzeituhr, die bei ausgeschaltetem Strom mit einer Lithiumbatterie betrieben werden kann und so wenig verbraucht, dass der CR2032 nach Berechnungen mehrere Jahre lang ausreichen sollte. Darüber hinaus stellte der Hersteller sogar den für diese Uhr erforderlichen 32-kHz-Quarz auf die Platine. Es bleibt nur, den Batteriehalter auf die Platine zu kleben und die Verkabelung von dort mit dem gemeinsamen Minus und dem speziellen Anschluss des Mikrocontrollers zu verlöten, was ich zu Hause getan habe.
Uhrzeit, Tag und Monat werden oben links im Hauptbildschirm angezeigt:
Mit derselben Echtzeituhr werden die Druckzeit und die Betriebsstunden der Komponenten gezählt. Und sie werden auch im Bildschirmschoner verwendet, der unten beschrieben wird.
6.2 Sperren des Bildschirms vor versehentlichem Klicken während des Druckvorgangs
Dies geschah auf Wunsch eines Bekannten. Warum nicht, kann es in einigen Fällen nützlich sein. Die Sperre wird durch langes Drücken (~ 2,5 Sek.) Auf der Druckbildkopfzeile ein- und ausgeschaltet. Wenn die Sperre aktiv ist, wird in der oberen rechten Ecke eine rote Sperre angezeigt. Am Ende des Druckvorgangs wird die Sperre automatisch aufgehoben.
6.3 Verringern Sie den Motorstrom im Haltemodus und schalten Sie den Motor im Leerlauf aus
Zur Reduzierung des gesamten Wärmestaus im Druckerkörper. Der Motor kann nach der konfigurierten No-Move-Zeit mit reduziertem Strom in den Hold-Modus versetzt werden. Diese Funktion ist übrigens bei "erwachsenen" Schrittmotortreibern vom Typ TB6560 weit verbreitet. Außerdem können Sie in den Einstellungen die Zeit einstellen, nach der der Motor ohne Bewegung vollständig stromlos wird. Dies führt aber auch dazu, dass das Nullstellen der Achse, falls es durchgeführt wurde, ungültig wird. Beide Funktionen können in denselben Einstellungen vollständig deaktiviert werden.
6.4 Bildschirmschoner
Wie eine Uhr - nur weil ich kann. Wenn der Bildschirm nach der in den Einstellungen angegebenen Zeit nicht gedrückt wird, wechselt der Bildschirm in den Emulationsmodus einer digitalen Desktop-Uhr:
Neben der Uhrzeit wird auch das vollständige Datum mit dem Wochentag angezeigt. Die Firmware verlässt diesen Modus durch Drücken eines beliebigen Teils des Displays. Wenn man bedenkt, dass die Zahlen ziemlich groß sind und der Stromverbrauch bei ausgeschaltetem Motor weniger als 2 Watt beträgt, kann ein Drucker mit einem solchen Bildschirmschoner durchaus als Raumuhr dienen :) Während des Druckvorgangs wird der Bildschirmschoner auch nach einer bestimmten Zeit angezeigt, jedoch mit einem Zusatz - dem Druckfortschritt am unteren Bildschirmrand:
In den Einstellungen können Sie die Reaktionszeit des Bildschirmschoners einstellen oder deaktivieren.
6.5 Überprüfung der Hintergrundbeleuchtung und des Displays
Dieser Bildschirm kann über das Menü "Service" aufgerufen werden und ist nützlich, wenn Sie die Hintergrundbeleuchtungsdioden oder die UV-Anzeige überprüfen. Oben wird eines von drei Bildern ausgewählt, die auf dem UV-Display angezeigt werden - Rahmen, vollständige Beleuchtung des gesamten Displays, Rechtecke. Unten befinden sich zwei Tasten, mit denen Sie die Hintergrundbeleuchtung und das Display ein- und ausschalten können. Das mitgelieferte Licht erlischt automatisch nach 2 Minuten. Normalerweise reicht diese Zeit für jeden Test aus. Wenn Sie diesen Bildschirm verlassen, werden sowohl die Hintergrundbeleuchtung als auch das Display automatisch ausgeschaltet.
6.6 Einstellungen
Dieser Bildschirm ist auch über das Menü Extras zugänglich. Hier gibt es nur sehr wenige Einstellungen, und um ehrlich zu sein, habe ich nie herausgefunden, welche Einstellungen so häufig nachgefragt werden, dass es sinnvoll wäre, sie in die Benutzeroberfläche und nicht nur in die Konfigurationsdatei einzufügen. Dies bietet auch die Möglichkeit, die Betriebszeitzähler für Druckerkomponenten zurückzusetzen. Nun, ich weiß es nicht mehr :)
Natürlich können Sie hier die Uhrzeit und das Datum (da es eine Uhr gibt) auf dem Bildschirm
einstellen, der separat geöffnet wird: Sie können die Hubhöhe der Plattform in der Pause einstellen und ein- und ausschalten Ton von Anzeigeklicks und Meldungen. Beim Ändern der Einstellungen werden die neuen Werte nur wirksam, bis die Stromversorgung ausgeschaltet wird, und werden nicht im EPROM gespeichert. Um sie zu speichern, drücken Sie nach dem Ändern der Parameter die Speichern-Taste im Menü (mit einem Diskettensymbol).
Numerische Werte werden in einem speziellen Bildschirm eingegeben:
Hier habe ich alle Funktionen implementiert, die mir bei anderen Druckern fehlten.
- "±" und "." Tasten funktionieren nur, wenn der bearbeitete Parameter negativ bzw. gebrochen sein kann.
- Wenn nach dem Aufrufen dieses Bildschirms zuerst eine Zifferntaste gedrückt wird, wird der alte Wert durch die entsprechende Ziffer ersetzt. Wenn die Schaltfläche "." Ist, wird sie durch "0" ersetzt. Das heißt, Sie müssen den alten Wert nicht löschen. Sie können sofort mit der Eingabe eines neuen Werts beginnen.
- Taste "", die den aktuellen Wert auf Null setzt.
Durch Drücken der Zurück-Taste wird der neue Wert nicht übernommen. Um es anzuwenden, müssen Sie auf "OK" klicken.
6.7 Endlich - Bildschirm mit den Druckerinformationen
Dieser Bildschirm ist direkt über das Hauptmenü zugänglich. Das Wichtigste dabei ist die Firmware / FPGA-Version und die Betriebszeitzähler. Unten finden Sie noch Informationen über den Autor der Schnittstelle und die Adresse des Repositorys auf GitHub. Der Autor der Schnittstelle ist die Grundlage für die Zukunft. Wenn ich es weiterhin möglich mache, die Schnittstelle über eine einfache Textdatei zu konfigurieren, besteht die Möglichkeit, den Namen des Autors anzugeben.
das Ende
Dies ist der letzte Teil für mein Lieblingsprojekt. Das Projekt lebt und entwickelt sich zwar nicht so schnell wie ich es gerne hätte, aber es ist bereits recht effizient.
Ich hätte wahrscheinlich mehr Code einfügen sollen ... Aber ich glaube nicht, dass mein Code Teile enthält, mit denen ich angeben kann. Meiner Meinung nach ist es wichtiger zu beschreiben, wie es funktioniert und was getan wurde, und der Code ist da, es ist alles auf GitHub, wer interessiert sein wird, ich kann ihn dort in seiner Gesamtheit sehen. Ich denke so.
Ich freue mich auf Ihre Fragen und Kommentare und danke für Ihr Interesse an diesen Artikeln.
- Teil 1: 1. Benutzeroberfläche.
- Teil 2: 2. Arbeiten mit dem Dateisystem auf einem USB-Stick. 3. Schrittmotorsteuerung für Plattformbewegung.
- Teil 3:4. Ausgabe von Bildern von Ebenen an die Hintergrundbeleuchtung. 5. Alles, wie die Steuerung von Beleuchtung und Lüftern, das Laden und Speichern von Einstellungen usw. 6. Zusätzliche Funktionen für Komfort und Bequemlichkeit.
Links
MKS DLP-Kit auf Aliexpress
Original-Firmware-Quellen des Herstellers auf GitHub
Schemes vom Hersteller von zwei Versionen des Boards auf GitHub
Meine Quellen auf GitHub