Pedal no chão: crie outro manipulador de pé para PC


Há apenas um mês, me deparei com este artigo, que fala sobre pedalar o Vim. Um pouco mais tarde, após meu longo estudo de três minutos, descobri que esse tópico não é mais novo e bastante popular. Eu mesmo uso o Vim apenas em caso de emergência (se tiver que trabalhar no console, prefiro o Nano), mas você pode fazer o mesmo para outros aplicativos.

Inicialmente, queria criar um pequeno artigo, mas recebi um tutorial completo sobre como criar este dispositivo com a redação passo a passo do código e uma explicação sobre o que e como. Para não inflar o artigo, nos spoilers haverá várias informações que pareciam interessantes e dignas da atenção dos recém-chegados ao Arduino, usuários avançados e especialmente apressados ​​podem não perder tempo com isso. O código fonte completo também é apresentado no final do artigo.

Por que eu preciso disso?


Se você não tiver dúvidas sobre a necessidade e utilidade deste dispositivo, poderá pular este item. De resto, gostaria de falar primeiro sobre os pré-requisitos para a criação deste dispositivo.

Em todos os momentos, programadores e designers tentam criar uma interface conveniente e amigável para que o usuário possa trabalhar com o aplicativo usando o mouse e o teclado sem problemas desnecessários. Por que precisamos de outro manipulador? Bem, vamos olhar um pouco para a história, ou melhor, no início do século 18, quando um instrumento musical como o piano foi inventado. Como você sabe, essa palavra se traduz literalmente como "alto e silencioso", mas poucas pessoas pensam que um mestre italiano inteligente recebeu esse instrumento ao "esmagar" o cravo que existia na época, o que tornou possível controlar o volume do som até certo ponto, sem tirando as mãos das teclas.

Existem muitos exemplos. O carro tem pedais para não jogar o volante, se você precisar adicionar gasolina. O kit de bateria também possui pedais para bater no bumbo e nos pratos. E o que os pedais podem oferecer ao usar um computador? Bem, por exemplo, você pode definir uma combinação de teclas de atalho ou até mesmo adicionar uma tecla que não existe, como ativar e desativar o som. Os pedais podem ajudar se suas mãos estiverem ocupadas: eu mesmo toco violão e, às vezes, para o acompanhamento, seria muito conveniente rolar a parte de trás sem tentar alcançar constantemente o teclado. E, finalmente, os controladores podem oferecer possibilidades completamente desumanas nos jogos: seria legal construir toda a sua base em uma estratégia com um clique ou destruir inimigos a uma velocidade de uma dúzia de batidas por segundo em atiradores, certo?

Em geral, espero ter convencido você, o que significa que é hora de prosseguir diretamente para o próprio desenvolvimento.

Recursos necessários


  • Na verdade, os pedais. Algumas dificuldades surgiram imediatamente devido ao fato de eu não conseguir pensar em um nome para esse pedal. Eu só sabia que essas coisas são usadas em máquinas de costura. Em geral, a pedido do pedal elétrico, ainda consegui encontrar o que precisava no Aliexpress e, sem pensar duas vezes, pedi 3 peças.
  • Controlador A pedaleira deve emular o teclado e, possivelmente, o mouse para poder se conectar a um PC sem drivers desnecessários. Para isso, a placa Arduino Pro Micro é perfeita e, apesar de não ter algumas conclusões, é a mais compacta possível. Vamos ao mesmo Aliexpress e compramos a versão chinesa desse milagre.
  • Fios. Para colocar 3 pedais embaixo da mesa, você precisa de pelo menos um fio de quatro fios com um comprimento de pelo menos um metro. Penso que aqui não devem surgir problemas.
  • LED RGB e botão. O primeiro é necessário para indicar os modos e o segundo é para trocá-los.
  • Bem, é claro, precisamos de um IDE do Arduino, um ferro de solda e braços retos.

Diagrama do dispositivo


Antes mesmo de as encomendas chegarem, comecei a criar um diagrama de dispositivos. Embora isso seja dito muito, já que eu tive que conectar os pedais, diodo e botão. Aconteceu algo assim:



Para pedais, decidi alocar 4 portas PB1-PB4 de uma só vez, ou seja, duas para a esquerda e duas para a perna direita, embora até agora eu tenha apenas três pedais.Além disso, eles estão todos no mesmo grupo e estão localizados em um só lugar. Sob o LED, peguei as saídas PD0, PD1 e PD4, sob o botão - PD7.
Nesse caso, não precisamos de resistores pull-up, se você usar aqueles que estão embutidos no controlador. É verdade que, quando você pressiona um botão ou pedal, a entrada será baixa e, quando liberada, será alta, ou seja, as prensas serão invertidas e você não deve esquecer isso.

Escrita de código


Essa etapa foi a mais difícil: devido a alguns erros nos ponteiros, eu apaguei o carregador de inicialização várias vezes e, como resultado, quase falhei na placa no nível do software. Abaixo de todos os estágios de criação do firmware, são descritos em detalhes; para aqueles que desejam apenas obter um código funcional, ele estará no final do artigo.

Preparação


Primeiro, precisamos entender o que é o pedal em termos de programa. Decidi tornar possível definir os pedais de um dos dois modos - tempo real e gatilho. Ao mesmo tempo, cada pedal possui dois programas: o primeiro é executado quando o pedal é pressionado em tempo real ou com pressionamentos ímpares no modo de disparo, o segundo é quando o pedal é liberado em tempo real ou quando os pedais são pressionados uniformemente no modo de disparo. O pedal também possui uma porta, um estado e duas variáveis ​​- as posições atuais nos programas 1 e 2. Eu obtive essa estrutura:

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

O Arduino possui um pouco de memória e também possui 8 bits, portanto, é melhor tentar usar char em vez de int sempre que possível.

Também precisamos da biblioteca padrão do teclado para funcionar como um teclado.

Clique em Processamento


Agora precisamos criar um intérprete que leia os dados da matriz e envie-os na forma de pressionamentos de teclas para a máquina, além de selecionar vários valores para vários comandos internos. Abrimos a página com os códigos de chave e vemos o que e como podemos clicar. Eu não me aprofundava e estudava todos os tipos de padrões de teclado, pois as informações aqui me pareciam suficientes para esse projeto. A primeira metade é reservada para caracteres ASCII padrão (embora alguns deles não sejam imprimíveis ou não sejam usados), a segunda metade é para várias teclas modificadoras. Existem até códigos separados para as teclas esquerda e direita, o que é muito agradável, mas eu não vi nenhum código especial para os números do nampad, embora, tanto quanto eu saiba, eles sejam percebidos de uma maneira especial no sistema que os números comuns. Talvez seus códigos estejam em algum lugar nos “buracos”, entre os intervalos, mas agora não é sobre isso. Portanto, o maior código é a chave para cima - 218, o que significa que o intervalo 219-255 pode ser considerado livre, ou, pelo menos, não há chaves 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; } } 

Eu acho que mesmo uma pessoa com o mais alto nível de conhecimento de C não terá perguntas sobre o que está acontecendo aqui. Primeiro, a função seleciona o pedal desejado e determina, dependendo do modo e condição do pedal, qual programa deve ser executado. Ao ler cada elemento da matriz, se não for um caractere de controle, a função Keyboard.write () é chamada, que emula a pressão e a liberação de uma tecla. Os caracteres de controle são processados ​​separadamente e são necessários para fixar as combinações de teclas e navegar no programa.

Alguns recursos do modo teclado
Keyboard.write () tem algumas nuances simples, mas não óbvias para iniciantes, com base no fato de enviarmos dados não na forma bruta, mas como pressionamentos de tecla. Em primeiro lugar, estranhamente, sem drivers adicionais, o computador pode aceitar apenas caracteres do teclado que estão no teclado, o que significa que não poderemos enviar 0x03 (sinal de interrupção) ou 0x1B (início da sequência ESCAPE). Em segundo lugar, podemos ajustar as letras maiúsculas como estão na tabela ASCII, mas a máquina obterá a combinação de teclas Shift + <letra minúscula>. Isso pode se tornar um problema se o CapsLock estiver ativado e "inesperadamente" recebermos letras pequenas em vez de letras grandes e vice-versa. Em terceiro lugar, não podemos usar o idioma russo, nem em qualquer outro idioma. Isso acontece novamente devido a coisas irritantes como códigos de chave . Embora Keyboard.write () aceite isso como argumento, o código correspondente à tecla na qual ele está no layout padrão em inglês ainda é enviado via USB, e se tentarmos enviar o alfabeto cirílico, não saberemos o que. Portanto, se quisermos dizer olá aos nossos amigos que falam russo através do Arduino, no código, precisamos escrever "Ghbdtn" e enviá-lo depois de selecionar o layout em russo. Essa "saudação" funcionará no layout ucraniano, mas em búlgaro, apesar de haver também um alfabeto cirílico, nada resultará disso, pois as letras estão em lugares completamente diferentes. (Uma vez ouvi a opinião de que, para muitos desenvolvedores americanos e ingleses, é incompreensível que alguém possa precisar usar vários layouts, mas também trocá-los.)

Portanto, temos um intérprete e um entendimento aproximado de como nossa pedaleira interage com um computador. Agora precisamos levar tudo isso ao estado de firmware completo e verificar o desempenho em um pedal. Se você criar uma instância do pedal e chamar ciclicamente pedalAction (), em teoria, executaremos o programa especificado na estrutura.

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

A propósito, nunca esqueça os terminadores nulos nesses "programas" se o tamanho for menor que o tamanho da matriz e se não forem cíclicos, porque o Arduino não apenas tentará interpretar os dados que não estão definidos, mas também os enviará para a máquina com grande velocidade, e é o mesmo que dar um teclado a um macaco.

Um pedal é bom e dois é melhor


Agora é hora de lidar com o processamento de sinais de vários pedais, além de adicionar modos de comutação. No início do artigo, foram alocadas 4 portas para pedais, cada uma das quais deve poder trabalhar em sete modos. Por que 7? Porque, sem usar o PWM, nosso LED pode fornecer apenas 7 cores e a oitava desligada. Essa quantidade é suficiente para o usuário médio, mas em casos extremos pode ser facilmente aumentada. Portanto, armazenaremos os pedais em uma matriz bidimensional de 7 x 4. Para não obstruir a memória, valores comuns a várias estruturas, como o número da porta, podem ser obtidos em matrizes separadas. Como resultado, temos algo parecido com isto:

 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; //   

A magia do número 255
Você provavelmente notou que, no artigo, o número 255 costuma aparecer, onde seria mais lógico colocar 0. Olhando para o futuro, direi que isso é necessário para a conveniência de salvar pedais na EEPROM, já que na fábrica cada uma de suas células não contém 0, mas apenas 255, o que significa que esse número será muito mais conveniente para indicar variáveis ​​indefinidas que 0, para que você não substitua a memória toda vez.

É importante que conheçamos apenas o tipo de pedal e dois programas, para que os deixemos apenas diretamente na estrutura, deixando a automação fazer o resto. Os métodos de preparação e loop agora terão a seguinte aparência:

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

O controlador considerará o modo não utilizado se não for declarado um único pedal (modo = 255), o que significa que, quando o tocar, passará imediatamente para o próximo, mas o primeiro modo sempre existirá. Ao alternar o modo, todos os valores nas matrizes são anulados, pois não precisamos salvá-los para cada modo (certo?). E o loop ignora todos os pedais e chama pedalAction para eles.

Além disso, no início do método pedalAction (), você precisa adicionar a seguinte linha para que ele entenda com qual das estruturas lidar:

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

A estrutura existente do pedal1 pode ser removida como desnecessária.

Tudo isso também funciona muito bem, no entanto, encontrei um problema: alguns programas não têm tempo para receber cliques na velocidade com que o Arduino os envia. A solução mais óbvia é adicionar a capacidade de definir atrasos entre as ações, quando necessário. É somente quando nos sentamos para escrever programas para microcontroladores que todos os chips, como multithreading de hardware, permanecem em algum lugar lá, em computadores de alto nível, quando adicionamos um atraso, todo o programa para até que o controlador conte o número certo de ciclos. Como não temos multithreading, teremos que criá-lo.

Difícil dizer sim fácil de fazer


Eu não inventei uma bicicleta, mas peguei a biblioteca ArduinoThread pronta. Aqui você pode ler um pouco sobre como funciona e fazer o download. Você pode fazer o download da biblioteca a partir do próprio IDE do Arduino. Em resumo, permite executar periodicamente uma função com um determinado intervalo, enquanto não permite entrar em um loop infinito se a execução demorar mais que o intervalo. O que você precisa Crie outra matriz com threads para cada pedal:

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

Agora, temos 6 threads virtuais idênticos, mas ao mesmo tempo são objetos diferentes.

Vamos reescrever o ciclo de desvio do pedal para trabalhar com a nova funcionalidade:

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

Agora, o valor 252 na matriz do programa, que corresponde a "não fazer nada", fornecerá um atraso de 10 milissegundos (embora na verdade um pouco mais, pois a execução do código também leva tempo). A adição de algumas linhas ao intérprete possibilitará definir o atraso em vários desses "quanta", gastando apenas 2 bytes da matriz:

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

Diferentemente de outros comandos, esta instrução deve ser adicionada exatamente no início do intérprete, ou seja, imediatamente após "while (1) {", pois o atraso deve ser processado antes que o intérprete continue a ler o programa. A matriz de espera precisa ser declarada da mesma maneira que foi feita com portas, estado etc. e também redefina suas células ao alternar o modo, para que o atraso não vá para outro programa.

Agora, com a possibilidade de definir um atraso de até 2,55 segundos, não devem surgir problemas com a definição de chaves pelos programas.

Programação on-the-go


Em princípio, aqui seria possível terminar o código e começar a montar o dispositivo, mas, neste caso, se alguém de repente reprogramar os pedais, ele terá que abrir o Arduino IDE, editar o código e baixar o firmware novamente. Naturalmente, essa opção não é a melhor, então decidi adicionar a capacidade de alterar o programa da porta serial do Arduino e armazenar os programas na EEPROM. Para trabalhar com memória não volátil, você deve conectar a biblioteca padrão EEPROM.h. O código do modo de programação é o seguinte:

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

O que esse código faz é explicado pela ajuda contida nele: um número de espaço é inserido para o número do modo, número do pedal e um comando, dos quais existem 3 - leitura, gravação e execução de uma exclusão de programa. Todos os dados nos pedais são armazenados um após o outro em uma sequência de 33 bytes, ou seja, o tipo de pedal e dois programas, e ocupamos 7 * 4 * 33 = 924 de 1024 bytes de EEPROM. Joguei fora a opção de usar o tamanho dinâmico dos pedais na memória, porque, neste caso, ao reprogramar um pedal, você precisará substituir quase todas as células e há um número finito de ciclos de reescrita, por isso recomendamos fazer o mínimo possível.

Recursos de trabalho com EEPROM
Gostaria também de chamar a atenção para as linhas do formulário:
  PORTD = 0b00000010 + (PORTD & 0b11101100); ... PORTD = 0b00000001 + (PORTD & 0b11101100); 

Graças a esta biblioteca, do ponto de vista do programador, a memória não volátil é uma matriz de caracteres comum, mas, como "arduino", precisamos entender que gravar na ROM é uma operação muito difícil, que leva até ~ 3 segundos do controlador e é recomendável não interromper isso. processo. Esse design faz o diodo brilhar em vermelho durante essas operações e, em seguida, retorna a cor verde "segura".

No modo de gravação de programa, a entrada é feita diretamente pelos valores de bytes no sistema de números decimais com um espaço. Acontece com muita gravidade, mas você não precisa escrever um analisador complexo. Além disso, a reprogramação não ocorre com tanta frequência e, nesses casos, é bem possível examinar a tabela ASCII.

Com a preservação das estruturas resolvidas, agora precisamos, de alguma forma, extrair nossos dados de lá e convertê-los na visualização "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++]; } } } } ... 

Nada sobrenatural também acontece aqui: o controlador lê os dados da memória e preenche as estruturas existentes.

A vantagem da programação através do UART é que, novamente, não precisamos de drivers especiais, para que você possa definir o comportamento do manipulador, mesmo a partir do telefone.

Demonstração




Código fonte completo


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


Posfácio


Embora inicialmente eu pedi uma pedaleira para a possibilidade de rolar a gravação enquanto tocava guitarra, no entanto, pessoalmente achei conveniente usar os pedais em tarefas comuns, o principal é me acostumar com um manipulador tão incomum. E aqui está outro problema: já sem seus pedais favoritos, trabalhar ao contrário se torna mais difícil, pois você precisa se lembrar do que, onde e por que pressionar. Se os pedais ainda podem ser usados ​​e conectados ao escritório, então no instituto é mais difícil andar com eles nas salas de aula. Portanto, usar este dispositivo para algo que não seja seu objetivo original é por sua conta e risco.

Pedal montado:

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


All Articles