I have an Adafruit Feather M0 board, and I would like to configure its timers (at least one for now) to count pulses from an external source. I am using Arduino IDE 1.6.9. Just a quick note, Adafruit Feather M0 uses the same microprocessor as the Arduino Zero, except the the former do not have the EDBG chip on the board.
I found this code from MartinL posted here and modified it a little bit to try to configure the TC to count the pulses feed through pin 12 of the Feather M0 board.
void setup() {
Serial.begin(115200);
delay(7000); //have ample time to catch text in serial monitor
Serial.println("My Project");
pinMode(DCDC_EN, OUTPUT);
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 x
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
digitalWrite(DCDC_EN, 0); //Turn off sensor power
delay(3000);
NVIC_DisableIRQ(EIC_IRQn);
NVIC_ClearPendingIRQ(EIC_IRQn);
NVIC_SetPriority(EIC_IRQn, 0);
NVIC_EnableIRQ(EIC_IRQn);
// Enable GCLK for IEC (External Interrupt Controller)
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_EIC));
// Choose EIC external interrupt to use
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
// Enable EIC
EIC->CTRL.bit.ENABLE = 1;
while (EIC->STATUS.bit.SYNCBUSY == 1) { }
uint32_t ulPin = 12; // Board pin to use
EPioType ulPeripheral = PIO_EXTINT;
if ( g_APinDescription[ulPin].ulPin & 1 ) // is pin odd?
{
uint32_t temp ;
// Get whole current setup for both odd and even pins and remove odd one
temp = (PORT->Group[g_APinDescription[ulPin].ulPort].PMUX[g_APinDescription[ulPin].ulPin >> 1].reg) & PORT_PMUX_PMUXE( 0xF ) ;
// Set new muxing
PORT->Group[g_APinDescription[ulPin].ulPort].PMUX[g_APinDescription[ulPin].ulPin >> 1].reg = temp|PORT_PMUX_PMUXO( ulPeripheral ) ;
// Enable port mux
PORT->Group[g_APinDescription[ulPin].ulPort].PINCFG[g_APinDescription[ulPin].ulPin].reg |= PORT_PINCFG_PMUXEN ;
}
else // even pin
{
uint32_t temp ;
temp = (PORT->Group[g_APinDescription[ulPin].ulPort].PMUX[g_APinDescription[ulPin].ulPin >> 1].reg) & PORT_PMUX_PMUXO( 0xF ) ;
PORT->Group[g_APinDescription[ulPin].ulPort].PMUX[g_APinDescription[ulPin].ulPin >> 1].reg = temp|PORT_PMUX_PMUXE( ulPeripheral ) ;
PORT->Group[g_APinDescription[ulPin].ulPort].PINCFG[g_APinDescription[ulPin].ulPin].reg |= PORT_PINCFG_PMUXEN ; // Enable port mux
}
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TC3_EVU); // Set the event user (receiver) as timer TC3
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_EIC_EXTINT_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
REG_TC3_EVCTRL |= TC_EVCTRL_TCEI | // Enable the TC event input
/*TC_EVCTRL_TCINV |*/ // Invert the event input
TC_EVCTRL_EVACT_COUNT; // Set up the timer for counting events
REG_TC3_READREQ = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(TC_COUNT16_COUNT_OFFSET); // Offset of the COUNT register
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
REG_TC3_CTRLBCLR = TC_CTRLBCLR_DIR; // Set register for counter to count up
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (write) synchronization
REG_TC3_COUNT16_COUNT = 0x0000; // Clear timer's COUNT value
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
REG_TC3_CTRLA |= TC_CTRLA_PRESCALER_DIV1 | // Set timer prescaler
TC_CTRLA_ENABLE; // Enable TC3
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
Serial.println("Done with TC init");
delay(1000); //wait for some delay before turning on power to sensors
timer = millis();
digitalWrite(DCDC_EN, 1);
delay(1000); //wait for some delay for power to stabilize
}
void loop() {
// if millis() or timer wraps around, we'll just reset it
if (timer > millis()) timer = millis();
if (millis() - timer > 100) {
timer = millis(); // reset the timer
ReadCountValTC3(); //print COUNT register value every ~100ms.
}
}
void ReadCountValTC3()
{
static uint32_t sampleCntr=0;
if(TC3->COUNT16.INTFLAG.bit.OVF && TC3->COUNT16.INTENSET.bit.OVF)
{
Serial.println("OVF!");
}
else
{
REG_TC3_READREQ = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(TC_COUNT16_COUNT_OFFSET); // Offset address of the COUNT register
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
Serial.println(TC3->COUNT16.COUNT.reg, DEC);
TC3->COUNT16.COUNT.reg = 0x0000;
// REG_TC3_COUNT16_COUNT = 0x0000;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
}
}
What I struggle with is understanding how to configure the EVENT system, the EIC, GCLK, and PORT (MUX's), so I the signal from the pin where I feed the pulses to can propagate to the TC3 timer module. I read these modules in SAMD21 datasheet but I personally think the information there is just not enough for me to be able to manipulate the registers to do what I wanted to do. Or maybe it's just me who thinks it is what it is.
Any help from you guys would be greatly appreciated. Thanks a lot!
My guess is there are not too many 'feathers' out with our forum members which is why no one is answering.
My previous experience with Adafruit forums has not been a great experience, but you did pay a premium for your board and a portion of that cost is for technical support... maybe you will get lucky if you ask in the Adafruit forum.
After much reading and consult I have implemented code to count pulses form external sources applied to Arduino M0 board.
#define PinLED 13
void setup() {
// put your setup code here, to run once:
SerialUSB.begin(19200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
pinMode(PinLED, OUTPUT);
digitalWrite(PinLED, LOW);
REG_PM_APBCMASK |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
PM->APBCMASK.reg |= PM_APBCMASK_TCC0; // Enable TCC0 Bus clock (Timer counter control clock)
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK0 | // .... on GCLK0...
GCLK_CLKCTRL_ID_EIC; // ... to feed the GCLK0 to EIC peripheral
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK_CLKCTRL realizada para EIC");
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable the generic clock...
GCLK_CLKCTRL_GEN_GCLK0 | // ....on GCLK0...
GCLK_CLKCTRL_ID_TCC0_TCC1; // ... to feed the GCLK5 to TCC0 and TCC1 peripheral
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
SerialUSB.println("Configuración GCLK_CLKCTRL realizada para TCC0/TCC1");
/* - Configuración del EIC - */
REG_EIC_EVCTRL |= EIC_EVCTRL_EXTINTEO3; // Enable event from pin on external interrupt 3 (EXTINT03)
attachInterrupt(12, NULL, RISING); // Attach external interrupt to digital pin 12 (external interrupt 3)
SerialUSB.println("Configuración PA19/EXTINT_03 -Pin 12- realizada");
/* - Configuración del EVSYS - */
REG_EVSYS_USER = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel n=0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0); // Set the event user (receiver) as timer TCC0, event 1
SerialUSB.println("Configuración EVE_SYS_USER realizada");
REG_EVSYS_CHANNEL = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT | // No event output edge detection
EVSYS_CHANNEL_PATH_ASYNCHRONOUS | // Set event path as asynchronous
EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
SerialUSB.println("Configuración EVE_SYS_CHANNEL realizada");
/* - Configuración de TCC0 - */
REG_TCC0_CTRLA &=~TCC_CTRLA_ENABLE; // Disable TCC0 peripheral
SerialUSB.println("Comenzando configuración de TCC0");
REG_TCC0_CTRLBCLR |= TCC_CTRLBCLR_DIR; // Clear DIR bit to count up
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for (write) synchronization
SerialUSB.println("Configuración TCC0 CTRLB realizada");
REG_TCC0_EVCTRL |= TCC_EVCTRL_TCEI0 | // Enable the TCC event 0 input
//TCC_EVCTRL_TCEI1 | // Enable the TCC event 1 input
//TCC_EVCTRL_TCINV1 | // Invert the event 1 input
TCC_EVCTRL_EVACT0_COUNT; // Set up TCC timer/counter to count on event
SerialUSB.println("Configuración TCC0_EVCTRL realizada");
REG_TCC0_CTRLA |= TCC_CTRLA_ENABLE; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
SerialUSB.println("Módulo TCC0 habilitado");
}
void loop() {
// put your main code here, to run repeatedly:
delay(1000); // Wait one second
PORT->Group[PORTA].OUTTGL.reg = PORT_OUTTGL_OUTTGL(1<<17); // Toggle PORT_PA17 (Pin 13 = LED)
//REG_PORT_OUTTGL0 = PORT_PA17;
REG_TCC0_CTRLBSET = TCC_CTRLBSET_CMD_READSYNC; // Trigger a read synchronization on the COUNT register
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for the CTRLB register write synchronization
while (TCC0->SYNCBUSY.bit.COUNT); // Wait for the COUNT register read sychronization
SerialUSB.print("TCC0 count: ");
SerialUSB.println(REG_TCC0_COUNT, DEC); // Print the result
}
For incoming pin you must select a pin with external interrup (EXTINTxx) capability and attach an external interrupt ( attachInterrupt() ) with arduino or Feather M0 pin number to complete EIC configuration. variant.cpp file has pin-out connection between arduino pin and SAMD21Gxx chip, you can find it in C:\Users\YourName\AppData\Local\Arduino15\packages\arduino\hardware\samd\1.6.11\variants\arduino_mzero\variant.cpp
Hi Petirrojo31, I tried your code but I always get "TCC0 count: 0" I tried setting the pulldown for the D12 pin but no success. Any idea what could be missing? Does that code work right for you?
Thanks!
Roberto
Here's some code that'll capture and display the period and pulse width of an input signal on D12, using timer TCC0.
The code is designed to work with an RC receiver with an accuracy of 1us. Greater resolution is possible by running the TCC0 timer faster, (than the 1MHz used in this example):
// Setup TCC0 to capture pulse-width and period
volatile boolean periodComplete;
volatile uint16_t isrPeriod;
volatile uint16_t isrPulsewidth;
uint16_t period;
uint16_t pulsewidth;
void setup()
{
SerialUSB.begin(115200); // Send data back on the Zero's native port
while(!SerialUSB); // Wait for the SerialUSB port to be ready
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(3) | // Divide the 48MHz system clock by 3 = 16MHz
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_TCC0_TCC1; // Feed the GCLK5 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
// 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;
//attachInterrupt(12, NULL, HIGH); // Attach interrupts to digital pin 12 (external interrupt 3)
EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO3; // Enable event output on external interrupt 3
EIC->CONFIG[0].reg |= EIC_CONFIG_SENSE3_HIGH; // Set event detecting a HIGH level
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT3; // Clear the interrupt flag on channel 3
EIC->CTRL.reg |= EIC_CTRL_ENABLE; // Enable EIC peripheral
while (EIC->STATUS.bit.SYNCBUSY); // Wait for synchronization
EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) | // Attach the event user (receiver) to channel 0 (n + 1)
EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_1); // Set the event user (receiver) as timer TCC0, event 1
EVSYS->CHANNEL.reg = 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_EIC_EXTINT_3) | // Set event generator (sender) as external interrupt 3
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
TCC0->EVCTRL.reg |= TCC_EVCTRL_MCEI1 | // Enable the match or capture channel 1 event input
TCC_EVCTRL_MCEI0 | //.Enable the match or capture channel 0 event input
TCC_EVCTRL_TCEI1 | // Enable the TCC event 1 input
/*TCC_EVCTRL_TCINV1 |*/ // Invert the event 1 input
TCC_EVCTRL_EVACT1_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
//NVIC_DisableIRQ(TCC0_IRQn);
//NVIC_ClearPendingIRQ(TCC0_IRQn);
NVIC_SetPriority(TCC0_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC0 to 0 (highest)
NVIC_EnableIRQ(TCC0_IRQn); // Connect the TCC0 timer to the Nested Vector Interrupt Controller (NVIC)
TCC0->INTENSET.reg = TCC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TCC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
TCC0->CTRLA.reg |= TCC_CTRLA_CPTEN1 | // Enable capture on CC1
TCC_CTRLA_CPTEN0 | // Enable capture on CC0
TCC_CTRLA_PRESCALER_DIV16; // Set prescaler to 16, 16MHz/16 = 1MHz
TCC0->CTRLA.bit.ENABLE = 1; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop()
{
if (periodComplete) // Check if the period is complete
{
noInterrupts(); // Read the new period and pulse-width
period = isrPeriod;
pulsewidth = isrPulsewidth;
interrupts();
SerialUSB.print(period); // Output the results
SerialUSB.print(F(" "));
SerialUSB.println(pulsewidth);
periodComplete = false; // Start a new period
}
}
void TCC0_Handler() // Interrupt Service Routine (ISR) for timer TCC0
{
// Check for match counter 0 (MC0) interrupt
if (TCC0->INTFLAG.bit.MC0)
{
isrPeriod = TCC0->CC[0].reg; // Copy the period
periodComplete = true; // Indicate that the period is complete
}
// Check for match counter 1 (MC1) interrupt
if (TCC0->INTFLAG.bit.MC1)
{
isrPulsewidth = TCC0->CC[1].reg; // Copy the pulse-width
}
}