cancel
Showing results for 
Search instead for 
Did you mean: 

Hardware-triggered spi?

sb_st
Senior

Hi!

I’m working on a motor controller design that utilizes an stm32f4 and a magnetic angular position sensor which communicates via spi.

My design currently performs current sensing; I use channel4 of timer1 to trigger ADC reads near the center of my center-aligned UVW PWM period.

This works great so far, but now I’m trying to think through how/when to schedule SPI reads of my angular sensor. I can straightforwardly perform an spi read when the ADC conversion completes, as part of my inner torque control loop. Is this reasonable to do? Is there a better way? It seems like the best time to touch off such a read is at the same time as when I trigger my ADCs. But I can’t quite figure out how I might do this.

Thanks!

1 ACCEPTED SOLUTION

Accepted Solutions

To try to close the loop on this thread: I eventually discovered that the reason the above code doesn't quite work is because the DMA controller that tim4 uses isn't connected to the peripheral bus on which SPI1 resides. As such can't "talk" to each other on an f4, which is specific to that MCU family. I hadn't really bothered to deeply internalize this (because I've not yet experimented with having one peripheral talk to another). 

Switching to use a timer with a DMA controller that CAN talk to AHB1 (which is where spi1 lives) allows the code above to work fine. In my case, this could be either timer1 or timer8. 

All in all, a very neat trick that I'm glad to have learned!

Screenshot 2026-01-12 at 11.04.58 AM.png

View solution in original post

3 REPLIES 3
TDK
Super User

If SPI is in two-way mode, you can trigger a transaction by a write to SPIx_DR. Any DMA trigger can do this, such as a timer channel or ADC complete. You may need to chain multiple triggers if the transaction is more than one word.

For example, set up a transfer from memory to SPIx_DR when TIM1->CH4 gets hit. Might need a different timer channel since you can only do one thing per trigger, but that's the idea.

If you feel a post has answered your question, please click "Accept as Solution".

Thanks so much! This is a new idea to me, and it seems pretty neat. I'll try to say that all back to you to make sure I understand:

  • DMA is enabled on a timer, and one of the channels can be configured to, say, perform a "memory to peripheral" transaction when the channel's OC gets hit. 
  • SPI itself can be set up to use DMA, so that when the Timer DMA writes, say, a read command to the SPI_DR register, it 'kicks off' an SPI transaction. 
  • The SPI DMA can write the received data to a location in memory. 

With some help from an AI I think I understand the ins and outs of doing this, but I am struggling to implement it correctly. I'm curious if an example exists that I could refer to for this?

What I'm doing:

  • I set up a separate timer, timer4, as a slave timer to timer1. Channels 1 and 2 are set to generate a PWM "pulse" when this timer is triggered. There is a slight delay between the two pulses (which I'll explain in a moment). 
  • SPI is configured to 16-bit mode full duplex
  • SPI and TIM DMA are all set to circular mode, no memory increment. 
  • On the Channel1 PWM pulse, I have an interrupt callback that pulls my sensor CS pin low
  • Then, I configure this DMA trickery as follows:

 

uint32_t angle_sensor_read_cmd = 0x0000;
uint32_t sensor_data;

// Arm the SPI Rx DMA
HAL_DMA_Start(&hdma_spi1_rx,
              (uint32_t)&hspi1.Instance->DR,               
              (uint32_t)&sensor_data, 1);

__HAL_DMA_ENABLE_IT(&hdma_spi1_rx, DMA_IT_TC);

// Arm the timer channel2 DMA
HAL_DMA_Start(&hdma_tim4_ch2,
              (uint32_t)&angle_sensor_read_cmd,
              (uint32_t)&hspi1.Instance->DR, 1);

SET_BIT(&hspi1.Instance->CR2, SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN);
__HAL_SPI_ENABLE(&hspi1);
__HAL_TIM_ENABLE_DMA(&htim4, TIM_DMA_CC2);

HAL_TIM_PWM_Start_IT(&htim4, TIM_CHANNEL_1); // First, pull down CS
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_2);    // Second, start the SPI transaction via DMA

When I run this code, I can see my HAL_TIM_PWM_PulseFinishedCallback() being called (which pulls CS low), and I can see what appears to be a reasonable value show up in SPI1->DR. But my HAL_SPI_RxCpltCallback() is never called, and the value I see in SPI1->DR never seems to be transferred back out to my sensor_data buffer.

I've tried checking for DMA overflow or error bits, none are set. I will keep banging on this to see if I can make it work, because it does seem like quite a useful technique to be able to leverage. But I worry I am strolling deep into the unnecessarily-complicated troubleshooting weeds and might be overlooking something simple here, so if anyone spots something I'm doing that's obviously wrong or has an example I can check out, I would be very grateful!

 

 

To try to close the loop on this thread: I eventually discovered that the reason the above code doesn't quite work is because the DMA controller that tim4 uses isn't connected to the peripheral bus on which SPI1 resides. As such can't "talk" to each other on an f4, which is specific to that MCU family. I hadn't really bothered to deeply internalize this (because I've not yet experimented with having one peripheral talk to another). 

Switching to use a timer with a DMA controller that CAN talk to AHB1 (which is where spi1 lives) allows the code above to work fine. In my case, this could be either timer1 or timer8. 

All in all, a very neat trick that I'm glad to have learned!

Screenshot 2026-01-12 at 11.04.58 AM.png