2024 - 2026 BSP Structure

A brief overview of the BSP structure and motivation, intended to be both a refresher and a summary of the design decisions from .

What is a BSP?

From Wikipedia:

In embedded systems, a board support package (BSP) is the layer of software containing hardware-specific boot firmware and device drivers and other routines that allow a given embedded operating system, for example a real-time operating system (RTOS), to function in a given hardware environment (a motherboard), integrated with the embedded operating system.

Why do we need a new BSP?

The original BSP, designed for our previous two solar vehicles Lonestar and Daybreak, had a myriad of issues. Firstly, having two different systems implement two different versions of the BSP, copying and pasting features from one another, was a pain in the ass. Each system introduced two different sets of bugs that would have to be debugged at competition, since some edge cases were not found until then. Furthermore, many of the BSP functions existed purely to wrap calls to the STM StdPeriph library functions (with some very minor additional processing).

Since that version of the BSP was designed, a lot has changed.

  1. We now have four different systems to support, all using the same chipset (STM32F4 and STM32L4) and all using similar hardware (see ). Thus, a move to a shared BSP was necessary to keep bugs and parallel work to a minimum (instead of having four different systems write the same functionality with different code, have the electrical team come up with one shared library that everyone uses).

  2. Secondly, STM32 now has more enhanced support for HAL, the Hardware Abstraction Library. This library provides greater functionality than its predecessor (StdPeriph) which means that if our BSP’s purpose stayed the same, it would merely wrap calls to the HAL, essentially eliminating the need for it at all. Thus, the design motivation and function of the BSP have slightly shifted.

What is the new BSP design motivation?

The new BSP’s motivations are as follows:

  1. Decrease response time of hardware peripherals by relying on interrupts (and later, DMA) to retrieve info for the software on the edge of a transaction (rather than polling).

  2. Utilize FreeRTOS queue’s to reduce thread-safety considerations in user driver and application code.

  3. Utilize the pre-generated HAL functions from STM for a more tried and tested lower-level library, as well as being as hardware-agnostic as possible (in order to support both the L4 and F4 microcontrollers).

  4. Share the BSP code such that any LHRS system can import and use it with ease.

What is the new BSP structure?

Every BSP file is fundamentally based on some transmit/receive protocol. This includes SPI, I2C, UART, CAN, ADC, and similar hardware peripherals that we commonly need to transmit with/receive from.

A standard BSP file will have three main functionalities:

Initialization: A HAL struct containing config and initialization information will be passed down from the user (driver level) to the BSP_Peripheral_Init function, which will then pass it down to the HAL. This is the simplest way to initialize a peripheral with the required settings without making the init structure far too complex.

Receive: A receive will look like this:

  1. The user will pass in a static FreeRTOS queue pointer that they have initialized to their required size, along with some message identifier that they would like to receive (for CAN, this may be a CAN ID, whereas for SPI it may be a certain chip select device) to the BSP_Peripheral_Receive function.

  2. The hardware peripheral, upon receiving any message, will use some data structure to find the correct queue to push the received message into. This will happen on the edge of an RX interrupt (increasing response time), but since FreeRTOS queue interactions are considered critical sections, pushing the message is OK.

  3. From the user perspective, the guarantee that the BSP makes is that the requested message will arrive in the queue as soon as possible. Blocking and non-blocking variants are completely user-dependent in this scheme; a user can choose to pend for the message to arrive in their queue, or just continue and check the queue later.

Transmit: A transmit will look like this:

  1. The user will run the BSP_Peripheral_Transmit function with the required arguments/data to be transmitted.

  2. Internally to the BSP_Peripheral.c module, a FreeRTOS queue (or multiple, depending on the usecase) will exist to transmit messages.

    1. If there is no transmission in progress, the BSP will bypass the queue and directly send the message to the hardware peripheral.

    2. If a transmission is in progress, the BSP will append the latest requested message to be transmitted to the queue as a buffering mechanism.

  3. On a TX_Complete interrupt (which means the hardware has just completed a transmission), the next message (if any) will be taken out of the TX queue and put in the hardware peripheral. This increases response time as messages are pipelined to the hardware as soon as the peripheral is ready to send another message.

  4. This loop will continue until the queue is empty.

Implementation will differ based on the peripheral, but now you should understand the basic intention behind the BSP. Credit to Tianda Huang for developing this scheme, and to Connor Leu, Lakshay Gupta and Nathaniel Delgado for developing it further.