/* Macro Board Made by Bozarre */ /* * saving presets in eeprom */ #include struct MemSlot { String displayName; int address; byte value; }; MemSlot memory[] = //128 bytes for the Teensy LC { {"alt", 1, 0}, //preset {"bt1", 2, 0}, //button mode (0 = default, 1 = toggle) {"bt2", 3, 0}, {"bt3", 4, 0}, {"bt4", 5, 0}, {"bt5", 6, 0}, {"bt6", 7, 0}, {"bt7", 8, 0}, {"bt8", 9, 0}, {"bt9", 10, 0}, {"bt10", 11, 0}, {"bt11", 12, 0}, {"bt12", 13, 0}, {"effc", 14, 2}, {"afps", 15, 24}, {"clea", 0, 0}, // != 0 -> reset eeprom }; /* * Menu */ bool isInMenu = false; struct MenuItem { String path; bool isValue; String displayName; }; MenuItem menuEntries[] = { {"", true, "alt"}, {"", false, "btn"}, {"", true, "effc"}, {"", true, "afps"}, {"", true, "clea"}, {"/btn", true, "bt1"}, {"/btn", true, "bt2"}, {"/btn", true, "bt3"}, {"/btn", true, "bt4"}, {"/btn", true, "bt5"}, {"/btn", true, "bt6"}, {"/btn", true, "bt7"}, {"/btn", true, "bt8"}, {"/btn", true, "bt9"}, {"/btn", true, "bt10"}, {"/btn", true, "bt11"}, {"/btn", true, "bt12"}, }; String menuCurrentPath = ""; unsigned int menuSelectionIndex = 0; unsigned int menuCurrentPathItems = 0; MenuItem menuCurrentItem; bool menuIsSettingValue = false; /* * 7 Segment 4 digits display */ #include "SevSeg.h" SevSeg sevseg; /* * key button matrix */ #include const byte rows = 4; //four rows const byte cols = 3; //three columns //Hardware inputs ordered from the bottom left = 1 to the top right = 12 on the switch matrix, char keys[rows][cols] = { // assiging arbitraty uid to each button {9,5,1}, {10,6,2}, {11,7,3}, {12,8,4} }; byte rowPins[rows] = {1, 2, 3, 0}; //connect to the row pinouts of the keypad byte colPins[cols] = {21, 22, 23}; //connect to the column pinouts of the keypad Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, rows, cols ); bool buttonsVirtualState[] = { false, false, false, false, false, false, false, false, false, false, false, false, }; /* * Rotary Encoder */ #include SimpleRotary rotaryEncoder(19, 20, 4); byte rotaryEncoderState = 0; // Rotary push button const int BUTTON_Rotary = 4; /* * mode presets and action */ #include "modeAction.h" /* * custom display animations */ #include "animations.h" int animationFrame = 0; bool animationPlaying = false; unsigned int animationLastFrameMillis = 0; unsigned int animationFrameDelayMillis = 64; bool animationIdle = false; bool animationEnabled = true; /* * time cooldown managment */ unsigned int lastInputMillis = 0; unsigned int inputCoolDown = 10; unsigned int sevSegCoolDown = 5000; /* * INIT */ void setup() { //debug Serial.begin(9600); Serial.println("setup start"); //memory initEepromMemory(); applyMemoryChange(); //display initSevSegDisplay(); //Init rotary encoders rotaryEncoder.setDebounceDelay(2); rotaryEncoder.setErrorDelay(100); pinMode(BUTTON_Rotary, INPUT_PULLUP); // Init keyboard output Keyboard.begin(); //init cooldowns lastInputMillis = millis(); Serial.println("setup complete"); playAnimation(0); } void initEepromMemory() { //write eeprom first init byte memValue = EEPROM.read(0); if (memValue > 0) // cannot find settings, writes default ones { for (byte i = 0; i < (sizeof(memory)/sizeof(MemSlot)); i++) { EEPROM.write(memory[i].address, memory[i].value); } EEPROM.write(getMemorySlot("clea")->address,0); } //read eeprom for (byte i = 0; i < (sizeof(memory)/sizeof(MemSlot)); i++) { memory[i].value = EEPROM.read(memory[i].address); } } void initSevSegDisplay() { //Init display byte numDigits = 4; byte digitPins[] = {7, 8, 9, 6}; byte segmentPins[] = {10, 12, 14, 16, 17, 11, 13, 15}; bool resistorsOnSegments = true; //bool updateWithDelaysIn = true; byte hardwareConfig = COMMON_CATHODE; sevseg.begin(hardwareConfig, numDigits, digitPins, segmentPins, resistorsOnSegments); sevseg.setBrightness(90); } /* * Tick Loop */ void loop() { //animation draw tick if (animationIdle && !animationPlaying && animationEnabled && !isInMenu) { playAnimation(4); } if (animationEnabled && animationPlaying && (millis() > (animationLastFrameMillis + animationFrameDelayMillis))) { drawAnimation(); animationLastFrameMillis = millis(); } //INPUTS if (millis() > lastInputMillis + inputCoolDown) { char customKey = keypad.getKey(); //-1 because our array stars with 1 instead of 0 if (customKey > 0) { sendInputOuput(customKey-1); } if (rotaryEncoder.push() == 1) // = pressed { sendInputOuput(14); } rotaryEncoderState = rotaryEncoder.rotate(); if (rotaryEncoderState == 1 || rotaryEncoderState == 2) { if ( rotaryEncoderState == 1 ) //Turned Clockwise { sendInputOuput(13); } if ( rotaryEncoderState == 2 ) //Turned Counter-Clockwise { sendInputOuput(12); } } } /*//clear screen after no input delay if (millis() > lastInputMillis + sevSegCoolDown) { //sevseg.blank(); }*/ if(!animationPlaying && !isInMenu) { drawCustomSegments(); } sevseg.refreshDisplay(); } void printMenu() { Serial.println("printMenu"); Serial.print(menuCurrentPath + "/ "); if (isInMenu) { if(menuIsSettingValue) { sevseg.setNumber(menuGetValue(menuCurrentItem.displayName)); Serial.println(menuGetValue(menuCurrentItem.displayName)); } else { updateMenu(); sevseg.setChars(menuCurrentItem.displayName.c_str()); Serial.println(menuCurrentItem.displayName); } } } void updateMenu() { unsigned int correspondingPathItemIndex = 0; for (unsigned int i = 0; i < (sizeof(menuEntries)/sizeof(MenuItem)); i++) { if (menuEntries[i].path == menuCurrentPath) { if(correspondingPathItemIndex == menuSelectionIndex) { menuCurrentItem = menuEntries[i]; } correspondingPathItemIndex++; } } menuCurrentPathItems = correspondingPathItemIndex; //Serial.println(menuCurrentPathItems); } void menuBack() { if (isInMenu) { if (menuCurrentPath == "/" || menuCurrentPath == "" ) { Serial.println("menuExit"); isInMenu = false; menuSelectionIndex = 0; menuCurrentPath = ""; sevseg.blank(); playAnimation(3); } else { Serial.println("menuBack"); menuSelectionIndex = menuGetSelectionIndexFromPath(menuCurrentPath); menuCurrentPath = menuCurrentPath.substring(0,menuCurrentPath.lastIndexOf('/')); menuIsSettingValue = false; printMenu(); } } } void menuEnter() { Serial.println("menuEnter"); if (isInMenu) { menuCurrentPath = menuCurrentItem.path + '/'; menuCurrentPath+= menuCurrentItem.displayName; menuSelectionIndex = 0; if (menuCurrentItem.isValue) { if (menuIsSettingValue) { for (unsigned int i = 0; i < (sizeof(memory)/sizeof(MemSlot)); i++) { if (memory[i].displayName == menuCurrentItem.displayName) { EEPROM.write(memory[i].address, memory[i].value); Serial.println("value saved in eeprom"); menuBack(); return; } } } else { menuIsSettingValue = true; Serial.println("menuIsSettingValue"); } } printMenu(); } } void menuPrev() { if (isInMenu) { if (menuIsSettingValue) { menuSetValue(menuCurrentItem.displayName, menuGetValue(menuCurrentItem.displayName) - 1); } else if (menuSelectionIndex > 0) { menuSelectionIndex = menuSelectionIndex - 1; } printMenu(); } } void menuNext() { if (isInMenu) { if (menuIsSettingValue) { menuSetValue(menuCurrentItem.displayName, menuGetValue(menuCurrentItem.displayName) + 1); } else if (menuSelectionIndex < menuCurrentPathItems-1) { menuSelectionIndex = menuSelectionIndex + 1; } printMenu(); } } byte menuGetValue(String variableName) { MemSlot* memorySlot = getMemorySlot(variableName); if (memorySlot != NULL) { return memorySlot->value; } return 0; } bool menuSetValue(String variableName, byte newValue) { MemSlot* memorySlot = getMemorySlot(variableName); if (memorySlot != NULL) { memorySlot->value = newValue; applyMemoryChange(); return true; } return false; } void applyMemoryChange() { animationEnabled = (getMemorySlot("effc")->value > 0); animationFrameDelayMillis = 1000 / max(1, getMemorySlot("afps")->value); animationIdle = (getMemorySlot("effc")->value == 2); } int menuGetSelectionIndexFromPath(String path) { int correspondingPathItemIndex = 0; String makePath = ""; String parentPath = path.substring(0,path.lastIndexOf('/')); for (unsigned int i = 0; i < (sizeof(menuEntries)/sizeof(MenuItem)); i++) { if (menuEntries[i].path == parentPath) { makePath = menuEntries[i].path + '/'; makePath += menuEntries[i].displayName; if(makePath == path) { return correspondingPathItemIndex; } correspondingPathItemIndex++; } } return 0; } MemSlot* getMemorySlot(String memSlotName) { for (unsigned int i = 0; i < (sizeof(memory)/sizeof(MemSlot)); i++) { if (memory[i].displayName == memSlotName) { return &memory[i]; } } return NULL; } void sendInputOuput(int input) { //get current presetID int currentPreset = (getMemorySlot("alt")->value) % getPresetsCount(); //Hardware inputs ordered from the bottom left = 1 to the top right = 12 on the switch matrix, Action actionToRun = getAction(currentPreset, input); //getting the virtual inputs from the modes array. //Inputs uids starts with 1, so input - 1 to get the index 0 of the array String inputName = "bt"; inputName += input +1; MemSlot* buttonInputMem = getMemorySlot(inputName); Serial.println(inputName); if (isInMenu) { animationPlaying = false; } else { playAnimation(1); //toggle mode if (buttonInputMem != NULL) { if (buttonInputMem->value == 1) { buttonsVirtualState[input] = !buttonsVirtualState[input]; actionToRun = getAction(currentPreset + (int)!buttonsVirtualState[input], input); //Toggle mode sends the next preset's input if the troggle state is true } if (buttonInputMem->value == 0) { buttonsVirtualState[input] = false; } } clickKey(actionToRun.keyID, actionToRun.keyModifierID); } if (actionToRun.keyID == -1) //menu input, input mapping can be changed in modeAction.h { if (isInMenu) { menuBack(); //playAnimation(3); } else { isInMenu = true; sevseg.blank(); // clear display playAnimation(2); printMenu(); } } else if (isInMenu && input == 12) //menu prev, hardcoded input :/ { menuPrev(); } else if (isInMenu && input == 13) //menu next, hardcoded input :/ { menuNext(); } else if (isInMenu && input == 14) //menu enter, hardcoded input :/ { menuEnter(); } lastInputMillis = millis(); } void clickKey(int key, int modifier) { Serial.println(key); Keyboard.set_modifier(modifier); Keyboard.press(key); Keyboard.release(key); Keyboard.set_modifier(0); Keyboard.send_now(); //i think that helps to avoid key to stay active } void playAnimation(int animID) { if (animationEnabled) { currentAnimationID = animID; animationFrame = 0; animationPlaying = true; } } void drawAnimation() { int currentAnimationFrameTotal = animData[currentAnimationID].animationFrameCount; sevseg.setSegments(animData[currentAnimationID].animationPtr + (animationFrame * (sizeof(const uint8_t)*4))); animationFrame++; if (animationFrame == currentAnimationFrameTotal) { animationFrame = 0; if (!(animData[currentAnimationID].animationLooping)) { animationPlaying = false; sevseg.blank(); if (isInMenu) //not great { printMenu(); } } } } void drawCustomSegments() { uint8_t seg0 = (0b00001000 * buttonsVirtualState[0]) + (0b01000000 * buttonsVirtualState[4]) + (0b00000001 * buttonsVirtualState[8]); uint8_t seg1 = (0b00001000 * buttonsVirtualState[1]) + (0b01000000 * buttonsVirtualState[5]) + (0b00000001 * buttonsVirtualState[9]); uint8_t seg2 = (0b00001000 * buttonsVirtualState[2]) + (0b01000000 * buttonsVirtualState[6]) + (0b00000001 * buttonsVirtualState[10]); uint8_t seg3 = (0b00001000 * buttonsVirtualState[3]) + (0b01000000 * buttonsVirtualState[7]) + (0b00000001 * buttonsVirtualState[11]); sevseg.setSegmentsDigit(0,seg0); sevseg.setSegmentsDigit(1,seg1); sevseg.setSegmentsDigit(2,seg2); sevseg.setSegmentsDigit(3,seg3); }