Building an envelope generator on STM32

Hi All,

I am trying to build an envelope generator based on Olivier’s code. Instead of an ATMEGA328 I plan to use an STM32 device (e.g. STM32F103).
Currently, I am reading through Olivier’s envelope generator code. I have run into a few questions and I was wondering if someone could help me
by explaining the following:
In the code for rendering the envelope generator an inline assembly function called U8MixU16 is used. I can understand some parts of the assembly
but also some things are lost on me. Why is this function needed? What does the function do mathematically?

I have attached a screen grab of the function in the picture (just copy-pasting it here resulted in unreadable code)

Any help is much appreciated! :slight_smile:


A tips is that it may be easier to look at the C counterpart found here

Thanks Bjarne! I didn’t know there were C counterparts to these functions :slight_smile:

The Shruthi code was optimized… for AVR, a platform in which multiplying two 16-bit numbers (let’s not even say 32-bit) was a costly affair… So you had all these inline assembly functions for various flavours of multiply and shift operations.

But there’s no need to look at this stuff… Instead, look at:

Envelope class for Cortex M3 (fixed point, uses multiplications)

Envelope class for Cortex M4 (floating point)

Thanks Olivier! I will take a look at your links :slight_smile:

I currently made my own version of an envelope generator. To get it working I am running code that does float calculations for the values. This code is being run at 1kHz inside the Systick_Handler. I did this because I thought it would be hard to get Olivier’s C++ code running.
Currently I think I am at the limit of the MCU I am using and I want to go forward with getting Olivier’s code running. I have a few questions though and I hope someone can answer them…

Which envelope class that Olivier made is faster? The code running on the M3 or the code running on the M4 FPU (assuming F_CPU 72MHz)? How many cycles or which percentage would the codes take on the MCU?

Do the codes need to be run in a Systick_Handler or is the code running in the while(1) loop in main? For the while(1) loop: I am guessing that in this case the EG values are stored in buffers and are sent by DMA to a DAC. Is this correct?

> Do the codes need to be run in a Systick_Handler or is the code running in the while(1) loop in main?

Neither is a good idea. Systick is too slow (and in Olivier’s architecture reserved to UI stuff), while(1) is not timed. You should run it into the handler of the DAC’s DMA interrupt: each time the DAC asks for food you’ll have $n$ sample periods to compute the next block of $n$ samples.

> How many cycles or which percentage would the codes take on the MCU?

It depends on the sample rate of course. But OTOH I could see several instances of this easily running at 96kHz on an M4 (which is far overkill for an envelope). In other words, if you’re hitting the MCU’s limit you’re doing something wrong IMO.

Good luck!

> or the code running on the M4 FPU

You didn’t mention what is the target.

If there’s no FPU on your target, then don’t use floating point - it’ll be emulated and horribly slow.

On an architecture with an FPU, I expect very similar run time for the fixed point (integer) and floating point versions.

> How many cycles or which percentage would the codes take on the MCU?

I haven’t timed it, but Peaks’ envelope code shouldn’t take more than 200 cycles per sample. Could be as low as less than 100 cycles…

> is the code running in the while(1) loop in main?

If you write to your DAC using SPI or I2S with DMA, then you render your samples in the interrupt that indicates that a half-buffer needs to be filled.

If you don’t use DMA, you should probably have a timer interrupt writing one sample to the DAC every 1/fs second. You can either do the calculation in this ISR, or, in the ISR, pull a sample from a FIFO, which is filled from a while(1) loop in main whenever more than N samples are ready to be written.

Code is usually more efficient when you process N samples in a single loop: state variables can be loaded in registers before the loop, remain in registers within the loop, and are copied back to RAM at the end of the loop.

> You should run it into the handler of the DAC’s DMA interrupt

What’s the best approach if you want to use an STM32F103’s PWM outputs to generate EG’s?

Use the timers overflow interrupt to load the next “sample” into the compare register

@TheslowGrowth - thanks.

This seems closer to what I have in mind, mind you all the writeDAC functions need to be changed to pwmWrite:

Have you looked at my code for Batumi? It does exactly that (although I don’t guarantee that it’s ideal)

Thanks again for the great answers guys! :slight_smile:

I will explain a bit what I want to do. I have a Moog Satellite which is a pretty boring synth and to learn how to build more advanced synthesizers I want to build a quite elaborate MIDI expansion kit for it.

The Moog Satellite lends itself well for this project: it does not have many controls besides cutoff, resonance, LFO rate, LFO intensity but it does actually have a nice monophonice analog sound engine with two different filters (CA3080 SVF and a two-stage ladder filter).
The plan is to add about 20 potentiometers to the front panel that are sampled by the MCU so parameters can be set and patch storage will be possible. Also I want to see if it’s possible to send the potentiometer data via MIDI OUT to a DAW. This to record live improvisation and allow the user to modify automation curves in the DAW allowing cleanup (similar to what Elektron is doing with their Overbridge I think) before using the DAW to play the Satellite.
The envelope generators in the satellite are very simple AR envelopes and I want to implement software-based ADSRs. Also I want to implement two software-based LFOs to get more waveforms, allow MIDI sync, delay, reset phase, intensity, retriggering etc. For the DACs I am currently looking at the MCP4728 because it’s cheap and I found that 12 bit DACs actually give quite reasonable performance in synth circuits.

I have been playing with STM32 for a few months now and I think I understand most of the basic concepts. What I don’t know is what STM32 MCU to choose.
At the moment I am looking at the STM32F303 because it has a more than 20 12 bit ADC inputs, has a FPU and I think it’s incredible value for money at 4,50 euro. The MCU speed of 72MHz should be more than enough for doing what I want.

I have a few questions:
-is the STM32F303 a decent choice for what I want to do?
-is it smart to use a MCU with a FPU or is it better to stick to a M3 device? I am also asking this question because I have not yet found LFO code for M4F in Olivier’s github. I did find this for M3 as an LFO has been implemented in the peaks module.
-what would be the smartest way to get peaks envelope generator working? I have a licensed version of Atollic TrueStudio but I don’t know if this is the best way forward…

The M4F core is just the M3 core plus some more bits. If you’re not using those bits there’s no difference. For envelope code that uses integer maths, they’re the same.

If the F303 has the peripherals you need and the price is acceptable then just use it for now. Get a dev kit and write your code. It’s easy to move projects between different STM32 MCUs, they are very compatible, so if you find you need less/more power as you move towards a finished product you won’t have to start over.

Thanks rvense! Your comment made me realize I can do both the integer and the float code on the M4F device so I am going to stick with the F303 for now! :slight_smile:

I’m having a hard time getting the code to work. Is there somebody here who knows how to get the code running and is willing to teach me over skype. I am willing to pay a good hourly rate. :slight_smile: