DIY guitar distortion effect pedal with ESP8266: construction, use and adjustment

Introduction

Welcome to the world of sound customization with the creation of a distortion effect pedal for electric guitar! If you are an electric guitar enthusiast and want to explore new sound horizons, you are in the right place. In this article, we’ll dive into building a DIY guitar distortion, harnessing the power of the ESP8266. This project will allow you to shape your sound, creating that iconic distortion effect that characterizes many rock performances. We’ll delve into the construction process, explore how to use it and discover how to adjust the distortion to get the perfect sound. If you’re ready to give a unique touch to your musical style, let’s continue together!

This distortion also has an adjustment of the frequencies of the distorted signal. For this function we will use the low pass filter described in the article Sound engineering: build your own low pass op amp filter with adjustable cutoff frequency with digital potentiometer which I strongly recommend you read. As usual, to write the firmware we will use the excellent PlatformIO IDE.

In this case the low pass filter will be adjustable not only in cutoff frequency but also in gain, again with REST APIs as in the case of the cited article.

A brief history of electric guitar distortion: a journey into rebellious sound

Electric guitar distortion has always been an intrinsic part of the evolution of rock music. Going back to the 1940s and 1950s, players began looking for ways to get a more aggressive and powerful sound from their guitars. With the advent of technology, the first tube distortions were born, such as the Gibson Maestro FZ-1, which characterized the rebellious sounds of rock ‘n’ roll of those years.

In the 1960s, the distorted sound became a signature of psychedelic rock, with artists like Jimi Hendrix experimenting with controlled feedback and heavier tones. The introduction of fuzz pedals, such as the legendary Fuzz Face, helped define the psychedelic sound of the era.

In the ’70s and ’80s, the evolution of transistor amplifiers led to a new generation of more accessible and portable distortion devices. Iconic pedals like the Pro Co Rat and the Ibanez Tube Screamer became essential for lovers of hard rock and heavy metal.

With the advent of digital technology in the 1990s, multi-effect distortion devices were developed that offered a wide range of sounds, allowing guitarists to customize their distortion. Today, thanks to DIY projects like our ESP8266 distortion, the search for the perfect distorted sound continues, keeping alive the tradition of exploring new sonic horizons in the history of the electric guitar.

This project involves the use of operational amplifiers, digital potentiometers and filters, so we will start with a brief description of them.

What is an operational amplifier?

The operational amplifier (also called opamp) is a solid-state analog electronic component capable, as its name suggests, of amplifying an electrical signal. It is defined as operational because it is capable of carrying out some mathematical operations in a totally analogue manner on electrical signals. The generally possible operations are addition, subtraction, differentiation and integration, logarithm etc etc.
It is also a fundamental part of active low-pass, high-pass, band-pass, notch etc etc filters.

It is equipped with two inputs, one inverting (indicated with the – sign) and one non-inverting (indicated with the + sign), and one output. It is generally powered with a dual voltage (usually indicated with the signs V+ and V).

In case the signal enters through the non-inverting input, the output signal is in the same phase as the input signal. If, however, the signal enters the inverting input, the output signal will be in phase opposition (i.e. 180 degrees out of phase) compared to the input signal.

An inverting amplifier is, therefore, an operational amplifier whose non-inverting input is connected to ground while the inverting one is connected to the VINPUT input source.

Electrical diagram of an operational amplifier in inverting configuration
Electrical diagram of an operational amplifier in inverting configuration

This circuit takes the signal at the INPUT terminal and presents it in the amplified OUTPUT (i.e. multiplied by a value called gain which we will indicate with the symbol G).

So, in formula, we have that:

VOUTPUT = G * VINPUT

We said that this amplifier inverts the phase of the output signal with respect to the phase of the input signal (the two signals are 180° out of phase). This means that the value of G is negative. For example, if the input signal were amplified 10 times, we would have G = -10 [V/V] and therefore:

VOUTPUT = -10 * VINPUT

If, for example, the signal VINPUT = 1V we would have VOUTPUT = -10 V while with VINPUT = -1 V we would have VOUTPUT = 10 V.

In practice the output signal is given by the input signal enlarged by 10 times and reversed.

PLEASE NOTE: the gain, being the ratio between two voltages

G = VOUTPUT / VINPUT

it is expressed by a pure number. However, we can express it, alternatively, with the notation [V/V] (which is always a pure number).

How do you set the gain of the inverting amplifier?

The gain is easily determined starting from the values ​​of the two resistors R1 and R2. In particular we have that the gain, in absolute value, is given by:

|G| = R2 / R1

So, if we had R1 = 1 kΩ and R2 = 10 KΩ, we would have |G| = 10.

Considering the 180° phase shift we have that

G = -R2 / R1

With the values ​​of the previous example we have that G = -10 [V/V].

In other words:

VOUTPUT = -(R2 / R1) * VINPUT

meaning what:

VOUTPUT = -10 * VINPUT

The gain can also be expressed in dB (decibel) according to the relationship:

GdB = 20 * log(|G|) = 20 * log(|-R2 / R1|) = 20 * log (R2 / R1)

For example, if R1 and R2 were 1 kΩ and 10 kΩ respectively, we would have G = -10 [V/V] and GdB = 20 dB. If R1 and R2 were 2 kΩ and 15 kΩ respectively, we would have G = -7.5 [V/V] and GdB = 17.5 dB.

How does a classic potentiometer work?

A potentiometer is a 3-terminal device used as a variable resistor. It is equipped with a rotary contact controlled by the knob and is used as a voltage divider.

A classic potentiometer
A classic potentiometer

Its electrical symbol is that of a classic resistor but with the intermediate contact controlled by the knob:

The electrical symbol of the potentiometer
The electrical symbol of the potentiometer

How does a voltage divider work?

A voltage divider is a simple circuit in which the output voltage is a fraction of the input voltage. Two fixed resistors or a potentiometer can be used.

Let’s see the scheme and formula:

A simple example of a voltage divider made with a potentiometer
A simple example of a voltage divider made with a potentiometer

The potentiometer is indicated with the letter P while R1 and R2 are the two sections of the total resistance which vary depending on the position of the cursor (which is controlled by the knob).

As you can see, the output voltage Vo is a fraction of the input voltage Vi. Vo varies between two extremes:

  • Vo = 0 when R2 = 0 (the knob is turned all the way towards the terminal connected to the negative pole of the battery)
  • Vo = Vi when R1 = 0 (the knob is turned all the way towards the terminal connected to the positive pole of the battery)

Digital potentiometers

These examples are based on mechanical potentiometers but there are also types of potentiometers on the market that are completely electronic and adjustable via an appropriate input signal. They are the so-called digital potentiometers.

Today we want to test one by controlling it via ESP8266, in particular the MCP41010 model, which is a single potentiometer. Its maximum value is 10 kΩ while the minimum value is around 100Ω.

An MCP41010 digital potentiometer
An MCP41010 digital potentiometer

Let’s now see the pinout of this component:

Pinout of an MCP41010
Pinout of an MCP41010

The name of the device depends on the maximum resistance value of the single digital potentiometer and the number of potentiometers present inside it. For example:

  • MCP41010: single potentiometer, 10 kΩ
  • MCP41050: single potentiometer, 50 kΩ
  • MCP41100: single potentiometer, 100 kΩ
  • MCP42010: two independent potentiometers, 10 kΩ
  • MCP42050: two independent potentiometers, 50 kΩ
  • MCP42100: two independent potentiometers, 100 kΩ

We will control our digital potentiometer through its SPI port appropriately driven by the ESP8266.

Looking at the datasheet of the digital potentiometer you can see how, to command this chip, it is necessary to first send it a “command byte” (to tell the chip what it must do) and then a “data byte” (to tell the chip what resistance value set, from 0 to 255).

For example, to set a resistance of 10 kΩ, we must send a data byte equal to 11111111 (corresponding to 255), to set a resistance of 5 kΩ we must send a data byte equal to 10000000 (corresponding to 128) and so on.

For the command to be executed, first the CS terminal must be set to 0 (LOW value) then the command byte must be sent followed by the data byte (for a total of 16 bits). Finally, you need to bring the CS terminal back to value 1 (HIGH value). Only then will the command be executed (from the datasheet:”Executing any command is accomplished by setting CS low and then clocking-in a command byte followed by a data byte into the 16-bit shift register. The command is executed when CS is raised.”)

We have already addressed the use of one of these digital potentiometers in the articles How to control a digital potentiometer using Arduino UNO and How to control an inverting operational amplifier using Arduino UNO and a digital potentiometer which I recommend you read.

The diode

The diode is a two-terminal electronic device that allows current to flow in one direction only, while blocking it in the opposite direction. Its main feature is the p-n junction, a region of semiconductor material in which atoms are doped with impurities to create an excess or deficiency of charge carriers. When a positive voltage is applied to the anode (p) terminal, and negative to the cathode (n) terminal, the p-n junction is forward biased, allowing the free flow of charge carriers and thus creating a low resistance. Conversely, when the bias is reversed, the junction behaves as an electrical barrier, preventing the flow of current. This unidirectional conduction characteristic makes the diode essential in many applications, including AC rectifiers, protection circuits, and digital logic.

The diode, therefore, is a fundamental electronic component, playing a crucial role in numerous electronic circuits, including those dedicated to the art of sound amplification and distortion, as in the case of guitar distortions.

The property of conducting current in only one direction makes the diode an ideal element for controlling electrical flow and plays a key role in creating desired sound effects.

In distortion circuits, diodes are often used to introduce the phenomenon of “clipping”, i.e. cutting the amplitude of the signal. This happens when the signal exceeds a certain threshold, creating a distorted, more or less squared waveform, which gives the sound a unique quality rich in harmonics.

In essence, the diode is a kind of modern electronic valve, which allows or blocks the flow of current based on the needs of the circuit. Its versatility and precise control of electrical flow make it an indispensable tool in electronics and musical device design, contributing to artistic expression through sound modulation.

The diode has a band on its container of a different color from that of the container (for example black or grey) in correspondence with the cathode. For example, this model of 1N4148 has a black band to distinguish the cathode:

A 1N4148 diode
A 1N4148 diode

The transistor

Transistor is a three-terminal device that plays a crucial role in amplifying and controlling the flow of current in an electronic circuit. Based on semiconductor materials, the transistor is composed of two p-n junctions and operates through current amplification processes. Its terminals are called Emitter, Base and Collector. The basic mode of operation involves applying a small, time-varying voltage to the base-emitter junction, resulting in a relatively large current flow between the collector and emitter, which follows changes in the input signal. In the NPN type configuration, current flows from the base to the emitter, thus controlling the larger current flowing between collector and emitter. This property allows the transistor to act as a signal amplifier. Furthermore, in digital electronics it is used as a controlled switch. Its versatility makes transistors critical for applications in analog and digital electronics, including integrated circuits, audio amplifiers, and digital logic in modern electronic devices.

The transistor is a milestone in the evolution of electronics, playing key roles in the creation of amplifiers and distortions, essential tools in the musical field.

Its ability to amplify an electrical signal makes it essential in audio applications. Among the various configurations, the common emitter one is often used in distortion circuits.

In the context of guitar distortion, the transistor, particularly in the common-emitter configuration, acts as a high-gain amplifier. When the input signal exceeds a certain threshold, the transistor reaches its saturation, generating a “clipping” effect that contributes to the distortion of the sound. This saturation causes a distorted waveform, rich in harmonics, giving the guitar that characteristic and powerful sound.

In summary, the transistor is the architect of amplification and distortion, playing a vital role in transforming the raw audio signal into a powerful sonic expression. Its versatility makes it an indispensable tool for electric music enthusiasts looking to shape their own unique and unmistakable sound.

Transistors have various types of “case” (container) that depend on their use (signal transistors, power transistors, high voltage transistors and so on). In general there is no way to visually identify the three terminals Emitter, Base and Collector so we rely on the datasheets that report this information (in addition to all the electrical, thermal and mechanical characteristics of the device).

Let’s start with a bit of theory

Linear Time Invariant Systems (SLTI)

The study of linear time invariant systems (SLTI) is fundamental in the field of electronic and control engineering, as it provides a systems approach to the analysis and design of dynamic systems. A system is considered LTI if it satisfies two fundamental properties: linearity and invariance over time. Linearity implies that the system respects the principle of superposition of effects, while invariance over time indicates that the response of the system does not change over time, as long as the input remains unchanged. Two common representations of such systems are the impulse response and the transfer function.

Impulse Response

The impulse response is a fundamental characteristic of an LTI dynamical system and is defined as the response of the system to a unit impulse, also known as the Dirac delta (denoted δ(t)). Therefore, if I place a Dirac impulse at the input of an SLTI system, I obtain the response of the system to that impulse, which I will indicate with the function h(t):

blank

If h(t) is the impulse response of an LTI system, then the response y(t) of the system to any input x(t) can be obtained by computing the convolution between the input and the impulse response:

blank

So, returning to the block diagrams, we will have that:

blank

where the output y(t) is, as mentioned before, the convolution between the input x(t) and the impulse response h(t) (calculated with the integral above).

The convolution operation is indicated with the symbol * so we can write that y(t) = x(t) * h(t).

In essence, the impulse response h(t) of an SLTI is the function that characterizes the behavior of the system in the time domain.

Transfer Function

The transfer function, often denoted H(s) in the Laplace domain, is an alternative way of describing an LTI system. It represents the ratio between the Laplace transform of the response Y(s) and that of the input X(s). In practice it is the Laplace transform of the impulse response h(t):

blank

Graphically we will represent our system like this:

blank

The transfer function is useful because it simplifies the analysis of frequency systems. Just to give an example, convolution (not a very simple operation) transforms into a simple multiplication. So, in Laplace’s world we will have that the previous relation transforms into

Y(s) = X(s) H(s).

The poles (roots of the denominator) and zeros (roots of the numerator) of the transfer function provide crucial information about the stability and frequency response of the system.

The function H(s) is a function of a complex variable. In fact, the variable s, called Laplace variable, is a complex variable (i.e. with a real part and an imaginary part) and is represented as follows: s = α + jω (with real α and ω). For α = 0 we have s = jω. In this case the transfer function therefore depends only on the pulsation ω, i.e. we have H(ω) where the pulsation is linked to the frequency by the relation ω = 2πf. The magnitude and phase of H(ω) will be functions of ω. It therefore characterizes the behavior of the system in the frequency domain.

Frequency response of an SLTI

A key aspect of the study of LTI systems is the analysis of their frequency response. This analysis involves evaluating how the system responds to sinusoidal signals at different frequencies. The magnitude and phase of the transfer function are usually represented graphically via the Bode plot, providing a clear view of the attenuation and phase characteristics of the system as a function of frequency.

Brief theory of the low pass filter

A first-order low-pass filter is a simple electronic circuit or, more generally, a dynamic system characterized by a cutoff frequency that represents the point at which the signal begins to be attenuated. The transfer function H(s) of a first-order low-pass filter in the Laplace domain is expressed as:

blank

where:

  • K represents the static gain of the filter, i.e. the value of the gain when the frequency tends to zero.
  • s is the complex variable of the Laplace transform
  • T is the time constant of the system

The time constant T is inversely proportional to the cutoff frequency fc​, and the relationship between them is given by:

blank

The first order low pass filter acts like an RC circuit, where R is the resistance and C is the capacitance. The transfer function can also be written in the time domain as the impulsive response of the system h(t) (i.e. by calculating the anti-Laplace transform of the transfer function H(s)):

blank

where:

  • t is the time
  • e is the base of the natural logarithm

The frequency response of the first-order low-pass filter means that it gradually attenuates frequencies above the cutoff frequency. This behavior can be visualized in the frequency domain through the Bode plot, where the magnitude of the transfer function decreases with a slope of -20 dB/decade, while the phase varies from 0° to -90°.

Bode plot for K = 1 and different values ​​of the cut-off frequency

Filter Bode plot (module only)
Filter Bode plot (module only)

In general, the first-order low-pass filter finds wide use in several applications, including audio circuits, control systems, power electronics and more. Its simplicity makes it a popular choice for filtering signals with relatively low attenuation needs at higher frequencies.

The low pass filter made with an operational amplifier

As already seen, a low-pass filter is an electronic circuit designed to allow low-frequency signals to pass through while attenuating higher-frequency ones. This type of filter is fundamental in various audio and electronic applications, as it allows you to isolate and focus on the low frequency components of a signal. In the context of sound engineering, low pass filters are often used to eliminate or reduce unwanted frequencies and to focus on reproducing the deepest, most resonant tones of a sound. They are also essential in communications systems, where frequency separation is crucial to ensure clear, interference-free transmission. Due to their versatility, low-pass filters find use in a wide range of devices, from the design of audio speakers and amplifiers to the design of electronic signal processing circuits.

Obviously, in addition to low pass filters there are also high pass filters, band pass filters and band eliminate filters.

The cutoff frequency in a low pass filter is the critical point at which the filter begins to attenuate the frequencies of the signal. This is the frequency at which the signal begins to be reduced, and below this frequency the filter allows almost complete passage of low frequency components. In an ideal low-pass filter, above the cutoff frequency, the signal is attenuated gradually, eventually reaching a point where higher frequencies are dramatically reduced.

The cutoff frequency is a key parameter in the design of low pass filters, as it determines the range of frequencies that will be retained or attenuated. By adjusting the cutoff frequency, you can tailor the filter to the specific needs of your application, allowing precise control over the reproduction of frequencies in your audio or electronic signal. The correct setting of the cutoff frequency is essential to obtain the desired result in signal filtering.

A first-order low-pass filter can be made by using an operational amplifier in inverting configuration together with a reactive component, such as a capacitor. This type of filter is also known as a single-pole RC filter, since it involves only one reactive component. The typical configuration of a first-order low-pass filter with an operational amplifier is called a low-pass RC filter.

In RC low pass filter, the capacitor is connected between the inverting input of the op amp and its output. The resistor is connected to the inverting input and output terminal of the amplifier (in parallel with the capacitor). The cutoff frequency of the filter is determined by the relationship between the resistance (R) and the capacitance (C) according to the formula:

fc = 1/(2πRC)

Where:

  • fc is the cutoff frequency,
  • R is the resistance,
  • C is the capacity.

Below we see an example of a first order low pass filter created with an operational amplifier in inverting configuration:

Basic diagram of a first order low pass filter made with an operational amplifier in inverting configuration
Basic diagram of a first order low pass filter made with an operational amplifier in inverting configuration

In this case the two resistors have, in general, different values ​​(unlike the case presented in the article Sound engineering: build your own low pass op amp filter with adjustable cutoff frequency with digital potentiometer where R1 = R2). This allows us to adjust both the cutoff frequency and the gain of the filter. In fact, the transfer function H(s) of this filter is given by:

blank

which, in the most general form we can express as:

blank

Therefore, the ratio (changed in sign) between R2 and R1 represents the gain while the product between R2 and C represents the time constant, as indicated in the following image:

blank

I remind you that the (minus) sign indicates that we are inverting the phase (the operational amplifier is in inverting configuration). The time constant C R2 is inversely proportional to the pulsation (and therefore to the frequency).

Therefore, first the cutoff frequency is established by giving an appropriate value to R2 (assuming that the value for C has already been established, which however remains constant), after which an appropriate value of R1 is established to obtain the desired gain.

In this case, the voltage gain, minus the sign, is equal to R2/R1 [V/V] for frequencies quite below the cutoff frequency fc and, as the frequency increases, it gradually decreases. This type of filter offers 20 dB/decade attenuation for input frequencies above the cutoff frequency.

The first-order low-pass filter configuration with an operational amplifier offers a simple and effective solution to achieve smooth attenuation of high frequencies, and is widely used in audio and signal processing applications.

Description of the distortion effect pedal

This guitar distortion essentially consists of three parts:

  • OPTIONAL: a preamplifier pedal to be placed before the stage at the second point in case the amplitude of the signal coming from the guitar pick-ups is particularly low
  • a first input stage which receives the signal from the guitar pick-ups, amplifies it and squares it
  • an impedance matching stage
  • a low pass filter with operational amplifier in inverting configuration that we have already encountered in the article Sound engineering: build your own low pass op amp filter with adjustable cutoff frequency with digital potentiometer with the difference that in this filter, in addition to modifying the cutoff frequency, it is possible to vary the gain in order to give the guitarist more freedom of adjustment

The first stage

The first stage is the actual distortion and we can observe it in the following diagram:

Electrical diagram of the first stage
Electrical diagram of the first stage

The configuration of transistor amplification followed by diodes represents a strategy widely used in the universe of rock guitars to obtain that distinctive and powerful “crunchy” sound. This configuration uses a transistor, often of the common-emitter 2N2222 type, as the initial amplification stage. In this phase, the signal coming from the electric guitar undergoes significant amplification, creating an initial saturation of the signal.

The scheme was created using LTSpice to do some simulation. In practice we have a 2N2222 transistor that receives the signal from the C4 capacitor and presents it, strongly amplified, on its collector. The transistor is in common emitter configuration, so it inverts the phase of the signal. Feedback via the 2.2MΩ resistor R9 results in a strong gain in this stage. This is because it must amplify the weak signals produced by the pick-ups and bring them at least to a level that can then be treated by the subsequent components (the diodes). Diodes do the actual distortion. These diodes act as clipping elements, limiting the amplitude of the signal and thus generating the desired distortion. As you will notice, they are connected in antiparallel. Typically a diode passes the current only in one direction (when the potential on the anode is greater than that on the cathode) while blocking it with opposite polarization. Therefore, when there is a positive half-wave on the OUT terminal, the diode D1 enters into conduction while the D2 is inhibited (does not conduct). When there is a negative half-wave the roles are reversed: D1 is inhibited and D2 conducts. These two 1N4148 type diodes are silicon, which means that when they conduct they have a voltage drop of approximately 0.6V/0.7V. So the two positive and negative half-waves are “sheared” at that level. A sine wave, for example, is reduced to a square wave with an amplitude of approximately 0.6V/0.7V (for each half-wave). If you want to accentuate the distortion, you can put different diodes, for example a silicon diode like 1N4148 in one branch and a germanium diode (like 1N34a) in the other branch (keeping it in antiparallel). This is because germanium diodes have a voltage drop of approximately 0.2V/0.3V. The result is an asymmetric square wave (with one half-wave of 0.6V/0.7V and the other half-wave of 0.2V/0.3V), increasing the distortion even further.

If the signal coming from the pick-ups is too weak, a small preamplifier can be placed in front of the input.

The second stage

An impedance matching stage follows. As you will notice, it consists of an operational amplifier in inverting configuration but with unity gain (the two resistors are the same). This means that the amplitude of the signal remains unchanged (but not the phase which is inverted). This stage is used to match the impedance between the distortion output and the filter input. This is because, depending on how it is regulated, the input impedance of the filter can also be very low (a few tens of Ω) and this fact, if there were no impedance adapter, would cancel out the weak signal coming from the diodes. Instead, with the current scheme, the diodes (and therefore the distortion output) always “see” an impedance constantly equal to 82kΩ.

In the figure we see a representation:

Impedance adapter wiring diagram
Impedance adapter wiring diagram

The third stage

It consists of a low pass filter with an operational amplifier in inverting configuration which we have already encountered in the article Sound engineering: build your own low pass op amp filter with adjustable cutoff frequency with digital potentiometer.

However, this filter has been slightly modified. In the filter of the cited article, the digital potentiometers are adjusted so that they always have the same value between them. This is because the CS (chip select) terminal of both is connected to the same GPIO of the ESP8266. In this way, by varying them you can adjust the cutoff frequency of the filter but not its gain (which always remains equal to 0dB for frequencies well below the cutoff frequency).

In the current filter the two digital potentiometers have been made independent (i.e. their CS terminals refer to two different GPIOs of the ESP8266) so as to be able to adjust not only the cut-off frequency but also the gain, giving the guitarist much more possibilities to adjust the distortion sound type.

In the following figure we can see the complete electrical diagram:

Complete electrical diagram of the distortion effect pedal
Complete electrical diagram of the distortion effect pedal

Below is a simulation performed using LTSpice for various values ​​of the cutoff frequency and filter gain:

Simulation performed for various values ​​of cutoff frequency and filter gain
Simulation performed for various values ​​of cutoff frequency and filter gain

Adding a remote control

How do we control the various parameters of the filter connected to the distortion? As already anticipated in the introduction, we can exploit the ESP8266’s ability to connect to WiFi and be controlled via REST APIs. This allows us to insert our distortion into a larger and more complex system where modules of this kind are controlled and set in real time by special programs in order to generate different effects. We have already discussed REST APIs in the article How to build a REST API server with ESP32 so I recommend you go and take a look at it. Even if we talk about ESP32 there, the conceptual basis does not change.

As already highlighted in the aforementioned article, REST APIs are a way to make different devices communicate with each other or to allow us to interact with each other. In the sketch that we will use in this article we will create a web server with the ESP8266 so that it exposes a set of REST APIs. We can use these REST APIs to interact with the ESP8266 in order to receive data from the filter or send data to the filter. In particular we will send a value between 0 and 255 to set the resistance of one of the two digital potentiometers with a POST to set the filter cutoff frequency and another value, always between 0 and 255 with the same POST to adjust the gain of the filter. If we wanted to obtain the cutoff frequency values, the corresponding resistance value and the code between 0 and 255 assigned to the gain we would use an appropriate GET API.

The GET type API will look like this:

http://IP_ESP8266/getValues

and will send a Json file like this as a response:

[
    {
        "name": "cutoffFrequency",
        "value": 338799.2813,
        "unit": "Hz"
    },
    {
        "name": "cutoffResistance",
        "value": 10,
        "unit": "Ω"
    },
    {
        "name": "gainCode",
        "value": 0,
        "unit": ""
    }
]

where IP_ESP8266 is the IP address assigned to the board by the router, cutoffFrequency represents the value of the calculated cutoff frequency while cutoffResistance represents the resistance value of the corresponding digital potentiometer. The gainCode value is between 0 and 255 and correspondingly adjusts the digital potentiometer that sets the filter gain.

The POST type API looks like this:

http://IP_ESP8266/setCutoffFrequency

and it will send a Json file like this to our ESP8266:

{
        "resistanceFrequencyCode": "1",
        "gainCode": "25"
}

where IP_ESP8266 is the IP address assigned to the board by the router, resistanceFrequencyCode represents the value between 0 and 255 to set the resistance of the digital potentiometer that regulates the frequency while gainCode, between 0 and 255, correspondingly regulates the digital potentiometer that sets the filter gain.

To interact with the ESP8266 via the REST API we will use a program called Postman, whose use we will see later.

Also for this example we will create the project using the IDE PlatformIO.

What components do we need?

The list of components is not particularly long:

  • two breadboards to connect the NodeMCU ESP8266 to other components
  • some DuPont wires (male – male, male – female, female – female)
  • 3 x 47kΩ resistors
  • 2 x 10Ω resistors
  • 2 x 82kΩ resistors
  • a 680Ω resistors
  • a 2.2MΩ resistors
  • 6 x 47nF capacitors
  • 2 x 1N4148 diodes (or, alternatively, a 1N4148 diode and a 1N34a diodea)
  • a 2N2222 transistor
  • a LM833 operational amplifier
  • 2 x MCP41010 digital potentiometers
  • 2 x 9V battery connectors
  • 2 x 9V batteries
  • and, of course, a NodeMCU ESP8266 board!

Project implementation

The electrical diagram

Before creating the actual circuit let’s take a look at the pinout of the board:

NodeMCU ESP8266 pinout
NodeMCU ESP8266 pinout

Let’s now see the Fritzing electrical diagram of the project:

The sketch

Let’s create the PlatformIO project

We have already seen the procedure for creating a PlatformIO project in the article How to create a project for NodeMCU ESP8266 with PlatformIO.

Please do not install the libraries mentioned in the article as we will not be using any libraries.

Now edit the platformio.ini file to add these lines:

monitor_speed = 115200
upload_speed = 921600
lib_deps = 
	bblanchon/ArduinoJson@^6.21.0
	https://github.com/tzapu/WiFiManager.git

so that it looks like this:

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps = 
	bblanchon/ArduinoJson@^6.21.0
	https://github.com/tzapu/WiFiManager.git

Obviously you can download the project from the following link:

Replace the main.cpp file of the project you created with the one present in the zip file.

Now let’s see how the sketch works.

We start with the inclusion of the usual Arduino.h library and the libraries for managing the webserver and WiFi:

#include <Arduino.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>
#include <WiFiManager.h>

The GPIOs controlling the digital potentiometers are then defined:

int CS_signal_frequency = D2;                      // Chip Select signal frequency on pin D2 of ESP8266
int CS_signal_gain = D1;                      // Chip Select signal gain on pin D1 of ESP8266

int CLK_signal = D4;                     // Clock signal on pin D4 of ESP8266
int MOSI_signal = D5;                    // MOSI signal on pin D5 of ESP8266

and the command byte (which we talked about in the paragraph dedicated to digital potentiometers) together with the initialization value:

byte cmd_byte2 = B00010001 ;            // Command byte
int initial_value = 0;                // Setting up the initial value

Two other parameters are then defined:

#define RESISTANCE_STEP 30.5
double CAPACITY = 0.000000047;

The CAPACITY is simply the value of the feedback capacitor (the 47nF one) expressed in Farads (we need it for calculating the cut-off frequency).

The RESISTANCE_STEP parameter is an experimental parameter. In reality, the maximum value measured on the MCP41010 digital potentiometers turned out to be different from 10kΩ (I measured them by placing the control value at its maximum, i.e. 255) but much closer to 7.8KΩ. So the resistance increase step is not given (at least in my case) by 10000Ω/256 i.e. approximately 39Ω but by 7800Ω/256 i.e. approximately 30.5Ω

PLEASE NOTE: it is obvious that if between the cursor of the digital potentiometer and one of the other two terminals the resistance value is maximum, between the cursor and the other terminal the value is zero (or almost zero).

The following variables are then defined which represent the values ​​of the cut-off frequency and the corresponding resistance:

float cutoffFrequency;
float cutoffResistance;

The variables are then defined

unsigned long measureDelay = 500;        // 0.5 seconds        
unsigned long lastTimeRan;

which decide how often the loop function must control the digital potentiometers.

Subsequently, the server listening on port 80 is instantiated, a Json document is defined, a 1024 character buffer, the variable that will contain the code (between 0 and 255) that will set the resistance of the digital potentiometer that will vary the cutoff frequency and the one that will contain the code (between 0 and 255) that will set the resistance of the digital potentiometer that will vary the gain of the filter:

// Web server running on port 80
ESP8266WebServer server(80);

StaticJsonDocument<1024> jsonDocument;

char buffer[1024];

int resistanceFrequencyCode = 0;
int gainCode = 0;

Following is the handlePost function which handles the POST type API (adapted for cutoff frequency and gain codes):

void handlePost() {
  if (server.hasArg("plain") == false) {
    //handle error here
  }
  String body = server.arg("plain");
  deserializeJson(jsonDocument, body);
  
  // Get code for resistance
  resistanceFrequencyCode = jsonDocument["resistanceFrequencyCode"];
  gainCode = jsonDocument["gainCode"];

  // Respond to the client
  server.send(200, "application/json", "{}");
}

and the createJson, addJsonObject, getValues ​​functions which manage the Json document and return, following a GET request, the cutoffFrequency, cutoffResistance and gainCode values::

void createJson(char *name, float value, char *unit) {  
  jsonDocument.clear();
  jsonDocument["name"] = name;
  jsonDocument["value"] = value;
  jsonDocument["unit"] = unit;
  serializeJson(jsonDocument, buffer);  
}

void addJsonObject(char *name, float value, char *unit) {
  JsonObject obj = jsonDocument.createNestedObject();
  obj["name"] = name;
  obj["value"] = value;
  obj["unit"] = unit;  
}

void getValues() {
  Serial.println("Get all values");
  jsonDocument.clear(); // Clear json buffer
  addJsonObject("cutoffFrequency", cutoffFrequency, "Hz");
  addJsonObject("cutoffResistance", cutoffResistance, "Ω");
  addJsonObject("gainCode", gainCode, "");
  serializeJson(jsonDocument, buffer);
  server.send(200, "application/json", buffer);
}

The setupApi function follows which routes the /getValues ​​and /setCutoffFrequency urls to the appropriate functions and initializes the server:

void setupApi() {
  server.on("/getValues", getValues);
  server.on("/setCutoffFrequency", HTTP_POST, handlePost);
 
  // start server
  server.begin();
}

Followed by the spi_transfer and spi_out functions which manage the transmission through the SPI port

void spi_transfer(byte working) {
    for(int i = 1; i <= 8; i++) {                                           // Set up a loop of 8 iterations (8 bits in a byte)
     if (working > 127) { 
       digitalWrite (MOSI_signal,HIGH) ;                                    // If the MSB is a 1 then set MOSI high
     } else { 
       digitalWrite (MOSI_signal, LOW) ; }                                  // If the MSB is a 0 then set MOSI low                                           
    
    digitalWrite (CLK_signal,HIGH) ;                                        // Pulse the CLK_signal high
    working = working << 1 ;                                                // Bit-shift the working byte
    digitalWrite(CLK_signal,LOW) ;                                          // Pulse the CLK_signal low
    }
}

void spi_out(int CS, byte cmd_byte, byte data_byte){                        // we need this function to send command byte and data byte to the chip
    
    digitalWrite (CS, LOW);                                                 // to start the transmission, the chip select must be low
    spi_transfer(cmd_byte); // send the COMMAND BYTE
    delay(2);
    spi_transfer(data_byte); // send the DATA BYTE
    delay(2);
    digitalWrite(CS, HIGH);                                                 // to stop the transmission, the chip select must be high
}

the initialize function that sends the initial value to the SPI port:

void initialize() {                     // send the command byte of value 0 (initial value)
    spi_out(CS_signal, cmd_byte2, initial_value);
}

and the commandPotentiometer function used in the loop to set the various values ​​to the two digital potentiometers to adjust the cutoff frequency and gain of the filter and to calculate the corresponding cutoff frequency actually set on the filter:

void commandPotentiometer(int step) {
    Serial.print("gainCode: ");
    Serial.println(gainCode); 
    spi_out(CS_signal_gain, cmd_byte2, gainCode);   // change gain
    spi_out(CS_signal_frequency, cmd_byte2, step);   // change cutoff frequency
    Serial.print("step: ");
    Serial.println(step); 
    Serial.print("approximate resistance value: ");
    cutoffResistance = 10 + (step * RESISTANCE_STEP);
    Serial.print(cutoffResistance); 
    Serial.println(" Ω");
    Serial.print("approximate cutoff frequency value: ");
    cutoffFrequency = 1/((10 + (step * RESISTANCE_STEP)) * 3.14 * 2 * CAPACITY);
    Serial.print(cutoffFrequency); 
    Serial.println(" Hz");
    Serial.println();
    Serial.println();
}

Subsequently, the setup function sets the various GPIOs as OUTPUT, initializes the SPI port, activates the serial port:

pinMode (CS_signal_frequency, OUTPUT);
pinMode (CS_signal_gain, OUTPUT);
pinMode (CLK_signal, OUTPUT);
pinMode (MOSI_signal, OUTPUT);

initialize();

Serial.begin(115200);                                                     // setting the serial speed
// This delay gives the chance to wait for a Serial Monitor without blocking if none is found
delay(1500); 
Serial.println("ready!");

It also contains the part that deals with the management of the WiFi connection and calls the setupApi() function:

WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
// it is a good practice to make sure your code sets wifi mode how you want it.

//WiFiManager, Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wm;

// reset settings - wipe stored credentials for testing
// these are stored by the esp library
// wm.resetSettings();

// Automatically connect using saved credentials,
// if connection fails, it starts an access point with the specified name ( "AutoConnectAP"),
// if empty will auto generate SSID, if password is blank it will be anonymous AP (wm.autoConnect())
// then goes into a blocking loop awaiting configuration and will return success result

bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
// res = wm.autoConnect("AutoConnectAP"); // anonymous ap
res = wm.autoConnect("AutoConnectAP","password"); // password protected ap

if(!res) {
    Serial.println("Failed to connect");
    ESP.restart();
} 
else {
    //if you get here you have connected to the WiFi    
    Serial.println("Connected...yeey :)");
}

setupApi();

The loop function manages the client calls from the server and, every measureDelay seconds, calls the commandPotentiometer(resistanceFrequencyCode) function which sets the value contained in resistanceFrequencyCode (received from the POST API) and the gainCode value, always received from the POST API:

  server.handleClient();

  if (millis() > lastTimeRan + measureDelay)  {
    commandPotentiometer(resistanceFrequencyCode);
    lastTimeRan = millis();

   }

How to connect the board to the Internet

After uploading the sketch to the board, open the Serial Monitor to see the messages coming from the device.

First the board goes into Access Point mode and will provide us with an IP address that we will use shortly. This operation is used to connect the board to the Internet without having to enter the WiFi network parameters (SSID and password) in the code.

The board provides us with its IP address
The board provides us with its IP address

In this case the IP address is 192.168.4.1.

At this point the ESP8266 is in Access Point mode (with AutoConnectAP SSID) and we need to connect our computer to the AutoConnectAP network. If we go to the networks menu of our computer, we should also see the AutoConnectAP network in the list of wireless networks.

List of available WiFi networks
List of available WiFi networks

Connect your computer to the AutoConnectAP network. Then go to your browser and enter the IP previously provided by the ESP8266 (which in this example is 192.168.4.1)

You will see a screen like this:

The browser screen for choosing the network
The browser screen for choosing the network

Click the ConfigureWiFi button. It will show you the available networks:

List of available networks
List of available networks

Choose your network’s SSID:

Choose your network
Choose your network

Enter your network password and click the save button:

Enter your password
Enter your password

The board's response
The board’s response

The ESP8266 module keeps the access parameters stored even if you turn it off, it will remember them when restarting and will automatically reconnect without having to repeat this procedure. Only if you reset it by uncommenting this line

// wm.resetSettings();

will lose the connection parameters.

Please note: the device can only memorize one network. If you later connect it to another network, it will forget the settings of the previous network.

Let’s test the project with REST APIs

Once the ESP8266 has been connected to the WiFi network it will provide us with its IP address via the PlatformIO Serial Monitor, as visible in the following figure:

We obtain the IP of the board
We obtain the IP of the board

In this case the IP assigned by the router to the board is 192.168.1.9. This IP will be used to compose the REST API.

To interact with the board we need special software called Postman. After installing the program, we are ready to use it.

This is what its home screen looks like:

Postman home screen
Postman home screen

In the main window there is a bar where you will need to enter the API.

To the left of this bar there is a drop-down menu that allows you to choose the type of API (for example GET, POST, PUT…).

Now choose the POST type and enter the POST API that will have the following format:

192.168.1.8/setCutoffFrequency

NOTE: in subsequent experiments the IP 192.168.1.8 was assigned so the following images will refer to this IP.

Obviously you will have to enter the IP address assigned to your ESP8266.

Before pressing the Send button, you will need to select the Body item which is under the URL bar. Then select the raw item (under Body) and then, on the drop-down menu on the right, select the JSON item instead of Text, as shown in the photo:

Selection of parameters for the POST type API
Selection of parameters for the POST type API

In the window below enter the Json:

{
        "resistanceFrequencyCode": "1",
        "gainCode": "25"
}

This is what the Postman window should look like:

The Postman window with the POST API
The Postman window with the POST API

Press the Send button to send the Json with the resistanceFrequencyCode and gainCode parameters. By varying the value of the resistanceFrequencyCode parameter (between 0 and 255) you will vary the resistance of the digital potentiometer associated with it and, consequently, the cut-off frequency of the filter. The closer the resistanceFrequencyCode gets to 255, the lower the cutoff frequency becomes.

By varying the value of the gainCode parameter (between 0 and 255) you will vary the resistance of the digital potentiometer associated with it and, consequently, the gain of the filter. The closer the gainCode is to 255, the higher the gain.

To see the current state of the filter (i.e. to see what resistance has been set and, consequently, what cutoff frequency, and what gainCode value) now choose the GET type and enter the GET API that will have formed:

http://IP_ESP8266/getValues

For example, in this case, since the assigned IP is 192.168.1.8, the API URL will be:

http://192.168.1.8/getValues

Obviously you will have to enter the IP address assigned to your ESP8266.

Press the Send button on the right.

The API will return a Json file reporting the detected cutoffFrequency , cutoffResistance and gainCode values ​​as shown in the following image:

GET API result
GET API result

Demonstration video with guitar distortion measurement tools

In the following video you will see the device under test. In particular, a sinusoidal signal of 100mVpp and 1 kHz will be injected into the input and its behavior will be shown on the screen of an oscilloscope, as the resistanceFrequencyCode and gainCode parameters entered via Postman vary.

In particular you will observe three traces: the light blue trace is the sinusoidal signal at the input, the yellow trace is the distorted signal at the output of the distortion (i.e. at the ends of the diodes) while the purple trace is the signal at the output of the low pass filter (therefore the distorted signal which was then low pass filtered).

As you will notice, by keeping the gainCode value fixed at 1 and setting the resistanceFrequencyCode value at 1, the yellow and purple traces overlap. This is because the cutoff frequency of the filter is very high and therefore it is passing practically almost all the frequencies of the signal squared by the diodes. If then, always keeping the gainCode value at 1, you increase the resistanceFrequencyCode value up to 255, the cutoff frequency will lower and the filter will increasingly attenuate the low frequencies (including the fundamental at 1 kHz), making it always resemble plus the purple trace to a triangle wave. Increasing the gainCode value towards 255 increases the gain of the filter, consequently the purple track increases its amplitude. So to find the “right” sound you need to try a few combinations of these two parameters (considering that the amplifier that reproduces the sound will also add its timbre).

Demonstration video with electric guitar

In the following video I will engage in a truly unforgettable performance (I’m not a guitarist at all) to test our distortion with various values ​​of the cutoff frequency and filter gain. Good vision:

The Richardson amplifier used for reproduction is a 5W tube amplifier designed and built by me.

Newsletter

If you want to be informed about the release of new articles, subscribe to the newsletter. Before subscribing to the newsletter read the page Privacy Policy (UE)

If you want to unsubscribe from the newsletter, click on the link that you will find in the newsletter email.

Enter your name
Enter your email
Scroll to Top