My Bachelor's Thesis Project



Topic

As the subject of my bachelor’s thesis I picked my own idea, which was Design and implementation of a digital synthesizer using a dedicated programmable platform. From the start, my intetion was to make it an embedded project, and I visioned something like a simple keyboard instrument.

Motivation

There were two reasons why I decided on this topic. The first reason was my general interest in sound synthesis and processing that I’ve developed through my amateur attempts at electronic music production. The other reason was my growing interest in embedded systems. My subject of choice was born from the combination of these two hobbies.

Hardware

As the platform of choice I’ve decided on STM32 Black Pill development board, based on the STM32F411CEU6 MCU, with 128 KB of RAM, and maximum clock frequency 100 MHz.

Dedicated DAC was also necessary for digital-to-analog conversion between the MCU and the speaker. I’ve decided on the one from SparkFun as it supports communication via I2S.

What Was Accomplished

During the period of working on this project I managed to both assemble the device from various parts and write software for it. It resulted in simple electronic keyboard visible on the picture below.

Picture presenting final device with LCD screen, speaker and keyboard

Final device

The following features were implemented:

The video below is a showcase of different functions. The volume level jumps later in the recording.

Showcase of changing waveforms and octaves

Zoom On The Spectrum Analysis

Spectrum analysis for single A4 note generated by sine wave

Sine wave at frequency 440 Hz - A4 note

Spectrum analysis for single A4 note generated by triangle wave

Triangle wave at frequency 440 Hz - A4 note

Spectrum analysis for single A4 note generated by sawtooth wave

Sawtooth wave at frequency 440 Hz - A4 note

Spectrum analysis for single A4 note generated by square wave

Square wave at frequency 440 Hz - A4 note

Spectrum analysis for single A4 note generated by square wave with applied low pass filter at cutoff frequency 302 Hz

Square wave at frequency 440 Hz - A4 note, with applied low-pass filter with cutoff frequency 302 Hz

Spectrum analysis for notes C5 and G5

Triangular wave, notes C5 and G5 played simultaneously

How It Works

The algorithm is based on the infinite loop, that either reads from or writes to circular buffer. If there’s a space in the buffer, signal generation will start.

The Buffering

While testing different parameters, I’ve noticed that smaller number of bigger buffers has better performance than using a lot of smaller buffers. Therefore I’ve decided to use one buffer for writing data into, and one for reading data from. When operations on both buffers are complete, they swap roles so new data can be read, and old data to be overwritten. During the buffering, program checks the state of each note-button and then generates signal for each note.

Signal Generation

The signal for an individual note can be generated by using a waveform function. The waveform function specifies the “shape” of the signal with values between -1 and 1 over a single period. From the given frequency, the length of the period can be derived, and after normalizing the provided time value to the range of one period, the value defined by the “shape” can be returned.

The program iterates over all active keys and applies the waveform function to each note. All output values are summed into the single sample which is then written into the buffer.

To represent the time as the direction I’ve decided to use a global variable called time_counter which is incremented after every sample, and wraps around after it reaches the maximum value. Next, the time in seconds is calculated by dividing time_counter by sampling frequency. With this approach, the passing of time is strictly correlated with the sound generation, which ensures continuity of signal and independence of the system clock.

Playback

After the buffer is filled with samples, the data can be sent to the DAC. My implementation uses a non-blocking function to pass samples to the hardware. After the data is transmitted, the interrupt starts execution of another function, which attempts to send new data if it is ready. This chain effect ensures that the program will attempt to send new data with minimal delay. In case data is not ready after the previous transmission finishes, the main loop also tries to execute the send function, as a fallback. To avoid conflicting operations, a simple lock mechanism was implemented.

What Would I Do Differently

This digital synthesizer was my first bigger embedded project. While going back to it I’ve noticed several things I could’ve done differently.

Use Interrupts Instead Of Polling For Keys

In current version, the program has to iterate over all of the keys to check if they are pressed. It would be much more efficient to have an array that would store the key state, which would be altered by interrupts on both key press and release. That should improve performance as access to RAM is generally faster than access to peripherals. That solution would require to either add low pass filters for keys or implement some method of debouncing.

Use DMA With I2S

In current version, data is send via I2C do DAC with non-blocking function that flags interrupt when done. However the transfer of data still is managed by CPU. The DMA approach would free the CPU of this responsibility, so it can focus on other operations.

Use MCU With Double Precision FPU

I’ve noticed that there’s quite noticeable difference of audio quality depending on processing signal with single or double precision floating points. Unfortunately, my MCU has only a single precision FPU and double precision operations are simulated by the software which makes them much slower.