Switch scanning not working

Hi

I am having a problem with this switch scanning code. When I press any of the switches (tactile switch) I do not get a response on serial monitor. serial monitor shows the DEBUG option being on or off and also shows if the switch for selecting either brown or grey mapping.

I have triple checked the connections and all the inputs and outputs are wired correctly.

Is the something I'm doing wrong which I can't see.

Thank you

/*

  - ATmega2560, 5×12 switch matrix
  - Columns float HIGH via INPUT_PULLUP; only driven LOW when scanning
  - Rows are inputs with INPUT_PULLUP, so a closed switch pulls the row pin LOW
  - OPTION_SW_1 (pin 22) = Grey/Brown mapping
  - OPTION_SW_4 (pin 25) = Debug toggle
  - SCAN_INTERVAL = 2 ms
  - POWER_LED (pin 38) lights at startup
*/

#include <Arduino.h>

// Switch rows (12 inputs, active LOW when pressed)
static const uint8_t switchInputs[12] = {
  2, 3, 4, 5, 6, 7,
  8, 9, 10, 11, 12, 13
};

// Switch columns (5 lines, normally pulled HIGH via INPUT_PULLUP)
static const uint8_t switchOutputs[5] = {
  44, 43, 42, 41, 40
};

// Mapping‐select switch: HIGH = Grey, LOW = Brown
#define OPTION_SW_1 22

// Debug‐toggle switch: LOW = Debug ON, HIGH = Debug OFF
#define OPTION_SW_4 25

// Power LED
#define POWER_LED 38

// 2 ms scan interval
#define SCAN_INTERVAL 2

// Track last pressed state for each (col,row)
bool lastPressed[5][12] = { false };

// Timestamp of last scan
unsigned long lastScan = 0;

// Whether Serial debug is currently active
bool serialActive = false;

// Last mapping state (to detect changes)
bool lastMappingGrey = false;

void setup() {
  // 1) Turn on the power LED
  pinMode(POWER_LED, OUTPUT);
  digitalWrite(POWER_LED, HIGH);

  // 2) Configure row inputs as INPUT (they read HIGH until pulled LOW by a switch)
  for (uint8_t i = 0; i < 12; i++) {
    pinMode(switchInputs[i], INPUT_PULLUP);
  }

    for (uint8_t i = 0; i < 5; i++) {
    pinMode(switchOutputs[i], OUTPUT);
   
  }

  // 4) Configure mapping and debug switches
  pinMode(OPTION_SW_1, INPUT_PULLUP);
  pinMode(OPTION_SW_4, INPUT_PULLUP);

  // 5) Always start Serial at 115200
  Serial.begin(115200);
  delay(5);
  Serial.println("System initialized.");

  // 6) Print initial mapping
  bool mappingGrey = (digitalRead(OPTION_SW_1) == HIGH);
  lastMappingGrey = mappingGrey;
  Serial.print("Mapping: ");
  Serial.println(mappingGrey ? "GREY" : "BROWN");

  // 7) Check debug switch at startup
  if (digitalRead(OPTION_SW_4) == LOW) {
    serialActive = true;
    Serial.println("Debug enabled (pin 25 LOW).");
  } else {
    serialActive = false;
    Serial.println("Debug disabled (pin 25 HIGH).");
  }
}

void loop() {
  // 1) Detect mapping switch changes
  bool mappingGrey = (digitalRead(OPTION_SW_1) == HIGH);
  if (mappingGrey != lastMappingGrey) {
    lastMappingGrey = mappingGrey;
    if (serialActive) {
      Serial.print("Mapping changed: ");
      Serial.println(mappingGrey ? "GREY" : "BROWN");
    }
  }

  // 2) Detect debug‐toggle changes
  bool nowDebug = (digitalRead(OPTION_SW_4) == LOW);
  if (nowDebug && !serialActive) {
    serialActive = true;
    Serial.println("Debug enabled (pin 25 LOW).");
  }
  else if (!nowDebug && serialActive) {
    Serial.println("Debug disabled (pin 25 HIGH).");
    serialActive = false;
  }

  // 3) Only scan every SCAN_INTERVAL milliseconds
  if (millis() - lastScan >= SCAN_INTERVAL) {
    lastScan = millis();
    scanMatrix();
  }

  // 4) (Optional) insert checkSafety(); here
}

// Scan a 5×12 matrix using “open-drain” style: columns idle as INPUT_PULLUP (HIGH),
// and are driven LOW only during the brief scan.
void scanMatrix() {
  bool isGreyMode = (digitalRead(OPTION_SW_1) == HIGH);

  for (uint8_t col = 0; col < 5; col++) {
    // 1) Activate this column: switch from INPUT_PULLUP → OUTPUT + LOW
    pinMode(switchOutputs[col], OUTPUT);
    digitalWrite(switchOutputs[col], LOW);
    delayMicroseconds(50);  // allow settling time

    // 2) Read each of the 12 rows
    for (uint8_t row = 0; row < 12; row++) {
      bool pressed = (digitalRead(switchInputs[row]) == LOW);
      if (pressed != lastPressed[col][row]) {
        lastPressed[col][row] = pressed;
        if (serialActive) {
          Serial.print(pressed ? "Pressed " : "Released ");
          Serial.print("Row ");
          Serial.print(row + 1);
          Serial.print(" Col ");
          Serial.print(col + 1);
          Serial.print(" → ");
          Serial.println(mapToFunction(row, col, isGreyMode));
        }
        // TODO: Add your relay control here.
      }
    }

    // 3) Deactivate this column: switch back to INPUT_PULLUP
    pinMode(switchOutputs[col], INPUT_PULLUP);
  }
}

// Returns a two‐ or three‐letter code for a given (row, col) and mapping
String mapToFunction(uint8_t row, uint8_t col, bool isGrey) {
  if (isGrey) {
    // GREY MAPPING
    if (row == 2  && col == 2) return "BRR";
    if (row == 7  && col == 2) return "BRF";
    if (row == 2  && col == 3) return "FTD";
    if (row == 7  && col == 3) return "FTU";
    if (row == 5  && col == 2) return "RTD";
    if (row == 3  && col == 2) return "RTU";
    if (row == 3  && col == 3) return "PF";
    if (row == 5  && col == 3) return "PR";
    if (row == 8  && col == 0) return "OSR";
    if (row == 9  && col == 0) return "OSL";
    if (row == 10 && col == 0) return "OSD";
    if (row == 11 && col == 0) return "OSU";
    if (row == 8  && col == 1) return "NSR";
    if (row == 9  && col == 1) return "NSL";
    if (row == 10 && col == 1) return "NSD";
    if (row == 11 && col == 1) return "NSU";
  }
  else {
    // BROWN MAPPING
    if (row == 6  && col == 2) return "BRR";
    if (row == 1  && col == 2) return "BRF";
    if (row == 2  && col == 3) return "FTD";
    if (row == 7  && col == 3) return "FTU";
    if (row == 0  && col == 2) return "RTD";
    if (row == 4  && col == 2) return "RTU";
    if (row == 3  && col == 3) return "PF";
    if (row == 5  && col == 3) return "PR";
  }
  return "Unknown";
}

TLDR, but I don't see a button library or any code after a quick look that will deal with bouncing. Button handling is much more tricky than the new user realizes.

Shouldn’t you also declare outputs here as INPUT_PULLUP to deactivate them?

I've added debouce and INPUT_PULLUP on the outputs.

Still not working

/*

  - 5×12 switch matrix (rows: pins 2…13, columns: pins 44,43,42,41,40)
  - Debounce each key:  keep a 20 ms stable reading before declaring “pressed”
  - Prints “Row X Col Y pressed” once per debounced press
  - Scans every 2 ms
*/

#include <Arduino.h>

// ===== PIN DEFINITIONS =====

// Row input pins (12 total, wired with pull-up resistors)
static const uint8_t ROW_PINS[12] = {
  2,  3,  4,  5,  6,  7,
  8,  9, 10, 11, 12, 13
};

// Column output pins (5 total, will be toggled between INPUT_PULLUP and OUTPUT LOW)
static const uint8_t COL_PINS[5] = {
  44, 43, 42, 41, 40
};

// How long to wait (ms) for a stable reading before accepting a state change
#define DEBOUNCE_MS 20

// How often to scan the entire matrix (milliseconds)
#define SCAN_INTERVAL 2

// Tracks the “last raw reading” for each key [col][row]
bool lastRaw[5][12] = { false };

// Tracks the “debounced (stable) state” for each key [col][row]
bool debouncedState[5][12] = { false };

// Tracks when the last raw change occurred [col][row] (millis())
unsigned long lastChangeTime[5][12] = { {0} };

// For timing the 2 ms scans
unsigned long lastScan = 0;

void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.println("Debounced matrix scan started.");

  // Configure all row pins as INPUT_PULLUP
  for (uint8_t r = 0; r < 12; r++) {
    pinMode(ROW_PINS[r], INPUT_PULLUP);
  }

  // Configure all column pins as INPUT_PULLUP initially
  for (uint8_t c = 0; c < 5; c++) {
    pinMode(COL_PINS[c], INPUT_PULLUP);
  }
}

void loop() {
  // Only scan every SCAN_INTERVAL ms
  if (millis() - lastScan >= SCAN_INTERVAL) {
    lastScan = millis();
    scanMatrixDebounced();
  }
}

void scanMatrixDebounced() {
  // For each column, pull it LOW temporarily then read all rows
  for (uint8_t col = 0; col < 5; col++) {
    // 1) Activate this column: switch from INPUT_PULLUP → OUTPUT + LOW
    pinMode(COL_PINS[col], OUTPUT);
    digitalWrite(COL_PINS[col], LOW);
    delayMicroseconds(50);  // Allow signals to settle

    // 2) Read each row’s raw state (LOW if pressed, HIGH if not)
    for (uint8_t row = 0; row < 12; row++) {
      bool rawPressed = (digitalRead(ROW_PINS[row]) == LOW);

      // If the raw reading changed from last time, reset debounce timer
      if (rawPressed != lastRaw[col][row]) {
        lastRaw[col][row] = rawPressed;
        lastChangeTime[col][row] = millis();
      }

      // See if the raw reading has stayed stable long enough
      if ((millis() - lastChangeTime[col][row]) >= DEBOUNCE_MS) {
        // If the debounced state differs, register the new stable state
        if (rawPressed != debouncedState[col][row]) {
          debouncedState[col][row] = rawPressed;

          // Only print on a newly debounced press
          if (debouncedState[col][row]) {
            Serial.print("Row ");
            Serial.print(row + 1);
            Serial.print(" Col ");
            Serial.print(col + 1);
            Serial.println(" pressed");
          }
          // (You could optionally print “released” here if you need it)
        }
      }
    }

    // 3) Deactivate this column: revert back to INPUT_PULLUP
    pinMode(COL_PINS[col], INPUT_PULLUP);
  }
}

If I had a nickel for every time I'd read that.

There's almost certainly something wrong that we can't see. In your wiring that you've triple checked.

How can I say that?

I loaded your sketch into a Mega and ran it. And got the following output.

System initialized.
Mapping: GREY
Debug enabled (pin 25 LOW).
Pressed Row 1 Col 5 → Unknown
Released Row 1 Col 5 → Unknown
Pressed Row 3 Col 5 → Unknown
Released Row 3 Col 5 → Unknown

Perhaps it's just me, but it looks like the scanning is responding when a connection is made.

Rule #517 of debugging: when you can't find the problem, it will always reside in the part that you are sure is correct.

3 Likes

I will have another check tomorrow.

What AI are you using?

Have you a breadboard like this?

I do

Always use known working code to test your circuit, components and wiring.

In your case I think you should be able to use the keypad library and one of the example sketches that come with that library.

If you don't want to use that library in your final project, that's fine. But at least you will be able to develop your own code knowing there isn't a problem with the circuit.

Does this library allow you to change the speed of the scanning. I need there to be 2ms delay between each output scan.

Yes, I think you can achieve that by not calling keypad.getKey() until 2ms after the previous call.

But please explain why you want this 2ms delay. It may not be long enough to avoid button bouncing problems. You will probably need something closer to 20ms between scans to effectively debounce your keys.

There will be another ecu attached to the outputs that relies on the timing of the scanning to read a set of shared switches. I can't change that

I did not find your description clear at all. Do you mean an Engine Control Unit?

There is a steering column tilt ecu which uses 5 switches to store memorized positions. The relay board i am trying to re create uses the same memory switches

I'm sorry, I can't understand. Each time you try to clarify, it makes less and less sense to me.

If this "ECU" is designed to monitor 5 buttons, how can it scan the matrix you mentioned, which seems to have 60 buttons?

If it can scan 60 buttons, how can the Arduino also scan those 60 buttons? Was the idea that if the Arduino scans the matrix every 2ms, it will not clash with the ECU scanning the buttons? I don't think they will stay synchonised for long.

I went back and checked all my switches wiring and all was ok. I thought I'd check the pcb i had made and there was no connections between 2560 and the resistors non 6 of the inputs. I'll wire them tomorrow and try again

Update. Connected all the missing tracks and got 10 of the 12 inputs working. 2 won't read with the 100k resistor in series with the input from the switch and the 2560. Remove the 100k from them 2 inputs and connect directly from switch to 2560 and it works.