Skip to main content
Associate II
August 7, 2026
Question

Nucleo H563ZI SPI Comms with an ADC: Works with HAL Interrupt APIs, Not with DMA APIs

  • August 7, 2026
  • 5 replies
  • 46 views

Hi all - I have a problem getting DMA working with SPI, for communication with an MCP3564R ADC as one task in a complex project running on FreeRTOS. I have made a stripped-down version of the project that just does the ADC reading task. It runs on a bare Nucleo board and illustrates the basic problem which shows up from the first transmit, so a connected ADC isn’t needed to see the problem.

The code uses SPI3 at 20MHz (although the problem occurs at lower speeds as well)

The GPIO pins I’m using are not the defaults:

GPDMA1 is configured with two channels for SPI3 TX and RX:

My code can be configured to use the HAL SPI DMA APIs or the interrupt APIs like so:

static uint8_t write_register(const uint8_t * reg_write_cmd_p, uint8_t num_bytes)
{
HAL_StatusTypeDef hal_status;
BaseType_t wait_ret_val;
uint32_t notify_bits;
uint8_t ret_val = 0; /* Failure */

#if (MCP3564_HAL_SPI_MODE == MCP3564_USE_HAL_SPI_DMA_MODE)
hal_status = HAL_SPI_Transmit_DMA(&ADC_SPI_PERIPH_HANDLE, reg_write_cmd_p, num_bytes);
#elif (MCP3564_HAL_SPI_MODE == MCP3564_USE_HAL_SPI_INT_MODE)
hal_status = HAL_SPI_Transmit_IT(&ADC_SPI_PERIPH_HANDLE, reg_write_cmd_p, num_bytes);
#else
#error "unexpected MCP3564_HAL_SPI_MODE"
#endif
if (HAL_OK == hal_status)
{
wait_ret_val = xTaskNotifyWait(0x0, ADC_TASK_NOTIFY_TX_COMPLETE, &notify_bits,
pdMS_TO_TICKS(ADC_HAL_SPI_TIMEOUT_MSEC));
if ((pdTRUE == wait_ret_val) && (notify_bits & ADC_TASK_NOTIFY_TX_COMPLETE))
{
ret_val = 1;
}
}

return ret_val;
}

When I have it building for the interrupt APIs, the first transmission looks just like I expect (note that the pulse edges look more sawtooth than square because I’m getting close to the maximum time resolution of my Analog Discovery USB scope, but it still decodes OK):

When I have it building for the DMA APIs, I’m only seeing zero go out:

I had all this code (and much more) working quite nicely at one time but then the hardware design I’m targeting changed and I had to relocate some pins. DMA hasn’t worked right since despite trying to build an .ioc file back up starting with the basic Nucleo board.

I’ve grilled Google AI about this problem and it has had many suggestions but most of them turned out to be mistaken - for example, it insists that the problem is likely the data cache, although on the STM32H563 family parts my understanding is this could not be an issue due to a difference in the DMA architecture. In any case the cache is not enabled. I worked through many more suggestions from Google AI and they became increasingly far-fetched and incorrect and it stopped answering me.

If anyone has this Nucleo board and would like to look at the project, or step through the code, I have placed the project on GitHub here: https://github.com/paulrpotts/nucleo-h563zi-adc-MCP3564R

Without hardware connected like my hand-built prototype board with the ADC, of course the ADC will not answer and the whole ADC monitoring task will not run, but the failure happens right with the first call to write_register() in task_adc_MCP3564R_test.c line 317. The #define to build for DMA APIs or interrupt APIs is in adc_MCP3564R_config.h at line 100.

Any suggestions for how to get past this non-working DMA appreciated, even just getting one initial write working and seeing the expected data on the SPI bus would be progress.

Thanks,

Paul R. Potts

5 replies

AScha.3
Super User
August 8, 2026

Hi,

i didnt look at all your post, but first things…

  • set in Cube , SPI → master keep io state : enable (to keep the lines active)
  • set pin speed low → medium (to allow 20Mbit signal at all to come out )
  • the DMA can be tricky/complex on H563 , so what you use: linked list or not ?
  • where you start DMA ?

with linked list DMA looks like : (for SAI here, you use ..SPI..  then )

  __HAL_LINKDMA(&hsai_BlockA2, hdmatx, handle_GPDMA1_Channel7);	//Associate the DMA handle /without -> hard fault !
MX_SAIQueue_Config();
HAL_DMAEx_List_SetCircularMode(&SAIQueue);
HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel7, &SAIQueue); // Link SAI queue to DMA channel
fresult = HAL_SAI_Transmit_DMA(&hsai_BlockA2, (uint8_t *)playbuf , (sizeof(playbuf))/4);// SAI start

 

If you feel a post has answered your question, please click on " Best Answer ".
Associate II
August 8, 2026

Hi AScha.3,

Thanks for your reply.

The pin speed is a good idea - I had them set to medium in an earlier project but forgot to change it in this .ioc file. It doesn’t fix this problem but it should improve reliability.

The “Master Keep Io State” setting: I have never seen that setting mentioned in any examples or documentation. I tried setting it to “Master Keep Io State Enable” but that change didn’t make any difference.

My DMA settings are as shown: for channel 0 (RX):

For channel 1 (TX):

My peripheral initialization is not modified from the code generated by STM32CubeMX in spi.c. See:

https://github.com/paulrpotts/nucleo-h563zi-adc-MCP3564R/blob/trunk/Core/Src/spi.c

I’m not using a linked list or circular mode.

Re: your question as to where I start DMA: I am using the Cube MX-generated code in main.c and spi.c and HAL APIs HAL_SPI_Transmit_DMA() and HAL_SPI_TransmitReceive_DMA(). Is there some other API call I need to allow the DMA to run that is not in the generated code? I have looked at a number of examples and don’t remember seeing another API call used.
 

Associate II
August 8, 2026

It appears I have a clue. It looks like initializing the flash peripheral at startup, but never explicitly using it, is interfering with the correct functioning of the DMA.

My .ioc file defines special memory areas to allow my code to access the highest/last page of user flash either through the regular user flash addresses or through the high cycle data area addresses:

and

This results in the flash peripheral enabled:

And also the creation of MPU_Config() in main.c, and calling it at the very start of main(), although this specifically does not seem to be the cause.

If I turn off this setting:

Then I can turn off the Enable checkbox under FLASH.

Cube MX then no longer generates flash.c and flash.h and no longer calls MX_Flash_Init() from main(). The MPU_Config() is not changed and is still called at the start of main(). But without the call to MX_Flash_Init() at startup, the SPI DMA transmit works correctly.

So, it appears that initializing the flash peripheral is interfering with the correct functioning of the DMA, even though the code in this project is not actually using those flash pages. So I will have to figure out what the conflict is. My full application uses that flash page to store calibration data so I can’t just disable it completely.

It doesn’t seem to be caused by the MMU configuration, since that is still being generated in main.c and that did not change.

I’ll have to look at this more on Monday. If anyone wants to take a look, here is the branch with the working code:

https://github.com/paulrpotts/nucleo-h563zi-adc-MCP3564R/tree/disable-reserved-high-flash-page

To see a diff:

https://github.com/paulrpotts/nucleo-h563zi-adc-MCP3564R/compare/disable-reserved-high-flash-page?expand=1

Note that most of the diff is .mxproject and .ioc files due to the removal of flash.c and flash.h. The actual code change is no longer calling MX_Flash_Init().

Associate II
August 8, 2026

OK, with the assistance of the STM32 “Sidekick” AI, I have worked out that I needed to set the access permission for the Application Region “FLASH” to “RO by any privilege level.” If it isn’t set this way, the generated MX_FLASH_Init() sets the FLASH_BB_PRIV bits to 1 except for the separate Application Region I defined as the last page of flash. Then when the DMA attempts to read const data that the compiler put into flash, it gets a privilege violation and reads only zero bits.


Changing this results in the generated MX_FLASH_Init() function containing _only_ Flash_Unlock() and Flash_Lock(), which pointless, so I turned off generation of MX_FLASH_Init() under Project Manager/Advanced Settings, since it is not needed.

I will leave this here since this solution did not seem obvious - Google AI in particular never came up with any suggestions having to do with the access permissions coming from FLASH_BB_PRIV bits interfering with the DMA’s ability to read from flash, but not the CPU’s. Some kind of option on the DMA configuration pages, to enable reading from flash, might also have been useful.

Associate II
August 8, 2026

And there’s a similar issue in SRAM that is fixed in a similar way. All this came about behind the scenes because I wanted my code to be able to write to a single page of flash.