Midi clock ouput using a phase accumulator

I am trying to make a midi clock output (24PPQN) using the UART on an ATMEGA328
I want to use a 32 bit phase accumulator for this.
I am ticking the accumulator at a rate of 8Khz using a 16 bit timer.

to calculate the phase increment values i am using the following formula with python:

isr_freq = 8000
max32 = 1 << 32

bpm = numpy.arange(0,256)
hertz = bpm / 60 * 24
phaseIncrements = hertz / isr_freq * max32;

i then tick the phase inside the ISR, check for a rising edge and add a midi timing tick to a ring buffer.
when the uart is ready i pull some data from the buffer and send it.

ISR(TIMER1_COMPA_vect){
    phase += inc;
    if(phase < inc){
        if(ringBuffer.writable(){
            ringBuffer.write(0xF8);
        }
    }
    if(ringBuffer.readable() && uart.writeable(){
        uart.write(ringBuffer.read());
    }
}

When i test my code it gives me a bpm output with about a 10th of deviation / jitter.

  • ISR seems to be running stable at 8khz, when measured with an oscilloscope.
  • tried out multiple midi devices / monitors which all give more or less the same result.
  • midi connection seems to be good, i am able to send note on/of, cc messages, etc
  • tried it without a ring buffer and instead used a blocking UART write.

I cannot seem to figure out what i am doing wrong.
What am i overlooking here?

Are you talking about jitter or bpm error?

Phase increment or not, your ticks will be scheduled on an 8kHz grid.

Example:

121 bpm -> 165 ticks on the 8kHz grid between clock messages -> actual bpm of 2.5 / (0.000125 x 165) -> 121.21 bpm

Is that the thing you’re considering as a problem?

1 Like

Thanks for the reply.

bpm error, i wasn’t sure yet what is was.

I have been puzzling away with this, but i cant figure out the calculation.

what i think i understand is:
60 sec / 24 pulses = 2.5
1 sec / 8khz = 0.000125
8 khz / hertz = 165

But i am struggling to put it all together.

121 bpm = 121 / 60 beats per second = 24 * 121 / 60 pulses per second.

So 60 / 24 / 121 = 2.5 / 121 seconds between pulses.

Your timer is at 8kHz, so 2.5 / 121 * 8000 = 165.28 clock ticks between pulses. This is not a round number. Either you round it and get an inaccurate bpm without jitter, or you leave it as is and you get the correct average tempo with jitter (roughly 3 out of 4 pulses will be distant by 165 clock ticks, and 1 out of 4 pulses will be distant by 166 clock ticks).

3 Likes

thanks for taking the time to explain this, i think i have better understanding of it now.

but i’m starting to realize my mental math skills are on a pathetic level, which i’m a bit embaressed about :slight_smile:
i’m really enjoying learning embedded programming but the thing that is really slowing me down is a basic calculation like this, which i feel i should be able to grasp quite quickly.
Where should i improve?
basic algebra?
i don’t mind taking large steps back if thats what it takes, i just want to know what i should focus on.

Sorry to revive this after 3 weeks.

If i understand correctly 125BPM should give a tempo without jitter.
2.5 / 125 * 8000 = 160
this is a round number so i should be able to get a stable clock.
This is not case with me.
No matter how i measure the BPM, it is always jumping up and down from 125.0 to 125.1 quite rapitly

Is there perhaps something wrong with my phase increment calculation?

isr_freq = 8000
max32 = 1 << 32

bpm = numpy.arange(0,256)
hertz = bpm / 60 * 24
phaseIncrements = hertz / isr_freq * max32;

If your code was such that a clock pulse is sent every 160th call to the ISR, you would get a jitter-free clock with a tempo of 125 BPM and an ISR rate of 8kHz. To get a clock pulse sent every 160th call to the ISR, you can naively increment a counter and send a pulse every time the counter reaches 160; or use an accurate phase accumulator.

But you’re not using an accurate phase accumulator here because it uses integer maths and things are rounded up! So you have to consider the rounding errors too! This is because 160 does not divide (1<<32), so the “wrap arounds” won’t occur neatly every 160th ISR call - there will be the occasional interval where the wraparound occurs every 159th or 161th call (depending on whether you round up or down). It’s a rare event, though.

In other words, with your present implementation, the conditions for an absolutely jitter-free clock is that:

  • isr_freq / hertz is an integer (so that the clock pulses are aligned on an 8kHz grid).
  • This integer divides (1<<32) (or equivalently is a power of 2 under 32 - so that rounding errors do not occur).

These conditions are met for 156.25 BPM and 78.125 BPM.

You should consider handling the special case where isr_freq / hertz is an integer with an extra piece of code.

I would also advise you to investigate if the code actually sending the 0xf8 introduces additional delays, and if your MIDI clock measuring device does not introduce jitter from its own timing measurements. Have you checked the actual MIDI signal with a scope?

1 Like

thanks for the reply.
just measured the midi signal with a scope.
with 125 bpm i get a signal that alternates between 50hz and 50.13hz.
i will check if my code causes any delays.

Maybe another ISR is occasionally invoked and interrupts the other one? Try to build the most minimalist code (direct UART write with a timer ISR at 160 Hz…) and add one by one all the layers of complexity.

I’m only using timer 1 interrupts.
Maybe i am initializing another one somewhere in my code.
Going to build the minimalist code and build it up as you advised tomorrow.
Really hoping i can solve this thing then.

Ok so after hours testing i finally found the problem (sort of).
After a long time of testing i eventually decided to see if the actual micro controller was the problem, and it turns it was.
The identical code including ringbuffers, serial class, etc, gave me a stable clock on a fresh micro controller.
I’m still not sure what went wrong though.
Thanks so much for your help and patience.
Oh and happy birthday :slight_smile:

Oh wow that’s really strange! Maybe it was set to run on an internal RC oscillator instead of an accurate, external crystal? (Thinking of an AVR whose fuses have not been properly set).

i was thinking maybe the brown out detection fuse wasn’t properly set?
causing the mcu to reset randomly.
i will have to check.