i want to port my CircuitPython TLC5975 library
to Arduino
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
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! ) and started to play around to get my D9 pin to do something
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
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
i have revisited every step and - as fare as i understand currently -
i have configured all things correctly
does someone has a idea how to debug this?
or can help me with pointing out what i have missed?
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:
// 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() {}
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.
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
and indeed i have it working now as i want it to
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 - '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!