Fri, Dec 31, 21, drivers for low/high-level drivers
This is a draft, the content is not complete and of poor quality!

readme

developing

stm32Cube

drone-stm32f4-hal

Drone OS hardware abstraction layer (HAL) for STM32F4 micro-controllers.

Usage

Add the crate to your Cargo.toml dependencies:

[dependencies]
drone-stm32f4-hal = { git = "https://github.com/rmja/drone-stm32f4-hal", features = ["..."] }

A feature maps to a peripheral driver. There are the following low-level drivers:

  • rcc Type safe clock configuration model, and a rcc, pwr, and flash driver.
  • gpio Type safe pin-setup configuration primitives.
  • dma Dma primitives for uniform configuration across other, dma-dependent drivers.

There are the following more high-level drivers:

  • exti An external pin-interrupt driver.
  • spi Dma driven, future based spi driver.
  • uart Dma driven, future based uart driver.
  • fmc External SDRAM driver.

RCC

A necessary but often complicated task when starting a new embedded application is to correctly configure the various clocks within the mcu. The rcc feature contains two parts:

  1. A clock tree configuration model, and
  2. a set of drivers that effectuate the model on boot.

The following is the model configuration example from the uart example app:

use drone_stm32f4_hal::rcc::clktree::*;

pub const HSECLK: HseClk = HseClk::new(8_000_000);
pub const PLLSRC_HSECLK: PllSrcMuxSignal = PllSrcMuxSignal::Hse(HSECLK);
pub const PLL: Pll = PLLSRC_HSECLK.to_pllsrc(8).to_pll(360, 2, 8);
pub const SYSCLK_PLL: SysClkMuxSignal = SysClkMuxSignal::Pll(PLL.p);
pub const SYSCLK: SysClk = SYSCLK_PLL.to_sysclk();
pub const HCLK: HClk = SYSCLK.to_hclk(1);
pub const PCLK1: PClk1 = HCLK.to_pclk1(4);
pub const PCLK2: PClk2 = HCLK.to_pclk2(2);

The model desribes the following configuration:

  • The mcu board is equipped with an 8MHz crystal wired to the mcu, and we want to use it as the high-speed oscillator (HSE).
  • The HSE should be used as signal source for the internal PLL.
  • The PLL has two output signals: PLL_P and PLL_Q. With the HSE signal source, configure the pll outputs with the parameters m, n, p, and q as follows:
    • PLL_P = ((HSECLK / 8) * 360) / 2 = 180MHz
    • PLL_Q = ((HSECLK / 8) * 360) / 8 = 45MHz
  • Use the PLL’s PLL_P clock signal as source for the sysclk.
  • Define SYSCLK to be equal to the selected sysclk signal. This means that this model will run the mcu at 180MHz.
  • Let the AHB bus, memory, dma, etc. sourced with HCLK=SYSCLK/1, i.e. run at the same frequency with a prescaler of 1.
  • Let the low speed peripheral bus run at PCLK1=HCLK/4=45MHz.
  • Let the high speed peripheral bus run at PCLK2=HCLK/2=90MHz.

Note that the configuration is type safe and that parameters are verified at compile time, ensuring that only valid parameters are passed to the RCC configuration registers, and that it is guaranteed that the mcu is not overclocked.

The model is applied as follows (again from the uart example app):

use drone_stm32f4_hal::rcc::{
  periph_flash,
  periph_pwr,
  periph_rcc,
  traits::*,
  Flash, Pwr, Rcc, RccSetup,
};

thr.rcc.enable_int();

let rcc_setup = RccSetup {
  rcc: periph_rcc!(reg),
  rcc_int: thr.rcc,
};
let rcc = Rcc::init(rcc_setup);
let pwr = Pwr::with_enabled_clock(periph_pwr!(reg));
let flash = Flash::new(periph_flash!(reg));

let hseclk = rcc.stabilize(consts::HSECLK).await;
let pll = rcc.select(consts::PLLSRC_HSECLK, hseclk).stabilize(consts::PLL).await;
let hclk = rcc.configure(consts::HCLK);
let pclk1 = rcc.configure(consts::PCLK1);
let pclk2 = rcc.configure(consts::PCLK2);
pwr.enable_overdrive();
flash.set_latency(consts::HCLK.get_wait_states(VoltageRange::HighVoltage));
swo::flush();
swo::update_prescaler(consts::HCLK.f() / log::baud_rate!() - 1);
rcc.select(consts::SYSCLK_PLL, pll.p());

A lot is going on here, so one thing at a time. We first enable rcc interupt in the NVIC and then we initialize the rcc, pwr, and flash drivers from their respective peripheral mappings.

When the drivers are ready, we can start applying our model: We start by stabilizing the HSE clock, meaning that 1) we configure it according to the HSECLK model configuration, and 2) we start it. Stabilizing a clock is an asynchronous operation, so we need to await its completion. The stabilization returns a ConfiguredClk<HseClk> where ConfiguredClk<Clk> is a zero-overhead indicator that tells us that the clock is ready to use (recall that consts::HSECLK is of type HseClk).

Moving on, we can now select the HSE clock signal as the source for our PLL. The select() method requires a ConfiguredClk<Clk> so we are guaranteed that the source clock has actually stabilized prior to selecting it as input. Only after that has happened are we able to stablizie the PLL and wait for it to become ready. After this, we can easily configure the peripheral clocks (This could have been done at any time, so there is no need to inforce any guarantees on prior stabilization of clocks).

The next couple of lines enables over-drive (available in e.g. stm32f429) for high-speed operation, sets the correct flash latency for the mcu in the specified voltage range, configures the swo for logging. Lastly we are ready to select the PLL’s PLL_P output as the source for the sysclk, effectively setting the desired 180MHz mcu speed.

GPIO

The gpio feature includes a set of types that makes it easy and safe to configure ports and their respective pins. Consider the following example that configures pin A5 into alternate-function mode, with push/pull type, and for high speed operation.

use drone_stm32f4_hal::gpio::{prelude::*, GpioHead};

let gpio_a = GpioHead::with_enabled_clock(periph_gpio_a_head!(reg));
let pin_sck = gpio_a.pin(periph_gpio_a5!(reg))
  .into_alternate()
  .into_pushpull()
  .with_speed(GpioPinSpeed::VeryHighSpeed);

The pin_sck has type GpioPin<GpioA5, AlternateMode<Af>, PushPullType, NoPull> where Af is any of the alternate function marker types PinAf0,...,PinAf15. It is not needed to explicitly specify the alternate function, as it is included in the function prototypes for any drivers that uses a pin, and so its type information flows “backwards” into the configuration of the pin.

The clock for gpio_a is enabled when it is constructed as the clock is required when configuring the gpio. The clock should be explicitly disabled if so desired. This is unsafe, as special care should be taken if operating with disabled gpio clocks:

unsafe {
  port_a.disable_clock(); // Explicitly disable clock - use with care!
}

DMA

The dma driver is simple, but includes type safety features for other drivers. Consider the following lines of code from the uart example:

let dma1 = DmaCfg::with_enabled_clock(periph_dma1!(reg));
let rx_dma = dma1.ch(DmaChSetup::new(periph_dma1_ch5!(reg), thr.dma_1_ch_5));

rx_dma has type DmaChCfg<Dma1Ch5, DmaStCh, DmaInt> where Dma1Ch5 means “DMA1 stream 5”. ST has changed the nomenclature for newer mcu’s, and Drone OS uses the new naming scheme. The F4 series operate with streams and channels. We call the channels within a stream a “stream channel” to avoid confusion.

DmaStCh may be any of DmaStCh0,...,DmaStCh7 as there 8 possible stream channels within a stream. As for the alternate function mode in the gpio driver, the actual stream channel is not explicitly specified, as this information flows back into the type of rx_dma when the variable is actually used.

EXTI

The exti driver is similar to the smartoris-exti driver, but with type safety from the gpio pin configuration ensuring that interrupts for a given pin is configured on the correct exti peripheral. There is a working Drone OS example application in the examples folder:

thr.exti_2.enable_int();

let syscfg = Syscfg::with_enabled_clock(periph_syscfg!(reg));
let exti = ExtiDrv::new(periph_exti2!(reg), thr.exti_2, &syscfg).into_rising_edge();

let gpio = GpioHead::with_enabled_clock(periph_gpio_i_head!(reg));
let pin = gpio.pin(periph_gpio_i2!(reg)).into_input().into_pushpull().into_pulldown();

let line = exti.line(&pin);

let stream = line.create_saturating_stream();
exti.listen();

while let Some(tick) = stream.next().await {
  // Rising edge was triggered
}

The exti interrupt is first enabled in the nvic. The exti driver is then initialized, after which the pin is configured as input. The exti driver provides the line() function returning an ExtiLine struct, from which one can create a stream of events. We start to listen when the stream is ready to consume the interrupts.

SPI

The spi driver provides future based spi transfers using dma. There is a working Drone OS example application in the examples folder. The driver is initially setup like the following:

thr.spi_1.enable_int();
thr.dma_2_ch_2.enable_int();
thr.dma_2_ch_3.enable_int();

let gpio_a = GpioHead::with_enabled_clock(periph_gpio_a_head!(reg));
let pin_sck = gpio_a.pin(periph_gpio_a5!(reg))
  .into_alternate()
  .into_pushpull()
  .with_speed(GpioPinSpeed::VeryHighSpeed);
let pin_miso = gpio_a.pin(periph_gpio_a6!(reg))
  .into_alternate()
  .into_pushpull()
  .with_speed(GpioPinSpeed::VeryHighSpeed);
let pin_mosi = gpio_a.pin(periph_gpio_a7!(reg))
  .into_alternate()
  .into_pushpull()
  .with_speed(GpioPinSpeed::VeryHighSpeed);

let dma2 = DmaCfg::with_enabled_clock(periph_dma2!(reg));
let miso_dma = dma2.ch(DmaChSetup::new(periph_dma2_ch2!(reg), thr.dma_2_ch_2));
let mosi_dma = dma2.ch(DmaChSetup::new(periph_dma2_ch3!(reg), thr.dma_2_ch_3));

let pins = SpiPins::default().sck(pin_sck).miso(pin_miso).mosi(pin_mosi);
let setup = SpiSetup::new(
    periph_spi1!(reg),
    thr.spi_1,
    pins,
    pclk2,
    BaudRate::Max(7_700_000),
);
let spi = SpiDrv::init(setup).into_master(miso_dma, mosi_dma);

The spi interrupt is first enabled in the nvic before the spi pins are configured. The base driver does not handle chip selection, and it is up to the user to correctly select the desired chip before transferring on the spi bus (see below). The dma and its channels matching the used spi peripheral are then configured. A SpiSetup structure containing all the parameters for the driver are created with the spi pins to verify that the pins actually map to the spi peripheral. The peripheral clock is also specified together witht the maximum allowed baud rate.

Chip Selection

Chip selection is not an integrated part of the spi driver, but a small chip controller shim is included in the driver. It handles simple select/deselect like the following:

use drone_stm32f4_hal::spi::chipctrl::*;

let pin_cs = gpio_b.pin(periph_gpio_b7!(reg))
  .into_output()
  .with_speed(GpioPinSpeed::HighSpeed);

let mut chip = SpiChip::new_deselected(pin_cs);
let selection = spi_master.select(&mut chip);
// Do some communication...
drop(selection); // drop() deselects chip.

It extends the spi master driver with the select() method which returns a guard that deselects the chip when dropped.

Communication

The communication can be done using the three methods write(), read(), and xfer():

let selection = spi_master.select(&mut chip);
let tx_buf = [1, 2, 3, 4].as_ref();
let mut rx_buf = [0;4];
spi_master.write(tx_buf).root_wait();
spi_master.read(&mut rx_buf).root_wait();
spi_master.xfer(tx_buf, &mut rx_buf).root_wait();
drop(selection); // drop() deselects chip.

The write() function simply writes the buffer and discards all bytes received during the write, the read() method emits 0 on the spi bus to “clock out” the selected chip. The received bytes are written to the provided buffer. xfer() performs a full duplex transfer (the two buffer slices must have the same size).

UART

The uart driver uses any of the stm32 uart periperals together with their corresponding dma rx/tx streams to achieve asynchronous read and write operations with minimal cpu overhead.

There is a working Drone OS example application in the examples folder. The driver is initially setup like the following:

thr.usart_2.enable_int();
thr.dma_1_ch_5.enable_int();
thr.dma_1_ch_6.enable_int();

let gpio_a = GpioHead::with_enabled_clock(periph_gpio_a_head!(reg));
let pin_tx = gpio_a.pin(periph_gpio_a2!(reg))
  .into_alternate()
  .into_pushpull()
  .with_speed(GpioPinSpeed::VeryHighSpeed);
let pin_rx = GpioPin::from(periph_gpio_a3!(reg))
  .into_alternate()
  .into_pushpull()
  .with_speed(GpioPinSpeed::VeryHighSpeed);

let dma1 = DmaCfg::with_enabled_clock(periph_dma1!(reg));
let rx_dma = dma1.ch(DmaChSetup::new(periph_dma1_ch5!(reg), thr.dma_1_ch_5));
let tx_dma = dma1.ch(DmaChSetup::new(periph_dma1_ch6!(reg), thr.dma_1_ch_6));

let uart_pins = UartPins::default().tx(pin_tx).rx(pin_rx);

let setup = UartSetup::init(periph_usart2!(reg), thr.usart_2, pclk1);
let uart_drv = UartDrv::init(setup);

The usart interrupt is first enabled in the nvic. The rx/tx pins are then configured together with the dma’s. A uart_pins variable is created that is used later when initializing the rx/tx operation. It should be noted that it is the assigned uart_pins that ultimately decides the alternate function of the two pins. The usart peripheral, the usart thread, and the configured periheral clock to the UartSetup initialization function, creating a uart setup structure. The default setup is 9600/8N1. Any other setting can be specified on the public properties of setup (the setup variable must be declared mut in this case). The setup is finally passed to the driver initialization function.

The rx and tx operation of the driver are completely separated, and each of them needs further initiation before use.

TX Operation

Completing the setup for tx operation looks like this:

let tx_setup = DmaChSetup::init(periph_dma1_ch6!(reg), thr.dma_1_ch_6);
let mut tx_drv = uart_drv.into_tx(tx_dma, &uart_pins);

With tx_drv we are now finally able to do some communication. The uart is not started at this time. For this we must invoke the start() function which returns a guard that stops the transmitter when dropped. We can start writing to the uart after it is started:

let mut tx = tx_drv.start();
tx.write(b"Hello World!\n").await;
tx.write(b"Drone OS is awesome!\n").await;
tx.flush().await;
drop(tx);

The driver supports multiple subsequent calls to the write() function which is an asynchronous call that returns a future that completes when the dma controller is ready to receive more bytes to be written. In this way it is possible to fully saturate the uart even for fast baud rates, without any “spacings” due to a software design with multiple write() calls. In the example this have the effect that the \n character after the Hello World! write is completely adjacent to the D when expecting the uart tx line. As a consequence of this design: When write() completes this does not mean that the data have actually been sent. For this we use flush() which, when returned tells that all data are completely transmitted at which time it is safe to stop the uart.

RX Operation

The rx part of the driver is initialized like the following:

let rx_setup = DmaChSetup::init(periph_dma1_ch5!(reg), thr.dma_1_ch_5);
let mut rx_drv = uart_drv.into_rx(rx_setup, &uart_pins);

Again, as for the tx part, the driver is not yet ready to receive. For this we need to start the receiver:

let rx_ring_buf = vec![0; 256].into_boxed_slice();
let mut rx = rx_drv.start(rx_ring_buf);

loop {
  let mut buf = [0; 16];
  match rx.read(&mut buf).await {
    Ok(n) => {
      // Data is available in the slice &buf[..n].
    }
    Err(e) => {
      // The ring buffer has overflowed.
    }
  };
}

drop(rx);

The driver is started with the ring buffer rx_ring_buf. This buffer is internally assigned to the dma controller, and must be sufficiently large to store received bytes before they are consumed using the read() function by the user. read() returns a future with the number of bytes read. This number may be smaller than the size of the provided read buffer. It returns immediately if there is any data readily available in the ring buffer, or in the future as soon as there is data available. More specifically for n = read(&buf).await?, we have 1 <= n <= len(buf). This is not using busy waiting on data to become available, but is achieved internally by registering a [Drone OS] fiber that completes when any data becomes available in the dma controller and therefore the ring buffer.

The read() method may return an error if read() is not called fast enough, in which case it can happen that the ring buffer has overflowed since the last call to read().

TX and RX Operation

The two previous examples have shown tx-only and rx-only operation. One can split the driver into both a tx and rx driver as follows:

let tx_setup = DmaChSetup::init(periph_dma1_ch6!(reg), thr.dma_1_ch_6);
let rx_setup = DmaChSetup::init(periph_dma1_ch5!(reg), thr.dma_1_ch_5);
let (mut tx_drv, mut rx_drv) = uart_drv.into_trx(tx_setup, rx_setup, &uart_pins);

FMC

The fmc feature provides an sdram driver. The driver ensures that all required pins are configured correctly, after which one can obtain a &mut [T] slice of the address mapped memory. There is an example app where the majority of the configuration is setting up pins into the alternate function mode. After that, the initialization and use of the driver is straight forward:

let sdram_pins = FmcSdRamPins::default()
  .sdclk(gpio_g.pin(periph_gpio_g8!(reg)).into_alternate().with_speed(GpioPinSpeed::HighSpeed))
  ...;
let address_pins = FmcSdRamAddressPins::default()
  .a0(gpio_f.pin(periph_gpio_f0!(reg)).into_alternate().with_speed(GpioPinSpeed::HighSpeed))
  ...;
let data_pins = FmcSdRamDataPins::default()
  .d0(gpio_d.pin(periph_gpio_d14!(reg)).into_alternate().with_speed(GpioPinSpeed::HighSpeed))
  ...;
let bank_pins = FmcSdRamBankPins::default()
  .ba0(gpio_g.pin(periph_gpio_g4!(reg)).into_alternate().with_speed(GpioPinSpeed::HighSpeed))
  ...;
let mask_pins = FmcSdRamByteMaskPins::default()
  .nbl0(gpio_e.pin(periph_gpio_e0!(reg)).into_alternate().with_speed(GpioPinSpeed::HighSpeed))
  ...;

let fmc = FmcDrv::init_sdram(SdRamSetup::new_bank2(periph_fmc!(reg), consts::SDRAM_CFG, hclk), sdram_pins, address_pins, data_pins, bank_pins, mask_pins);
let ram = fmc.bank2_slice::<usize>();

for i in 0..ram.len() {
    ram[i] = i;
}

for i in 0..ram.len() {
    assert_eq!(i, ram[i], "SDRAM sanity check error!");
}

The driver ensures that the correct number of pins is mapped corresponding to the sdram consts::SDRAM_CFG configuration parameters, and that they are set into alternate function mode.

Supported Devices

stm32_mcu
stm32f401
stm32f405
stm32f407
stm32f410
stm32f411
stm32f412
stm32f413
stm32f427
stm32f429
stm32f446
stm32f469

References

License

Licensed under either of

  • Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
  • MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

The following wiki, pages and posts are tagged with

TitleTypeExcerpt
2021-09-26-thesis-indoor-drone.md post After launching a file, call the following services to initialize the drone in Gazebo and the Particle Filter algorithm
Udemy qt5 course by Packt Publishing post Tue, Oct 26, 21, Dive into custom model-views, showcasing the power and flexibility of the mvodel view architecture, with extensive www applications
Pilot handbook + drone resource wiki post Tue, Nov 02, 21, pilot's handbook summarized on top of key cocnepts from rapa drone-resource
Single rotor drone post Thu, Nov 04, 21, single rotor air vehilce with rudder and flap to navigate
Pilot's preflight checklist FAA post Tue, Nov 09, 21, preflight checklist with data mining, d3 visualization and google sheet implementation
final-project post Sat, Nov 27, 21, motion planning dashboard with django vue and fcnd
motion planning dashboard hardware setup post Wed, Dec 01, 21, master, raspi, database, video-streaming, api server setup
px4 mavlink and qgc integration with 4gremoteoperation post Tue, Jan 18, 22, powerful 3d simulation environment for autonomous robots suitable for testing object-avoidance and cv
Airlink by skydrone, youtube post Friday, airlink for mission flight, LTE connectivity and dl-ready
set up with raspi connected to fc post Tue, Jan 25, 22, ardupilot documentation
drone programming primer for software development post Mon, Jan 31, 22, flight stack with firmware middleware and api
runcam with fc connection post Tue, Feb 15, 22, runcam split 2 with fc
my new fixed wing AR Wing Pro, ready for dji HD fpv system post Thu, Feb 17, 22, setup guide after opening the package
realflight 7 setup and console game post Thu, Feb 24, 22, flight simulation with real flight 7
uavmatrix's cast pro docs post Tue, Mar 01, 22, another way to integrate devices to gcs
firmtech7 of naver cafe raspi drone project post Thu, Mar 03, 22, using raspi as fc to control small drone
Garupner Polaron ex post Sun, Mar 06, 22, polaron 2 channels dc charger
svg visualization messages and parameters post Mon, Mar 07, 22, organized structure and tree map of px4 messages and parameters
lx network, airlink, gcs and data transmission on smart radio, rf mesh and quantum encryption post Tue, Apr 26, 22, all about setup and how it operates and managed
Advanced Features page
Advanced Configuration page
Advanced Flight Controller Orientation Tuning page
rflysim tltr page
Bootloader Update page
Bootloader Flashing onto Betaflight Systems page
Compass Power Compensation page
drones.md page my drones I work with and at my disposal.
ESC Calibration page
Flight Termination Configuration page
my 100 supporters page my freelancers I work with since 2018.
index.md page My recent projects are leveraging generative AI across various domains, yielding significant achievements. These encompass Digital Twin, Voice-to-Command, RA...
Land Detector Configuration page
About this site and its author portfolio My portofolio site and its mission statement
🔭AIOT projects page summary.
contents deploy automation page Pilot test on the automation prototype.
pixhawk apm racing drone page summary.
Challenger Engineering Project page summary.
pixhawk tools page rFlyeval project details where Matlab Mathwor Simulink were used for complete process of UAV and UAS.
Korea drone companies page summary.
Racing drone, attck drone page summary.
Django Django Two scoops page summary.
docker learning curve page summary.
🔭 Ground Control Station web-based approach page summary.
gitlab page summary.
🔭lora monitoring app page summary.
🔭 MQTT pages page summary.
My course list page my course list from udemy, udacity, NCS and other sources
Nextcloud page summary.
Automation pipeline page summary.
Pixhawk 4 page summary.
Pixhawk overview page summary.
🔭raspberry pi project page summary.
🔭yuneec realsense obstacle avoidance page summary.
ROS topic for micro control page summary.
🔭 RQt-based gui page summary.
🔭sensor detection page RealSense with Open3D
🔭Serializer with API page summary.
Rules of thumb page Contact me for any support issues.
web-dev ops pages page
🔭 Webrtc page summary.
Parameter Reference page
Finding/Updating Parameters page
Precision Landing page
pixhawk tools advanced page rFlyeval project details where Matlab Mathwork Simulink were used for complete process of UAV and UAS.
pixhawk tools page rFlyeval project details where Matlab Mathwor Simulink were used for complete process of UAV and UAS.
RTK GPS page GNSS/GPS systems
Iridium/RockBlock Satellite Communication System page
Static Pressure Buildup page # Static Pressure Buildup Air flowing over an enclosed vehicle can cause the *static pressure* to change within the canopy/hull. Depending on the location of holes/leaks in the hull, you can end up with under or overpressure (similar to a wing). The change in pressure can affect barometer measurements, leading...
Air Traffic Avoidance: ADS-B/FLARM page
Air Traffic Avoidance: UAS Traffic Management (UTM) page
Using the ECL EKF page