1. osu! forums
  2. osu!
  3. Development
  4. Completed Projects
posted
Light Gate driven osu! Keypad

:idea: Hey! :idea:
I made a light gate driven keypad for osu!
It has 2 light gates which act as keys to play the game. The keypad can be connected to any computer via USB, no software required!

It can be played at any non transparent object to operate on.

It features:
> 3ms response time.
> Display with hit counter
> Mode switch to control display modes
> 2 LEDs

Why bother?
It does not matter at all how hard you press the "key" a hit is registered as soon as the gap between your finger and the bottom surface is smaller than 0.2mm.
That means you decide how far your fingers travels and how strong you tap!
Also there is no noise, no keys wearing off and your fingers are not getting tired.
You decide on what surface you play on. I personally prefer my mousepad.

CONCLUSION
I LOVE playing on it, will never go back to a keyboard, even though I have tried various cherry mx switches.



HOW I MADE IT:










About the key logic: This is the controller of an old keyboard. Trace the Key matrix to the keys you want to use that you know that pins to short by the MOSFETs!
Please Watch this to understand:


You might need up to 3x 1000uF to save the counter to eeprom depends on execution time!

Download and print the housing here: https://www.thingiverse.com/thing:2840946


#include <Arduino.h>
#include <EEPROM.h>

#define BIT_0 1
#define BIT_1 2
#define BIT_2 4
#define BIT_3 8
#define BIT_4 16
#define BIT_5 32
#define BIT_6 64
#define BIT_7 128
#define BIT_8 256
#define BIT_9 512
#define BIT_10 1024
#define BIT_11 2048
#define BIT_12 4096
#define BIT_13 8192
#define BIT_14 16384
#define BIT_15 32768

#define EEPROM_SAVE 48

//#define HIGH 1
//#define LOW 0
#define ON 1
#define OFF 0

#define DIG_ON HIGH
#define DIG_OFF LOW
#define SEG_ON LOW
#define SEG_OFF HIGH

#define INTERRUPT_ON_PIN_2 0
#define INTERRUPT_ON_PIN_3 1

#define D_0 10
#define D_1 5
#define D_2 6
#define D_3 9
#define S_1 19
#define S_2 18
#define S_3 17
#define S_4 16
#define S_5 15
#define S_6 14
#define S_7 13

#define POWER 2

///#define BUTTON 3 //reserved for future uses

#define X_PRESS 11
#define C_PRESS 4
#define L_READ 7
#define R_READ 8

#define MODE 12

///DATATYPES
typedef struct _save {unsigned int cnt; unsigned int cnt_k; unsigned int cnt_m;} save;
typedef struct _display {int A; int B; int C; int D;} display;
//typedef struct _digit {byte A; byte B; byte C; byte D; byte E; byte F; byte G;} digit;

///PROTOTYPES
int mode_routine(int);
void clear_save_EEPROM (void);
void store_save_EEPROM (save);
save read_save_EEPROM (void);
void initialize_display(void);
void l_triggered (void);
void r_triggered (void);
void count_up (void);
display save_to_display(int);
void char_to_digit(int);
void char_to_digit_off(void);
void save_routine (void);

///GOLBAL VARIABLES
save cnt; //3x16BIT
display cur_display;

void setup()
{
pinMode(D_0, OUTPUT);
pinMode(D_1, OUTPUT);
pinMode(D_2, OUTPUT);
pinMode(D_3, OUTPUT);
pinMode(S_1, OUTPUT);
pinMode(S_2, OUTPUT);
pinMode(S_3, OUTPUT);
pinMode(S_4, OUTPUT);
pinMode(S_5, OUTPUT);
pinMode(S_6, OUTPUT);
pinMode(S_7, OUTPUT);

pinMode(POWER, INPUT);

pinMode(X_PRESS, OUTPUT);
pinMode(C_PRESS, OUTPUT);

pinMode(L_READ, INPUT);
pinMode(R_READ, INPUT);

pinMode(MODE, INPUT);

///debug
///Serial.begin(9600);

initialize_display(); //turn display off
cnt = read_save_EEPROM(); //receive save filexxxxxxxcccccccccccccccccccccccccccccc
///cnt.cnt_k = 999;
///cnt.cnt = 998;
///cnt.cnt_m = 6;
///store_save_EEPROM(cnt);

delay(200); //allow time to set up

attachInterrupt(INTERRUPT_ON_PIN_2, save_routine, FALLING);
}

void loop()
{
///VARIABLES
int display_mode = 1;
int last_l = 0;
int last_r = 0;

// loop
while(1)
{
///////////////////////////////////////////////////////////
//display updates ////
cur_display = save_to_display(display_mode);

digitalWrite(D_3, DIG_OFF);
char_to_digit_off();
char_to_digit(cur_display.A);
digitalWrite(D_0, DIG_ON);

delay(1);

digitalWrite(D_0, DIG_OFF);
char_to_digit_off();
char_to_digit(cur_display.B);
digitalWrite(D_1, DIG_ON);

delay(1);

digitalWrite(D_1, DIG_OFF);
char_to_digit_off();
char_to_digit(cur_display.C);
digitalWrite(D_2, DIG_ON);

delay(1);

digitalWrite(D_2, DIG_OFF);
char_to_digit_off();
char_to_digit(cur_display.D);
digitalWrite(D_3, DIG_ON);

delay(1); ////
///////////////////////////////////////////////////////////

//the mode switch
if (digitalRead(MODE) == HIGH)
{
display_mode = mode_routine(display_mode);
}

///////////////////////////////////////////////////////////
//register keystrokes ////

if (digitalRead(L_READ) == HIGH)
{
digitalWrite(X_PRESS, HIGH);

if (last_l == 0)
count_up();

last_l = 1;
}
else
{
digitalWrite(X_PRESS, LOW);
last_l = 0;
}

if (digitalRead(R_READ) == HIGH)
{
digitalWrite(C_PRESS, HIGH);

if (last_r == 0)
count_up();

last_r = 1;
}
else
{
digitalWrite(C_PRESS, LOW);
last_r = 0;
} ////
///////////////////////////////////////////////////////////
}
}

///FUNCTIONS
int mode_routine(int display_mode)
{
//display M M
initialize_display();
char_to_digit(11);
digitalWrite(D_0, DIG_ON);
digitalWrite(D_3, DIG_ON);

//toogle mode
if (display_mode == 1)
display_mode = 0;
else
display_mode = 1;

delay(800);

//reset?
if (digitalRead(MODE) == HIGH)
{
//display E E
initialize_display();
char_to_digit(15);
digitalWrite(D_0, DIG_ON);
digitalWrite(D_3, DIG_ON);

//wait for button hold
delay(5000);

//erase
if (digitalRead(MODE) == HIGH)
{
clear_save_EEPROM();
cnt.cnt = 0;
cnt.cnt_k = 0;
cnt.cnt_m = 0;
}

initialize_display();

//wait for button release
delay(4000);
}

//clean up
initialize_display();

return display_mode;
}

void clear_save_EEPROM (void)
{
int i = 0;

for (i=0; i<EEPROM_SAVE; i++)
EEPROM.write(i, 0);

}

void store_save_EEPROM (save temp_save_file)
{
int i = 0;

unsigned int temp_cnt = temp_save_file.cnt;
unsigned int temp_cnt_k = temp_save_file.cnt_k;
unsigned int temp_cnt_m = temp_save_file.cnt_m;

//save cnt
int cur_BIT = 1; // BIT_0
for (i=0; i<16; i++) //16 BIT int
{
if (temp_cnt & cur_BIT) //is temp_BIT active?
{
EEPROM.write(i, 1); //set the BIT in EEPROM to 1
}
else
{
EEPROM.write(i, 0); //set the BIT in EEPROM to 0
}

cur_BIT *= 2; //go to next BIT

}

//save cnt_k
cur_BIT = 1; // BIT_0
for (i=0; i<16; i++) //8 BIT char
{
if (temp_cnt_k & cur_BIT) //is temp_BIT active?
{
EEPROM.write(i+16, 1); //set the BIT in EEPROM to 1 (skipping already written memory)
}
else
{
EEPROM.write(i+16, 0); //set the BIT in EEPROM to 0 (skipping already written memory)
}

cur_BIT *= 2; //go to next BIT
}

//save cnt_m
cur_BIT = 1; // BIT_0
for (i=0; i<16; i++) //8 BIT char
{
if (temp_cnt_m & cur_BIT) //is temp_BIT active?
{
EEPROM.write(i+16+16, 1); //set the BIT in EEPROM to 1 (skipping already written memory)
}
else
{
EEPROM.write(i+16+16, 0); //set the BIT in EEPROM to 0 (skipping already written memory)
}

cur_BIT *= 2; //go to next BIT
}
}

save read_save_EEPROM (void)
{
unsigned int temp_cnt = 0;
unsigned int temp_cnt_k = 0;
unsigned int temp_cnt_m = 0;

save temp_save_file;

int i = 0;

//reveive cnt
int cur_BIT = 1; // BIT_0
for (i=0; i<16; i++) //16 BIT int
{
int temp_bit = EEPROM.read(i); //read the EEPROM BIT
if (temp_bit == 1) //is temp_BIT active?
{
temp_cnt |= cur_BIT; //set it in the new variable
}

cur_BIT *= 2; //go to next BIT
}

//receive cnt_k
cur_BIT = 1; // BIT_0
for (i=0; i<16; i++) //8 BIT char
{
int temp_bit = EEPROM.read(i+16); //read the EEPROM BIT (skipping already written memory)
if (temp_bit == 1) //is temp_BIT active?
{
temp_cnt_k |= cur_BIT; //set it in the new variable
}

cur_BIT *= 2; //go to next BIT
}

//receive cnt_m
cur_BIT = 1; // BIT_0
for (i=0; i<16; i++) //8 BIT char
{
int temp_bit = EEPROM.read(i+16+16); //read the EEPROM BIT (skipping already written memory)
if (temp_bit == 1) //is temp_BIT active?
{
temp_cnt_m |= cur_BIT; //set it in the new variable
}

cur_BIT *= 2; //go to next BIT
}

//write to struct
temp_save_file.cnt = temp_cnt;
temp_save_file.cnt_k = temp_cnt_k;
temp_save_file.cnt_m = temp_cnt_m;

return temp_save_file;
}

void initialize_display(void)
{
digitalWrite(S_1, SEG_OFF);
digitalWrite(S_2, SEG_OFF);
digitalWrite(S_3, SEG_OFF);
digitalWrite(S_4, SEG_OFF);
digitalWrite(S_5, SEG_ON);
digitalWrite(S_6, SEG_OFF);
digitalWrite(S_7, SEG_OFF);

digitalWrite(D_0, DIG_OFF);
digitalWrite(D_1, DIG_OFF);
digitalWrite(D_2, DIG_OFF);
digitalWrite(D_3, DIG_OFF);

cur_display.A = 0;
cur_display.B = 0;
cur_display.C = 0;
cur_display.D = 0;
}

void count_up (void)
{
cnt.cnt++;

if (cnt.cnt > 999)
{
cnt.cnt = 0;
cnt.cnt_k++;

if (cnt.cnt_k > 999)
{
cnt.cnt_k = 0;
cnt.cnt_m++;
}
}
}

display save_to_display(int mode)
{
display new_display;

if (mode == 1)
{
if (cnt.cnt_k != 0 || cnt.cnt_m != 0)
{
if (cnt.cnt_m != 0)
{
new_display.B = 11; ///m
new_display.A = cnt.cnt_m;

new_display.D = 13; ///h
new_display.C = cnt.cnt_k / 100;

return new_display;
}

new_display.D = 10; ///k

new_display.C = cnt.cnt_k%10;
new_display.B = (cnt.cnt_k/10)%10;
new_display.A = cnt.cnt_k/100;

return new_display;
}
else
{
new_display.D = cnt.cnt%10;
new_display.C = (cnt.cnt/10)%10;
new_display.B = cnt.cnt/100;
new_display.A = 0;

return new_display;
}
}
else
{
new_display.D = cnt.cnt%10;
new_display.C = (cnt.cnt/10)%10;
new_display.B = cnt.cnt/100;
new_display.A = 14; ///C

return new_display;
}

}

void char_to_digit(int character)
{
///ONLY SUPPORTS:
/*
1
2
3
4
5
6
7
8
9
0

10 > k
11 > m
13 > t
14 > C
15 > E
*/

switch (character)
{
case 0: digitalWrite(S_1, SEG_ON);
digitalWrite(S_2, SEG_ON);
digitalWrite(S_3, SEG_ON);
digitalWrite(S_5, SEG_ON);
digitalWrite(S_6, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 1: digitalWrite(S_6, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 2: digitalWrite(S_2, SEG_ON);
digitalWrite(S_3, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_5, SEG_ON);
digitalWrite(S_6, SEG_ON);
break;

case 3: digitalWrite(S_3, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_5, SEG_ON);
digitalWrite(S_6, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 4: digitalWrite(S_1, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_6, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 5: digitalWrite(S_1, SEG_ON);
digitalWrite(S_3, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_5, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 6: digitalWrite(S_1, SEG_ON);
digitalWrite(S_2, SEG_ON);
digitalWrite(S_3, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_5, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 7: digitalWrite(S_3, SEG_ON);
digitalWrite(S_6, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 8: digitalWrite(S_1, SEG_ON);
digitalWrite(S_2, SEG_ON);
digitalWrite(S_3, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_5, SEG_ON);
digitalWrite(S_6, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 9: digitalWrite(S_1, SEG_ON);
digitalWrite(S_3, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_5, SEG_ON);
digitalWrite(S_6, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 10: digitalWrite(S_1, SEG_ON); //k
digitalWrite(S_2, SEG_ON);
digitalWrite(S_5, SEG_ON);
break;

case 11: digitalWrite(S_2, SEG_ON); //m
digitalWrite(S_3, SEG_ON);
digitalWrite(S_7, SEG_ON);
break;

case 13: digitalWrite(S_1, SEG_ON); //t
digitalWrite(S_2, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_5, SEG_ON);
break;

case 14: digitalWrite(S_1, SEG_ON); //C
digitalWrite(S_2, SEG_ON);
digitalWrite(S_3, SEG_ON);
digitalWrite(S_5, SEG_ON);
break;

case 15: digitalWrite(S_1, SEG_ON); //E/E
digitalWrite(S_2, SEG_ON);
digitalWrite(S_3, SEG_ON);
digitalWrite(S_4, SEG_ON);
digitalWrite(S_5, SEG_ON);
break;

default: digitalWrite(S_5, SEG_ON);
break;
}
}

void char_to_digit_off(void)
{
digitalWrite(S_1, SEG_OFF);
digitalWrite(S_2, SEG_OFF);
digitalWrite(S_3, SEG_OFF);
digitalWrite(S_4, SEG_OFF);
digitalWrite(S_5, SEG_OFF);
digitalWrite(S_6, SEG_OFF);
digitalWrite(S_7, SEG_OFF);
}

void save_routine (void)
{
initialize_display();
digitalWrite(X_PRESS, LOW);
digitalWrite(C_PRESS, LOW);

store_save_EEPROM(cnt);

initialize_display();

delay(60000);
}



Please let me know what you think!
posted
awesome
posted

pyan wrote:

awesome
:3
posted
This looks pretty sick, have you thought about like, a basic version, without key counter, just the light gate?
posted

TheMeq wrote:

This looks pretty sick, have you thought about like, a basic version, without key counter, just the light gate?
I can make one of corse but since I allready have one, I don't need a second.
But that would make it a lot easier, because you don't need a micro controller...

Btw, still playing on it. 200k hits, and counting :3
Please sign in to reply.