2025-03-29 7:37 PM - edited 2025-03-29 7:45 PM
I'm on STM32F103CBT6.
I have a TI DAC8564 on SPI2. It has a 24-bit input register, requiring me to TX a pair of 16-bit values (extra 8 low bits are ignored) while SYNC is low. After sending, I have to toggle SYNC high and then low to begin a new write cycle.
I want to perform this write sequence (TX 16-bit value, TX 16-bit value, SYNC high, SYNC low) using DMA, ideally all synchronized by a single timer (currently TIM2). The timer will run at 160kHz and send a DAC buffer consisting of 64 samples of 40kHz audio for each of 4 channels, each sample being a 16-bit pair.
I've had success using DMA to send the SYNC high/low to GPIO at defined phases of the timer cycle. I'm now looking at using DMA to write the pair of 16-bit values at two other phases of TIM2. RM0008 lists TIM2_CH2 and TIM2_CH4 as available requests for DMA1_Channel7. Can a single DMA channel receive requests from two different channels of the same timer, or are these mutually exclusive?
I found this line from the docs a little hard to interpret:
The 7 requests from the peripherals (TIMx[1,2,3,4], ADC1, SPI1, SPI/I2S2, I2Cx[1,2] and
USARTx[1,2,3]) are simply logically ORed before entering the DMA1, this means that only
one request must be enabled at a time.
I also see this in the libs:
/**
* @brief Enables or disables the TIMx�s DMA Requests.
* TIMx: where x can be 1 to 8 to select the TIM peripheral.
* TIM_DMASource: specifies the DMA Request sources.
* This parameter can be any combination of the following values:
* @arg TIM_DMA_Update: TIM update Interrupt source
* @arg TIM_DMA_CC1: TIM Capture Compare 1 DMA source
* @arg TIM_DMA_CC2: TIM Capture Compare 2 DMA source
* @arg TIM_DMA_CC3: TIM Capture Compare 3 DMA source
* @arg TIM_DMA_CC4: TIM Capture Compare 4 DMA source
* @arg TIM_DMA_COM: TIM Commutation DMA source
* @arg TIM_DMA_Trigger: TIM Trigger DMA source
* NewState: new state of the DMA Request sources.
* This parameter can be: ENABLE or DISABLE.
* @retval None
*/
void TIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState)
But I'm unsure whether "any combination" is just to allow the timer's channels to request multiple DMA channels, not for the timer's channels to independently request the same DMA channel.
Thanks!
Solved! Go to Solution.
2025-04-05 2:08 AM - edited 2025-04-05 2:09 AM
> writing new firmware for an old batch of assembled hardware
Let me preach here on importance of making extensive proof-of-concept testing on the key elements of the designed device early on in the design process... ;)
> I'm wondering just how unsafe,
The "undocumented" level of unsafe. If you can live with "I've tried and it works", or even could go for "I've tried which was the limit of the two triggers being set apart, until it started to fail, and in the real application I set them significantly further", well, maybe it's workable. I don't know and I personally wouldn't use it in anything for which I'm paid for (read, I am no stranger to doing weird things, including questioning the available documentation; but at the same time I am aware of the consequences, technical and organisational/legal).
> the "right" way of managing spacing is to use the SPI2 TXE DMA request instead of a timer
My concern here is not that the SPI's DR wouldn't be able to "absorb" the consecutive writes; but that the DMA could not be capable of distinguishing or otherwise handling two consecutive triggers (requests). The key problem is, that the DMA request mechanism is not described in sufficient detail. For example, in the dual-port DMA (which is NOT in the 'F1, I am writing about it only to give illustration of what sort of problems may we be dealing with), if you remove the trigger source amidst an ongoing DMA transfer (again this is NOT the issue here, again it's just an illustration), the given DMA "channel" will lock up.
The single-port DMA in the 'F1 is much simpler, but for example there's a latch catching the DMA trigger (even if that is probably part of timer), being reset by DMA itself when the transfer finishes. Now with the requests being ORed, question is, how are these latches reset - most likely all ORed requests' latches are reset at the same time. It would then mean, that if a new requests comes through a different input to that OR during a previous requests still being handled, that new request's latch would be reset (and thus DMA transfer missed) when that previous DMA transfer is handled. There may be other similarly nuanced issues, too.
> I had the impression that SPI2->DR can only be the target of 1 DMA channel at a time. Is this wrong?
Yes it's wrong, the memory/peripheral pointers of individual DMA channels are not interlocked in any way. In the single-port DMA in 'F1, the channels work consecutively (there's an arbiter which choses which channel is going to work next), and they have only one port to the bus matrix through which they read/write data from/to the system.
Any limitation in this regard would again come only from the trigger/request mechanism. For example, if you trigger a channel from SPIx TXE, you must write to SPIx_DR from that channel to clear this request (apparently only triggers originating in timer are latched - this again is undocumented but experiments prove this behaviour), otherwise the SPIx TXE never gets cleared thus given DMA channel would run forever. Similarly, would there be one channel triggered from SPIx TXE writing to SPIx_DR, and another one triggered from timer writing to SPIx_DR, the exact outcome would be dependent on exact timing of timer and SPIx TXE together with the very intimate details of the arbitrator in DMA, probably not something you would want to have.
JW
2025-04-03 11:41 AM
Any input appreciated, thanks!
2025-04-03 1:19 PM - edited 2025-04-03 1:19 PM
1. You are surely aware of the fact that 'F103 not bought through a reputable source are most likely to be counterfeited, where any knowledge related to nuances of the original 'F103 is moot.
2. It may be much better to generate the sync signal directly by the timer (or by several timers running mutually synchronously), rather than triggering DMA-to-GPIO transfers by the timer just to generate a pulse on a single pin
3. I haven't had a look at the DAC's DS, but what you describe sounds much like some of the I2S modes (PCM?), so perhaps check that out. In 'F1xx, only the models with 256kB and up have the I2S mode in SPI. Other STM32 families can have better mix of peripherals than the ancient 'F1 family. Also, there are dedicated audio DACs out there, which may interface directly through I2S.
4. newer STM32 have SAI module, which may be even better suited to generate directly the signals mix you need
5. TIM_DMACmd() sounds like being from SPL, which is open source, so simply look up what does it do. My guess is that it writes TIMx_DIER bits, i.e. gates the DMA triggers (requests) at the source (in timer).
6. I don't know if two timer trigger sources may be used simultaneously. RM says no. It may work but there may be undocumented constraints (e.g. one trigger must not occur while processing of the other trigger is still under way). It's not a good idea to use undocumented features.
7. However, you certainly can use two DMA channels, e.g. channel 1 triggered by TIM2_CH3 and channel 7 triggered by TIM2_CH4.
JW
2025-04-04 9:47 AM - edited 2025-04-04 9:49 AM
1. Good to know, thanks! I believe these were sourced from Mouser.
2. I unfortunately can't rewire (writing new firmware for an old batch of assembled hardware), so I have to output sync on PB12, which I believe doesn't have any timer output options.
3. Yeah, I'm on medium-density so no I2S
5. Correct, it sets TIMx->DIER
6. Fair. I'm wondering just how unsafe, because I can get better phase control over the spacing of SPI2 TX if I can trigger DMA from two different trigger sources. Obviously the "right" way of managing spacing is to use the SPI2 TXE DMA request instead of a timer, but I need to ensure that SYNC occurs after each TX pair, instead of a third TX.
7. I had the impression that SPI2->DR can only be the target of 1 DMA channel at a time. Is this wrong?
Thanks again!
2025-04-05 2:08 AM - edited 2025-04-05 2:09 AM
> writing new firmware for an old batch of assembled hardware
Let me preach here on importance of making extensive proof-of-concept testing on the key elements of the designed device early on in the design process... ;)
> I'm wondering just how unsafe,
The "undocumented" level of unsafe. If you can live with "I've tried and it works", or even could go for "I've tried which was the limit of the two triggers being set apart, until it started to fail, and in the real application I set them significantly further", well, maybe it's workable. I don't know and I personally wouldn't use it in anything for which I'm paid for (read, I am no stranger to doing weird things, including questioning the available documentation; but at the same time I am aware of the consequences, technical and organisational/legal).
> the "right" way of managing spacing is to use the SPI2 TXE DMA request instead of a timer
My concern here is not that the SPI's DR wouldn't be able to "absorb" the consecutive writes; but that the DMA could not be capable of distinguishing or otherwise handling two consecutive triggers (requests). The key problem is, that the DMA request mechanism is not described in sufficient detail. For example, in the dual-port DMA (which is NOT in the 'F1, I am writing about it only to give illustration of what sort of problems may we be dealing with), if you remove the trigger source amidst an ongoing DMA transfer (again this is NOT the issue here, again it's just an illustration), the given DMA "channel" will lock up.
The single-port DMA in the 'F1 is much simpler, but for example there's a latch catching the DMA trigger (even if that is probably part of timer), being reset by DMA itself when the transfer finishes. Now with the requests being ORed, question is, how are these latches reset - most likely all ORed requests' latches are reset at the same time. It would then mean, that if a new requests comes through a different input to that OR during a previous requests still being handled, that new request's latch would be reset (and thus DMA transfer missed) when that previous DMA transfer is handled. There may be other similarly nuanced issues, too.
> I had the impression that SPI2->DR can only be the target of 1 DMA channel at a time. Is this wrong?
Yes it's wrong, the memory/peripheral pointers of individual DMA channels are not interlocked in any way. In the single-port DMA in 'F1, the channels work consecutively (there's an arbiter which choses which channel is going to work next), and they have only one port to the bus matrix through which they read/write data from/to the system.
Any limitation in this regard would again come only from the trigger/request mechanism. For example, if you trigger a channel from SPIx TXE, you must write to SPIx_DR from that channel to clear this request (apparently only triggers originating in timer are latched - this again is undocumented but experiments prove this behaviour), otherwise the SPIx TXE never gets cleared thus given DMA channel would run forever. Similarly, would there be one channel triggered from SPIx TXE writing to SPIx_DR, and another one triggered from timer writing to SPIx_DR, the exact outcome would be dependent on exact timing of timer and SPIx TXE together with the very intimate details of the arbitrator in DMA, probably not something you would want to have.
JW
2025-04-07 1:37 PM - edited 2025-04-07 1:43 PM
> Let me preach here on importance of making extensive proof-of-concept testing on the key elements of the designed device early on in the design process... ;)
Definitely true. I'm actually writing third-party firmware for an open-source synthesizer module sold on the mass market, so I'm pretty constrained. Maybe one day I'll start fresh with new hardware, but I don't have the EE skills yet.
> The "undocumented" level of unsafe.
Yeah, makes sense.
> The single-port DMA in the 'F1 is much simpler, but for example there's a latch catching the DMA trigger (even if that is probably part of timer), being reset by DMA itself when the transfer finishes. Now with the requests being ORed, question is, how are these latches reset - most likely all ORed requests' latches are reset at the same time. It would then mean, that if a new requests comes through a different input to that OR during a previous requests still being handled, that new request's latch would be reset (and thus DMA transfer missed) when that previous DMA transfer is handled. There may be other similarly nuanced issues, too.
That's thought-provoking. I could imagine that a release is sent to all of the DMA channel's configured request sources whether or not they have an active request. That could explain what the manual is implying about the OR.
> For example, if you trigger a channel from SPIx TXE, you must write to SPIx_DR from that channel to clear this request (apparently only triggers originating in timer are latched - this again is undocumented but experiments prove this behaviour)
Whoa, that's definitely good to know. I was considering a weird scheme where one DMA channel is requested by TXE, but then instead of writing to DR, starts a one-shot timer that kicks of a sequence of DMA operations on other channels.
> Similarly, would there be one channel triggered from SPIx TXE writing to SPIx_DR, and another one triggered from timer writing to SPIx_DR, the exact outcome would be dependent on exact timing of timer and SPIx TXE together with the very intimate details of the arbitrator in DMA, probably not something you would want to have.
In my situation, I plan to ignore TXE completely and just rely on timer phase / SPI baud rate to clear out DR before the next DMA transfer. Hopefully that means I have one less problem if I'm not relying on TXE. I've read some scary stuff about SPI lockup, but it seems like that's mainly in RX situations.
2025-04-14 3:00 PM - edited 2025-04-14 3:09 PM
I ended up doubling the timer freq to 320kHz to match my desired SPI TX frequency, and using a NOOP-padded buffer for the SYNC GPIO toggles to place them between SPI pairs. This worked and avoided the need to share a DMA channel between two 160khZ timer channels (my original idea) or share SPI->DR between two 160kHz DMA channels (@waclawek.jan's mention). I ended up not needing direct phase control over SPI -- I have a ~2x safety margin on the duration of SPI TX, and that's been stable. DMA is cool when it works.
I initially had issues with the startup sequence being sensitive to timing/cycle-count, resulting in the DAC never receiving commands. I fixed this by preventing my main loop from filling the buffer before the first DMA HT/TC interrupt fires. I don't understand why that was necessary, since (in the event of CPU exhaustion) it's still entirely possible for the code to end up writing to the buffer half that DMA is currently consuming, and this only causes a brief DAC glitch, rather than a permanent DAC failure.