Notifications
Clear all

A $5 Function Generator with ESP32


Pugwash
(@pugwash)
Noble Member
Joined: 3 years ago
Posts: 1011
Topic starter  

I recently received the AZDelivery(Germany) newsletter and found this most interesting setup for an ESP32.

The following code was posted on a public forum therefore free for the public domain.

For those that can read German here is the original website:

https://www.az-delivery.de/blogs/azdelivery-blog-fur-arduino-und-raspberry-pi/funktionsgenerator-mit-dem-esp32-teil1?goal=0_569b1a8f94-c0c38c5a96-18812343&mc_cid=c0c38c5a96&mc_eid=0c167ed24f

The code has been tested by myself on an ESPVROOM32 and works.

I have taken the liberty of translating the screen output and sketch description into English but if anyone wants to take on a translation of the code comments, be my guest.

How it works!

Attach an oscilloscope to Gnd and GPIO26.

Fire up the ESP32 and you will be required to enter the wave type required i.e. MS for sine wave, MT for triangle wave and MR for a square wave. The generator initially starts with the default settings 1000Hz and 50% pulse width.

So here is the code:

 

/*  Function generator for Sine, Triangle and Square Waves
 *  Frequency Range 20 Hz to 20 KHz
 *  Pulse width control from 1 to 100%
 *  Output signal positive 3.3V
*/

//Bibliotheken zum direkten Zugriff auf Steuerregister des ESP32
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
#include "soc/rtc.h"

//Bibliotheken zur Verwendung des Digital zu Analog Konverters und für den I2S-Bus
#include "driver/dac.h"
#include "driver/i2s.h"

#define SINFAKT 127.0 //gemessen für Schrittweite = 1 und kein Vorteiler (8.3MHz)

//Buffer zum Erstellen der Dreieckfunktion
uint32_t buf[128];

//Einstellwerte für Kurvenform, Frequenz und Tastverhältnis
char mode = 'S'; //S=Sinus, R=Rechteck, T=Dreieck
float frequency = 1000; //20 bis 200000 Hz
uint8_t ratio = 50; //Tastverhältnis 0 bis 100%

//Flag Ist wahr, wenn die Initialisierung bereits erfolgte
bool initDone = false;

//Konfiguration für den I2S Bus
i2s_config_t i2s_config = {
     .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN), //Betriebsart
     .sample_rate = 100000, //Abtastrate
     .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // der DAC verwendet nur 8 Bit des MSB
     .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // Kanalformat ESP32 unterstützt nur Stereo
     .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S_MSB, //Standard Format für I2S
     .intr_alloc_flags = 0, // Standard Interrupt 
     .dma_buf_count = 2, //Anzahl der FIFO Buffer
     .dma_buf_len = 32, //Größe der FIFO Buffer
     .use_apll = 0 //Taktquelle
    };


//Buffer für Dreieck Wellenform füllen
//Parameter up ist die Dauer für den Anstieg in Prozent
//Parameter sz gibt die Buffergröße für eine Periode an
//es werden die Werte für eine Periode in den Buffer geschrieben
void fillBuffer(uint8_t up, uint8_t sz) {
  uint8_t down;  //Zeit für die fallende Flanke in %
  uint32_t sample; //32Bit Datenwort (I2S benötigt zwei Kanäle mit je 16 Bit
  float du,dd,val; //Hilfsvariablen
  down=100-up;
  //Anzahl der Schritte für Anstieg und Abfall berechnen
  uint16_t stup = round(1.0*sz/100 * up);
  uint16_t stdwn = round(1.0*sz/100*down);
  uint16_t i;
  if ((stup + stdwn) < sz) stup++;//Ausgleich eventueller Rundungsfehler
  //Amplitudenänderung pro Schritt für Anstieg und Abfall 
  du = 256.0/stup;
  dd = 256.0/stdwn;
  //füllen des Buffers
  val = 0; //Anstieg beginnt mit 0
  for (i=0; i<stup; i++) {
    sample = val; 
    sample = sample << 8; //Byte in das höherwertige Byte verschieben 
    buf[i]=sample;
    val = val+du; //Wert erhöhen
  }
  val=255; //Abfallende Flanke beginnt mit Maximalwert
  //Rest wie bei der ansteigenden Flanke
  for (i=0; i<stdwn; i++) {
    sample = val;
    sample = sample << 8;
    buf[i+stup]=sample;
    val = val-dd;
  }
}


//Alle  Ausgänge stoppen
void stopAll(){
    ledcDetachPin(26); 
    i2s_driver_uninstall((i2s_port_t)0); 
    dac_output_disable(DAC_CHANNEL_2);
    dac_i2s_disable();
    initDone=false;
}

//Kurvenform Rechteck starten
//Pin 26 als Ausgang zuweisen
void startRectangle(){
    ledcAttachPin(26,1 );
    initDone=true;
}

//Frequenz für Rechteck setzen mit entsprechendem Tastverhältnis
void rectangleSetFrequency(double frequency,uint8_t ratio)
{
    ledcSetup(1,frequency,7); //Wir nutzen die LEDC Funktion mit 7 bit Auflösung
    ledcWrite(1,127.0*ratio/100);  //Berechnung der Schrittanzahl für Zustand = 1
}


//Dreiecksignal starten
void startTriangle(){
  i2s_set_pin((i2s_port_t)0, NULL); //I2S wird mit dem DAC genutzt
    initDone=true;
}

//Frequenz für Dreieck setzen mit entsprechendem Tastverhältnis
double triangleSetFrequency(double frequency,uint8_t ratio)
{
  int size=64;
  //zuerst wird die geeignete Buffergröße ermittelt
  //damit die Ausgabe funktionier muss die I2S Abtastrate zwischen
  //5200 und 650000 liegen
  if (frequency<5000) {
    size = 64;
  } else if (frequency<10000) {
    size = 32;
  } else if (frequency<20000) {
    size = 16;
  } else {
    size = 8;
  }
  //Abtastrate muss in einer Periode beide Buffer ausgeben
  uint32_t rate = frequency * 2 * size;
  //Die Abtastrate darf nur innerhalb der Grenzwerte liegen
  if (rate < 5200) rate = 5200;
  if (rate > 650000) rate = 650000;
  //wirklichen Frequenzwert setzen
  frequency = rate / 2 / size;

  //I2S Treiber entfernen 
  i2s_driver_uninstall((i2s_port_t)0);
  //Konfiguration anpassen 
  i2s_config.sample_rate = rate;
  i2s_config.dma_buf_len = size;
  //und mit der neuen Konfiguration installieren
  i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
  //Abtastrate einstellen
  i2s_set_sample_rates((i2s_port_t)0, rate); 
  //Buffer füllen
  fillBuffer(ratio,size*2);
  //und einmal ausgeben
  i2s_write_bytes((i2s_port_t)0, (const char *)&buf, size*8, 100);  
  return frequency;
}

//Sinusausgabe vorbereiten
void startSinus(){
    //Ausgang für Pin26 freigeben
    dac_output_enable(DAC_CHANNEL_2);
    // Sinusgenerator aktivieren
    SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN);
    // Ausgabe auf Kanal 1 starten
    SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M);
    // Vorzeichenbit umkehren
    SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV2, 2, SENS_DAC_INV2_S);
    initDone=true;
}

//Frequenz für Sinus setzen
double sinusSetFrequency(double frequency)
{
  //Formel f = s * SINFAKT / v
  //s sind die Schritte pro Taktimpuls
  //v ist der Vorteiler für den 8MHz Takt
  //Es gibt 8 Vorteiler von 1 bis 1/8 um die Kombination Vorteiler und
  //Schrittanzahl zu finden, testen wir alle acht Vorteiler Varianten
  //Die Kombination mit der geringsten Frequenzabweichung wird gewählt
  
    double f,delta,delta_min = 999999999.0;
    uint16_t divi=0, step=1, s;
    uint8_t clk_8m_div = 0;//0 bis 7
    for (uint8_t div = 1; div<9; div++){
      s=round(frequency * div/SINFAKT);
      if ((s>0) && ((div == 1) || (s<1024))) {
        f= SINFAKT*s/div;
        /*
        Serial.print(f); Serial.print(" ");
        Serial.print(div); Serial.print(" ");
        Serial.println(s);
        */
        delta = abs(f-frequency);
        if (delta < delta_min) { //Abweichung geringer -> aktuelle Werte merken
          step = s; divi = div-1; delta_min = delta;
        }
      }
    }
    //wirklichen Frequenzwert setzen
    frequency = SINFAKT * step / (divi+1);
    // Vorteiler einstellen
    REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, divi);
    // Schritte pro Taktimpuls einstellen
    SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP, step, SENS_SW_FSTEP_S);
    return frequency;
}

//Einstellungsänderungen durchführen
void controlGenerator() {
  switch (mode) {
    case 'S' :
    case 's': if (!initDone) startSinus();
        frequency = sinusSetFrequency(frequency);
        break;
    case 'T' :
    case 't' : if (!initDone) startTriangle();
        frequency = triangleSetFrequency(frequency,ratio);
        break;
    case 'R' :
    case 'r' : if (!initDone) startRectangle();
        rectangleSetFrequency(frequency,ratio);
        break;
  }
}

//Serielle Schnittstelle aktivieren und 
//Defaulteinstellungen 1kHz Sinus setzen
void setup()
{
    Serial.begin(115200);
    controlGenerator();
    Serial.println("Commands :");
    Serial.println("MS = Sine Wave \t MT = Triangle Wave \t MR = Square Wave");
    Serial.println("F#### = Frequency Hz");
    Serial.println("R## = Pulse Width %");
}


void loop(){
  //Serielle Schnittstelle abfragen
  if (Serial.available() > 0) {
    //Befehl von der Schnittstelle einlesen
    String inp = Serial.readStringUntil('\n');
    //und zur Kontrolle ausgeben
    Serial.println(inp);
    char cmd = inp[0]; //erstes Zeichen ist das Kommando 
    if ((cmd == 'M') || (cmd == 'm')) { //war das Zeichen 'M' wird die Betriebsart eingestellt
      char newMode = inp[1]; //zweites Zeichen ist die Betriebsart
      if (newMode != mode) { //Nur wenn eine Änderung vorliegt, mus was getan werden
        stopAll(); 
        mode=newMode;
        controlGenerator();
      }
    } else {
      //bei den anderen Befehlen folgt ein Zahlenwert
      String dat = inp.substring(1);
      //je nach Befehl, werden die Daten geändert
      switch (cmd) {
        case 'F' :
        case 'f' :frequency = dat.toDouble(); break; //Frequenz
        case 'R' :
        case 'r' :ratio = dat.toInt(); break;  //Tastverhältnis
      }
      //Grenzwerte werden überprüft
      if (ratio > 100) ratio = 100;
      if (frequency < 20) frequency = 20;
      if (frequency > 200000) frequency = 200000;
      controlGenerator();
    }
    //aktuelle Werte ausgeben
    String ba;
    switch (mode) {
      case 'S':
      case 's': ba="Sine"; break;
      case 'T':
      case 't': ba="Triangle"; break;
      case 'R':
      case 'r': ba="Square"; break;
    }
    Serial.println("************* Current Settings **********************");
    Serial.print("Operation Mode    = "); Serial.println(ba);
    Serial.print("Frequency         = "); Serial.print(frequency); Serial.println("Hz");
    Serial.print("Keying Ratio      = "); Serial.print(ratio); Serial.println("%");
    Serial.println();
    Serial.println("Commands :");
    Serial.println("MS = Sine Wave \t MT = Triangle Wave \t MR = Square Wave");
    Serial.println("F#### = Frequency Hz");
    Serial.println("R## = Pulse Width %");
  }
}

 

Now for the limitations:

Sine and square wave from 20Hz to 200kHz

Triangle wave from 40Hz to 20kHz

Just download this file instead!


Quote
Pugwash
(@pugwash)
Noble Member
Joined: 3 years ago
Posts: 1011
Topic starter  

Further testing revealed that both square and triangle wave frequencies were quite accurate (less than 0.5%) but the sine wave frequency was off to about +2.5%.

I tested this at 8kHz, 40kHz and 80kHZ. The resulting sine wave frequency was measured to be 8.2KHz, 41.1kHz and 82.1kHz respectively.

So I tweaked the SINFAKT constant from 127.0 to 130.0 and this has corrected the error.

This tweak had no noticeable effect on the square and triangle wave frequencies.

I also noticed that the square wave was producing about 21% overshoot and undershoot but a small capacitor in the pF range should be enough to compensate for this.


ReplyQuote
YurkshireLad
(@yurkshirelad)
Reputable Member
Joined: 2 years ago
Posts: 465
 

I think I'll try this out - I need a basic sine wave generator with a low frequency. Thanks!


ReplyQuote
YurkshireLad
(@yurkshirelad)
Reputable Member
Joined: 2 years ago
Posts: 465
 

I ran the article through Google translate:

In this article we want to build a function generator with an ESP32 that uses 100 percent of the hardware of the ESP32. The software is only used for operation. Since the waveforms are generated by the built-in hardware of the ESP32, there are no interference with the program flow.

The function generator supplies sine and square wave signals with a frequency of 20Hz to 200kHz, as well as triangular signals with a frequency of 40Hz to 20kHz. The pulse duty factor can be set between 0 and 100% for rectangles and triangles. The output voltage is only positive between 0 and 3.3 V. The signal can be taken from GPIO26 of the ESP32. It is operated via the serial interface.

In the second part, the function generator receives a display and operation via joystick and, of course, a housing from the 3D printer.

The sine generator

The ESP32 has a built-in sine wave generator that can output its signal at the two digital to analog converter outputs (GPIO25 and GPIO26). A period can be divided into up to 65536 steps. The internal 8MHz clock is used as the clock. This means that for one step per cycle the frequency would have to be 8,000,000 / 65536 = 122 Hz. Tests have shown, however, that the frequency with this setting is 127 Hz. Thus the internal clock is higher than 8MHz.

To set the frequency, the step size per cycle can be set. That means the frequency = 127 * step size. The frequency can thus be set in 127 Hz steps. Since this is too imprecise for low frequencies, there is a second setting option. The measure can be divided by 1 to 8. This means that the lowest frequency is 127/8 = 15.9 Hz. The entire frequency formula is therefore frequency = 127 / prescaler * increment. For small frequencies it looks like this.

You can see that in order to approximate a desired frequency as closely as possible, one has to try out one of the two variables, step size or prescaler. Since the increment 65536 possibilities, but the prescaler only has eight possibilities, it is obvious to try out the prescaler.

To set the frequency, we calculate the step size for each of the possible prescaler settings and use the one at which the smallest frequency deviation occurs. In order to achieve a nice sinusoidal shape, the step size should not be larger than 1024. A sine wave is thus composed of 64 steps.

Since there is no complete library for the sine wave generator, the corresponding bits must be set in the control registers of the ESP32. Anyone who is interested in how this works in detail will receive the necessary information from

ESP32 Technical Reference Manual

and to control the registers from the Arduino IDE

https://github.com/espressif/arduino-esp32/blob/master/tools/sdk/include/soc/soc/soc.h

The rectangle generator

The ESP32 has internal timers with which square-wave signals with an adjustable duty cycle can be generated at any GPIO pin. These signals are primarily intended to generate pulse width modulation, but can also be used as a square wave generator with a variable duty cycle.

With the function ledcAttachPin (26,1) GPIO26 is defined as a signal output for timer 1. The function ledcSetup (1, frequency, 7) sets the frequency for Timer1 and the resolution for the duty cycle to 7 bits. The function ledcWrite (1,127.0 * ratio / 100) sets the duty cycle of Timer1. 127 is the maximum number of steps with 7 bits. The variable ratio contains the duty cycle in percent. The connection GPIO26 is released again with ledcDetachPin (26).

The triangle generator

The built-in I2S interface was used for the triangle generator. An operating mode of the I2S interface enables audio data, for example from a WAV file, to be output to the two analog outputs GPIO25 and GPIO26.

The output takes place as a stereo signal, namely the right channel on GPIO25 and the left on GPO26. Each sample has 32 bits. The more significant 16 bits contain the right and the less significant the left channel. Other settings for 1-channel output and 8-bit are possible, but do not work. A FiFo (First in, First out) buffer is used for output.

Now the trick that we use to create a triangle generator with it. If the entire FiFo buffer is filled with exactly one period of the triangular signal and no further writing takes place, the I2S interface outputs the contents of the FiFo buffer again and again at the set sampling rate.

Experiments have shown that the sampling rate can be between 5.2 kHz and 650 kHz. If we use 128 samples for a period, this results in a frequency range of 5200/128 = 40.6 Hz to 5.1 kHz. For higher frequencies, the number of samples per period must be reduced. With 64 samples you get 10kHz with 32 samples 20kHz and with 16 samples 40kHz. However, the shape of the curve becomes worse and worse as the number of samples decreases. See second figure with 16 steps per period.

The duty cycle can be used to create a sawtooth instead of a triangle signal. Depending on the pulse duty factor, the sampled values are divided per period. With a pulse duty factor of 20%, 0.2 * 128 = 26 steps are used for the rise and 102 steps for the falling edge.

The software

(Source Code)

Service

It is operated via the serial interface. At the top of the serial monitor is a line in which the commands can be entered. With the "Send" button, the text is sent to the serial interface.

The following commands are possible:

MS sinus
MR rectangle
MT triangle
F #### frequency in heart
R ## duty cycle in percent

Lower case letters can also be used. # stands for number entry.

Here is the whole article for download.


ReplyQuote
YurkshireLad
(@yurkshirelad)
Reputable Member
Joined: 2 years ago
Posts: 465
 

Comments translated (I don't know how to enable syntax highlighting):

 

/*  Function generator for Sine, Triangle and Square Waves
    Frequency Range 20 Hz to 20 KHz
    Pulse width control from 1 to 100%
    Output signal positive 3.3V
*/

//Libraries for direct access to ESP32 tax registers
#include "soc/rtc_cntl_reg.h"
#include "soc/sens_reg.h"
#include "soc/rtc.h"

//Libraries for using the digital to analog converter and for the I2S bus
#include "driver/dac.h"
#include "driver/i2s.h"

#define SINFAKT 127.0 //measured for step size = 1 and no advantage (8.3MHz)

//Buffer for creating the triangle function
uint32_t buf[128];

//Setting values for curve shape, frequency and duty cycle
char mode = 'S'; //S=Sinus, R=Rectangle, T=Triangle
float frequency = 1000; //20 until 200000 Hz
uint8_t ratio = 50; //Duty cycle 0 until 100%

//Flag Is true if initialization has already been done
bool initDone = false;

//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), //Mode
  .sample_rate = 100000, //Sampling rate
  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // the DAC uses only 8 bits of the MSB
  .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // Channel format ESP32 supports stereo only
  .communication_format = (i2s_comm_format_t)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 FIFO buffers
  .use_apll = 0 //Taktquelle
};


//Buffer by triangle Fill waveform
//Parameter up is the duration for the increase in percent
//Parameter sz specifies the buffer size for a period
//the values for a period are written to the buffer
void fillBuffer(uint8_t up, uint8_t sz) {
  uint8_t down;  //Time for the falling flank in %
  uint32_t sample; //32Bit data word (I2S requires two channels of 16 bits each
  float du, dd, val; //Auxiliary variables
  down = 100 - up;
  //Calculate the number of steps for rise and fall
  uint16_t stup = round(1.0 * sz / 100 * up);
  uint16_t stdwn = round(1.0 * sz / 100 * down);
  uint16_t i;
  if ((stup + stdwn) < sz) stup++;//Compensation for possible rounding errors
  //Amplitude change per step for rise and fall
  du = 256.0 / stup;
  dd = 256.0 / stdwn;
  //filling the buffer
  val = 0; //Increase starts with 0
  for (i = 0; i < stup; i++) {
    sample = val;
    sample = sample << 8; //Move bytes to the higher-value byte
    buf[i] = sample;
    val = val + du; //Increase value
  }
  val = 255; //Falling flank starts with maximum value
  //Rest as with the rising flank
  for (i = 0; i < stdwn; i++) {
    sample = val;
    sample = sample << 8;
    buf[i + stup] = sample;
    val = val - dd;
  }
}


//Stop all outputs
void stopAll() {
  ledcDetachPin(26);
  i2s_driver_uninstall((i2s_port_t)0);
  dac_output_disable(DAC_CHANNEL_2);
  dac_i2s_disable();
  initDone = false;
}

//Start curve shape rectangle
//Pin 26 assign as output
void startRectangle() {
  ledcAttachPin(26, 1 );
  initDone = true;
}

//Set frequency for rectangle with corresponding duty cycle
void rectangleSetFrequency(double frequency, uint8_t ratio)
{
  ledcSetup(1, frequency, 7); //We use the LEDC function with 7 bit resolution
  ledcWrite(1, 127.0 * ratio / 100); //Calculation of the number of steps for state = 1
}


//Start triangle signal
void startTriangle() {
  i2s_set_pin((i2s_port_t)0, NULL); //I2S is used with the DAC
  initDone = true;
}

//Set frequency for triangle with corresponding 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 be 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 limit values
  if (rate < 5200) rate = 5200;
  if (rate > 650000) rate = 650000;
  //set the real frequency value
  frequency = rate / 2 / size;

  //Remove I2S driver
  i2s_driver_uninstall((i2s_port_t)0);
  //Customize Configuration
  i2s_config.sample_rate = rate;
  i2s_config.dma_buf_len = size;
  //and install with the new configuration
  i2s_driver_install((i2s_port_t)0, &i2s_config, 0, NULL);
  //Setting the sampling rate
  i2s_set_sample_rates((i2s_port_t)0, rate);
  //Fill Buffer
  fillBuffer(ratio, size * 2);
  //and spend once
  i2s_write_bytes((i2s_port_t)0, (const char *)&buf, size * 8, 100);
  return frequency;
}

//Preparing sine wave output
void startSinus() {
  //Release output for Pin26
  dac_output_enable(DAC_CHANNEL_2);
  // Activate sine wave generator
  SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN);
  // Start output on channel 1
  SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN2_M);
  // Reverse Sign Bit
  SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV2, 2, SENS_DAC_INV2_S);
  initDone = true;
}

//Set frequency for sine
double sinusSetFrequency(double frequency)
{
  //Formula f = s * SINFAKT / v
  //s are the steps per clock pulse
  //v is the advantage for the 8MHz clock
  //There are 8 advantages from 1 to 1/8 around the combination of advantages and
  //To find the number of steps, we test all eight advantage variants
  //Die Kombination mit der geringsten Frequenzabweichung wird gewählt

  double f, delta, delta_min = 999999999.0;
  uint16_t divi = 0, step = 1, s;
  uint8_t clk_8m_div = 0;//0 bis 7
  for (uint8_t div = 1; div < 9; div++) {
    s = round(frequency * div / SINFAKT);
    if ((s > 0) && ((div == 1) || (s < 1024))) {
      f = SINFAKT * s / div;
      /*
        Serial.print(f); Serial.print(" ");
        Serial.print(div); Serial.print(" ");
        Serial.println(s);
      */
      delta = abs(f - frequency);
      if (delta < delta_min) { //Deviation of less -> remember current values
        step = s; divi = div - 1; delta_min = delta;
      }
    }
  }
  //set the real frequency value
  frequency = SINFAKT * step / (divi + 1);
  // Vorteiler einstellen
  REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, divi);
  // Set steps per clock pulse
  SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP, step, SENS_SW_FSTEP_S);
  return frequency;
}

//Make setting changes
void controlGenerator() {
  switch (mode) {
    case 'S' :
    case 's': if (!initDone) startSinus();
      frequency = sinusSetFrequency(frequency);
      break;
    case 'T' :
    case 't' : if (!initDone) startTriangle();
      frequency = triangleSetFrequency(frequency, ratio);
      break;
    case 'R' :
    case 'r' : if (!initDone) startRectangle();
      rectangleSetFrequency(frequency, ratio);
      break;
  }
}

//Enable serial port and
//Set default settings 1kHz sine
void setup()
{
  Serial.begin(115200);
  controlGenerator();
  Serial.println("Commands :");
  Serial.println("MS = Sine Wave \t MT = Triangle Wave \t MR = Square Wave");
  Serial.println("F#### = Frequency Hz");
  Serial.println("R## = Pulse Width %");
}


void loop() {
  //Query serial interface
  if (Serial.available() > 0) {
    //Read command from the interface
    String inp = Serial.readStringUntil('\n');
    //and output for control
    Serial.println(inp);
    char cmd = inp[0]; //first character is the command
    if ((cmd == 'M') || (cmd == 'm')) { //was the sign 'M' the operating mode is set
      char newMode = inp[1]; //the second character is the operating mode
      if (newMode != mode) { //Only if there is a change, what must be done
        stopAll();
        mode = newMode;
        controlGenerator();
      }
    } else {
      //the other commands are followed by a numerical value
      String dat = inp.substring(1);
      //depending on the command, the data is changed
      switch (cmd) {
        case 'F' :
        case 'f' : frequency = dat.toDouble(); break; //Frequency
        case 'R' :
        case 'r' : ratio = dat.toInt(); break; //Duty cycle
      }
      //Limit values are checked
      if (ratio > 100) ratio = 100;
      if (frequency < 20) frequency = 20;
      if (frequency > 200000) frequency = 200000;
      controlGenerator();
    }
    //Output current values
    String ba;
    switch (mode) {
      case 'S':
      case 's': ba = "Sine"; break;
      case 'T':
      case 't': ba = "Triangle"; break;
      case 'R':
      case 'r': ba = "Square"; break;
    }
    Serial.println("********** Current Settings *******************");
    Serial.print("Operation Mode    = "); Serial.println(ba);
    Serial.print("Frequency         = "); Serial.print(frequency); Serial.println("Hz");
    Serial.print("Keying Ratio      = "); Serial.print(ratio); Serial.println("%");
    Serial.println();
    Serial.println("Commands :");
    Serial.println("MS = Sine Wave \t MT = Triangle Wave \t MR = Square Wave");
    Serial.println("F#### = Frequency Hz");
    Serial.println("R## = Pulse Width %");
  }
}
This post was modified 11 months ago by YurkshireLad

ReplyQuote
YurkshireLad
(@yurkshirelad)
Reputable Member
Joined: 2 years ago
Posts: 465
 

I need to work out how to reduce the amplitude to somewhere around 500mV-1V peak to peak max.


ReplyQuote
robotBuilder
(@robotbuilder)
Noble Member
Joined: 3 years ago
Posts: 1500
 

@yurkshirelad 

Comments translated (I don't know how to enable syntax highlighting):

The important thing is you have the indents.  When it is copied and pasted into an IDE the highlights will appear as set for that IDE.

With the Arduino IDE you can Select All and the Save as HTML.  In your post hit the enter key a few times and then click the {;} button in the top bar. Insert between a <p></p> pair.

 

 


ReplyQuote
YurkshireLad
(@yurkshirelad)
Reputable Member
Joined: 2 years ago
Posts: 465
 

Thanks, I'll try to remember that for next time. 👍 


ReplyQuote