STM32 with CubeMX: correct establishment order for UART in DMA mode - Page 1
Forum section: electronics / general

author: liteyear
author_rank: Regular Contributor
timestamp: February 06, 2025, 11:22:26 pm
content: Didn't get any bites over at ST, so am seeking your wise counsel.When initialising a UART using CubeMX generated code, either the receiver or transmitter or both are enabled. Typically that means that before receiving data with the UART for the first time, theRDRregister should be cleared (or theRXFRQfunction used), to clear out any junk that got read during boot.But if using DMA RX mode I've discovered an extra consideration - if anerroris received before callingHAL_UART_Receive_DMA()for the first time, the error handler will be called. The defaultHAL_UART_IRQHandler()(at least instm32u5xx_hal_uart.c) aborts the DMA RX on error (regardless of theDDREsetting). So the user is required to restart if necessary.This leads to an awkward situation where the error handler (which has to restart DMA RX) can be called beforeHAL_UART_Receive_DMA()is called for the first time. Rather that handling that strange edge case, it seems prudent to eliminate the undesirable order of operations.Specifically, is there a sensible way to configure the UART with CubeMX, but then inhibit it (just the receiver part would do, I think) until we're ready to start receiving?I could enable transmit only in CubeMX, which would prevent RE being set. But thenHAL_UART_Receive_DMA()doesn't set it, so doing so would be custom.It seems to me that given the design of theHAL_UART_Receive*interface, that it would never be desirable to have the receiver enabled prior to them being called, so I wonder if there's a more logical way to go about this.

author: eTobey
author_rank: Super Contributor
timestamp: February 07, 2025, 09:56:28 pm
content: Id go with the manual. It gives you the right steps (probably). All that HAL is just a black box in between.Have you cleared the error before enabling anything DMA / interrupt / receive stuff?Also you have to initialize the DMA before the peripheral.

author: liteyear
author_rank: Regular Contributor
timestamp: February 09, 2025, 10:45:49 pm
content: Yeah, "just bypass the HAL" is a common refrain! I've stayed the HAL course for a couple of decades now and have too much proven-in-use collateral to abandon ship now. Particularly given the extraordinary complexity of the peripherals these days, and the desire to lean on vendors to ensure resistance to change stays low.Yes, errors are cleared prior to initialisation. The HAL actually does a pretty good job of this, ensuring you don't get interrupts on initialisation. It's the period between initialisation and starting a receive that is no mans land. By then interrupts are already armed to fire, so it's too late to clear anything.Yes, fortunately that UART-before-DMA initialisation bug has been fixed for a while. Thisseemsto be a deliberate ordering of things, which is why I'm wondering if I've misinterpreted the interface.FWIW, in the meantime I'm proceeding with the solution I hinted at: in CubeMX I configure all UARTs asTransmit Only. Then, immediately after starting a receive for the first time I manually flip the "receiver enable" bit. Feels a bit crude, but so far so good. Does what you would hope - the peripheral is configured with all the complex intra-peripheral settings you setup in CubeMX, and the Transmit Only setting doesn't seem to inhibit my options. But then no interrupts fire untilafteryou make your firstHAL_UART_Receive*call.PS. Maybe not what you meant, but note the HAL is not a black box. The only reason I've stuck with it for so long is that it's human-readable source code! You can even step through it in the debugger, and I do so regularly. That's essential, and I've abandoned other vendors that have gone the black box route. Now is theintenta black box? Well technical support... varies.

author: eTobey
author_rank: Super Contributor
timestamp: February 10, 2025, 07:46:41 am
content: Indeed the term i used is wrong in this case. To me its just a "black bocx", that i have never opened.I have used it in the beginning, but had a few cases where i had to look into that black box. But then i thought it would be better to have a deeper understanding of the hardware, to write a good program. Also its overhead is HUGE, and i am glad that i did not go with HAL at the very beginning, because im gonna need quite a bit of space."Yes, errors are cleared prior to initialisation."Cant you debug where the issue happens exactly? Add some delays maybe in the init. Also sometimes you want to clear errors only exactly before you enable a interrupt.

author: liteyear
author_rank: Regular Contributor
timestamp: February 10, 2025, 10:18:18 pm
content: I could be wrong, but I'm pretty sure the issue is clear:CubeMX'sMX_UARTn_Init()function configures the peripheral and sets the uart, transmitter, and receiver enable flags.The uart peripheral starts looking for a start bit, and will begin decoding a byte.If a valid byte arrives, the peripheral holds it in the receive buffer (or FIFO if you've enabled it) until you call one of theHAL_UART_Receive*functions. Typically people purge the receive buffer before doing so, so they only get the data they're prepared for.However, if aninvalidbyte arrives, the interrupt fires and your error handling callback is called. In the special case of a UART configured for DMA RX, DMA is aborted, even if you haven't started it yet with a call toHAL_UART_Receive_DMA().So the error handler is faced with a tricky situation. In normal operation it needs to restart DMA. But because it's called in interrupt context, it can happen before, after, or even during your first call toHAL_UART_Receive_DMA(). That's awkward to manage.For example, scroll down to "Step #5" here:https://deepbluembedded.com/stm32-uart-dma-receive-transmit-rx-tx-examples/#stm32-uart-dma-receive-rx-example-overviewThe author does the traditional thing, not much different to what I'm doing, though much simpler:Code:HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();HAL_UART_Receive_DMA (&huart1, UART1_rxBuffer, 12);The problem is that between the call toMX_USART1_UART_Init()and the call toHAL_UART_Receive_DMA(), the receiver is already enabled. DMA isn't, but the UART will still happily receive a byte and throw an error if it detects one.Doesn't that seem a little back to front?

author: eTobey
author_rank: Super Contributor
timestamp: February 11, 2025, 11:37:13 am
content: I can not answer your question, nor give you a proper advice (in case for the HAL), since its a box that i rather dont want to look into, but since you state:Quote from: liteyear on February 10, 2025, 10:18:18 pm, to me the solution is clear:If HAL gives you trouble, get rid of it!BTW. what is your flash usage? If you are a bit short, then its well worth thinking about getting rid of HAL.

author: liteyear
author_rank: Regular Contributor
timestamp: February 12, 2025, 12:21:54 am
content: Okay. There's somewhere north of 10,000 hours of personal investment in the HAL at this point that is paying dividends every day. This issue is a good example - I spent a total of zero hours on developing a DMA enabled UART HAL driver, which I'm now deploying across multiple STM32 families for various clients. Given my familiarity with the HAL I was able to identify and workaround this issue (using the receiver-disabled trick I mentioned) quickly.If I'm looking for advice on better use of the HAL, you can bet that "stop using the HAL" isn't going to stack up on my cost-benefit analysisFWIW, one medium-term project I have open at the moment is at 13% Flash usage for 35000+ lines of application-specific source code, excluding all the HAL and CMSIS stuff, but including the big-bertha stdlib with float support. That's pretty typical. This isn't Zephyr. But yes, if someone is out there looking to save space, it's one thing to consider.