Simple tight clock bpm teensy 4.1

Hi ! Im aiming at building a step sequencer ! I come here in a total state of humbleness, absolutely no code experience here hehe.

For now, im just trying to add an encoder to increase and decrease the BPM and its been already 2 full days on that with no result hehe.

1- Can anyone could help me with the issue in that code ?

2- Also i was wondering which path should i take to achieve the tighter, stable and efficient clock for a musical sequencer. For now im guessing between timerOne and uClock ? But maybe there are better ways ?

Teensy 4.1

Thanks a lot !

My code for now:


#include <TimerOne.h>

// Define the initial BPM and the corresponding interval in microseconds
int bpm = 120;
int interval_micros = (60000000 / bpm);

// Define the pins for the encoder inputs
#define ENCODER_PIN_A 33
#define ENCODER_PIN_B 34

// Define the pin for the clock output
#define CLOCK_PIN 23

// Initialize the timer interrupt
void timerISR() {
static boolean state = false;
digitalWrite(CLOCK_PIN, state);
state = !state;
}

void setup() {
pinMode(CLOCK_PIN, OUTPUT);

pinMode(ENCODER_PIN_A, INPUT_PULLUP);
pinMode(ENCODER_PIN_B, INPUT_PULLUP);

// Attach interrupt to the encoder pins
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_A), encoderISR, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENCODER_PIN_B), encoderISR, CHANGE);

// Set up Timer1 to generate interrupts at the initial interval
Timer1.initialize(interval_micros);
Timer1.attachInterrupt(timerISR);
}

void loop() {
// do nothing
}

// Interrupt service routine for the encoder pins
void encoderISR() {
static int encoder_pos = 0;
int new_encoder_pos = encoder_pos;
if (digitalRead(ENCODER_PIN_A) == digitalRead(ENCODER_PIN_B)) {
new_encoder_pos--;
} else {
new_encoder_pos++;
}
if (new_encoder_pos > encoder_pos) {
// Encoder turned clockwise
bpm++;
} else if (new_encoder_pos < encoder_pos) {
// Encoder turned counterclockwise
bpm--;
}
encoder_pos = new_encoder_pos;
// Constrain the BPM to reasonable values
bpm = constrain(bpm, 30, 240);
// Update the interval based on the new BPM
interval_micros = (60000000 / bpm);
Timer1.setPeriod(interval_micros);
}

BTW: I know that the encoder physically working since I've tested it with the serial monitor.
 
There is no need to do the encoder readout with your own code. There are a lot of proven libs around doing this for you. Here an example using the EncoderTool. The library allows to attach a callback function which will be invoked whenever the encoder value changes. The callback gets the current value and the difference to the old value passed in which is handy for your application. It also allows to limit the encoder range (hard limits or cyclic limits) which I used for your bpm limits. I also changed your timer from TimerOne (which is 16bits only and won't support periods larger than some 50ms) to the 32bit IntervalTimer.

Hope that helps get you going.

Code:
[size=3][color=#000000][/color][color=#000099]#include[/color] [color=#000099]"EncoderTool.h"[/color][color=#000099][/color]
[color=#000000][/color][color=#001177]using namespace[/color] [color=#000000]EncoderTool[/color][color=#000000];[/color] [color=#000000][/color][color=#0b810d]//https://github.com/luni64/EncoderTool[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0b810d]// Define the initial BPM[/color]
[color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]bpm[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]120[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0b810d]// Define the pins for the encoder inputs[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]encoder_pin_A[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]33[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]encoder_pin_B[/color] [color=#000000]=[/color] [color=#000000][/color][color=#a52a2a]34[/color][color=#000000][/color][color=#000000];[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0b810d]// Define the pin for the clock output[/color]
[color=#000000][/color][color=#001177]constexpr[/color] [color=#000000][/color][color=#0000ff]unsigned[/color] [color=#000000]clock_pin[/color] [color=#000000]=[/color] [color=#000000]LED_BUILTIN[/color][color=#000000];[/color]
[color=#000000][/color]
[color=#000000]IntervalTimer timer[/color][color=#000000];[/color]
[color=#000000]Encoder       bpmEncoder[/color][color=#000000];[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]onTimer[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#000000][b]digitalToggleFast[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]clock_pin[/color][color=#000000]);[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]onEncoderChanged[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000][/color][color=#0000ff]int[/color] [color=#000000]value[/color][color=#000000],[/color] [color=#000000][/color][color=#0000ff]int[/color] [color=#000000]delta[/color][color=#000000])[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000]    Serial[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]println[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]value[/color][color=#000000]);[/color]                          [color=#000000][/color][color=#0b810d]// debugging[/color]
[color=#000000]    timer[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]update[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000][/color][color=#a52a2a]60'000'000[/color] [color=#000000][/color][color=#000000]/[/color] [color=#000000]value[/color][color=#000000]);[/color]               [color=#000000][/color][color=#0b810d]// here, the encoder value directly equals the bpm setting[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]setup[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#000000][b]pinMode[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]clock_pin[/color][color=#000000],[/color] [color=#000000]OUTPUT[/color][color=#000000]);[/color]
[color=#000000][/color]
[color=#000000]    timer[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]begin[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]onTimer[/color][color=#000000],[/color] [color=#000000][/color][color=#a52a2a]60'000'000[/color] [color=#000000][/color][color=#000000]/[/color] [color=#000000]bpm[/color][color=#000000]);[/color]
[color=#000000][/color]
[color=#000000]    bpmEncoder[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]begin[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]encoder_pin_A[/color][color=#000000],[/color] [color=#000000]encoder_pin_B[/color][color=#000000]);[/color] [color=#000000][/color][color=#0b810d]// use standard configuration for mechanical encoders, common ground, one detent per quadrature cycle[/color]
[color=#000000]    bpmEncoder[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]setLimits[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000][/color][color=#a52a2a]30[/color][color=#000000][/color][color=#000000],[/color] [color=#000000][/color][color=#a52a2a]240[/color][color=#000000][/color][color=#000000]);[/color]                  [color=#000000][/color][color=#0b810d]// limit value range to [30,240][/color]
[color=#000000]    bpmEncoder[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]setValue[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]bpm[/color][color=#000000]);[/color]                       [color=#000000][/color][color=#0b810d]// default after startup would be 0. Here, we want to start with initial  bpm value[/color]
[color=#000000]    bpmEncoder[/color][color=#000000].[/color][color=#000000][/color][color=#000000][b]attachCallback[/b][/color][color=#000000][/color][color=#000000]([/color][color=#000000]onEncoderChanged[/color][color=#000000]);[/color]    [color=#000000][/color][color=#0b810d]// invoke onEncoderChanged whenever the encoder value changes[/color]
[color=#000000][/color][color=#000000]}[/color]
[color=#000000][/color]
[color=#000000][/color][color=#0000ff]void[/color] [color=#000000][/color][color=#000000][b]loop[/b][/color][color=#000000][/color][color=#000000]()[/color]
[color=#000000][/color][color=#000000]{[/color]
[color=#000000][/color]    [color=#0b810d]// do nothing[/color]
[color=#000000][/color][color=#000000]}[/color][color=#000000][/color]
[/size]
 
Last edited:
It works !! Its alive haha !! Wow thank you so much

And thanks for the cues also :)

Moving to the next steps now

Have a great day !
 
For the tightest sequencer timing I would suggest running the code in the audio callback. I've done it before on iOS and am working on groovebox sequencer for the teensy.
I am not sure it as simple as using hardware timers but the accuracy will be sample level accurate.
 
Back
Top