I am trying to communicate between two Seeeduino XIAO (chip: ATSAMD21G18A-MU) by way of TCC capture using register timers.
Square wave pulse duration varies from 200ns to 4us.
I found these two code samples, and I am now trying to change them up to make them work the way I intend to read and write these short pulses.
1) XIAO writing square wave (generate output)
This code works, but it is changing the pulse width and not the pulse period. Output is on pin D2
and D3
. How can I make it change the polse period instead of the polse width? Source
// Output 300kHz dual slope PWM on TCC0 with complementary outputs and dead time insertion
void setup()
{
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable GCLK0
GCLK_CLKCTRL_GEN_GCLK0 | // Select GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TCC0_TCC1; // Route GCLK0 to TCC0 and TCC1
PORT->Group[PORTA].PINCFG[10].bit.PMUXEN = 1; // Enable the port multiplexer for port pins PA10 and PA11
PORT->Group[PORTA].PINCFG[11].bit.PMUXEN = 1;
// Select the port pin multiplexer switch to option F for TCC0/WO[2] and TCC0/WO[3] on
// port pins PA10 and PA11 respectively
PORT->Group[PORTA].PMUX[10 >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
TCC0->WAVE.reg = TCC_WAVE_POL2 | // Reverse the signal polarity on channel 2
TCC_WAVE_WAVEGEN_DSBOTTOM; // Dual slope PWM on TCC0
while (TCC0->SYNCBUSY.bit.WAVE); // Wait for synchronization
// 48,000,000 / 300,000 = 160
TCC0->PER.reg = 79; // Set the frequency of the PWM on TCC0 to 300kHz
while(TCC0->SYNCBUSY.bit.PER); // Wait for synchronization
TCC0->CC[2].reg = 40; // Output a 50% duty-cycle
while(TCC0->SYNCBUSY.bit.CC2); // Wait for synchronization
TCC0->CC[3].reg = 35; // Output a 43% duty-cycle
while(TCC0->SYNCBUSY.bit.CC3); // Wait for synchronization
TCC0->CTRLA.bit.ENABLE = 1; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop(){
TCC0->CTRLBSET.reg = TCC_CTRLBSET_LUPD; // Set the Lock Update (LUPD) bit
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for synchroniztion
TCC0->CCB[2].reg = 40; // Output a 50% duty-cycle
while(TCC0->SYNCBUSY.bit.CCB2); // Wait for synchronization
TCC0->CCB[3].reg = 35; // Output a 43% duty-cycle
while(TCC0->SYNCBUSY.bit.CCB3); // Wait for synchronization
TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD; // Clear the Lock Update (LUPD) bit
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for synchroniztion
delay(1000); // Wait for 1 second
TCC0->CTRLBSET.reg = TCC_CTRLBSET_LUPD; // Set the Lock Update (LUPD) bit
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for synchroniztion
TCC0->CCB[2].reg = 20; // Output a 25% duty-cycle
while(TCC0->SYNCBUSY.bit.CCB2); // Wait for synchronization
TCC0->CCB[3].reg = 15; // Output a 18% duty-cycle
while(TCC0->SYNCBUSY.bit.CCB3); // Wait for synchronization
TCC0->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD; // Clear the Lock Update (LUPD) bit
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for synchroniztion
delay(1000);
}
2) XIAO reading square wave (input capture); Period and Pulse-Width (PPW) Capture
This code works, but it reads the frequency over a period of 1 second, and not just the duration of a polse period. Input is on D6
. How can I make these changes to the code for it to work? Source
// Count the number of pulses on pin D6 (PB08) over a 1 second period
void setup()
{
SerialUSB.begin(115200); // Initialise the native serial port
while(!SerialUSB); // Wait for the console to open
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
////////////////////////////////////////////////////////////////////////////////////////
// Generic Clock Initialisation
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) | // Select clock divisor to 1
GCLK_GENDIV_ID(4); // Select GLCK4
GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK
GCLK_GENCTRL_SRC_XOSC32K | // Select GCLK source as
// external 32.768kHz crystal (XOSC32K)
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable generic clock
GCLK_CLKCTRL_GEN_GCLK0 | // GCLK0 at 48MHz
GCLK_CLKCTRL_ID_TCC0_TCC1; // As a clock source for TCC0 and TCC1
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Enable generic clock
GCLK_CLKCTRL_GEN_GCLK4 | // GCLK4 at 32.768kHz
GCLK_CLKCTRL_ID_TCC2_TC3; // As a clock source for TCC2 and TC3
//////////////////////////////////////////////////////////////////////////////////////////////
// TCC2 Initialisation - reference timer: measures a 1s period
TCC2->PER.reg = 32767; // Set the period (PER) register for a
// PWM period of 1s
while (TCC2->SYNCBUSY.bit.PER); // Wait for synchronization
TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM; // Set timer to Normal PWM mode (NPWM)
while (TCC2->SYNCBUSY.bit.WAVE); // Wait for synchronization
TCC2->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT; // Enable oneshot operation
while(TCC2->SYNCBUSY.bit.CTRLB); // Wait for synchronization
NVIC_SetPriority(TCC2_IRQn, 0); // Set the Nested Vector Interrupt Controller
// (NVIC) priority for TCC2 to 0 (highest)
NVIC_EnableIRQ(TCC2_IRQn); // Connect TCC2 to Nested Vector Interrupt
// Controller (NVIC)
TCC2->INTENSET.reg = TCC_INTENSET_OVF; // Enable overflow (OVF) interrupts on TCC2
TCC2->CTRLA.bit.ENABLE = 1; // Enable TCC2
while (TCC2->SYNCBUSY.bit.ENABLE); // Wait for synchronization
////////////////////////////////////////////////////////////////////////////////////////
// TCC0 Initialisation - measurement counter: counts the number of incoming of pulses
PORT->Group[PORTB].PINCFG[8].bit.PMUXEN = 1; // Enable the port multiplexer
// on port pin PB08 (D6)
PORT->Group[PORTB].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_A; // Set-up PB08 (D6) as an
// EIC (interrupt)
EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO8; // Enable event output on external interrupt 8
EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE0_HIGH; // Set interrupt to detect a HIGH level
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT8; // Clear the interrupt flag on channel 8
EIC->CTRL.bit.ENABLE = 1; // 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_0); // Set the event user (receiver)
// as timer TCC0, event 0
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_8) | // Set event generator
// (sender) as external interrupt 8
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender)
// to channel 0
TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI0 | // Enable TCC0 event 0 inputs
TCC_EVCTRL_EVACT0_INC; // Increment the TCC0 counter on receiving an event 0
TCC0->CTRLA.bit.ENABLE = 1; // Enable TCC0
while (TCC0->SYNCBUSY.bit.ENABLE); // Wait for synchronization
}
void loop()
{
startConversion(); // Start a conversion over the 1 second integration window
delay(3500); // Wait for 1 second
}
void startConversion()
{
TCC2->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER; // Retrigger the timer TCC2 to
// start the integration window
while (TCC2->SYNCBUSY.bit.CTRLB); // Wait for synchronization
TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER; // Retrigger the timer TCC0 to
// start the count
while (TCC0->SYNCBUSY.bit.CTRLB); // Wait for synchronization
}
void TCC2_Handler()
{
TCC2->INTFLAG.bit.OVF = 1; // Clear the TCC2 overflow interrupt flag
TCC0->CTRLBSET.reg = 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.println(TCC0->COUNT.reg); // Print the result
}
Some helpful page explaining these timers
https://www.picotech.com/support/topic24051.html
https://shawnhymel.com/1710/arduino-zero-samd21-raw-pwm-using-cmsis/
1 Answer 1
1) Writing short square wave pulses to pin D3
using Seeeduino XIAO
Timer setup derived from this github post and includes update suggestion by MartinL.
void setup() //TCC1 Timer-Setup AT-SAMD21-G18 ARM Cortex M0
{
setupTimers();
changePer(125);
}
void loop() {
// testing
changePer(20);
delay(3000);
for (byte i = 0; i < 15; i++) {
changePer(40 + (i+1)*4);
delay(1000);
}
delay(2000);
}
// Change Pulse Period
void changePer(uint16_t myPer) {
TCC1->CTRLBSET.reg = TCC_CTRLBSET_LUPD; // Set the Lock Update bit
while (TCC1->SYNCBUSY.bit.CTRLB); // Wait for synchronization
TCC1->PERB.reg = myPer; // Set period
while(TCC1->SYNCBUSY.bit.PERB); // Wait for synchronization
TCC1->CCB[1].reg = myPer/2; // Set duty-cycle to 50%
while(TCC1->SYNCBUSY.bit.CCB0); // Wait for synchronization
TCC1->CTRLBCLR.reg = TCC_CTRLBCLR_LUPD; // Clear the Lock Update bit
while (TCC1->SYNCBUSY.bit.CTRLB); // Wait for synchronization
}
// Output PWM on digital pin D3 using timer TCC1 (10-bit resolution)
void setupTimers() {
REG_GCLK_GENDIV = GCLK_GENDIV_DIV(1) | // Divide the 48MHz clock source by divisor N=1: 48MHz/1=48MHz
GCLK_GENDIV_ID(4); // Select Generic Clock (GCLK) 4
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 GCLK4
GCLK_GENCTRL_SRC_DFLL48M | // Set the 48MHz clock source
GCLK_GENCTRL_ID(4); // Select GCLK4
while (GCLK->STATUS.bit.SYNCBUSY) ; // Wait for synchronization
// Enable the port multiplexer for the digital pin D3 and D11 **** g_APinDescription() converts Arduino Pin to SAMD21 pin
PORT->Group[g_APinDescription[3].ulPort].PINCFG[g_APinDescription[3].ulPin].bit.PMUXEN = 1;
// Connect the TCC1 timer to digital output D3 - port pins are paired odd PMUO and even PMUXE
// F & E specify the timers: TCC0, TCC1 and TCC2
// D2 is on PA10 = even, use Device E for TCC1
PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg |= PORT_PMUX_PMUXO_E;
// Feed GCLK4 to TCC0 and TCC1
REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | // Enable GCLK4 to TCC0 and TCC1
GCLK_CLKCTRL_GEN_GCLK4 | // Select GCLK4
GCLK_CLKCTRL_ID_TCC0_TCC1; // Feed GCLK4 to TCC0 and TCC1
while (GCLK->STATUS.bit.SYNCBUSY) ; // Wait for synchronization
// Dual slope PWM operation: timers countinuously count up to PER register value then down 0
REG_TCC1_WAVE |= TCC_WAVE_POL(0xF) | // Reverse the output polarity on all TCC0 outputs
TCC_WAVE_WAVEGEN_DSBOTH; // Setup dual slope PWM on TCC0
while (TCC1->SYNCBUSY.bit.WAVE) ; // Wait for synchronization
// Each timer counts up to a maximum or TOP value set by the PER register,
// this determines the frequency of the PWM operation: Freq = 48Mhz/(2*N*PER)
REG_TCC1_PER = 256; // Set the FreqTcc of the PWM on TCC1 to 24Khz
while (TCC1->SYNCBUSY.bit.PER) ; // Wait for synchronization
// Divide the GCLOCK signal by 1 giving in this case 48MHz (20.83ns) TCC1 timer tick and enable the outputs
REG_TCC1_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | // Divide GCLK4 by 1
TCC_CTRLA_ENABLE; // Enable the TCC0 output
while (TCC1->SYNCBUSY.bit.ENABLE) ; // Wait for synchronization
}
2) Reading short square waves with Seeeduino XIAO pin D2
It is thanks to MartinL that I got this to work. Source
// Setup TC4 to capture pulse-width and period on digital pin D2 on Seeeduino Xiao
volatile boolean periodComplete;
volatile uint32_t isrPeriod;
volatile uint32_t isrPulsewidth;
uint32_t period;
uint32_t pulsewidth;
void setup() {
SerialUSB.begin(115200); // Initialise the native serial port
while(!SerialUSB); // Wait for the console to open
PM->APBCMASK.reg |= PM_APBCMASK_EVSYS; // Switch on the event system peripheral
GCLK->GENDIV.reg = GCLK_GENDIV_DIV(1) | // Divide the 48MHz system clock by 1 = 48MHz
GCLK_GENDIV_ID(4); // Set division on Generic Clock Generator (GCLK) 4
GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC | // Set the duty cycle to 50/50 HIGH/LOW
GCLK_GENCTRL_GENEN | // Enable GCLK 4
GCLK_GENCTRL_SRC_DFLL48M | // Set the clock source to 48MHz
GCLK_GENCTRL_ID(4); // Set clock source on GCLK 4
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | // Route GCLK4 to TC4 and TC5
GCLK_CLKCTRL_GEN_GCLK4 |
GCLK_CLKCTRL_ID_TC4_TC5;
// Enable the port multiplexer on port pin PA10
PORT->Group[PORTA].PINCFG[10].bit.PMUXEN = 1;
// Set-up the pin as an EIC (interrupt) on port pin PA10
PORT->Group[PORTA].PMUX[10 >> 1].reg |= PORT_PMUX_PMUXE_A;
EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO10; // Enable event output on external interrupt 10
EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE2_HIGH; // Set event detecting a HIGH level
EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT10; // Disable interrupts on external interrupt 10
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_TC4_EVU); // Set the event user (receiver) as timer TC4
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_10) | // Set event generator (sender) as external interrupt 10
EVSYS_CHANNEL_CHANNEL(0); // Attach the generator (sender) to channel 0
TC4->COUNT32.EVCTRL.reg = TC_EVCTRL_TCEI | // Enable the TC event input
//TC_EVCTRL_TCINV | // Invert the event input
TC_EVCTRL_EVACT_PPW; // Set up the timer for capture: CC0 period, CC1 pulsewidth
TC4->COUNT32.CTRLC.reg = TC_CTRLC_CPTEN1 | // Enable capture on CC1
TC_CTRLC_CPTEN0; // Enable capture on CC0
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for synchronization
NVIC_SetPriority(TC4_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC4 to 0 (highest)
NVIC_EnableIRQ(TC4_IRQn); // Connect the TC4 timer to the Nested Vector Interrupt Controller (NVIC)
TC4->COUNT32.INTENSET.reg = TC_INTENSET_MC1 | // Enable compare channel 1 (CC1) interrupts
TC_INTENSET_MC0; // Enable compare channel 0 (CC0) interrupts
TC4->COUNT32.CTRLA.reg = //TC_CTRLA_PRESCSYNC_PRESC | // Overflow on precaler clock, (rather than the GCLK)
TC_CTRLA_PRESCALER_DIV1 | // Set prescaler to 1, 48MHz/1 = 48MHz
TC_CTRLA_MODE_COUNT32; // Set TC4/TC5 to 32-bit timer mode
TC4->COUNT32.CTRLA.bit.ENABLE = 1; // Enable TC4
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // 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("PW: ");
SerialUSB.print(pulsewidth);
SerialUSB.print(F(" "));
SerialUSB.print("P: ");
SerialUSB.println(period);
periodComplete = false; // Start a new period
}
}
void TC4_Handler() // Interrupt Service Routine (ISR) for timer TC4
{
// Check for match counter 0 (MC0) interrupt
if (TC4->COUNT32.INTFLAG.bit.MC0)
{
TC4->COUNT32.READREQ.reg = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(0x18); // Offset address of the CC0 register
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
isrPeriod = TC4->COUNT32.CC[0].reg; // Copy the period
periodComplete = true; // Indicate that the period is complete
}
// Check for match counter 1 (MC1) interrupt
if (TC4->COUNT32.INTFLAG.bit.MC1)
{
TC4->COUNT32.READREQ.reg = TC_READREQ_RREQ | // Enable a read request
TC_READREQ_ADDR(0x1A); // Offset address of the CC1 register
while (TC4->COUNT32.STATUS.bit.SYNCBUSY); // Wait for (read) synchronization
isrPulsewidth = TC4->COUNT32.CC[1].reg; // Copy the pulse-width
}
}
Explore related questions
See similar questions with these tags.