Generating a 400 kHz square wave

I tried to use pwm function to output a square wave from one of the pins. It works for such pins as A1, A2 and so on (doesn’t work for A0). However, the maximum frequency seems to be limited by 46.8 kHz. Trying to specify a higher value results in 46.8 kHz output. I need 400 kHz. How do I program Seeeduino XIAO PWM generator to output a 400 kHz square wave? Duty cycle does not matter (say, 50% is fine).

Hi,
i am poor at English.Please understand.
Are you working from i2c and want to achieve 400khz output?

if yes,400K speed is certainly not to, processing from I2C also involves state transition, not a few instructions to get it done. But I2C does not have strict timing requirements. Although the master is very fast, it has to wait for the slave’s response, so the slave can actually be very slow. It works perfectly. If you have high requirements for speed, it is recommended not to choose I2C.For such I2C influencing factors are actually particularly many, such as ZLG7290 I only used 30K, the propaganda seems to be 400K. It doesn’t work over 50K

My question has absolutely nothing to do with I2C.

I just need a 400 kHz square wave to clock some external circuitry. Ordinary Arduino Nano can easily do 400 kHz from its PWM generator. Are you telling me that a lot more powerful XIAO does not have a built-in PWM generator capable of producing 400 kHz???

XIAO’s F_CPU is 48000000. So XIAO should be able to easily generate a 400kHz square output. My question is: how do I do that? Is there an API? A library?

Hi,
Now understand, I will use my rest time over the weekend to do the test with my colleagues and give you the answer next Monday

I already figured it out.

The limitation of 46.8 kHz comes from the implementation of pwm function. Since it uses 10 bit resolution for PWM (from 0 to 1023), the maximum PWM frequency it can support is 48000000/1024 = 46875 Hz. Higher PWM frequency would “erode” the PWM resolution.

Just for the sake of experiment, one can go into packages\Seeeduino\hardware\samd\1.8.2\cores\arduino\wiring_pwm.cpp and manually change the

#define PWM_API_RESOLUTION 10

to, say,

#define PWM_API_RESOLUTION 2

This will reduce PWM resolution to 2 bits (0 to 3), but after that change pwm(A1, 400000, 2); will successfully generate 400 kHz square wave on A1. Of course, editing that source code is not a good idea.

great!Would you mind sharing your thoughts and research?
If you have any development problems, please share them with us at [Share Your Own Ideas] Share you own thoughts with us · Issue #322 · Seeed-Studio/wiki-documents · GitHub

By the way, we have a ranger project: [Before Everything Starts] Public Seeed Studio Wiki Platform Updates · Issue #336 · Seeed-Studio/wiki-documents · GitHub

Come in and contribute articles, projects can be, we have product incentives

There’s not much to share beyond what I said above. This part of pwm function

  // Ensure that our period does not erode the API resolution
  if(period < (1<<PWM_API_RESOLUTION))
    period = (1<<PWM_API_RESOLUTION) - 1;

along with the value of PWM_API_RESOLUTION being 10 is what limits PWM frequency to 46.8 kHz.

Below is my square_wave function for Seeduino XIAO (SAMD21), which does not have that limitation. It is based on the original code of pwm from wiring_pwm.cpp. The function below will set up XIAO to generate 50% square wave, supporting frequencies well into MHz range. It is intended for TCC pins only: A1-A5, A8-A10 (no A6 and no A7). It worked for 16 MHz in my experiments, although at such frequencies the shape of the wave begins to deteriorate. 400 kHz works perfectly.

#include <wiring_private.h>

static inline void syncTCC(const volatile Tcc *TCCx) 
{
  while (TCCx->SYNCBUSY.reg & TCC_SYNCBUSY_MASK);
}

constexpr uint32_t MAX_PERIOD = 0xFFFF;

constexpr uint16_t GCLK_CLKCTRL_IDs[] = 
{
  GCLK_CLKCTRL_ID(GCM_TCC0_TCC1), // TCC0
  GCLK_CLKCTRL_ID(GCM_TCC0_TCC1), // TCC1
  GCLK_CLKCTRL_ID(GCM_TCC2_TC3),  // TCC2
  GCLK_CLKCTRL_ID(GCM_TCC2_TC3),  // TC3
  GCLK_CLKCTRL_ID(GCM_TC4_TC5),   // TC4
  GCLK_CLKCTRL_ID(GCM_TC4_TC5),   // TC5
  GCLK_CLKCTRL_ID(GCM_TC6_TC7),   // TC6
  GCLK_CLKCTRL_ID(GCM_TC6_TC7),   // TC7
};

void square_wave(uint32_t outputPin, uint32_t frequency)
{
  const PinDescription &pinDesc = g_APinDescription[outputPin];
  uint32_t attr = pinDesc.ulPinAttribute;
  if ((attr & PIN_ATTR_PWM) != PIN_ATTR_PWM)
    return;

  unsigned tcNum = GetTCNumber(pinDesc.ulPWMChannel);
  unsigned tcChannel = GetTCChannelNumber(pinDesc.ulPWMChannel);

  if ((attr & PIN_ATTR_TIMER) == PIN_ATTR_TIMER)
    pinPeripheral(outputPin, PIO_TIMER);
  else if ((attr & PIN_ATTR_TIMER_ALT) == PIN_ATTR_TIMER_ALT)
    pinPeripheral(outputPin, PIO_TIMER_ALT);
  else
    return;

  frequency = constrain(frequency, 1, F_CPU / 2);
  uint32_t period = F_CPU / frequency - 1;

  unsigned i_div = 0;
  while (period > MAX_PERIOD)
  {
    ++i_div;
    if (i_div == 4 || i_div == 6 || i_div == 8) //DIV32 DIV128 and DIV512 are not available
      ++i_div;

    period = F_CPU / frequency / (1 << i_div) - 1;
  }

  static constexpr unsigned long PRESCALER_VAL[] = 
  { 
    TC_CTRLA_PRESCALER_DIV1_Val, 
    TC_CTRLA_PRESCALER_DIV2_Val, 
    TC_CTRLA_PRESCALER_DIV4_Val,
    TC_CTRLA_PRESCALER_DIV8_Val,
    TC_CTRLA_PRESCALER_DIV16_Val,
    0, 
    TC_CTRLA_PRESCALER_DIV64_Val,
    0,
    TC_CTRLA_PRESCALER_DIV256_Val,
    0,
    TC_CTRLA_PRESCALER_DIV1024_Val
  };

  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_IDs[tcNum]);
  while (GCLK->STATUS.bit.SYNCBUSY == 1);

  Tcc *TCCx = (Tcc *) GetTC(pinDesc.ulPWMChannel);

  TCCx->CTRLA.bit.ENABLE = 0;
  syncTCC(TCCx);

  TCCx->CTRLA.bit.PRESCALER = PRESCALER_VAL[i_div];
  syncTCC(TCCx);
  TCCx->WAVE.reg |= TCC_WAVE_WAVEGEN_NPWM; // normal PWM
  syncTCC(TCCx);
  TCCx->CC[tcChannel].reg = period / 2; // initial value
  syncTCC(TCCx);
  TCCx->PER.reg = period;
  syncTCC(TCCx);
  
  TCCx->CTRLA.bit.ENABLE = 1;
  syncTCC(TCCx);
}

In order to stop the generator, use the existing noPwm function.