Seeeduino СЯО запись и чтение ШИМ продолжительность (период) с помощью таймеров

pwm timers communication samd21g seeeduino-xiao

Я пытаюсь установить связь между двумя Seeeduino XIAO (чип: ATSAMD21G18A-MU) с помощью захвата TCC с использованием регистровых таймеров.

Длительность импульса прямоугольной волны варьируется от 200 нс до 4 мс.

Я нашел эти два примера кода и теперь пытаюсь изменить их, чтобы они работали так, как я намерен читать и записывать эти короткие импульсы.


1) СЯО пишет квадратную волну (генерирует вывод)

Этот код работает, но он изменяет ширину импульса, а не период импульса. Выход находится на выводах D2 и D3. Как я могу заставить его изменить период поля вместо ширины поля? Источник

// 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) Квадратная волна считывания СЯО (захват входного сигнала); Захват периода и ширины импульса (PPW)

Этот код работает, но он считывает частоту в течение 1 секунды, а не только продолжительность периода времени. Вход находится на D6. Как я могу внести эти изменения в код, чтобы он работал? Источник

// Подсчитайте количество импульсов на выводе D6 (PB08) за 1 секунду
void setup()
{
  SerialUSB.begin(115200);                  // Инициализируйте собственный последовательный порт
  while(!SerialUSB);                        // Дождитесь открытия консоли

  PM->APBCMASK.reg |= PM_APBCMASK_EVSYS;    // Включите периферийное устройство системы событий
 
  ////////////////////////////////////////////////////////////////////////////////////////
  // Общая Инициализация Clock

  GCLK->GENDIV.reg =  GCLK_GENDIV_DIV(1) |          // Выберите делитель часов на 1          
                      GCLK_GENDIV_ID(4);            // Выберите GLCK4         
 
  GCLK->GENCTRL.reg = GCLK_GENCTRL_IDC |            // Установите рабочий цикл на 50/50 ВЫСОКИЙ/НИЗКИЙ 
                      GCLK_GENCTRL_GENEN |          // Включить GCLK                   
                      GCLK_GENCTRL_SRC_XOSC32K |    //  Выбрать источник GCLK в качестве
                                                    //  внешнего кристалла 32,768 кГц (XOSC32K)
                      GCLK_GENCTRL_ID(4);           // Выберите GCLK4             
  while (GCLK->STATUS.bit.SYNCBUSY);                // Дождитесь синхронизации

  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |          // Включить общие часы
                      GCLK_CLKCTRL_GEN_GCLK0 |      // GCLK0 на 48MHz 
                      GCLK_CLKCTRL_ID_TCC0_TCC1;    // В качестве источника тактовой частоты для TCC0 и TCC1
  
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |          // Включить общие часы
                      GCLK_CLKCTRL_GEN_GCLK4 |      // GCLK4 на частоте 32,768 кГц
                      GCLK_CLKCTRL_ID_TCC2_TC3;     // В качестве источника синхронизации для TCC2 и TC3

  //////////////////////////////////////////////////////////////////////////////////////////////
  // Инициализация TCC2 - опорный таймер: измеряет период 1 с
                    
  TCC2->PER.reg = 32767;                             // Установите регистр периода (PER) для
// периода ШИМ 1
  while (TCC2->SYNCBUSY.bit.PER);                    // Дождитесь синхронизации

  TCC2->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;            // Установите таймер в нормальный режим ШИМ (NPWM)
  while (TCC2->SYNCBUSY.bit.WAVE);                   // Дождитесь синхронизации
    
  TCC2->CTRLBSET.reg = TCC_CTRLBSET_ONESHOT;         // Включите операцию oneshot
  while(TCC2->SYNCBUSY.bit.CTRLB);                   // Дождитесь синхронизации
  
  NVIC_SetPriority(TCC2_IRQn, 0);    // Установите вложенный векторный контроллер прерываний
                                     // (NVIC) приоритет для TCC2 равным 0 (самый высокий) 
  NVIC_EnableIRQ(TCC2_IRQn); // Подключите TCC2 к вложенному векторному прерыванию 
                                     // Контроллер (NVIC)

  TCC2->INTENSET.reg = TCC_INTENSET_OVF;   // Включение прерываний переполнения (OVF) на TCC2

  TCC2->CTRLA.bit.ENABLE = 1;              // Включить TCC2
  while (TCC2->SYNCBUSY.bit.ENABLE);       // Дождитесь синхронизации

  ////////////////////////////////////////////////////////////////////////////////////////
  // Инициализация TCC0 - счетчик измерений: подсчитывает количество входящих импульсов

  PORT->Group[PORTB].PINCFG[8].bit.PMUXEN = 1;               // Включить мультиплексор портов
                                                                // на выводе порта PB08 (D6)
  PORT->Group[PORTB].PMUX[8 >> 1].reg |= PORT_PMUX_PMUXE_A;  // Настройка PB08 (D6) в качестве
// EIC (прерывание)
  
  EIC->EVCTRL.reg |= EIC_EVCTRL_EXTINTEO8;       // Включить вывод событий при внешнем прерывании 8
  EIC->CONFIG[1].reg |= EIC_CONFIG_SENSE0_HIGH;  // Установите прерывание для обнаружения ВЫСОКОГО уровня
  EIC->INTENCLR.reg = EIC_INTENCLR_EXTINT8; // Снимите флаг прерывания на канале 8
  EIC->CTRL.bit.ENABLE = 1;                      // Включить периферийное устройство EIC
  while (EIC->STATUS.bit.SYNCBUSY);              // Дождитесь синхронизации
  
  EVSYS->USER.reg = EVSYS_USER_CHANNEL(1) |      //  Подключите пользователя события (приемник) к
// каналу 0 (n + 1)
                    EVSYS_USER_USER(EVSYS_ID_USER_TCC0_EV_0);  // Установите пользователя события (получателя) 
                                                               // как таймер TCC0, событие 0
                    
  EVSYS->CHANNEL.reg = EVSYS_CHANNEL_EDGSEL_NO_EVT_OUTPUT |    // Обнаружение границ событий отсутствует
                       EVSYS_CHANNEL_PATH_ASYNCHRONOUS |       // Задать путь к событию как асинхронный
                       EVSYS_CHANNEL_EVGEN(EVSYS_ID_GEN_EIC_EXTINT_8) |  // установить генератор событий
// (отправитель) в качестве внешнего прерывания 8
                       EVSYS_CHANNEL_CHANNEL(0);               // Подключить генератор (отправитель) 
                                                               // на канал 0

  TCC0->EVCTRL.reg = TCC_EVCTRL_TCEI0 |       // Включить входы события 0 TCC0
                     TCC_EVCTRL_EVACT0_INC;   // Увеличить счетчик TCC0 при получении события 0
                                                                
  TCC0->CTRLA.bit.ENABLE = 1;          // Включить TCC0
  while (TCC0->SYNCBUSY.bit.ENABLE);   // Дождитесь синхронизации
}

void loop()
{
  startConversion();     // Начните преобразование с задержкой окна интеграции в 1 секунду
  delay(3500);           // Подождите 1 секунду
}

void startConversion()
{  
  TCC2->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;   // Повторно запустите таймер TCC2, чтобы
// запустить окно интеграции
  while (TCC2->SYNCBUSY.bit.CTRLB);                  // Дождитесь синхронизации
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_RETRIGGER;   // Повторно запустите таймер TCC0, чтобы
// начать подсчет
  while (TCC0->SYNCBUSY.bit.CTRLB);                  // Дождитесь синхронизации
}

void TCC2_Handler()
{
  TCC2->INTFLAG.bit.OVF = 1;                         // Снимите флаг прерывания переполнения TCC2
  TCC0->CTRLBSET.reg = TCC_CTRLBSET_CMD_READSYNC;    //  включить синхронизацию чтения 
 // на счет зарегистрируйтесь
  while (TCC0->SYNCBUSY.bit.CTRLB);                  // ждать реестр CTRLB написать синхронизации
  while (TCC0->SYNCBUSY.bit.COUNT);                  // ждать рассчитывать прочитать регистр синхронизации
  SerialUSB.println(TCC0->COUNT.reg);                // Распечатать результат
}

Какая-нибудь полезная страница, объясняющая эти таймеры

https://www.picotech.com/support/topic24051.html

https://shawnhymel.com/1710/arduino-zero-samd21-raw-pwm-using-cmsis/

, 👍0


1 ответ


Лучший ответ:

0

1) Запись коротких импульсов прямоугольной волны на вывод D3 с помощью Seeeduino XIAO

Настройка таймера, полученная из этого сообщения на github и включающая предложение об обновлении от 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) Считывание коротких прямоугольных волн с помощью вывода Seeeduino XIAO D2

Именно благодаря Мартинлю я заставил это работать. Источник

// 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
  }
}
,