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/

MeSo2
  • 177
  • 10

1 Answers1

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/(2NPER) 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 } }

MeSo2
  • 177
  • 10