stm32f4xx-hal

repository·master·Indexed 20 days ago

https://github.com/stm32-rs/stm32f4xx-hal

A hardware abstraction layer (HAL) for the STM32F4 series microcontrollers, providing a unified API across different models using feature gates. It implements a partial set of embedded-hal traits and supports a wide range of MCUs including stm32f401, stm32f407, stm32f411, and stm32f429. The crate includes support for peripherals such as ADC, CRC32, DMA, and FSMC LCD, with optional features for RTICv1/v2, defmt, CAN, I2S, and USB OTG.

Tokens
12.8K
Snippets
46
Records
67
Agent score
72%

What's inside stm32f4xx-hal

  1. Overview of stm32f4xx-hal

    master

    The stm32f4xx-hal crate provides a multi-device hardware abstraction layer (HAL) for the STMicroelectronics STM32F4 series microcontrollers. It sits on top of the peripheral access API and implements a partial set of embedded-hal traits.

    Device selection is managed via feature gates. Instead of using different crates for every model, you enable the feature corresponding to your specific MCU. Supported models include:

    • stm32f401, stm32f405, stm32f407, stm32f410, stm32f411, stm32f412
    • stm32f413, stm32f415, stm32f417, stm32f423, stm32f427, stm32f429
    • stm32f437, stm32f439, stm32f446, stm32f469, stm32f479

    If a Board Support Crate (BSP) exists for your specific board in the stm32-rs organization, it will likely already include stm32f4xx-hal with the correct features pre-configured.

  2. Set up a stm32f4xx-hal project manually

    master

    To set up a project from scratch:

    1. Initialize Project: Run cargo init.
    2. Add Dependencies: Update your Cargo.toml with the required crates. You must specify your specific MCU as a feature in stm32f4xx-hal.
    3. Configure Linker and Memory: Copy .cargo/config.toml and memory.x from the stm32f4xx-hal repository to your project. Crucial: Ensure the memory sizes in memory.x match your specific MCU's datasheet.
    4. Implement Logic: You can use examples/delay-syst-blinky.rs from the HAL repository as a template for a basic LED blinky application.
    [dependencies]
    embedded-hal = "1.0"
    nb = "1"
    cortex-m = "0.7"
    cortex-m-rt = "0.7"
    panic-halt = "1.0"
    
    [dependencies.stm32f4xx-hal]
    version = "0.23.0"
    features = ["stm32f407"] # Replace with your specific MCU model
  3. Fast start with stm32-template

    master

    You can quickly scaffold an empty project using cargo generate with the stm32-template. Note that you will still need to specify your exact chip name during the generation process.

    $ cargo generate --git https://github.com/burrbull/stm32-template/
  4. Use the HAL's embedded-hal compatibility layers

    master

    The crate re-exports two versions of the embedded-hal traits to ensure compatibility with the wider Rust embedded ecosystem:

    • hal: The current embedded-hal version.
    • hal_02: The embedded-hal version 0.2.x.

    Additionally, the crate re-exports nb (non-blocking) and its block macro for handling non-blocking operations.

    use stm32f4xx_hal as hal;
    use hal::hal::blocking::spi::Transfer;
    use hal::block;
    
    // Example of using the block macro with a non-blocking API
    // block!(peripheral.read())
  5. Split and Rejoin Serial objects

    master

    A Serial object can be decomposed into its constituent Tx and Rx parts using .split(). This allows you to pass the transmitter and receiver to different tasks or threads.

    Conversely, if you have a Tx and an Rx object, you can recombine them into a single Serial object using .join().

    // Splitting
    let (tx, rx) = serial.split();
    
    // Rejoining
    let serial = rx.join(tx);
  6. Configure the SAI Serial Audio Interface protocol

    master

    The Protocol struct defines the audio transmission parameters for an SAI instance. You must specify the synchronization scheme, word size, slot size, and the number of slots (channels) per frame.

    Protocol Parameters

    • sync: The Synchronization mode (e.g., I2S, MSBJustified, LSBJustified, PCMShortFrame, or PCMLongFrame).
    • word_size: The number of bits used for audio data. Supported values: Bit8, Bit10, Bit16, Bit20, Bit24, Bit32.
    • slot_size: The number of bits transmitted per word. If not matching word_size, only Bit16 and Bit32 are allowed. If using a master clock, powers of two are recommended for integer ratios.
    • num_slots: The number of audio channels per frame. For non-PCM protocols, this must be 2 (stereo).
    let protocol = Protocol {
        sync: Synchronization::I2S,
        word_size: WordSize::Bit16,
        slot_size: SlotSize::Bit16,
        num_slots: 2,
    };
  7. Identify DMA Instances and Channels

    master

    The HAL uses several traits to represent the DMA hardware hierarchy:

    • Instance: Represents a DMA peripheral instance (e.g., DMA1, DMA2).
    • Channel: A marker trait representing a specific DMA channel.
    • DMASet<STREAM, const CHANNEL: u8, DIRECTION>: A safety trait that marks a specific combination of Stream, Channel, and Direction as valid for a given Peripheral. This prevents users from configuring invalid DMA mappings at compile time.
  8. How PwmInput works and its interrupt behavior

    master

    The PwmInput abstraction uses a timer's input capture channels to measure PWM signals. It configures the timer such that:

    1. TI1 is selected as the active input for both CC1 and CC2.
    2. CC1 is configured for rising edge capture (to detect the start of a cycle).
    3. CC2 is configured for falling edge capture (to detect the end of the pulse).
    4. The timer is set to Reset Mode using TI1FP1 as the trigger.

    Interrupts: The peripheral is configured to enable interrupts on the CC2 channel (cc2ie). Because of how the hardware handles the trigger and capture, you will receive interrupts at two distinct points in the PWM waveform. You must use is_valid_capture() to distinguish between a measurement of the pulse width and the start of a new period.

    // Example interrupt handler logic
    fn tim_cc2_interrupt_handler(monitor: &PwmInput<TIM8>) {
        // Check if this was a valid pulse capture or just the start of a new cycle
        if monitor.is_valid_capture() {
            let duty = monitor.get_duty_cycle();
            // Process duty cycle...
        }
    }
  9. Configure a Synchronous SAI sub-block

    master

    Synchronous sub-blocks are always configured as slaves and do not require their own clock/sync pins; they follow the signals of the other block in the SAI instance.

    • slave_rx(sd, protocol): Configures the block as a Slave Receiver. Requires the Sd (Serial Data) pin.
    • slave_tx(sd, protocol): Configures the block as a Slave Transmitter. Requires the Sd (Serial Data) pin.
  10. Convert I2C to non-blocking DMA mode

    master

    You can convert a standard blocking I2c instance into a non-blocking I2CMasterDma instance using one of three methods depending on your requirements:

    1. Full Duplex (TX and RX): Use use_dma to provide both a transmit stream and a receive stream.
    2. Transmit Only: Use use_dma_tx to provide only a transmit stream.
    3. Receive Only: Use use_dma_rx to provide only a receive stream.

    Requirements for non-blocking operation:

    • You must enable interrupts for the DMA streams used for transmit and receive.
    • You must enable the I2C error interrupt (I2Cx_ER).
    • You must call handle_dma_interrupt() and handle_error_interrupt() (from the I2CMasterHandleIT trait) within your interrupt service routines to manage the transfer lifecycle and callbacks.
    // Example: Converting to full duplex DMA
    let i2c_dma = i2c.use_dma(tx_stream, rx_stream);
  11. Configure and initialize Serial communication

    master

    To use serial communication, you can initialize a full Serial object, or just a Tx (transmitter) or Rx (receiver) component. This is done using the SerialExt trait implemented on UART instances.

    By default, the serial interface uses 8-bit data words (u8). If you need 9-bit data words, configure the Config with wordlength_9() and then use the with_u16_data() method to convert the resulting Serial<_, u8> into a Serial<_, u16> object.

    // Example: Initializing a full Serial instance
    let serial = dp.USART1.serial(
        (tx_pin, rx_pin), 
        config, 
        &mut rcc
    ).unwrap();
    
    // Example: Initializing only a Transmitter
    let tx = dp.USART1.tx(tx_pin, config, &mut rcc).unwrap();
    
    // Example: Initializing only a Receiver
    let rx = dp.USART1.rx(rx_pin, config, &mut rcc).unwrap();
  12. Configure an Asynchronous SAI sub-block

    master

    Asynchronous sub-blocks have their own set of clock pins. You can configure them as Master or Slave for either Transmission or Reception.

    Master Configuration

    Requires providing pins for Mclk (optional), Fs, Sck, and Sd.

    • master_rx(...): Configures as Master Receiver.
    • master_tx(...): Configures as Master Transmitter.

    Slave Configuration

    • slave_rx(...): Configures as Slave Receiver. Requires Fs, Sck, and Sd pins.
    • slave_tx(...): Configures as Slave Transmitter. Requires Fs, Sck, and Sd pins.

    All configuration methods require a Protocol and, for masters, a sample_freq (in Hertz) and the rcc::Clocks object.