STM32 F4 DAC DMA Waveform Generator

Goal: generating an arbitrary periodic waveform using a DAC with DMA and TIM6 as a trigger.

Agenda:
  1. Modeling a waveform in MATLAB and getting the waveform data
  2. Studying the DAC, DMA, and TIM6 to see how it can be used to generate a waveform
  3. Coding and testing a couple of functions


%% Generating an n-bit sine wave
% Modifiable parameters: step, bits, offset
close; clear; clc;

points = 128;                            % number of points between sin(0) to sin(2*pi)
bits   = 12;                             % 12-bit sine wave for 12-bit DAC
offset = 75;                             % limiting DAC output voltage

t = 0:((2*pi/(points-1))):(2*pi);        % creating a vector from 0 to 2*pi
y = sin(t);                              % getting the sine values
y = y + 1;                               % getting rid of negative values (shifting up by 1)
y = y*((2^bits-1)-2*offset)/2+offset;    % limiting the range (0+offset) to (2^bits-offset)
y = round(y);                            % rounding the values
plot(t, y); grid                         % plotting for visual confirmation

fprintf('%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, \n', y);



There's a trade-off between the sine wave resolution (number of points from sin(0) to sin(2*pi)), output frequency range, and precision of the output frequency (e.g. we want a 20kHz wave, but we can only get 19.8kHz or 20.2kHz because the step is 0.4kHz). The output frequency is a non-linear function with multiple variables. To complicate it further, some of these variables must be integers within 1 to 65535 range which makes it impossible to output certain frequencies precisely.
Although precise frequency control is terribly hard (if not impossible), one feature does stand out - ability to generate a periodic waveform of any shape. 
Below is the code for mediocre range/precision/resolution but excellent versatility in terms of shaping the output waveform.

@input - uint16_t function[waveform_resolution]
@output - PA4 in analog configuration
@parameters - OUT_FREQ, SIN_RES

To tailor other parameters, study the DAC channel block diagram, electrical characteristics, timing diagrams, etc. To switch DAC channels, see memory map, specifically DAC DHRx register for DMA writes.


#include <stm32f4xx.h>
#include "other_stuff.h"

#define   OUT_FREQ          5000                                 // Output waveform frequency
#define   SINE_RES          128                                  // Waveform resolution
#define   DAC_DHR12R1_ADDR  0x40007408                           // DMA writes into this reg on every request
#define   CNT_FREQ          42000000                             // TIM6 counter clock (prescaled APB1)
#define   TIM_PERIOD        ((CNT_FREQ)/((SINE_RES)*(OUT_FREQ))) // Autoreload reg value

const uint16_t function[SINE_RES] = { 2048, 2145, 2242, 2339, 2435, 2530, 2624, 2717, 2808, 2897, 
                                      2984, 3069, 3151, 3230, 3307, 3381, 3451, 3518, 3581, 3640, 
                                      3696, 3748, 3795, 3838, 3877, 3911, 3941, 3966, 3986, 4002, 
                                      4013, 4019, 4020, 4016, 4008, 3995, 3977, 3954, 3926, 3894, 
                                      3858, 3817, 3772, 3722, 3669, 3611, 3550, 3485, 3416, 3344, 
                                      3269, 3191, 3110, 3027, 2941, 2853, 2763, 2671, 2578, 2483, 
                                      2387, 2291, 2194, 2096, 1999, 1901, 1804, 1708, 1612, 1517, 
                                      1424, 1332, 1242, 1154, 1068, 985, 904, 826, 751, 679, 
                                      610, 545, 484, 426, 373, 323, 278, 237, 201, 169, 
                                      141, 118, 100, 87, 79, 75, 76, 82, 93, 109, 
                                      129, 154, 184, 218, 257, 300, 347, 399, 455, 514, 
                                      577, 644, 714, 788, 865, 944, 1026, 1111, 1198, 1287, 
                                      1378, 1471, 1565, 1660, 1756, 1853, 1950, 2047 };           

static void TIM6_Config(void);
static void DAC1_Config(void);           

int main()
{
  GPIO_InitTypeDef gpio_A;
 
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);                  
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

  gpio_A.GPIO_Pin  = GPIO_Pin_4;
  gpio_A.GPIO_Mode = GPIO_Mode_AN;
  gpio_A.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOA, &gpio_A);

  TIM6_Config();  
  DAC1_Config();
 
  while (1)
  {
    
  }
 
}

static void TIM6_Config(void)
{
  TIM_TimeBaseInitTypeDef TIM6_TimeBase;

  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
 
  TIM_TimeBaseStructInit(&TIM6_TimeBase); 
  TIM6_TimeBase.TIM_Period        = (uint16_t)TIM_PERIOD;          
  TIM6_TimeBase.TIM_Prescaler     = 0;       
  TIM6_TimeBase.TIM_ClockDivision = 0;    
  TIM6_TimeBase.TIM_CounterMode   = TIM_CounterMode_Up;  
  TIM_TimeBaseInit(TIM6, &TIM6_TimeBase);
  TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);

  TIM_Cmd(TIM6, ENABLE);
}

static void DAC1_Config(void)
{
  DAC_InitTypeDef DAC_INIT;
  DMA_InitTypeDef DMA_INIT;
  
  DAC_INIT.DAC_Trigger        = DAC_Trigger_T6_TRGO;
  DAC_INIT.DAC_WaveGeneration = DAC_WaveGeneration_None;
  DAC_INIT.DAC_OutputBuffer   = DAC_OutputBuffer_Enable;
  DAC_Init(DAC_Channel_1, &DAC_INIT);

  DMA_DeInit(DMA1_Stream5);
  DMA_INIT.DMA_Channel            = DMA_Channel_7;  
  DMA_INIT.DMA_PeripheralBaseAddr = (uint32_t)DAC_DHR12R1_ADDR;
  DMA_INIT.DMA_Memory0BaseAddr    = (uint32_t)&function;
  DMA_INIT.DMA_DIR                = DMA_DIR_MemoryToPeripheral;
  DMA_INIT.DMA_BufferSize         = SINE_RES;
  DMA_INIT.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
  DMA_INIT.DMA_MemoryInc          = DMA_MemoryInc_Enable;
  DMA_INIT.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  DMA_INIT.DMA_MemoryDataSize     = DMA_MemoryDataSize_HalfWord;
  DMA_INIT.DMA_Mode               = DMA_Mode_Circular;
  DMA_INIT.DMA_Priority           = DMA_Priority_High;
  DMA_INIT.DMA_FIFOMode           = DMA_FIFOMode_Disable;         
  DMA_INIT.DMA_FIFOThreshold      = DMA_FIFOThreshold_HalfFull;
  DMA_INIT.DMA_MemoryBurst        = DMA_MemoryBurst_Single;
  DMA_INIT.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single;
  DMA_Init(DMA1_Stream5, &DMA_INIT);

  DMA_Cmd(DMA1_Stream5, ENABLE);
  DAC_Cmd(DAC_Channel_1, ENABLE);
  DAC_DMACmd(DAC_Channel_1, ENABLE);
}

Using the code above we are supposed to get a 5kHz sine wave constructed with 128 points (for better quality, consider using more points).
Here's a picture of what we actually get (off by 25Hz, not too bad).


And here's the cool sinc(x) function. To generate other functions, model it in MATLAB, cast to 12-bit, STM32F4 does the rest. 


52 comments:

  1. Awesome Awesome Awesome !!!! in PIC32, there is not a DAC :( so once, I used an external I²C DAC to generate a rectified sine wave, I've calculated the values to send to the DAC with a C program (under eclipse) and pasted the values in a byte vector in the microcontroller program ^_^

    ReplyDelete
    Replies
    1. Awesome :) How fast can you move the data from memory to the external I²C DAC? In other words, what's the highest frequency sine wave you can output if you use 128 points to construct a sine wave like in my example above?

      Delete
    2. I'm going to retest the LTC2635 I²C DAC as those days I'm coding my drivers in C++ classes and I ll tell you very soon, I ll use your example
      the DAC can operate at 400 Khz (fast I²C mode)
      BTW : nice blog !

      Delete
    3. My DAC is 8 bits (I commanded the 12-bits but when I tested it, it was an 8-bit DAC grrrr), with a 400 kbps I²C, the MAXIMUM throughput and a 128 points to construct the sin wave, the frequency of the latter was 100 Hz hahahaha, with a 20 points I reached 500 Hz but the sin wave was "pixelized"

      With 256 points I had a 6.66 Hz sin wave (satan frequency)

      I am still far from 1 KHz xD

      I tried also the sinc function it's amazing ! I don't have matlab installed where I work, so I used a C program under Eclipse to calculate it (between - 6pi and 6pi)

      I didn't use an offset, my sin wave oscillated between 0 V and Vdd, I understanded your formula but can you tell why did you choose your offset to be 75 ?

      Delete
    4. I did a little mistake :
      71.4 Hz with 128 points
      130 Hz with 64 points

      Delete
    5. Why did I use an offset of 75?

      Short answer:
      to limit the DAC's output voltage range; 75 is an arbitrary number.

      Long answer:
      The ideal DAC output voltage range is 0V to Vref+. Vref+ is between 1.8V and Vdda. (assume that Vref+ = Vdda = 3.6V).
      I have the DAC_OutputBuffer enabled, which reduces the output impedance and limits the output range to (0.2V - 3.4V).

      Using the DAC output voltage equation above (DACoutput = Vref+ * DOR/4095), we get the following ranges for the DAC output register (DOR):
      (0V - 3.6V) translates into (0 - 4095) or (0x000 to 0xFFF). This is the ideal 12-bit DAC range.
      (0.2V - 3.4V) translates into (227 to 3868) or (0x0E3 to 0xF1C). This is guaranteed by STM32F407 (according to the documentation); realistic 12-bit DAC range.

      In my case, I just chose 75 (again, just an arbitrary number) and limited the output to (75 to 4020).
      (75 to 4020) is (0.06V - 3.53V) which is pretty close to ideal, but if you look at the oscilloscope picture, you can see that the range is about (0.1V - 2.9V).
      It doesn't agree with my logic above, but note that we assumed that Vref+ is 3.6V.
      In my case, Vref+ should be around 3.0V.

      I hope it makes sense :)

      Delete
  2. Well, My DAC is a 12-bit, I thought it was 8-bit one because the full scale with 0xFFF, 0x3FF and 0x0FF were the same in the oscilloscope, but today, I have computed and measured with a multimeter the resolution (code 0x001) and I realized that it was effectively a 12-bit DAC.

    ReplyDelete
  3. Hi, Can anyone help me with this? I am trying to do this using the DAC channel 2 since I am already using the pin PA4 as the analog input for the audio DAC CS43L22. I have connected the pins PA5 and PA4 together so that the output of DAC channel 2 goes to the analog input of CS43L22 and I can then hear it through the headphone jack. However, I am not getting any tone. I know I have configured the CS43L22 properly because I can hear music when I output it from the computer to the analong pin PA4.

    This is my DAC configuration:


    static void DAC2_Config(void)
    {
    DAC_InitTypeDef DAC_INIT;
    DMA_InitTypeDef DMA_INIT;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

    DAC_INIT.DAC_Trigger = DAC_Trigger_T6_TRGO;
    DAC_INIT.DAC_WaveGeneration = DAC_WaveGeneration_None; //DAC_WaveGeneration_None;
    DAC_INIT.DAC_OutputBuffer = DAC_OutputBuffer_Enable;
    DAC_Init(DAC_Channel_2, &DAC_INIT);

    DMA_DeInit(DMA1_Stream6);
    DMA_INIT.DMA_Channel = DMA_Channel_7;
    DMA_INIT.DMA_PeripheralBaseAddr = (uint32_t)DAC_DHR12R1_ADDR;
    DMA_INIT.DMA_Memory0BaseAddr = (uint32_t)&function;
    DMA_INIT.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_INIT.DMA_BufferSize = SINE_RES;
    DMA_INIT.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_INIT.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_INIT.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_INIT.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_INIT.DMA_Mode = DMA_Mode_Circular;
    DMA_INIT.DMA_Priority = DMA_Priority_High;
    DMA_INIT.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_INIT.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_INIT.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_INIT.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
    DMA_Init(DMA1_Stream6, &DMA_INIT);

    DMA_Cmd(DMA1_Stream6, ENABLE);
    DAC_Cmd(DAC_Channel_2, ENABLE);
    DAC_DMACmd(DAC_Channel_2, ENABLE);
    }


    --I have also changed the the DAC register address from 0x40007408 to 0x40007414. Am I missing anything?

    ReplyDelete
    Replies
    1. Hi John,

      How did you set up GPIOA and TIM6? This piece of code looks good:
      DAC Ch.2 is using (DMA1, Stream 6, Channel 7) so that's correct.
      Peripheral base address for DMA (DAC_DHR12R2_ADDR) is 0x40007414 so that's also correct.

      Delete
  4. Hello Sergey,

    I have a question. If I see your function with the digital sample values of your sinus:

    const uint16_t function[SINE_RES] = {......................}

    I ask myself how could you change the amplitude of your sinus, for example: I would connect to my STM32F407VG an ultrasonic sensor. If my hands approach, the amplitude of your sinus should become bigger, and smaller if I remove my hands.

    But I suppose you have here only a fixed buffer [SINE_RES] ={ ....digital values.....} and any ONGOING changes are not possible????

    Is it true?

    Thank you for your reply.

    Maciej

    ReplyDelete
    Replies
    1. Hi Maciej,

      You can make ongoing changes by loading the DHRx register with (almost) any 12-bit value.
      In my example, I have a buffer with fixed values. DMA loads these fixed values one by one to the DHRx register. The DAC automatically transfers the value from DHRx to DORx and then outputs it on the GPIO pin.

      The "const" keyword means that my buffer is read-only, but it doesn't have to be. You can create a buffer (not const) and modify the values inside at any time (make ongoing changes).

      The amplitude of the output waveform is calculated using this equation:
      DACoutput = Vref * (DOR/4095)
      DACoutput is the pseudo-analog output voltage (the one you can measure on the GPIO pin)
      Vref is the voltage applied to the DAC's Vref pin
      DOR is the digital 12-bit value loaded to the DORx register (this is what you modify to change the amplitude)

      How do you plan to use your ultrasonic sensor? What is your setup?

      Delete
  5. Hi Sergey,

    I want to use an ultrasonic sensor HC-SR04 buyed here in Austria

    ( trigger square signal for sensor= 50 Hz, output echo signal = 25 Hz).

    I configurated my capture function for the sensor. And it works ok. (Clock = 26Mhz)

    void timer1_konfigurieren(void) {

    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Prescaler = 240;
    TIM_TimeBaseStructure.TIM_Period = 2600;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_BothEdge;
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0;
    TIM_ICInit(TIM1, &TIM_ICInitStructure);


    TIM_PWMIConfig(TIM1, &TIM_ICInitStructure);


    TIM_SelectInputTrigger(TIM1, TIM_TS_TI1FP1);
    // TIM_SelectInputTrigger(TIM1, TIM_TS_TI2FP2);
    TIM_SelectSlaveMode(TIM1, TIM_SlaveMode_Reset);
    TIM_SelectMasterSlaveMode(TIM1, TIM_MasterSlaveMode_Enable);

    TIM_Cmd(TIM1, ENABLE);
    TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);

    }
    void TIM1_CC_IRQHandler(void)
    {
    if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET )
    {
    Amplitude = TIM_GetCapture1(TIM1); // capture my edges
    TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);
    }
    }

    The question is what can I make with the value "Amplitude" ( ist my captured signal of sensor) then??

    You use uint16_t function[SINE_RES] = { ....................} to generate your sinus wave of 5kHz.

    You say WITHOUT const. I understand it.

    The code could be:
    ..............
    ..................
    DMA_INIT.DMA_PeripheralBaseAddr = (uint32_t) (uint32_t)DAC_DHR12R1_ADDR; // .>> we need 12 bit aligned
    DMA_INIT.DMA_Memory0BaseAddr = (uint32_t)&function
    DMA_INIT.DMA_BufferSize = SINE_RES;
    ......................................

    But my captured "Amplitude" would need something like: Amplitude*(sin(2*pi*trigger_period*output_frequency* control_variable for the trigger_period))

    You have only a buffer (const) uint16_t function[SINE_RES] = { ....................} with amplitude values.

    It would meen, that I must decrement/ increment ALL values in function [SINE_RES {....} correlated to my "Amplitude" depending on the position of my hands. I need to compress or to stretch the sinus wave, you know.

    Does it happen automatically if I overwrite the output register DORx with "Amplitude" in the DMA_IRQHandler for example??

    Something like this for DMA1? :

    void DMA1_Stream5_IRQHandler(void)

    {
    if( DMA_GetITStatus(DMA1_FLAG_TC5) )

    { DAC->DORx =Amplitude; // for Channel 1
    DMA_ClearITPendingBit(DMA1_IT_TC5); // Clear Transfer Complete Flag

    }

    Thank you for your reply.

    Maciej



    ReplyDelete
  6. HI Sergey,

    Good post.
    I am using an STM32F4 Discovery. Using your code exactly as is, my DAC is outputting a sine wave at 9.95KHz. If I try to change the OUT_FREQ to some other value, such as 2000, there is no DAC output at all.

    Any idea of what might be going on?

    Thanks!

    ReplyDelete
    Replies
    1. You have to swap call function TIM6_Config(); DAC1_Config();

      Delete
    2. I am facing the same problem, do you mean that I need to call DAC1_Config before TIM6_Config?
      What's the reason to do this? Could you explain better?

      Thanks

      Delete
    3. You saved my day, thanks!

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Здравствуйте Сергей, у меня проблема как человека выше, при понижении частоты, DAC перестает работать, во всех его регистрах нули, кроме CR, там 0x0000340F

    ReplyDelete
  9. Проблема решена, изменением вызова функций TIM6_Config(); DAC1_Config();

    ReplyDelete
    Replies
    1. Спасибо за добавленную информацию! Возможно эир связано с внутренними изменениями в std periph library.

      Delete
  10. can u give me otherstuff.h file..

    ReplyDelete
  11. #define DAC_DHR12R1_ADDR 0x40007408 // DMA writes into this reg on every request
    i don't understand 0x40007408. how can you have this address?

    ReplyDelete
    Replies
    1. It's from the peripheral manual.
      You can check the memory map and specifically the DAC registers.

      Delete
  12. Огромное спасибо за пример !!!

    ReplyDelete
  13. Sergey,

    A good post, but I still have some questions about this.
    Can you explain the relationship of the sine table to the frequency, or is there one? When Matlab generates it, there is only the number of points, so frequency shouldn't be a factor as seen with your formula. So, the output works at 5KHz. Then changing down, at 4.5KHz, it stops working. Can you tell me why it would stop working below that frequency, and what would I do to get it to around 100Hz?

    Thanks you!

    ReplyDelete
    Replies
    1. Hi Gary,

      Each time DAC is triggered, only 1 out of 128 points from the sine table gets converted and can be seen as voltage on the output pin.

      If you trigger 128*1 times per second, you get a 1Hz waveform.
      If you trigger 128*2 times per second, you get a 2Hz waveform.
      ...
      If you trigger 128*100 times per second, you get a 100Hz waveform.

      Now, look at what happens if you double the number of points in the sine table (from 128 to 256).

      If you trigger 256*1 timer per second, you get a 1Hz waveform.
      If you trigger 256*2 timer per second, you get a 2Hz waveform.
      ...
      If you trigger 256*100 times per second, you get a 100Hz waveform.

      You should be able to see the relationship now - doubling the number of points in the sine table results in doubling the trigger frequency if you want to have the same output frequency.

      Now, what affects trigger frequency?
      - APB1 frequency (clock that drives the timer)
      - timer prescaler (provides a coarse-grained control for timer frequency)
      - timer autoreload value (provides a fine-grained control for generating trigger events)

      There are limitations to how fast/slow you can trigger. They are mentioned in the post (mostly focusing on the fast side).

      Delete
    2. Okay, I think your first line is the key: "....only 1 out of 128 points from the sine table gets converted and can be seen as voltage on the output pin."
      So if I wanted a value of 1.65V, I would reload constantly with 2048? Does the frequency of the timer matter?

      Thanks again!

      Delete
    3. V = Vref * X / 4095 (you can find a plot for this equation above)

      V is the voltage on the output pin.
      Vref is ~3.0V in my example.
      X is the value you load into the DOR register (e.g. 1 out of 128 points at a time).

      For example,
      V = 3.0 * 2048 / 4095 = ~1.5V.

      Timer/trigger frequency would not matter in this case.
      However, it's always good to read electrical characteristics and whatnot to see how exactly the DAC will behave (and test of course).

      Delete
  14. Hi Sergey,

    I was away on vacation for a month and just got back. Thanks for a very thorough explanation, it now makes good sense. I tested it and it works nicely!
    Just out of curiosity, I have another question I hope you could answer about the DAC. If my STM32F4 uses a 3.3V supply, and I want a steady DC level from my DAC, not an AC signal, is this possible?

    For example, if I want a level of VCC/2, I know this is 4096/2 = 2048. But how is the DAC configured to accomplish this?

    Thanks again!!!
    Gary


    ReplyDelete
    Replies
    1. Hi Gary, yes, it is possible.
      You can find the equations in my answer above (September 14, 2016 at 9:32 PM).

      You can have the DAC configured the same way as in my code above.
      Here's how you can quickly test with the code above:

      change: #define SINE_RES 128
      to: #define SINE_RES 1

      change: const uint16_t function[SINE_RES] = {...};
      to: const uint16_t function[SINE_RES] = { 2048 };

      It would be nice too see how clean or noisy the output is.
      Maybe you could email me (00xnor@gmail.com) the scope shot if you get to it.

      Thanks,
      Sergey

      Delete
  15. Hi Sergey.
    I am doing DAC sinewave generation and filters. Your example is very understandable. But I don't understand something:
    1. Is "#define OUT_FREQ 5000" 5kHz frequency?
    2. If 5kHz is right, is it DAC sine signal output frequency? Is right?
    How can I check it?
    My filter is FIR low pass filter. I generate filter coefficients in MATLAB. Filter passes 1-1.5kHz and which closed other frequency signal. But it doesn't work. I configured OUT_FREQ is 1000, filter has passed. But I configured OUT_FREQ is 10000, filter has passed, i think it doesn't passed. But I configured OUT_FREQ is 12000, filter hasn't worked. So I think maybe OUT_FREQ is not right.
    3. how do you compute the Trigger frequency? What is the prescaler and autoreload value? where can I configure prescaler and atoreload value?

    please help me.
    Thank you.
    BOLDMAA

    ReplyDelete
    Replies
    1. Hi Boldmaa,

      1. Yes, it is.
      2. Yes, 5kHz is the expected frequency of the sine waveform. You can check it with an oscilloscope.
      However, precise frequency control is impossible in many cases; changing only OUT_FREQ will not work.
      3. The equation for trigger frequency is given in the post. Prescaler divides the APB1 clock which drives the timer (you can check my previous posts about timers and clock tree for more information). Autoreload value is the timer's upper limit (it starts counting from 0 to the autoreload value; when autoreload value is reached, it generates a counter overflow event; then the counter restarts from 0 again).

      1. What kind of waveform are you trying to output? A sine wave?
      2. How many points are you using to construct the waveform?
      3. What is the desired frequency of the waveform?

      Delete
  16. Hi Sergey
    Can you make program for sinvawe generator similar to; https://www.romanblack.com/onesec/SineDDS.htm
    with ability to change phase and amplitude of one channel for fixed frequency for example 10 kHz using STM32F103C8T6.
    Regards
    Ted

    ReplyDelete
    Replies
    1. Hi Ted,

      Yes, I can.
      What precision are you looking for for both phase and amplitude?
      In other words, what application are you targeting?

      Best regards,
      Sergey

      Delete
    2. Hi |Sergey
      0.1 deg step; 0-360 deg, 1mw step; 0-1V
      Regards
      Ted
      korted(at)rogers.com

      Delete
    3. This comment has been removed by the author.

      Delete
  17. Hi Sergey
    0.1deg steps; 0-360ged, 1mV steps, 0-1 V
    Regards
    Ted
    korted(at)rogers.com

    ReplyDelete
  18. Can you please post the other stuff file? I am new to this and I am having some problem generating triangular wave. I would like to refer your code.

    ReplyDelete
    Replies
    1. It's the Standard Peripheral Library from STMicroelectronics.
      You can just add the whole library to your project/environment.

      Delete
  19. Im trying to generate a pure as possible sine wave of 1 Khz, but when i run the original code im getting a 1.9khz signal instead of 5khz. and when i change anything to the OUT_FREQ the signal disappears completely.
    Do you know what could be the cause, thanks in advance

    ReplyDelete
    Replies
    1. 1. Why 1.9khz signal instead of 5khz?

      It is the clock configuration.
      In my post, APB1 clock is configured to be 42MHz. It drives the timer (TIM6) which triggers DMA transfers.
      All the calculations were done based on this (42MHz) initial value.

      #define CNT_FREQ 42000000 // TIM6 counter clock (prescaled APB1)

      Here's the clock tree which you can refer to:
      http://00xnor.blogspot.jp/2014/01/1-stm32-f4-clock-configuration.html

      2. Why signal disappears completely?

      Please read my response to Gary in the comments dated [July 23, 2016 at 6:53 PM].
      He essentially asked the same question.

      Delete
  20. But with the exact same clock
    * SYSCLK(Hz) | 168000000
    *-----------------------------------------------------------------------------
    * HCLK(Hz) | 168000000
    *-----------------------------------------------------------------------------
    * AHB Prescaler | 1
    *-----------------------------------------------------------------------------
    * APB1 Prescaler | 4

    and code used above, i would expect the exact same 5khz result?

    ReplyDelete
    Replies
    1. The goal is to have [TIM6 counter clock] equal 42MHz.

      Below is how [TIM6 counter clock] is derived:

      1: 8MHz external crystal
      2: Main PLL clock is used to drive SYSCLK
      3: M = 8
      4: N = 336
      5: P = 4

      [external crystal] --> [divide by M] --> [times N] --> [divide by P] --> [SYSCLK] --> [AHB prescaler] --> [AHB clock] --> [APB1 prescaler] --> [APB1 clock] --> [TIM6 counter clock]

      If [APB1 prescaler] equals 1, [TIM6 counter clock] equals [AHB clock]
      If [APB1 prescaler] doesn't equal 1, [TIM6 counter clock] equals [APB1 clock] times 2.

      Example:

      [external crystal 8MHz]--> [divide by 8] --> [times 336] --> [divide by 4] --> [SYSCLK 84MHz] --> [AHB prescaler 1] --> [AHB clock 84MHz] --> [APB1 prescaler 4] --> [APB1 timer clock 21MHz] --> [TIM6 counter clock 42MHz]

      Delete
  21. Is it possible to increase the APB1 clock even further?

    Im trying to get a range between 100hz and 10Khz, but I'm unable to get it right with these parameters, best im getting is 300hz-10Khz?
    Do you know what to change to get the range desired?

    ReplyDelete
  22. Hello, Sergey!
    Up in the topic you have mentioned that it is possible to change the output buffer on the go.
    I am trying to execute this process but it just doesn't change a thing.
    Maybe I need to DISABLE some peripheral for buffer_change_process?
    The programm is written as your example, but in the while() cycle I need to do the buffer change.

    ReplyDelete
    Replies
    1. Hello,

      1.
      run the original code to make sure it works with your setup.

      2.
      change const uint16_t function[SINE_RES] = {...};
      to uint16_t function[SINE_RES] = {...};

      this way the data will be placed in RW region instead of RO.

      3.
      modify the data inside while(1) {}
      for example, you could write all zeros to the buffer, sleep a few seconds, then restore the original values, etc.

      This doesn't address synchronization, but should be a good starting point.

      Let me know if it works.

      Delete
  23. According to STM32F407 reference manual RM0090, APB1 timer clock frequency is double of APB1 prescalled output clock when the APB1 prescaller is >1.
    This means that your signal actually has a 10kHz period instead of 5kHz, because the APB1 clock for the timers is 84MHz, not 42MHz.
    My test also supports this.

    Sergey, could you please comment on this?

    ReplyDelete
    Replies
    1. I'm copying my answer to a similar question above.

      Below is how [TIM6 counter clock] is derived:

      1: 8MHz external crystal
      2: Main PLL clock is used to drive SYSCLK
      3: M = 8
      4: N = 336
      5: P = 4

      [external crystal] --> [divide by M] --> [times N] --> [divide by P] --> [SYSCLK] --> [AHB prescaler] --> [AHB clock] --> [APB1 prescaler] --> [APB1 clock] --> [TIM6 counter clock]

      If [APB1 prescaler] equals 1, [TIM6 counter clock] equals [AHB clock]
      If [APB1 prescaler] doesn't equal 1, [TIM6 counter clock] equals [APB1 clock] times 2.

      Example:

      [external crystal 8MHz]--> [divide by 8] --> [times 336] --> [divide by 4] --> [SYSCLK 84MHz] --> [AHB prescaler 1] --> [AHB clock 84MHz] --> [APB1 prescaler 4] --> [APB1 timer clock 21MHz] --> [TIM6 counter clock 42MHz]

      Let me know if this makes sense.

      Delete
  24. thank you very much for such nice and detailed explanation about configuration of trigger and DAC_DMA. i've implemented tone generator using STM32F0 and external flash to hold tones. I'm Using DAC with DMA , and also implemented circular buffer to for tone data. When i use the monotones i can hear pop up sound at startup of tone, but its not case with audio(voice) tones. when i observed the DAC output waveforms, there is glitch(spike) there around 5us and some rogue data around 55us at startup which create pop up sound. Did you observed this in sine waveform with your implementation. if its there then how to suppress it, any suggestion will work, thank you.

    ReplyDelete