## DIY Spark Gap Transmitter

This article plunges you into the mesmerrizing world of early radio technology through the assembly and analysis of a DIY spark gap transmitter. This project offers a practical insight into the fascinating dynamics of damped harmonic oscillators and explains charasteristics such the logarithmic decrement, decay rate, damping factor, q factor, and beyond. Prepare to spark your curiosity in a technology that once revolutionized global communication.

Simple spark gap transmitter built from a handful of components on a acrylic glass base

#### Circuit

The circuitry in a spark gap transmitter is fairly straightforward. It’s worth noting, however, that the spark gap transmitters from the Titanic era were constructed differently. Nonetheless, the underlying principle is unchanged, and my design is primarily crafted for display purposes.

Simple spark gap transmitter with an emission frequency of around 3 MHz.

The spark gap is constructed using carriage bolts and acrylic. The breakdwon voltage of air is around 3 kV/mm. At least when assuming an idealized homogeneous field and an electrode spacing of 10mm. While this simplification ignores a bit of reality, delving into Paschen’s law is beyond the scope of this article. The spark gap is set for a breakdown voltage of roughly 18 kV, equivalent to an electrode gap of approximately 6mm.

The breakdown voltage is reached at a rate determined by R1 and C1, as outlined in Equation 1. Where VC1 is the voltage across C1, Vin the supply voltage of the circuit and t represents time in seconds. Specifically, the voltage across C1 approximates 63% of the input voltage after one time constant (equal to R1 multiplied by C1). This percentage climbs to roughly 87% after two time constants. Thus, by varying the input voltage, this RC circuit facilitates a straightforward method for modifying the spark gap’s triggering frequency.

(1)

Once the breakthrough voltage is reached, the spark gap activates, creating an almost direct path for electron flow. Subsequently, charge balancing among C1, C2, and L2 energizes the resonant LC circuit formed by C2 and L1. The resonant frequency mainly depends on L1 and C2 (Eq. 2). This statement is generally accurate, though certain factors can partially invalidate this assumption. These factors will be discussed in detail when presenting the actual test results. The components values used in this example should yield a resonant frequency of close to 3 MHz (Eq. 3).

(2)

(3)

The components in this circuit must be rated for the high voltage applied in this experiment. R1, C1, and C2 can withstand voltages exceeding 30 kV. L1 is a handmade, 8-turn air core inductor wound from enamelled copper wire, featuring a tap 1.5 windings from the ground connection to connect an antenna or oscilloscope probe. This tap also facilitates basic impedance matching for connecting a 50-ohm antenna to the circuit. For readers in the U.S., it’s important to note that the Federal Communications Commission (FCC) strictly prohibits damped wave emissions altogether. The reasons behind the term “damped wave emissions” and why regulatory authorities might disapprove will soon become clear when the emissions from this circuit are examined closer.

#### Testing and Analysis

For the initial test, a length of wire is soldered directly to the inductor’s tap. The input of the circuit is connected to my DIY high-voltage power supply. An XHDATA D-808 multi-band receiver, set to 2.995 MHz and Amplitude Modulation (AM), is utilized to monitor the emissions while keying the high-voltage supply on and off. The sound produced is clearly audible and difficult to characterize. Adjusting the supply voltage varies the spark gap’s firing rate, which in turn modifies the pitch of the audio.

The demodulated audio was recorded twice: first utilizing the SDRplay RSPdx and SDRuno and then with the XHDATA D-808 feeding directly into my sound card:

Next, the antena wire is removed and an oscilloscope is connected. Since I was not really in the mood of potentially frying one of my better scopes, I used the scope where high voltage induced damage would upset me the least. So I apologize in advance for the moderate to bad screen captures. With the oscilloscope set to single capture, the spark gap is fired and the resulting output signal recorded.

Test setup: High-voltage power supply (right), DIY spark gap transmitter (center) and oscilloscope (left)

A sinusoidal signal, characterized by a rapidly decaying amplitude, becomes immediately noticeable. The frequency is roughly 3 MHz. Observant readers may also detect approximately 1.5 cycles of a signal at a different frequency preceding the 3 MHz This off-frequency component will be ignored for now and addressed later in this article. It should be noted that this pulse repeats everytime the spark gap is triggered.

Waveform of a single emission pulse from the spark gap transmitter.

Observing that the amplitude envelope exhibits an exponential decay requires little creativity, showcasing a typical characteristic of damped oscillations. This behavior aligns with the principles of a damped harmonic oscillator, a common phenomenon in physics regardless of the system—be it an LC circuit, a spring, or a pendulum. As such, the output signal follows the behaviour shown in equation 4. Where V(t) is the output voltage at the time t, A the initial Amplitude, the decay rate, the angular frequency (). Characterizing all of those parameters isn’t all too difficult.

(4)

Closeup of a single output pulse from the spark gap transmitter

With proper markers in place and zoomed in a bit, and still ignoring the first 1.5 cycles, the frequency can be confirmed to be around 3 MHz, 2.976 MHz to be precise. The amplitude of the first peak is 266 V, the second peak is at 174 V and the consequent 3 peaks are at 126 V, 96 V, 72 V and 52 V. This is all the information needed to calculate the so called logarithmic decrement (Eq. 5 and 6).

(5)

(6)

Relating the logarithmic decrement to the time period of the oscillation (1/f), immediately yields the decay rate (Eq. 7 and 8).

(7)

(8)

With some basic math out of the way, the damped wave output of the spark gap transmitter can now mathematically be expressed as a function of time (Eq. 9).

(9)

This derived equation is then plotted using Python. The result looks indeed a lot like the signal captured with the oscilloscope.

Plot of a single output pulse with the parameters previously calculated

One thing that is missing from the plot, however, is the faster oscillation at the beginning, that I elected to ignore for now. Upon further inspection, this undesired oscillation occurs at around 10.42 MHz.

10.42 MHz oscillation at the beginning of each output pulse.

10.42 MHz is close enough to 3.33 times higher than the desired resonant frequency. Assuming that the inductance of L1 is constant, rearranging Equation 2 would yield such an oscillation if the parallel capacitance is around 1 nF. Which coincidentally is the capacitance of C1. The problem is that the moment the spark gap is triggered, C1 is fully charged and C2 is empty. Initially, the fully charged C1 alone interacts with L1, with C2’s contribution delayed, a process taking about 96 ns. This isn’t the sole issue. Once the spark gap triggers, incorporating both capacitors alters the resonant frequency, creating a third frequency slightly below that of C1 and L1 in isolation. The desired resonant frequency, determined solely by C1 and L1, is only achieved post spark extinguishment. Ignoring parasitics of course. Accordingly, the output spectrum looks fairly unappealing.

Output spectrum of the spark gap transmitter.

The output spectrum looks like an electromagnetic interference nightmare. Which answers the question of why regulatory agencies might have an issue with such transmitters. Speaking of bandwidth, since has been previously calculated, it’s just a small step to calculate the quality factor Q of this shocking harmonic oscillator. The first step is to calculate the damping ratio from (Eq. 10) through which the quality factor Q can be calculated using Equation 12.

(10)

(11)

(12)

(13)

Plugging all the values into the Equations yields a numerical Q factor of 11.56 (Eq. 10 and 13). At a frequency of 3 MHz this corresponds to a half-power bandwidth of approximately 260 kHz. Which seems to align reasonably well enough with the FFT spectrum.

#### Video

If a picture is worth a thousand words, then a video is worth a million. Therefore, I took the time and effort to also post a video on my YouTube channel:

#### Conclusions

[1] “DIY: Adjustable 30 kV High Voltage Power Supply”, Baltic Lab: https://baltic-lab.com/

## DIY: Adjustable 30 kV High Voltage Power Supply

High voltage power supplies can open interesting doors to research topics such as laser, x-ray, spark gap transmitters. And many other specialized niches. Unfortunately, laboratory grade high voltage power supplies are rather costly. Luckily, there is a much less expensive way to put high voltage experiments into the hobbyists reach. This article shows how to build an inexpensive and adjustable 5 kV to 30 kV high voltage supply from a commercial laser supply.

UPDATE: There also is a German version of this article bd the corresponding YouTube video available on my German blog: Einstellbares 30 kV Hochspannungs-Netzeil im Eigenbau

Homebrew 5 kV to 30 kV high voltage power supply.

#### Overview

The foundation of this project is a commercial laser power supply. It’s a 50 Watt CO2 laser supply made by the manufacturer VEVOR [1]. According to the manufacturer, it has a starting voltage of 20 kV and a maximum output current of 20 mA. In reality, the supply can start as low as 5 kV, produces voltages in excess of 30 kV and provides output currents of over 30 mA. These operating parameters seem perfect for laser and spark gap experiments.

Front view of the homebrew 5 kV to 30 kV high voltage power supply.

The entire project is mounted inside a 19" case. There is a (pretty fancy) single-pole, single-throw switch and a small momentary switch to enable the high-voltage output. A 10-turn potentiometer allows for easy adjustment of the desired output voltage. Current and voltmeters provide insight into the current operating parameters. To avoid having to find a panel mount high voltage connector, the high voltage cable (rated for up to 50 kV) was simply fed through a rubber grommet to the outside world. An inexpensive high voltage connector is then used to establish connections to future projects [3].

Top-view of the high-voltage power supply and its internal wiring around the VEVOR laser power supply

#### Current and Voltmeter

One primary design requirement is to have a voltmeter and a current meter to monitor voltage and current simultaneously. The current measurement is accomplished through a 30 mA panel meter. To stay clear of the high voltage itself, the current meter is installed so that the measurement occurs between the load and ground. Low side current sensing so to speak.

Front facing top-view of the high-voltage power supply and its internal wiring

A simple voltmeter would be quite easy to buy off the shelf for very little money, if it wasn’t necessary to measure up to 30 kV or possibly more. The simple, low-cost solution is to use a 50 µA current meter and a 1 Gigaohm resistor connected between the high voltage supply rail and ground. Following Ohm’s law (I = V/R), the current through a one Gigaohm resistor is conveniently 1 µA per kilovolt. So at 30 kV, the current meter schould indicate a current of 30 µA (Eq. 1).

A 1 Gigaohm resistor and a 0 – 50 µA Ammeter suitable for measuring voltages up to 50 kV

High voltage measurement set-up using a 1 Gigaohm resistor and a 0 to 50 μA ammeter

(1)

(2)

Careful readers might note that the current meter itself has an internal resistance and that, therefore, the real current is actually lower than expected. Thus the current meter’s indication should be lower than desired. While this is technically correct, with the meaurement resistor having a value of 1 Gigaohm, even a few hunded Kiloohms more make little to no difference. Equation 2 shows that even an extra 125 Kiloohms in series will still yield a result that is accurate to within two decimal places. Therefore, the meter’s internal resistance can be considered to be insignificant for this purpose. For the perfectionists out there, any resulting error can be trimmed out using the adjustment screw on the meter itself.

From left to right: High voltage output cable (red), 1 Gigaohms measurement Resistor, 50 µA current meter for voltage measurement through measurement resistor, ground connection and 30 mA current meter.

The solder junction between the high voltage output cable and the 1 Gigaohm measurement resistor poses a problem of its own; Usually, a bit of heat shrink tubing would be sufficient to prevent most exposed contacts from causing havoc. But not at a voltage of up to 30 kV. The solder joint is covered in multiple layers of MG Chemicals 4226A insulting varnish [2]. And while this varnish has an extremely high breakdown voltage of 3 kV per mil (= 0.0254mm), I am not stopping there. Several pieces of cloth are soaked in epoxy and carefully wrapped around the joint. This is quite literally a t-shirt cut up into little strips, soaked in epoxy. Somewhat peculiar but efficient.

#### Laser Supply Pinout & Control Wiring

Figuring out the pinout of the laser power supply took a bit of effort. While there was a manual included, it’s English and nomenclature was fairly questionable. With a bit of try and error (and an unintended laser discharge), I was able to figure out the proper pinout eventually.

Connections to the commercial laser power supply: Mains voltage input (left connector), output voltage adjustment (center connector) and enable / disable input (right connector)

To make life easier for anyone following along at home, here is the correct pinout table for the connectors on the laser power supply:

Mains Connector (left):

 Pin 1 (left) Pin 2 Pin 3 Pin 4 (right) Ground(white) Mains Ground(green/yellow) Mains Neutral(blue) Mains Phase(black) 

Control Voltage Input Connector (center):

 Pin 1 (left) Pin 2 Pin 3 Pin 4 Pin 5 Pin 6 (right) To Pin 2(black) To Pin 1(black) Not Connected Voltage adj. Ground(black) Voltage Control Input(orange) Voltage adj. 5 VDC(red) 

Enable / Disable Input Connector (right):

 Pin 1 (left) Pin 2 Pin 3 Pin 4 (right) Not Connected GND Out(Orange) 5 V DC Out(10K Resistor to Pin 4) Low-active input(orange) 

With the correct pinout figured out, the rest of the control wiring is pretty straight-foward. Mains power is fed into the casing through a switchable, fused IEC320 IEC60320 connector. The mains voltage is connected directly to the laser power supply. It should be mentioned that signal ground, high voltage ground and earth ground are connected internally inside of the laser supply. Just to be sure that there’s a proper connection to the metal frame of the 19" case, some of the paint is removed and a ring terminal with a dedicated ground wire is attached to the case. Additionally, the paint is also stripped below the laser supply before bolting it in place to ensure a good ground connection between the case and the supply itself.

Close-up of the internal wiring: IEC320 mains voltage intput connector (left), VEVOR laser power supply connectors (top) , enable / disable switches and voltage adjustment potentiometer (right)

A 10-turn, 10 Kiloohms potentiometer is used to set the desired output voltage. The control voltage input of the laser power supply expects a control voltage between 0 to 5 Volts to adjust the output voltage from minimum to maximum. Conveniently, ground and 5 V DC terminals are provided directly next to the control voltage input. The latter is connected to the wiper of the potentiometer, ground and 5 Volts DC to the ends of the resistive strip. A single-pole, single-throw switch and a momentary switch for enabling the high voltage output are wired in parallel and connected between ground and the active-low enable input. Just to be on the safe side, I added a 10 Kiloohms pull-up resistor between the enable pin and 5 V DC.

#### Test Results

Let’s be honest, testing a high-voltage power supply is the most fun part of the entire build. But I wanted this moment to be special. Simply producing some arcs is kind of uninspired. So I quickly built a (sub-optimal) spark gap transmitter with an emission frequency of around 3 MHz. With the spark gap connected to the power supply, the mains voltage is finally connected to the high voltage power supply. Somewhat fearful of what might arc over, the momentary switch is pushed and after adjusting the output voltage to a sufficiently high level, the spark gap transmitter hapilly fired away.

Spark-gap transmitter used for testing the high voltage power supply

Just as a teaser: The spark gap transmitter itself, including much better designs than the one shown here, will be the subject of an upcoming article. So stay tuned!

#### Conclusion

My idea of combining commercial reliability and low-costs homebrewing is a complete success. The overall cost ist about 100 to 150 USD, depending on where exactly the components are sourced. This high voltage power supply will be the foundation of some interesting future content. I should add that there are commercial laser supplies that are capable of providing up to 50 kV output voltage with currents in excess of 30 mA. So if you need even higher voltages than the 30 KV shown in this article, that might be an avenue to explore. However, the higher voltage laser supplies are of course also more expensive. Still a lot less expensive than a commercial high voltage laboratory power supply.

[1] VEVOR 50W CO2 Laser Power Supply, Amazon: https://amazon.com/
[2] MG Chemicals 4226A, Dr. Dietrich Müller GmbH: https://www.muellerbestellung.de/
[3] MCWlaser High Voltage connectors, Amazon: https://amazon.com/

## Diplexer with integrated Bias-T (Triplexer) for QO-100 and X-band microwave reception using LNBs

This article shows how to build a simple Triplexer (Diplexer plus integrated DC Bias-T) for use with commercial TV LNBs for QO-100 or X-band microwave experiments.

#### Introduction

When using modified, commercial LNBs for reception of the QO-100 amateur radio satellite or X-band microwave experiments, it is necessary to pass a clock-reference signal, a DC supply voltage and the receive IF signal through the same coax. It is, therefore, necessary to separate these signals at the other end of the coax. For that purpose a Diplexer for IF / clock-reference separation plus an additional Bias-T is needed. Additionally, the LNBs are designed for a typical systems impedance of 75 Ohms instead of the 50 Ohms commonly used in RF communications equipment. An ideal Diplexer with integrated bias-T, which I will call a Triplexer from now on, also takes care of the impedance matching.

Different PCB prototype versions of the Diplexer with integrated bias-T

#### Circuit Design

For the Intermediate Frequency (IF) and clock-reference separation, the circuit makes use of two T-type LC-networks. Their corner frequencies are calculated in a way that even reception on the 2m amateur radio band would be possible on the IF-side of the setup. The additional benefit of the low-pass characteristic of the T-network on the clock-reference side is that a reference square wave will effectively be turned into a sine-wave. Therefore, square wave shaped clock signal sources can be used.

Schematic of the LNB Triplexer (Diplexer with integrated Bias-T) for use in QO-100 reception and X-band microwave receive setups.

The IF and reference-clock nodes are then combined and sent to the primary side of a MiniCircuits ADT1.5-17+ transformer with an impedance transformation ratio of 1:1.5 [1]. The secondary side of the transformer (now 75 Ohm impedance) is isolated from the DC bias-T by a simple blocking capacitor. The bias-T itself, consisting out of C5 and L4, is a simple LC-low-pass filter. The combined signal-path from the secondary side of T1 and the supply voltage from the bias-T are combined and connected to an F-type output connector.

For C1, C2 and C3 no capacitors with class 2 dielectric are recommended. C0G or NP0 type capacitors are recommended to provider more stability of the passband characteristics of the T-filters.

#### PCB Design

Except for the RF-connectors and the DC input terminal, all components are surface mount devices. By using mostly 1206 footprint passive components, this is still extremely easy to assemble at home. The PCB layout is intended for standard 1.6mm thick FR4 material and a . The general design-considerations regarding impedance matching of PCB traces, or rather to what extend it is even necessary, from my article “Critical length of a PCB trace and when to treat it as a transmission line” [2 have been applied in this design. For that reason, the IF portion is situated a lot closer to the primary side of the transformer than the reference-clock input.

PCB design with part markings, without copper pur (for better visibility) for the Triplexer.

The PCB features two edge-mount SMA connectors for the reference-clock input and the IF output. The DC supply voltage can be applied using 2,54 mm screw terminals. Care has been taken to orient all inductors in a way that coupling of their magnetic fields is reduced to a minimum.

Triplexer PCB with all components except the connectors assembled.

Backside of the Triplexer PCB with F-type connector for the LNB-syde, edge mount SMA connectors and screw terminal for the DC supply voltage

#### Test Results

For a subjective, general function test, modified Goobay 67269 LNB [3] was used for initial testing. The LNB was connected to the F-connector using a few metres of 75 Ohm coax. As a test signal source with a frequency of 10.488 GHZ, the 23rd harmonic of a 456 MHz signal (Po = 500 mW) from a handheld radio was used. The LO frequency was set to 10 GHz by injecting a reference-clock frequency 25.641025 MHz.

Output from the IF port viewed on a SDRPlay RSPdx, reference input frequency 10.488 GHz, reference clock input = 25.641025 MHz

It should be noted that for this prototype test, I deviated from the calculated component values. 270 nH inductors where used in Place of the 220 nH inductors (L2 and L3)and some deviation was also on the IF-side, but I can’t really remember what capacitor values were actually used. I will update the figures once I have the updated prototype revision with the correct component values assembled. Nonetheless, port characteristics were measured with a NanoVNA.

Nano VNA used to verify the port characteristics of the IF port. The LNB port and the reference clock port are properly terminated with a 75 Ohm (LNB) and 50 Ohm load (reference clock).

To measure the input VSWR of the IF port, the LNB connector was properly terminated with a 75 Ohm load. The reference clock input was terminated with a 50 Ohm load. The resulting VSWR-plot is okay, but I’d like to see better results than that.

S11 VSWR of the IF port with all other ports properly terminated.

The test was repeated on the reference-clock port by connecting the NanoVNA to the reference-clock input port and terminating the IF port with a 50 Ohm load. The result is acceptable but should be re-verified with the next revision.

Reference clock input port VSWR measurement with all other ports properly terminated.

#### Conclusions

This article shows a simple design approach on how to implement a functional triplexer design for QO-100 reception and X-band microwave experiments using with a modified LNB. Some improvements will be implemented in the next revision, such as a larger power inductor being able to handle higher supply currents. When the next revision becomes available, I will update this article to provide measurements and the design-files (Gerber and drill files).

[2] Westerhold, S. (2023), “Critical length of a PCB trace and when to treat it as a transmission line”. Baltic Lab High Frequency Projects Blog. ISSN (Online): 2751-8140.: https://baltic-lab.com

[3] Westerhold, S. (2023), “LNB Modification for 10 GHz QO-100 Satellite Reception”. Baltic Lab High Frequency Projects Blog. ISSN (Online): 2751-8140.: https://baltic-lab.com

## Tremolo DSP Effect for Arduino

This article continues my newly acquired interest in implementing digital signal processing algorithms on the Arduino GIGA R1 by showing how to implement a tremolo effect algorithm in software.

After showing how to implement an Audio Loopback Device and a DSP Diode Clipping Algorithm for Overdrive and Distortion Effects on the Arduino GIGA R1 WiFi, this article will show how to implement a tremolo effect in software.

#### The Tremolo Effect

The tremolo effect has 2 basic parameters: Depth and Frequency. The depth determines how much the incoming audio signal is being altered by the effect and inversely how much of the original signal is passed-through unaltered. The frequency determines the frequency of a low-frequency oscillator. Another common effect parameter is the waveform itself. Sinusoidal, triangle and rectangle LFO waveforms are common.

Block Diagram of a Tremolo Effect

The LFO produces a waveform at the pre-selected frequency. A portion of the incoming audio signal (or all) corresponding to the set depth-amount is then multiplied by the LFO signal, shaping it’s waveform envelope. The resulting signal is then added back together with the desired portion (or none) of the unaltered, original signal. And that’s all there is to the tremolo effect.

#### Software Implementation

The main loop of the program continiously acquires samples from the ADC0 input at a fixed sample-rate with the help of the AdvancedAnalog-library. Whenever the sample buffer is full, the samples get passed to an effect stack-function. The function first applies a DSP distortion algorithm and then passes the sample on to the tremolo function itself.

Tremolo effect implemented in an Arduino sketch

A global variable keeps track where the acquisition currently is located in time, relative to the sample-rate. MY example code only implements a sinusoidal waveform. Since the DSP alorithm operates in discrete-time and not continuous time, a variable holding the amount of radians per sample has been pre-calculated. The input sample is then multiplied by the sine value of the given frequency at the given sampling time. The resulting value is further multiplied by the fractional depth value. The result is then added to a fration of the original signal with the inverse amount of depth.

And this is what the end result sounds like (99 % modulation depth, LFO frequency = 5 Hz):

#### Conclusions

The tremolo effect is extremely simple to implement in software. As expected, the Arduino GIGA R1 is perfectly up to the task and even more than capable of stacking multiple effects for well-sounding results.

#### Arduino Sketch

The following sketch was tested on an Arduino GIGA R1 with various sample rates. Please note that the ADC is only able to capture positive voltages and may even be destroyed when applying a negative signal. Therefore, the input audio signal has to be shifted up before connecting it to the ADC0 input pin. A simple 10K / 10K voltage divider at the ADC input between the 3.3 V supply rail and ground will suffice for basic experiments.

/*
 * Arduino Giga R1 WiFi - DSP Tremolo Effect Example
 *
 * Copyright (C) 2022 Westerhold, S. (AI5GW) 
 * Web (EN): https://baltic-lab.com
 * ORCID: https://orcid.org/0000-0001-7965-3140
 *
 * Tested sample-rates: 192000, 96000, 48000, 44100
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 */

#include <Arduino_AdvancedAnalog.h>

// Instantiate ADC 0 (Pin A0), DAC 0 (Pin A12) and DAC 1 (Pin A13)
AdvancedADC Chan1(A0);
AdvancedDAC Out1(A12);
AdvancedDAC Out2(A13);

// Define ADC Midpoint
#define ADC_MIDPOINT 2038
#define SAMPLE_RATE 96000
static float RADIAN_PER_SAMPLE = (float)(2.0 * PI) / SAMPLE_RATE;

long SAMP_POS = 0;

void setup() {

  Serial.begin(115200);

  // Set-up ADC0, stop program on failure
  // Resolution, sample rate, number of samples per channel, queue depth.
  if (!Chan1.begin(AN_RESOLUTION_12, SAMPLE_RATE, 128, 128)) {
    Serial.println("ADC fail...");
    while (1)
      ;
  }

  // Set-up DAC0, stop program on failure
  // Resolution, sample rate, number of samples per channel, queue depth.
  if (!Out1.begin(AN_RESOLUTION_12, SAMPLE_RATE, 128, 128)) {
    Serial.println("Failed to initialize DAC 1");
    while (1)
      ;
  }

  // Set-up DAC1, stop program on failure
  // Resolution, sample rate, number of samples per channel, queue depth.
  if (!Out2.begin(AN_RESOLUTION_12, SAMPLE_RATE, 128, 128)) {
    Serial.println("Failed to initialize DAC 1");
    while (1)
      ;
  }
}

void loop() {

  if (Chan1.available()) {
    // Get handle for the input buffer
    SampleBuffer inBuf = Chan1.read();

    // Get handle for the output buffer
    SampleBuffer outBuf1 = Out1.dequeue();
    SampleBuffer outBuf2 = Out2.dequeue();

    for (int i = 0; i < inBuf.size(); i++) {
      // Store buffer value
      int INVal = inBuf[i];

      // Write copy to DAC0 output buffer
      outBuf1.data()[i] = INVal;

      // Send value to the effect stack and write result to DAC1 buffer
      outBuf2.data()[i] = EffectStack(INVal);
    }

    // Write contents of the output buffer to the DACs
    Out1.write(outBuf1);
    Out2.write(outBuf2);

    // Release the input buffer
    inBuf.release();
  }
}

int EffectStack(int INPUT) {
  float IN;
  int OUT = 0;

  if (SAMP_POS >= SAMPLE_RATE) { SAMP_POS = 0; }

  // Normalize to Zero around ADC midpoint
  IN = (float)INPUT - ADC_MIDPOINT;

  //Apply Effects
  OUT = DiodeClippingStep(IN, 300);

  // Tremolo
  OUT = Tremolo(OUT, 99, 5);

  // Denormalize
  OUT = OUT + ADC_MIDPOINT;

  // Update Sampling Position
  SAMP_POS++;

  // return result
  return round(OUT);
}

int Tremolo(float INPUT, float DEPTH, float FREQ) {
  float OUT = 0;
  DEPTH = (float)(DEPTH / 2.0) / 100.0;

  OUT = (float)INPUT * sin(RADIAN_PER_SAMPLE * (float)SAMP_POS * FREQ) * DEPTH;
  OUT = OUT + (INPUT * (1.0 - DEPTH));

  return round(OUT);
}

int DiodeClippingStep(int INPUT, int THRESHOLD) {
  float IN, OUT;
  int BUF = INPUT;  // Store for sign revovery

  // Normalize input to threshold level = 1.0
  IN = (float)abs(INPUT) / THRESHOLD;

  if (IN <= (1.0 / 3.0)) {
    OUT = 2 * IN;
  } else if (IN <= (2.0 / 3.0)) {
    OUT = (-3.0 * (float)pow(IN, 2)) + (4.0 * IN) - (1.0 / 3.0);
  } else {
    OUT = 1;
  }

  // Undo Normalization
  OUT = OUT * THRESHOLD;
  // recover sign
  if (BUF <= 0) { OUT = -1.0 * OUT; }

  return round(OUT);
}

[1] Westerhold, S. (2023), "Arduino GIGA R1 WiFi | Audio Loopback Example". Baltic Lab High Frequency Projects Blog. ISSN (Online): 2751-8140.: https://baltic-lab.com/

[2] Westerhold, S. (2023), "DSP Diode Clipping Algorithm for Overdrive and Distortion Effects". Baltic Lab High Frequency Projects Blog. ISSN (Online): 2751-8140.: https://baltic-lab.com/

## DSP Diode Clipping Algorithm for Overdrive and Distortion Effects

This article shows how to approximate the behaviour of a regular diode in a mathematical equation and how to subsequently implement the behaviour in software. The DSP algorithm can be modified to implement different topologies, such as single diode clipping, dual diode symmetrical soft clipping or asymmetrical clipping.

The easiest way to implement distortion through a DSP algorithm would be hard clipping. With this method any value above a certain threshold will be fixed to that threshold level. While this is absolutely possible, hard clipping sounds very harsh. This might be desirable for some metal fans but it doesn’t mimic the behaviour of tube or diode distortion circuits in which the clipping process is a lot more gradual.

Update (08/21/2023): A YouTube video titled “Overdrive and Distortion Effect Digital Signal Processing (DSP) Algorithms on the Arduino GIGA R1” is now available.

#### Modelling Clipging Mathematically

The Shockley ideal diode equation is a good starting point to model the behaviour of semiconductor diodes. It descibes the current through a diode as a function of the applied voltage. In order to develop a suitable DSP algorithm, however, an equation describing the output voltage as a function of input voltage is needed. There are many different equations approximating diode clipping circulating around the web and in professional literature [1], albeit not always without errors.

Single diode clipping step-function with output voltage as a function of input voltage shown (blue)

A simple approximation for diode clipping through a step function is shown in equation 1 (positive clipping) and 2 (negative clipping). The equation consists out of a linear part, a quadratic part and a hard-clipping part. Which one of the terms is shaping the output signal is determined by the input voltage.

(1)

(2)

Both equations were combined and plotted in Python to simulate dual diode clipping as it is often found in guitar distortion pedals:

Dual, symmetrical diode clipping step-function with output voltage as a function of input voltage shown (blue)

Admittedly the easiest model that can be implemented in software is using only the hyperbolic tangent function (Eq. 3). Use of this function results in an even softer clipping at the top-end of the input voltage range.

(3)

Output voltages for the different clipping equations as a function of input voltage. Step-function (Blue), exponential function (orange) and hyperbolic tangens (green)

The Schockley ideal diode equation is a natural exponential function. Therefore, it would make sense that a mathematical approximation would also be an exponential function with Euler’s number being raised to some variable. There is one particular function found in [1] that circulates around the web. The original equation contains a significant sign-error. Only the corrected version of the equation (Eq. 4) is shown here.

(4)

(5)

But even in the corrected form shown above (Eq. 4), the equation is still not suitable for direct implementation into a DSP algorithm just yet. The problem is the way the original author implements the signum function, which returns only the sign (+ or -) of a value. The and terms are supposed to switch the signs of operands depending on whether the input is positive or negative. This works very well until an input value is 0, because the function’s domain is limited to all non-zero values (Eq. 5). We can do better than that and transform the exponential equation from the book (Eq. 4) into another step-function (Eq. 6). Doing this actually also improves the computational resources required.

(6)

The following graph simulates the resulting output voltage waveform for a sinusoidal 2 Vpp signal applied to the input of each of the algorithms shown here.

Time-domain simulation of output voltages for the different clipping equations as a function of time with a 2 Vpp sinusoidal signal applied. Step-function (Blue), exponential function (orange) and hyperbolic tangens (green)

#### Test and Results

With the math out of the way, the rest is fairly easy. For this experiment, the new Arduino GIGA R1 WiFi [2] has been used as it has two inbuilt DACs that come in handy for trying out the developed algorithms. The framework used here is derived from my Arduino GIGA R1 WiFi | Audio Loopback Example. The entire sketch can be found in the section at the end of the article.

For my first experiment, I implemented the step-functions (Eq. 1 and 2) for a dual diode, symetrical type of distortion. The results are quite satisfying.

Output (yellow) and input (purple) voltages of the step-function clipping algorithm implementet with an Arduino

Next, I made use of the two DACs of the Arduino GIGA R1 WiFi by applying different overdrive / distortion algorithms to the output of each DAC. The first channel utilizes the step-function (Eq. 1 and 2), the second the re-packaged natural exponent step function (Eq. 6). The results for various input voltage levels are shown in the following images.

Input voltage (yellow), dual-diode step-function output from DAC0 (purple) and exponential function output from DAC1 (green) just before clipping.

Input voltage (yellow), dual-diode step-function output from DAC0 (purple) and exponential function output from DAC1 (green) with clipping activity becoming visible.

Input voltage (yellow), dual-diode step-function output from DAC0 (purple) and exponential function output from DAC1 (green) with strongly visible clipping.

#### Caution: Aliasing

Due to the nature of distortion, higher order harmonics of the input signal’s frequency will be present in the signal after applying distortion algorithms. All hormonics that exceed the Nyquist-limit (half the sample-rate) will inadvertently cause aliasing. Therefore, in a practical high-quality digital signal processing application, the sampled input signal would be upsampled first, the algorithm would be applied, followed by an anti-aliasing filter before downsampling back to the original sampling rate (or any other desired rate for the DAC). For this very simple example I omitted this step. Given the relatively high sample rate of 96 kS/s, the Nyquist limit is 48 kHz and thus relatively far away from the high-spectral density part of the usual guitar harmonics. However, this issue should be something to be aware of when implementing the alogrithm shown here in own designs.

#### Conclusions

It is absolutely possible to model different diode clipping behaviours and easily package the resulting equations into a DSP algorithm. Given the relatively low computational power needed, even a lower-end Arduino is perfectly capable of performing this task, allowing for software adjustable parameterization of the exact overdrive and distortion characteristics.

#### Arduino Sketch

/*
 * Arduino Giga R1 WiFi - DSP Diode Clipping Example
 *
 * Copyright (C) 2023 Westerhold, S. (AI5GW) 
* Web (EN): https://baltic-lab.com
 * ORCID: https://orcid.org/0000-0001-7965-3140
 *
 * Tested sample-rates: 192000, 96000, 48000, 44100
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 */

#include <Arduino_AdvancedAnalog.h>

// Instantiate ADC 0 (Pin A0), DAC 0 (Pin A12) and DAC 1 (Pin A13)
AdvancedADC Chan1(A0);
AdvancedDAC Out1(A12);
AdvancedDAC Out2(A13);

// Define ADC Midpoint
#define ADC_MIDPOINT 2047

void setup() {

  // Set-up ADC0, stop program on failure
  // Resolution, sample rate, number of samples per channel, queue depth.
  if (!Chan1.begin(AN_RESOLUTION_12, 96000, 32, 32)) {
    Serial.println("ADC fail...");
    while (1)
      ;
  }

  // Set-up DAC0, stop program on failure
  // Resolution, sample rate, number of samples per channel, queue depth.
  if (!Out1.begin(AN_RESOLUTION_12, 96000, 32, 32)) {
    Serial.println("Failed to initialize DAC 1");
    while (1)
      ;
  }

  // Set-up DAC1, stop program on failure
  // Resolution, sample rate, number of samples per channel, queue depth.
  if (!Out2.begin(AN_RESOLUTION_12, 96000, 32, 32)) {
    Serial.println("Failed to initialize DAC 1");
    while (1)
      ;
  }
}

void loop() {

  if (Chan1.available()) {
    // Get handle for the input buffer
    SampleBuffer inBuf = Chan1.read();

    // Get handle for the output buffer
    SampleBuffer outBuf1 = Out1.dequeue();
    SampleBuffer outBuf2 = Out2.dequeue();

    for (int i = 0; i < inBuf.size(); i++) {
      // Dual diode, symmetrical clipping step-function on DAC0
      outBuf1.data()[i] = DiodeClippingStep(inBuf[i] - ADC_MIDPOINT, 800) + ADC_MIDPOINT;

      // Dual diode, symmetrical clipping exponential-function on DAC1
      outBuf2.data()[i] = DiodeClippingExponential(inBuf[i] - ADC_MIDPOINT, 800) + ADC_MIDPOINT;
    }

    // Write contents of the output buffer to the DACs
    Out1.write(outBuf1);
    Out2.write(outBuf2);

    // Release the input buffer
    inBuf.release();
  }
}

int DiodeClippingStep(int INPUT, int THRESHOLD) {
  float IN, OUT;
  int BUF = INPUT; // Store for sign revovery

  // Normalize input to threshold level = 1.0
  IN = (float)abs(INPUT) / THRESHOLD;

  if (IN <= (1.0 / 3.0)) {
    OUT = 2 * IN;
  } else if (IN <= (2.0 / 3.0)) {
    OUT = (-3.0 * (float)pow(IN, 2)) + (4.0 * IN) - (1.0 / 3.0);
  } else {
    OUT = 1;
  }

  // Undo Normalization
  OUT = OUT * THRESHOLD;
  // recover sign
  if (BUF <= 0) { OUT = -1.0 * OUT; }

  return round(OUT);
}

int DiodeClippingExponential(int INPUT, int THRESHOLD) {
  float IN, OUT;

  // Normalize input to threshold level = 1.0
  IN = (float)INPUT / THRESHOLD;

  if (IN < 0) {
    OUT = -1.0 + exp(IN);
  } else if (IN > 0) {
    OUT = 1.0 - exp(-IN);
  } else {
    OUT = 0;
  }

  // Undo Normalization
  OUT = OUT * THRESHOLD;
  return round(OUT);
}