Timers...

Hi,

this code (orange LED blinking) works perfectly:

#include <Arduino.h>
#include "Adafruit_ZeroTimer.h"

// timer tester
Adafruit_ZeroTimer zt3 = Adafruit_ZeroTimer(3);

//define the interrupt handlers
void TC3_Handler(){
  Adafruit_ZeroTimer::timerHandler(3);
}

// the timer 3 callbacks
void Timer3Callback0()
{
  digitalWrite(6, LOW);
}

void Timer3Callback1()
{
  digitalWrite(6, HIGH);
}

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

  /********************* Timer #3, 16 bit, two PWM outs, period = 65535 */
  zt3.configure(TC_CLOCK_PRESCALER_DIV1024, // prescaler
                TC_COUNTER_SIZE_16BIT,   // bit width of timer/counter
                TC_WAVE_GENERATION_NORMAL_PWM // frequency or PWM mode
                );

  zt3.setCompare(0, 0xFFFF/4);
  zt3.setCompare(1, 0xFFFF/2);
  zt3.setCallback(true, TC_CALLBACK_CC_CHANNEL0, Timer3Callback0);  // this one sets pin low
  zt3.setCallback(true, TC_CALLBACK_CC_CHANNEL1, Timer3Callback1);  // this one sets pin high
  zt3.enable(true);
}

void loop() {
}

From the SAMD21 datasheet:

30.6.2.4 Counter Mode
The counter mode is selected by the Mode bit group in the Control A register (CTRLA.MODE). By default,
the counter is enabled in the 16-bit counter resolution. Three counter resolutions are available:
• COUNT8: The 8-bit TC has its own Period register (PER). This register is used to store the period
value that can be used as the top value for waveform generation.
• COUNT16: 16-bit is the default counter mode. There is no dedicated period register in this mode.
• COUNT32: This mode is achieved by pairing two 16-bit TC peripherals. TC3 is paired with TC4,
and TC5 is paired with TC6. TC7 does not support 32-bit resolution.

Now I want to use a 32 bit timer, instead of only 16 bit, which is possible by pairing two timers (see above).
But how is this written in code?
When I simply change TC_COUNTER_SIZE_16BIT to TC_COUNTER_SIZE_32BIT it does not work any more.

Someone has experience with the Adafruit_ZeroTimer library?

Hi there I can't help tonight with a 32 bit timer, but will have look tomorrow - but I have been struggling badly with the Adafruit_ZeroTimer library on my MKR1010. I have a couple of questions … 1) which version of the Adafruit_ZeroTimer library are you using in your code above? and 2) has anyone managed to use TC4 and drive the output pins successfully? Based on the circuit diagram, TC4 should be available on pins "0" and "1" (ie header J3 pin 9 and pin 10) - these are apparently connected to PA22 and PA23 on the SAMD21 chip according to the schematic. I have failed miserably to get these pins to drive directly from the timer (ie a direct hardware output, not driving the pins as GPIOs from software following an interrrupt). I have also failed to get any of the timers to drive their respective pins correctly. Any help gratefully received. Mark

Here's some code that chains the two 16-bit TC4 and TC5 timers, to create a 32-bit timer. (The SAMD21 datasheet is incorrect, in that it states that TC3 can be chained with TC4).

Setting up TC4 as a 32-bit timer automatically chains it to TC5. Thereafter all communication is conducted through TC4's registers.

The code sets up TC4/TC5 and outputs the timers' 32-bit value (COUNT) to the native USB port console:

// Setup timers TC4/TC5 in 32-bit mode

void setup()
{
  SerialUSB.begin(115200);
  while (!SerialUSB);
  
  GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) |    // Divide the 48MHz system clock by 1 = 48MHz
                     GCLK_GENDIV_ID(5);      // Set division on Generic Clock Generator (GCLK) 5
  while (GCLK->STATUS.bit.SYNCBUSY);        // Wait for synchronization

  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                      GCLK_GENCTRL_GENEN |         // Enable GCLK 5
                      GCLK_GENCTRL_SRC_DFLL48M |   // Set the clock source to 48MHz
                      GCLK_GENCTRL_ID(5);          // Set clock source on GCLK 5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |         // Enable the generic clock...
                      GCLK_CLKCTRL_GEN_GCLK5 |     // ....on GCLK5
                      GCLK_CLKCTRL_ID_TC4_TC5;     // Feed the GCLK5 to TC4 and TC5
  while (GCLK->STATUS.bit.SYNCBUSY);               // Wait for synchronization

  TC4->COUNT32.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                            TC_CTRLA_MODE_COUNT32;         // Set the TC4 timer to 32-bit mode in conjuction with timer TC5
                   
  TC4->COUNT32.CTRLA.bit.ENABLE = 1;               // Enable TC4
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);        // Wait for synchronization
  
  TC4->COUNT32.READREQ.reg = TC_READREQ_RCONT |            // Enable a continuous read request
                             TC_READREQ_ADDR(0x10);        // Offset of the 32-bit COUNT register
  while (TC4->COUNT32.STATUS.bit.SYNCBUSY);                // Wait for (read) synchronization
}

void loop()
{
  SerialUSB.println(TC4->COUNT32.COUNT.reg);       // Output the results
}

To enable the TC4 outputs, it's necessary to enable the pin multiplexer for each pin and then set the mutliplexer switch for peripheral E (TC4/WO[0] and TC4/WO[1] on digital pins 0 and 1):

Just place this code before enabling the timer:

// Enable the pin multiplexer for digital pins 0 and 1
PORT->Group[g_APinDescription[0].ulPort].PINCFG[g_APinDescription[0].ulPin].bit.PMUXEN = 1;
PORT->Group[g_APinDescription[1].ulPort].PINCFG[g_APinDescription[1].ulPin].bit.PMUXEN = 1;

// Set the multiplexer switch to peripheral E for digtial pins 0 and 1
PORT->Group[g_APinDescription[1].ulPort].PMUX[g_APinDescription[1].ulPin >> 1].reg = PORT_PMUX_PMUXO_E | PORT_PMUX_PMUXE_E;

The TC4 timer output waveform then depends on the mode selected: NFRQ, MFRQ, NPWM or MPWM and timing configuration.

Thank you very much for your help - I now have a waveform on the correct pins from Timer 4 and can control exactly what I need... (which is a 'start convert' pulse train with hardware timing accuracy for an external ADC). Can you (easily) point me to any documentation or code that would explain to me how I could have worked this out for myself? (or at least tried to!) I am more hardware oriented than pure software, so I get exactly what had to be done at a register level on the processor. But a) how does the code that you have given me actually interact with poking the registers - I mean where/how is the almost human readable code converted into real register addresses and bit values? b) how would I update the Adfruit_ZeroTimer (.h and .cpp) 2.0.0 files in order to make things work from these higher level calls rather than by your direct (and very snappy) register pokes??

I am not expecting you to explain at length or correct the libraries - but it would be nice to learn my way round the seemingly bottomless library structures !! But - many thanks for you help. You are a star. Mark

Hi mark890,

The main source of documentation is the SAMD21 datasheet. Unfortunately, this is pretty much the only information regarding the microcontroller's registers. Atmel (now Microchip) promotes the use of it's ASF (Atmel Software Foundation), but personally I find it easier to access the registers directly.

The register definitions are buried in the directory, (on my Windows machine at least):

C:\Users\Computer\AppData\Local\Arduino15\packages\arduino\tools\CMSIS-Atmel\1.2.0\CMSIS\Device\ATMEL\samd21\include...

In the include directory you'll find both "component" and "instance" sub directories. Both sub directories contain the various .h files for the microcontroller's peripherals, for example tc.h, tcc.h, sercom.h etc...

The "component" directory contains the declarations for the registers' structure and bitfield definitions, while the "instance" contains the definitions for the registers themselves in each peripheral.

There are two ways of accessing the registers, either using the "instance" definitions:

REG_TC4_COUNT32_CTRLA |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                         TC_CTRLA_MODE_COUNT32;         // Set the TC4 timer to 32-bit mode in conjuction with timer TC5

or using the "component" structure:

TC4->COUNT32.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1 |      // Set prescaler to 1, 48MHz/1 = 48MHz
                          TC_CTRLA_MODE_COUNT32;         // Set the TC4 timer to 32-bit mode in conjuction with timer TC5

The advantage with the structure method, is that it's also possible to specifiy the bitfields:

TC4->COUNT32.CTRLA.bit.ENABLE = 1;               // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);        // Wait for synchronization

I mainly reference the register descriptions at the end of each chapter of the SAMD21 datasheet, to get an idea of what each register bit or bitfield does and combine this with the register definition files opened in Notepad++. I then just paste the definitions into my Arduino IDE sketch.

Arduino activate most of the microcontroller's peripherals in their core code like the TC timers by default, however if you use peripherals that are not, such as the comparator or event system, it's necessary to first activate the perhipheral in the power management register, for example:

REG_PM_APBCMASK |= PM_APBCMASK_EVSYS;     // Switch on the event system peripheral

Again - thanks for taking the time to answer. Armed with the example(s) you gave me and having now found the files (where you said) on my Windows machine - I have a chance of driving the SAMD21 as I need to.

Separately, I have now managed to get SERCOM1 (ie the SPI connections that are brought out) to read and write using DMAs - so I can now transfer data over SPI at many (say 10) MBit/s for sustained continuous transfers - this is considerably faster than the SPI.h library calls which are slow (circa 10us/byte, and only one byte at a time). I don't know if this would be helpful to anyone, or how/where to post the code if it would be. Anyway, its not particularly clever - but it would have helped me had I found it.

Now I (just) need to get the WiFi NINA library to send a UDP packet in less than the 2ms it is taking at the moment - ideally in about 50us so only a 40x speed up!

Anyway, thanks again for you help with the registers and also my original TC4 problem. Mark