Micrologiciel DIY pour imprimante 3D LCD photopolymère. Partie 1

image



... ou comment j'ai inventé mes propres vélos avec des préférences et une geisha à mon goût - j'ai écrit un micrologiciel pour une imprimante photopolymère à partir de zéro. Pour le moment, le firmware est déjà entièrement fonctionnel.



La carte MKS DLP vendue sur Aliexpress a été prise comme base, pour laquelle le fabricant fournit un circuit et des codes source de firmware, que j'ai rejetés en faveur de tout écrire à partir de zéro.

L'article s'avère très long, j'ai donc décidé de le scinder en deux parties. Dans cette partie, il y aura un arrière-plan et une description d'une interface graphique maison pour un écran tactile. À la fin, il y aura des liens vers le sujet de l'intimidation lui-même et vers les référentiels GitHub.





- Partie 1: 1. Interface utilisateur.

- Partie 2: 2. Travailler avec le système de fichiers sur une clé USB. 3. Commande de moteur pas à pas pour le mouvement de la plate-forme.

- Partie 3: 4. Affichage des images des couches sur l'écran rétroéclairé. 5. Chaque petite chose, comme le contrôle de l'éclairage et des ventilateurs, le chargement et l'enregistrement des paramètres, etc. 6. Caractéristiques supplémentaires pour le confort et la commodité.



Pour une meilleure compréhension, je vais donner une très brève description du travail des imprimantes 3D photopolymères LCD pour ceux qui ne les connaissent pas:



Une brève explication du fonctionnement de la plupart des imprimantes photopolymères grand public
— LCD- ( , 5.5" 25601440 ( — 47.25 ). 405 . , FEP-. , «» . , -. , «» , . . , , . , . , .



Contexte



Comment en suis-je arrivé là et pourquoi j'ai commencé à écrire mon propre firmware au lieu de simplement peaufiner le code source du fabricant pour moi-même.



La trame de fond s'est avérée longue, alors je l'ai enlevée sous le spoiler
5 3D-. , , . FDM-, — Anet A8. - , , . - — - , , . , . - — Anycubic Photon S. , .



, , «» — , . , .., FDM-. , , — 11565 , :) «» , , , . . «» — . , , 20-30 . , — , . .



. , , .. , , , , , . . , (), . , , . - . , 3D- — MKS DLP. : , (5.5", 25601440) (3.5", 480320). — ! , , .



, , . , - , , . . , . … -, CMSIS HAL ST ( STM32F407). -, Marlin 3D. — Marlin 3D — FDM 3D-. 6 , , , G- - . 3 . . — G- . , . , FDM- .



, GUI- , . , , - .



Donc ce que nous avons:



  • Kit MKS DLP, qui comprend: carte mère, écran d'interface 3,5 "480 x 320 et écran rétroéclairé 5,5" 2560 x 1440
  • sources natives du fabricant
  • schéma de la carte mère (sans noms des valeurs actives et nominales des composants passifs)


La carte mère est basée sur le microcontrôleur STM32F407. Pour contrôler l'affichage du rétroéclairage , la carte contient un FPGA du fabricant chinois GW1N-LV4LQ144ES, une SDRAM et deux puces d'interface SSD2828 MIPI. Le microcontrôleur conduit l'image de la couche dans le FPGA, le FPGA la stocke dans la SDRAM et de là rafraîchit l'affichage via le SSD2828. D'ailleurs, le fabricant ne fournit pas la configuration FPGA (firmware) dans le code source: (De plus, la carte mère a:



  • alimentation 12-24 volts
  • USB A /
  • A4988
  • Z —
  • WiFi
  • FLASH- W25Q64
  • EEPROM- AT24C16


L'écran d'interface avec un panneau tactile résistif est connecté avec un câble plat à 40 broches. Contrôleur d'affichage - ILI9488, contrôleur d'écran tactile - HR2046 (similaire à TSC2046).



Pour initialiser les périphériques, j'ai utilisé le programme STM32CUBE MX. Mais je n'ai pas utilisé le résultat obtenu directement, mais j'ai inséré les pièces nécessaires dans mes sources. Lorsque je travaillais avec des périphériques, j'utilisais les bibliothèques HAL de ST, et là où j'avais besoin d'obtenir la vitesse maximale, je travaillais directement avec les registres.



Donc, il y a une tâche - ce kit devrait être capable d'imprimer des fichiers à partir d'un lecteur flash avec facilité pour l'utilisateur. J'ai divisé ce problème dans son ensemble en grandes parties, ce qui a abouti à trois publications.



Je tiens à vous prévenir tout de suite que ni mon code ni mon approche ne prétendent être idéaux ou même simplement bons, je joue comme je peux. Pour moi, la programmation est plus un passe-temps qu'une profession. Ne jugez donc pas par des normes trop strictes.



1. Interface utilisateur



Le premier était l'initialisation de l'affichage. Il n'y a rien d'intéressant ici, la séquence standard du contrôleur ILI9488. Je l'ai arraché des sources natives, supprimant le code d'initialisation pour d'autres types d'affichages (qui, probablement, sont restés là de la vie FDM de ces sources). Ensuite, je suis entré dans les polices.



1.1 Polices



Il existe de nombreuses bibliothèques de polices pour microcontrôleurs sur le net, mais la grande majorité d'entre elles fonctionnent avec des polices à espacement fixe, et je n'aime pas vraiment ça. C'est lorsque tous les caractères ont la même largeur, comme la lettre «z», que la lettre «i». J'ai écrit une fois une bibliothèque de polices proportionnelle pour l'un de mes projets favoris. Il utilise deux tableaux pour chaque police - un tableau avec les données binaires des caractères eux-mêmes et un tableau avec la largeur de chaque caractère. Et une petite structure avec des paramètres de police - pointeurs vers des tableaux, hauteur de la police, nombre de caractères dans la police:



typedef struct
{
	uint16_t	*width;
	uint8_t		*data;
	uint8_t		height;
	uint16_t	symcount;
} LCDUI_FONT;


Il semblerait qu'une telle organisation de polices devrait occuper plus d'espace mémoire qu'une simple image bitmap à espacement fixe, mais ce n'est pas tout à fait vrai. Premièrement, le monospace lui-même donne lieu à un surplus de données stockées. Par exemple, si dans une police de 8 pixels de haut et 5 pixels de large, 1 octet (1 bit de large et 8 bits de haut) suffirait pour la lettre «i», alors il faudra encore 5 octets de données (5 bits de large et 8 bits de haut), puisque la largeur est fixe. Deuxièmement, en règle générale, dans de telles polices, l'alignement est effectué sur les limites d'octets de chaque ligne ou de chaque colonne, en fonction de l'organisation des données.



Par exemple, prenez la même police 5x8. Si les données binaires sont stockées ligne par ligne, alors il y a un excès de 3 bits pour chaque ligne. Ou 3 octets par caractère:



image



Ou une police 7x12 avec stockage de données en colonnes, alors il y a un excès de 4 bits par colonne ou 3,5 octets par caractère:



image



Dans ma bibliothèque, les données binaires sont continues pour un caractère et l'alignement sur la limite d'octets n'est qu'à la fin du caractère.



De plus, il y a une autre petite astuce qui vous permet de réduire légèrement la taille de la police stockée: un caractère peut ne pas avoir de données binaires, mais faire référence à un autre caractère avec le même style. Par exemple, les lettres cyrilliques «A», «B», «E», «K», etc. peut avoir une référence à des lettres latines avec le même style. Cela se fait en spécifiant une valeur négative pour la largeur du caractère correspondant dans le tableau de largeurs de caractères. S'il y a là une valeur négative, alors l'image de ce caractère est tirée du caractère en position (largeur * -1).



Voici la procédure pour rechercher un caractère dans un tableau:



uint8_t*	_lcdui_GetCharData(char c)
{
	if (c < 32)
		return 0;
	if (c > 126)
		c -= 65;
	c -= 32;
	if (c >= lcdui_current_font->symcount)
		return 0;
	uint16_t c1 = lcdui_current_font->width[c];
	if (c1 & 0x8000)
		c = (c1 & 0x7FFF);
	uint16_t ch = lcdui_current_font->height;
	int32_t i = 0, ptr = 0, bits = 0, line_bits = ch;
	for (i = 0; i < c; i++)
	{
		if (lcdui_current_font->width[i] & 0x8000)
			continue;
		bits = lcdui_current_font->width[i] * line_bits;
		ptr += bits >> 3;
		if (bits & 0x07)
			ptr++;
	}

	return &(lcdui_current_font->data[ptr]);
}


Tout cela donne souvent même un gain de quantité de données pour la police. Sans oublier, le type proportionnel semble plus naturel.



La vitesse de rendu d'une telle police est assez décente en raison de la sortie fenêtrée - l'affichage reçoit d'abord la commande de limiter la fenêtre de sortie à la taille du caractère dans la position souhaitée, puis les données du caractère entier y sont versées dans un flux. Il n'est pas nécessaire de définir les coordonnées séparément pour chaque pixel.



Par exemple, sur la photo ci-dessous, le texte bleu et la ligne blanche supérieure ont été rendus par ma bibliothèque, et le fond blanc - par la bibliothèque standard de type arduino à partir de sources natives: le



image



texte bleu a été rendu plusieurs fois plus rapidement que la ligne blanche inférieure.



Dans le même temps, j'ai dû inventer un utilitaire pour créer des tableaux de polices prêts à être utilisés dans un programme à partir d'une image. Dans Photoshop, une image de la hauteur souhaitée est créée avec tous les caractères de la police, puis les coordonnées X de la dernière colonne de chaque caractère sont saisies à la main dans le fichier texte, puis l'utilitaire est défini sur l'image et ce fichier texte. Cela crée un fichier .c avec les tableaux requis. Un peu fastidieux, bien sûr, mais simple.



La procédure d'affichage du texte est capable d'habiller le texte sur une nouvelle ligne à la fin de l'écran ou par un caractère de saut de ligne rencontré, peut s'aligner à gauche, à droite et au centre, limiter la zone au-delà de laquelle le texte ne passera pas (sera coupé). Et il est capable d'afficher des symboles avec une peinture d'arrière-plan avec une couleur d'arrière-plan ou avec une préservation de l'arrière-plan. La deuxième option fonctionne plus lentement, car il n'est plus possible de remplir les données de caractères dans l'affichage en un seul flux, mais elle est toujours suffisamment rapide pour que la sortie de 3-4 lignes ne soit pas visible à l'œil.



1.2 Affichage des images d'interface



Pour l'interface utilisateur, vous devez afficher des images - arrière-plan, icônes, boutons. Dans un premier temps, j'ai décidé de ne pas trop me déranger et de stocker toutes les images au format .bmp dans la mémoire flash de 8 Mo disponible sur la carte. Et j'ai même écrit une procédure pour cela. Le fichier est enregistré au format 16 bits (R5 G6 B5) avec un ordre de ligne de bout en bout ou de bout en bout, et peut déjà être directement transmis à la routine de rendu. Mais la taille d'une image d'arrière-plan de 480x320 est supérieure à 300 Ko. Considérant qu'une partie de cette mémoire flash sera dédiée aux mises à jour du firmware, 30 images d'arrière-plan occuperont toute la mémoire. Cela semble beaucoup, mais toujours moins que ce que j'aimerais avoir, juste au cas où. Mais il devrait également y avoir des boutons, des icônes, etc. Par conséquent, il a été décidé de convertir les images dans une sorte de format compressé.



Il n'y a pas beaucoup d'options avec la compression - tous les algorithmes qui compressent plus ou moins bien les images nécessitent une RAM décente (selon les normes d'un microcontrôleur) ou un temps décent pour décompresser. Les images, en revanche, doivent être affichées, en se desserrant à la volée, et il est souhaitable que l'image lors de l'affichage ne ressemble pas à une barre de progression d'exploration. Par conséquent, je me suis installé sur la compression RLE - 1 octet code le nombre de répétitions, et les deux qui le suivent - la couleur. Pour cela, un utilitaire a également été écrit qui convertit les fichiers .bmp en images compressées de cette manière. L'en-tête se compose de seulement 4 octets - 2 octets pour la largeur et la hauteur de l'image. En moyenne, les images d'arrière-plan sont compressées de cette manière de 5 à 7 fois, ce qui dépend fortement de la taille des zones monochromes (ce qui est prévisible). Par exemple, une image comme celle-ci est passée des 307 Ko d'origine à 74 Ko:



image



Mais celui-ci - jusqu'à 23 Ko à partir du même 307:





Au fait, mon concepteur est encore plus nul qu'un programmeur ...



J'étais satisfait de ce résultat. Le décodage et l'affichage des images sont très rapides - environ 40 millisecondes par image d'arrière-plan complète. J'ai donc opté pour cette option.



Et en passant, le passage en mode DMA pour la sortie de données sur l'écran n'a donné presque aucune accélération de la sortie. L'écran est connecté via un bus de données externe 16 bits en tant que mémoire externe, mais ses synchronisations sont plutôt tristes, ce qui annule presque les avantages de la sortie DMA par rapport à la sortie manuelle des pixels.



1.3 Cadre GUI



Les textes sont affichés, les images sont dessinées, il est maintenant temps de réfléchir à l'organisation de la base de l'interface utilisateur.



Avec l'écran tactile, tout est simple - le microcontrôleur interroge en permanence le contrôleur de l'écran tactile par interruptions et fait la moyenne des 4 derniers résultats obtenus, les convertissant en coordonnées d'affichage. Ainsi, l'état du capteur est connu à tout moment - qu'il soit enfoncé ou non, et s'il est enfoncé, alors à quel endroit. Une autre couche entre le panneau tactile et la partie principale du programme est la procédure de traitement des clics de bouton, qui erre de projet en projet avec de petites adaptations pour des conditions spécifiques depuis un certain temps.



Voici un bref résumé de son fonctionnement.
«». (100-150 ). , «». , . , , «», . , «», «». «», «». - «» «», . ( «»), - . , , .



Le panneau tactile sert de seul bouton d'interface, mais en plus du simple fait d'appuyer dessus, les coordonnées du clic sont également analysées.



Maintenant, tout doit être fait pour qu'une grande variété d'éléments d'interface puissent être affichés à l'écran, qui pourraient ou non réagir aux clics, mettre à jour par des événements, avoir des tailles et des images différentes, etc.



En fin de compte, je suis arrivé à ce schéma: l'interface se compose de deux principaux types d'éléments - les écrans et les boutons.



Un écran est une sorte de conteneur plein écran pour les boutons. L'écran a les propriétés suivantes:



  • image de fond
  • Couleur de l'arrière plan
  • façon de dessiner l'arrière-plan - remplissage avec une couleur d'arrière-plan ou affichage d'une image
  • en-tête
  • couleur du texte du titre
  • police du texte de l'en-tête
  • un pointeur vers l'écran parent (auquel revenir lors de la fermeture)
  • un tableau de pointeurs vers des boutons
  • un pointeur vers une procédure événementielle (appelée périodiquement dans la boucle principale du programme)
  • pointeur vers la routine de dessin d'écran


Structure de l'écran
typedef struct
{
	void				*addparameter;

	char				*bgimagename;
	
	void				*prevscreen;
	
	LNG_STRING_ID		name;
	TG_RECT				nameposition;
	TG_TEXTOPTIONS		nameoptions;
	
	uint8_t				btns_count;
	TG_BUTTON			*buttons;
	
	LCDUI_FONT_TYPE		font;
	LCDUI_FONT_TYPE		namefont;
	uint16_t			textcolor;
	uint16_t			nametextcolor;
	uint16_t			backcolor;

	struct {
		paintfunc		_callpaint;	// repaint screen
		processfunc		_process;	// screen process handling (check for changes, touch pressed, etc)
	} funcs;
} TG_SCREEN;




Les boutons en fait peuvent être non seulement des boutons, mais aussi du texte, une icône, une sorte d'élément changeant tel qu'un compteur ou une horloge. Il s'est avéré pratique de tout combiner dans un seul type et de définir le comportement de chaque bouton spécifique via ses propriétés.



Propriétés du bouton:



  • coordonnées à l'écran
  • Couleur de l'arrière plan
  • image de fond pour l'état libre
  • image d'arrière-plan pour l'état pressé
  • image d'arrière-plan pour l'état désactivé
  • image d'arrière-plan pour l'état actif (pour l'élément actif d'un groupe de boutons radio, par exemple)
  • méthode de rendu - image ou couleur d'arrière-plan
  • S'il faut redessiner le bouton lorsqu'il est enfoncé et relâché
  • texte du bouton
  • ( )
  • (, )
  • ( )
  • ,
  • ,


typedef struct
{
	void				*addparameter;
	
	uint8_t				button_id;
	

	int8_t				group_id;		// for swithed options buttons, >0 - single selection from group (select), <0 - multiple selection (switch)
	
	TG_RECT				position;
	
	void				*parentscreen;
	void				*childscreen;

	char				*bgimagename_en;
	char				*bgimagename_press;
	char				*bgimagename_dis;
	char				*bgimagename_act;	// for swithed options buttons

	LNG_STRING_ID		text;
	TG_RECT				textposition;
	LCDUI_FONT_TYPE		font;
	uint16_t			textcolor_en;
	uint16_t			textcolor_press;
	uint16_t			textcolor_dis;
	uint16_t			textcolor_act;	// for swithed options buttons
	uint16_t			backcolor_en;
	uint16_t			backcolor_press;
	uint16_t			backcolor_dis;
	uint16_t			backcolor_act;	// for swithed options buttons
	
	struct {
		uint8_t				active:1;		// for swithed options buttons
		uint8_t				needrepaint:1;
		uint8_t				pressed:1;
		uint8_t				disabled:1;
		uint8_t				repaintonpress:1;		// repaint or not when pressed - for indicate pressed state
		BGPAINT_TYPE		bgpaint:2;
	} options;
	
	TG_TEXTOPTIONS	textoptions;

	struct {
		paintfunc		_call_paint;	// repaint button
		pressfunc		_call_press;	// touch events handling
		pressfunc		_call_longpress;	// touch events handling
		processfunc		_call_process;	// periodical processing (for example text value refresh)
	} funcs;
} TG_BUTTON;




Avec l'aide de cet ensemble de propriétés, il est devenu possible de créer presque tout dans l'interface basé sur un tel élément. Si un écran ou un bouton a un pointeur vers l'une des procédures nulles, la procédure standard correspondante est appelée. Au lieu d'un pointeur de procédure en appuyant sur un bouton, par exemple, il peut y avoir un identifiant spécial indiquant que vous devez aller à l'écran enfant ou précédent, puis la procédure standard le fera. En général, les procédures standard couvrent presque tous les cas d'utilisation de boutons ordinaires et vous devez créer vos propres procédures pour un bouton uniquement dans des cas non standard - par exemple, lorsqu'un bouton fonctionne comme une horloge ou en tant qu'élément d'une liste de fichiers.



Mais ce qui manquait aux capacités de ce schéma, c'était pour les fenêtres modales avec des messages ou des questions (comme MessageBox dans l'API Windows), j'ai donc créé un type d'écran distinct pour eux. Aucune image d'arrière-plan et une taille déterminée par le titre ou le message lui-même. Ces messages peuvent être créés en quatre versions - avec des boutons "Oui / Non", avec des boutons "OK / Annuler", avec un bouton "OK", ou sans boutons du tout (comme "Attendre, les données se chargent ...").







Structure de la fenêtre de message
typedef struct
{
	MSGBOXTYPE			type;
	
	void				*prevscreen;
	
	char				caption[128];
	char				text[512];
	TG_RECT				boxpos;
	
	uint8_t				btns_count;
	TG_BUTTON			buttons[TG_BTN_CNT_MSGBOX];
	
	uint16_t			caption_height;
	
	LCDUI_FONT_TYPE		font_caption;
	LCDUI_FONT_TYPE		font_text;
	uint16_t			text_color;
	uint16_t			box_backcolor;
	uint16_t			capt_textcolor;
	uint16_t			capt_backcolor;
} TG_MSGBOX;




C'est sur la base de ces trois types que toute l'interface a été construite, elle s'est avérée assez flexible. Désormais, l'initialisation de tous les éléments se fait strictement dans le firmware, mais il y a une idée pour donner aux utilisateurs la possibilité de créer leur propre interface en décrivant les propriétés de tous les éléments dans le fichier de configuration et en ajoutant un certain nombre d'images nécessaires. En théorie, il sera possible de changer le contenu des différents écrans - quels boutons mettre sur l'écran principal, quels boutons pour l'écran de service, etc.



1.4 Multilingue







Le multilinguisme faisait partie des tâches au départ. Mais au début, j'ai emprunté le chemin stupide - lors de l'initialisation de tous les éléments, je leur ai attribué des textes de la table des langues qui était la table actuelle. Changer de langue signifiait réinitialiser tous les éléments de texte, et quand il y avait plus de deux écrans dans l'interface, et plus de 20 boutons et étiquettes, j'ai réalisé que je ne pouvais plus vivre comme ça. Puis il a fait toutes les références aux textes à travers la procédure. La procédure reçoit un identificateur de texte en tant que paramètre, et elle renvoie un pointeur vers le texte dans la langue actuelle:



	char *mshortname = LANG_GetString(LSTR_SHORT_JANUARY);


Lors du changement de langue, le pointeur passe simplement d'un tableau de textes dans l'ancienne langue à un tableau avec des textes dans la nouvelle langue:



void		LANG_SetLanguage(uint8_t lang)
{
	lngCurrent = lngLanguages[lang].strings;
	
	return;
}


Tous les textes source sont en codage UTF-8. J'ai également dû bricoler ces encodages. Textes - en UTF-8, fichiers cyrilliques - en Unicode-16, certaines chaînes - en ANSI standard. Je ne voulais pas extraire tout un ensemble de bibliothèques pour prendre en charge les encodages multi-octets dans le firmware, donc plusieurs fonctions ont été écrites pour la conversion de l'encodage à l'encodage et pour les opérations avec des textes dans différents encodages, par exemple, l'ajout d'une chaîne UTF-8 à la fin d'une chaîne Unicode16.

L'ajout d'une nouvelle langue s'est maintenant réduit à créer une table de textes et à changer la valeur de la constante LNG_LANGS_COUNT. Certes, il reste une question avec les polices, si la nouvelle langue utilise des symboles autres que le cyrillique et le latin ... Maintenant, je supporte le russe et l'anglais traduit par Google dans le code source.



1.5 Stockage d'images et d'autres ressources



Pour stocker de grandes ressources, la carte dispose d'un flash SPI de 8 mégaoctets W25Q64. Au départ, je voulais faire comme toujours - définir un décalage pour chaque ressource à l'intérieur du flash et les y enregistrer sous forme de données binaires. Mais ensuite, j'ai réalisé que les problèmes avec cette méthode me sont garantis dès que le nombre de ressources sauvegardées dépasse quelques dizaines et que je veux changer, par exemple, une image enregistrée sixième dans l'ordre. Si sa taille augmente, vous devrez décaler les adresses de toutes les ressources suivantes et les réécrire. Ou laissez un espace libre de taille inconnue après chaque ressource - qui sait comment l'une des ressources peut changer. Oui, dans un cercueil j'ai vu cette histoire :) Alors j'ai craché et organisé un système de fichiers sur ce flash.À ce moment-là, j'avais déjà un système de fichiers USB basé sur la bibliothèque FatFS, il me suffisait donc d'écrire simplement des fonctions de lecture / écriture de bas niveau séparées pour les secteurs. Une seule chose m'a légèrement bouleversé - la taille du secteur effacé dans ce microcircuit est déjà de 4 Ko. Cela conduit d'une part au fait que les fichiers prendront de l'espace par portions de 4 Ko (le fichier a été écrit 200 octets - il a fallu 4 Ko de mémoire flash), et d'autre part, le tampon dans la structure de chaque pointeur de fichier consommera les mêmes 4 Ko de RAM, qui en le microcontrôleur n'est pas tant que ça - 192 Ko. On pourrait, bien sûr, être perverti et écrire des fonctions de bas niveau afin qu'elles puissent écrire et lire dans des portions plus petites, rapportant la taille du secteur, par exemple, 512 octets. Mais cela ralentirait le Flash, ce qui laisse la taille du secteur à 4 Ko.Ainsi, toute ressource est accessible simplement par son nom de fichier, ce qui s'est avéré très pratique. Pour le moment, par exemple, le nombre de ressources stockées a déjà dépassé 90. Et j'ai rendu leur mise à jour aussi simple que possible - les ressources mises à jour (ou nouvelles) sont écrites sur une clé USB dans un certain répertoire, la clé USB est insérée dans la carte, la carte est redémarrée en mode service (pendant allumez ou redémarrez, appuyez et maintenez le coin supérieur droit de l'écran) et copie automatiquement tous les fichiers trouvés dans ce répertoire de la clé USB vers le flash SPI.la carte redémarre en mode service (lors de la mise sous tension ou du redémarrage, appuyez et maintenez le coin supérieur droit de l'écran) et copie automatiquement tous les fichiers trouvés dans ce répertoire du lecteur flash USB vers le flash SPI.la carte redémarre en mode service (lors de la mise sous tension ou du redémarrage, appuyez sur le coin supérieur droit de l'écran et maintenez-le enfoncé) et copie automatiquement tous les fichiers trouvés dans ce répertoire du lecteur flash USB vers le flash SPI.







À suivre...



Peut-être que la partie la plus volumineuse est sortie sur l'interface. Si cet article s'avère intéressant pour la communauté, dans la deuxième partie, j'essaierai de tenir compte de tout le reste.



Eh bien, je serai heureux de répondre aux questions et aux commentaires.



- Partie 1: 1. Interface utilisateur.

- Partie 2: 2. Travailler avec le système de fichiers sur une clé USB. 3. Commande de moteur pas à pas pour le mouvement de la plate-forme.

- Partie 3: 4. Affichage des images des couches sur l'écran rétroéclairé. 5. Chaque petite chose, comme le contrôle de l'éclairage et des ventilateurs, le chargement et l'enregistrement des paramètres, etc. 6. Caractéristiques supplémentaires pour le confort et la commodité.



Liens



Kit MKS DLP sur Aliexpress Codes

sources du firmware d'origine du fabricant sur les

schémas GitHub du fabricant de deux versions de la carte sur GitHub

Mes sources sur GitHub



All Articles