In the earlier builds of the Arduino core code, it was possible to set-up a pin as an interrupt by using the attachInterrupt() function and passing a NULL argument as the function callback pointer:
attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
However, in the later builds this was changed so that attachInterrupt() would skip pin set-up if a NULL pointer was detected.
This means at the moment it's necessary to either use attachInterrupt() with a dummy (empty) callback function, or set-up the pin using regsiter manipulation.
I've raised the isse on Github with the Arduino SAMD core developers, as this changes reduces the flexibility of this function.
The first section of code just switches the pin from GPIO to the peripheral multiplexer and then selects the peripheral as the External Interrupt Controller (EIC):
// Enable the port multiplexer on digital pin D12
PORT->Group[g_APinDescription[12].ulPort].PINCFG[g_APinDescription[12].ulPin].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) peripheral on D12
PORT->Group[g_APinDescription[12].ulPort].PMUX[g_APinDescription[12].ulPin >> 1].reg |= PORT_PMUX_PMUXO_A;
The following lines then just activate the interrupt on D12, which is on EIC channel 3 and set the pin sensing to interrupt on a HIGH level:
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
REG_EIC_CONFIG0 |= EIC_CONFIG_SENSE3_HIGH; // Set event detecting a HIGH level
REG_EIC_CTRL |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
I think I probably got the wrong end of the stick with regard to your requirements, it's just that this thread was dealing with capturing an external signal and for this it's necessary to use an external interrupt.
If you're application doesn't require an external interrupt then it's possible to just use TC timer in 32-bit mode with internal interrupts and events.
Setting the TC4 timer to 32-bit mode automatically feeds one timer from another. Essentially it turns timer TC4 into a 32-bit timer. The SAMD21's hardware does this for you, chaining TC4 to TC3 in the background.
Reading the TC4's 32-bit count register gives you a seamless 32-bit result.
It's also possible to set-up CCx counter compare and overflow interrupts in 32-bits.
Well I did that and it shows TC4 value is zero on the serial monitor.
That's because with this code the TC timer is set up to read a high speed external interrupt.
How the TC timer is initialised depends on what you're trying to achieve. It can be set up in number of different ways depending on your requirement.
If you're just requiring a regular internal interrupt then the normal (NFRQ) or match frequency (MFRQ) modes are suitable. If however you require an external PWM output signal then normal (NPWM) or match (MPWM) PWM modes are probably best. There's also the event system, where one peripheral can be used to trigger another without CPU intervention.
TcCount32* TC = (TcCount32*) TC4; // The type cast must fit with the selected timer mode
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT32; // Set Timer counter Mode to 32 bits
TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_NFRQ; // Set TC as normal Frequency
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV256; // Set perscaler
TC->CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC
REG_TC4_READREQ = TC_READREQ_RCONT | // Enable a continuous read request
TC_READREQ_ADDR(0x10); // Offset of the 16 bit COUNT register
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
}
void loop() {
TcCount32* TC = (TcCount32*) TC4; // The type cast must fit with the selected timer mode TcCount32* TC = (TcCount32*) TC3; // The type cast must fit with the selected timer mode
Now I would just like the counter (when rolls over to zero ) it should go to TC5 from TC4 and should not start counting itself from zero. I know I am asking the same thing but I am still there. so,if you can help how can this connection be achieved.
This sounds to be my solution , but I need help in initialization :
**There's also the event system, where one peripheral can be used to trigger another without CPU intervention.**
EVGEN(Event Generator) TC3_MC0 should generate an event to the USER_TC4
No problem, hopefully I can help with the initialization.
First of all I need to know the frequency you intend to clock the timers. I notice in your code you're using a 256 prescaler divider.
My questions are:
Are you intending to clock the timers at 187.5kHz (48MHz/256)?
Do you wish the timers to free run and overflow at their maximum (2^32)?
Regarding the TC timer chaining, setting the TC timers to 32-bit mode is effectively the same as TC timer chaining on the Arduino Due, like in ard_newbie's code:
TC0->TC_BMR = TC_BMR_TC1XC1S_TIOA0; // Timer Counter 0 channel 1 is internally clocked by TIOA0
TC0->TC_CHANNEL[1].TC_CMR = TC_CMR_TCCLKS_XC1 // External clock XC1 selected
| TC_CMR_WAVE // Waveform mode
| TC_CMR_WAVSEL_UP_RC // UP mode with automatic trigger on RC Compare
| TC_CMR_ACPA_CLEAR // Clear TIOA1 on RA compare match
| TC_CMR_ACPC_SET; // Set TIOA1 on RC compare match
It's just that the SAMD21 requires less input from the programmer, as its internal hardware handles this for you. It means that you don't have to use the event system or interrupts to chain the timers.
What I need is to "restart" the TCCount timer (TC4 & slave TC3) to 0 when the value of REG_TC4_COUNT32_COUNT rises a dedicated number (i.e lenght of bit measurement in my application).
Then how can I reset the TC timer to 0 and let it count again ?
You can do this by setting the TC4 (plus TC3) to match frquency (MFRQ) mode.
In MFRQ mode the timer period can be set by the CC0 (counter compare 0) register, instead of the timer's maximum value:
REG_TC4_COUNT32_CC0 = 1919; // Set the TC4 CC0 register as the TOP value in match frequency mode
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
The timer will return to 0 when it reaches the value in the CC0 register.
The timer is set to MFRQ mode in the CTRLA register:
REG_TC4_CTRLA |= TC_CTRLA_PRESCALER_DIV1 | // Set prescaler to 1, 48MHz/1 = 48MHz
TC_CTRLA_WAVEGEN_MFRQ | // Put the timer TC4 into match frequency (MFRQ) mode
TC_CTRLA_MODE_COUNT32 | // Set the TC4 timer to 32-bit mode in conjuction with timer TC3
TC_CTRLA_ENABLE; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
Here I wanted to use Timer 1 which is having 3 channels (T1_CH0; T1_CH1; T1_CH2).
I initialised 2 channels channel 0 and 1.
I have set: channel 0 in waveform mode with master clock (MCK/2 which is 96/2 for due) with trigger on RC compare. After Timer 1 channel 0 is done counting it generates a output TIOA0 and this is fed input to Timer 1 channel 1 .
Set Channel 1 with same parameters as channel 0 (waveform mode, rc compare ) then set the input as TIOA0 instead of master clock.
Then simply print the counter value. (check pg. 861 Clock chaining diagram from datasheet SAM3X / SAM3A Series )
The following sets up the 16-bit TC3 timer at 48MHz and gets it to overflow every microsecond (1MHz). On each overflow the timer outputs an event that is received by the 32-bit TC4 (with TC5 as the slave). TC4 is set up to increment each time it receives an event, in other words it's clocked at 1MHz.
There appears to be an error in the SAMD21 datasheet. It says that TC4 is chained with TC3 for 32-bit mode, in reality however it's chained to TC5.
This code does the same as the Arduino Due TC timers:
// Setup TC3 to clock TC4/TC5 in 32-bit mode at 1MHz using the event system
uint32_t timeMicros;
void setup()
{
SerialUSB.begin(115200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
REG_GCLK_GENDIV = 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
REG_GCLK_GENCTRL = 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*/
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK5 | // ....on GCLK5
GCLK_CLKCTRL_ID_TCC2_TC3; // Feed the GCLK5 to TCC2 and TC3
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_GCLK_CLKCTRL = 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
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC4_EVU); // Set the event user (receiver) as timer TC4
REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_TC3_OVF) | // Set event generator (sender) as TC3 overflow
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
REG_TC4_EVCTRL |= TC_EVCTRL_TCEI | // Enable asynchronous input events on the TC timer
TC_EVCTRL_EVACT_COUNT; // Increment the TC timer each time an event is received
REG_TC3_EVCTRL |= TC_EVCTRL_OVFEO; // Enable an event output on overflow of the TC3 timer
REG_TC3_COUNT16_CC0 = 47; // Set the TC3 CC0 register to overflow every 1us (1MHz)
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_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
TC_CTRLA_ENABLE; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC4_READREQ = 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
REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV1 | // Set prescaler to 1, 48MHz/1 = 48MHz
TC_CTRLA_WAVEGEN_MFRQ | // Put the timer TC3 into match frequency (MFRQ) mode
TC_CTRLA_ENABLE; // Enable TC3
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
// Test timer TC3
//REG_TC3_READREQ = TC_READREQ_RCONT | // Enable a continuous read request
// TC_READREQ_ADDR(0x10); // Offset of the 32-bit COUNT register
//while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
timeMicros = micros();
}
void loop()
{
SerialUSB.print(REG_TC4_COUNT32_COUNT); // Output the results
SerialUSB.print(F(" "));
SerialUSB.println(micros() - timeMicros);
//SerialUSB.println(REG_TC3_COUNT32_COUNT); // Test timer TC3
}
The TC timer is simply counting the number of input pulses. So the time elapsed depends on how fast you're clocking the input and if that input's regular or not. If there's no input, the counter will stop counting.
The match frequency mode just resets the TC timer back to 0 when it's COUNT register matches the value in the CC0 register.
Taking the 1919 value in the example with a regular 40kHz PWM input gives you:
Time elapsed = (1 / 40000) * (1919 + 1) = 48ms
Add 1 to the 1919 to take into account that we're counting from 0.
If you want to stop the timer in your code:
REG_TC4_CTRLBSET = TC_CTRLBCLR_CMD_STOP;
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);
...and to restart...
REG_TC4_CTRLBSET = TC_CTRLBCLR_CMD_RETRIGGER;
while (TC4->COUNT32.STATUS.bit.SYNCBUSY);