cancel
Showing results for 
Search instead for 
Did you mean: 

How to generate a sine wave using DAC with STM32F410RB, using register programming?

overbite8934
Associate

I want to generate a sine wave using my STM32F410RB with DAC. I looked up the web for answers but I couldn't find one without using the HAL library. I want to achieve this using register programming only. Your help is highly appreciated. Thanks 🙂

6 REPLIES 6
Danish1
Lead II

There are quite a few steps in that process. And not all of them involve HAL.

What are you struggling with? What works?

If you choose not to use HAL (like me) you must read and put a lot of effort into understanding the Reference Manual for your stm32. This is complicated because stm32 are amazingly powerful devices and the RM includes all the options, features and controls. In other words it is proper documentation.

(Part of my dislike of HAL is that I have never seen adequate documentation for HAL.)

I am just a week into my journey with STM32 programming. Initially, I started the programming using HAL unknown of the fact that HAL is, in fact, a library to make things easier. From there on, I switched to getting on with the bare metal programming.

I have been trying to understand the Reference Manual for DAC. I am also referring to this Application Notes document: https://www.st.com/resource/en/application_note/an3126-audio-and-waveform-generation-using-the-dac-in-stm32-products-stmicroelectronics.pdf. Section 2.1 is about sine wave generation using DAC. 

I am not much familiar with the DAC module. So, I'm struggling to ascertain what steps lead me to the output I desire. The document (and also other sources I referred to) use DMA for transfer, which is another thing I am much less familiar with. I'm guessing I would need to understand the raw concepts first, and dive deeper into the Reference Manual.

Blinky works?

> what steps lead me to the output I desire

1. Reading.
2. Experimenting.
3. Perseverance.

So, take a deep breath and

- read the DAC chapter in RM and write a program which - similarly to blinky - outputs two different values to DAC in a loop, with delays in between. GPIOA and DAC clocks need to be enabled in RCC; PA5 needs to be set as Analog in GPIOA_MODER; DAC in its control register needs to be enabled with all other bits left at their default state (i.e. 0) (start with internal buffer enabled, unless you have an external buffer, to avoid effects related by loading the high-impedance "native" DAC output, just beware of the buffer not being rail-to-rail). Write different values to DAC_DHR12R1 and put delays in between. Observe output on oscilloscope.

- try different DAC values to see the effect on output. You can try to do a multi-level experiment. You can stop the program and experiment by writing directly to DAC registers in debugger.

- modify this program so, that you set DAC_CR.TSEL1=0b111 for software trigger and set DAC_CR.TEN1 to enable trigger, and in program (or in debugger) observe the effect by setting some output level (A), delay, then set other output level (B) and set the DAC_SWTRIGR.SWTRIG1 bit, delay, etc. with other levels so that you see changes. Level (A) should not be output at all, as there was no trigger to output it from DAC_DHR12R1 to DAC_DOR1. Again, this experiment can be done entirely in debugger, too.

- now for the timer - there's only one timer trigger for DAC in 'F410, TIM5_TRGO. First, generate some basic PWM with TIM5: Enable TIM5 clock in RCC.  Set some TIM5_CHx pin to AF in GPIOx_MODER and select proper AF in GPIOx_AFR[] (see datasheet for AF selection).  Set TIM5_ARR to desired period; TIM5_CCRx to desired PWM pulse width; in TIM5_CCMRx leave CC1S at default 0b00 for Output Compare and set OC1M to 0b110 for PWM1 mode; set TIM5_CCER.CCxE to enable output; set TIM5_CR1.CEN to run the timer's counter. Observe on chosen pin the PWM output, experiment with TIM5_CCRx to see the pulse width changing (this latter can be again done in debugger - you can experiment also with TIM5_ARR to see effect on frequency, but beware, if you do it on running timer, you should have ARR preload enabled by setting TIM5_CR1.ARPE, otherwise if you set ARR to value below current CNT, output gets lost until CNT reaches 0xFFFF'FFFF).

- now use TIM5 to trigger the DAC - first, set TIM5_CR2.MMS = 0b010 for Update, so upon each TIM5 counter rollover a trigger is generated on the internal TIM5_TRGO signal. In DAC, set DAC_CR.TSEL1=0b011 for TIM5_TRGO trigger (while DAC_CR.TEN1 is set). In program, in loop set various (e.g. random or increasing) values to DAC_DHR12R1 and observe on oscilloscope, that DAC output changes only at the edge of TIM5_CHx which corresponds to the Update (TIM5_CNT rollover).

At this point, you could generate the sinewave by polling: in main loop, check if DAC_DOR1 == DAC_DHR12R1, which means, that DAC has been triggered and transferred the value from holding register to output register (there's no directly observable flag for this in DAC, but internally this is the event which will in next step trigger the DMA transfer); and at that moment transfer the next value from the sinewave values table to DAC_DHR12R1.

- now for the DMA part: Enable DMA clock in RCC. In DMA chapter of RM, check, which Stream has the DAC trigger (there's only one here, although for some DMA requests/triggers there may be more). For that given Stream, set address of the sinewave values table (an array of uint32_t[] !) in DMA_SxM0AR, set address of target DAC register (DAC_DHR12R1) in DMA_SxPAR, set number of elements in sinewave values table in DMA_SxNDTR, (you don't need to touch DMA_SxFCR in this experiment); and in DMA_SxCR, set CHSEL to the number of Channel which corresponds to the DAC trigger from the DMA requests table, set PSIZE to 0b10 for word (32-bit) (you can also set MSIZE for 32-bit, even if it's ignored, for that's a good practice not to be forgotten in case FIFO is used where it does matter), set MINC so that the pointer for sinewave table is incremented, set CIRC for circular mode (i.e. DMA working "forever"), set DIR to 0b01 for memory-to-peripheral direction, and finelly set EN to enable the stream  (if you'd enable this channel repeatedly, you'd need to clear the corresponding Transfer-Complete flag in DMA_LISR/DMA_HISR by setting the corresponding bit in DMA_LIFCR/DMA_HIFCR before enabling the Stream, but if this setup is just after reset, that bit is clear). Leave all other fields in DMA_SxCR as zero. As in any other cases, don't perform these writes as successive read-modify-write (RMW) operations, but calculate the resulting value and perform one single write.

- in DAC, set DAC_CR.DMAEN1

- lean back and enjoy.

JW

Starting bare metal after week show your "expert" level. Maybe on 8bit, but 32-bit RISC is other pc of hw.

HAL isnt perfect for all, but is clean and readable for primary code parts. Refer to examples ... compare LL HAL

STM32CubeF4/Projects at master · STMicroelectronics/STM32CubeF4 · GitHub


@MM..1 wrote:

Starting bare metal after week show your "expert" level.


I didn't quite get what you meant by this.

I think what I said wasn't clearly stated, so I apologize for that. I meant to say that I started with HAL, and the went on to bare metal the day after. And now it's been a week of me learning an STM32.


@MM..1 wrote:

HAL isnt perfect for all, but is clean and readable for primary code parts. Refer to examples ... compare LL HAL


Also, thank you for this. I'd definitely have a good look at this!

Thank you so much, JW! This is gonna help a ton : )