2025-10-20 8:52 AM - last edited on 2025-10-20 8:56 AM by mƎALLEm
I'm losing my mind a little, because this seems like it should be a simple task.
I have configured my I2C module to enable DMA and interrupts:
I can confirm that my call to HAL_I2C_Master_Transmit_DMA returns OK. I can also confirm that the generated DMA1_Channel2_IRQHandler is called. That's where things start breaking down.
Other modules in the HAL library give you a hook (e.g.HAL_GPIO_EXTI_Callback) signature that you can implement and have it be called by the HAL top-level ISR, but it seems like DMA has not implemented that. Instead, the HAL DMA module implements HAL_DMA_RegisterCallback, and you can successfully call it - but then those callback pointers are overwritten by ones in the HAL itself.
OK... so then I tried copying some of the code from HAL_DMA_IRQHandler to (re-)determine which DMA flag applies:
static void dma_states(
const DMA_HandleTypeDef *restrict hdma,
bool *restrict is_complete,
bool *restrict is_error
) {
const uint32_t flag_it = hdma->DmaBaseAddress->ISR;
const uint32_t source_it = hdma->Instance->CCR;
*is_complete = (flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex)) && (source_it & DMA_IT_TC);
*is_error = (flag_it & (DMA_FLAG_TE1 << hdma->ChannelIndex)) && (source_it & DMA_IT_TE);
}
And this evaluates complete and error to false in both cases, always seemingly because the TC and TE flags have not been enabled by the HAL in CCR.
What gives? Is this implementation just broken? Surely the implementers didn't expect users to have to bit-fiddle in CCR to get the most basic interrupts.
Solved! Go to Solution.
2025-10-20 9:03 AM
The transfer is not complete when the DMA flag gets set. That is the reason for how it is implemented.
Here is the correct usage for HAL_I2C_Master_Transmit_DMA , per the source code.
(+) Transmit in master mode an amount of data in non-blocking mode (DMA) using
HAL_I2C_Master_Transmit_DMA()
(+) At transmission end of transfer, HAL_I2C_MasterTxCpltCallback() is executed and users can
add their own code by customization of function pointer HAL_I2C_MasterTxCpltCallback()
So your callback is HAL_I2C_MasterTxCpltCallback, provided everything is correct in your code.
You can see an example project which does this here:
2025-10-20 9:03 AM
The transfer is not complete when the DMA flag gets set. That is the reason for how it is implemented.
Here is the correct usage for HAL_I2C_Master_Transmit_DMA , per the source code.
(+) Transmit in master mode an amount of data in non-blocking mode (DMA) using
HAL_I2C_Master_Transmit_DMA()
(+) At transmission end of transfer, HAL_I2C_MasterTxCpltCallback() is executed and users can
add their own code by customization of function pointer HAL_I2C_MasterTxCpltCallback()
So your callback is HAL_I2C_MasterTxCpltCallback, provided everything is correct in your code.
You can see an example project which does this here:
2025-10-20 9:30 AM
My deepest thanks for both the accuracy and promptness of this answer. It is exactly what I was looking for.
Aside: should I be disabling the I2C interrupts from MX, if all I care about are the DMA completion callbacks?
2025-10-20 9:51 AM
In general, you should not disable interrupts that CubeMX has configured.
In this specific case, I believe the I2C interrupt is needed to trigger HAL_I2C_MasterTxCpltCallback. If you're disabling it, you won't get HAL_I2C_MasterTxCpltCallback and also the software state machine (I2C handle) may not get updated correctly.
The DMA TC flag gets set but doesn't call HAL_I2C_MasterTxCpltCallback since the transaction is not actually complete. DMA TC indicates the data has been sent to the I2C and the DMA is done with its job but not that the I2C has sent it out--it is still mid-transaction.
2025-10-20 9:53 AM
Testing successfully bears that description out; thanks again.