Pédalez jusqu'au sol: créez un autre manipulateur de pied pour PC


Il y a tout juste un mois, je suis tombé sur cet article, qui raconte comment pédaler Vim. Un peu plus tard, après ma longue étude de trois minutes, j'ai découvert que ce sujet n'était plus nouveau et assez populaire. Je n'utilise moi-même Vim qu'en cas d'urgence (si je dois travailler dans la console, je préfère Nano), mais vous pouvez faire de même pour d'autres applications.

Au départ, je voulais faire un petit article, mais j'ai eu un tutoriel complet sur la création de cet appareil avec l'écriture de code étape par étape et une explication de quoi et comment. Afin de ne pas gonfler l'article, sous les spoilers il y aura diverses informations qui semblaient intéressantes et dignes de l'attention des nouveaux arrivants à Arduino, les utilisateurs avancés et surtout hâtifs ne perdront pas de temps à ce sujet. Le code source complet est également présenté à la fin de l'article.

Pourquoi en ai-je besoin?


Si vous n'avez aucun doute sur la nécessité et l'utilité de cet appareil, vous pouvez ignorer cet élément. Pour le reste, je voudrais d'abord parler des conditions préalables à la création de cet appareil.

À tout moment, les programmeurs et les concepteurs ont essayé de créer une interface pratique et conviviale afin que l'utilisateur puisse travailler avec l'application à l'aide de la souris et du clavier sans problèmes inutiles, alors pourquoi avons-nous besoin d'un autre manipulateur? Eh bien, regardons un peu l'histoire, ou plutôt, au début du XVIIIe siècle, quand un instrument de musique comme le piano a été inventé. Comme vous le savez, ce mot se traduit littéralement par «fort et silencieux», mais peu de gens pensent qu'un maître italien intelligent a reçu un tel instrument en «écrasant» le clavecin qui existait alors, ce qui a permis de contrôler le volume sonore dans une certaine mesure, sans enlever les mains des clés.

Il existe de nombreux exemples. La voiture a des pédales pour ne pas lancer le volant si vous devez ajouter de l'essence. Le kit de batterie a également des pédales pour frapper la grosse caisse et les cymbales. Et que peuvent donner les pédales lors de l'utilisation d'un ordinateur? Eh bien, par exemple, vous pouvez définir une combinaison de touches de raccourci, ou même ajouter une touche qui n'est pas là, comme activer et désactiver le son. Les pédales peuvent aider si vos mains sont occupées: je joue de la guitare moi-même, et parfois à l'accompagnement, il serait très pratique pour moi de rouler le backing sans essayer d'atteindre constamment le clavier. Et enfin, les contrôleurs peuvent donner des possibilités complètement inhumaines dans les jeux: ce serait cool de construire votre base entière dans une stratégie en un clic ou de détruire les ennemis à une vitesse d'une douzaine de battements par seconde dans les tireurs, non?

En général, j'espère vous avoir convaincu, ce qui signifie qu'il est temps de commencer directement le développement lui-même.

Ressources nécessaires


  • En fait, les pédales. Des difficultés ont immédiatement surgi du fait que je ne pouvais pas penser à un nom pour une telle pédale. Je savais seulement que de telles choses sont utilisées dans les machines à coudre. En général, à la demande de pédale électrique, j'ai quand même réussi à trouver ce dont j'avais besoin sur Aliexpress, et sans y réfléchir à deux fois, j'ai commandé 3 pièces.
  • Contrôleur Le pédalier doit émuler le clavier et, éventuellement, la souris pour pouvoir se connecter à un PC sans pilotes inutiles. Pour cela, la carte Arduino Pro Micro est parfaite, qui, bien qu'elle n'ait pas de conclusions, est aussi compacte que possible. Nous allons au même Aliexpress et achetons la version chinoise de ce miracle.
  • Fils. Pour placer 3 pédales sous la table, vous avez besoin d'au moins un fil à quatre fils d'une longueur d'au moins un mètre. Ici, je pense que les problèmes ne devraient pas se poser.
  • LED RGB et bouton. Le premier est nécessaire pour indiquer les modes, et le second est de les changer.
  • Eh bien, bien sûr, nous avons besoin d'un IDE Arduino, d'un fer à souder et de bras droits.

Diagramme de l'appareil


Avant même l'arrivée des colis, j'ai commencé à créer un diagramme de périphérique. Bien que cela en soit dit beaucoup, puisque je n'avais qu'à brancher les pédales, la diode et le bouton. Il s'est avéré en quelque sorte comme ceci:



Pour les pédales, j'ai décidé d'allouer 4 ports PB1-PB4 à la fois, c'est-à-dire deux pour la gauche et deux pour la jambe droite, bien que jusqu'à présent je n'ai que 3 pédales. De plus, elles sont toutes dans le même groupe et sont situées au même endroit. Sous la LED, j'ai pris les sorties PD0, PD1 et PD4, sous le bouton - PD7.
Dans ce cas, nous n'avons pas besoin de résistances de rappel, si vous utilisez celles qui sont intégrées dans le contrôleur. Il est vrai que lorsque vous appuyez sur un bouton ou une pédale, l'entrée sera faible et lorsqu'elle sera relâchée, elle sera élevée, c'est-à-dire que les pressions seront inversées, et vous ne devriez pas oublier cela.

Écriture de code


Cette étape a été la plus difficile: en raison de quelques erreurs dans les pointeurs, j'ai effacé le chargeur de démarrage plusieurs fois et, par conséquent, j'ai failli faire échouer la carte au niveau du logiciel. Ci-dessous, toutes les étapes de création du firmware sont décrites en détail, pour ceux qui veulent juste obtenir un code de travail, ce sera à la fin de l'article.

La préparation


Nous devons d'abord comprendre ce qu'est la pédale en termes de programme. J'ai décidé de permettre de régler les pédales de l'un des deux modes - temps réel et déclenchement. En même temps, chaque pédale a deux programmes: le premier est exécuté lorsque la pédale est maintenue en temps réel ou avec des pressions impaires en mode de déclenchement, la seconde est lorsque la pédale est relâchée en temps réel ou lorsque les pédales sont enfoncées uniformément en mode de déclenchement. La pédale a également un port, un état et deux variables - les positions actuelles dans les programmes 1 et 2. J'ai obtenu cette structure:

struct pedal { char port; //   char state; //  ,   char oldState; //  ,   char pos1; //  1 char pos2; //  2 unsigned char type; //0 —   , 1 —  ; unsigned char act1[16]; // 1 unsigned char act2[16]; // 2 }; 

Arduino a un peu de mémoire et est également 8 bits, il est donc préférable d'essayer d'utiliser char plutôt que int dans la mesure du possible.

Nous avons également besoin de la bibliothèque de claviers standard pour fonctionner comme un clavier.

Cliquez sur Traitement


Nous devons maintenant créer un interpréteur qui lira les données du tableau et les enverra sous forme de touches à la machine, ainsi que sélectionner plusieurs valeurs pour diverses commandes internes. Nous ouvrons la page avec les codes clés, et voyons quoi et comment nous pouvons cliquer. Je n'ai pas creusé profondément et étudié toutes sortes de normes de clavier, car les informations ici me semblaient assez suffisantes pour un tel projet. La première moitié est réservée aux caractères ASCII standard (bien que certains d'entre eux ne soient pas imprimables ou non utilisés), la seconde moitié est destinée à diverses touches de modification. Il y a même des codes séparés pour les touches gauche et droite, ce qui était très agréable, mais je n'ai pas vu de codes spéciaux pour les numéros du nampad, bien que, pour autant que je sache, ils sont perçus d'une manière spéciale dans le système que les numéros ordinaires. Peut-être que leurs codes se trouvent quelque part dans les «trous», entre les plages, mais ce n'est plus le cas. Donc, le plus gros code est la touche haut - 218, ce qui signifie que la plage 219-255 peut être considérée comme libre, enfin, ou du moins il n'y a pas de clés importantes.

 void pedalAction() { //255  ,     if (pedal1->type == 255) return; //     unsigned char *prg; //     char *pos; if (pedal1->type) { //       int current; if ((current = digitalRead(ports[num])) != oldState[num]) { if (!current) state[num] = !state[num]; oldState[num] = current; } if (!state[num]) { //act1 pos2[num] = 0; pos = &(pos1[num]); prg = pedal1->act1; } else { //act2 pos1[num] = 0; pos = &(pos2[num]); prg = pedal1->act2; } } else { //        if (!digitalRead(ports[num])) { //act1 pos2[num] = 0; pos = &(pos1[num]); prg = pedal1->act1; } else { //act2 pos1[num] = 0; pos = &(pos2[num]); prg = pedal1->act2; } } while (1) { if (prg[*pos] == 254) { // ,   *pos Keyboard.press(prg[++*pos]); } else if (prg[*pos] == 253) { // ,   *pos Keyboard.release(prg[++*pos]); } else if (prg[*pos] == 252) { //" ",    ++*pos; return; } else if (prg[*pos] == 251) { //       *pos+1 *pos = prg[*pos + 1]; return; } else if (prg[*pos] == 255 || prg[*pos] == 0) { // ,   return; } else { //   Keyboard.write(prg[*pos]); } //       ,     if (++*pos>=16) pos = 0; } } 

Je pense que même une personne n'ayant pas le plus haut niveau de connaissance de C n'aura pas de questions sur ce qui se passe ici. Tout d'abord, la fonction sélectionne la pédale souhaitée et détermine, en fonction du mode et de l'état de la pédale, le programme à exécuter. Lors de la lecture de chaque élément du tableau, s'il ne s'agit pas d'un caractère de contrôle, la fonction Keyboard.write () est appelée, qui émule en appuyant et en relâchant une touche. Les caractères de contrôle sont traités séparément et sont nécessaires pour bloquer les combinaisons de touches et naviguer dans le programme.

Quelques fonctionnalités du mode clavier
Keyboard.write () a des nuances simples, mais pas évidentes pour les débutants, basées sur le fait que nous envoyons des données non pas sous forme brute, mais sous forme de touches. Tout d'abord, étrangement, sans pilotes supplémentaires, l'ordinateur ne peut accepter que les caractères du clavier qui se trouvent sur le clavier, ce qui signifie que nous ne pourrons pas envoyer de 0x03 (signal d'interruption) ou 0x1B (début de la séquence ESCAPE). Deuxièmement, nous pouvons ajuster les lettres majuscules telles qu'elles sont dans le tableau ASCII, mais la machine obtiendra la combinaison de touches Maj + <lettre minuscule>. Cela peut devenir un problème si nous avons activé le verrouillage des majuscules et que nous recevrons "de façon inattendue" des petites lettres au lieu de grandes lettres et vice versa. Troisièmement, nous ne pouvons pas utiliser la langue russe, ni dans aucune autre langue. Cela se produit à nouveau en raison de choses ennuyeuses telles que les codes clés . Bien que Keyboard.write () l'accepte comme argument, le code correspondant à la clé sur laquelle il se trouve dans la disposition standard en anglais est toujours envoyé via USB, et si nous essayons d'envoyer l'alphabet cyrillique, nous ne saurons pas quoi. Par conséquent, si nous voulons dire bonjour à nos amis russophones via Arduino, alors dans le code, nous devons écrire "Ghbdtn", puis l'envoyer, après avoir sélectionné la mise en page russe. Une telle «salutation» fonctionnera dans la mise en page ukrainienne, mais en bulgare, malgré le fait qu'il existe également un alphabet cyrillique, rien n'en sortira, car les lettres y sont à des endroits complètement différents. (J'ai entendu une fois l'opinion que pour de nombreux développeurs américains et anglais, il est incompréhensible que quelqu'un puisse même avoir besoin d'utiliser plusieurs mises en page, mais aussi de les changer.)

Nous avons donc un interprète et une compréhension approximative de la façon dont notre pédalier interagit avec un ordinateur. Maintenant, nous devons mettre tout cela à l'état de firmware complet et vérifier les performances sur une seule pédale. Si vous créez une instance de la pédale et appelez cycliquement pedalAction (), alors en théorie nous exécuterons le programme spécifié dans la structure.

 struct pedal *pedal1 = {15, 0, 0, 0, 0, 0, "Hello, world!\0", 0}; void prepare () { pinMode(15, 2); //2 - INPUT_PULLUP,        Keyboard.begin(); } void loop() { pedalAction(); } 

Soit dit en passant, n'oubliez jamais les terminateurs nuls dans ces "programmes" si leur longueur est inférieure à la taille du tableau et s'ils ne sont pas cycliques, car Arduino tentera non seulement d'interpréter les données qui ne sont pas définies, mais les enverra également à la machine à grande vitesse, et c'est la même chose que de donner un clavier à un singe.

Une pédale c'est bien, et deux c'est mieux


Il est maintenant temps de traiter le traitement des signaux de plusieurs pédales, ainsi que d'ajouter des modes de commutation. Au début de l'article, 4 ports ont été alloués aux pédales, chacun devant pouvoir fonctionner en sept modes. Pourquoi 7? Parce que sans utiliser PWM, notre LED ne peut donner que 7 couleurs, et la huitième - éteinte. Ce montant est tout à fait suffisant pour l'utilisateur moyen, mais dans les cas extrêmes, il peut facilement être augmenté. Nous allons donc stocker les pédales dans un tableau bidimensionnel de 7 x 4. Afin de ne pas obstruer la mémoire, les valeurs communes à plusieurs structures, telles que le numéro de port, peuvent être extraites dans des tableaux séparés. En conséquence, nous obtenons quelque chose comme ceci:

 struct pedal { unsigned char type; unsigned char act1[16]; unsigned char act2[16]; }; struct pedal pedals[7][4] = { { { 255, {"Hello, world!\0"}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} } }; char ports[4] = {15, 16, 14, 8}; char pos1[4] = {0, 0, 0, 0}; char pos2[4] = {0, 0, 0, 0}; char state[4] = {0, 0, 0, 0}; char oldState[4] = {0, 0, 0, 0}; char mode = 0; //  char curPedal = 0; //   

La magie du numéro 255
Vous avez probablement remarqué que dans l'article le numéro 255 apparaît souvent, où il serait plus logique de mettre 0. À l'avenir, je dirai que cela est nécessaire pour la commodité du stockage des pédales en EEPROM, car de l'usine, chacune de ses cellules ne contient pas 0, mais juste 255, ce qui signifie que ce nombre sera beaucoup plus pratique à utiliser pour indiquer des variables non définies que 0, de sorte que vous n'écrasez pas la mémoire à chaque fois.

Il est important pour nous de ne connaître que le type de pédale et deux programmes, donc nous ne les laisserons que directement dans la structure, laisser l'automatisation faire le reste. Les méthodes prepare et loop ressembleront maintenant à ceci:

 void prepare(){ pinMode(2, 1); pinMode(3, 1); pinMode(4, 1); pinMode(6, 2); for (int i : ports) pinMode(i, 2); Keyboard.begin(); } void loop() { for (int i = 0; i < 6; i++) { int current; if ((current = digitalRead(modeButton)) != last) { if (!current) { if (++mode >= 7) mode = 0; while (pedals[mode][0].type == 255 && pedals[mode][1].type == 255 && pedals[mode][2].type == 255 && pedals[mode][3].type == 255) if (++mode >= 7) { mode = 0; break; } } last = current; digitalWrite(2, (mode + 1) & 0b001); digitalWrite(3, (mode + 1) & 0b010); digitalWrite(4, (mode + 1) & 0b100); for (int i = 0; i < 4; i++) { pos1[i] = 0; pos2[i] = 0; state[i] = 0; oldState[i] = 0; } delay(50); } curPedal = i; pedalAction } } } 

Le contrôleur considérera le mode inutilisé si aucune pédale n'y est déclarée (mode = 255), ce qui signifie que lorsqu'il le frappera, il passera immédiatement au suivant, mais le premier mode existera toujours. Lors du changement de mode, toutes les valeurs dans les tableaux sont annulées, car nous n'avons pas besoin de les enregistrer pour chaque mode (non?), Et puis la boucle contourne toutes les pédales et appelle PedalAction pour elles.

De plus, au début de la méthode pedalAction (), vous devez ajouter la ligne suivante afin qu'elle comprenne laquelle des structures traiter:

 struct pedal *pedal1 = &pedals[mode][curPedal]; 

La structure existante de la pédale1 peut être supprimée car inutile.

Tout cela fonctionne également très bien, cependant, j'ai rencontré un problème: certains programmes n'ont pas le temps de recevoir des clics à la vitesse à laquelle Arduino les envoie. La solution la plus évidente consiste à ajouter la possibilité de définir des délais entre les actions si nécessaire. Ce n'est que lorsque nous nous asseyons pour écrire des programmes pour microcontrôleurs que toutes les puces, comme le multithreading matériel, sont restées quelque part, dans les ordinateurs de haut niveau, lorsque nous ajoutons un retard, le programme entier s'arrête jusqu'à ce que le contrôleur compte le bon nombre de cycles. Comme nous n'avons pas de multithreading, nous devrons le créer.

Difficile à dire oui facile à faire


Je n'ai pas inventé de vélo, mais j'ai pris la bibliothèque ArduinoThread terminée. Ici, vous pouvez lire un peu comment cela fonctionne et le télécharger. Vous pouvez télécharger la bibliothèque à partir de l'IDE Arduino lui-même. En bref, il vous permet d'effectuer périodiquement une fonction avec un certain intervalle, tout en ne vous permettant pas d'entrer dans une boucle infinie si l'exécution prend plus de temps que l'intervalle. Ce dont vous avez besoin. Créez un autre tableau avec des threads pour chaque pédale:

 Thread pedalThreads[6] = {Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10)}; 

Nous avons maintenant 6 threads virtuels identiques, mais en même temps, ce sont des objets différents.

Réécrivons le cycle de contournement de la pédale pour travailler avec la nouvelle fonctionnalité:

 ... for (int i = 0; i < 4; i++) { if (pedalThreads[i].shouldRun()) { curPedal = i; pedalThreads[i].run(); } } ... 

Maintenant, la valeur 252 dans le tableau des programmes, qui correspond à «ne rien faire», donnera un délai de 10 millisecondes (bien qu'en fait un peu plus, car l'exécution du code prend également du temps). L'ajout de quelques lignes à l'interpréteur permettra de régler le retard dans plusieurs de ces "quanta", en ne dépensant que 2 octets du tableau:

 ... if (wait[num]) { wait[num]--; return; } else if (prg[*pos] == 250) { wait[num] = prg[++*pos]; } ... 

Contrairement à d'autres commandes, cette instruction doit être ajoutée exactement au début de l'interpréteur, c'est-à-dire immédiatement après «while (1) {», car le délai doit être traité avant que l'interprète ne poursuive la lecture du programme. Le tableau d'attente doit être déclaré de la même manière que pour les ports, l'état, etc. et également réinitialiser ses cellules lors du changement de mode, afin que le retard ne passe pas à un autre programme.

Désormais, avec la possibilité de définir un délai pouvant aller jusqu'à 2,55 secondes, aucun problème de définition des clés par programme ne devrait se poser.

Programmation en déplacement


En principe, ici, il serait possible de terminer avec le code et de commencer à assembler l'appareil, mais dans ce cas, si quelqu'un veut soudainement reprogrammer les pédales, il devra ouvrir l'Arduino IDE, modifier le code et télécharger à nouveau le firmware. Naturellement, cette option n'est pas la meilleure, j'ai donc décidé d'ajouter la possibilité de changer le programme à partir du port série Arduino et de stocker les programmes eux-mêmes dans l'EEPROM. Pour travailler avec de la mémoire non volatile, vous devez connecter la bibliothèque standard EEPROM.h. Le code du mode de programmation est le suivant:

 ... if (!digitalRead(modeButton)) { //  Serial.begin(9600); while (!Serial) { PORTD = 0b00000000 + (PORTD & 0b11101100); delay(250); PORTD = 0b00010000 + (PORTD & 0b11101100); delay(250); } Serial.println(F("***Programming mode***")); Serial.println(F("Write the command as <m> <p> <c>")); Serial.println(F("m - number of mode, one digit")); Serial.println(F("p - number of pedal, one digit")); Serial.println(F("c - command, it can be:")); Serial.println(F("\tr - read pedal info")); Serial.println(F("\tw - enter to writing mode and change pedal programm")); Serial.println(F("\te - erase pedal programm and delete it")); Serial.println(F("There are up to 7 modes and 6 pedals per mode can be configured")); Serial.println(F("Mode will be incative if there is no pedal configured in it")); while (1) { while (Serial.available()) { Serial.read(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(""); Serial.println(F("Enter command")); while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); delay(3); if (Serial.available() == 3) { int curMode = Serial.read() - 48; int curPedal = Serial.read() - 48; char cmd = Serial.read(); if (curMode > 6 || curMode < 0) { Serial.print(F("Mode must be in 0-6. You entered ")); Serial.println(curMode); continue; } if (curPedal > 3 || curPedal < 0) { Serial.print(F("Pedal must be in 0-3. You entered ")); Serial.println(curPedal); continue; } Serial.println(); if (cmd == 'r') { int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); Serial.print("type: "); int curAddress = beginAddress; Serial.println(EEPROM[curAddress++]); Serial.print("act1: "); for (int i = curAddress ; i < curAddress + (sizeof(struct pedal) - 1) / 2; i++) { Serial.print(EEPROM[i]); Serial.print("\t"); } Serial.println(); curAddress = beginAddress + 1 + (sizeof(struct pedal) - 1) / 2; Serial.print("act2: "); for (int i = curAddress ; i < curAddress + (sizeof(struct pedal) - 1) / 2; i++) { Serial.print(EEPROM[i]); Serial.print("\t"); } Serial.println(); } else if (cmd == 'w') { Serial.println(F("Enter type:")); PORTD = 0b00000001 + (PORTD & 0b11101100); while (!Serial.available()); int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); int curAddress = beginAddress; PORTD = 0b00000010 + (PORTD & 0b11101100); EEPROM[curAddress++] = (char)Serial.parseInt(); PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Enter act1 in DEC divided by space:")); while (Serial.available()) { Serial.read(); delay(1); } while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); while (Serial.available()) { EEPROM[curAddress++] = (char)Serial.parseInt(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); curAddress = beginAddress + 1 + (sizeof(struct pedal) - 1) / 2; Serial.println(F("Enter act2 in DEC divided by space:")); while (Serial.available()) { Serial.read(); delay(1); } while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); while (Serial.available()) { EEPROM[curAddress++] = (char)Serial.parseInt(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Finished, don't forget to verify written data!")); } else if (cmd == 'e') { int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); Serial.println(F("Disabling pedal...")); PORTD = 0b00000010 + (PORTD & 0b11101100); EEPROM[beginAddress] = 255; PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Pedal disabled")); } } else { Serial.println(F("Incorrect command, please read help above")); } }; } ... 

Ce que fait ce code est expliqué par l'aide qu'il contient: un numéro d'espace est entré pour le numéro de mode, le numéro de pédale et une commande, dont il existe 3 - lecture, écriture et exécution d'une suppression de programme. Toutes les données sur les pédales sont stockées l'une après l'autre dans une séquence de 33 octets, c'est-à-dire le type de pédale et deux programmes, et que nous occupons 7 * 4 * 33 = 924 de 1024 octets d'EEPROM. J'ai abandonné la possibilité d'utiliser la taille dynamique des pédales dans la mémoire, car dans ce cas, lors de la reprogrammation d'une pédale, vous devrez écraser presque toutes les cellules, et il existe un nombre fini de cycles de réécriture, nous vous recommandons donc de le faire le moins possible.

Caractéristiques du travail avec EEPROM
Je voudrais également attirer l'attention sur les lignes du formulaire:
  PORTD = 0b00000010 + (PORTD & 0b11101100); ... PORTD = 0b00000001 + (PORTD & 0b11101100); 

Grâce à cette bibliothèque, du point de vue du programmeur, la mémoire non volatile est un tableau de caractères ordinaire, mais, en tant que «arduino», nous devons comprendre que l'écriture sur ROM est une opération très difficile, qui prend jusqu'à ~ 3 secondes au contrôleur, et il est conseillé de ne pas l'interrompre. processus. Cette conception fait briller la diode rouge pendant de telles opérations, puis renvoie la couleur verte «sûre».

Dans le mode d'enregistrement de programme, l'entrée est effectuée directement par les valeurs d'octets dans le système de nombres décimaux avec un espace. Cela s'avère assez sévère, mais vous n'avez pas besoin d'écrire un analyseur complexe. De plus, la reprogrammation ne se produit pas si souvent, et dans ces cas, il est tout à fait possible de consulter la table ASCII.

Avec la préservation des structures triées, nous devons maintenant en quelque sorte extraire nos données de là et les convertir en vue "pédale":

 ... for (int i = 0; i < 7; i++) { for (int j = 0; j < 4; j++) { struct pedal *p = &pedals[i][j]; int beginAddress = sizeof(struct pedal) * (i * 6 + j); int curAddress = beginAddress; unsigned char type = EEPROM[curAddress++]; if (type == 0 || type == 1) { p->type = type; for (int k = 0 ; k < 16; k++) { p->act1[k] = EEPROM[curAddress++]; } for (int k = 0 ; k < 16; k++) { p->act2[k] = EEPROM[curAddress++]; } } } } ... 

Rien de surnaturel ne se produit ici non plus: le contrôleur lit les données de la mémoire et en remplit les structures existantes.

L'avantage de la programmation via UART est que nous n'avons à nouveau pas besoin de pilotes spéciaux, vous pouvez donc définir le comportement du manipulateur même à partir du téléphone.

Démonstration




Code source complet


Il est là
 #include <Keyboard.h> #include <Thread.h> #include <EEPROM.h> #define modeButton 6 struct pedal { unsigned char type; //0 —   , 1 —  , 255 —    unsigned char act1[16]; unsigned char act2[16]; }; struct pedal pedals[7][4] = { { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} }, { { 255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}}, {255, {255}, {255}} } }; char ports[4] = {8, 16, 15, 14}; char pos1[4] = {0, 0, 0, 0}; char pos2[4] = {0, 0, 0, 0}; char state[4] = {0, 0, 0, 0}; char oldState[4] = {0, 0, 0, 0}; char wait[4] = {0, 0, 0, 0}; void pedalAction(); char mode = 0; char curPedal; Thread pedalThreads[6] = {Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10), Thread(pedalAction, 10)}; void setup() { pinMode(2, 1); pinMode(3, 1); pinMode(4, 1); pinMode(modeButton, 2); if (!digitalRead(modeButton)) { //  Serial.begin(9600); while (!Serial) { PORTD = 0b00000000 + (PORTD & 0b11101100); delay(250); PORTD = 0b00010000 + (PORTD & 0b11101100); delay(250); } Serial.println(F("***Programming mode***")); Serial.println(F("Write the command as <m> <p> <c>")); Serial.println(F("m - number of mode, one digit")); Serial.println(F("p - number of pedal, one digit")); Serial.println(F("c - command, it can be:")); Serial.println(F("\tr - read pedal info")); Serial.println(F("\tw - enter to writing mode and change pedal programm")); Serial.println(F("\te - erase pedal programm and delete it")); Serial.println(F("There are up to 7 modes and 6 pedals per mode can be configured")); Serial.println(F("Mode will be incative if there is no pedal configured in it")); while (1) { while (Serial.available()) { Serial.read(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(""); Serial.println(F("Enter command")); while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); delay(3); if (Serial.available() == 3) { int curMode = Serial.read() - 48; int curPedal = Serial.read() - 48; char cmd = Serial.read(); if (curMode > 6 || curMode < 0) { Serial.print(F("Mode must be in 0-6. You entered ")); Serial.println(curMode); continue; } if (curPedal > 3 || curPedal < 0) { Serial.print(F("Pedal must be in 0-3. You entered ")); Serial.println(curPedal); continue; } Serial.println(); if (cmd == 'r') { int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); Serial.print("type: "); int curAddress = beginAddress; Serial.println(EEPROM[curAddress++]); Serial.print("act1: "); for (int i = curAddress ; i < curAddress + (sizeof(struct pedal) - 1) / 2; i++) { Serial.print(EEPROM[i]); Serial.print("\t"); } Serial.println(); curAddress = beginAddress + 1 + (sizeof(struct pedal) - 1) / 2; Serial.print("act2: "); for (int i = curAddress ; i < curAddress + (sizeof(struct pedal) - 1) / 2; i++) { Serial.print(EEPROM[i]); Serial.print("\t"); } Serial.println(); } else if (cmd == 'w') { Serial.println(F("Enter type:")); PORTD = 0b00000001 + (PORTD & 0b11101100); while (!Serial.available()); int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); int curAddress = beginAddress; PORTD = 0b00000010 + (PORTD & 0b11101100); EEPROM[curAddress++] = (char)Serial.parseInt(); PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Enter act1 in DEC divided by space:")); while (Serial.available()) { Serial.read(); delay(1); } while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); while (Serial.available()) { EEPROM[curAddress++] = (char)Serial.parseInt(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); curAddress = beginAddress + 1 + (sizeof(struct pedal) - 1) / 2; Serial.println(F("Enter act2 in DEC divided by space:")); while (Serial.available()) { Serial.read(); delay(1); } while (!Serial.available()); PORTD = 0b00000010 + (PORTD & 0b11101100); while (Serial.available()) { EEPROM[curAddress++] = (char)Serial.parseInt(); delay(1); } PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Finished, don't forget to verify written data!")); } else if (cmd == 'e') { int beginAddress = sizeof(struct pedal) * (curMode * 6 + curPedal); Serial.println(F("Disabling pedal...")); PORTD = 0b00000010 + (PORTD & 0b11101100); EEPROM[beginAddress] = 255; PORTD = 0b00000001 + (PORTD & 0b11101100); Serial.println(F("Pedal disabled")); } } else { Serial.println(F("Incorrect command, please read help above")); } }; } for (int i : ports) pinMode(i, 2); pinMode(17, 1); for (int i = 0; i < 7; i++) { for (int j = 0; j < 4; j++) { struct pedal *p = &pedals[i][j]; int beginAddress = sizeof(struct pedal) * (i * 6 + j); int curAddress = beginAddress; unsigned char type = EEPROM[curAddress++]; if (type == 0 || type == 1) { p->type = type; for (int k = 0 ; k < 16; k++) { p->act1[k] = EEPROM[curAddress++]; } for (int k = 0 ; k < 16; k++) { p->act2[k] = EEPROM[curAddress++]; } } } } Keyboard.begin(); } int last = 0; void loop() { int current; if ((current = digitalRead(modeButton)) != last) { if (!current) { if (++mode >= 7) mode = 0; while (pedals[mode][0].type == 255 && pedals[mode][1].type == 255 && pedals[mode][2].type == 255 && pedals[mode][3].type == 255) if (++mode >= 7) { mode = 0; break; } } last = current; digitalWrite(2, (mode + 1) & 0b001); digitalWrite(3, (mode + 1) & 0b010); digitalWrite(4, (mode + 1) & 0b100); for (int i = 0; i < 4; i++) { pos1[i] = 0; pos2[i] = 0; state[i] = 0; oldState[i] = 0; wait[i] = 0; } delay(50); } for (int i = 0; i < 4; i++) { if (pedalThreads[i].shouldRun()) { curPedal = i; pedalThreads[i].run(); } } } void pedalAction() { struct pedal *pedal1 = &pedals[mode][curPedal]; if (pedal1->type == 255) return; unsigned char *prg; char *pos; if (pedal1->type) { int current; if ((current = digitalRead(ports[curPedal])) != oldState[curPedal]) { if (!current) state[curPedal] = !state[curPedal]; oldState[curPedal] = current; } if (!state[curPedal]) { //act1 pos2[curPedal] = 0; pos = &(pos1[curPedal]); prg = pedal1->act1; } else { //act2 pos1[curPedal] = 0; pos = &(pos2[curPedal]); prg = pedal1->act2; } } else { if (!digitalRead(ports[curPedal])) { //act1 pos2[curPedal] = 0; pos = &(pos1[curPedal]); prg = pedal1->act1; } else { //act2 pos1[curPedal] = 0; pos = &(pos2[curPedal]); prg = pedal1->act2; } } while (1) { if (wait[curPedal]) { wait[curPedal]--; return; } else if (prg[*pos] == 250) { wait[curPedal] = prg[++*pos]; } else if (prg[*pos] == 254) { // ,   *pos Keyboard.press(prg[++*pos]); } else if (prg[*pos] == 253) { // ,   *pos Keyboard.release(prg[++*pos]); } else if (prg[*pos] == 252) { delay(10); //" ",    ++*pos; return; } else if (prg[*pos] == 251) { //       *pos+1 *pos = prg[*pos + 1]; return; } else if (prg[*pos] == 255 || prg[*pos] == 0) { // ,   return; } else { //   Keyboard.write(prg[*pos]); } //       ,     if (++*pos >= 16) pos = 0; } } 


Postface


Bien qu'au départ j'ai fait un pédalier pour la possibilité de faire défiler l'enregistrement tout en jouant de la guitare, cependant, personnellement, j'ai trouvé pratique d'utiliser les pédales dans des tâches ordinaires, l'essentiel est de s'habituer un peu à un manipulateur aussi inhabituel. Et là réside encore un autre problème: déjà sans vos pédales préférées, travailler au contraire devient plus difficile, car il faut se rappeler quoi, où et pourquoi appuyer. Si les pédales peuvent toujours être portées et connectées au bureau, alors à l'institut, il est plus difficile de courir avec elles dans les salles de classe. Donc, utiliser cet appareil pour autre chose que son objectif d'origine est à vos risques et périls.

Pédalier assemblé:

Source: https://habr.com/ru/post/fr462263/


All Articles