Modular synth clock (NANO 33, SAMD21 internal timers)

Hi,

Can anyone share a basic example of a SAMD21 TC count function which has a BPM of 120 (4/4) and a duty cycle of 50%. With both the BPM and duty cycle values updatable.

  • Beats-per-minute: 120 BPM
  • Beats-per-second: 2 Hz
  • Length of 1 beat: 0.5 second
  • Length of 1 bar: 2 second

Reference link: BPM calculator

The BPM and Duty Cycle would be updated using two rotary encoders. The output would be 0-3.3v via a jack socket to control a Eurorack Modular synth.

Background:

I've been working with a NANO 33 IoT and have had multiple issues with timers when transferring code from a UNO/MEGA. Because of this I think it’s time to learn and how to use the internal timers (TC) on the SAM D21 platform. The code Markus Bader supplied in post #8 of this thread helped a lot (link below):

/*
   
   Testing with Arduino NANO 33 IoT
   @author Markus Bader
   @brief this program shows how to use the TC timer with interrupts on an Arduino Zero board
   @email markus.bader@tuwien.ac.at

   https://forum.arduino.cc/index.php?topic=332275.0
   Post: #8
*/

int pin_ovf_led = 2;  // debug pin for overflow led
int pin_mc0_led = 5;  // debug pin for compare led
unsigned int loop_count = 0;
unsigned int irq_ovf_count = 0;

void setup() {

  pinMode(pin_ovf_led, OUTPUT);   // for debug leds
  digitalWrite(pin_ovf_led, LOW); // for debug leds
  pinMode(pin_mc0_led, OUTPUT);   // for debug leds
  digitalWrite(pin_mc0_led, LOW); // for debug leds

  // Enable clock for TC
  REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3) ;
  while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync

  // The type cast must fit with the selected timer mode
  TcCount16* TC = (TcCount16*) TC3; // get timer struct

  TC->CTRLA.reg &= ~TC_CTRLA_ENABLE;   // Disable TC
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16;  // Set Timer counter Mode to 16 bits
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
  TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ; // Set TC as normal Normal Frq
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256;   // Set perscaler (1,2,4,8,64,256,1024)
  //TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024;   // Set perscaler
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // TC->PER.reg = 0xFF;   // Set counter Top using the PER register but the 16/32 bit timer counts allway to max
  // while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  TC->CC[0].reg = 0xFFF;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

  // Interrupts
  TC->INTENSET.reg = 0;              // disable all interrupts
  TC->INTENSET.bit.OVF = 1;          // enable overfollow
  TC->INTENSET.bit.MC0 = 1;          // enable compare match to CC0

  // Enable InterruptVector
  NVIC_EnableIRQ(TC3_IRQn);

  // Enable TC
  TC->CTRLA.reg |= TC_CTRLA_ENABLE;
  while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync

}

void loop() {
  // dummy
  delay(500);
}

void TC3_Handler()
{
  TcCount16* TC = (TcCount16*) TC3; // get timer struct

  if (TC->INTFLAG.bit.OVF == 1) {  // A overflow caused the interrupt
    digitalWrite(pin_ovf_led, irq_ovf_count % 2); // for debug leds
    digitalWrite(pin_mc0_led, HIGH); // for debug leds
    TC->INTFLAG.bit.OVF = 1;    // writing a one clears the flag ovf flag
    irq_ovf_count++;                 // for debug leds
  }

  if (TC->INTFLAG.bit.MC0 == 1) {  // A compare to cc0 caused the interrupt
    digitalWrite(pin_mc0_led, LOW);  // for debug leds
    TC->INTFLAG.bit.MC0 = 1;    // writing a one clears the flag ovf flag
  }

}

https://forum.arduino.cc/index.php?topic=332275.0

Also the official documentation (Atmel-42123-SAM-Timer-Counter-(TC)-Driver_ApplicationNote_AT03263) is worth reading and having to hand. So now I understand some of the terminology and methods but its still confusing.

To my project is based on the SM Tik Tak BPM Clock with CV (control voltage) outputs. With the addition of rotary encoders and OLED display. For this to work effectively the timer count needs to be the primary function and cannot be blocked by any other activity (encoders, display updates, etc). The original project used a timer library but this is not compatible with the SAM D21 platform.

SM Tik-Tak

This is a simple project which could be used as a basis for multiple Eurorack modules.

Circuit components:

  • NANO 33 IoT
  • BPM rotary encoder
  • Duty Cycle rotary encoder
  • BPM out, 3.5mm jack socket
  • Half division out, jack socket
  • Quarter division out, jack socket
  • Eighth division out, jack socket
  • OLED display (BPM, Duty Cycle, Divisions)

The circuit is built and tested.

This was a useful read and the code is easy to work with. The post is from 2016 and its written for the Zero which is compatible with the NANO 33 IoT.

Smoothly Changing a Timer’s Frequency on the Arduino Zero