#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <string>
#define ROWS 4
#define COLS 4
#define R1 13
#define R2 12
#define R3 14
#define R4 27
#define C1 26
#define C2 25
#define C3 33
#define C4 32
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
short rows[4] = { R1, R2, R3, R4 };
short columns[4] = { C1, C2, C3, C4 };
bool keypad_state[ROWS][COLS] = {
{ false, false, false, false },
{ false, false, false, false },
{ false, false, false, false },
{ false, false, false, false }
};
unsigned int keypad_last_input[ROWS][COLS] = {
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 },
{ 0, 0, 0, 0 }
};
unsigned char PROGMEM up_arrow[8] = {
0x18,
0x24,
0x42,
0xe7,
0x24,
0x24,
0x24,
0x3c
};
// lowercase or uppercase
bool is_lowercase = true;
char lowercase(char ch) {
if (ch >= 'A' && ch <= 'Z') {
ch += ('a' - 'A');
}
return ch;
}
// active key -> { row, col, index, last_pressed_time }
unsigned int active_key[4] = { 5, 5, 0, 0 };
// using telephone keypad system
// null char '\0' means none (for keys with only 3 letters or less)
// KEYPAD:
// (1) (ABC2) (DEF3) (BACKSPACE)
// (GHI4) (JKL5) (MNO6) (ENTER)
// (PQRS7) (TUV8) (QXYZ9) (CAPSLOCK)
// (*) ( 0) (#) ()
char keypad_keys[ROWS][COLS][5] = {
// first row
{ { '1', '\0', '\0', '\0', '\0' },
{ 'A', 'B', 'C', '2', '\0' },
{ 'D', 'E', 'F', '3', '\0' },
{ '\b', '\0', '\0', '\0', '\0' }
},
// second row
{ { 'G', 'H', 'I', '4', '\0' },
{ 'J', 'K', 'L', '5', '\0' },
{ 'M', 'N', 'O', '6', '\0' },
{ '\n', '\0', '\0', '\0', '\0' }
},
// third row
{ { 'P', 'Q', 'R', 'S', '7' },
{ 'T', 'U', 'V', '8', '\0' },
{ 'W', 'X', 'Y', 'Z', '9' },
{ '\v', '\0', '\0', '\0', '\0' }
},
// fourth row
{ { '*', '\0', '\0', '\0', '\0' },
{ ' ', '0', '\0', '\0', '\0' },
{ '#', '\0', '\0', '\0', '\0' },
{ '\0', '\0', '\0', '\0', '\0' }
},
};
String text = "";
void scanKeypad() {
for (int i = 0; i < 4; i++) {
digitalWrite(rows[i], LOW);
for (int j = 0; j < 4; j++) {
// key just pressed event
if (digitalRead(columns[j]) == LOW && !keypad_state[i][j]) {
// debouncing
if (millis() - keypad_last_input[i][j] > 50) {
keyPress(i, j);
keypad_state[i][j] = true;
keypad_last_input[i][j] = millis();
}
}
// key just released event
else if (digitalRead(columns[j]) == HIGH && keypad_state[i][j]) {
// debouncing
if (millis() - keypad_last_input[i][j] > 50) {
keypad_state[i][j] = false;
keypad_last_input[i][j] = millis();
}
}
}
digitalWrite(rows[i], HIGH);
}
}
// key just pressed event
void keyPress(int row, int column) {
bool new_button = false;
if (active_key[0] != row || active_key[1] != column || millis() - active_key[3] > 1000) {
active_key[0] = row;
active_key[1] = column;
active_key[2] = 0;
new_button = true;
}
else {
active_key[2]++;
}
// reset to first index if encounter \0
if (keypad_keys[row][column][active_key[2]] == '\0') {
active_key[2] = 0;
}
switch (keypad_keys[row][column][active_key[2]]) {
// if first index is also \0 then skip
case '\0':
return;
// backspace
case '\b':
text.remove(text.length() - 1);
break;
// caps lock
case '\v':
if (is_lowercase) {
is_lowercase = false;
}
else {
is_lowercase = true;
}
break;
default:
if (new_button) {
text += 'a';
}
if (is_lowercase) {
text[text.length() - 1] = lowercase(keypad_keys[row][column][active_key[2]]);
}
else {
text[text.length() - 1] = keypad_keys[row][column][active_key[2]];
}
active_key[3] = millis();
}
display.clearDisplay();
display.setCursor(0, 0);
if (!is_lowercase) {
displayCapsLock();
}
display.print(text);
display.display();
// debug
/*
Serial.print('{');
Serial.print(active_key[0]);
Serial.print(',');
Serial.print(active_key[1]);
Serial.print(',');
Serial.print(active_key[2]);
Serial.print(',');
Serial.print(active_key[3]);
Serial.print('}');
Serial.println();
*/
}
void initKeypad() {
for (int i = 0; i < 4; i++) {
pinMode(columns[i], INPUT_PULLUP);
pinMode(rows[i], OUTPUT);
digitalWrite(rows[i], HIGH);
}
}
void displayCapsLock() {
display.drawBitmap(SCREEN_WIDTH - 8, SCREEN_HEIGHT - 8, up_arrow, 8, 8, SSD1306_INVERSE);
}
void setup() {
Serial.begin(115200);
initKeypad();
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.clearDisplay();
display.setCursor(0, 0);
display.setTextColor(SSD1306_INVERSE);
display.setTextSize(1);
display.setTextWrap(true);
}
void loop() {
scanKeypad();
delay(10);
}