cancel
Showing results for 
Search instead for 
Did you mean: 

Timer events as DMA triggers - Improving the current situation in CubeMX/HAL

BarryWhit
Senior III

Hello ST,

 

Today, in two separate threads, I've had to walk two separate newcomers to STM32 through the pitfalls of trying to trigger a DMA to an arbitrary peripheral from a timer event. The threads are:

what-are-gpio-events-how-do-i-connect-an-event-to-a-peripheral 

and

spi-dma-request-triggered-by-timer-compare 

 

I myself had to struggle (using the G431) not that long ago to make sense of what the CubeMX generated code does, and why it isn't usable.

HAL functions HAL_TIM_IC_Start_DMA/HAL_TIM_OC_Start_DMA hardcode the DMA peripheral register to which read/write is performed, thus they are not usable. They do however perform a lot of prep work, which it is not trivial

for beginners to reconstruct on their own (which they tend to do since they can't figure out what CUbeMX is actually

generating code to do). This is evident by the two threads above as well as my own experience.

 

There is no HAL API (that I know of) that allows the developer to specify the peripheral-side register address.

 

I strongly encourage the CubeMX developers to address this shortcoming in an upcoming release, for the following reasons:

1. It's a common use-case which currently isn't addressed. 

2. It's not obvious to a newcomer what the existing API functions (such as HAL_TIM_OC_Start_DMA) are intended for. It takes quite a lot of spleunking to figure out what the HAL functions are tailored. In particular, I think it's confusing to most people that the API ties the *source* of the DMA *trigger* (e.g. timer event X), to the nature (src/target) of the DMA request (e.g. streaming capture values to memory). The hardware doesn't force this,

so no wonder people are baffled when the HAL API does.

3. It seems like this would be relatively simple fix. What is requires is just a generalized version of  HAL_TIM_IC_Start_DMA/HAL_TIM_OC_Start_DMA, which takes the source/target register address as an argument. perhaps HAL_TIM_IC_Start_DMA/HAL_TIM_OC_Start_DMA can even be rewritten to simply call the generic function to eliminate some code duplication.

4. Finally, an separately, I think there's an inconsistency in the way CubeMX handles DMA to different peripherals,

and how this is integrated with the HAL. Consistency is important in order to simplify learning. In my experience, users expect the flow to be "choose a DMA trigger, choose a DMA channel, choose dest/src". In fact, support for triggering DMA from a timer is different between peripherals, and between events.

You can bind a channel with TIMx_CHy or TIMx_UP (for example) from the timer periph's DMA tab, and in the GUI this looks consistent. But the HAL doesn't treat these two equally. Using TIMx_UP gets no dedicated API (you have to call Base_Start and DMA_start yourself), while the IC/OC case gets a dedicated API, which silently assumes that since you want to use the IC/OC as a trigger you must also want to use them as a data src/target (for writing capture values to memory, or reprogramming the compare value for waveform generation). And, if you want to use a timer to do DMA->DAC, it's different again. the DAC peripheral GUI lets you specify the timer as a (conversion) trigger, and if you assign a DMA channel (and call the associated HAL API), the DAC itself will trigger the DMA (AFAICT).  For each of these the users need to figure out the specifics of each as a special case, and this makes the learning curve so much steeper. I've been through each of these cases myself, and I can attest that It takes a lot of effort to find your way though this maze of special cases.

 

I hope you're listening,

Barry

 

Update 2024-07-09: Another thread 

- If a post has answered your question, please acknowledge the help you received by clicking "Accept as Solution".
- Once you've solved your issue, please consider posting a summary of any additional details you've learned. Your new knowledge may help others in the future.
11 REPLIES 11
Imen.D
ST Employee

Hello @BarryWhit 

Thanks for you feedback and for taking time to report this with many details.

I escalated this post internally via internal Ticket ID 184627 and will be back to you with update.

When your question is answered, please close this topic by clicking "Accept as Solution".
Thanks
Imen
BarryWhit
Senior III

Dear @Imen.D,

I've updated my post with more details as I gradually understand more about the issue. It is partly metamorphosing into a rant, I realize. To keep thing concrete, my hope is that the software dev team will add a missing function to the HAL, a function which specifically allows users to perform DMA with *any* timer event configured as a trigger, to/from an *arbitrary* peripheral address. The deeper issues of how to architect the CubeMX GUI in cooperation with the HAL are complex, and I realize it might not be feasible to make deep changes at this point. I hope whomever you routed this to internally at least reads the updated post.

 

Thanks,

Barry

 

- If a post has answered your question, please acknowledge the help you received by clicking "Accept as Solution".
- Once you've solved your issue, please consider posting a summary of any additional details you've learned. Your new knowledge may help others in the future.

Hello @BarryWhit 

 

The proposed pseudocode introduces a new function, HAL_TIM_Flexible_DMA_Start, which allows developers to specify any timer event as a trigger and define an arbitrary peripheral address for the DMA transfer.

 

 

// Pseudocode for a flexible DMA triggering function in the STM32 HAL API

// Define a structure to hold the configuration parameters for the DMA trigger
struct DMA_Trigger_Config {
    TIM_HandleTypeDef *htim;          // Handle to the timer
    uint32_t Channel;                 // Timer channel acting as the DMA request source
    uint32_t TimerEvent;              // Timer event used to trigger the DMA
    uint32_t *PeripheralAddress;      // Address of the peripheral data register
    uint32_t *MemoryAddress;          // Address of the memory location
    uint32_t DataLength;              // Number of data items to transfer
};

// Define the flexible DMA triggering function
HAL_StatusTypeDef HAL_TIM_Flexible_DMA_Start(DMA_Trigger_Config *config) {
    // Validate the input parameters
    if (config == NULL || config->htim == NULL || config->PeripheralAddress == NULL || config->MemoryAddress == NULL) {
        return HAL_ERROR;
    }

    // Configure the timer to generate a DMA request on the specified event
    // This step involves setting up the correct timer registers and modes based on the TimerEvent
    ConfigureTimerForDMA(config->htim, config->Channel, config->TimerEvent);

    // Configure the DMA channel to transfer data from the specified memory address to the peripheral address
    // This step involves setting up the DMA controller with the source and destination addresses, data length, and other relevant parameters
    ConfigureDMAChannel(config->htim->hdma[config->Channel], config->PeripheralAddress, config->MemoryAddress, config->DataLength);

    // Enable the DMA stream/channel
    HAL_DMA_Start(config->htim->hdma[config->Channel], (uint32_t)config->MemoryAddress, (uint32_t)config->PeripheralAddress, config->DataLength);

    // Enable the timer DMA request
    __HAL_TIM_ENABLE_DMA(config->htim, config->Channel);

    // Start the timer
    HAL_TIM_Base_Start(config->htim);

    return HAL_OK;
}

// Example usage of the flexible DMA triggering function
void ExampleUsage() {
    // Create a configuration structure and populate it with the desired settings
    DMA_Trigger_Config dmaConfig;
    dmaConfig.htim = &htim2; // Assume htim2 is already initialized
    dmaConfig.Channel = TIM_CHANNEL_1;
    dmaConfig.TimerEvent = TIM_EVENT_UPDATE; // For example, use the update event
    dmaConfig.PeripheralAddress = (uint32_t*)&(DAC->DHR12R1); // DAC data holding register
    dmaConfig.MemoryAddress = (uint32_t*)audioBuffer; // Buffer containing audio data
    dmaConfig.DataLength = AUDIO_BUFFER_SIZE; // Size of the audio buffer

    // Call the flexible DMA triggering function with the configuration
    if (HAL_TIM_Flexible_DMA_Start(&dmaConfig) != HAL_OK) {
        // Handle error
    }
}

 

 

This is a high-level representation intended to illustrate the concept and is not a complete implementation. It is designed to be a starting point for discussion.

I would greatly appreciate your feedback on this approach. Does this pseudocode align with the solution you are looking for? Are there any specific aspects you would like to see adjusted to better fit your use case?

If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar
BarryWhit
Senior III

Omar, Hurray Hurray Hurray!

 

I will look at this and give you any useful feedback I can.

- If a post has answered your question, please acknowledge the help you received by clicking "Accept as Solution".
- Once you've solved your issue, please consider posting a summary of any additional details you've learned. Your new knowledge may help others in the future.
BarryWhit
Senior III

@Andrew Neil, I'd value your input. We're actually, like, changing the system, man! 😂

- If a post has answered your question, please acknowledge the help you received by clicking "Accept as Solution".
- Once you've solved your issue, please consider posting a summary of any additional details you've learned. Your new knowledge may help others in the future.
BarryWhit
Senior III

 

@Saket_Om , I have looked at the pseudocode and pondered and here are my thoughts:

 

First, Does ST has an internal procedure for "experimental API"? I can't be sure I'm not missing anything,

and neither can you. Do you have a way to test out an API and getting wider feedback from users before

you cast it in concrete? 

 

As for the code itself, the following issues came to mind:

  1. If the new "convenience" API requires that a new type of struct be instantiated and passed in,
    it's important for the CubeMX code generator to create one for the user and populate it with the values
    Entered in the CubeMX GUI. (or alternatively, code gen should create a "factory" function for it that returns/populates such a struct).
    If the (CubeMX) user has to manually create this struct, much of the "convenience" is already lost, and this conflicts with the expectation that the CubeMX DMA will result in suitable code generation.
  2.  [removed due to misunderstanding]
  3. [also] Please confirm that configuring a DMA channel (width/direction/increment/circular) for any timer event in CubeMX will result in appropriate code generated as part of the _init code.
  4. possibly, someone someday may need to separate the configuration of this whole thing, from the actual
    moment the timer is started. Remember, the timer may have other channels in use, other things going on.
    So the call to HAL_TIM_Base_Start, may prevent someone from using the rest of this function. (possible solution: add a bool start_timer), as It's clear you're trying to expand the existing naming convention of the HAL _start function. This may require some thought and perhaps it should not be a _start but a _prepare
    function (that's up to your internal sewing circle to debate 🙂 ).
  5. ConfigureTimerForDMA must take great care not to interfere with existing configuration of timer and other channels. Either by CodeGen Init functions, or manually by user prior to calling this function.
  6. ExampleUsage looks ok (aside from the previous points), but this should be considered
    example usage *only for the Non-CubeMX user*. Those who configue the DMA in CubeMX should be benefit
    by requiring much less lines (and not having to repeat themselves.)

 

That's all I have for now, perhaps more to come.

 

Thank you for considering the issue I raised seriously, it's a wonderful thing for to see that you really do respond to feedback from your community about pain points. I appreciate it very much.

 

- If a post has answered your question, please acknowledge the help you received by clicking "Accept as Solution".
- Once you've solved your issue, please consider posting a summary of any additional details you've learned. Your new knowledge may help others in the future.

Hello @BarryWhit 

 

I appreciate your diligence in ensuring that the API meets your needs. To clarify, the pseudo code I provided serves as an example to confirm the functionality you require. This is an essential step, as it helps to communicate your requirement effectively to our development team.

 

>> Does ST has an internal procedure for "experimental API"?

Yes, we do have an internal process for validating and testing our APIs. This process is designed to ensure that the APIs we develop are robust and meet the needs of our users.

If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar

@BarryWhit wrote:

@Andrew Neil, I'd value your input. 


I don't tend to use DMA much, so nothing really to say.

But a big trouble with the HAL (and it's documentation), I find, is that it treats "DMA" entirely separately and in isolation from the peripherals - there is nothing about how you use the DMA with the peripherals.

The examples don't help here, because they just give a fait accompli: they just show you what it looks like when it's all done and finished - they don't take you through the process of actually getting to that point.

The coverage of interrupts is similar - with "NVIC" being entirely separate from the peripheral support.

 

But I think that's all a bit of a side-track from your point here?


@Saket_Om wrote:

Yes, we do have an internal process for validating and testing our APIs. This process is designed to ensure that the APIs we develop are robust and meet the needs of our users.


my emphasis - how is that bit ensured ?

🤔