// 2025 Richard E Barber
#include <Arduino.h>
// ---- Wiring (columns and rows) ----
// Columns: driven OUTPUT, idle HIGH, one pulled LOW per scan step
#define C0 2
#define C1 3
#define C2 4
#define C3 5
// Rows: read as INPUT_PULLUP (idle HIGH, go LOW when pressed with active column)
#define R0 6
#define R1 7
#define R2 8
#define R3 9
// If your keypad part outputs HIGH on press instead of shorting, set to 1
#define PRESSED_READS_HIGH 0
// ---- BCD bus (UNO analog pins used as digital) ----
// A0..A3 = digital 14..17
// We'll drive these with digitalWrite(14..17, 0/1).
// (No external latch required; we latch in software.)
byte g_latchedCode = 0; // last latched 4-bit code
byte g_anyPressed = 0; // 1 while any key is currently held
// ---- Helpers that avoid arrays (iCircuit likes this better) ----
static byte colPin(byte idx) {
if (idx == 0) return C0;
if (idx == 1) return C1;
if (idx == 2) return C2;
return C3; // idx == 3
}
static byte rowPin(byte idx) {
if (idx == 0) return R0;
if (idx == 1) return R1;
if (idx == 2) return R2;
return R3; // idx == 3
}
// Key lookup without 2D array initializers
static char keyAt(byte r, byte c) {
// Row-major: { 1 2 3 A ; 4 5 6 B ; 7 8 9 C ; * 0 # D }
if (r == 0) {
if (c == 0) return '1';
if (c == 1) return '2';
if (c == 2) return '3';
return 'A';
} else if (r == 1) {
if (c == 0) return '4';
if (c == 1) return '5';
if (c == 2) return '6';
return 'B';
} else if (r == 2) {
if (c == 0) return '7';
if (c == 1) return '8';
if (c == 2) return '9';
return 'C';
} else { // r == 3
if (c == 0) return '*';
if (c == 1) return '0';
if (c == 2) return '#';
return 'D';
}
}
// Map a key char to a 4-bit code (0x0..0xF)
byte codeForKey(char k) {
if (k == '0') return 0x0;
if (k == '1') return 0x1;
if (k == '2') return 0x2;
if (k == '3') return 0x3;
if (k == '4') return 0x4;
if (k == '5') return 0x5;
if (k == '6') return 0x6;
if (k == '7') return 0x7;
if (k == '8') return 0x8;
if (k == '9') return 0x9;
if (k == 'A') return 0xA;
if (k == 'B') return 0xB;
if (k == 'C') return 0xC;
if (k == 'D') return 0xD;
if (k == '*') return 0xE;
if (k == '#') return 0xF;
return 0x0;
}
// Drive the 4-bit nibble on A0..A3 (pins 14..17), very parser-friendly
void outputBCD(byte code) {
int b;
b = 0;
if (code & 0x01) b = 1;
digitalWrite(14, b); // A0 (LSB)
b = 0;
if (code & 0x02) b = 1;
digitalWrite(15, b); // A1
b = 0;
if (code & 0x04) b = 1;
digitalWrite(16, b); // A2
b = 0;
if (code & 0x08) b = 1;
digitalWrite(17, b); // A3 (MSB)
}
void setup() {
// Console
Serial.begin(9600);
Serial.println("4x4 kpd scanner to BCD.");
// Columns as outputs, idle HIGH
pinMode(C0, OUTPUT); digitalWrite(C0, HIGH);
pinMode(C1, OUTPUT); digitalWrite(C1, HIGH);
pinMode(C2, OUTPUT); digitalWrite(C2, HIGH);
pinMode(C3, OUTPUT); digitalWrite(C3, HIGH);
// Rows as inputs with pullups
pinMode(R0, INPUT_PULLUP);
pinMode(R1, INPUT_PULLUP);
pinMode(R2, INPUT_PULLUP);
pinMode(R3, INPUT_PULLUP);
// BCD bus pins (A0..A3 as 14..17)
pinMode(14, OUTPUT);
pinMode(15, OUTPUT);
pinMode(16, OUTPUT);
pinMode(17, OUTPUT);
// Start bus at 0000 (latched)
g_latchedCode = 0x0;
outputBCD(g_latchedCode);
}
void driveAllColsHigh() {
digitalWrite(C0, HIGH);
digitalWrite(C1, HIGH);
digitalWrite(C2, HIGH);
digitalWrite(C3, HIGH);
}
void loop() {
// Scan each column LOW one at a time
for (byte c = 0; c < 4; c++) {
driveAllColsHigh();
digitalWrite(colPin(c), LOW);
// small settle (avoid microsecond API for iCircuit)
delay(1);
// Read each row
for (byte r = 0; r < 4; r++) {
int v = digitalRead(rowPin(r));
byte pressed = (PRESSED_READS_HIGH ? (v == HIGH) : (v == LOW));
if (pressed) {
char k = keyAt(r, c);
// Only latch on the rising edge (first frame of a new press)
if (!g_anyPressed) {
byte code = codeForKey(k);
g_latchedCode = code;
outputBCD(g_latchedCode);
Serial.print("Key press: ");
Serial.print(k);
Serial.print(" -> BCD 0x");
Serial.println((int)code, HEX);
g_anyPressed = 1;
}
// crude debounce so the console doesn't spam
delay(50);
}
}
// pacing between columns
delay(1);
}
// Detect release: if no key is currently detected, clear the "pressed" flag
// (bus remains latched with last value)
byte anyNow = 0;
for (byte c = 0; c < 4; c++) {
// quick re-scan (very short) to check if any key is down
driveAllColsHigh();
digitalWrite(colPin(c), LOW);
delay(1);
for (byte r = 0; r < 4; r++) {
int v = digitalRead(rowPin(r));
byte pressed = (PRESSED_READS_HIGH ? (v == HIGH) : (v == LOW));
if (pressed) {
anyNow = 1;
}
}
if (anyNow) break;
}
if (!anyNow) {
g_anyPressed = 0; // ready to latch next new press
}
}