Sliding game opener project

A valiant effort.

I should have left more comment-like comments as clues. I'm not in my lab, so add a few grains of sand to what I write here, it is all untested.

This section was

  if (keyEvent)
    Serial.println("         ignored red button!");
    
  keyEvent = false;  // unless the button got pressed:

  getSignal();

The only thing you should do with an event flag before the code to see if it should be raised (made true) executes is to check if it is still up from last time you looped - this would indicate an event about to be left unhandled.

A plausible duplication for blue tooth would be

// check for stale unhandled events 
  if (keyEvent)
    Serial.println("          ignored red button!");

  if (btEvent)
    Serial.println("          ignored blue button!");
    
  keyEvent = false;  // unless the button gets pressed:
  btEvent = false;  // unless the button gets pressed:

// see if buttons got pressed
  getSignal();

Never Try not to mess with the state variables except from within the state machine.

Somewhere in your machine is (are) the state(s) you will be in when you expect to want to react to a blue toothed event.

That is where you should do, and at that exact point lower (clear (set to flase)) the blue tooth event flag, recording the fact that you handled it. No annoying reminder will issue.


Now you have also duplicate the code for managing the state change detection. It works, but only because you can't press both buttons at once. It would be better not to count on that, even if you promise it will never happen, as coding against it is easy.

You just have to be slavish, and not so clever as to intermingle the handling the two signals.

  lastKeyPad = keyPad;
  if (keyPad && !lastKeyPad) {
    if (keyPad){
      Serial.println("I see you pressed the red button.");

      keyEvent = true;
    }
  }
  lastKeyPad = keyPad;

  lastBtSignal = btSignal;
  if (btSignal && !lastBtSignal) {
    if (btSignal) {
      Serial.println("I see you pressed the blue button.");

      blueEvent = true;
    }
  }
  lastBtSignal = btSignal;

Here's where we usually point out that when you find yourself writing code by means of copy/paste/editing some code you already have, you should ask whether a function might make life easier. For two buttons, it's a toss-up. If you had six buttons, using those half-dozen lines to do the job would be a nightmare to get right, as well as to modify later.

Look at you state machine and decide where to handle blue tooth events, and what handling them will consist in at each point where you want something to happen because. And clear the flag.

Does you blue tooth device deliver a simple digital logic level to you as your code suggests? If you need to do more to develop the logical btSignal, I hope it is obvious to you that the input section is where to completely take care of that. In no other way should you needa change anything you've written or will write using the blue button proxy.

HTH

a7

I'll be able to dive into this a bit more tomorrow, been a long day.

Yeah it sends a simple digital signal. So it should be easy to change the button for the dabble getpinState() function call.

As I'm going through this, I am thinking I want the second signal from either source to stop the gate, then a third would resume the previous action.

  case WAITING_OPEN:
    if (now - startTime >= gateOpenDuration) {
      Serial.println("time's up! Closing gate.");
      gateState = CLOSING;
    }
    if (blueEvent || keyEvent) {
      Serial.println("Command received gate holding.");
      prevGateState = gateState;
      gateState = HOLDING_OPEN;
    }

    break;
 
  case HOLDING_OPEN:
    motors = OFF;
    if (blueEvent || keyEvent) {
      Serial.println("Command received gate resuming.");
      gateState = prevGateState;
    }

    break;

So far I've just modded the WAITING_OPEN state, and this seems to work. I am able to press either button to get it to start, stop, then resume. Is this a reasonable way to accomplish this? If so I'll copy the if events statement to the other places I want to be able to pause the gate.

so gateState and prevGateState are only used in the FSM function, should they be declared in the function rather than globally? tested this, they are used somewhere else. :face_with_spiral_eyes:

There is no roll for prevGateState, at least not that I see and there shoukd not be a need to ever know the previous state of the machine. If there was, it would just mean that your state machine does not adequately describe the process. inputs + current state :arrow_right: new state + outputs.

But yes, the machine state is global and is used to effect. As I commented earlier, this is undesirable, but changing it means a mess of a different kind, so with little machines in the context of a few hundred lines of code, I am not object to such minor uses as make things easier.

So I can't say never here, but I will advise you to seek first alternatives that leave all messing with the state inside the state machine function.

At a glance, it seems like you have caught the general idea of the flagged events and how/whether to handle them in your state cases code.

a7

I used prevGateStates so I could implement the pause and resume functionality. I'll try and rethink how to do it.

I am trying to test on real hardware using buttons and leds, but it's gets stuck in a boot loop :confused:

This seems to be the killer line...
pinMode(keyPadPin, INPUT_PULLUP);

If your real life experience is different to your wokwi results, you can usually 99.97 percent of the time focus on real life issues not software.

So now it is a matter of the actual code you are running IRL, and a schematic of what all you have connected to what, with particular attention to your sources of power and how power is routed to the parts that need it.

You my prefer to work with prevGateState and gateState. I tend to being a purist, so I would not.

So in that case, the actual state is prevGateState plus gateState, for it is that combined state that you are using to decide things in your case code sections.

Not plus like addition, but plus like the two variables seen right next to each other.

So your gateState state names are less meaningful, as they do not tell the hole story.

Again, in a sketch of a few hundred lines it may not make any different.

a7

I've got my hardware assembled, except the keypad, since I need to install the opener to the gate before getting that hooked up and in place, so I'm testing on real hardware now and running into some weird issues.

It doesn't seem to ever go to the closing step. I've got my steam deck hooked up to it, since I don't have a laptop, so I can get the serial output.

 in FSM, state is CLOSED
making the motor(s) opening the gate
gate is (still) opening
GATE OPEN
stop the motor(s)
motors off
 in FSM, state is OPEN
 in FSM, state is WAITING_OPEN
time's up! Closing gate.
 in FSM, state is OPENING

From the output everything seems to go well but after it says closing gate it moves to opening.

Here is the current state of the FSM code:


void gateFSM() {
  static gateStates lastReport;
  if (gateState != lastReport) {
    Serial.print(" in FSM, state is ");
    Serial.println(stateNames[gateState]);
    lastReport = gateState;
  }

  switch (gateState) {
    case UNKNOWN:
      if (keyEvent) {
        gateState = CLOSING;

        keyEvent = false;
      }

      if (blueEvent) {
        gateState = CLOSING;

        blueEvent = false;
      }

      break;

    case CLOSED:
      motors = OFF;
      if (keyEvent) {
        gateState = OPENING;

        keyEvent = false;  // handled
      }

      if (blueEvent) {
        gateState = OPENING;

        blueEvent = false;  // handled
      }

      break;

    case OPENING:
      motors = TO_OPEN;
      if (gateOpen) {
        Serial.println("GATE OPEN");
        motors = OFF;
        gateState = OPEN;
      }
      if (openProximity) {
        Serial.println("OBJECT FOUND WHILE OPENING");
        gateState = JAMMED_OPENING;
      }
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate holding.");
        gateState = HOLDING_OPEN;
      }

      break;

    case JAMMED_OPENING:
      motors = OFF;
      alarm();
      if (!openProximity)  // <- should we wait for clearance or just
        gateState = OPENING;

      break;

    case OPEN:
      motors = OFF;
      startTime = now;
      gateState = WAITING_OPEN;

      break;

    case WAITING_OPEN:
      if (now - startTime >= gateOpenDuration) {
        Serial.println("time's up! Closing gate.");
        gateState = CLOSING;
      }
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate holding.");
        gateState = HOLDING_OPEN;
      }

      break;

    case HOLDING_OPEN:
      motors = OFF;
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate resuming.");
        gateState = OPENING;
      }

      break;

    case CLOSING:
      motors = TO_CLOSE;
      if (gateClosed) {
        Serial.println("GATE CLOSED");
        motors = OFF;
        gateState = CLOSED;
      }
      if (closeProximity) {
        Serial.println("OBJECT FOUND WHILE CLOSING");
        gateState = JAMMED_CLOSING;
      }
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate holding.");
        gateState = HOLDING_CLOSE;
      }

    case HOLDING_CLOSE:
      motors = OFF;
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate resuming.");
        gateState = CLOSING;
      }

      break;

      break;

    case JAMMED_CLOSING:
      motors = OFF;
      alarm();
      if (!closeProximity)  // <- should we wait for clearance or just
        gateState = CLOSING;

      break;
  }
}

Here is the full code:


// https://wokwi.com/projects/378031652666068993
// https://forum.arduino.cc/t/sliding-game-opener-project/1143078
#include "pitches.h"

const int gateOpenPin = 2;            // GPIO2 for gate open
const int gateClosePin = 15;          // GPIO3 for gate close
const int openProximityPin = 17;      // GPIO4 for open proximity sensor
const int closeProximityPin = 4;     // GPIO5 for close proximity sensor
const uint8_t motorControlPinR = 27;  // GPIO6 for right motor control (PWM)
const uint8_t motorControlPinL = 14;  // GPIO7 for left motor control (PWM)
const int keyPadPin = 21;              // GPIO8 for keypad signal
const int speakerPin = 22;

//Varibales for checking pin states
int gateOpen;
int gateClosed;
int openProximity;
int closeProximity;
int keyPad;
int btSignal;

bool keyEvent;   // set on red button press
bool blueEvent;  // set on blue button press

// notes in the melody:

int melody[] = {
  NOTE_C4, NOTE_G3, NOTE_A3, NOTE_B3
};

// Variables used for motor speed control
const int maxMotorSpeed = 128;  // 50% PWM speed (0-255)
int currentMotorSpeed = 128;
int openChannel = 1;
int closeChannel = 2;

enum motorStates { TO_OPEN,
                   OFF,
                   TO_CLOSE,
                   NONE };  // should we be closing, off or opening with da motor(s)
unsigned char motors;       // the output variable from the P phase

// Variables used for timing

unsigned long now;
unsigned long startTime = 0;
const unsigned long gateOpenDuration = 5000;  // 45 seconds someday

//Variables for gate states
enum gateStates {
  OPEN,
  WAITING_OPEN,
  HOLDING_OPEN,
  CLOSED,
  OPENING,
  CLOSING,
  HOLDING_CLOSE,
  JAMMED_OPENING,
  JAMMED_CLOSING,
  UNKNOWN,
  NO_STATE,
} state;

#define IDNAME(name) #name
const char* stateNames[] = {
  IDNAME(OPEN),
  IDNAME(WAITING_OPEN),
  IDNAME(HOLDING_OPEN),
  IDNAME(HOLDING_CLOSE),
  IDNAME(CLOSED),
  IDNAME(OPENING),
  IDNAME(CLOSING),
  IDNAME(JAMMED_OPENING),
  IDNAME(JAMMED_CLOSING),
  IDNAME(UNKNOWN),
  IDNAME(NO_STATE),
};

enum gateStates gateState;

#define CUSTOM_SETTINGS
#define INCLUDE_LEDCONTROL_MODULE
#include <DabbleESP32.h>

void setup() {
  // Initialize serial communication
  Serial.begin(115200);
  Dabble.begin("Gate");

  // Initialize pins
  pinMode(gateOpenPin, INPUT_PULLUP);
  pinMode(gateClosePin, INPUT_PULLUP);
  pinMode(openProximityPin, INPUT_PULLUP);
  pinMode(closeProximityPin, INPUT_PULLUP);
  pinMode(keyPadPin, INPUT_PULLUP);
  ledcAttachPin(motorControlPinL, closeChannel);
  ledcAttachPin(motorControlPinR, openChannel);
  ledcSetup(closeChannel, 1000, 8);
  ledcSetup(openChannel, 1000, 8);
  ledcWrite(closeChannel, 0);

  //Get the current state of the various sensors
  keyPad = digitalRead(keyPadPin);
  gateOpen = digitalRead(gateOpenPin);
  gateClosed = digitalRead(gateClosePin);
  openProximity = digitalRead(openProximityPin);
  closeProximity = digitalRead(closeProximityPin);
  Serial.print("gateOpen status: ");
  Serial.println(gateOpen);
  Serial.print("gateClosed status: ");
  Serial.println(gateClosed);

  // determine the initial gate position
  if (gateOpen == LOW && gateClosed == LOW) {
    gateState = UNKNOWN;
    Serial.println("GATE POSITION UNKNOWN");
    gateState = CLOSING;
  }

  if (gateOpen == HIGH && gateClosed == HIGH) {
    gateState = UNKNOWN;
    Serial.println("GATE POSITION UNKNOWN");
    gateState = CLOSING;
  }

  if (gateOpen == HIGH && gateClosed == LOW) {
    Serial.println("GATE OPEN CHECK");
    gateState = OPENING;
  }

  if (gateOpen == LOW && gateClosed == HIGH) {
    Serial.println("GATE CLOSED CHECK");
    gateState = CLOSING;
  }
}

// compensate for active LOW or active HIGH sensor signals
#define GATE_SENSE LOW
#define PROX_SENSE LOW
#define BUTTON_SENSE LOW

void loop() {
  static unsigned long lastTime;
  Dabble.processInput();

  now = millis();


  /* Get the current state of the various sensors
  
  keyPad true if button is being pressed
  gateOpen true if the gate open limit has been reached
  gateClose true if the gate close limit has been reached 
  openProximity true if something is blockage the opening movement
  closeProximity true if something is blockage the closing movement
*/

  // INPUT

  keyPad = digitalRead(keyPadPin) == BUTTON_SENSE;
  btSignal = LedControl.getpinState()/* == BUTTON_SENSE*/;
  gateOpen = digitalRead(gateOpenPin) == GATE_SENSE;
  gateClosed = digitalRead(gateClosePin) == GATE_SENSE;
  openProximity = digitalRead(openProximityPin) == PROX_SENSE;
  closeProximity = digitalRead(closeProximityPin) == PROX_SENSE;

  // PROCESS - keypad
  if (keyEvent)
    Serial.println("         ignored red button!");

  if (blueEvent)
    Serial.println("Blue signal received holding gate");

  keyEvent = false;   // unless the button got pressed:
  blueEvent = false;  // unless the button got pressed:

  getSignal();

  // PROCESS limit and jam switches
  static unsigned char lastMotors = NONE;
  motors = OFF;  // unless told otherwise by the process phase
  gateFSM();     // process the inputs, develop motors control variable

  // OUTPUT - set the motor(s) to the task
  //  Serial.println("now set the motor(s) opening, closing or off");
  // if you need to, probably really should always in real life but
  // we can cut the spam to printing only when a change is made

  if (motors != lastMotors) {
    lastMotors = motors;

    if (motors == TO_OPEN) {
      Serial.println("making the motor(s) opening the gate");
      motorControlO();
    }

    if (motors == TO_CLOSE) {
      Serial.println("making the motor(s) closing the gate");
      motorControlC();
    }

    if (motors == OFF) {
      Serial.println("stop the motor(s)");
      motorsOff();
    }
  }

  delay(50);  // poor man's loop throttle, debounce and spam mitigation
}

void getSignal() {
  static bool lastKeyPad;
  static bool lastBtSignal;
  if ((keyPad && !lastKeyPad) || (btSignal && !lastBtSignal)) {
    if (keyPad) {
      Serial.println("I see you pressed the red button.");

      keyEvent = true;
    } else if (btSignal) {
      Serial.println("I see you pressed the blue button.");

      blueEvent = true;
    }
  }

  lastKeyPad = keyPad;
  lastBtSignal = btSignal;
}

void gateFSM() {
  static gateStates lastReport;
  if (gateState != lastReport) {
    Serial.print(" in FSM, state is ");
    Serial.println(stateNames[gateState]);
    lastReport = gateState;
  }

  switch (gateState) {
    case UNKNOWN:
      if (keyEvent) {
        gateState = CLOSING;

        keyEvent = false;
      }

      if (blueEvent) {
        gateState = CLOSING;

        blueEvent = false;
      }

      break;

    case CLOSED:
      motors = OFF;
      if (keyEvent) {
        gateState = OPENING;

        keyEvent = false;  // handled
      }

      if (blueEvent) {
        gateState = OPENING;

        blueEvent = false;  // handled
      }

      break;

    case OPENING:
      motors = TO_OPEN;
      if (gateOpen) {
        Serial.println("GATE OPEN");
        motors = OFF;
        gateState = OPEN;
      }
      if (openProximity) {
        Serial.println("OBJECT FOUND WHILE OPENING");
        gateState = JAMMED_OPENING;
      }
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate holding.");
        gateState = HOLDING_OPEN;
      }

      break;

    case JAMMED_OPENING:
      motors = OFF;
      alarm();
      if (!openProximity)  // <- should we wait for clearance or just
        gateState = OPENING;

      break;

    case OPEN:
      motors = OFF;
      startTime = now;
      gateState = WAITING_OPEN;

      break;

    case WAITING_OPEN:
      if (now - startTime >= gateOpenDuration) {
        Serial.println("time's up! Closing gate.");
        gateState = CLOSING;
      }
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate holding.");
        gateState = HOLDING_OPEN;
      }

      break;

    case HOLDING_OPEN:
      motors = OFF;
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate resuming.");
        gateState = OPENING;
      }

      break;

    case CLOSING:
      motors = TO_CLOSE;
      if (gateClosed) {
        Serial.println("GATE CLOSED");
        motors = OFF;
        gateState = CLOSED;
      }
      if (closeProximity) {
        Serial.println("OBJECT FOUND WHILE CLOSING");
        gateState = JAMMED_CLOSING;
      }
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate holding.");
        gateState = HOLDING_CLOSE;
      }

    case HOLDING_CLOSE:
      motors = OFF;
      if (blueEvent || keyEvent) {
        Serial.println("Command received gate resuming.");
        gateState = CLOSING;
      }

      break;

      break;

    case JAMMED_CLOSING:
      motors = OFF;
      alarm();
      if (!closeProximity)  // <- should we wait for clearance or just
        gateState = CLOSING;

      break;
  }
}

void motorControlC() {
  Serial.println("gate is (still) closing");

  ledcWrite(openChannel, 0);
  ledcWrite(closeChannel, currentMotorSpeed);
}

void motorControlO() {
  Serial.println("gate is (still) opening");

  ledcWrite(closeChannel, 0);
  ledcWrite(openChannel, currentMotorSpeed);
}

void motorsOff() {
  Serial.println("motors off");

  ledcWrite(openChannel, 0);
  ledcWrite(closeChannel, 0);
}

void alarm() {
  int currentNote = random(1,4);
  tone(speakerPin, melody[currentNote], 1000/4);
}

Thanks for any help.

I can't right now, you may know why. :expressionless:

In the meantime please say if the code you just posted works one way on the simulator and differently in real life.

If the code is different, I would stay in the simulator until the newer version worked perfectly there.

If the code is the same... you have a wiring or hardware issue almost certainly.

Please post a schematic. Make sure to show where power comes from and how it gets to the parts that need it.

Please post a diagram or pictures detailing your physival circumstances.

a7

From post #7... I made a wokwi diagram.json label maker...

// TEXT LABEL maker for wokwi.com daigram.json

// Edit the "TEXT" below.  Copy/paste serial output into diagram.json. MIND THE COMMA OR LACK OF COMMA
char textLabel[] = "TEXT"; // A number will be appended to this text as well as the "txt" identifier.
int textIndex = 1;

void setup() {
  Serial.begin(115200);
  printText(textLabel, textIndex);
}

void loop() {}

void printText(char text[], int num) {
  int index = 3;
  for (int i = 0; i < num; i++) {
  Serial.println(); // moves text down one row for copying
  Serial.print("    {");
  Serial.print(" \"type\": \"wokwi-text\",");
  Serial.print(" \"id\": \"txt");
  Serial.print(i);
  Serial.print("\",");
  Serial.print(" \"top\": ");
  Serial.print(100 + i * 20);
  Serial.print(",");
  Serial.print(" \"left\": ");
  Serial.print(100 + i * 10);
  Serial.print(",");
  Serial.print(" \"attrs\": { \"text\": \"");
  Serial.print(text);
  Serial.print(" ");
  Serial.print(i);
  Serial.print("\" }");
  Serial.print(" }");
  if (i != num)
    Serial.println(",\n");
  else
    Serial.println();
  }
}

/* EXAMPLE
    { "type": "wokwi-text", "id": "txt0", "top": 100, "left": 100, "attrs": { "text": "TEXT 0" }    },

*/

So I posted it into the Wokwi simulator and it's being weird there as well, I've been working on the mounting hardware for the gate, when I get done there I'll work on a schematic and figuring out what the simple changes to the code broke everything...

Thanks.

Good, good. I really don't want this to be a software/sim problem.

Nice work. I will give it the attention it deserves when I'm next in the lab.

a7

ok, I read through the code. This will come in handy if I can remember to use it…

I quoted your advisory message. If only the designers of the JSON format had taken a page from the long ago C programming language, which explicitly allows for a final comma in an initialiser list for reasons, one being to make automatic table generation easier.

How hard is it to automatically not write the last comma? Not hard at all, but it is more code and therefore is an avenue for mistakes.

The tolerance for comma makes human editing of table data easier, also.

If you want to waste time, look into it and see the reasoning as well as the controversy that bringing it up can engender.

I put the unnecessary comma there as a habit, just because I can. :wink:

a7

I created a schematic in Wokwi, I couldn't figure out any of the schematic software I could find, and hand drawing it was just as messy as a photograph. I used a lot of custom chips since much of my hardware that I'm using isn't in the database. Gotta get the little one from school, so I'll be back later. I chose the esp32 that was the closest to my board, but none of them are that close, so some of my pins are exact, but you can see how all the connections are.

Yes. Even an arrant keystroke mangles the whole .json. I made the widget when I wanted to pre-name a handful of text labels, or re-order a set of labels. I use an index to increase the item name as well as the label... commas between labels, after the last index.

  if (i != num)
    Serial.println(",\n");
  else
    Serial.println();
  }

I am now interested in @jgslade custom devices... especially how to make them. I usually use pushbuttons or slide-switches to represent external input in the simulations.

There's a custom chip option, then you can add connections and name them. I would imagine you can do more with them, but I haven't looked into it at all.

Working on my keypad now, and it seems it's more complicated than I thought it would be. It's a wiegand compatible keypad, so it uses 2 signal wires rather than just sending a simple activation signal when it gets the code.

This is the library that is available in the ide so I think I'll use it: GitHub - paulo-raca/YetAnotherArduinoWiegandLibrary: Event-driven Wiegand Library for Arduino. Based on this quote from the readme I guess I should use interrupts, but I have no idea what the difference between polling and interrupts is and why one way is better than another.

Since the hardware changes a lot, this library doesn't assume anything on how to read the data pins.

When the state of a pin has changed, it's up to you to call Wiegand.setPinState(pin, state).

There are examples on how to use it with Interruptions and Polling on Arduinos.

(You probably want to use interruptions)

Currently reading through the interrupts example provided with the library to try and learn what's going on. I've had to order a 12v power supply so I can test with the code on the test bench since my uses 12v power and I don't have a way to power it at the test bench now that the rest of the opener is assembled and installed on the gate.

You should be able to use any of the three. I always avoid interrupts. You have a non-blocking trivial program. Polling should work for your case if it works for anyone's.

The library uses callbacks. So you could use the callbacks to set variables in your program.

Get all three examples to work without modifying them, and without any other hardware but the keypad attached.

Then maybe you will have learned enough to fit one or all into your current sketch. If not, post your attempt to do here.

I dare to assume that you have eliminated the differences that make it work in simulation but not in real life.

How are you testing without a keypad?

a7

Cool, thanks.

I was just going to play the interrupt one, but I'll do this now. I was then going to add an led.

Here's hoping. :slight_smile:

I reverted back to using prevGateState for holding since it was working.

With the bluetooth control.

The power supply should arrive sometime this morning, so hopefully I'll be able to get this figured out today.

Thanks for the feedback.

The polling example gives this output:

State changed: DISCONNECTED

State changed: CONNECTED

State changed: DISCONNECTED

State changed: CONNECTED

State changed: DISCONNECTED

State changed: CONNECTED

State changed: DISCONNECTED

State changed: CONNECTED

State changed: DISCONNECTED

State changed: CONNECTED

Card read error: Communication Error - Raw data: 1bits / 00

Card read error: Communication Error - Raw data: 1bits / 00

Card read error: Communication Error - Raw data: 1bits / 00

Card read error: Communication Error - Raw data: 1bits / 00

State changed: DISCONNECTED

State changed: CONNECTED

Compiling the pin_change_interrupts gives this error:

exit status 1

Compilation error: YetAnotherPcInt.h: No such file or directory

the interrupts example gives me this:

Card read error: Communication Error - Raw data: 1bits / 00
State changed: DISCONNECTED
State changed: CONNEGuru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1). 

Core  1 register dump:
PC      : 0x4008ac46  PS      : 0x00060b35  A0      : 0x80089bbe  A1      : 0x3ffbf08c  
A2      : 0x3ffb897c  A3      : 0x3ffbce0c  A4      : 0x00000004  A5      : 0x00060b23  
A6      : 0x00060b23  A7      : 0x00000001  A8      : 0x3ffbce0c  A9      : 0x00000019  
A10     : 0x3ffbce0c  A11     : 0x00000019  A12     : 0x3ffc2314  A13     : 0x00060b23  
A14     : 0x007bf2f8  A15     : 0x003fffff  SAR     : 0x0000000a  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x400864cd  LEND    : 0x400864dd  LCOUNT  : 0xfffffffd  
Core  1 was running in ISR context:
EPC1    : 0x400dabdb  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x00000000


Backtrace: 0x4008ac43:0x3ffbf08c |<-CORRUPTED


Core  0 register dump:
PC      : 0x4008ade3  PS      : 0x00060035  A0      : 0x800897e7  A1      : 0x3ffbea1c  
A2      : 0x3ffbf2f8  A3 �

I wonder if the pins I selected are incompatible with the communication...

I'm using this board https://m.media-amazon.com/images/I/61afDc5iOYL._AC_SL1001_.jpg with D0 on pin 17 and D1 on pin 16. Gonna try on 20 and 22. Whelp that didn't work... The keypad went into error mode and the serial output was:

h �� %R]��r�& aHuw��% �\e�([�� ��'#H�H� a��&@)8����a!J]�PE0�/ �a!(�� �Bμi� � � ^_� �X��� I�8���-����f��.�{�B%�^б��� M 8�B&JJz� ��̼ �� ,��] R/��R��� M�$�!ġ�b�ڔ!X� ��w�DP<����VŌH�) �a�T�����N

��'��w�a�T�����N

�a��!ġ�b��f� �� �a�$��� �N

� ��w� DP<���,[�V3�!+�a@��h\ ڜI�qA�!�zY�b�bb�H�!+�a�T�����N

�a� � D����b��O�DX�a@���\ V��D���RK���8 a�����%n�a@A$��a(cbĆP�9 �����1Sf`I� I�G� aV�XhP�!�'b����,�XL^� ݁�U5.a�0$@#,�IȠ*p �;/\``�� ���/�1�� ���'U��B�a�`'I@��-�^RlcZe$�`��� P���b(c`�"�`` ��!�O�+)�N

H� �!�z~��)� J$+�qA� $���K�6���h ,�!�O�+)�N�H1a�z��B ��1IaI�� ,�'J]�b[������ � DP��K��lb���H���a&ġ�b�bbo�=��!J���0���� O`c� �P�I�\�bb/�0 ,��EP*�� �b�x��\� D��I�lV�=�^DP�� ����\� ���

�*�N�HO�. ��*�����HO�.JO�^��n�Ѐ|�G@HO�W�V���A^!� �ġ�`� �� ����AP B]� ��@�$'�1��!�a��6�V����'@ ,�JB��l,E���] ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

B]�b�����}� \���"@ ;���$��E� �K1-��Ό���

��z�ʌŀ�$$�̅ �z�

�X�q ����a!JB. b[Aʨ$��EP'J,�()�x$Kg��������V)� ��D~�����0)��L<��� � O� /݂ ��Ih� ��z�p�$��'��'J�Kq,���L\�q��"T�I� ��8L,�p� �@O��B J� '�^ �G@�0Kq\[ )ٍOh �a%B]ű��N�<� �V � O��B %� '�^`�a@(�]b[�j�`�} �a@�a0Kq,e��� Oк��!�����.�L\�q��aJ$Kq,��Ό|� �V'% q,e”�=�]���E�N��

�R����38���@���v��5 =�a@�Pp���� �A^��EP R�b����a!��#����

ap$$�<@�a!J$K1-��.�$+g'��RT��"V �b"�$���� �

/�b� M�^�V�] z��lV)ÃH�.���]z���V�P5�A^�� ��J���V�P1eNhaÄ-(��lV)C�о�a!J �q,3�f�%�q�. ��z��j,@�$$�ȍa�]b[Q��A^P�!T�I�b���$�8��%��� )�V�K���� ��-Z_����&�q��!T�)"��!H{��D������V��9&q��� ��Z_�����=�`�D�Nzy�j�V)�Ih,� �(����� !��>,� ��º��*�.�&�y��+<�E0J� O�]��B�肗�,3�LX0'������<�B�|��|�'#�'�l�jVAb^� �]a��)"�X,�H����"(�+�Ƣ ^� ] �

\�`PiÖ� � � &� �� ��b@(� O�8`� ���бn�f�m8��#(���� {㆘Ih � �R�p�^� ��� U��bA&p$5 w�

Rebooting didn't do anything...