0

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/

asked Aug 23, 2021 at 22:02

1 Answer 1

0

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
 }
}
answered Aug 24, 2021 at 23:20
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.