diff --git a/docs/esp32/general.rst b/docs/esp32/general.rst index 8137c042d921..78cc23e4bbd9 100644 --- a/docs/esp32/general.rst +++ b/docs/esp32/general.rst @@ -51,7 +51,7 @@ For your convenience, some of technical specifications are provided below: * I2C: 2 I2C (bitbang implementation available on any pins) * I2S: 2 * ADC: 12-bit SAR ADC up to 18 channels -* DAC: 2 8-bit DACs +* DAC: 2 8-bit DACs with sine-wave generator * RMT: 8 channels allowing accurate pulse transmit/receive * Programming: using BootROM bootloader from UART - due to external FlashROM and always-available BootROM bootloader, the ESP32 is not brickable diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index c58f4aa76085..a65e5fb173a3 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -240,6 +240,73 @@ ESP32 specific ADC class method reference: - ``ADC.WIDTH_11BIT``: 11 bit data - ``ADC.WIDTH_12BIT``: 12 bit data - this is the default configuration +DAC (digital to analog conversion) +---------------------------------- + +On the ESP32 DAC functionality is available on Pins 25 and 26. Note that, when creating +the DAC object for a pin, the DAC value will be initialized at 0. + +Use the :ref:`machine.DAC ` class:: + + from machine import DAC + + dac = DAC(Pin(25)) # create DAC object on pin 25 + dac.write(128) # set the output voltage to 1.65V + + dac.cosine_enable() # enable the cosine generator + dac.frequency_set(7, 1) # set the output frequency using the RTC clock divider and + # frequency steps for the CW generator, (7, 1) -> ~15 Hz + + +ESP32 specific DAC class method reference: + +.. method:: DAC.cosine_enable() + + This method enables the cosine generator for the DAC. + +.. method:: DAC.cosine_disable() + + This method disables the cosine generator for the DAC. + +.. method:: DAC.frequency_step(frequency_step) + + This method sets the frequency steps of the CW generator. The base is the 8 MHz RTC clock. The + ``frequency_step`` adjusts the frequency as ``rtc_clock x frequency_step / 65536``. The RTC + clock is by default 8 MHz, and can be divided down by ``DAC.rtc_clk_div``. + +.. method:: DAC.rtc_clk_div(clk_8m_div) + This method sets the RTC clock divider. This allows to achieve lower frequencies of the CW generator. + ``clk_8m_div`` is the divider and must be between 0 and 7. + +.. Warning:: + Be cautious in changing this value as it might affect other peripherals that use the 8 MHz RTC clock! + +.. method:: DAC.scale_set(scale) + + This method allows to scale the sine wave: + + - 0 -> no scale + - 1 -> scale to 1/2 + - 2 -> scale to 1/4 + - 3 -> scale to 1/8 + +.. method:: DAC.offset_set(offset) + + This method allows to apply an offset. The range is from 0 to 255 + +.. method:: DAC.invert_set(invert) + + This method can invert the output pattern: + + - 0 -> does not invert anything + - 1 -> inverts all bits + - 2 -> inverts MSB + - 3 -> inverts all bits except the MSB + +.. method:: DAC.frequency_get() + + This method returns the current frequency in Hz. + Software SPI bus ---------------- diff --git a/ports/esp32/machine_dac.c b/ports/esp32/machine_dac.c index bd0804ec4151..bf8586bafc5e 100644 --- a/ports/esp32/machine_dac.c +++ b/ports/esp32/machine_dac.c @@ -29,34 +29,45 @@ #include "esp_log.h" +#include "soc/sens_reg.h" +#include "soc/rtc_io_reg.h" +#include "soc/rtc_cntl_reg.h" +#include "soc/rtc.h" + #include "driver/gpio.h" #include "driver/dac.h" #include "py/runtime.h" #include "py/mphal.h" #include "modmachine.h" +#include "mphalport.h" typedef struct _mdac_obj_t { mp_obj_base_t base; gpio_num_t gpio_id; dac_channel_t dac_id; + int clk_8m_div; + int frequency_step; } mdac_obj_t; -STATIC const mdac_obj_t mdac_obj[] = { - {{&machine_dac_type}, GPIO_NUM_25, DAC_CHANNEL_1}, - {{&machine_dac_type}, GPIO_NUM_26, DAC_CHANNEL_2}, -}; - STATIC mp_obj_t mdac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, 1, true); gpio_num_t pin_id = machine_pin_get_id(args[0]); - const mdac_obj_t *self = NULL; - for (int i = 0; i < MP_ARRAY_SIZE(mdac_obj); i++) { - if (pin_id == mdac_obj[i].gpio_id) { self = &mdac_obj[i]; break; } + if (pin_id != GPIO_NUM_25 && pin_id != GPIO_NUM_26) mp_raise_ValueError("invalid Pin for DAC"); + + mdac_obj_t *self = m_new_obj(mdac_obj_t); + self->base.type = &machine_dac_type; + + self->gpio_id = pin_id; + if (pin_id == GPIO_NUM_25) { + self->dac_id = DAC_CHANNEL_1; + } else { + self->dac_id = DAC_CHANNEL_2; } - if (!self) mp_raise_ValueError("invalid Pin for DAC"); + self->clk_8m_div = 0; + self->frequency_step = 1; esp_err_t err = dac_output_enable(self->dac_id); if (err == ESP_OK) { @@ -82,8 +93,179 @@ STATIC mp_obj_t mdac_write(mp_obj_t self_in, mp_obj_t value_in) { } MP_DEFINE_CONST_FUN_OBJ_2(mdac_write_obj, mdac_write); +/* + * Enable cosine waveform generator on a DAC channel + */ +STATIC mp_obj_t mdac_cosine_enable(mp_obj_t self_in) { + mdac_obj_t *self = self_in; + // Enable tone generator common to both channels + SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN); + switch(self->dac_id) { + case DAC_CHANNEL_1: + // Enable / connect tone tone generator on / to this channel + SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M); + // Invert MSB, otherwise part of waveform will have inverted + SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV1, 2, SENS_DAC_INV1_S); + break; + case DAC_CHANNEL_2: + 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); + break; + default : + mp_raise_ValueError("Parameter Error"); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mdac_cosine_enable_obj, mdac_cosine_enable); + +/* + * Disable cosine waveform generator on a DAC channel + */ +STATIC mp_obj_t mdac_cosine_disable(mp_obj_t self_in) { + mdac_obj_t *self = self_in; + switch(self->dac_id) { + case DAC_CHANNEL_1: + // disable / connect tone tone generator on / to this channel + CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M); + break; + case DAC_CHANNEL_2: + CLEAR_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M); + break; + default : + mp_raise_ValueError("Parameter Error"); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(mdac_cosine_disable_obj, mdac_cosine_disable); + +/* + * Set frequency steps of internal CW generator common to both DAC channels + * + * frequency_step: range 0x0001 - 0xFFFF + * + */ +STATIC mp_obj_t mdac_frequency_step(mp_obj_t self_in, mp_obj_t frequency_step_in) { + mdac_obj_t *self = self_in; + int frequency_step = mp_obj_get_int(frequency_step_in); + if (frequency_step < 1 || frequency_step > 0xffff) mp_raise_ValueError("Frequency_step out of range"); + self->frequency_step = frequency_step; + SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP, frequency_step, SENS_SW_FSTEP_S); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(mdac_frequency_step_obj, mdac_frequency_step); + +/* + * Set the RTC 8 MHz clock divider. This can be dangerous if other code uses the 8 MHz clock. + * + * clk_8m_div: range 0b000 - 0b111 + */ +STATIC mp_obj_t mdac_rtc_clk_div(mp_obj_t self_in, mp_obj_t clk_8m_div_in) { + mdac_obj_t *self = self_in; + int clk_8m_div = mp_obj_get_int(clk_8m_div_in); + if (clk_8m_div < 0 || clk_8m_div > 0b111) mp_raise_ValueError("Cl_8m_div out of range"); + self->clk_8m_div = clk_8m_div; + REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, clk_8m_div); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(mdac_rtc_clk_div_obj, mdac_rtc_clk_div); + +/* + * 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 + * + */ +STATIC mp_obj_t mdac_scale_set(mp_obj_t self_in, mp_obj_t scale_in) { + mdac_obj_t *self = self_in; + int scale = mp_obj_get_int(scale_in); + if (scale < 0 || scale > 0b11) mp_raise_ValueError("Scale out of range"); + switch(self->dac_id) { + case DAC_CHANNEL_1: + SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_SCALE1, scale, SENS_DAC_SCALE1_S); + break; + case DAC_CHANNEL_2: + SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_SCALE2, scale, SENS_DAC_SCALE2_S); + break; + default : + mp_raise_ValueError("Parameter Error"); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(mdac_scale_set_obj, mdac_scale_set); + +/* + * Offset output of a DAC channel + * + * Range 0x00 - 0xFF + * + */ +STATIC mp_obj_t mdac_offset_set(mp_obj_t self_in, mp_obj_t offset_in) { + mdac_obj_t *self = self_in; + int offset = mp_obj_get_int(offset_in); + if (offset < 0 || offset > 0xFF) mp_raise_ValueError("Offset out of range"); + switch(self->dac_id) { + case DAC_CHANNEL_1: + SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_DC1, offset, SENS_DAC_DC1_S); + break; + case DAC_CHANNEL_2: + SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_DC2, offset, SENS_DAC_DC2_S); + break; + default : + mp_raise_ValueError("Parameter Error"); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(mdac_offset_set_obj, mdac_offset_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 + * + */ +STATIC mp_obj_t mdac_invert_set(mp_obj_t self_in, mp_obj_t invert_in) { + mdac_obj_t *self = self_in; + int invert = mp_obj_get_int(invert_in); + if (invert < 0 || invert > 0b11) mp_raise_ValueError("Invert out of range"); + switch(self->dac_id) { + case DAC_CHANNEL_1: + SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV1, invert, SENS_DAC_INV1_S); + break; + case DAC_CHANNEL_2: + SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV2, invert, SENS_DAC_INV2_S); + break; + default : + mp_raise_ValueError("Parameter Error"); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(mdac_invert_set_obj, mdac_invert_set); + +/* + * Get the current frequency + */ +STATIC mp_obj_t mdac_frequency_get(mp_obj_t self_in) { + mdac_obj_t *self = self_in; + return mp_obj_new_int(RTC_FAST_CLK_FREQ_APPROX / (1 + self->clk_8m_div) * (float) self->frequency_step / 65536); +} +MP_DEFINE_CONST_FUN_OBJ_1(mdac_frequency_get_obj, mdac_frequency_get); + STATIC const mp_rom_map_elem_t mdac_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mdac_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_cosine_enable), MP_ROM_PTR(&mdac_cosine_enable_obj) }, + { MP_ROM_QSTR(MP_QSTR_cosine_disable), MP_ROM_PTR(&mdac_cosine_disable_obj) }, + { MP_ROM_QSTR(MP_QSTR_frequency_step), MP_ROM_PTR(&mdac_frequency_step_obj) }, + { MP_ROM_QSTR(MP_QSTR_rtc_clk_div), MP_ROM_PTR(&mdac_rtc_clk_div_obj) }, + { MP_ROM_QSTR(MP_QSTR_scale_set), MP_ROM_PTR(&mdac_scale_set_obj) }, + { MP_ROM_QSTR(MP_QSTR_offset_set), MP_ROM_PTR(&mdac_offset_set_obj) }, + { MP_ROM_QSTR(MP_QSTR_invert_set), MP_ROM_PTR(&mdac_invert_set_obj) }, + { MP_ROM_QSTR(MP_QSTR_frequency_get), MP_ROM_PTR(&mdac_frequency_get_obj) } }; STATIC MP_DEFINE_CONST_DICT(mdac_locals_dict, mdac_locals_dict_table);