cancel
Showing results for 
Search instead for 
Did you mean: 

SSI and Circular DMA

sb_st
Associate III

Hello!

I am writing a driver for the mt6701ct rotary magnetic encoder, using cubeIDE and an stm32f405 MCU. This sensor communicates via SSI, which is to say that it's read-only (MISO-only; there is no physical connection to MOSI). 

Using a simple polling strategy, I can communicate fine with this sensor using HAL_SPI_Receive().

I'd like to learn more about using DMA for this though, and am interested in trying to get circular DMA working. 

My SPI is configured like so:

Screenshot 2025-01-14 at 8.17.05 PM.png

Screenshot 2025-01-14 at 8.21.15 PM.png

The HAL Reference Manual notes:  

Screenshot 2025-01-14 at 8.15.52 PM.png

To try to sidestep issue #1 with my SSI setup here, I do this in my code to start the circular DMA:

typedef struct
{
  SPI_HandleTypeDef *spi_handle;
  uint16_t cs_pin;
  GPIO_TypeDef *cs_pin_port;
  uint8_t sensor_receive_dma_buffer[3];
} SMT6701CT_HandleTypeDef;

// IMPORTANT! On Page 1033 of the HAL Reference Manual,
// there's a note that says we can't use circular DMA
// with SPI with only RX data (which is what we want).
// To get around this, what I'm doing is creating a
// dummy TX buffer, and then using HAL_SPI_TransmitReceive()
// to simultaneously send and receive data. There is no
// physical TX connection here, so this does nothing really,
// it's just a workaround to allow us to do this DMA trick.
uint8_t tx_dummy[3] = {0x00, 0x00, 0x00};

// Pull cs low for the sensor.
// Note that we only activate this, we don't
// deactivate it here.
MT6701CT_enable_cs(sensor);

// Now, start the DMA read
hal_status = HAL_SPI_TransmitReceive_DMA(sensor->spi_handle, tx_dummy, sensor->sensor_receive_dma_buffer, 3);

Then, I have an interrupt handler like so:

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
    if (hspi == &hspi1) {
    	HAL_StatusTypeDef hal_status;
	hal_status = HAL_SPI_DMAPause(sensor->spi_handle);

	// ...
	// do a bunch of stuff to calculate the angle
	// ...

	// resume the DMA reader
	hal_status = HAL_SPI_DMAResume(sensor->spi_handle);
    }
}

I can see, via stepping through the code with the debugger in CubeIDE, that the DMA seems to be started ok, and the interrupt handler is indeed being called periodically. However, the data inside my sensor->sensor_receive_dma_buffer seems to be junk - it updates periodically as I expect, but just seems to be noise or other nonsensical data. 

I know it's hard to pinpoint the issue based on these limited code snippets, but I'm curious how I can troubleshoot what might be going wrong here? I think signs point to my hardware behaving correctly, and that I'm just dealing with a code problem. But, this is my first go-around with DMA, so I'm sure I'm overlooking something here.

 

Thank you!

4 REPLIES 4

> HAL_SPI_DMAPause

Just don't.

JW

sb_st
Associate III

To try to add bit more info:

I thought to try using HAL_SPI_DMAPause()/Resume() because this sensor sends a 24-bit chunk of data, 6 bits of which are a CRC that I need to manually decode before doing some math to turn the remaining 18 bits into a floating-point angle. All of this seemed arduous enough that pausing the DMA while I was doing that seemed prudent, but maybe not. 

But, setting that aside for the moment: I'm currently trying to simplify thing by pulling all of that out. Currently, I simply do:

uint8_t tx_dummy[3] = {0x00, 0x00, 0x00};
uint8_t rx_buf[3];

HAL_GPIO_WritePin(SPI1_CSN_GPIO_Port, SPI1_CSN_Pin, GPIO_PIN_RESET);

// Now, start the DMA read
HAL_StatusTypeDef hal_status;
hal_status = HAL_SPI_TransmitReceive_DMA(&hspi1, tx_dummy, rx_buf, 3);

while(1) {
  osDelay(pdMS_TO_TICKS(10));
}

 Then I enter the debugger and monitor rx_buf: 

dma_buffer.gif

Gif compression aside (the values change more rapidly than what is seen here), this doesn't look like meaningful information to me - the values only switch between 0 and 255, e.g. 

I wonder if it is relevant to highlight that I'm doing this from within a freeRTOS task? rx_buf is currently declared a global within one of my tasks. It feels as though I am making a mistake with correctly writing to a memory location or something, but this is where I'm bumping up against my inexperience. 

 

Have you tried to use SPI without DMA?

Or even bit-bang the interface, if the slave supports slow clocks?

Have you observed the signals on the pins?

JW

Oh good question, thanks - I think this led me to a great clue!

  • Yes, to your first question: as I mentioned in my initial post, the sensor works fine using SPI in a polling fashion. I can get data, parse it, check the CRC, get a floating-point angle. It all works fine. 
  • To your third question, I did that just now; the results offer some clues. At first, I see the sensor seemingly providing meaningful data. My logic analyzer seems to have trouble making sense of things, I think maybe because it's unclear where one transaction ends and another starts (but that's me guessing):
    Screenshot 2025-01-15 at 10.11.32 AM.png
  • Then, after a brief period, it seems like the sensor stops providing output:
    Screenshot 2025-01-15 at 10.11.51 AM.png

    It seems to stay in this state for a bit:

    Screenshot 2025-01-15 at 10.12.02 AM.png

    Then finally starts pulsing what I'm guessing is an error signal:

    Screenshot 2025-01-15 at 10.12.16 AM.png

 

All of this led me to realize that I never pull CS High again after each individual transaction (which is what I do when reading the sensor in a polling fashion). From Page 23 of the sensor's datasheet:

"The MT6701 SSI is shown in Figure 24, a data transfer starts when CSN is pulled to logic ‘Low’. The MT6701 transfers data on the falling edge of CLK, and the data transfer finally stops when CSN is pulled to logic ‘High’"

Suspecting that this toggling of CS between transactions is mandatory, I added this to my interrupt handler: 

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
    if (hspi == &hspi1) {
	
        //HAL_SPI_DMAPause(sensor->spi_handle);
        disable_cs();
        read_and_parse_sensor_data();
        enable_cs();
    	//HAL_SPI_DMAResume(sensor->spi_handle);
    }
}

This kinda-sorta seems to work - when viewing my angle variable in the debugger, I see it now reporting meaningful, reasonable values. Except, it seems kind of "stuttery" - it sort of returns a few good values, then hangs for a while (seemingly), then returns more values, hangs, etc. 

When inspecting things now with a logic probe, I see this:

Screenshot 2025-01-15 at 10.40.22 AM.png

 I wonder if what's happening now is that things are no longer properly synchronized - it looks like the SPI clock continues to run separately from me yanking CS high or low. 

Stepping back: I wanted to try digging into this to learn, but am I choosing a poor test case for circular DMA? I can't tell if I'm using this as it was intended to be used or if I'm just inadvertently creating a bigger problem for myself.