ItsyBitsy M4 - SAMD51 - generate 6MHz..30MHz clock on pin D9

Hello Community,

i want to port my CircuitPython TLC5975 library
to Arduino :wink:
under CP i could get a high speed (6MHz) output on Pin D9 of the
ItsyBitsy M4.
with a little tweaking of CP i got up to ~30MHz :wink:
for higher i would need to switch the clock source..

for the TLC5957 i need a High Speed Clock signal for the Gray-Scale-Clock - up to 33MHz.

thanks to the forum topic with the posts form MartinL
i know that for the ItsyBitsy M4 Arduino defaults is 1.8 Khz - and that is what i can also confirm on my oscilloscope.

in the topic MartinL explains in great detail how to setup the Timers in general.
i tested his example (it woked! :wink: ) and started to play around to get my D9 pin to do something :wink:

first i checked what port D9 is on -
the schematics make this a easy task - it is PA19
now i could look up what timer things are available for PA19 in
the Datasheet (page34):
TC3/WO[1] | TCC1/WO[3] | TCC0/WO[7]
i later found this nice google doc table
(it also lists the Dn pin numbers for the Adafruit boards directly! great!)
it is only a little bit confusing that there it says
TC3/WO1 | TCC0/WO3

but TC3 is the same - in both - and the TCC thing could be easily verified by trying :wink:

so i tryed this for TC3 on P9:

// based on:
// http://forum.arduino.cc/index.php?topic=589655.msg4010877#msg4010877

// Adafruit ItsyBitsy M4 Only:
// Set-up digital pin D9 to output 50Hz, single slope PWM with a 50% duty cycle
void setup()
{

    // Activate timer TC3
    // CLK_TC3_APB
    MCLK->APBBMASK.reg |= MCLK_APBBMASK_TC3;

    // Set up the generic clock (GCLK7)
    GCLK->GENCTRL[7].reg =
        // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
        GCLK_GENCTRL_DIV(1) |
        // Set the duty cycle to 50/50 HIGH/LOW
        GCLK_GENCTRL_IDC |
        // Enable GCLK7
        GCLK_GENCTRL_GENEN |
        // Select 48MHz DFLL clock source
        GCLK_GENCTRL_SRC_DFLL;
        // Select 100MHz DPLL clock source
        //GCLK_GENCTRL_SRC_DPLL1;
        // Select 120MHz DPLL clock source
        //GCLK_GENCTRL_SRC_DPLL0;
    // Wait for synchronization
    while (GCLK->SYNCBUSY.bit.GENCTRL7);

    // for PCHCTRL numbers have a look at Table 14-9. PCHCTRLm Mapping page168ff
    // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf#page=169&zoom=page-width,-8,696
    GCLK->PCHCTRL[26].reg =
        // Enable the TC3 peripheral channel
        GCLK_PCHCTRL_CHEN |
        // Connect generic clock 7 to TC3
        GCLK_PCHCTRL_GEN_GCLK7;

    // Enable the peripheral multiplexer on pin D9
    PORT->Group[g_APinDescription[9].ulPort].
        PINCFG[g_APinDescription[9].ulPin].bit.PMUXEN = 1;

    // Set the D9 (PORT_PA19) peripheral multiplexer to
    // peripheral (even port number) E(6): TC3, Channel 1
    PORT->Group[g_APinDescription[9].ulPort].
        PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXE(4);

    // Set prescaler to 8, 48MHz/8 = 6MHz
    TC3->COUNT8.CTRLA.reg = TC_CTRLA_PRESCALER_DIV8 |
        // Set the reset/reload to trigger on prescaler clock
        TC_CTRLA_PRESCSYNC_PRESC;

    // Set-up TC3 timer for Normal Frequency Generation (NFRQ)
    TC3->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NFRQ;
    // Wait for synchronization
    // while (TC3->COUNT8.SYNCBUSY.bit.WAVE)

    // Set-up the PER (period) register 50Hz PWM
    TC3->COUNT8.PER.reg = 50;
    // Wait for synchronization
    while (TC3->COUNT8.SYNCBUSY.bit.PER);

    // // Set-up the CC (counter compare), channel 7 register for 50% duty-cycle
    // TC3->COUNT8.CC[3].reg = 59999;
    // // Wait for synchronization
    // while (TC3->COUNT8.SYNCBUSY.bit.CC0);

    // Enable timer TC3
    TC3->COUNT8.CTRLA.bit.ENABLE = 1;
    // Wait for synchronization
    while (TC3->COUNT8.SYNCBUSY.bit.ENABLE);
}

void loop() {}

this compiles fine. but i get no signal on D9 :frowning:
i have revisited every step and - as fare as i understand currently -
i have configured all things correctly :wink:

does someone has a idea how to debug this?
or can help me with pointing out what i have missed?

sunny greetings
stefan

update...

on re re reading i found the mentioning of

All that needs to be changed is the digital pin number for the pin configuration (PINCFG) and multiplexer (PMUX) registers from 7 to 4, and as we're now using an odd port pin number (PB13), the PMUX bitfield definition to odd:

humpf :confused: :astonished:

this now generates output :slight_smile:

but i don't yet understand how the Normal Frequency Generation (NFRQ)and Match Frequency Generation (MFRQ)
are supposed to work... have to read and experiment some more on this :wink:

// based on:
// http://forum.arduino.cc/index.php?topic=589655.msg4010877#msg4010877

// Adafruit ItsyBitsy M4 Only:
// Set-up digital pin D9 to output 50Hz, single slope PWM with a 50% duty cycle
void setup()
{

    // Activate timer TC3
    // CLK_TC3_APB
    MCLK->APBBMASK.reg |= MCLK_APBBMASK_TC3;

    // Set up the generic clock (GCLK7)
    GCLK->GENCTRL[7].reg =
        // Divide the 48MHz clock source by divisor 1: 48MHz/1 = 48MHz
        GCLK_GENCTRL_DIV(1) |
        // Set the duty cycle to 50/50 HIGH/LOW
        GCLK_GENCTRL_IDC |
        // Enable GCLK7
        GCLK_GENCTRL_GENEN |
        // Select 48MHz DFLL clock source
        GCLK_GENCTRL_SRC_DFLL;
        // Select 100MHz DPLL clock source
        //GCLK_GENCTRL_SRC_DPLL1;
        // Select 120MHz DPLL clock source
        // GCLK_GENCTRL_SRC_DPLL0;
    // Wait for synchronization
    while (GCLK->SYNCBUSY.bit.GENCTRL7);

    // for PCHCTRL numbers have a look at Table 14-9. PCHCTRLm Mapping page168ff
    // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf#page=169&zoom=page-width,-8,696
    GCLK->PCHCTRL[26].reg =
        // Enable the TC3 peripheral channel
        GCLK_PCHCTRL_CHEN |
        // Connect generic clock 7 to TC3
        GCLK_PCHCTRL_GEN_GCLK7;

    // Enable the peripheral multiplexer on pin D9
    PORT->Group[g_APinDescription[9].ulPort].
        PINCFG[g_APinDescription[9].ulPin].bit.PMUXEN = 1;

    // Set the D9 (PORT_PA19) peripheral multiplexer to
    // peripheral (odd port number) E(6): TC3, Channel 1
    // check if you need even or odd PMUX!!!
    // http://forum.arduino.cc/index.php?topic=589655.msg4064311#msg4064311
    PORT->Group[g_APinDescription[9].ulPort].
        PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXO(4);

    TC3->COUNT8.CTRLA.reg =
        // Set prescaler to 1, 48MHz/1 = 48MHz
        // Set prescaler to 1, 120MHz/1 = 120MHz
        // TC_CTRLA_PRESCALER_DIV1 |
        // Set prescaler to 8, 48MHz/8 = 6MHz
        TC_CTRLA_PRESCALER_DIV8 |
        // Set the reset/reload to trigger on prescaler clock
        TC_CTRLA_PRESCSYNC_PRESC;

    // Set-up TC3 timer for Normal Frequency Generation (NFRQ)
    // TC3->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_NFRQ;
    // Set-up TC3 timer for Match Frequency Generation (MFRQ)
    TC3->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ;
    // Wait for synchronization
    // while (TC3->COUNT8.SYNCBUSY.bit.WAVE)

    // Set-up the PER (period) register 50Hz PWM
    TC3->COUNT8.PER.reg = 200;
    // Wait for synchronization
    while (TC3->COUNT8.SYNCBUSY.bit.PER);

    // // Set-up the CC (counter compare), channel 1 register for 50% duty-cycle
    TC3->COUNT8.CC[1].reg = 20;
    // // Wait for synchronization
    while (TC3->COUNT8.SYNCBUSY.bit.CC1);

    // Enable timer TC3
    TC3->COUNT8.CTRLA.bit.ENABLE = 1;
    // Wait for synchronization
    while (TC3->COUNT8.SYNCBUSY.bit.ENABLE);
}

void loop() {}

Hi s-lighte,

The SAMD51's TC timers aren't as fully featured as the TCC timers. The TCC timers offer more in the way of advanced output control, although the TC timers are still capable of PWM output. Usually I use the TCC timers for PWM output and the TC timers for internal interrupt service routine (ISR) generation.

The difference between Normal PWM (NPWM) and Match PWM (MPWM) on the TC timers, is that NPWM offers two output channels: CC0 and CC1, whereas MPWM offers only one: CC1.

On the TC timers the period (PER) register is disabled in 16-bit (COUNT16) and 32-bit (COUNT32) timer modes. In Normal PWM (NPWM) and in the absence of the PER register, the TOP value of the timer (65535 in 16-bit mode) is used. This however only provides limited frequency control over the PWM output signals themselves. MPWM sacrifices channel CC0 to act as the period (PER) register, thereby providing frequency control, but only on the single CC1 output channel.

In 8-bit (COUNT8) mode by contrast, the TC timer's PER register is activated and allows frequency control on both channels, but only at a reduced 8-bit resolution.

The TC timers on the SAMD51 (unlike the SAMD21), also offer buffered counter compare (CCBUFx) and period (PERBUF) registers. These registers only update the PWM output at the beginning of the timer cycle, therefore preventing glitches from occuring on your output when changing the duty-cycle or period. Changes to the CCx and PER registers take effect on the PWM outputs immediately, regardless of the current position in the timer cycle.

Hi MartinL,
thanks for your post!

i know that the TCs are not full feauterd..
as i only need a freuquency generation and no pwm i thought that this should be fine :wink:

and indeed i have it working now as i want it to :slight_smile:
i think the only point i missed in the last test was that i tried to set CC[1] - but i need to set CC[0]...
(the datasheet just says so - and it works :wink: - 'you have reached your destination' )

my full example sketch for the clock generation and testing can be found at

here are the most interesting parts:

void setup_D9_1MHz() {

    // Activate timer TC3
    // CLK_TC3_APB
    MCLK->APBBMASK.reg |= MCLK_APBBMASK_TC3;

    // Set up the generic clock (GCLK7)
    GCLK->GENCTRL[7].reg =
        // Divide clock source by divisor 1
        GCLK_GENCTRL_DIV(1) |
        // Set the duty cycle to 50/50 HIGH/LOW
        GCLK_GENCTRL_IDC |
        // Enable GCLK7
        GCLK_GENCTRL_GENEN |
        // Select 48MHz DFLL clock source
        // GCLK_GENCTRL_SRC_DFLL;
        // Select 100MHz DPLL clock source
        //GCLK_GENCTRL_SRC_DPLL1;
        // Select 120MHz DPLL clock source
        GCLK_GENCTRL_SRC_DPLL0;
    // Wait for synchronization
    while (GCLK->SYNCBUSY.bit.GENCTRL7);

    // for PCHCTRL numbers have a look at Table 14-9. PCHCTRLm Mapping page168ff
    // http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf#page=169&zoom=page-width,-8,696
    GCLK->PCHCTRL[26].reg =
        // Enable the TC3 peripheral channel
        GCLK_PCHCTRL_CHEN |
        // Connect generic clock 7 to TC3
        GCLK_PCHCTRL_GEN_GCLK7;

    // Enable the peripheral multiplexer on pin D9
    PORT->Group[g_APinDescription[9].ulPort].
        PINCFG[g_APinDescription[9].ulPin].bit.PMUXEN = 1;

    // Set the D9 (PORT_PA19) peripheral multiplexer to
    // peripheral (odd port number) E(6): TC3, Channel 1
    // check if you need even or odd PMUX!!!
    // http://forum.arduino.cc/index.php?topic=589655.msg4064311#msg4064311
    PORT->Group[g_APinDescription[9].ulPort].
        PMUX[g_APinDescription[9].ulPin >> 1].reg |= PORT_PMUX_PMUXO(4);

    TC3->COUNT8.CTRLA.reg =
        // Set prescaler to 1
        // TC_CTRLA_PRESCALER_DIV1 |

        // Set prescaler to 2
        // 120MHz/2 = 60MHz
        TC_CTRLA_PRESCALER_DIV2 |

        // Set prescaler to 8
        // 48MHz/8 = 6MHz
        // TC_CTRLA_PRESCALER_DIV8 |

        // Set prescaler to 16
        // 48MHz/16 = 3MHz
        // TC_CTRLA_PRESCALER_DIV16 |

        // Set prescaler to 64
        // 48MHz/64 = 0.75MHz = 750kHz = 1,33us
        // 120MHz/64 = 1.875MHz = 1875kHz = 0,53us
        // TC_CTRLA_PRESCALER_DIV64 |

        // Set the reset/reload to trigger on prescaler clock
        TC_CTRLA_PRESCSYNC_PRESC;

    // Set-up TC3 timer for
    // Match Frequency Generation (MFRQ)
    // the period time (T) is controlled by the CC0 register.
    // (instead of PER or MAX)
    // WO[0] toggles on each Update condition.
    TC3->COUNT8.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ;
    // Wait for synchronization
    // while (TC3->COUNT8.SYNCBUSY.bit.WAVE)

    // Set-up the CC (counter compare), channel 0 register
    // this sets the period
    // 750 / (2 * (CC0 + 1))  = outputfrequence
    // (750 / 2) / (CC0 + 1)  = outputfrequence
    // (750 / 2) / (2 + 1)  = 125
    // 750kHz / (2 * (255 + 1))  = 1,4648kHz
    // tests
    //       750.00kHz =   1,33us
    //   0 = 375.00kHz =   2.67us
    //   1 = 187.50kHz =   5.35us
    //   2 = 125.00kHz =   8.02us
    //   3 =  93.75kHz =  10.67us
    //   4 =  74.90kHz =  13.40us
    //   5 =  61.80kHz =  16.20us
    //  10 =  33.60kHz =  29.80us
    //  64 =   5.74kHz = 174.00us
    // 128 =   2.89kHz = 346.00us
    // 255 =   1.46kHz = 687.00us
    //
    // (clockfreq / 2) / (CC0 + 1)  = outfreq  | * (CC0 + 1)
    // (clockfreq / 2) = outfreq * (CC0 + 1)   | / outfreq
    // (clockfreq / 2) / outfreq  = CC0 + 1    | -1
    // ((clockfreq / 2) / outfreq) -1  = CC0
    // (750 / 2) / 93.75  = CC0 + 1
    // ((750 / 2) / 93.75) - 1  = CC0
    // --------------------------
    // ((60 / 2) / 2) -1  = CC0
    // (60MHz / 2) / (0 + 1)  = 30MHz
    // (60MHz / 2) / (255 + 1)  = 0,117MHz = 117kHz
    //
    //       60.0MHz
    //   0 = 30.0MHz
    //   1 = 15.0MHz
    //   2 = 10.0MHz
    //   3 =  7.5MHz
    //   4 =  6.0MHz
    //   5 =  5.0MHz
    //   9 =  3.0MHz
    //  10 =  2.7MHz
    //  14 =  2.0MHz
    //  29 =  1.0MHz
    //  59 =  0.5MHz = 500kHz
    //  74 =  0.4MHz
    //  99 =  0.3MHz
    // 149 =  0.2MHz
    // 255 =  0.11MHz
    // start with 1MHz
    TC3->COUNT8.CC[0].reg = 29;
    // Wait for synchronization
    while (TC3->COUNT8.SYNCBUSY.bit.CC1);

    // Enable timer TC3
    TC3->COUNT8.CTRLA.bit.ENABLE = 1;
    // Wait for synchronization
    while (TC3->COUNT8.SYNCBUSY.bit.ENABLE);
}


float gsclock_set_frequency(float frequency_MHz) {
    const float frequency_MHz_min = 0.117 ;
    const float frequency_MHz_max = 30.0;
    if (frequency_MHz < frequency_MHz_min) {
        frequency_MHz = frequency_MHz_min;
    }
    if (frequency_MHz > frequency_MHz_max) {
        frequency_MHz = frequency_MHz_max;
    }
    float frequency_MHz_result = -1;
    // initialise to 1MHz
    uint8_t period_reg = 29;
    float req_raw = ((60 / 2) / frequency_MHz) -1;
    period_reg = int(req_raw);
    set_D9_period_reg(period_reg);
    // calculate actual used frequency
    frequency_MHz_result = (60.0 / 2) / (period_reg + 1);
    return frequency_MHz_result;
}


void set_D9_period_reg(uint8_t period_reg) {
    TC3->COUNT8.CC[0].reg = period_reg;
    while (TC3->COUNT8.SYNCBUSY.bit.CC1);
}

thanks for all your explanations and examples!
that made it really easy to experiment with this things!

sunny greetings
stefan