esphome / feature-requests

ESPHome Feature Request Tracker
https://esphome.io/
422 stars 27 forks source link

ESP32 cosine generator #1644

Closed sunshine-nick closed 2 years ago

sunshine-nick commented 2 years ago

Describe the problem you have/What new integration you would like

ESP32 has a built in cosine generator see https://github.com/krzychb/dac-cosine I would like to add this to toESPHome and use it like ledC pwm I have the relevant ESP32 and IDF experience but have not compiled ESPHome from scratch. Is there anyone who has added the cosine generator already or is there anyone that would like to help me do this. Please describe your use case for this integration and alternatives you've tried:

I need to use a sine wave (10 -50KHz) to for generating AC signals, neasuring response and applying ouput to opamp gain circuits. I would use this in a very similar way to using the ESP32 ledC output. API. Entity state is on/off and attributes would be chan nr , frequency and possibly amplitude. I should add that using the cosine generator adds no CPU overhead whereas an interrupt driven dac loads the cpu and may affect other running timers. So there is a clear advantage in going this route. Additional context

I have 5 years low level ESP32 experience with the ESP IDF and would like to compile ESPhome in IDF mode but have not got any experience with this and would like some help in setting up the correct tool chain

nagyrobi commented 2 years ago

Excellent idea!

huVVer commented 2 years ago

Here is a test program I finished yesterday in the Arduino IDE. Note that the DAC outputs need to be buffered to work properly. I used MCP6001UT rail-rail input/output OpAmps for this, and scale the outputs to 5V.

include <driver/dac.h>

dac_cw_config_t dac_cw_config;

bool leftright = true; // simple way to demonstrate left or right channels uint32_t frequency = 131; uint8_t Left = 0, Right = 0; static const int i2s_num = 0; // i2s port number

// // Stereo tones. 4 levels (1 - 4) of logarithmic tones, plus tone OFF (0) per channel. // void Tones (uint32_t frequency, int8_t levelLeft, int8_t levelRight){

dac_cw_scale_t Scale;

switch (levelLeft) {
  case 0: 
  dac_output_disable(DAC_CHANNEL_1);  // off
  break;

  case 1: 
  dac_output_enable(DAC_CHANNEL_1);
  Scale = DAC_CW_SCALE_8; // quietest
  break;

  case 2: 
  dac_output_enable(DAC_CHANNEL_1);
  Scale = DAC_CW_SCALE_4;
  break;

  case 3: 
  dac_output_enable(DAC_CHANNEL_1);
  Scale = DAC_CW_SCALE_2;
  break;

  case 4: 
  dac_output_enable(DAC_CHANNEL_1);
  Scale = DAC_CW_SCALE_1; // loudest
  break;

default:
  dac_output_disable(DAC_CHANNEL_1);
  break;
}

  dac_cw_config = {
    .en_ch  = DAC_CHANNEL_1,
    .scale  = Scale,
    .phase  = DAC_CW_PHASE_0,
    .freq = frequency,
    .offset = 127
  };

  dac_cw_generator_config (&dac_cw_config);

switch (levelRight) {
  case 0: 
  dac_output_disable(DAC_CHANNEL_2);  // off
  break;

  case 1: 
  dac_output_enable(DAC_CHANNEL_2);
  Scale = DAC_CW_SCALE_8; // quietest
  break;

  case 2: 
  dac_output_enable(DAC_CHANNEL_2);
  Scale = DAC_CW_SCALE_4;
  break;

  case 3: 
  dac_output_enable(DAC_CHANNEL_2);
  Scale = DAC_CW_SCALE_2;
  break;

  case 4: 
  dac_output_enable(DAC_CHANNEL_2);
  Scale = DAC_CW_SCALE_1; // loudest
  break;

default:
  dac_output_disable(DAC_CHANNEL_2);
  break;
}

  dac_cw_config = {
    .en_ch  = DAC_CHANNEL_2,
    .scale  = Scale,
    .phase  = DAC_CW_PHASE_0,
    .freq = frequency,
    .offset = 127
   };

   dac_cw_generator_config (&dac_cw_config);

};

void setup() { dac_output_enable(DAC_CHANNEL_1); dac_output_enable(DAC_CHANNEL_2); dacWrite(25, 127); // set zero point dacWrite(26, 127); // set zero point dac_cw_generator_enable(); Serial.begin (115200); delay (100); }

void loop() delay (62);

Tones (frequency, leftright, !leftright);

frequency += 20;
if (frequency > 1500){
  frequency = 131;
  leftright = !leftright;

  delay (500);
}

}

sunshine-nick commented 2 years ago

Hello HuVVer, Thanks alot. I shall be testing this later today. I know there is an example in the ESPhome dpcs on how to add Arduino style extensions so I will be studying that first. Thats the first hurdle for me as I normally only use the IDF command line, although I have tried a few arduino style ESP32 projects in the past to see how it works. So I understanmd the code you have written.

Just as a matter of interest, when I add this code through an ESPhome YAML script and compile how does this affect Platform IO dependancies for the DAC interface, Is platform IO just included in the ESPHome toolchain and works more or less transparently ?

Thanks alot for your help.

sunshine-nick commented 2 years ago

HI WuVVer, I have now learnt to compile ESPhome with the command line and it seems to be a sort of command line arduino package. I have added a customer (empty device successfully) now trying to add your code. But I get errors on missing type assignments shown below.

These types are not in the dac.h file and I dont see them in the orioginal example. I wonder if I am missing a layer that you added, where these types that you use in the interfcae calls are defined. ??

regards

Nick

VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV Compiling .pioenvs\ttgotest1\src\main.cpp.o In file included from src\main.cpp:28:0: src\my_custom_sensor.h:7:1: error: 'dac_cw_config_t' does not name a type dac_cw_config_t dac_cw_config; ^ src\my_custom_sensor.h: In member function 'void MyCustomSensor::Tones(uint32_t, int8_t, int8_t)': src\my_custom_sensor.h:19:2: error: 'dac_cw_scale_t' was not declared in this scope dac_cw_scale_t Scale; ^ src\my_custom_sensor.h:21:48: error: 'Scale' was not declared in this scope case 1: dac_output_enable(DAC_CHANNEL_1); Scale = DAC_CW_SCALE_8; // quietest break; ^ src\my_custom_sensor.h:21:56: error: 'DAC_CW_SCALE_8' was not declared in this scope case 1: dac_output_enable(DAC_CHANNEL_1); Scale = DAC_CW_SCALE_8; // quietest break;

sunshine-nick commented 2 years ago

Hi, well I solved my problem. I went back to the original example and checked some of the incloude files in hal_dac.c and moved the C code into a emprty ESPhome custome component, and he prestot ot worked. This is my ESPhome yamal file:

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv esphome: includes:

esp32: board: esp32dev framework: type: arduino

Enable logging

logger:

Enable Home Assistant API

api: password: ""

ota: password: ""

wifi: ssid: "work2" password: "tangray21"

Enable fallback hotspot (captive portal) in case wifi connection fails

ap: ssid: "Ttgotest1 Fallback Hotspot" password: "nZfEshRRytzU"

captive_portal:

light:

Example configuration entry

sensor:

^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

And this is my C/C++ test case.

vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

include "esp32-hal-dac.h"

include "freertos/FreeRTOS.h"

include "freertos/task.h"

include "rom/ets_sys.h"

include "esp_attr.h"

include "esp_intr.h"

include "soc/rtc_io_reg.h"

include "soc/rtc_cntl_reg.h"

include "soc/sens_reg.h"

include "soc/rtc.h"

include <driver/dac.h>

include "esphome.h"

int clk_8m_div = 0; // RTC 8M clock divider (division is by clk_8m_div+1, i.e. 0 means 8MHz frequency) int frequency_step = 8; // Frequency step for CW generator int scale = 1; // 50% of the full scale int offset; // leave it default / 0 = no any offset int invert = 2; // invert MSB to get sine waveform char sbuf[80];

class MyCustomSensor : public PollingComponent, public Sensor {

void dac_cosine_enable(dac_channel_t channel) { // Enable tone generator common to both channels SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN); switch(channel) { 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 : printf("Channel %d\n", channel); } }

void dac_frequency_set(int clk_8m_div, int frequency_step) { REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, clk_8m_div); SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP, frequency_step, SENS_SW_FSTEP_S); }

/*

/*

/*

public: float count = 10.0;

// constructor MyCustomSensor() : PollingComponent(5000) {}

float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; }

void setup() override { // This will be called by App.setup() dac_cosine_enable(DAC_CHANNEL_1); dac_cosine_enable(DAC_CHANNEL_2);

dac_output_enable(DAC_CHANNEL_1); dac_output_enable(DAC_CHANNEL_2);
}

void update() override { // This will be called every "update_interval" milliseconds. publish_state(count); count = count +1; if (count == 40) count = 10;

// frequency setting is common to both channels dac_frequency_set(clk_8m_div, frequency_step); /* Tune parameters of channel 2 only

};

sunshine-nick commented 2 years ago

So I get this feature request issue is closed.