Learning embedded programming
Background
I have always been attracted to embedded development (even though it felt intimidating before) and the whole magic of making physical devices do cool stuff. As with any of my spare time projects, I also like the idea of bridging my knowledge about music production with my skills as a c++ programmer.
A few years back, before I really knew anything about programming and computer science, I messed around a bit with the whole Arduino universe (controllers, IDE and API) and was really excited about its capabilities. I remember clearly thinking that this kind of development was something I really wanted to learn more about once I had learned more about writing code. I also remember reading somewhere that using the Arduino API was the "easy way" and that if you really wanted to do anything for real you would have to know more about the C language, memory, tasks, CPUs and probably learn and reference the official API from the chip manufacturer directly.
That was one of those annoying and frustrating moments for me where I wanted to just snap my fingers and magically have all experience and knowledge needed. While I absolutely don't know everything today, I feel that I'm ready do dive in to the topic again. This time the "hard way".
Distorsion Pedal & ESP32
The end goal of this project is to create a physical distorsion pedal which I can plug my guitar in and that will spit out some kind of sound in the other end. The actual signal processing will be performed on an ESP32-S3 with external ADC and DAC converters on either end.​​​​​​​​​​​
Current state & Road map
There is a lot of stuff I need to learn in order to make this happen. Not only will I need to learn how to use the ESP-IDF and work with a completely different development environment. I'll also need to learn some basic audio circuit design (and probably fall into all the usual traps), learn more about the hardware differences between a computer and an embedded device and of course learn more about programming for a chip like the ESP32-S3.
​
At the moment, I have a workstation for development set up including my coding environment (VS Code with IDF extension), the board (ESP32-S3- WROOM-N16R8), ADC (PCM1808), DAC (PCM5102), breadboards, jacks and pots. I have gone through some example code from the IDF as well as written some tests of my own in order to learn how to speak to the board and the DAC.
Following is a rough plan on how I will proceed:
​
Pre production​
-
Integrate the ADC and read audio from it.
-
Create a full audio flow from input jack to output jack (with null-tests in a DAW to find out what the converters actually sound like).
-
Integrate pots and make them control values on the device.
Alpha -
Research distorsion code.
-
Develop my own distorsion algorithm.
-
Lots of testing and iterations.
-
Plan out the pedal design and purchase everything needed.
Beta -
Assemble the product and try it out in the studio!

DAC output and Distorsion blockout
In order to be able to properly iterate on the distorsion code I decided to focus on making the output work first.
The program fires two parallel tasks, waits a specified amount of time and then shuts down.
One task is constantly outputting a 440Hz sine wave and calculating the distorsion from it. The distorsion code is a simple compression into clipping flow and the result should sound like a sine wave transforming into a square wave once you increase the drive knob.
The other task is responsible for reading values from the GPIO pins where the input/drive- and output-pots are connected.
These two tasks are fired using the xTaskCreatePinnedToCore in order to make them run in parallell.
​
Threads and Locks
The audio DSP loop must be wait free (no memory allocations, no prints, no mutexes, no reading physical pins etc) to avoid awful glitchy/noisy audio coming out the DAC. This was the original reasoning behind putting the reading of knobs in its own task.
To solve the problem of two threads sharing variables I took advantage of the fact that the ESP32 can write 32 bit floats in a single, indivisible, clock cycle. This means that the variables for gain and volume can never be "half written" once the audio thread reads them.
​
While this solution works perfectly fine with only one write/read pair in the whole program, this is not good C practice as volatile variables are not intended for multi-threaded use. As I understand it they simply mean "this variable can be modified externally so don't store it in your cache register but fetch it from RAM all the time".
A better solution is to make them into atomic variables.
​
This will not only enable the two cores to store the variables into their corresponding cache memory, but will also be lock free on a 32 bit environment given that atomic_is_lock_free(float_variable) returns true.
In order to also make the operations wait free I have to restrict the access to be "Single writer" (the control_knob_task in my case).
​