Search code examples
csyntaxembeddedstm32hal

STM32 HAL USART drivers: How does this syntax work?


I am learning about STM32 programming and trying to implement simple asynchronous serial communication using USART peripheral on GPIO pins.

The HAL manual describes how to use HAL USART drivers:

  1. Declare a USART_HandleTypeDef structure
  2. Implement HAL_USART_MspInit()
    • Enable USART & GPIO clocks
    • Configure GPIOs
  3. Program the communication parameters in the USART_InitTypeDef
  4. Call HAL_USART_Init()


As I wrote my code, I declared the USART_HandleTypeDef, instinctively filled my USART_InitTypeDef structure and started to fill the HandleTypeDef:

  USART_HandleTypeDef UsartHandle;

  USART_InitTypeDef UsartInit;
  UsartInit.BaudRate   = 9600;
  UsartInit.WordLength = USART_WORDLENGTH_8B;
  UsartInit.StopBits   = USART_STOPBITS_1;
  UsartInit.Parity     = USART_PARITY_NONE;
  UsartInit.Mode       = USART_MODE_TX_RX;

  UsartHandle.Instance = USART6;
  UsartHandle.Init     = &UsartInit;
  /* do I really have to init EVERY data field? */

  HAL_USART_Init(&UsartHandle);


I then noticed that there's many data fields to fill. Referring to code examples in the manual and on the web, I noticed nobody actually defines all of the USART_HandleTypeDef fields - they somehow combine the HandleTypeDef and InitTypeDef in one step, like this:

UART_HandleTypeDef UartHandle;

UartHandle.Init.BaudRate = 9600;
UartHandle.Init.WordLength = UART_DATABITS_8;
UartHandle.Init.StopBits = UART_STOPBITS_1;
UartHandle.Init.Parity = UART_PARITY_NONE;
UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
UartHandle.Init.Mode = UART_MODE_TX_RX;
UartHandle.Init.Instance = USART1;
HAL_UART_Init(&UartHandle);


How does this work? What part of C syntax do I have to learn, to understand where did that UartHandle.Init.xxx come from? Is it possible to do it "the long way", as I planned to? If I don't fill every datafield of HandleTypeDef, where do they get initialized?

PS. I am not using STM32 recommended IDEs or CubeMX, working on Linux, using PlatformIO. Board: STM32F746 discovery kit

PPS. I am really unsure whether to put this question here or on electronics stack. Please correct me or move the question there if it's not suitable for this stackexchange.


Solution

  • How does this work? What part of C syntax do I have to learn, to understand where did that UartHandle.Init.xxx come from?

    This is basic C struct syntax. The USART_HandleTypeDef struct contains an instance of a USART_InitTypeDef struct named Init. You can think of it as a nested struct. You can reference the members of nested structs with repeated '.'s. Note that, the Init member is NOT a pointer to a USART_InitTypeDef struct. It's literally a complete USART_InitTypeDef instance contained within a USART_HandleTypeDef instance.

    Is it possible to do it "the long way", as I planned to?

    Yes, except your code contains an error. You need to make the assignment like this.

    UsartHandle.Init     = UsartInit;    // Note no `&`
    

    Remember that the Init member of USART_HandleTypeDef is not a pointer but a complete struct. Therefore you need to assign it with a complete struct, not a pointer.

    But realize that when you define your UsartInit variable, you are allocating space for an instance of the struct. If UsartInit is a function local variable then that space is likely on the stack. Your initialization statements are initializing your copy of the struct. Then when you assign UsartInit to UsartHandle.Init the compiler creates code that will copy the entire contents of the struct. After the copy, if your UsartInit is a local variable, it will go out of scope and be deallocated.

    It's really not necessary to define and allocate space for your own USART_InitTypeDef struct and then copy the entire struct into UsartHandle.Init. UsartHandle already contains space allocated for its USART_InitTypeDef member. So it's more efficient to simply initialize the UsartHandle.Init member directly, like the ST example code does.

    If I don't fill every datafield of HandleTypeDef, where do they get initialized?

    You don't need to fill every datafield of USART_HandleTypeDef. Refer to the HAL Reference Manual to learn what you need to initialize. You probably need to initialize only the Instance and Init members. The remaining members are used internally by the HAL USART driver and they will be initialized and used by the driver functions (you can think of them as private variables if that helps). The designer of the API named that struct member "Init" as a cue to you that this is what you need to initialize. The ST example code provides further evidence of what you need to initialize.

    [Several experienced developers on Stack Overflow advise against using the ST HAL and encourage people to develop their own drivers based on the device's Reference Manual. Realize that these developers have years of experience, they've worked with a variety of microcontroller families and peripherals, and they're capable of understanding the Reference Manual and writing drivers from scratch. I agree that the ST HAL adds some bloat that might be detrimental to some applications. But I disagree that beginners should avoid using the ST HAL. The ST HAL works well enough for many applications and it is easier for beginners to use than writing their own drivers from scratch (especially given the many examples provided with the HAL).]