Generate one sine wave with I2S and internal DAC

KBerger
Posts: 4
Joined: Thu Mar 17, 2022 3:30 pm

Generate one sine wave with I2S and internal DAC

Postby KBerger » Mon Mar 21, 2022 8:57 pm

I'm stumped as to how to output exactly one cycle of a sinewave at a selected frequency. The sine needs to start at the mid-value (127) run to the positive peak, back through the midpoint, then the negative peak (0) before returning to 127.

This code outputs a continuous wave of the desired frequency.

How can it be made to output a single cycle?

Code: Select all

// Configuration for the I2S Bus
i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), // Operating mode
    .sample_rate = 100000,                                                       // Sampling rate
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,                                // the DAC only uses 8 bits of the MSB
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,                                // Channel format ESP32 only supports stereo
    .communication_format = I2S_COMM_FORMAT_I2S_MSB,                             // Standard Format for I2S
    .intr_alloc_flags = 0,                                                       // Standard Interrupt
    .dma_buf_count = 2,                                                          // Number of FIFO buffers
    .dma_buf_len = 32,                                                           // Size of the FIFO Buffer
    .use_apll = false                                                            // clock source
};

void startSine()
{
  dac_output_enable(DAC_CHANNEL_2);
  SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN);
  SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M);
  SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV2, 2, SENS_DAC_INV2_S);
  initDone = true;
}

double setSineFrequency(double frequency)
{
  double f, delta, delta_min = 999999999.0;
  uint16_t divi = 0, step = 1, s;
  for (uint8_t div = 1; div < 9; div++)
  {
    s = round(frequency * div / SINE_FACTOR);
    if ((s > 0) && ((div == 1) || (s < 1024)))
    {
      f = SINE_FACTOR * s / div;
      delta = abs(f - frequency);
      if (delta < delta_min)
      { 
	    step = s;
        divi = div - 1;
        delta_min = delta;
      }
    }
  }
  frequency = SINE_FACTOR * step / (divi + 1);
  REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, divi);
  SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP, step, SENS_SW_FSTEP_S);
  return frequency;
}

void controlGenerator()
{
  if (!initDone)
    startSine();
  frequency = setSineFrequency(frequency);
}

void setup()
{
  Serial.begin(115200);
  controlGenerator();
}

void loop()
{
} // loop()

ESP_Sprite
Posts: 9730
Joined: Thu Nov 26, 2015 4:08 am

Re: Generate one sine wave with I2S and internal DAC

Postby ESP_Sprite » Tue Mar 22, 2022 2:17 am

You probably want to ignore the hardware sine generators and feed the data you want into the I2S peripheral yourself.

KBerger
Posts: 4
Joined: Thu Mar 17, 2022 3:30 pm

Re: Generate one sine wave with I2S and internal DAC

Postby KBerger » Tue Mar 22, 2022 3:06 pm

How does that solve the problem of generating just one cycle? It seems that if I could do that with an external DAC I could also do it with the internal ones.

ESP_Sprite
Posts: 9730
Joined: Thu Nov 26, 2015 4:08 am

Re: Generate one sine wave with I2S and internal DAC

Postby ESP_Sprite » Wed Mar 23, 2022 1:58 am

No, my point is that at this point you hook the DACs up to the sine wave generator internal to the ESP32 (by setting SENS_SW_TONE_EN). The alternative is to not enable that, and use the I2S driver in it's 'normal' way: you calculate samples (in your case, one sine wave and then silence) in software and then write that to the I2S peripheral. Same hardware as you have now, but because you calculate the samples yourself instead of letting the SENS_SW_TONE hardware do it, you have more control over it.

KBerger
Posts: 4
Joined: Thu Mar 17, 2022 3:30 pm

Re: Generate one sine wave with I2S and internal DAC

Postby KBerger » Wed Mar 23, 2022 4:42 pm

OK, I see your point. Define a sine wave and save it in a buffer. I've tried doing for a triangular wave that but still get a continuous output. How do I get just one cycle?

Here is the relevant code for a triangular wave in buf:

Code: Select all

i2s_config_t configI2S(int rate, int size)
{
  // Configuration for the I2S Bus
  i2s_config_t i2s_config = {
      .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), // Operating mode
      .sample_rate = rate,                                                         // Sampling rate
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,                                // the DAC only uses 8 bits of the MSB
      .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,                                // Channel format ESP32 only supports stereo
      .communication_format = I2S_COMM_FORMAT_I2S_MSB,                             // Standard Format for I2S
      .intr_alloc_flags = 0,                                                       // Standard Interrupt
      .dma_buf_count = 2,                                                          // Number of FIFO buffers
      .dma_buf_len = size,                                                         // Size of the FIFO Buffer
      .use_apll = false                                                            // clock source
  };
  return i2s_config;
}

void startTriangle()
{
  i2s_set_pin(I2S_NUM_0, NULL); // I2S is used with the DAC
  initDone = true;
}

// Set frequency for triangle with appropriate duty cycle
double triangleSetFrequency(double frequency, uint8_t ratio)
{
  int size = 64;
  // first the appropriate buffer size is determined
  // for the output to work the I2S sampling rate
  // must lie between 5200 and 650000
  if (frequency < 5000)
  {
    size = 64;
  }
  else if (frequency < 10000)
  {
    size = 32;
  }
  else if (frequency < 20000)
  {
    size = 16;
  }
  else
  {
    size = 8;
  }
  // Sample rate must output both buffers in one period
  uint32_t rate = frequency * 2 * size;
  // The sampling rate must only be within the limits
  if (rate < 5200)
    rate = 5200;
  if (rate > 650000)
    rate = 650000;
  // set actual frequency value
  frequency = rate / 2 / size;

  i2s_driver_uninstall(I2S_NUM_0);                           // Remove I2S driver
  i2s_config_t i2s_config = configI2S(rate, size);           // set new configuration
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);       // and install with the new configuration
  fillBuffer(ratio, size * 2);                               // Fill the buffer
  size_t i2s_bytes_write = 0;                                // ????
  i2s_write(I2S_NUM_0, buf, size * 8, &i2s_bytes_write, 10); // and print once

  return frequency;
}

ESP_Sprite
Posts: 9730
Joined: Thu Nov 26, 2015 4:08 am

Re: Generate one sine wave with I2S and internal DAC

Postby ESP_Sprite » Thu Mar 24, 2022 1:36 am

You likely want to fill the buffer afterwards with a load of silence. If you don't fill the buffer and the I2S hardware runs out of samples, I think it will simply repeat what's still in its buffers.

KBerger
Posts: 4
Joined: Thu Mar 17, 2022 3:30 pm

Re: Generate one sine wave with I2S and internal DAC

Postby KBerger » Thu Mar 24, 2022 3:54 pm

Excellent suggestion! I created a null buffer and pushed it to I2S immediately after the filled buffer.

This will work but I'm going to explore the use of an interrupt - new territory for me.

Code: Select all

  
  uint32_t nullBuf[128] = {0};
  .
  .
  .
  i2s_driver_uninstall(I2S_NUM_0);                               // Remove I2S driver
  i2s_config_t i2s_config = configI2S(rate, size);               // set new configuration
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);           // and install with the new configuration
  fillBuffer(size * 2);                                          // Fill the buffer
  size_t i2s_bytes_write = 0;                                    // ????
  i2s_write(I2S_NUM_0, buf, size * 8, &i2s_bytes_write, 10);     // and send once
  i2s_write(I2S_NUM_0, nullBuf, size * 8, &i2s_bytes_write, 10); // then send the null buffer
Attachments
IMG_005.jpg
IMG_005.jpg (58.76 KiB) Viewed 5577 times

Who is online

Users browsing this forum: No registered users and 66 guests