Created
April 22, 2025 17:06
-
-
Save idwpan/a8002e446eba3e6e48e9ca5d3b58bbed to your computer and use it in GitHub Desktop.
Arduino Digital Tempo Controller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Wire.h> | |
#include <LiquidCrystal_I2C.h> | |
// --- LCD Setup --- | |
LiquidCrystal_I2C lcd(0x27, 20, 4); // 20x4 LCD at address 0x27 | |
// --- Pins --- | |
const int ENC_CLK = 2; // Encoder A (interrupt) | |
const int ENC_DT = 3; // Encoder B | |
const int ENC_SW = 10; // Encoder push button | |
const int SW2_PIN = 11; // Step size switch 1 | |
const int SW1_PIN = 12; // Step size switch 2 | |
const int TAP_OUT = 13; // Tap tempo output (to pedal) | |
const unsigned long MIN_DELAY = 67; | |
const unsigned long MAX_DELAY = 10000; | |
unsigned long delay_ms = 450; | |
volatile int encoderDirection = 0; | |
volatile bool encoderMoved = false; | |
bool synced = true; | |
unsigned long lastEncoderActivity = 0; | |
int stepSize = 1; | |
// Tap control | |
bool tapInProgress = false; | |
unsigned long tapTimer = 0; | |
int tapStage = 0; | |
void setup() { | |
// Initialize LCD | |
lcd.init(); | |
lcd.backlight(); | |
// Setup pins | |
pinMode(ENC_CLK, INPUT_PULLUP); | |
pinMode(ENC_DT, INPUT_PULLUP); | |
pinMode(ENC_SW, INPUT_PULLUP); | |
pinMode(SW1_PIN, INPUT_PULLUP); | |
pinMode(SW2_PIN, INPUT_PULLUP); | |
pinMode(TAP_OUT, OUTPUT); | |
digitalWrite(TAP_OUT, LOW); | |
// Setup encoder interrupt | |
attachInterrupt(digitalPinToInterrupt(ENC_CLK), encoderISR, FALLING); | |
updateStepSize(); | |
updateDisplay(); | |
} | |
void loop() { | |
unsigned long now = millis(); | |
// Update step size if needed | |
static bool lastSW1 = HIGH; | |
static bool lastSW2 = HIGH; | |
bool currSW1 = digitalRead(SW1_PIN); | |
bool currSW2 = digitalRead(SW2_PIN); | |
if (currSW1 != lastSW1 || currSW2 != lastSW2) { | |
updateStepSize(); | |
updateDisplay(); | |
lastSW1 = currSW1; | |
lastSW2 = currSW2; | |
} | |
// Handle encoder movement | |
if (encoderMoved) { | |
encoderMoved = false; | |
lastEncoderActivity = now; | |
synced = false; | |
if (encoderDirection > 0) { | |
delay_ms = min(delay_ms + stepSize, MAX_DELAY); | |
} else if (encoderDirection < 0) { | |
delay_ms = max(delay_ms - stepSize, MIN_DELAY); | |
} | |
updateDisplay(); | |
} | |
// Check pushbutton (encoder press) | |
static bool lastButtonState = HIGH; | |
bool currButtonState = digitalRead(ENC_SW); | |
if (lastButtonState == HIGH && currButtonState == LOW) { | |
requestTap(); | |
} | |
lastButtonState = currButtonState; | |
// Handle tap state machine | |
if (tapInProgress) { | |
handleTap(now); | |
} | |
} | |
void encoderISR() { | |
if (digitalRead(ENC_DT) == LOW) { | |
encoderDirection = -1; // CCW | |
} else { | |
encoderDirection = 1; // CW | |
} | |
encoderMoved = true; | |
} | |
void updateStepSize() { | |
bool s1 = digitalRead(SW1_PIN) == LOW; | |
bool s2 = digitalRead(SW2_PIN) == LOW; | |
if (!s1 && !s2) { | |
stepSize = 10; | |
} else if (s1 && !s2) { | |
stepSize = 1; | |
} else if (!s1 && s2) { | |
stepSize = 100; | |
} | |
} | |
void updateDisplay() { | |
lcd.clear(); | |
// Line 1: Tempo Controller (centered) | |
lcd.setCursor(0, 0); | |
lcd.print(centerText("Tempo Controller")); | |
// Line 2: Delay ms (centered) | |
lcd.setCursor(0, 1); | |
String delayText = String(delay_ms) + " ms"; | |
lcd.print(centerText(delayText)); | |
// Line 3: SYNCED / *UNSYNCED* (centered) | |
lcd.setCursor(0, 2); | |
String statusText = synced ? "SYNCED" : "*UNSYNCED*"; | |
lcd.print(centerText(statusText)); | |
// Line 4: Step info (left and right aligned) | |
lcd.setCursor(0, 3); | |
lcd.print("Step:"); | |
String stepText = String(stepSize) + " ms"; | |
lcd.setCursor(20 - stepText.length(), 3); | |
lcd.print(stepText); | |
} | |
String centerText(String text) { | |
int spaces = (20 - text.length()) / 2; | |
String result = ""; | |
for (int i = 0; i < spaces; i++) { | |
result += " "; | |
} | |
result += text; | |
return result; | |
} | |
void requestTap() { | |
if (tapInProgress) return; | |
tapInProgress = true; | |
tapStage = 1; | |
tapTimer = millis(); | |
} | |
void handleTap(unsigned long now) { | |
switch (tapStage) { | |
case 1: // Tap 1 ON | |
digitalWrite(TAP_OUT, HIGH); | |
tapStage = 2; | |
tapTimer = now + 50; | |
break; | |
case 2: // Tap 1 OFF | |
if (now >= tapTimer) { | |
digitalWrite(TAP_OUT, LOW); | |
tapStage = 3; | |
tapTimer = now + (delay_ms - 50); | |
} | |
break; | |
case 3: // Tap 2 ON | |
if (now >= tapTimer) { | |
digitalWrite(TAP_OUT, HIGH); | |
tapStage = 4; | |
tapTimer = now + 50; | |
} | |
break; | |
case 4: // Tap 2 OFF | |
if (now >= tapTimer) { | |
digitalWrite(TAP_OUT, LOW); | |
tapInProgress = false; | |
synced = true; | |
updateDisplay(); | |
} | |
break; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment