Skip to content

Instantly share code, notes, and snippets.

@DragRedSim
Last active September 9, 2022 11:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DragRedSim/c88eae2913853c3e5e8dee6284426549 to your computer and use it in GitHub Desktop.
Save DragRedSim/c88eae2913853c3e5e8dee6284426549 to your computer and use it in GitHub Desktop.
//BUTTON BOX
//USE w ProMicro
//Tested in WIN10 + Assetto Corsa
//AMSTUDIO
//20.8.17
#include <Keypad.h>
#include <Joystick.h>
#define ENABLE_PULLUPS
#define NUMROTARIES 4
#define NUMBUTTONS 24
#define NUMROWS 5
#define NUMCOLS 5
#define QUARTER_STEP
byte buttons[NUMROWS][NUMCOLS] = {
{0,1,2,3,4},
{5,6,7,8,9},
{10,11,12,13,14},
{15,16,17,18,19},
{20,21,22,23},
};
struct rotariesdef {
byte pin1;
byte pin2;
int ccwchar;
int cwchar;
volatile byte state;
};
rotariesdef rotaries[NUMROTARIES] {
{0, 1, 24, 25, 0},
{2, 3, 26, 27, 0},
{4, 5, 28, 29, 0},
{6, 7, 30, 31, 0},
};
#define DROP_THIS_CYCLE 0x80
#define DROP_NEXT_CYCLE 0x40
#define DIR_CW 0x20
#define DIR_CCW 0x10
#define R_START 0x0
#if defined(QUARTER_STEP)
const byte ttable[4][4] = {
{0x0, DIR_CW | 0x1, DIR_CCW | 0x2, R_START | 0x3},
{DIR_CCW | 0x0, 0x1, R_START | 0x2, DIR_CW | 0x3},
{DIR_CW | 0x0, R_START | 0x1, 0x2, DIR_CCW | 0x3},
{R_START | 0x0, DIR_CCW | 0x1, DIR_CW | 0x2, 0x3},
//magic numbers are used to store the last state of the pins for comparison
};
#elif defined(HALF_STEP)
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const byte ttable[6][4] = {
// R_START (00)
{R_START_M, R_CW_BEGIN, R_CCW_BEGIN, R_START},
// R_CCW_BEGIN
{R_START_M | DIR_CCW, R_START, R_CCW_BEGIN, R_START},
// R_CW_BEGIN
{R_START_M | DIR_CW, R_CW_BEGIN, R_START, R_START},
// R_START_M (11)
{R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, R_START},
// R_CW_BEGIN_M
{R_START_M, R_START_M, R_CW_BEGIN_M, R_START | DIR_CW},
// R_CCW_BEGIN_M
{R_START_M, R_CCW_BEGIN_M, R_START_M, R_START | DIR_CCW},
};
#else //full-step
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6
const byte ttable[7][4] = {
// R_START
{R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START},
// R_CW_FINAL
{R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW},
// R_CW_BEGIN
{R_CW_NEXT, R_CW_BEGIN, R_START, R_START},
// R_CW_NEXT
{R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START},
// R_CCW_BEGIN
{R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START},
// R_CCW_FINAL
{R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW},
// R_CCW_NEXT
{R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif
byte rowPins[NUMROWS] = {21, 20, 19, 18, 15};
byte colPins[NUMCOLS] = {14, 16, 10, 9, 8};
Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS);
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,
JOYSTICK_TYPE_GAMEPAD, 32, 0,
false, false, false, false, false, false,
false, false, false, false, false);
void setup() {
Serial.begin(115200);
timer_loop_setup(); // set up an interrupt every 50ms to shift the rotaries state high bits to cycle towards clearing
rotary_init();
Joystick.begin();
}
void loop() {
CheckAllEncoders();
CheckAllButtons();
//delay(100);
}
void CheckAllButtons(void) {
if (buttbx.getKeys())
{
for (int _i = 0; _i < LIST_MAX; _i++)
{
if ( buttbx.key[_i].stateChanged )
{
switch (buttbx.key[_i].kstate) {
case PRESSED:
case HOLD:
Joystick.setButton(buttbx.key[_i].kchar, 1);
break;
case RELEASED:
case IDLE:
Joystick.setButton(buttbx.key[_i].kchar, 0);
break;
}
}
}
}
}
void rotary_init() {
for (int _i = 0; _i < NUMROTARIES; _i++) {
pinMode(rotaries[_i].pin1, INPUT);
pinMode(rotaries[_i].pin2, INPUT);
#ifdef ENABLE_PULLUPS
digitalWrite(rotaries[_i].pin1, HIGH);
digitalWrite(rotaries[_i].pin2, HIGH);
#endif
rotaries[_i].state = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1); // get 2-bit state of encoder at power on
Serial.print(String("Encoder ") + _i + " state: ");
printBinary(rotaries[_i].state);
Serial.println();
}
}
byte rotary_process(int _i) {
byte pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
#ifdef QUARTER_STEP
if(pinstate != (rotaries[_i].state & 0x03)) {
Serial.print(String("Current state for encoder ") + _i + ": ");
printBinary(rotaries[_i].state);
Serial.print(", 0x");
Serial.print(rotaries[_i].state, HEX);
Serial.print(", pinstate ");
Serial.print(pinstate, HEX);
rotaries[_i].state = ttable[rotaries[_i].state & 0x0f][pinstate] + (rotaries[_i].state & (DROP_THIS_CYCLE | DROP_NEXT_CYCLE)); //check truth table against previous and current states (x and y respectively), then add the current status of the drop-cycle bits
Serial.print(", returning ");
printBinary(rotaries[_i].state);
Serial.print(", 0x");
Serial.print(rotaries[_i].state, HEX);
if((rotaries[_i].state & DIR_CW) == DIR_CW) Serial.print(", rotated CW");
if((rotaries[_i].state & DIR_CCW) == DIR_CCW) Serial.print(", rotated CCW");
Serial.println();
}
#endif
return rotaries[_i].state; //changed output filter to not remove the drop-cycles bits 6 and 7
}
void CheckAllEncoders(void) {
for (int _i = 0; _i < NUMROTARIES; _i++) {
byte result = rotary_process(_i);
if ((result & DIR_CCW) == DIR_CCW) {
Serial.print("Returned byte: ");
printBinary(result);
Serial.println();
Serial.print("Triggering CCW button on, state = ");
printBinary(rotaries[_i].state);
Serial.println();
if ((result & DROP_THIS_CYCLE) == DROP_THIS_CYCLE) { //immediately turn button off, since it needs to trigger again
rotaries[_i].state = rotaries[_i].state & ~DROP_THIS_CYCLE;
Joystick.setButton(rotaries[_i].ccwchar, 0);
}
rotaries[_i].state = (rotaries[_i].state | DROP_NEXT_CYCLE) & ~DIR_CCW; //add drop-next-cycle bit, remove directional trigger
Joystick.setButton(rotaries[_i].ccwchar, 1);
Serial.print("Triggered CCW button on, state = ");
printBinary(rotaries[_i].state);
Serial.println();
};
if ((result & DIR_CW) == DIR_CW) {
Serial.print("Returned byte: ");
printBinary(result);
Serial.println();
Serial.print("Triggering CW button on, state = ");
printBinary(rotaries[_i].state);
Serial.println();
if ((result & DROP_THIS_CYCLE) == DROP_THIS_CYCLE) { //immediately turn button off, since it needs to trigger again
rotaries[_i].state = rotaries[_i].state & ~DROP_THIS_CYCLE;
Joystick.setButton(rotaries[_i].cwchar, 0);
}
rotaries[_i].state = (rotaries[_i].state | DROP_NEXT_CYCLE) & ~DIR_CW;
Joystick.setButton(rotaries[_i].cwchar, 1);
Serial.print("Triggered CW button on, state = ");
printBinary(rotaries[_i].state);
Serial.println();
};
};
}
void timer_loop_setup() {
// TIMER 1 for interrupt frequency 20 Hz:
cli(); // stop interrupts
TCCR1A = 0; // set entire TCCR1A register to 0
TCCR1B = 0; // same for TCCR1B
TCNT1 = 0; // initialize counter value to 0
// set compare match register for 10 Hz increments
OCR1A = 24999; // = 16000000 / (64 * 20) - 1 (must be <65536)
// turn on CTC mode
TCCR1B |= (1 << WGM12);
// Set CS12, CS11 and CS10 bits for 64 prescaler
TCCR1B |= (0 << CS12) | (1 << CS11) | (1 << CS10);
// enable timer compare interrupt
TIMSK1 |= (1 << OCIE1A);
sei(); // allow interrupts
}
ISR(TIMER1_COMPA_vect) { // fires every 50ms (20Hz), clears rotary buttons
for (int _i = 0; _i < NUMROTARIES; _i++) {
#ifdef QUARTER_STEP
rotaries[_i].state = rotaries[_i].state & 0xc3; //keep high 2, low 2 bits; high 2 are drop, low 2 are used for state in quarter-step mode
#else
rotaries[_i].state = rotaries[_i].state & 0xc0;
#endif
if ((rotaries[_i].state & DROP_THIS_CYCLE) == DROP_THIS_CYCLE) { //check drop-this-cycle
//clear buttons, reset status bits
Joystick.setButton(rotaries[_i].cwchar, 0);
Joystick.setButton(rotaries[_i].ccwchar, 0);
rotaries[_i].state = rotaries[_i].state & ~DROP_THIS_CYCLE; //remove drop-this-cycle
Serial.print("Rotary buttons off, state = ");
printBinary(rotaries[_i].state);
Serial.println();
};
if ((rotaries[_i].state & DROP_NEXT_CYCLE) == DROP_NEXT_CYCLE) { //check drop-next-cycle
// set bit 7 (drop-this-cycle), clear bit 6 (drop-next-cycle)
rotaries[_i].state = (rotaries[_i].state | DROP_THIS_CYCLE) & ~DROP_NEXT_CYCLE;
Serial.print("Drop-next to drop-this, state = ");
printBinary(rotaries[_i].state);
Serial.println();
}
};
}
void printBinary(byte inByte)
{
Serial.print('b');
for (int b = 7; b >= 0; b--)
{
Serial.print(bitRead(inByte, b));
};
}
@rluigi65
Copy link

rluigi65 commented Sep 9, 2022

I need an arduino program for 12 buttons directly connected to arduino micro ( no matrix) and 3 encoder CTS 288 (1 pulse every 1/4 step) .
Can you help?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment