ARM ST32F400 - best way to generate a 400Hz sinewave, w/o the CPU doing it all - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: February 07, 2021, 02:08:39 pm
content: What is the simplest way to feed repetitive data to one of the DACs, at about 40kHz?I reckon 100 samples per cycle to get the really clean waveform I am after (and with phase lag under 1 degree, so don't want too much filtering) i.e. 40kHz. Maybe 100 is too conservative but I don't know how to calculate the phase shift due to a lowpass filter which really cleans up the waveform so it looks "perfect" on a scope.I believe there is a way to link the DACs to a bit of RAM via DMA, but would it automatically reset the address pointer?I also need the CPU to periodically rewrite this area of RAM, but to avoid artefacts I would want to put the new data in another area of RAM, and just flip the pointer. Just like one can do with animated video.I also want to do this with both DACs, doing the same since waveform but with different phase shifts, so perhaps just different pointers.In fact, if one can get two DMA channels feeding the two DACs, and have both addresses wrapping around on the same boundaries, and just somehow change the current address pointer value, that would do it.Can one have two DMA controllers feeding from the same RAM block?In software this is easy but avoiding a 40kHz interrupt would be greatThe 32F4xx manual is 2000 pages and this stuff is really obscure. I wonder if anybody has done it and can suggest any tips?

author: hans
author_rank: Super Contributor
timestamp: February 07, 2021, 02:14:28 pm
content: I'm pretty sure that STM32 has circular buffer mode for DMA, you can probably use that to continuously write data the same data buffer to the DAC.If you want to swap the buffer at a determinate time point, you could do it during a DMA half or full transfer complete IRQ. 40kHz is still OK timespan, you have 25us to turn the DMA descriptor(s) around for the next update from the timer.

author: peter-h
author_rank: Super Contributor
timestamp: February 07, 2021, 02:39:25 pm
content: One Q is whether you can change the current address pointer, while the DMA is all set up, without affecting anything else. IOW, change the phase of the sinewave by some number of degrees.Actually thinking about it, it will be necessary in this project to have 0.1 degree steps i.e. 3600 per cycle i.e. 1.44MHz. I think that's too fast for the DACs so I would settle for 0.2deg steps i.e. 720kHz, but that is on each DAC, with both running concurrently, with phase shifted data.Almost certainly not possible under interrupt; would have to use DMA.The data sheet gives a 3us typ. settling time to 10 bits but a) I don't need 10 bits (8 would do) and b) can filter out the glitches since they would be at such a high frequency.

author: JOEBOBSICLE
author_rank: Regular Contributor
timestamp: February 07, 2021, 03:43:15 pm
content: You will likely need to stop and start the DMA channel. Which will take a couple of microseconds if you try and change the address.Or you could regenerate half the DMA buffer every half cycle, experiment with the buffer size and get it as small as possible so you can update the phase with a minimal delay.Another thing to experiment with would be looking at timer based triggering to control the phase delay. There's all sorts of magic you can do on the big F4s/F7s and H7s. Have a look for dac application notes

author: langwadt
author_rank: Super Contributor
timestamp: February 07, 2021, 05:09:23 pm
content: you don't need 3600 samples per cycle to get 0.1 degree, it is not a square wave. Just calculate the waveform for the new angle

author: Tagli
author_rank: Contributor
timestamp: February 07, 2021, 05:17:37 pm
content: F4 series probably have double buffered DMA streams (at least, F407 has it). This allows you to switch addresses while the DMA is running.You can configure DAC to be triggered by a TIM. Then DAC makes the DMA request. I have tried this one. But I guess you can also configure the DMA to be triggered by TIM (not DAC). In this case, TIM makes the DMA request.I suggest to start with low number of samples, like 16 or 32 per period. I used a 32-value table to generate 1 kHz sine waves (32 kHz DAC update rate). It wasn't too smooth, but I think it will be a good starting point.

author: ozcar
author_rank: Frequent Contributor
timestamp: February 07, 2021, 06:38:29 pm
content: Quote from: Tagli on February 07, 2021, 05:17:37 pmYes, each DMA stream has two memory address registers for double buffer mode, and there is a flag in a status register that the hardware updates to indicate which of the memory address registers is currently in use. The one not in use can be updated by software without stopping/disabling the DMA stream.Another way would be to update the data in the non-current buffer, and not keep changing the memory address registers. In that case the only advantage double buffered mode has over circular mode is that the buffers don't have to be contiguous.

author: peter-h
author_rank: Super Contributor
timestamp: February 07, 2021, 07:28:11 pm
content: I spent the afternoon reading various appnotes and peoples' sinewave generation projects. You basically set up a timer to produce the trigger for the DAC conversion, and then you set up a DMA channel to feed the DAC.To vary the phase one would just change the DMA source pointer, setting it to where you want the waveform to start right now.Yes it is a good point that recalculating the whole cycle's points might be a simple solution. The table size depends on the max slew rate of the angle, and how smoothly you want the phase to change. For example if one needs to support 100 degrees per second and wants to do it say in 1 degree increments, then one needs to be able to recalculate the whole cycle's values in 10ms. I don't know how long an 32F417 takes to calculate sin(x). It does a single float mult/div in 1 clock but trig uses doubles and I reckon it will be a few us at least. One could test that easily enough... Of course one could also precalculate the whole table and just copy the relevant portion, into the area where DMA picks it up.The other point I noticed is that if you want to generate exactly 400Hz (or exactly anything which is not a multiple of the various available divisors) then the table will need to have the x steps spaced appropriately to achieve that. For example somebody was trying to generate 44.1kHz (audio) and since his input data was fixed, he could not do it at all other than choosing a suitable CPU main clock.I will need to work out how close one can get the timer trigger to 40kHz. If the nearest one can achieve is say 35kHz then the table will need to have the samples spaced by 40/35 of 0.1 degree. I have APB1 timer=84MHz & APB2 timer=168MHz.There are some obscure conditions where the DMA can underrun but I don't get how that can happen.

author: langwadt
author_rank: Super Contributor
timestamp: February 07, 2021, 07:49:20 pm
content: Quote from: peter-h on February 07, 2021, 07:28:11 pmif the DMA can do different strides you could also use a bigger table and pick the start address to set the fine angleI don't know of a cpu that can do a division in a single cycle, but a simpler sine approximation should more than enough for a 12bit DAChttp://www.leonheller.com/Docs/Chapter_4.pdf

author: peter-h
author_rank: Super Contributor
timestamp: February 07, 2021, 08:13:44 pm
content: You are right - 14 clocks for a dividehttps://developer.arm.com/documentation/100166/0001/Floating-Point-Unit/FPU-functional-description/FPU-instruction-set-table?lang=enOne normally arranges things to multiply instead of dividing.

author: Tagli
author_rank: Contributor
timestamp: February 07, 2021, 08:47:07 pm
content: You don't need to calculate sin() from the scratch. You can use a pre-calculated table and than just scale & offset it. Of course, this must be applied to each element of the table, but still faster than calculating sin() from the scratch.CMSIS provides a DPS library, which can work on floating and fixed point numbers. F4 devices already have FP support, but I think it may worth trying 16-bit fixed point numbers, if your application is performance critical. M4 cores offer some special instructions which can operate on 2 x 16-bit or 4 x 8-bit numbers at the same time. I'm not sure, but I think 8 & 16 bit fixed point numbers (Q7 & Q15 as CMSIS calls them) may benefit from these instructions. Most of the CMSIS DSP library functions accept arrays and I believe they are optimized to use all instructions available.But again, first just get a low quality sin wave. You can make speed and resolution optimizations later.

author: ozcar
author_rank: Frequent Contributor
timestamp: February 07, 2021, 08:59:36 pm
content: Quote from: peter-h on February 07, 2021, 07:28:11 pmYou should be able to get exactly 40kHz. Well, as accurate as your 168MHz is.

author: hans
author_rank: Super Contributor
timestamp: February 08, 2021, 09:05:55 am
content: Quote from: langwadt on February 07, 2021, 07:49:20 pmYou could also offload the custom stride operation to software, in which you copy the samples for the DAC buffer from a huge LUT.The question is: how large does that LUT need to be. If my understanding is correct, the TS wants 0.1 degree steps (3600 steps) with 100 accurate steps per cycle, we're talking about a LUT with 360k pts (16-bit may fit in the 1MB F407 device, won't fit in the 512K FLASH device).At some point we're closing in at a DDS algorithm, in which a phase accumulate will be used for sin(x). There are also dedicated chips that do that, however, their sin ROM may not be high fidelity enough for 100 updates per period (not sure if those chips also do linear interpolation).Perhaps the best way forward in this is to benchmark how fast sin(x) is on the STM32F407 chip. And indeed, also verify the spectral output of that sin(x) is sufficiently clean, as some fast sin(x) libraries use a limited size LUT with interpolation.I don't know how much CPU time TS has for this (considering that interrupts at 40kHz was already too much..). A quick web search reveals to me that a sin(x) calculation seems to be possible in the order of 124 cycles or so (assuming that's accurate, which is a big statement for something 'found on the internet'). For 40k DAC samples/sec, it means ~5M cycles/sec consumed for the sin(x) table calculation. That's a good chunk, but not impossible for that chip..

author: langwadt
author_rank: Super Contributor
timestamp: February 08, 2021, 09:13:19 am
content: Quote from: hans on February 08, 2021, 09:05:55 amthe sine only needs to be as good as the "12bit" DAC, and I think the CMSIS arm_sin_q15() is ~30 cycles

author: peter-h
author_rank: Super Contributor
timestamp: February 08, 2021, 01:56:11 pm
content: For clarity, I need to be able to phase shift the waveform with 0.1deg resolution, and on what one might call "fast slew" (the input phase value changing by say 100 deg per second or more) generate 100 one degree steps per second i.e. 10ms per step. For slow input slew I would change the phase in 0.1 degree steps.A 40kHz interrupt would easily be fine on the 32F417 (168MHz clock) but I have other stuff running e.g. USB and ethernet and that code (ex ST dev kit and originally full of bugs) not only uses interrupts but is huuuge, so I don't want to set up an interrupt which will either be stomped on by a higher priority one, or have it stomp on a lower priority one which then breaks something, perhaps only in rare conditions.Hence DMA and a circular buffer containing 3600 (3599 actually) 12 bit values; basically 3599 uint16 values. I assume the DMA can pick off 16 bits at a time for the DAC, not just 32The DAC would just use the bottom 12. I know there is an 8 bit mode which - assuming the DMA can feed bytes - would halve the memory requirement, but I would prefer at least 10 bits for the amplitude because it makes filtering much easier.The time to generate that table is irrelevant; it would be done at system startup. I can just literally use sin(x) with x going 0 to 2xPI in 3600 steps.The important thing is that one can set the DMA source address to anywhere within the buffer *while* DMA is running.If the DMA trigger comes from the DAC, and the DAC is triggered by a timer, then one might have to wait until the timer has just gone off and then dive in and change the pointer. Or maybe the DMA controller doesn't care, but the change must be atomic otherwise you would get a duff value out of the DAC, for that ~25us.It is counter-intuitive; I would have expected the *DMA* to be triggered by a timer and just feed a word to the DAC. But it doesn't matter.

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 14, 2021, 10:34:01 am
content: How fast does the signal phase need to be updated? Definitely the approach would be to use a timer-driven RAM->DAC DMA.There's something that doesn't make sense to me. If you are feeding the DAC at 40KHZ to make a 400Hz signal, then you only use 10 samples per full wave?That won't achieve 0.1º precision ever? 360º/10 = 36º steps?You need sending 3600*400 = 1.44MHz sample rate!. Should be possible if correctly using the MCU resources.(Edit: I saw it was already said in the comments, sorry)I had an idea that could work nicely while barely using cpu power. Since the DMA won't cause collision (there's DMA arbitration), we can use 2 dma.One for the DAC and another for doing the phase.Set a full wave array in ram, ex. waveData. Leave that buffer as the reference to copy from.Set a second buffer for the DMA->DAC. ex. outData.Set the DAC DMA in single buffer, circular mode. You will never stop it, so the phase drift will be minimal.You could try storing the wave data in flash to save RAM, but keep in mind that flash access is way slower than ram. Could be fast enough.But if the ram is not constrained in your application, it's the way to go.Use a second DMA in memory-memory mode. Apparently memory-memory DMA can't be used in circular mode?(Says forbidden in the manual).So, at first, copy the array to the DAC DMA buffer. Ex. set DMA to copy from waveData to outData, 3600 len. Wait for transfer to complete.Start the DAC DMA. Set a timer to run at 1.44MHz and attach it to the DAC. Now a constant 400Hz signal should appear at the DAC output with 0º phase .To change the phase, since the said problem (no circular mode in memory-memory mode?) you could split the memory-memory transfer in 2 DMA transactions.Ex. You want 45º phase. Since 0.1º is 1 position in buffer address, we know 45º starts at waveData[450-1]. Set a DMA transfer from waveData[450-1] to outData, length= (3600-450).This would copy the first chunk. After the DMA completes, instantly issue another DMA transfer, this time from waveData to outData[3600-450], length=450.Now the DAC buffer has shifted 45º. The offset will be applied almost instanty at the DAC output. Since you didn't stop the DAC DMA, neither changed the pointer, the shift precision is seamless.Ex.:Wave data[]=[0,45,90,135,180,225,270,315,360]OutData[]= [45,90,35,180,225,270,315,360,45]Only take care about the DAC DMA pointer in the way that you always copy data enough data in the first transfer (Or slighly delay it to start the transfer in a proper moment), to ensure that the DAC DMA doesn't transfer "old-new-old" data under no circumstance before the second transfer is not complete.That way the DAC shouldn't pick up random data. The memory-memory DMA transfer will be much, much faster than 1.44MHz. Easily 30x that speed at 100MHzSet the DAC DMA priority higher than the memory DMA to avoid jittering.You must use 16bit DMA, since the DAC uses 12 bit, and you need 0.1º resolution (1 DAC word). If you could use 0.2º, then a 32-bit DMA transfer would double the transfer speed.But I think the CPU is powerful enough. Even for 0.05º or less.If the changes can applied with a slight delay (worse case, one period delay, 25mS) you could always update the dac buffer using the dac dma half transfer complete.

author: langwadt
author_rank: Super Contributor
timestamp: February 14, 2021, 05:55:12 pm
content: just for the fun of it,https://github.com/langwadt/singenusing the CMSIS fast sin approx the ISR is ~700ns

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 15, 2021, 04:03:31 am
content: If you were planning to it real time, that's too slow.At 1.44Mhz it's 694nS/sample.

author: langwadt
author_rank: Super Contributor
timestamp: February 15, 2021, 09:10:28 am
content: Quote from: DavidAlfa on February 15, 2021, 04:03:31 amthat's not how it works, 1.44MHz sample rate is extreme over kill, you do not need that to get 0.1 degree resolution, It is not a square wave. The phase is as much contained in the amplitude as in the timing

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 15, 2021, 10:57:16 am
content: Exactly, it's not a square wave. It's a sine wave.A square wave would be way easier with pwm.If you want to accurately generate 0.1° you need then to split that sine wave in at least 3600 parts.And if you want a 400hz signal... How good are your maths?This is not like pcm audio where you have an oversampling dac doing the filtering for you. So you need to increase sample rate to make a quality signal.If the device has capability of detecting 0.1° differences (Which looks outstanding to me) then it will be very sensitive to noise.You would have to see ex. CD audio signal without filtering. Absolutely terrible.An alternative is to use a squarewave instead DAC, and pass it thorough a lc filter tuned for 400hz, the output will be a perfect sine.But it will filter out the changes in the phase, it won't be as crisp. Ex. QPSK modulation, I highly doubt it could work.

author: langwadt
author_rank: Super Contributor
timestamp: February 15, 2021, 12:04:02 pm
content: Quote from: DavidAlfa on February 15, 2021, 10:57:16 amno, you don'tQuote from: DavidAlfa on February 15, 2021, 10:57:16 am40kHz sample rate is more then plenty for 400HzQuote from: DavidAlfa on February 15, 2021, 10:57:16 amwhy try to make a square wave into a sine when you can start with a sine?

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 15, 2021, 12:11:46 pm
content: Quote from: langwadt on February 15, 2021, 12:04:02 pmIf you're correct, show the point instead talking so much!Do you really think that a 400Hz sine signal sampled at 40KHz (10 samples/wave) is good for 0.1º phase precision?Ouch ok, I had the coffee too late today. 100 samples/wave!Anyways, that gives 3.6º steps. Filtering the output could be ok, or not, depending on the application.

author: RoGeorge
author_rank: Super Contributor
timestamp: February 15, 2021, 01:34:51 pm
content: Best way to generate a 400Hz sinewave, w/o the CPU doing it all would be a 400 Hz, 50% square digital output from a PWM pin, followed by an analogue filter.

author: langwadt
author_rank: Super Contributor
timestamp: February 15, 2021, 05:09:00 pm
content: Quote from: DavidAlfa on February 15, 2021, 12:11:46 pmhere's the the ten 12bit samples for a 400Hz sine wave sampled at 4kHz0 1204 1948 1948 1204 0 -1204 -1948 -1948 -1204here's the same sine wave shifted 0.1deg4 1207 1949 1947 1201 -4 -1207 -1949 -1947 -1201

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 15, 2021, 05:23:57 pm
content: Yes, but you must compute all, every time. The main thing was to not have to do that.So in that case you must have these 3600 steps stored somewhere.

author: langwadt
author_rank: Super Contributor
timestamp: February 15, 2021, 05:44:05 pm
content: Quote from: DavidAlfa on February 15, 2021, 05:23:57 pmwhy bother? a 700ns interrupt every 25us is less the 3% of the cpu

author: DavidAlfa
author_rank: Super Contributor
timestamp: February 16, 2021, 11:40:49 am
content: If it really does ok with 100 samples/wave...And you must add the interrupt overhead too.Anyways, in the same case, 100 samples in the dma will be a lot faster. And only requiring 400bytes of ram.