First steps in music synthesis and stm32

I want this post to be a complete tutorial on how to set up and run your first synthesizer application using stm32 microcontroller.

Introduction

This post might be for you if:

  • you want to make a simple synthesizer from scratch (no synth libraries)
  • you’re done with AVR, arduino..
  • you want to try stm32 but struggling with complex setup and ecosystem
  • you have no experience with embedded (that’s totally fine here)
  • you have basic coding skills

While there’s a lot of resources about making synthesizer using arduino I found it really hard to correctly set up everything on stm32 platform.

That’s why I’m writing this post, I wish I could have found some complete tutorial while I was starting my synthesis journey without any knowledge on embedded or DSP field.

In this tutorial I will guide you step by step with everything needed for creating a simple synthesizer. I won’t go deep into DSP details, note that this post is more about hardware setup than musical aspects.

Before you read it

This article might be quite long. Prepare your favorite drink and relax!

Although the post is not short I skipped a lot of details (why I do something this way, or that way). This tutorial should give you an overview on setting things up on stm32.

STM32 setup

In this article I will use stm32f4 discovery board with stm32f407.

The board also has cs43l22 digital to analog converter and that’s the IC which will produce our sound.

(Note that I will use DAC/CODEC naming interchangeably even they are not)

Let’s first look how DAC is connected to microcontroller.

Follow me, it’s not that hard!

There are 3 steps to establish communication with microcontroller:

  • set up I2C. Using it we will define DAC settings
  • set up I2S. Using it we will transfer audio data
  • set GPIO PD4 pin as CODEC_RESET

Let’s create new project using STM32CUBEIDE

If you’ve never heard about STM32CUBEIDE please visit this website:

In short: this tool (especially STM32CUBEMX which is part of IDE) allow you to set up necessary settings/peripherals really fast. Let’s see how it’s done!

After installing STM32CUBEIDE we have to create a new project:

First, click Start new STM32 project

Now you have to choose your microcontroller: ours is STM32F407VG, so let’s write it in Part Number dropdown. Select it in MCu/MPu list and click next.

Name your project and remember to change Targeted Language from C to C++. Click finish.

Great! We’ve made our first steps with stm32 setup. Now we must set up peripherals!

First, we will set our external crystal. To do that click on System Core, next choose RCC. Set High Speed Clock (HSE) as Crystal.

Now click on Clock Configuration

I know I know.. This looks fucking complicated!

As you can see I do not explain what each step in config does, I’m trying not to overwhelm you!

If you set up the app successfully you can come back to sections which you do not understand and simply google it!

For now let’s focus on our goal: MAKE STM32 BLEEP BLEEP, right?

Let’s start from left side:

  • Change input frequency from whatever value it is to 8.
  • In PLL Source: Change from HSI to HSE.
  • In System Clock: Change from whatever it is to PLLCLK.
  • NOW! Let’s do some magic: write down 168 in HCLK and hit ENTER. See how it magically adjusts all the parameters!

After these 4 steps check once again if everything looks exactly like on the above screenshot.

Now go to Project Manager:

Click Code Generator Tab:

Select Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral.

Come back to Pinout & Config tab

Let’s set up our I2C peripheral

Click on Connectivity Dropdown:

Select I2C1, next change mode from disable to I2C.

Note that two pins are selected now:

  • PB7-I2C1_SDA
  • PB6-I2C1_SCL

We have to remap SDA pin.

That’s really easy! Hold CTRL and now click on PB7-I2C1_SDA, you should see that PB9 pin is highlighted blue. Drag it from PB7 to PB9 and that’s all. We’ve finished I2C peripheral setup!

Let’s go to I2S.

Go to Multimedia tab and choose I2S3, set mode as Half-Duplex Master, check Master Clock Output.

In Parameter Settings tab:

  • change Selected Audio Frequency to 48kHz

Now go to DMA Settings tab:

First click Add and choose SPI3_TX. Change Mode to circular and Data Width to Half Word.

Other settings stay as default.

We will correct one more thing in I2S settings

When we go back to Parameter Settings of I2S you’ll notice Error between Selected and Real Frequency

See that mine is -2.88% now. It’s quite big! There’s a simple fix to make it nearly identical!

Go to Clock Configuration once again

And change PLLI2S to 123

Now look again at our error in I2S Parameter Settings

Great! It’s only 0.09%.

We’re done with I2S. Was it so stressful? I hope not! We’re getting closer to the finish line so stay focused!

Set up PD4 as Output

Click on PD4. Set it as GPIO_Output.

Now right click on PD4. Enter User Label and call it CODEC_RESET

If your pin looks the same whole CUBEMX setup is done.

Like literally…

We’ve finished setup of all the peripherals.

We can start writing code now!

Clicking save button will generate all peripherals setup code!

That’s how CUBEMX fine is. We don’t need to write all boring setup code every time we create new app.

For now you can think there’s already a lot to ‘click’, but believe me: this will save a lot of our time and energy!

Write Code

After hitting save button you should see main.c file.

There are two parts of our interest which illustrate pictures below:

First of all

Do you remember that we define language as cpp? You can clearly see that our main has .c extension. That’s wrong and we’re gonna fix it right now!

Right click on main.c and choose Rename option.

Write down main.cpp and update references.

We fixed our little issue. Now we can start to code. Let’s begin with initialization of our CODEC.

Write codec class

Right click on Src folder. Create new Class.

Name our class Codec. Be sure it’s Codec not codec, the capital letter here is important!

After clicking Finish you’ll notice .h/.cpp Codec files. We have to move .h file to the Inc folder. Simply drag it there.

Now you should see exactly the same files structure.

We’re gonna write some methods for codec setup.

You can rewrite it now or copy exact code from github repo.

Remember to include your code in main.cpp!

Look at lines:

  • 60: here we create object of our codec
  • 95–98: first we check if initialization of our codec went well. If status is not 0 there is some problem with I2C communication. HAL_ERROR is returned then.

That’s the whole code for the codec setup. As you can see it’s quite easy and short.

Write synthesizer class

Analogically we have to create Synth class. Repeat steps from above with class name: Synth.

As I said I don’t want to make this article so long. If you don’t understand the code don’t worry. What it does is it calculates values of triangle wave for given pitch (frequency).

We can call it lookup table kind of synthesis. If you want to learn more about it simply google it or..

watch this video, it’s very good.

We also have to create wavetable.h file in Inc directory.

Next

Create synth object and audiobuff table which will contain our signal.

make_sound function simply calculates signal values on each interrupt Half and Cplt cycle. If you don’t know anything about interrupts or DMA don’t worry. DMA simply reduces usage of our CPU so we have more power for another calculations.

We set frequency (pitch) of 220Hz (‘A’ note).

Last thing is to add HAL_I2S_Transmit_DMA function.

THE END

Let’s run our app and check if it’s working.

Click on the bug icon and create new stm32 app.

The window should look like this. We don’t need to change anything here. Just click OK.

Shut down debugger.

And finally..

Run our code by clicking on the green play button.

Now, just connect stm32 with any kind of speaker that has 3.5mm jack. The sound should appear!

The goal has been achieved!

Congratulations! It wasn’t easy but finally we did it!

As I said before: although I didn’t explain details about peripherals and code this tutorial turned out quite long.

If you are interested what’s going on behind the code just let me know in the comments. There’s a chance that some day I will write another article on that topic.

Below you will find complete STM32CUBEIDE project.

You can clone the repo and play with it instantly.

References

This tutorial is inspired by two projects:

Synthol is stm32f4 synthesizer. It’s way more complex than our example, if you want to add new features for sure it will be an invaluable help.

The DAC initialization is taken from forbot article. (unfortunately only for Polish audience)

Into: data science, dsp and embedded

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store