Help with a simple macro keyboard

Hello all! I'm new to arduino and coding in general and for my first project I decided to make a simple one button macro keyboard that runs a loop indefinitely until the button is pressed again, with the objective of controlling a character in an online videogame. The code mostly works, but eventually key presses start going out of order which messes up the entire sequence requiring me to stop and start the macro.

The macro is supposed to press "1" three times, hold alt and press "q", "w", "e", release alt, press "c", press enter (RETURN), release enter and loop until the button is pressed again. If you guys have any idea why the key presses start getting out of order after a while it would be much appreciated.

#define pinBotao 9

#include <Keyboard.h>

boolean botaoAtu = true;
boolean botaoAnt = true;
boolean estadoMacro = false;

static unsigned long intervaloMacro = 0;

void setup() {
  // put your setup code here, to run once:

  pinMode(pinBotao, INPUT_PULLUP);

  Keyboard.begin();
  
  } 
void loop() {
  // put your main code here, to run repeatedly:
  
  botaoAtu = digitalRead(pinBotao);
  delay(1);
  if (!botaoAtu && botaoAnt){
    Keyboard.releaseAll();
    intervaloMacro = millis();
    estadoMacro = !estadoMacro;
  }
  botaoAnt = botaoAtu;

  if (estadoMacro){
    
    iniciodamacro:
    if ((millis() - intervaloMacro) == 1000){
      Keyboard.write('1');
      delay(1);
    }
    if ((millis() - intervaloMacro) == 2000){
      Keyboard.write('1');
      delay(1);
    }
    if ((millis() - intervaloMacro) == 3000){
       Keyboard.write('1');
       delay(1);
    }
    if ((millis() - intervaloMacro) == 3300){
       Keyboard.press(130);
             delay(1);
    }
      if ((millis() - intervaloMacro) == 3600){ 
       Keyboard.write('q');
       delay(1);
    }
       if ((millis() - intervaloMacro) == 4300){
       Keyboard.write('w');
       delay(1);
       }
       if ((millis() - intervaloMacro) == 4600){
       Keyboard.write('e');
       delay(1);
       }
       if ((millis() - intervaloMacro) == 4900) {
       Keyboard.release(130);
       delay(1);
       }
       if ((millis() - intervaloMacro) == 5400){
       Keyboard.write('c');
       delay(1);
       }
       if ((millis() - intervaloMacro) == 5800){
       Keyboard.press(176); //código ascii é sem aspa
       delay(1);
       }
       if ((millis() - intervaloMacro) == 6100){
       Keyboard.release(176);
       delay(1);
       }
       if ((millis() - intervaloMacro) >= 6400){
       intervaloMacro = millis();
       delay(1);
       goto iniciodamacro;
    }
    

 
 }
  

}
1 Like

millis() does not step through all values, so you should change the == to >=.
You will need a couple of else too. :grinning:

There is no need to use goto.

I doubt that the delay(1); do any good besides hindering your loop flow.

I used == to make sure that commands run in order, if I use >= the macro won't leave the first step since >= 1000 makes it loop. Can you give an example of how I can solve this with else?

Also I added delay(1) because I only want every step to run once, since I used == the code makes it run once and wait for the next millisecond when the condition won't be true anymore so it goes to the next command. Is there a better way to do this?

The structure with if / else and fixed timings relative to a common start is rather inflexible.

A quite simple state machine would work better.

StateMachine

1 Like

I didn't finish my introduction tutorials yet but I'll look into that, thanks. In the meanwhile can you give an insight into why the macro is messing up? If I didn't make any mistakes in the logic my guess is that the latency in the communication between Arduino and PC is messing up an instruction running on such a small time frame.

In the initial version, because millis() sometimes skips.

It could be done like in this example:

class aKeyboard {
  public:
  void begin() {}
  void write(uint8_t what) {
    Serial.print(millis()+100000L);
    Serial.print(F(" write '"));
    Serial.write(what);
    Serial.println(F("'"));
  }
  void press(uint8_t what) {
    Serial.print(millis()+100000L);
    Serial.print(F(" press "));
    Serial.println(what);
  }
  void release(uint8_t what) {
    Serial.print(millis()+100000L);
    Serial.print(F(" release "));
    Serial.println(what);
  }
  void releaseAll() {
    Serial.print(millis()+100000L);
    Serial.println(F(" releaseAll"));
  }
} Keyboard;

enum mTypes {
  done,
  write,
  press,
  release,
  relAll,
  wait
};

class Cmd {
  public:
  uint8_t type;
  uint8_t parm; // write/press/release value, wait 100ms units
  Cmd(uint8_t iT, uint8_t iP) : type(iT), parm(iP) {}
} example[] = {
  { wait, 10},
  { write, '1'},
  { wait, 10},
  { write, '1'},
  { wait, 10},
  { write, '1'},
  { wait, 3},
  { press, 130},
  { wait, 3},
  { write, 'q'},
  { wait, 7},
  { write, 'w'},
  { wait, 3},
  { write, 'e'},
  { wait, 3},
  { release, 130},
  { wait, 5},
  { write, 'c'},
  { wait, 4},
  { press, 176},
  { wait, 3},
  { release, 176},
  { wait, 3},
  { done, 0 }
};

unsigned long lastTime = 0;
bool macroActive = false;
bool waiting = false;
Cmd* current = nullptr;

void execMacro(Cmd* what) {
  current = what;
  waiting = false;
  macroActive = true;
}

void setup() {
  Serial.begin(115200);
  Keyboard.begin();
  execMacro(example);
} 

void loop() {
    if (macroActive) {
      if (waiting) {
        if (millis() - lastTime >= current->parm*100) {
          waiting = false;
          current++;
        }
      } else {
        switch (current->type) {
          case done:
            Serial.print(millis()+100000L);
            Serial.println(F(" done"));
            macroActive = false;
            break;
          case write:
            Keyboard.write(current->parm);
            current++;
            break;
          case press:
            Keyboard.press(current->parm);
            current++;
            break;
          case release:
            Keyboard.release(current->parm);
            current++;
            break;
          case relAll:
            Keyboard.releaseAll();
            current++;
            break;
          case wait:
            lastTime = millis();
            waiting = true;
            break;
        }
      }
    }
}

Giving an output of

101000 write '1'
102000 write '1'
103000 write '1'
103300 press 130
103600 write 'q'
104300 write 'w'
104600 write 'e'
104900 release 130
105400 write 'c'
105800 press 176
106100 release 176
106400 done
1 Like
#define pinBotao 9

#include <Keyboard.h>

boolean botaoAtu = true;
boolean botaoAnt = true;
boolean estadoMacro = false;

static unsigned long intervaloMacro = 0;

void setup()
{
  // put your setup code here, to run once:

  pinMode(pinBotao, INPUT_PULLUP);

  Keyboard.begin();

}
void loop()
{
  // put your main code here, to run repeatedly:

  botaoAtu = digitalRead(pinBotao);
  delay(1);
  if (!botaoAtu && botaoAnt)
  {
    Keyboard.releaseAll();
    intervaloMacro = millis();
    estadoMacro = !estadoMacro;
  }
  botaoAnt = botaoAtu;

  if (estadoMacro)
    Sequence();
}

void Sequence()
{
  delay(1000);
  Keyboard.write('1');
  delay(1000);  // Now at 2000
  Keyboard.write('1');
  delay(1000);  // Now at 3000
  Keyboard.write('1');
  delay(300);  // Now at 3300
  Keyboard.press(130);
  delay(300);  // Now at 3600)
  Keyboard.write('q');
  delay(700);  // Now at 4300)
  Keyboard.write('w');
  delay(300);  // Now at 4600)
  Keyboard.write('e');
  delay(300);  // Now at 4900)
  Keyboard.release(130);
  delay(500);  // Now at 5400)
  Keyboard.write('c');
  delay(400);  // Now at 5800)
  Keyboard.press(176); //código ascii é sem aspa
  delay(300);  // Now at 6100
  Keyboard.release(176);
  delay(300);  // Now at 6400
}
1 Like

Thanks for the help. I got one more question. Is it possible to combine the functions:

void funcMacro(int macroKey, int timelapse1, int timelapse2) {
      if (gap >= timelapse1 && gap <= timelapse2){
      Keyboard.write(macroKey);
  }

and

void funcMacro(int macroKey, int timelapse1, int timelapse2) {
      if (gap >= timelapse1 && gap <= timelapse2){
      Keyboard.press(macroKey);
  }

into one function?

void funcMacro(int macroKey, int timelapse1, int timelapse2, bool doWriteNotPress) {
  if (gap >= timelapse1 && gap <= timelapse2){
    if (doWriteNotPress) {
        Keyboard.write(macroKey);
    } else {
        Keyboard.press(macroKey);
    }
  }
}
1 Like

Thanks!

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.