Introduction: Upgraded DDS Signal Generator From Arduino to STM32 Black-Pill Is Very Fast

About: I am a retired Electronic Systems Engineer now pursuing my hobbies full time. I share what I do especially with the world wide student community.

This is an update on my earlier Instructable: 10 Resistor Arduino Waveform Generator with an aim to maximize the Direct-Digital-Synthesis (DDS) signal generator frequency obtainable with the STM32F401CCU6 using the inbuilt Direct-Memory-Access (DMA) mode.

While the Arduino UNO operates at a clock frequency of 16MHz and the DDS clock obtained was ~372kHz, the STM32F401 has a clock frequency of 84MHz and the DDS clock obtained is 8.4Mhz. With this ~22 times improvement in DDS clock the signal generator maximum frequency is significantly increased.

The two figures show the significant difference in the output waveforms between the earlier Arduino version and this STM32 version. The maximum sinewave frequency obtained with the STM32 is 1MHz.

The STM32F401CCU6 Black-Pill has the advantage of USB connectivity and inbuilt features such as multiple timers, digital I/O and multiple DMA channels. The detailed methodology of using these features is explained in the following steps.

A Windows Graphical-User-Interface (GUI) has also been developed in VS2019 to provide commands to the unit via a PC USB port.

STM32F401 Cube MX and VS2019 GUI Project files on GitHub

Supplies

Step 1: 8-4-2-1 Voltage Output Resistor DAC

11-Resistors wired on a general purpose PCB create a Digital-to-Analog-Converter (DAC) which is interfaced to the digital outputs PA5-PA0 of the STM32F401.

PA5 provides the DDS signal generator square wave output and the DAC, sine/triangle/ and ramp outputs.

Step 2: DDS Signal Generation

Calculation of the current DAC value for DDS sine signal generation requires repetitive execution of the following typical code segment for every DDS clock instant:

phase_accumulator=phase_accumulator + phase_step;

table_index=(phase_accumulator)>>16; //shifting by 8,16 require less cycles

table_index=table_index>>1;

DDS_DAC_OUT =SIN_MAP[table_index];

Where the phase_accumulator and phase_step values determine the frequency and the table_index points to the DAC value in the SIN_MAP table.

This segment of code requires several processor clock cycles at 84MHz and cannot be executed completely at the desired DDS clock rate of 8.4MHz. However this is possible by using the timers and DMA mode features of the STM32F401.

Referring to the figure:

  • We use a Sine Table with 6-bit width and 128 Length
  • The 6-Bit Resistive 8421 DAC connected to A5-A0

Timer1 clock set to 8.4MHz with Internal clock 84Mhz and a divider of 10

Timer1 operates in Output Compare CH1 mode with Toggle on match giving a monitor output of 4.2MHz on PA8.

Timer1 Event is the Trigger for Timer2 Ext Clock Mode1 ITR0

Timer1 is in PWM mode with Divider 128 and PWM period 64 with no output

The DAC output is based on DMA transfer to Port in Circular Mode Word Width 128 samples

The DMA2_Stream5_IRQ Handler gives an interrupt at start of DDS_OUT_MAP transfer

  • The TOP Half of the Buffer is output to the DAC by the DMA and DDS values for BOTTOM are calculated here (using the typical code segment above for 64 values).

The HAL_TIM_PWM_PulseFinished Callback gives an interrupt at second half of DDS_OUT_MAP transfer

  • The BOTTOM Half of the Buffer is output to the DAC by the DMA and DDS values for TOP are calculated here (using the typical code segment above for 64 values).


DMA2_Stream5 sets LED on PC13 at start of computation and resets at end of computation of 64 DAC values. This indicates the calculation rate and time consumed. We obtain a frequency of 65.625kHz with a duty cycle of 44%. Which indicates a margin of 6%. 


Effectively the DMA transfers the 128 length circular buffer values to the DAC in memory-to-peripheral mode at 8.4MHz without using any CPU resources. The buffer values Top-Half and Bottom-Half are computed using two interrupt periods set by DMA-Complete and PWM-Period-Complete. So that while the DMA transfers are in progress we are able to populate the buffer with values to be output to the DAC.

Step 3: VS2019 Windows GUI and STM32F401 CUBEMX Code

All code is available at my GitHub page:

https://github.com/ajoyraman/STM32F401_DDS_Signal_Generator

Step 4: Programming the Black-Pill

The Black-Pill is programmed in DFU mode using the STM32CubeProgrammer

To enter DFU Mode:

Follow the standard process to put an STM32 "black pill" (STM32F401CC) into DFU mode so that you can upload the .Hex file using the STM32CubeProgrammer

  1. Hold down boot0.
  2. Press and release rst.
  3. Release boot0.

Install the STM32CubeProg from here: https://www.st.com/en/development-tools/stm32cubeprog.html

Use this to program the 007_DDS_SigGen [204685A5].hex file


Step 5: Setting Frequency and Waveform Without GUI Using Serial Terminal

A simple serial protocol can be used for selecting the STM32 Black-Pill Signal Generator. This protocol can also be used for developing custom GUI programs.

Connect with serial protocol 8-N-1 at 112500 baud


Follow the 4-byte --Commands Hex values --

Break Command 42 42 42 42 "B" in ASCII Used as Dummy

Identify 49 00 00 00 "I" in ASSCII Returns "STM32 DDS Signal Generator"

5kHz Sin 73 00 27 02 "s" in ASCII Followed by Phase_Step Setting in Hex 

25kHz Triangle 56 00 C3 0C "V" in ASCII Followed by Phase_Step Setting in Hex

10kHz RampUp 55 00 4E 05 "U" in ASCII Followed by Phase_Step Setting in Hex

15kHz RampDn 45 00 75 07 "E" in ASCII Followed by Phase_Step Setting in Hex


The 3-Byte Hex Value of Phase_Step (msb first) is calculated as:

Phase_Step = Frequency * (16777216 / 8400000) converted to 3-Byte Hex

Example:

Frequency 1kHz, Phase_Step = 1997 and 3-Byte Hex 00 07 CD

Sin 1kHz Command 73 00 07 CD