From 8116b1182a6ce18baa24cfdabade8c5afbc896cf Mon Sep 17 00:00:00 2001 From: Oleksiy Date: Sun, 6 Apr 2025 22:51:57 +0300 Subject: [PATCH] added encoder acceleration. still needs fine-tuning --- Extra/EncoderTest/EncoderTest.ino | 115 ++++++++++++++++++++++++++++++ Software/Gravity/Gravity.ino | 24 ++++--- Software/Gravity/Interactions.ino | 67 +++++++++-------- 3 files changed, 160 insertions(+), 46 deletions(-) create mode 100644 Extra/EncoderTest/EncoderTest.ino diff --git a/Extra/EncoderTest/EncoderTest.ino b/Extra/EncoderTest/EncoderTest.ino new file mode 100644 index 0000000..bde7c41 --- /dev/null +++ b/Extra/EncoderTest/EncoderTest.ino @@ -0,0 +1,115 @@ +#define ENC_D1_PIN 17 +#define ENC_D2_PIN 4 +bool reverseEnc = false; + +uint8_t encoderStatus; +uint32_t encoderCheckTime = 0; +uint32_t encoderTimeBetweenPulses = 0; +bool encoderDirectionOld; //0 = -, 1 = +, Old because current direction can be determined by encoderChange +int8_t encoderChange = 0; +uint8_t encoderBurstCount = 0; + + + +void checkEncoderStatus() { + bool pin1Status = digitalRead(ENC_D1_PIN); + bool pin2Status = digitalRead(ENC_D2_PIN); + uint8_t newStatus = (pin1Status << 1) | pin2Status; + switch(encoderStatus) { // encoderStatus & 0b00000011 - to check only 2 last bits + case 0b00: + if (newStatus == 0b01) { + encoderChange++; + } else if (newStatus == 0b10) { + encoderChange--; + } + break; + case 0b01: + if (newStatus == 0b11) { + encoderChange++; + } else if (newStatus == 0b00) { + encoderChange--; + } + break; + case 0b11: + if (newStatus == 0b10) { + encoderChange++; + } else if (newStatus == 0b01) { + encoderChange--; + } + break; + case 0b10: + if (newStatus == 0b00) { + encoderChange++; + } else if (newStatus == 0b11) { + encoderChange--; + } + break; + } + //encoderStatus = (encoderStatus << 2); //previous status is now stored in bits 2 and 3 + encoderStatus = bitWrite(encoderStatus, 1, pin1Status); + encoderStatus = bitWrite(encoderStatus, 0, pin2Status); //This can probably be more optimizied with bit logic + + uint32_t currentTime = millis(); + encoderTimeBetweenPulses = currentTime - encoderCheckTime; + encoderCheckTime = currentTime; +} + + + +void setup() { + Serial.begin(9600); + + pinMode(ENC_D1_PIN, INPUT_PULLUP); + pinMode(ENC_D2_PIN, INPUT_PULLUP); + + //Enabling PinChange Interrupts for the encoder + //pins 17 (PC3/PCINT11) and 4 (PD4/PCINT20), ports C and D + PCICR |= 0b00000110; + PCMSK1 |= 0b00001000; + PCMSK2 |= 0b00010000; + + checkEncoderStatus(); + +} + +void loop() { + checkInputs(); +} + +//Encoder interrupts +ISR (PCINT1_vect) { + checkEncoderStatus(); +} +ISR (PCINT2_vect) { + checkEncoderStatus(); +} + +void checkInputs() { + //encoder + // things to try: + // count "burst" changes and apply acceleration (test the period between ticks in a new sketch) + if (encoderChange != 0) { + if (!reverseEnc) { + encoderChange = encoderChange * -1; + } + + if (((encoderChange > 0) != encoderDirectionOld) && encoderTimeBetweenPulses < 200) { //filter out encoder "jumps" + encoderChange = 0; + } + encoderDirectionOld = (encoderChange > 0); + + //encoder acceleration + if (encoderTimeBetweenPulses < 50) { + encoderBurstCount++; + } else { + encoderBurstCount = 0; + } + if (encoderBurstCount > 2) { + encoderChange = encoderChange * 2; + } + + Serial.print("Change: " + String(encoderChange)); + Serial.println(", Period " + String(encoderTimeBetweenPulses)); + + } +} diff --git a/Software/Gravity/Gravity.ino b/Software/Gravity/Gravity.ino index d2b353c..77b2245 100644 --- a/Software/Gravity/Gravity.ino +++ b/Software/Gravity/Gravity.ino @@ -1,5 +1,4 @@ #include -//#include #include #include #include @@ -51,7 +50,7 @@ bool rotateScreen = true; uint16_t CV1Calibration = 512; uint16_t CV2Calibration = 512; -bool showDone = false; + const int subDivs[20] = { -24, -12, -8, -6, -4, -3, -2, 1, 2, 3, 4, 5, 6, 7, 8, 16, 24, 32, 64, 128 }; //positive - divide, negative - multiply, 0 - off @@ -128,6 +127,7 @@ byte menuItem = 0; bool menuItemSelected = false; byte lastMenuItem = 3; byte displayScreen = 0; //0 - main, 1 - sequencer, 2 - settings +bool showDone = false; bool playBtnPushed = false; bool shiftBtnPushed = false; @@ -151,7 +151,6 @@ int extTriggerCount; //unsigned long lastInteractionTime; // used for display timeout U8G2_SSD1306_128X64_NONAME_2_HW_I2C u8g2(U8G2_R2, SCL, SDA, U8X8_PIN_NONE); -//RotaryEncoder encoder(ENC_D1_PIN, ENC_D2_PIN, RotaryEncoder::LatchMode::TWO03); //Font const PROGMEM uint8_t velvetscreen[437] U8G2_FONT_SECTION("velvetscreen") = @@ -210,6 +209,15 @@ void setup() { pinMode(clockOutPin, OUTPUT); + pinMode(ENC_D1_PIN, INPUT_PULLUP); + pinMode(ENC_D2_PIN, INPUT_PULLUP); + + //Enabling PinChange Interrupts for the encoder + //pins 17 (PC3/PCINT11) and 4 (PD4/PCINT20), ports C and D + PCICR |= 0b00000110; + PCMSK1 |= 0b00001000; + PCMSK2 |= 0b00010000; + loadState(); u8g2.begin(); @@ -222,14 +230,6 @@ void setup() { resetClocks(); - pinMode(ENC_D1_PIN, INPUT_PULLUP); - pinMode(ENC_D2_PIN, INPUT_PULLUP); - //Enabling PinChange Interrupts for the encoder - //pins 17 (PC3/PCINT11) and 4 (PD4/PCINT20), ports C and D - PCICR |= 0b00000110; - PCMSK1 |= 0b00001000; - PCMSK2 |= 0b00010000; - FlexiTimer2::set(1, 1.0 / 10000, clock); // 1.0/1000 = 1ms period. If other than 1ms calculateBPMTiming() might need tweaking FlexiTimer2::start(); } @@ -254,6 +254,8 @@ uint32_t encoderCheckTime = 0; uint32_t encoderTimeBetweenPulses = 0; bool encoderDirectionOld; //0 = -, 1 = +, Old because current direction can be determined by encoderChange int8_t encoderChange = 0; +uint8_t encoderBurstCount = 0; + void checkEncoderStatus() { bool pin1Status = digitalRead(ENC_D1_PIN); bool pin2Status = digitalRead(ENC_D2_PIN); diff --git a/Software/Gravity/Interactions.ino b/Software/Gravity/Interactions.ino index 5e3dd13..65b2cdc 100644 --- a/Software/Gravity/Interactions.ino +++ b/Software/Gravity/Interactions.ino @@ -88,33 +88,30 @@ void checkInputs() { } //encoder - // things to try: - // 1 - make checkInputs one if-else chain and move the encoder part to the first condition - // 2 - count "burst" changes and apply acceleration - // 3 - pinchange interrupts? https://github.com/mathertel/RotaryEncoder/blob/master/examples/InterruptRotator/InterruptRotator.ino - //encoder.tick(); - //int encPosition = 0;//encoder.getPosition(); - //int8_t encDirection = 0;//(int)(encoder.getDirection()); if (encoderChange != 0) { - int change = encoderChange; if (!reverseEnc) { - change = change * -1; + encoderChange = encoderChange * -1; } - /*unsigned long ms = 0;//encoder.getMillisBetweenRotations(); - if (encDirectionOld == encDirection && ms < 18) { //encoder acceleration - change = change * 5; - } /* else if (encDirectionOld == encDirection && ms < 80) { - change = change * 2; - } else */ - if (((encoderChange > 0) != encoderDirectionOld) && encoderTimeBetweenPulses < 200) { //filter out encoder "jumps" - change = 0; + if (((encoderChange > 0) != encoderDirectionOld) && encoderTimeBetweenPulses < 150) { //filter out encoder "jumps" + encoderChange = 0; } encoderDirectionOld = (encoderChange > 0); + + //encoder acceleration + if (encoderTimeBetweenPulses < 60) { + encoderBurstCount++; + } else { + encoderBurstCount = 0; + } + if (encoderBurstCount > 2) { // + encoderChange = encoderChange * 3; // These two params need to be finetuned to feel natural + } + if (displayScreen == 0) { byte channelCV; if (!insideTab && !shiftBtnPushed) { //Change tab - displayTab = displayTab + change; + displayTab = displayTab + encoderChange; if (displayTab > 100) { //to address "negative" numbers displayTab = 0; } else if (displayTab > 6) { @@ -124,7 +121,7 @@ void checkInputs() { || (insideTab && menuItem == 0 && (menuItemSelected || shiftBtnPushed))) && displayTab == 0 && masterClockMode == 0) { //Change BPM - bpm = bpm + change; + bpm = bpm + encoderChange; if (bpm > MAXBPM) { bpm = MAXBPM; } else if (bpm < MINBPM) { @@ -137,7 +134,7 @@ void checkInputs() { && (menuItemSelected || shiftBtnPushed))) && displayTab != 0 && channels[displayTab - 1].mode == 0) { - channels[displayTab - 1].subDiv = channels[displayTab - 1].subDiv - change; + channels[displayTab - 1].subDiv = channels[displayTab - 1].subDiv -encoderChange; //channels[displayTab - 1].offset = 0; if (channels[displayTab - 1].subDiv > 100) { channels[displayTab - 1].subDiv = 0; @@ -162,7 +159,7 @@ void checkInputs() { && (menuItemSelected || shiftBtnPushed))) && displayTab != 0 && channels[displayTab - 1].mode == 1) { //Change Random - channels[displayTab - 1].random = channels[displayTab - 1].random + change; + channels[displayTab - 1].random = channels[displayTab - 1].random + encoderChange; if (channels[displayTab - 1].random > 100) { channels[displayTab - 1].random = 0; } else if (channels[displayTab - 1].random > 9) { @@ -174,7 +171,7 @@ void checkInputs() { && (menuItemSelected || shiftBtnPushed))) && displayTab != 0 && channels[displayTab - 1].mode == 2) { //Change SEQ pattern - channels[displayTab - 1].seqPattern = channels[displayTab - 1].seqPattern + change; + channels[displayTab - 1].seqPattern = channels[displayTab - 1].seqPattern + encoderChange; if (channels[displayTab - 1].seqPattern > 100) { channels[displayTab - 1].seqPattern = 0; } else if (channels[displayTab - 1].seqPattern > 15) { @@ -182,7 +179,7 @@ void checkInputs() { } saveState(); } else if (insideTab && !shiftBtnPushed && !menuItemSelected) { - menuItem = menuItem + change; + menuItem = menuItem + encoderChange; if (menuItem > 100) { //for "negative" values menuItem = 0; } else if (menuItem > lastMenuItem) { @@ -196,7 +193,7 @@ void checkInputs() { isPlaying = false; } - masterClockMode = masterClockMode + change; + masterClockMode = masterClockMode + encoderChange; if (masterClockMode > 100) { masterClockMode = 0; } else if (masterClockMode > 2) { //Set to 1 to hide MIDI mode @@ -208,7 +205,7 @@ void checkInputs() { && displayTab == 0 && menuItem == 2 && masterClockMode == 0) { //Modulation channel - bpmModulationChannel = bpmModulationChannel + change; + bpmModulationChannel = bpmModulationChannel + encoderChange; if (bpmModulationChannel == 0 || bpmModulationChannel == 1) { bpmModulationRange = 1; } else if (bpmModulationChannel > 100) { @@ -223,7 +220,7 @@ void checkInputs() { && displayTab == 0 && menuItem == 2 && masterClockMode == 1) { //PPQN - extClockPPQN = extClockPPQN + change; + extClockPPQN = extClockPPQN + encoderChange; if (extClockPPQN > 100) { extClockPPQN = 0; } else if (extClockPPQN > 1) { @@ -234,7 +231,7 @@ void checkInputs() { && (menuItemSelected || shiftBtnPushed) && displayTab == 0 && menuItem == 3) { //Modulation range - bpmModulationRange = bpmModulationRange + change; + bpmModulationRange = bpmModulationRange + encoderChange; if (bpmModulationRange == 0) { bpmModulationRange = 1; } else if (bpmModulationRange > 100) { @@ -247,7 +244,7 @@ void checkInputs() { && (menuItemSelected || shiftBtnPushed) && displayTab != 0 && menuItem == 1) { //Channel Mode - channels[displayTab - 1].mode = channels[displayTab - 1].mode + change; + channels[displayTab - 1].mode = channels[displayTab - 1].mode + encoderChange; if (channels[displayTab - 1].mode > 100) { channels[displayTab - 1].mode = 0; } else if (channels[displayTab - 1].mode > 2) { @@ -260,7 +257,7 @@ void checkInputs() { && (menuItemSelected || shiftBtnPushed) && displayTab != 0 && menuItem == 2 && channels[displayTab - 1].mode == 0) { //Offset - channels[displayTab - 1].offset = channels[displayTab - 1].offset + change; + channels[displayTab - 1].offset = channels[displayTab - 1].offset + encoderChange; if (channels[displayTab - 1].offset == 255) { // 0 - 1 for uint8 is 255 channels[displayTab - 1].offset = 0; } else if (channels[displayTab - 1].offset > channelPulsesPerCycle[displayTab-1]) { @@ -272,7 +269,7 @@ void checkInputs() { && displayTab != 0 && menuItem == 2 && channels[displayTab - 1].mode == 1) { //SUBDIV for RANDOM - channels[displayTab - 1].subDiv = channels[displayTab - 1].subDiv - change; + channels[displayTab - 1].subDiv = channels[displayTab - 1].subDiv - encoderChange; if (channels[displayTab - 1].subDiv > 200) { channels[displayTab - 1].subDiv = 0; } else if (channels[displayTab - 1].subDiv > (sizeof(subDivs) / sizeof(int)) - 1) { @@ -294,7 +291,7 @@ void checkInputs() { } else { channelCV = 0; } - channelCV = channelCV + change; + channelCV = channelCV + encoderChange; if (channelCV == 0 || channelCV > 100) { channelCV = 0; channels[displayTab - 1].CV1Target = 0; @@ -316,7 +313,7 @@ void checkInputs() { } else { channelCV = 0; } - channelCV = channelCV + change; + channelCV = channelCV + encoderChange; if (channelCV == 0 || channelCV > 100) { channelCV = 0; channels[displayTab - 1].CV1Target = 0; @@ -338,7 +335,7 @@ void checkInputs() { } else { channelCV = 0; } - channelCV = channelCV + change; + channelCV = channelCV + encoderChange; if (channelCV == 0 || channelCV > 100) { channelCV = 0; channels[displayTab - 1].CV1Target = 0; @@ -354,14 +351,14 @@ void checkInputs() { saveState(); } } else if (displayScreen == 1 && !isRecording) { - stepNumSelected = stepNumSelected + change; + stepNumSelected = stepNumSelected + encoderChange; if (stepNumSelected > 100) { stepNumSelected = 15; } else if (stepNumSelected > 15) { stepNumSelected = 0; } } else if (displayScreen == 2 && !shiftBtnPushed) { - menuItem = menuItem + change; + menuItem = menuItem + encoderChange; if (menuItem > 100) { //for "negative" values menuItem = 0; } else if (menuItem > lastMenuItem) {