Pedal to the floor: crea otro manipulador de pies para PC


Hace apenas un mes, me encontré con este artículo, que habla sobre pedalear a Vim. Un poco más tarde, después de mi largo estudio de tres minutos, descubrí que este tema ya no es nuevo y bastante popular. Yo mismo uso Vim solo en caso de emergencia (si tengo que trabajar en la consola, prefiero Nano), pero puede hacer lo mismo para otras aplicaciones.

Inicialmente, quería hacer un pequeño artículo, pero obtuve un tutorial completo sobre cómo crear este dispositivo con escritura de código paso a paso y una explicación de qué y cómo. Para no inflar el artículo, debajo de los spoilers habrá diversa información que parecía interesante y digna de la atención de los recién llegados a Arduino, los usuarios avanzados y especialmente apresurados pueden no perder el tiempo en eso. El código fuente completo también se presenta al final del artículo.

¿Por qué lo necesito?


Si no tiene dudas sobre la necesidad y la utilidad de este dispositivo, puede omitir este elemento. Por lo demás, primero me gustaría hablar sobre los requisitos previos para crear este dispositivo.

En todo momento, los programadores y diseñadores han intentado crear una interfaz conveniente y fácil de usar para que el usuario pueda trabajar con la aplicación usando el mouse y el teclado sin problemas innecesarios, entonces, ¿por qué necesitamos otro manipulador? Bueno, echemos un vistazo a la historia, o más bien, a principios del siglo XVIII, cuando se inventó un instrumento musical como el piano. Como saben, esta palabra se traduce literalmente como "alto y silencioso", pero pocas personas piensan que un maestro italiano inteligente recibió tal instrumento, en realidad "aprovechando" el clavecín que existía en ese momento, lo que hizo posible controlar en cierta medida el volumen del sonido, sin quitando las manos de las llaves.

Hay muchos ejemplos El automóvil tiene pedales para no tirar el volante si necesita agregar gasolina. El kit de batería también tiene pedales para tocar el bombo y los platillos. ¿Y qué pueden dar los pedales al usar una computadora? Bueno, por ejemplo, puede configurar alguna combinación de teclas de acceso rápido, o incluso agregar una tecla que no está allí, como activar y desactivar el sonido. Los pedales pueden ayudar si sus manos están ocupadas: toco la guitarra yo mismo y, a veces, para el acompañamiento, sería muy conveniente para mí rodar el respaldo sin tratar de alcanzar constantemente el teclado. Y finalmente, los controladores pueden dar posibilidades completamente inhumanas en los juegos: sería genial construir toda tu base en una estrategia con un solo clic o destruir enemigos a una velocidad de una docena de latidos por segundo en tiradores, ¿verdad?

En general, espero haberte convencido, lo que significa que es hora de comenzar directamente con el desarrollo en sí.

Recursos requeridos


  • En realidad, los pedales. Algunas dificultades surgieron inmediatamente debido al hecho de que no podía pensar en un nombre para tal pedal. Solo sabía que tales cosas se usan en máquinas de coser. En general, a pedido del pedal eléctrico, aún pude encontrar lo que necesitaba en Aliexpress, y sin pensarlo dos veces, pedí 3 piezas.
  • Controlador La pedalera debe emular el teclado y, posiblemente, el mouse para poder conectarse a una PC sin controladores innecesarios. Para esto, la placa Arduino Pro Micro es perfecta, que, aunque no tiene algunas conclusiones, está hecha lo más compacta posible. Vamos al mismo Aliexpress y compramos la versión china de este milagro.
  • Alambres Para colocar 3 pedales debajo de la mesa, necesita al menos un cable de cuatro hilos con una longitud de al menos un metro. Aquí, creo, no deberían surgir problemas.
  • LED RGB y botón. El primero es necesario para indicar los modos, y el segundo es cambiarlos.
  • Bueno, por supuesto, necesitamos un IDE Arduino, un soldador y brazos rectos.

Diagrama del dispositivo


Incluso antes de que llegaran los paquetes, comencé a crear un diagrama de dispositivo. Aunque esto se dice mucho, ya que solo tuve que conectar los pedales, el diodo y el botón. Resultó de alguna manera así:



Para los pedales, decidí asignar 4 puertos PB1-PB4 a la vez, es decir, dos para la pierna izquierda y dos para la pierna derecha, aunque hasta ahora solo tengo 3 pedales. Además, todos están en el mismo grupo y están ubicados en un solo lugar. Debajo del LED, tomé las salidas PD0, PD1 y PD4, debajo del botón - PD7.
En este caso, no necesitamos resistencias pull-up, si usa las que están integradas en el controlador. Sin embargo, cuando presione un botón o pedal, la entrada será baja, y cuando se suelte, será alta, es decir, las prensas se invertirán, y no debe olvidarse de esto.

Escritura de código


Esta etapa fue la más difícil: debido a un par de errores en los punteros, borré el gestor de arranque varias veces y, como resultado, casi fallé la placa a nivel de software. A continuación se describen en detalle todas las etapas de la creación del firmware, para aquellos que solo quieren obtener un código de trabajo, será al final del artículo.

Preparación


Primero necesitamos entender qué es el pedal en términos del programa. Decidí hacer posible configurar los pedales de uno de los dos modos: en tiempo real y disparador. Al mismo tiempo, cada pedal tiene dos programas: el primero se realiza cuando el pedal se mantiene en tiempo real o con presiones extrañas en el modo de disparo, el segundo es cuando el pedal se suelta en tiempo real o cuando los pedales se presionan de manera uniforme en el modo de disparo. El pedal también tiene un puerto, un estado y dos variables: las posiciones actuales en los programas 1 y 2. Obtuve esta estructura:

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 tiene bastante memoria y también es de 8 bits, por lo que es mejor intentar usar char en lugar de int cuando sea posible.

También necesitamos la biblioteca de teclado estándar para funcionar como teclado.

Procesamiento de clics


Ahora necesitamos hacer un intérprete que lea los datos de la matriz y los envíe en forma de pulsaciones de teclas a la máquina, así como seleccionar varios valores para varios comandos internos. Abrimos la página con los códigos clave y vemos qué y cómo podemos hacer clic. No profundicé y estudié todo tipo de estándares de teclado, ya que la información aquí me pareció suficiente para tal proyecto. La primera mitad está reservada para caracteres ASCII estándar (aunque algunos de ellos no se pueden imprimir o no se usan), la segunda mitad es para varias teclas modificadoras. Incluso hay códigos separados para las teclas izquierda y derecha, lo cual es muy agradable, pero no vi ningún código especial para los números del nampad, aunque, hasta donde yo sé, se perciben de una manera especial en el sistema que los números ordinarios. Quizás sus códigos están en algún lugar en los "agujeros", entre los rangos, pero ahora no se trata de eso. Entonces, el código más grande es la tecla arriba: 218, lo que significa que el rango 219-255 puede considerarse libre, bueno, o al menos no hay teclas 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; } } 

Creo que incluso una persona que no tenga el más alto nivel de conocimiento de C no tendrá preguntas sobre lo que está sucediendo aquí. Primero, la función selecciona el pedal deseado y determina, dependiendo del modo y la condición del pedal, qué programa se debe realizar. Al leer cada elemento de la matriz, si no es un carácter de control, se llama a la función Keyboard.write (), que emula al presionar y soltar una tecla. Los caracteres de control se procesan por separado y son necesarios para sujetar las combinaciones de teclas y navegar por el programa.

Algunas características del modo teclado
Keyboard.write () tiene algunos matices simples, pero no obvios para principiantes, basados ​​en el hecho de que enviamos datos no en forma cruda, sino como pulsaciones de teclas. En primer lugar, por extraño que parezca, sin controladores adicionales, la computadora solo puede aceptar caracteres del teclado que están en el teclado, lo que significa que no podremos enviar ningún 0x03 (señal de interrupción) o 0x1B (comienzo de la secuencia ESCAPE). En segundo lugar, podemos ajustar las letras mayúsculas tal como están en la tabla ASCII, pero la máquina obtendrá la combinación de teclas Mayús + <letra minúscula>. Esto puede convertirse en un problema si tenemos CapsLock habilitado y recibiremos "inesperadamente" letras pequeñas en lugar de letras grandes y viceversa. En tercer lugar, no podemos usar el idioma ruso, así como en cualquier otro idioma. Esto sucede nuevamente debido a cosas tan molestas como los códigos clave . Aunque Keyboard.write () lo acepta como argumento, el código USB aún envía el código correspondiente a la clave en la que está en el diseño estándar en inglés, y si intentamos enviar el alfabeto cirílico, no sabremos qué. Por lo tanto, si queremos saludar a nuestros amigos de habla rusa a través de Arduino, entonces en el código debemos escribir "Ghbdtn", y luego enviarlo, después de seleccionar el diseño en ruso. Tal "saludo" funcionará en el diseño ucraniano, pero en búlgaro, a pesar del hecho de que también hay un alfabeto cirílico, no saldrá nada de él, ya que las letras están en lugares completamente diferentes. (Una vez escuché la opinión de que para muchos desarrolladores estadounidenses e ingleses es incomprensible que alguien incluso necesite usar varios diseños, pero también cambiarlos).

Entonces, tenemos un intérprete y una comprensión aproximada de cómo nuestra pedalera interactúa con una computadora. Ahora necesitamos llevar todo esto al estado de firmware completo y verificar el rendimiento en un pedal. Si crea una instancia del pedal y llama cíclicamente pedalAction (), en teoría ejecutaremos el programa especificado en la estructura.

 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(); } 

Por cierto, nunca se olvide de los terminadores nulos en estos "programas" si su longitud es menor que el tamaño de la matriz y si no son cíclicos, porque Arduino no solo intentará interpretar los datos que no se especifican, sino que también los enviará a la máquina con gran velocidad, y eso es lo mismo que darle un teclado a un mono.

Un pedal es bueno y dos son mejores.


Ahora es el momento de tratar el procesamiento de señales de varios pedales, así como agregar modos de conmutación. Al comienzo del artículo, se asignaron 4 puertos para pedales, cada uno de los cuales debe poder trabajar en siete modos. ¿Por qué 7? Porque sin usar PWM, nuestro LED puede dar solo 7 colores y el octavo apagado. Esta cantidad es suficiente para el usuario promedio, pero en casos extremos se puede aumentar fácilmente. Por lo tanto, almacenaremos los pedales en una matriz bidimensional de 7 x 4. Para no obstruir la memoria, los valores comunes a varias estructuras, como el número de puerto, se pueden extraer en matrices separadas. Como resultado, obtenemos algo como esto:

 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 magia del número 255.
Probablemente notó que en el artículo aparece a menudo el número 255, donde sería más lógico poner 0. Mirando hacia el futuro, diré que esto es necesario para la comodidad de almacenar pedales en EEPROM, ya que desde la fábrica cada una de sus celdas no contiene 0, pero solo 255, lo que significa que este número será mucho más conveniente de usar para indicar variables indefinidas que 0, de modo que no sobrescriba la memoria cada vez.

Es importante para nosotros conocer solo el tipo de pedal y dos programas, por lo que los dejaremos solo directamente en la estructura, dejaremos que la automatización haga el resto. Los métodos de preparación y bucle ahora se verán así:

 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 } } } 

El controlador considerará el modo no utilizado si no se declara un solo pedal en él (modo = 255), lo que significa que cuando lo golpea, pasará inmediatamente al siguiente, pero el primer modo siempre existirá. Al cambiar el modo, todos los valores en las matrices se anulan, ya que no es necesario guardarlos para cada modo (¿verdad?), Y luego el bucle omite todos los pedales y llama a pedalAction por ellos.

Además, al comienzo del método pedalAction (), debe agregar la siguiente línea para que comprenda con qué estructuras tratar:

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

La estructura existente del pedal1 se puede eliminar como innecesaria.

Todo esto también funciona bastante bien, sin embargo, encontré un problema: algunos programas no tienen tiempo para recibir clics a la velocidad con la que Arduino los envía. La solución más obvia es agregar la capacidad de establecer demoras entre acciones cuando sea necesario. Solo cuando nos sentamos a escribir programas para microcontroladores, todos los chips, como el subprocesamiento múltiple de hardware, permanecieron en algún lugar, en computadoras de alto nivel, cuando agregamos un retraso, todo el programa se detiene hasta que el controlador cuenta el número correcto de ciclos. Como no tenemos subprocesos múltiples, tendremos que crearlo.

Difícil de decir que sí fácil de hacer


No inventé una bicicleta, pero tomé la biblioteca ArduinoThread terminada. Aquí puedes leer un poco sobre cómo funciona y descargarlo. Puede descargar la biblioteca desde el propio IDE de Arduino. En resumen, le permite realizar periódicamente una función con un cierto intervalo, mientras que no le permite entrar en un bucle infinito si la ejecución lleva más tiempo que el intervalo. Lo que necesitas Cree otra matriz con hilos para cada pedal:

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

Ahora tenemos 6 hilos virtuales idénticos, pero al mismo tiempo son objetos diferentes.

Reescribamos el ciclo de derivación del pedal para que funcione con la nueva funcionalidad:

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

Ahora el valor 252 en la matriz del programa, que corresponde a "no hacer nada", dará un retraso de 10 milisegundos (aunque en realidad un poco más, ya que la ejecución del código también lleva tiempo). Agregar algunas líneas al intérprete permitirá establecer el retraso en varios de estos "cuantos", gastando solo 2 bytes de la matriz:

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

A diferencia de otros comandos, esta instrucción debe agregarse exactamente al comienzo del intérprete, es decir, inmediatamente después de "while (1) {", ya que el retraso debe procesarse antes de que el intérprete continúe leyendo el programa. La matriz de espera debe declararse de la misma manera que se hizo con puertos, estado, etc. y también restablece sus celdas cuando cambia el modo, para que el retraso no pase a otro programa.

Ahora, con la posibilidad de establecer el retraso en 2.55 segundos, no deberían surgir problemas con la definición de teclas por programas.

Programación sobre la marcha


En principio, aquí sería posible terminar con el código y comenzar a ensamblar el dispositivo, pero en este caso, si alguien de repente quiere reprogramar los pedales, tendrá que abrir el IDE de Arduino, editar el código y volver a cargar el firmware. Naturalmente, esta opción no es la mejor, así que decidí agregar la capacidad de cambiar el programa desde el puerto serie Arduino y almacenar los programas en EEPROM. Para trabajar con memoria no volátil, debe conectar la biblioteca estándar EEPROM.h. El código del modo de programación es el siguiente:

 ... 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")); } }; } ... 

Lo que hace este código se explica por la ayuda que contiene: se ingresa un número de espacio para el número de modo, el número de pedal y un comando, de los cuales hay 3: leer, escribir y ejecutar la eliminación de un programa. Todos los datos en los pedales se almacenan uno tras otro en una secuencia de 33 bytes, es decir, el tipo de pedal y dos programas, y que ocupamos 7 * 4 * 33 = 924 de 1024 bytes de EEPROM. Descarté la opción de usar el tamaño dinámico de los pedales en la memoria, porque en este caso, al reprogramar un pedal, tendrá que sobrescribir casi todas las celdas, y hay un número finito de ciclos de reescritura, por lo que recomendamos hacer esto lo menos posible.

Características del trabajo con EEPROM
También me gustaría llamar la atención sobre las líneas del formulario:
  PORTD = 0b00000010 + (PORTD & 0b11101100); ... PORTD = 0b00000001 + (PORTD & 0b11101100); 

Gracias a esta biblioteca, desde el punto de vista del programador, la memoria no volátil es una matriz de caracteres ordinaria, pero, como "arduino", debemos entender que escribir en ROM es una operación muy difícil, que tarda aproximadamente ~ 3 segundos desde el controlador, y es aconsejable no interrumpir esto proceso Este diseño hace que el diodo brille rojo durante tales operaciones, y luego devuelve el color verde "seguro".

En el modo de grabación de programa, la entrada se realiza directamente por los valores de bytes en el sistema de números decimales con un espacio. Resulta bastante grave, pero no tiene que escribir un analizador complejo. Además, la reprogramación no ocurre con tanta frecuencia, y en estos casos es bastante posible mirar en la tabla ASCII.

Con la preservación de las estructuras resueltas, ahora necesitamos extraer de alguna manera nuestros datos de allí y convertirlos a la vista "pedal":

 ... 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++]; } } } } ... 

Aquí tampoco sucede nada sobrenatural: el controlador lee los datos de la memoria y llena las estructuras existentes con él.

La ventaja de programar a través de UART es que nuevamente no necesitamos controladores especiales, por lo que puede configurar el comportamiento del manipulador incluso desde el teléfono.

Demostración




Código fuente completo


El esta aqui
 #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; } } 


Epílogo


Aunque inicialmente hice una pedalera para la posibilidad de desplazar la grabación mientras tocaba la guitarra, sin embargo, personalmente me pareció conveniente usar los pedales en tareas normales, lo principal es acostumbrarme un poco a un manipulador tan inusual. Y aquí hay otro problema: sin los pedales favoritos, trabajar por el contrario se vuelve más difícil, ya que hay que recordar qué, dónde y por qué presionar. Si los pedales todavía se pueden usar y conectar a la oficina, entonces correr en las aulas del instituto ya es más difícil. Por lo tanto, usar este dispositivo para algo que no sea su propósito original es bajo su propio riesgo y riesgo.

Pedalero montado:

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


All Articles