Skip to content

Instantly share code, notes, and snippets.

@lgbeno
Last active January 6, 2020 23:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lgbeno/01498b829f9f4d23262a43e51fa493b6 to your computer and use it in GitHub Desktop.
Save lgbeno/01498b829f9f4d23262a43e51fa493b6 to your computer and use it in GitHub Desktop.
SAMD51 TC2 EVSYS Not working
#define PA13 22
#define J5E PA13
#define USE_EVSYS
void e_irq() {
TC2->COUNT16.CTRLBSET.bit.CMD = 4;
while(TC2->COUNT16.SYNCBUSY.bit.COUNT);
Serial.println(TC2->COUNT16.COUNT.reg);
}
void setup() {
Serial.begin(115200);
while(!Serial);
Serial.println("TC2 Test");
MCLK->APBAMASK.reg |= MCLK_APBAMASK_EIC;
MCLK->APBBMASK.reg |= MCLK_APBBMASK_TC2 | MCLK_APBBMASK_EVSYS;
GCLK->GENCTRL[7].bit.SRC = 3;
GCLK->PCHCTRL[TC2_GCLK_ID].bit.GEN = 7;
GCLK->PCHCTRL[TC2_GCLK_ID].bit.CHEN = 1;
GCLK->GENCTRL[7].bit.GENEN = 1;
TC2->COUNT16.CTRLA.bit.SWRST = 1;
while(TC2->COUNT16.SYNCBUSY.bit.SWRST);
TC2->COUNT16.INTENSET.reg = TC_INTENSET_MC0 | TC_INTENSET_MC1 | TC_INTENSET_OVF;
#ifdef USE_EVSYS
TC2->COUNT16.EVCTRL.reg = TC_EVCTRL_EVACT_PPW | TC_EVCTRL_TCEI;
#endif
TC2->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 |
#ifndef USE_EVSYS
TC_CTRLA_COPEN0 | TC_CTRLA_COPEN1 |
#endif
TC_CTRLA_CAPTEN0 | TC_CTRLA_CAPTEN1 |
TC_CTRLA_ENABLE;
NVIC_EnableIRQ( TC2_IRQn ) ;
// PA13 connectomux to E
#ifdef USE_EVSYS
PORT->Group[0].PMUX[6].bit.PMUXO = 0;
#else
PORT->Group[0].PMUX[6].bit.PMUXO = 4;
#endif
PORT->Group[0].PINCFG[13].bit.PULLEN = 0;
PORT->Group[0].PINCFG[13].bit.INEN = 1;
PORT->Group[0].PINCFG[13].bit.PMUXEN = 1;
#ifdef USE_EVSYS
attachInterrupt(J5E,e_irq,RISING);
EIC->CTRLA.bit.ENABLE = 0;
EIC->INTENCLR.bit.EXTINT = (1<<13);
EIC->EVCTRL.bit.EXTINTEO = (1<<13);
EIC->CTRLA.bit.ENABLE = 1;
EVSYS->USER[EVSYS_ID_USER_TC2_EVU].reg = 1;
EVSYS->Channel[0].CHANNEL.reg =(uint32_t) (EVSYS_ID_GEN_EIC_EXTINT_13 | EVSYS_CHANNEL_PATH_ASYNCHRONOUS);
#endif
}
void TC2_Handler() {
Serial.print("TC2 IRQ ");
Serial.print(TC2->COUNT16.INTFLAG.reg,HEX);
TC2->COUNT16.INTFLAG.reg = 0x33;
Serial.print(" ");
Serial.print(TC2->COUNT16.CC[0].reg);
Serial.print(" ");
Serial.print(TC2->COUNT16.CC[1].reg);
Serial.print(" ");
Serial.println(TC2->COUNT16.INTFLAG.reg,HEX);
}
void loop() {
delay(500);
}
@bradgrissom
Copy link

I added this comment on the Atmel community forum thread which references this gist:

This thread is one of the only places I've been able to find an example of using the Timer/Counter (TC) with the Event System (EVSYS) in Arduino on the SAMD51.

I was very impressed with @lgbeno's "from-scratch" implementation and I wanted to know what each individual register read and write was doing. So I annotated his example cross-referencing the huge Atmel SAMD5x Datasheet (Reference Manual) (Revision E 06/2019 aka DS60001507E with 2,129 pages).

Its worth noting that:

  • I got rid of his "#ifdef USE_EVSYS" and assumed that we were always using the EVSYS (to make the code more readable)

  • I have not tested this code.

  • I used really long lines to document each line of code, it looks much better in an editor with no line wrapping.

#include <Arduino.h>

#define PA13 22

void e_irq() {
    TC2->COUNT16.CTRLBSET.bit.CMD = 4;
    while(TC2->COUNT16.SYNCBUSY.bit.COUNT);
    Serial.println(TC2->COUNT16.COUNT.reg);
}

void setup() {
    Serial.begin(115200);
    while(!Serial);
    Serial.println("TC2 Test");

    // APB = Advanced Peripheral Bus, see whats connected to each bus on p.20
    MCLK->APBAMASK.reg |= MCLK_APBAMASK_EIC;  // Turn on External Interrupt Controller clock
    MCLK->APBBMASK.reg |= MCLK_APBBMASK_TC2 | MCLK_APBBMASK_EVSYS; // Turn on Timer/Counter 2 and Event System clocks

    // Looks like we're using Generic Clock 7, I'm not sure why...
    GCLK->GENCTRL[7].bit.SRC            = 3; // p.166 GCLK Source: 0x03 is GCLK_GEN1: Generic Clock Generator 1 output
    GCLK->PCHCTRL[TC2_GCLK_ID].bit.GEN  = 7; // p.168 Assign Peripheral Channel Control for TC2 to Generic Clock Generator 7 as above.
    GCLK->PCHCTRL[TC2_GCLK_ID].bit.CHEN = 1; // p.167 "the peripheral channel is enabled"
    GCLK->GENCTRL[7].bit.GENEN          = 1; // p.165 "generator is enabled"

    TC2->COUNT16.CTRLA.bit.SWRST = 1;
    while(TC2->COUNT16.SYNCBUSY.bit.SWRST);

    TC2->COUNT16.INTENSET.reg = TC_INTENSET_MC0       |
                                TC_INTENSET_MC1       |
                                TC_INTENSET_OVF;
    TC2->COUNT16.EVCTRL.reg   = TC_EVCTRL_EVACT_PPW   |
                                TC_EVCTRL_TCEI;
    TC2->COUNT16.CTRLA.reg    = TC_CTRLA_MODE_COUNT16 |
                                TC_CTRLA_CAPTEN0      |
                                TC_CTRLA_CAPTEN1      |
                                TC_CTRLA_ENABLE;
    NVIC_EnableIRQ(TC2_IRQn);

    // PA13 connectomux to E
    // Note there are 4 groups: .arduino15/packages/arduino/tools/CMSIS-Atmel/1.2.0/CMSIS/Device/ATMEL/samd51/include/component/port.h|407| <<typedef struct {>> PortGroup                 Group[4];    /**< \brief Offset: 0x00 PortGroup groups [GROUPS] */
    //     Group[0]   -->    PAxx
    //     Group[1]   -->    PBxx
    //     Group[2]   -->    PCxx
    //     Group[3]   -->    PDxx
    PORT->Group[0].PMUX[6]   .bit.PMUXO  = 0; // p.909 PinMux Odd pins: 2*n + 1, so 2*6+1 = 13 for PA13.  Set to 0 is peripheral function A.
    PORT->Group[0].PINCFG[13].bit.PULLEN = 0; // p.911 Internal pull-up resistor disabled
    PORT->Group[0].PINCFG[13].bit.INEN   = 1; // p.911 Input enabled
    PORT->Group[0].PINCFG[13].bit.PMUXEN = 1; // p.912 Enable the selected peripheral to control the direction and output of the pin

    // External Interrupt Controller
    attachInterrupt(PA13, e_irq, RISING);
    EIC->CTRLA   .bit.ENABLE   = 0;       // p.454, p.463 Below registers can only be written when CTRLA.ENABLE=0
                                          // p.66 I think there are only 16 EIC interrupts as per the register below.  On p.66, what is a "line"?
    EIC->INTENCLR.bit.EXTINT   = (1<<13); // p.468 The external interrupt 13 is enabled
    EIC->EVCTRL  .bit.EXTINTEO = (1<<13); // p.467 Event from pin 13 is enabled and will be generated when this pin matches the external interrupt sensing config.
    EIC->CTRLA   .bit.ENABLE   = 1;       // Enable EIC

    // Event System
    // "evsys" register defined here: .arduino15/packages/arduino/tools/CMSIS-Atmel/1.2.0/CMSIS/Device/ATMEL/samd51/include/instance/evsys.h|699| <<>> #define EVSYS_ID_USER_TC2_EVU       46
    // And the "user" register:       .arduino15/packages/arduino/tools/CMSIS-Atmel/1.2.0/CMSIS/Device/ATMEL/samd51/include/component/evsys.h|544| <<typedef union {>> } EVSYS_USER_Type;
    EVSYS->USER[EVSYS_ID_USER_TC2_EVU].reg = 1; // p.880 Connect evsys channel 0 to TC2 (see note on how reg val x selects channel n = x-1 )
    EVSYS->Channel[0].CHANNEL.reg =(uint32_t) (EVSYS_ID_GEN_EIC_EXTINT_13 | EVSYS_CHANNEL_PATH_ASYNCHRONOUS); // p.873 Select EIC_EXTINT_13 to connect to EVSYS channel 0
}

void TC2_Handler() {
    Serial.print("TC2 IRQ ");
    Serial.print(TC2->COUNT16.INTFLAG.reg,HEX);
    TC2->COUNT16.INTFLAG.reg = 0x33;
    Serial.print(" ");
    Serial.print(TC2->COUNT16.CC[0].reg);
    Serial.print(" ");
    Serial.print(TC2->COUNT16.CC[1].reg);
    Serial.print(" ");
    Serial.println(TC2->COUNT16.INTFLAG.reg,HEX);
}

void loop() {
    delay(500);
}

@bradgrissom
Copy link

Here is a working version that I've tested on the Adafruit NeoTrellis, compiling with the Arduino IDE. It uses the Row0/Column0 button (upper left button) as a GPIO to measure the time between button presses. This example is amazing because it all happens in the background using the TC hardware (Timer/Counter) and EVSYS (Event System). In other words the button presses trigger the event system to capture the timer values, all without using any CPU. Meaning your main loop (and hence the CPU) can be completely consumed with other software tasks and this incredibly accurate timer will be happening completely in the background and very accurately (with 21.33 uSec resolution and a max value of 1.4 seconds before it wraps). For other timing values (longer timer with less resolution or shorter timer with higher resolution), tweak the TC prescaler or change to a 32-bit timer (this example uses a 16-bit timer).

Here is example output from the serial terminal while intermittently pressing on the Row0/Column0 NeoTrellis button:

*Period[0x27ce] 217.39mSec 0.22sec
*Period[0x38f4] 311.04mSec 0.31sec
***************Period[0xc98d] 1100.74mSec 1.10sec
*Period[0x6ce7] 594.75mSec 0.59sec
*Period[0x7ce0] 681.98mSec 0.68sec
*Period[0x804e] 700.71mSec 0.70sec
*Period[0x6b00] 584.36mSec 0.58sec

And here is the full source code based on the above post (originally written by lgbeno) with added details referencing the huge 2,129 page Atmel SAMD51 datasheet (reference manual):

#include <Arduino.h>

// For the Adafruit NeoTrellis
#define COL0  (2)  // PA14 is pin 23.  Pin 2  comes from ~/Arduino/libraries/Adafruit_NeoTrellis_M4_Library/Adafruit_NeoTrellisM4.cpp|17| <<static byte colPins[COLS] = {2, 3, 4, 5, 6, 7, 8, 9}; //connect to the column pinouts of the keypad>> static byte colPins[COLS] = {2, 3, 4, 5, 6, 7, 8, 9}; //connect to the column pinouts of the keypad
#define ROW0  (14) // PA18 is pin 27.  Pin 14 comes from ~/Arduino/libraries/Adafruit_NeoTrellis_M4_Library/Adafruit_NeoTrellisM4.cpp|16| <<static byte rowPins[ROWS] = {14, 15, 16, 17}; //connect to the row pinouts of the keypad>> static byte rowPins[ROWS] = {14, 15, 16, 17}; //connect to the row pinouts of the keypad

int buttonState = 0;
volatile uint16_t cc0 = 0;
volatile uint16_t cc1 = 0;
volatile uint16_t lcc0 = 0; // Last
volatile uint16_t lcc1 = 0; // Last
volatile uint8_t gUpdate = 0;

// Forward Declarations
void TCsetup(void);

void setup() {
    Serial.begin(115200);
    while(!Serial);
    Serial.println("BAG_2020.01.06_1308");

    pinMode(ROW0, INPUT_PULLUP);
    pinMode(COL0, OUTPUT);
    digitalWrite(COL0, LOW);

    TCsetup();
}


volatile float calc = 0.0;
volatile float calcMsec = 0.0;

void loop() {
    /* BAG NOTE: ENABLE THIS TO VERIFY THAT GPIO STATE TRANSITIONS
     *           ARE ACTUALLY HAPPENING.  BUT THIS NOT NECESSARY TO
     *           TRIGGER THE TIMER/COUNTER

    // read the state of the pushbutton value:
    buttonState = digitalRead(ROW0);

    // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
    if (buttonState == HIGH) {
        Serial.printf("bag normally high\n");
    } else {
        Serial.printf("bag low\n");
    }
    */

    if (gUpdate == 1) {
        calc     = (float)(cc0 * (1.0/46875.0));
        calcMsec = (float)(cc0 * (1.0/46875.0) * 1000.0);
        Serial.printf("Period[0x%x] ", cc0);
        Serial.print(calcMsec);
        Serial.printf("mSec ");
        Serial.print(calc);
        Serial.printf("sec\n");
        gUpdate = 0;
    }
}


void TCsetup(void) {
    // APB = Advanced Peripheral Bus, see whats connected to each bus on p.20
    MCLK->APBAMASK.reg |= MCLK_APBAMASK_EIC;  // Turn on External Interrupt Controller clock
    MCLK->APBBMASK.reg |= MCLK_APBBMASK_TC2 | MCLK_APBBMASK_EVSYS; // Turn on Timer/Counter 2 and Event System clocks

    // Looks like we're using Generic Clock 7, I'm not sure why...
    GCLK->GENCTRL[7].bit.SRC            = 3; // p.166 GCLK Source: 0x03 is GCLK_GEN1: Generic Clock Generator 1 output
    GCLK->PCHCTRL[TC2_GCLK_ID].bit.GEN  = 7; // p.168 Assign Peripheral Channel Control for TC2 to Generic Clock Generator 7 as above.
    GCLK->PCHCTRL[TC2_GCLK_ID].bit.CHEN = 1; // p.167 "the peripheral channel is enabled"
    GCLK->GENCTRL[7].bit.GENEN          = 1; // p.165 "generator is enabled"

    // p.1754 Is "TC Register Summary - 16-bit Mode"
    TC2->COUNT16.CTRLA.bit.SWRST = 1;
    while(TC2->COUNT16.SYNCBUSY.bit.SWRST);

    // Note: on p.1755 there is COPENx which: selects the trigger source for capture operation, either events or I/O pin input.
    //       In other words, we can use a GPIO to trigger the TC directly.
    TC2->COUNT16.INTENSET.reg = TC_INTENSET_MC0       |  // p.1741 INTENSET
                                TC_INTENSET_MC1       |  // Enable interrupts for Match/Capture 0, 1, and Overflow
                                TC_INTENSET_OVF;
    TC2->COUNT16.EVCTRL.reg   = TC_EVCTRL_EVACT_PPW   |  // p.1738 EVCTRL  "EVACT_PPW" is Event Action: Period captured in CC0, pulse width in CC1
                                TC_EVCTRL_TCEI;          //        TCEI: TC Event Enable: Incoming events are enabled
    TC2->COUNT16.CTRLA.reg    = TC_CTRLA_MODE_COUNT16 |  // p.1755 CTRLA  p.1757 "MODE" is 16-bit mode
                                TC_CTRLA_CAPTEN0      |  // p.1756 CAPTENx: Enables capture on channel 0
                                TC_CTRLA_CAPTEN1      |  //                 Enables capture on channel 1
                                TC_CTRLA_PRESCALER_DIV1024 | // (48MHz / 1024) is 46,875 Hz or 1/freq is 21.33 uSec per clock tick and max 1.4sec for 0xFFFF (or 65,535 decimal for 16-bit timer)
                                TC_CTRLA_ENABLE;         // p.1757 ENABLE: The peripheral is enabled
    NVIC_EnableIRQ(TC2_IRQn);

    // No longer necessary:
    // We are now configuring our GPIOs using Arduino calls in setup()
    //     PORT->Group[0].PMUX[x]...
    //     PORT->Group[0].PINCFG[x]...

    // External Interrupt Controller
    attachInterrupt(ROW0, e_irq, FALLING);
    EIC->CTRLA   .bit.ENABLE   = 0;       // p.454, p.463 Below registers can only be written when CTRLA.ENABLE=0
                                          // p.66 I think there are only 16 EIC interrupts as per the register below.  On p.66, what is a "line"?
                                          // p.32 See pinmux table: Each literal pin can be assigned to function as one of the 16 EICs.
    EIC->INTENCLR.bit.EXTINT   = (1<<2);  // p.32 shows PA18 is EXTINT[2]   p.468 The external interrupt is enabled
    EIC->EVCTRL  .bit.EXTINTEO = (1<<2);  // p.32 shows PA18 is EXTINT[2]   p.467 Event from pin is enabled and will be generated when this pin matches the external interrupt sensing config.
    EIC->CTRLA   .bit.ENABLE   = 1;       // Enable EIC

    // Event System
    // "evsys" register defined here: .arduino15/packages/arduino/tools/CMSIS-Atmel/1.2.0/CMSIS/Device/ATMEL/samd51/include/instance/evsys.h|699| <<>> #define EVSYS_ID_USER_TC2_EVU       46
    // And the "user" register:       .arduino15/packages/arduino/tools/CMSIS-Atmel/1.2.0/CMSIS/Device/ATMEL/samd51/include/component/evsys.h|544| <<typedef union {>> } EVSYS_USER_Type;
    EVSYS->USER[EVSYS_ID_USER_TC2_EVU].reg = 1; // p.880 Connect evsys channel 0 to TC2 (see note on how reg val x selects channel n = x-1 )
    //EVSYS->Channel[0].CHANNEL.reg =(uint32_t) (EVSYS_ID_GEN_EIC_EXTINT_13 | EVSYS_CHANNEL_PATH_ASYNCHRONOUS); // p.873 Select EIC_EXTINT_13 to connect to EVSYS channel 0
    EVSYS->Channel[0].CHANNEL.reg =(uint32_t) (EVSYS_ID_GEN_EIC_EXTINT_2 | EVSYS_CHANNEL_PATH_ASYNCHRONOUS);
}


//
// THIS IS NOT CURRENTLY GETTING CALLED
// I'm not exactly sure why, but I never see this big, annoying
// print statement.
//
void e_irq() {
    Serial.println("IRQ IRQ IRQ IRQ IRQ IRQ IRQ in e_irq()...");
    TC2->COUNT16.CTRLBSET.bit.CMD = 4;
    while(TC2->COUNT16.SYNCBUSY.bit.COUNT);
    Serial.println(TC2->COUNT16.COUNT.reg);
}


void TC2_Handler() {
    TC2->COUNT16.INTFLAG.reg = 0x33; // Clear all the interrupt bits by writing 1's to them.
    lcc0 = cc0;
    lcc1 = cc1;
    cc0 = TC2->COUNT16.CC[0].reg;
    cc1 = TC2->COUNT16.CC[1].reg;
    if ( (lcc0 != cc0) ||
         (lcc1 != cc1) )
    {
        gUpdate = 1;
    }
    Serial.printf("*");
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment