ESP32 sine generator

Place your projects here
Post Reply
User avatar
cicciocb
Site Admin
Posts: 2613
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 547 times
Been thanked: 1853 times
Contact:

ESP32 sine generator

Post by cicciocb »

Hi all,
I recently received a request regarding the use of PEEK and POKE functions, reminiscent of our oldest programming memories.
Bishopxxl has already shown us in a post how to invert the polarity of the TX and RX lines of the serial port, and I, inspired by a request from PeterN concerning the implementation of the DAC cosine waveform generator, thought this was an excellent opportunity to do some old-school programming by peeking and poking into registers.

For clarification, classic ESP32 chips are equipped with a CW (Cosine Waveform) module which, in conjunction with the DACs, allows for the generation of sinusoidal waveforms independently of the processor, thus without affecting any CPU resources.
This generator is capable of producing frequencies from 130Hz to approximately 100KHz.
Theoretically, it could reach 8MHz, but significant distortion becomes apparent at around 100KHz.
With a clever trick, by modifying the RTC Clock frequency via a divider, it's possible to reach a minimum of 16Hz, but this could potentially affect the operation of other parts of the CPU.
You can find a very interesting article here: https://github.com/krzychb/dac-cosine, which I used as a basis for my work.
I have therefore written a small program in BASIC that uses PEEK and POKE functions to activate and control this generator.

Here's a demonstration of how to use the internal cosine DAC generator, which works only on the classic ESP32 !

This program demonstrates how to use PEEK and POKE functions to directly manipulate the ESP32's registers, enabling and controlling the cosine waveform generator. It sets up both DAC channels, configures the frequency, scale, offset, and inversion settings for the waveform output.

ESP32 DAC Cosine Generator Demo

Code: [Local Link Removed for Guests]

'demo on how use the internal cosine DAC generator
' works only on the ESP32 classic
' for more details look at 
' https://github.com/krzychb/dac-cosine
' Technical documentation available here (chapter 'Cosine Waveform Generator')
' https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
RTC_FAST_CLK_FREQ_APPROX = 8500000
DR_REG_SENS_BASE = 0x3ff48800
DR_REG_RTCCNTL_BASE = 0x3ff48000
RTC_CNTL_CLK_CONF_REG = DR_REG_RTCCNTL_BASE + 0x70
SENS_SAR_DAC_CTRL1_REG = DR_REG_SENS_BASE + 0x98
SENS_SAR_DAC_CTRL2_REG = DR_REG_SENS_BASE + 0x9C

pin.dac 25,0 ' enable the DAC1
pin.dac 26,0 ' enable the DAC2

'dac_cosine_enable
'Enable tone generator common to both channels
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL1_REG, 16 ' set bit SENS_SW_TONE_EN
'Enable / connect tone tone generator on / to this channel
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL2_REG, 24 ' set bit SENS_DAC_CW_EN1_M
'Invert MSB, otherwise part of waveform will have inverted
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, 2, 20  ' SENS_DAC_INV1_S (invert MSB)
' DAC Channel 2
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL2_REG, 25 ' set bit SENS_DAC_CW_EN2_M
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, 2, 22  ' SENS_DAC_INV2_S (invert MSB)

'dac_frequency_set
'/*
'* Set frequency of internal CW generator common to both DAC channels
'*
'* clk_8m_div: 0b000 - 0b111
'* frequency_step: range 0x0001 - 0xFFFF
'*/
clk_8m_div = 0 ' can be from 0 to 7
frequency_step = 772 ' can be from 1 to 65535
SET_PERI_REG_BITS RTC_CNTL_CLK_CONF_REG, 0x7, clk_8m_div, 12 ' set RTC_CNTL_CK8M_DIV_SEL
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL1_REG, 0xffff, frequency_step, 0 ' set frequency_step
frequency = RTC_FAST_CLK_FREQ_APPROX / (1 + clk_8m_div) * frequency_step / 65536
wlog "The frequency is ", frequency
print "The frequency is ", frequency

'dac_scale_set
'/*
' * Scale output of a DAC channel using two bit pattern:
' *
' * - 00: no scale
' * - 01: scale to 1/2
' * - 10: scale to 1/4
' * - 11: scale to 1/8
' */
scale1 = 0
scale2 = 1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale1, 16 ' set SENS_DAC_SCALE1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale2, 18 ' set SENS_DAC_SCALE2

'dac_offset_set
'/*
' * Offset output of a DAC channel
' *
' * Range 0x00 - 0xFF
' */
offset1 = 0
offset2 = 0
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0xff, offset1, 0 ' set SENS_DAC_DC1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0xff, offset2, 8 ' set SENS_DAC_DC2

'dac_invert_set
'/*
' * Invert output pattern of a DAC channel
' *
' * - 00: does not invert any bits,
' * - 01: inverts all bits,
' * - 10: inverts MSB,
' * - 11: inverts all bits except for MSB
' */
invert1 = 2
invert2 = 3
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, invert1, 20 ' set SENS_DAC_INV1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, invert2, 22 ' set SENS_DAC_INV2
end


'functions used to write into registers
sub SET_PERI_REG_MASK(reg,mask)
  local r, v
  r = bas.peek(reg)
  v = 1 << mask
  bas.poke reg, r or v
end sub

sub CLEAR_PERI_REG_MASK(reg,mask)
  local r, v
  r = bas.peek(reg)
  v = 1 << mask
  bas.poke reg, r and (not v)
end sub

sub SET_PERI_REG_BITS(reg,bit_map,value,shift)
  local r, mask, v
  r = bas.peek(reg)
  mask = bit_map << shift
  r = r and (not mask)
  v = (value and bit_map) << shift
  r = r or v
  bas.poke reg, r
end sub
P.S. :
For Peeking and Poking enthusiasts, this document (https://www.espressif.com/sites/default ... ual_en.pdf) can serve as a source of inspiration.
There are tons of registers to modify, but... well, good luck with that! :-)
You do not have the required permissions to view the files attached to this post.
User avatar
PeterN
Posts: 576
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 270 times
Been thanked: 312 times
Contact:

Re: ESP32 sine generator

Post by PeterN »

Yes! Old school! But VERY effective!
Thanks!
User avatar
PeterN
Posts: 576
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 270 times
Been thanked: 312 times
Contact:

Re: ESP32 sine generator

Post by PeterN »

This has given my DCF77 time server project a new direction.
The amplitude of the output can be modulated in 4 steps using the scale parameter.
Setting the frequency to 77.5kHz and switching the scale between 0 (100%) and 3 (12.5%) opens up a new possibility to generate the DCF77 time server signal with an almost correctly modulated amplitude (should be 100%/15%) directly in the ESP32 without the need for any additional external modulation hardware.
User avatar
PeterN
Posts: 576
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 270 times
Been thanked: 312 times
Contact:

Re: ESP32 sine generator

Post by PeterN »

I understand from the documentation that the RTC clock is derived from an RC-controlled generator and not from the crystal.
This could affect the accuracy and stability at higher generated frequencies.
The "APPROX" in "RTC_FAST_CLK_FREQ_APPROX" seems to have a reason.
In addition, the resulting smallest frequency steps are of course dependent on the settings of the dividers and multipliers.
I will try out what relevance this has at 77.5KHz
User avatar
cicciocb
Site Admin
Posts: 2613
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 547 times
Been thanked: 1853 times
Contact:

Re: ESP32 sine generator

Post by cicciocb »

[Local Link Removed for Guests] wrote: [Local Link Removed for Guests]Sat Nov 02, 2024 4:08 pm I understand from the documentation that the RTC clock is derived from an RC-controlled generator and not from the crystal.
This could affect the accuracy and stability at higher generated frequencies.
The "APPROX" in "RTC_FAST_CLK_FREQ_APPROX" seems to have a reason.
In addition, the resulting smallest frequency steps are of course dependent on the settings of the dividers and multipliers.
I will try out what relevance this has at 77.5KHz
Yes, it is not a great quality oscillator, seems more a concept than a real generator
User avatar
cicciocb
Site Admin
Posts: 2613
Joined: Mon Feb 03, 2020 1:15 pm
Location: Toulouse
Has thanked: 547 times
Been thanked: 1853 times
Contact:

Re: ESP32 sine generator

Post by cicciocb »

Okay, I was intrigued by the fact that it wasn't possible to use the crystal for this oscillator.
Indeed, in the documents I found, it's not mentioned, but in the datasheet, it's indicated that the RTC_FAST_CLK frequency can also come from the crystal, more precisely it's the crystal frequency divided by 4.
image.png
This is the corresponding bit in the register that must be set to 0
image.png
Normally, the crystal is 40 MHz, so the reference frequency becomes a stable 10 MHz instead of approximately 8.5 MHz.
The min frequency is now 10MHz/65536 = 152.58Hz
Below is the modified example with the line that changes the clock reference.
Note that the RC_FAST_CLOCK divider no longer has an effect because the source is now different.

Code: [Local Link Removed for Guests]

'demo on how use the internal cosine DAC generator
' works only on the ESP32 classic
' for more details look at 
' https://github.com/krzychb/dac-cosine
' Technical documentation available here (chapter 'Cosine Waveform Generator')
' https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
RTC_FAST_CLK_FREQ_APPROX = 8500000
XTAL_DIV4 = 40000000/4
DR_REG_SENS_BASE = 0x3ff48800
DR_REG_RTCCNTL_BASE = 0x3ff48000
RTC_CNTL_CLK_CONF_REG = DR_REG_RTCCNTL_BASE + 0x70
SENS_SAR_DAC_CTRL1_REG = DR_REG_SENS_BASE + 0x98
SENS_SAR_DAC_CTRL2_REG = DR_REG_SENS_BASE + 0x9C

' set XTAL_DIV4 instead of RC FAST CLK
CLEAR_PERI_REG_MASK RTC_CNTL_CLK_CONF_REG, 29 ' RTC_CNTL_RTC_FAST_CLK_SEL

pin.dac 25,0 ' enable the DAC1
pin.dac 26,0 ' enable the DAC2

'dac_cosine_enable
'Enable tone generator common to both channels
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL1_REG, 16 ' set bit SENS_SW_TONE_EN
'Enable / connect tone tone generator on / to this channel
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL2_REG, 24 ' set bit SENS_DAC_CW_EN1_M
'Invert MSB, otherwise part of waveform will have inverted
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, 2, 20  ' SENS_DAC_INV1_S (invert MSB)
' DAC Channel 2
SET_PERI_REG_MASK SENS_SAR_DAC_CTRL2_REG, 25 ' set bit SENS_DAC_CW_EN2_M
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, 2, 22  ' SENS_DAC_INV2_S (invert MSB)

'dac_frequency_set
'/*
'* Set frequency of internal CW generator common to both DAC channels
'*
'* clk_8m_div: 0b000 - 0b111
'* frequency_step: range 0x0001 - 0xFFFF
'*/
clk_8m_div = 0 ' can be from 0 to 7 (not working if XTAL_DIV4 is selected) 
frequency_step = 508 ' can be from 1 to 65535
SET_PERI_REG_BITS RTC_CNTL_CLK_CONF_REG, 0x7, clk_8m_div, 12 ' set RTC_CNTL_CK8M_DIV_SEL
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL1_REG, 0xffff, frequency_step, 0 ' set frequency_step
'frequency = RTC_FAST_CLK_FREQ_APPROX / (1 + clk_8m_div) * frequency_step / 65536
frequency = XTAL_DIV4 * frequency_step / 65536

wlog "The frequency is ", frequency
print "The frequency is ", frequency

'dac_scale_set
'/*
' * Scale output of a DAC channel using two bit pattern:
' *
' * - 00: no scale
' * - 01: scale to 1/2
' * - 10: scale to 1/4
' * - 11: scale to 1/8
' */
scale1 = 0
scale2 = 1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale1, 16 ' set SENS_DAC_SCALE1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, scale2, 18 ' set SENS_DAC_SCALE2

'dac_offset_set
'/*
' * Offset output of a DAC channel
' *
' * Range 0x00 - 0xFF
' */
offset1 = 0
offset2 = 0
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0xff, offset1, 0 ' set SENS_DAC_DC1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0xff, offset2, 8 ' set SENS_DAC_DC2

'dac_invert_set
'/*
' * Invert output pattern of a DAC channel
' *
' * - 00: does not invert any bits,
' * - 01: inverts all bits,
' * - 10: inverts MSB,
' * - 11: inverts all bits except for MSB
' */
invert1 = 2
invert2 = 3
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, invert1, 20 ' set SENS_DAC_INV1
SET_PERI_REG_BITS SENS_SAR_DAC_CTRL2_REG, 0x3, invert2, 22 ' set SENS_DAC_INV2
end


'functions used to write into registers
sub SET_PERI_REG_MASK(reg,mask)
  local r, v
  r = bas.peek(reg)
  v = 1 << mask
  bas.poke reg, r or v
end sub

sub CLEAR_PERI_REG_MASK(reg,mask)
  local r, v
  r = bas.peek(reg)
  v = 1 << mask
  bas.poke reg, r and (not v)
end sub

sub SET_PERI_REG_BITS(reg,bit_map,value,shift)
  local r, mask, v
  r = bas.peek(reg)
  mask = bit_map << shift
  r = r and (not mask)
  v = (value and bit_map) << shift
  r = r or v
  bas.poke reg, r
end sub
You do not have the required permissions to view the files attached to this post.
User avatar
PeterN
Posts: 576
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 270 times
Been thanked: 312 times
Contact:

Re: ESP32 sine generator

Post by PeterN »

Thank you Francesco!
I had started reading the docs you mentioned ... went to dinner with friends ... and now I come back and see your nice extended code, with another great use case of peek and poke and a stable output signal.
I'll hook it up to my oscilloscope tomorrow with the modulated 77.5kHz signal.
What a great thing!
User avatar
PeterN
Posts: 576
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 270 times
Been thanked: 312 times
Contact:

Re: ESP32 sine generator

Post by PeterN »

Hello Francesco,
I have tested the quality of the 77.5KHz - thanks for already setting fequency_step to the appropriate 508.
As expected, this resulted in a stable 77.514kHz sinusoidal signal.
Thanks to your subroutine, I was able to quickly reduce the amplitude for the required 0.1s pulses to what I need for my DCF77 project.
Perfect conditions to go further in this direction. Thank you!

This success has pushed me to test the limits :-)
Lowest possible frequency is 152.5Hz, as expected now adjustable in multiples of this frequency.
Because of the faster 10Mhz clock and the now missing pre-divider, I already expected the usable range of frequency_step to be smaller than 65535.
In fact, the signal changed from sine to sawtooth and worse when frequency_step becomes greater than 1024, increasing the output frequency to more than 155kHz.
But this is a very good range for the sine wave signal generated directly by the DAC!

Thank you for tirelessly exploring and harnessing the possibilities of the ESP32
User avatar
PeterN
Posts: 576
Joined: Mon Feb 08, 2021 7:56 pm
Location: Krefeld, Germany
Has thanked: 270 times
Been thanked: 312 times
Contact:

Re: ESP32 sine generator

Post by PeterN »

Thanks to the fantastic starting conditions, I was able to complete my DCF77 time server project:

[Local Link Removed for Guests]
Post Reply