Giter Club home page Giter Club logo

stm32f1xx-hal's Introduction

stm32f1xx-hal

HAL for the STM32F1 family of microcontrollers

Continuous integration crates.io Released API docs

Quick start guide

Embedded Rust development requires a bit more setup than ordinary development. For this guide, we'll assume you're using a stm32 blue pill board (shown below), but if you have another f1 microcontroller, you should be able to adapt it.

blue pill pinout

You will also need a debug probe, for example an stlink v3 mini for programming and debugging. (There are many different STLink probes out there, all of them should work fine with the instructions given here, other JTAG or SWD debug probes will work as well but will need different software or configuration).

Installing software

To program your microcontroller, you need to install:

  • openocd
  • gdb-multiarch (on some platforms you may need to use gdb-arm-none-eabi instead, make sure to update .cargo/config to reflect this change)

Finally, you need to install arm target support for the Rust compiler. To do so, run

rustup target install thumbv7m-none-eabi

Setting up your project

Create a new Rust project as you usually do with cargo init. The hello world of embedded development is usually to blink an LED and code to do so is available in examples/blinky.rs. Copy that file to the main.rs of your project.

You also need to add some dependencies to your Cargo.toml:

[dependencies]
embedded-hal = "0.2.7"
nb = "1"
cortex-m = "0.7.6"
cortex-m-rt = "0.7.1"
# Panic behaviour, see https://crates.io/keywords/panic-impl for alternatives
panic-halt = "0.2.0"

[dependencies.stm32f1xx-hal]
version = "0.10.0"
features = ["rt", "stm32f103", "medium"]

If you build your project now, you should get a single error: error: language item required, but not found: eh_personality. This unhelpful error message is fixed by compiling for the right target.

We also need to tell Rust how to link our executable, and how to lay out the result in memory. To accomplish all this, copy .cargo/config and memory.x from the stm32f1xx-hal repo to your project.

cargo build

If everything went well, your project should have built without errors.

Programming the microcontroller

It is now time to actually run the code on the hardware. To do so plug your debug probe into the blue pill and start openocd using

openocd -f interface/stlink-v3.cfg -f target/stm32f1x.cfg

If you are not using an stlink V3, change the interface accordingly. For more information, see the embeddonomicon.

If all went well, it should detect your microcontroller and say Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints. Keep it running in the background.

We will use gdb for uploading the compiled binary to the microcontroller and for debugging. Cargo will automatically start gdb thanks to the .cargo/config you added earlier. gdb also needs to be told to connect to openocd which is done by copying .gdbinit to the root of your project.

You may also need to tell gdb that it is safe to load .gdbinit from the working directory.

  • Linux
    echo "set auto-load safe-path $(pwd)" >> ~/.gdbinit
  • Windows
    echo set auto-load safe-path %CD% >> %USERPROFILE%\.gdbinit

If everything was successful, cargo should compile your project, start gdb, load your program and give you a prompt. If you type continue in the gdb prompt, your program should start and the green led on the blue pill should start blinking.

Going further

From here on, you can start adding more code to your project to make it do something more interesting. For crate documentation, see docs.rs/stm32f1xx-hal. There are also a lot more examples available. If something is unclear in the docs or examples, please, open an issue and we will try to improve it.

Selecting a microcontroller

This crate supports multiple microcontrollers in the stm32f1 family. Which specific microcontroller you want to build for has to be specified with a feature, for example stm32f103.

If no microcontroller is specified, the crate will not compile.

You may also need to specify the density of the device with medium, high or xl to enable certain peripherals. Generally the density can be determined by the 2nd character after the number in the device name (i.e. For STM32F103C6U, the 6 indicates a low-density device) but check the datasheet or CubeMX to be sure.

  • 4, 6 => low density, no feature required
  • 8, B => medium feature
  • C, D, E => high feature
  • F, G => xl feature

For microcontrollers of the connectivity line (stm32f105 and stm32f107) no density feature must be specified.

Supported Microcontrollers

  • stm32f100
  • stm32f101
  • stm32f103
  • stm32f105
  • stm32f107

Trying out the examples

You may need to give cargo permission to call gdb from the working directory.

  • Linux
    echo "set auto-load safe-path $(pwd)" >> ~/.gdbinit
  • Windows
    echo set auto-load safe-path %CD% >> %USERPROFILE%\.gdbinit

Compile, load, and launch the hardware debugger.

$ rustup target add thumbv7m-none-eabi

# on another terminal
$ openocd -f interface/$INTERFACE.cfg -f target/stm32f1x.cfg

# flash and debug the "Hello, world" example. Change stm32f103 to match your hardware
$ cargo run --features stm32f103 --example hello

$INTERFACE should be set based on your debugging hardware. If you are using an stlink V2, use stlink-v2.cfg. For more information, see the embeddonomicon.

Using as a Dependency

When using this crate as a dependency in your project, the microcontroller can be specified as part of the Cargo.toml definition.

[dependencies.stm32f1xx-hal]
version = "0.9.0"
features = ["stm32f100", "rt"]

Documentation

The documentation can be found at docs.rs.

License

Licensed under either of

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.

stm32f1xx-hal's People

Contributors

andreysmirnov81 avatar apeng2012 avatar bors[bot] avatar burrbull avatar cs2dsb avatar dbrgn avatar disasm avatar eupn avatar fomys avatar gbip avatar geomatsi avatar hazelutf8 avatar horazont avatar imihajlow avatar jamwaffles avatar jounathaen avatar khrs avatar mitchmindtree avatar nankeen avatar samueltardieu avatar skammer avatar surban avatar texitoi avatar thalesfragoso avatar therealprof avatar thezoq2 avatar timokroeger avatar windfisch avatar yjh0502 avatar yuaxan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stm32f1xx-hal's Issues

Help Wanted: How to write/read I2C register

Hi,
Related to #24
As far as my understanding goes, I2C transactions are split into 3 parts (Talking about 7 bit addressing): The device address, the register address and then the data to/from that register (which can be multiple bytes).
Like in this image (from Texas Instruments - SLVA704).
image

The functions in this HAL, however, require 2 parameters: address and a byte buffer.
fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error>

So here are my two (related) questions:
1.) Which address is meant by the addr parameter? The device address or the register address?

2.) How do I write/read a value from -let's say- device 12 register 34?
Is addr = 12, the first element in buf[0] = 34 and the other bytes in buf the data from/to the register in this case?

Thanks and greetings!

Provide more low-level access to the timers (prescaler, read value)

Hi,
for my project I need to use a timer as a "periodic stopwatch", with a manually set prescaler and auto-reload-register (actually, uint32_max is fine for me).
I would measure time between two events by just reading out the counter and subtract it from the old counter value; if the u32 subtraction wraps, this would lead to the right result (this works as long as the delay between two events is less than 2^32 timer ticks).

For now, I've hacked in the functions I require here: master...Windfisch:timer_additions
Basically, this allows me to

  1. configure a timer with a manual prescaler and arr value (start_raw())
  2. read out the timer's current count (cnt())
  3. read the timer's peripheral clock's frequency (clk())

Do you think such an API would be a useful addition to the HAL, and do you have any suggestions on how to improve my quickly-hacked-together API?

Calulation of brr for Serial Devices with Clocksource PCLK1

The Calculation of the BRR Register is not correct if the clock is not pclk2. In serial.rs:130 the brr value is calculated with the clock rate from pclk2 even if the device is using pclk1:

let brr = clocks.pclk2().0 / baud_rate.0;

pclk2 is only used for USART1, the others use pclk1.
Quote from the reference manual p. 796 27.3.4 "Fractional baud rate generation":

Input clock to the peripheral (PCLK1 for USART2, 3, 4, 5 or PCLK2 for USART1)

If pclk1 and pclk2 have different clock frequencies the baud rate of the USART device is not as expected:

This would NOT work for USART2, 3, 4 and 5 but for USART1
let clocks = rcc.cfgr.sysclk(64.mhz()).pclk1(32.mhz()).pclk2(64.mhz()).freeze(&mut flash.acr);

This would work for USART2, 3, 4 and 5 but for USART1
let clocks = rcc.cfgr.sysclk(64.mhz()).pclk1(32.mhz()).pclk2(32.mhz()).freeze(&mut flash.acr);

Depending on the USART device the correct clock frequency has to be selected to calculate the brr register.

Feature: fmt::Write for serial::Tx

Hi,
I'm new to this HAL, rather new to Rust and I still have a lot of confusion, so apologize if this does not belong here or is a stupid question.

I'm using the usart (like in the example) which provides the function Tx.write(byte). But there is no functionality to send a (formatted) string provided.

This can be implemented with something like:

use core::fmt::{self, Write};

struct Format_Tx {
    Tx :  stm32f1xx_hal::serial::Tx<USART1>,
}

impl fmt::Write for Format_Tx {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        for byte in s.bytes() {
            block!(self.Tx.write(byte)).ok();
        }
        Ok(())
    }
}

This enables the write macro, if you create a Format_Tx "relay" struct. For example write!(my_format_tx, "Formatted {} String", 123);

Could stm32f1xx_hal::serial::Tx implement this?
Then writing strings to the serial out would be as easy as write!(mytx, "foobar"). I guess this is a common use case.

Regards Jounathaen

timer interrupt not called

This is a request for help, not a bug report.
Hope this is fine, from the readme looks like this is the place to ask. (also not sure if an issue of this crate or cortex_m or cortex_m_rt)

My issue: after managing to run the blink, the step Nยฐ2 is the "BlinkWithoutDelay".

the pseudocode:

last = 0
loop:
  if millis() - last > 1000:
    last = millis()
    led.toggle()

so my final (non working) code (you can see i am trying to use TIM2, but also tried with SYST with no success too)

//#![deny(warnings)] //  If the Rust compiler generates a warning, stop the compilation with an error.
#![deny(unsafe_code)]  //  Don't allow unsafe code in this file.
#![no_std]  // std and main are not available for bare metal software
#![no_main] 


use cortex_m_rt::{entry};
//use cortex_m_rt::{entry, exception, ExceptionFrame};
use stm32f1xx_hal::device::interrupt;
use nb::block;

use stm32f1xx_hal::{
    prelude::*,
    pac,
    timer::{Timer,Event},
    serial::{Serial},
};

use core::sync::atomic::{AtomicUsize, Ordering};

static COUNTER_MS: AtomicUsize = AtomicUsize::new(0);

#[entry]
fn main() -> ! {
    // Get access to the core peripherals from the cortex-m crate
    let mut cp = cortex_m::Peripherals::take().unwrap();
    // Get access to the device specific peripherals from the peripheral access crate
    let dp = pac::Peripherals::take().unwrap();

    // Take ownership over the raw flash and rcc devices and convert them into the corresponding
    // HAL structs
    let mut flash = dp.FLASH.constrain();
    let mut rcc = dp.RCC.constrain();
    let mut afio = dp.AFIO.constrain(&mut rcc.apb2);

    //free pin PB3, PB4 and PA15
    afio.mapr.disable_jtag();

    // Freeze the configuration of all the clocks in the system and store
    // the frozen frequencies in `clocks`
    let clocks = rcc.cfgr.freeze(&mut flash.acr);

    // Acquire the GPIO peripheral
    let mut gpioa = dp.GPIOA.split(&mut rcc.apb2);
    let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);

    let mut led1 = gpiob.pb3.into_push_pull_output(&mut gpiob.crl);
    let mut led2 = gpiob.pb4.into_push_pull_output(&mut gpiob.crl);
    led2.set_high();
    
    led1.set_high();

    //let mut timer = Timer::syst(cp.SYST, 1_000.hz(), clocks);
    //timer.listen(Event::Update);
    
    let mut timer = Timer::tim2(dp.TIM2, 1_000.hz(), clocks, &mut rcc.apb1);
    timer.listen(Event::Update);
    
    // USART1
    let txp = gpioa.pa9.into_alternate_push_pull(&mut gpioa.crh);
    let rxp = gpioa.pa10;
    let serial = Serial::usart1(
        dp.USART1,
        (txp, rxp),
        &mut afio.mapr,
        9_600.bps(),
        clocks,
        &mut rcc.apb2,
    );

    led1.set_low();

    // Split the serial struct into a receiving and a transmitting part
    let (mut tx, mut rx) = serial.split();

    // Wait for the timer to trigger an update and change the state of the LED
    let mut timer_start = 0;
    
    loop {
        
        let now = COUNTER_MS.load(Ordering::Relaxed);
        
        let diff = now - timer_start;
        if diff > 1_000 {
            timer_start = now;
            led1.toggle();
        }
        block!(tx.write(now as u8)).ok();
        
        if let Ok(ret) = rx.read() {
            led1.toggle();
            cortex_m::asm::delay(5_000_000);
            led1.toggle();
            cortex_m::asm::delay(5_000_000);
            led1.toggle();
            cortex_m::peripheral::SCB::system_reset(&mut cp.SCB);
        }
    }

}

#[interrupt]
fn TIM2() {
    COUNTER_MS.fetch_add(1, Ordering::Relaxed);
}

/*
#[exception]
fn SysTick() {
    COUNTER_MS.fetch_add(1, Ordering::Relaxed);
}
*/

#[panic_handler]
fn my_panic(_info: &core::panic::PanicInfo) -> ! {
    loop {
    
        let dp = pac::Peripherals::take().unwrap();
        let mut rcc = dp.RCC.constrain();
        let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
        let mut led2 = gpiob.pb4.into_push_pull_output(&mut gpiob.crl);
        led2.toggle(); //is inverted logic
        cortex_m::asm::delay(100_000_000);
    }
}

the usart and reset is there for debug purpose

OutputPin trait not satisfied

It appears the embedded_hal::digital::OutputPin trait is not satisfied for stm32f1xx_hal GPIO pins? I'm seeing the following output when compiling:

$ cargo build
error[E0277]: the trait bound `stm32f1xx_hal::gpio::gpioa::PA4<stm32f1xx_hal::gpio::Output<stm32f1xx_hal::gpio::PushPull>>: embedded_hal::digital::OutputPin` is not satisfied
  --> src/main.rs:87:24
   |
87 |     let mut max31855 = max31855::Max31855::new(spi, temperature_cs_pin).unwrap();
   |                        ^^^^^^^^^^^^^^^^^^^^^^^ the trait `embedded_hal::digital::OutputPin` is not implemented for `stm32f1xx_hal::gpio::gpioa::PA4<stm32f1xx_hal::gpio::Output<stm32f1xx_hal::gpio::PushPull>>`
   |
   = note: required by `<max31855::Max31855<SPI, CS>>::new`

error: aborting due to previous error

Full source code:

#![no_std]
#![no_main]

extern crate panic_halt; // you can put a breakpoint on `rust_begin_unwind` to catch panics

use cortex_m_rt::entry;

use stm32f1xx_hal as hal;
use stm32f1xx_hal::{
    prelude::*,
    pac,
};

use max31855;

#[entry]
fn main() -> ! {
    let c = cortex_m::Peripherals::take().unwrap();
    let p = pac::Peripherals::take().unwrap();

    let mut rcc = p.RCC.constrain();
    let mut flash = p.FLASH.constrain();
    let clocks = rcc.cfgr.freeze(&mut flash.acr);
    let mut afio = p.AFIO.constrain(&mut rcc.apb2);
    let mut gpioa = p.GPIOA.split(&mut rcc.apb2);

    // SPI setup
    let spi_sck_pin = gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl);
    let spi_miso_pin = gpioa.pa6;
    let spi_mosi_pin = gpioa.pa7.into_alternate_push_pull(&mut gpioa.crl);
    let spi_mode = hal::spi::Mode {
        polarity: hal::spi::Polarity::IdleLow,
        phase: hal::spi::Phase::CaptureOnFirstTransition
    };

    let mut spi = hal::spi::Spi::spi1(
        p.SPI1,
        (spi_sck_pin, spi_miso_pin, spi_mosi_pin),
        &mut afio.mapr,
        spi_mode,
        100.khz(),
        clocks,
        &mut rcc.apb2
        );

    // temperature sensor driver
    let mut temperature_cs_pin =
        gpioa.pa4.into_push_pull_output_with_state(&mut gpioa.crl,
                                                   stm32f1xx_hal::gpio::State::Low);
    // This doesn't compile!
    let mut max31855 = max31855::Max31855::new(spi, temperature_cs_pin).unwrap();

    loop {}
}

get_clk returning 0 during timer.start

#34 introduced a new way to get the timer clock, it is working correctly during the init portion of an RTFM program, but when I change the frequency and call start in a SysTick interrupt it is returning 0. This worked correctly with the old method. Not sure what could be causing it.

Add functions for going to sleep

As discussed in #64, it would be nice to have functions for entering the various power save modes as that process requires quite a few steps.

adc example issue

I am trying to run this adc example. but I found this line of code failed to compile.

let mut adc = adc::Adc::adc1(p.ADC1, &mut rcc.apb2, clocks);

The error message is

error[E0061]: this function takes 2 parameters but 3 parameters were supplied
  --> src/main.rs:28:19
   |
28 |     let mut adc = adc::Adc::adc1(p.ADC1, &mut rcc.apb2, clocks);
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 2 parameters

error: aborting due to previous error

For more information about this error, try `rustc --explain E0061`.
error: Could not compile `blue-pill-quickstart`.

It seems the api has been changed.

EXTI interrupts

Is there any way to setup EXTI interrupts at the moment ?
I need to have an interrupt handler when a rising front occurs on a GPIO pin, how could I do that ?

GPIO pin PB5 and PB4 does not work correctly

Hi,

after managing to run the demo blink on led PC13, I wanted to also blink led on PB3, PB4 and PB5.

PC13 and PB5 works fine, PB3 and PB4 don't. I am aware they may be used for some debug stuff but I set debug to false to Cargo.toml. Also if pin would be in use by something else, i would expect compilation error

full code:

// std and main are not available for bare metal software
#![deny(unsafe_code)]  //  Don't allow unsafe code in this file.
//#![deny(warnings)] //  If the Rust compiler generates a warning, stop the compilation with an error.
#![no_std]

#![no_main] 

use nb::block;



use stm32f1xx_hal::{

    prelude::*,

    pac,

    timer::Timer,

};


#[cortex_m_rt::entry]

fn main() -> ! {

    // Get access to the core peripherals from the cortex-m crate

    let cp = cortex_m::Peripherals::take().unwrap();

    // Get access to the device specific peripherals from the peripheral access crate

    let dp = pac::Peripherals::take().unwrap();



    // Take ownership over the raw flash and rcc devices and convert them into the corresponding

    // HAL structs

    let mut flash = dp.FLASH.constrain();

    let mut rcc = dp.RCC.constrain();



    // Freeze the configuration of all the clocks in the system and store

    // the frozen frequencies in `clocks`

    let clocks = rcc.cfgr.freeze(&mut flash.acr);



    // Acquire the GPIOC peripheral

    let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
    let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);



    // Configure gpio C pin 13 as a push-pull output. The `crh` register is passed to the function

    // in order to configure the port. For pins 0-7, crl should be passed instead.

    let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
    let mut led1 = gpiob.pb3.into_push_pull_output(&mut gpiob.crl);
    let mut led2 = gpiob.pb4.into_push_pull_output(&mut gpiob.crl);
    let mut led3 = gpiob.pb5.into_push_pull_output(&mut gpiob.crl);
    led1.set_high();
    led2.set_high();
    led3.set_high();
    // Configure the syst timer to trigger an update every second

    let mut timer = Timer::syst(cp.SYST, 1.hz(), clocks);



    // Wait for the timer to trigger an update and change the state of the LED

    loop {

        block!(timer.wait()).unwrap();

        led.set_high();
        led1.set_high();
        led2.set_high();
        led3.set_high();

        block!(timer.wait()).unwrap();

        led.set_low();
        led1.set_low();
        led2.set_low();
        led3.set_low();
    }

}

#[panic_handler]
fn my_panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}

cargo.toml

[package]
name = "afro32"
version = "0.1.0"
authors = ["lesto <[email protected]>"]
edition = "2018"

[profile.release]
# optimize for size ('z' would optimize even more)
opt-level = 's'
# link with link time optimization (lto).
lto = true
# enable debugging in release mode.
debug = false

[dependencies]
cortex-m = "0.6.0"
nb = "0.1.2"
cortex-m-rt = "0.6.8"
stm32f1 = "0.7.1"

[dependencies.stm32f1xx-hal]
version = "0.3.0"
features = ["stm32f100", "rt"]

Add building and fmt on travis

To check that we compile on a set of different rust version, and that our formating is consistent, I propose to check on travis that the crate and its examples are building, and that our formating comply with rustfmt.

If it's OK, I can propose a PR for that.

MAPR remaping with disable_jtag causes debugger to fail on stm32f103RGT6

I recently got an XL density stm32f103 (stm32f103RGT6) and ran into a pretty strange issue.

When running the following code, the debugger stops responding and I have to reboot the microcontroller with BOOT0 pulled high in order to reflash it. The issue happens when configuring serial, when configuring SPI1 but not when configuring SPI2.

If mapr.mapr().modify(|_, w| w.spi1_remap().bit(PINS::REMAP)); is removed from spi.rs, things work fine which is a pretty clear indication that it is responsible.

Finally, removing disable_jtag also makes things work as normal

#![deny(unsafe_code)]
#![no_main]
#![no_std]

use panic_semihosting as _;

use stm32f1xx_hal::{
    prelude::*,
    pac,
    serial::{Config, Serial},
    spi::{Spi, Mode, Polarity, Phase}
};
use cortex_m_rt::entry;



#[entry]
fn main() -> ! {
    // Get access to the device specific peripherals from the peripheral access crate
    let p = pac::Peripherals::take().unwrap();

    let mut flash = p.FLASH.constrain();
    let mut rcc = p.RCC.constrain();

    let clocks=rcc.cfgr.freeze(&mut flash.acr);

    // Prepare the alternate function I/O registers
    let mut afio = p.AFIO.constrain(&mut rcc.apb2);

    // Prepare the GPIOB peripheral
    let mut gpioa = p.GPIOA.split(&mut rcc.apb2);
    let mut gpiob = p.GPIOB.split(&mut rcc.apb2);


    let (pa15, pb3, pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4);

    /*
    let right_usart = Serial::usart1(
        p.USART1,
        (
            gpioa.pa9.into_alternate_push_pull(&mut gpioa.crh),
            gpioa.pa10,
        ),
        &mut afio.mapr,
        Config::default().baudrate(921_600.bps()),
        clocks,
        &mut rcc.apb2,
    );
    */

    let mode = Mode{
        phase: Phase::CaptureOnFirstTransition,
        polarity: Polarity::IdleLow
    };
    let pins = (
        gpioa.pa5.into_alternate_push_pull(&mut gpioa.crl),
        gpioa.pa6,
        gpioa.pa7.into_alternate_push_pull(&mut gpioa.crl),
    );
    let mode = Mode{
        phase: Phase::CaptureOnFirstTransition,
        polarity: Polarity::IdleLow
    };

    let spi = Spi::spi1(p.SPI1, pins, &mut afio.mapr, mode, 1.khz(), clocks, &mut rcc.apb2);

    /*
    {
        let pins = (
            gpiob.pb13.into_alternate_push_pull(&mut gpiob.crh),
            gpiob.pb14,
            gpiob.pb15.into_alternate_push_pull(&mut gpiob.crh),
        );

        let spi = Spi::spi2(p.SPI2, pins, mode, 1.khz(), clocks, &mut rcc.apb1);
    }
    */
    loop {}
}

The same code works perfectly fine on the standard blue pill boards. This might also be a hardware issue, however as far as I know, the debug circuit on my custom PCB is the same as the blue pill.

Update to fixed stm32f1 to fix remaining compile errors

Currently the crate does not compile due to a SNAFU in the SVD files which are propagated through the generated PAC:

error[E0432]: unresolved imports `stm32::rcc::cfgr::SWW`, `stm32::rcc::cfgr::USBPREW`
 --> src/rcc.rs:4:24
  |
4 | use stm32::rcc::cfgr::{SWW, USBPREW};
  |                        ^^^  ^^^^^^^ no `USBPREW` in `stm32f103::rcc::cfgr`
  |                        |
  |                        no `SWW` in `stm32f103::rcc::cfgr`. Did you mean to use `_SWW`?

error[E0599]: no method named `usbpre` found for type `&mut stm32f1::stm32f103::rcc::cfgr::W` in the current scope
   --> src/rcc.rs:272:18
    |
272 |                 .usbpre()
    |                  ^^^^^^

error: aborting due to 2 previous errors

Some errors occurred: E0432, E0599.
For more information about an error, try `rustc --explain E0432`.

stm32f1xx_hal::gpio::Active for Debugger?

I'm not familiar with rust nor stm32, so it could be my fault. But when I want to write

    let b4 = gpiob.pb4.into_alternate_push_pull(&mut gpiob.crl);

I got the error

73 |     let mut b4 = gpiob.pb4.into_alternate_push_pull(&mut gpiob.crl).downgrade();
   |                            ^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `stm32f1xx_hal::gpio::gpiob::PB4<stm32f1xx_hal::gpio::Debugger>`
   |
   = note: the method `into_alternate_push_pull` exists but the following trait bounds were not satisfied:
           `stm32f1xx_hal::gpio::Debugger : stm32f1xx_hal::gpio::Active`

which seems to be related to the fact that PB4 is initially a Debugger, and Active trait is not implemented for it?

LED example toggle behavior

The LED example turns it on (PC13) briefly and then turns it back off. It happens in this line:

gpioc.pc13.into_push_pull_output(&mut gpioc.crh).set_high();

Stepping through it, it seems to be writing to BSRR (a couple steps after the LED was turned on) turning it off.

Is this a bug or am I missing something?

Cargo.toml

[dependencies]
cortex-m = "0.5.8"
cortex-m-rt = "0.6.5"
cortex-m-semihosting = "0.3.2"
panic-halt = "0.2.0"
panic-semihosting = "0.5.1"

[dependencies.stm32f1xx-hal]
version = "0.2.0"
features = ["stm32f103", "rt"]

Are there any working examples of the enc28j60 out there?

Hi, has anyone had any luck getting the enc28j60 example working? I am aware that it was disabled because it was not working on stable. However a nightly cargo build using the following command:
cargo build --example enc28j60 --features="stm32f103,rt"
gives a linker error as follows:

Compiling stm32f1xx-hal v0.1.1 (C:\Users\xxx\Source\Repos\stm32f1xx-hal) error: linking with 'rust-lld' failed: exit code: 1
...
= note: rust-lld: error: section '.text' will not fit in region 'FLASH': overflowed by 20 bytes rust-lld: error: section '.text' will not fit in region 'FLASH': overflowed by 70 bytes

Its not working on stm32f103xx-hal either.

Compiling with --release gives the following error:
error: can't perform LTO when compiling incrementally

SPI can become unresponsive in some conditions

I was attempting to write an SPI driver for ST7789 based displays using a Blue Pill as my base board. My screen was unresponsive so I used a logic analyzer to verify what was being sent out by my board. I noticed that the board was still toggling the LCD's DC pin just fine (and blinking an LED), but the SPI clock and data lines became completely silent after the first two bytes. The first two bytes were correct though.

What is weird is that I have also connected an SSD1306 SPI OLED display and it runs just fine. They use different SPI modes though, so that might be related.

I have attached my code as well as the logic probe data (collected through Saleae software) for both the non-functioning Blue Pill code as well as very similar code running from an Arduino library which works exactly as expected.

debug_rust_st7789_driver.zip

I have since also copied my rust code over to an atsamd based project and it runs just fine there, so this leads me to believe there is a bug in this crate.

Let me know if there is any more information I can provide. I have borrowed the probe temporarily, so if any more data collection is needed let me know soon so I can ensure I still have access.

pwm_input doesn't expose interrupts

Timers are commonly used as EXTI+timekeeping, for example when listening to a sensor, so that the MCU learns of occurence the event and capture its date of occurence at the same time, even if the interrupt is a bit slow to enter or has contention.

Support for different SPI configurations

This is awesome HAL in terms of usage safety than any other I seen before. But it isn't so yet flexible in several cases.

I would like to configure SPI for display driver, which uses unidirectional setup with only SCLK and MOSI lines.

As I can see, the Pins trait implementation for that case is missing.
Since MISO pin in target device already used as GPIO for other purpose, so I cannot use Pins<(sclk, miso, mosi)>.

Also there are some other SPI setups like a bi-directional half-duplex with single data line.

Possible to use peripherals in panic handler?

Hi, first thank you for the effort put into this wonderful ecosystem.

I'm writing a heater controller with this library, and one thing I want to make sure of, is that when panic happens, the PWM (which controls how much heat is produced) is disabled/zeroed and the program goes into an infinite loop, so that it doesn't burn down my house.

From what I gathered, peripherals are intentionally made hard to get for safety reasons. I can't take in the handler because that won't work. Maybe I should try global mutable variable to store the PWM instance, but it's considered a bad practice in rust?

Blink example & non-default RCC config

Sorry for a possibly really dumb question but can't seem to figure it out, in the blinky.rs example if I change

let clocks = rcc.cfgr.freeze(&mut flash.acr);

to

    let clocks = rcc.cfgr.use_hse(time::Hertz(8_000_000)).freeze(&mut flash.acr);

it continues to work but if I additionally set any other frequency, e.g.

    let clocks = rcc.cfgr.use_hse(time::Hertz(8_000_000)).sysclk(time::Hertz(72_000_000)).freeze(&mut flash.acr);

it stops working.

I'm trying to figure out how to explicitly create a config like rcc_clock_setup_in_hse_8mhz_out_72mhz from https://github.com/libopencm3/libopencm3/blob/master/lib/stm32/f1/rcc.c#L845

Thanks!

GPIO AF should be activated after enabling the corresponding peripheral

According to the manual ("9.1.4 Alternate functions (AF)"),

If software configures a GPIO pin as Alternate Function Output, but peripheral is not
activated, its output is not specified.

So it will be cleaner to enable the AF only after enabling the corresponding peripheral.

Not sure if it was caused by this, but I certainly saw something that looked like spurious SPI clock signals.

I'm not sure how to fix the problem cleanly. Perhaps, the pin struct should have methods activate_af and deactivate_af,`so the peripheral constructor will activate AF after enabling peripheral itself, and deactivate AF before disabling the peripheral (disabling peripherals doesn't seem to be implemented for any peripheral at the moment, though).

Write support for the Backup Data Register (BKP_DR, DR1, DR2, ...)

Currently, we can read from the Backup Data Register (for example DR1) using:

let device = pac::Peripherals::take().unwrap();
let _ = device.BKP.dr1.read().bits();

However, this will not be possible after calling rcc.bkp.constrain, because this function takes ownership of device.BKP:

let mut pwr = device.PWR;
let mut backup_domain = rcc.bkp.constrain(device.BKP, &mut rcc.apb1, &mut pwr);
let _ = device.BKP.dr1.read().bits(); // ERROR: borrow of moved value: `device.BKP`

Writing to the BKP_DR register should normally be only possible after enabling the power and backup interface clocks and setting the DBP bit the Power Control Register (page 81 of the RM0008 reference manual), i.e. calling rcc.bkp.constrain.
I'm not really sure how to fix this. I'm not familiar with svd2rust or any lower level code. I'm willing to help but I would for sure need some pointers...

Probably you should rethink API structure

Hello everyone.

I'm developing software more than 10 years.
I've seen a lot of anti-patters, but your code really impressed me.
I can definitely say that this is worst API approach I've ever seen in my life.

Looks like you've never try to write anything except POC with your API.
I was trying to create some kind of library using embedded-hal and stm32f1xx-hal
and found out that it is not worth it.
You should understand that I'm a really stubborn developer and I can dive deep into code
to understand how it works.

So what is wrong with your code.

Everything in your type system is wrong.
Sometimes it is even looks like some kind of joke to me, the same as bool x = true; if (x.toString().length() == 5) {}

Lets examine :gpio::gpiob::PB0 it is a separate struct that has same api as :gpio::gpiob::PB1
But PB1 is another struct so they can not be swapped in the code easily, you have to rewrite it.
Are you seriously thinking that defining separate types for any possible abstraction is good idea.

Also you've groupped pins into namespaces (probably is is somehow related to ports) and this is where really fun stuff begins.
Somehow deep inside your soul you are understand that using separate struct for each pin is bad idea.
So you've decided to make an abstraction via downgrade method that returns generic pin struct.
So now I'm able to downgrade PB0 to ::gpio::gpioa::PBx which is still bind to its namespace and it is not the same as ::gpio::gpioa::PAx.
Even generic pins in your API are not the same.

Next not so pleasant thing is pin function that mutates it to another "type" like fn into_push_pull_output(self, cr: &mut CRL) -> PA1<Output<PushPull>>.
It is not bad to mutate to another abstraction on state change, but only if method signature is like fn into_push_pull_output(self) -> PA1<Output<PushPull>>.
Expecting reference to second mutable parameter ruins everything.

My point here is that whole approach to API structure is wrong.
It is hard to understand and almost impossible to write flexible code.
But why we are using Rust here? We must remove complexity not adding new one.
Right now kernel GPIO API looks much more convenient than yours https://www.kernel.org/doc/Documentation/gpio/consumer.txt

Should you care about it?

Well I've suggest you too look into Arduino ecosystem. Arduino IDE is terrible and libraries system is half-work solution but it has Wiring with it's so called std library. Some devs clams that wiring is to high level and sometimes to slow comparing to C.
But it is simple, thus may devs were able to start their projects quickly.

stm32f1xx-hal should be more high level abstraction, for low level API we have cortex-m crates.

BTW I'm not only one who thinks that way nrf-rs/nrf-hal#8

Add `count` method to `Timer` struct

In order to create a reusable timer which we can use to handle timeouts[1] in our code, I would like to have an additional method count to the Timer struct. This would return the value of the cnt register. This method already exists for the QEI. User code would look something like this:

// Timer of 1,000 Hz, so once per ms
let timer = Timer::tim2(tim2, Hertz(1_000), clocks, apb1);
timer.start(Hertz(1_000)); // the counter should be reset here
// Some other code...
if timer.count() > 500 {
  // More than 500 ms have passed...
}

It's easier than using the timer's interrupt. Or am I missing something? It may also be useful to expose a reset() method that resets the counter. I've noticed that resetting a timer requires unsafe code...

[1] My use case is a timer that I can use for timeouts when implementing a serial protocol (in this case to talk to the ESP-01 in AT mode).

ITM logging not working in windows due to auto-load safe-path

Hi

Has anyone tried running this from Windows 10? I get the following warning message when running the following command:
cargo run --example itm --features="stm32f103,rt"

The gdb warning below assumes a linux or osx operating system and I was wondering if ITM logging works at all in Windows.

I tried to create a file C:\Users\xxx\.gdbinit with set auto-load safe-path / in contents but I get the same warning.

warning: File "C:\Users\xxx\Source\Repos\stm32f1xx-hal\.gdbinit" auto-loading has been declined by your 'auto-load safe-path' set to "$debugdir:$datadir/auto-load". To enable execution of this file add add-auto-load-safe-path C:\Users\xxx\Source\Repos\stm32f1xx-hal\.gdbinit line to your configuration file "$HOME/.gdbinit". To completely disable this security protection add set auto-load safe-path / line to your configuration file "$HOME/.gdbinit".

Document the features = [stm32f103] requirement

Given that the old stm32f103_hal crate is now deprecated, I figured I should update my projects. The first hurdle I ran into was a couple of unresolved import stm32 errors as well as duplicate definitions with name <name> errors. I managed to fix this once I figured out that you need to add a feature to specify which device you are compiling for.

However, this certainly wasn't obvious so it would be nice if it was added to the readme.

PWM on TIM1 outputs

What the reason that advanced timer (TIM1) cannot be configured for PWM output like a general purpose timers?
I know that it require little bit an extra configuration for advanced features but in basics advanced timer may operate like general purpose timers.

Calling timer.start with timer interrupts enabled triggers interrupt

When calling Timer::start, we trigger an update to reload the prescaler https://github.com/stm32-rs/stm32f1xx-hal/blob/master/src/timer.rs#L154. This update event triggers an interrupt if interrupts are enabled.

This can be mitigated temporarily by calling unlisten before calling start, but that is easy to miss. We should probably do that inside start if interrupts are enabled, or find a way to load the prescaler without an update event.

GPIO output toggling does not work on Alternate pins

I'm using the blue pill board, which has an LED connected to PC13.

With the following code, the LED lights up as soon as the pin is configured as push/pull output, but it cannot be turned off (set_low does not turn off the LED, and for sanity checking set_high does not either).

#![deny(unsafe_code)]
#![no_main]
#![cfg_attr(not(test), no_std)]

// Panicking behavior
extern crate panic_semihosting; // logs messages to the host stderr; requires a debugger

#[macro_use]
extern crate cortex_m_rt;

use cortex_m_semihosting::hprintln;
use embedded_hal::digital::v2::OutputPin;
use stm32f1xx_hal::{prelude::*, pac};

#[entry]
fn main() -> ! {
    hprintln!("init").unwrap();

    let device: pac::Peripherals = pac::Peripherals::take().unwrap();

    let mut rcc = device.RCC.constrain();
    let mut gpioc = device.GPIOC.split(&mut rcc.apb2);

    let mut led = gpioc.pc13.into_alternate_push_pull(&mut gpioc.crh);
    led.set_low().unwrap();

    hprintln!("loop").unwrap();
    loop {
    }
}

This code uses stm32f1 0.8 and stm32f1xx-hal 0.4. I also tried stm32f1 0.6 and stm32f1xx-hal 0.2, which showed the same behavior.

Any idea what the problem could be?

The full code can be found here (branch led-bug).

I2c Example

New to Rust - Would it be possible to have an example for I2c? After looking through the documentation, The Rust Book, and other examples, I am still having trouble figuring out proper implementation.

hclk calculated incorrectly

Reading through RM0008, it seems like hclk is being calculated incorrectly here. The calculation works up until division by 16, after that the reference manual skips to 64 whereas this crate continues with 32, so you get the following correspondence:

hpre_bits Reference manual Crate
0xxx SYSCLK not divided (1 << (0b0111 - 0b0111)) == 1
1000 SYSCLK divided by 2 (1 << (hpre_bits - 0b0111)) == 2
1001 SYSCLK divided by 4 (1 << (hpre_bits - 0b0111)) == 4
1010 SYSCLK divided by 8 (1 << (hpre_bits - 0b0111)) == 8
1011 SYSCLK divided by 16 (1 << (hpre_bits - 0b0111)) == 16
1100 SYSCLK divided by 64 (1 << (hpre_bits - 0b0111)) == 32
1101 SYSCLK divided by 128 (1 << (hpre_bits - 0b0111)) == 64
1110 SYSCLK divided by 256 (1 << (hpre_bits - 0b0111)) == 128
1111 SYSCLK divided by 512 (1 << (hpre_bits - 0b0111)) == 256

While it ends up setting hpre correctly, it calculates the resulting hclk incorrectly which is used in the following calculations of ppre2 and ppre1.

Cleanup examples

Maybe we can cleanup examples that doesn't show how to use this crate or test one of this crate functionnality? I think in particular to remove the examples relying on git dependencies.

I don't know if an example using RTFM (once 0.4 is out) is relevant. What do you think?

Program does not boot without debugger

Hi !
I can program my stm32f103 using gdb without any troubles, but as soon as I remove the debugger, I can't run my program.

I think that it is correctly flashed, but it does not boot from flash, even if BOOT0 and BOOT1 are set to low.

Do you have any idea to fix this problem ?

Improve Documentation

The documentation "has a little room for improvments"
:-D

Especially in terms of "How do I use XY"

blink example does not compile

Hello,
new in rust.
Following the readme of the project, i managed to set up the toml to make it work:

[package]
name = "xxxx"
version = "0.1.0"
authors = ["xxxx"]
edition = "2018"

[profile.release]
# optimize for size ('z' would optimize even more)
opt-level = 's'
# link with link time optimization (lto).
lto = true
# enable debugging in release mode.
debug = true

[dependencies]
cortex-m = "0.6.0"
nb = "0.1.2"
cortex-m-rt = "0.6.8"
stm32f1 = "0.7.1"

[dependencies.stm32f1xx-hal]
version = "0.2.0"
features = ["stm32f100", "rt"]

using 0.2.0 as requested from the readme return the error:

error[E0609]: no field `pc13` on type `stm32f1xx_hal::gpio::gpioc::Parts`

then i notice 0.3.0 is available, and with that one i get warning about digital::v1 (I read a little bit the issue by my skill level are 0 and i could not understand nothing).

Please please please fix your own doc and warning,
sincerely,

a frustrated beginner

Support for the STM32F107?

I will be getting this board and I'd like to have support for it. Given its current lack, I had a look at what would be needed and if I could write basic support myself. The clock circuitry (RCC) is the biggest difference, as there's two dividers and two PLLs instead of just the single PLL like on the lower-end MCUs. This makes the freeze function incredibly complex. The number of possible combinations is huge, and finding the optimal one for the given input clock and desired sysclk becomes much more calculation-intensive.

I wonder if others have looked into this, and whether there is a workable solution?

PWM expose 2 channels, take their pins and force their mode.

There are a few issues with the current code:

  • timers only expose 2 channels
  • timers takes the channels pins wether we want it or not.
  • timers force the pin mode to be OpenDrain or PushPull

example form timer_input:

impl Pins<TIM1> for (PA8<Alternate<OpenDrain>>, PA9<Alternate<OpenDrain>>) {
    const REMAP: u8 = 0b00;
}

I propose having the channels pins using the Option and a generic mode:

pub type Pwm1Mapping0<M1, M2, M3, M4> = (Option<PA8<Alternate<M1>>>,
                                         Option<PA9<Alternate<M2>>>,
                                         Option<PA10<Alternate<M3>>>,
                                         Option<PA11<Alternate<M4>>>);

here is a more complete example:
https://github.com/japaric/stm32f103xx-hal/blob/9f132cd3a8a95d0b726f925c239fad6e74b60d76/src/pwm2.rs#L219

Make rcc::APB1.enr(), rcc::APB1.rstr(), etc public

When enabling standby mode, there is a need to set PWREN bit in APB1 ENR register.

Right now it can be achieved by calling constrain on rcc.bkp. https://github.com/stm32-rs/stm32f1xx-hal/blob/master/src/rcc.rs#L367

And while it does set the right bits in the right places, it enables other registers as well, which may to may not be desirable.

Ideally, I'd like to be able to do it manually:

rcc.apb1.enr().modify(|_r, w| {
    w.pwren().set_bit()
});

Or maybe just call a function that does it for me, something like what set_sleepdeep() does: https://github.com/rust-embedded/cortex-m/blob/master/src/peripheral/scb.rs#L585

rcc.apb1.set_pwren();

I'd be happy to provide a PR for that, I'm just not entirely sure which is the best way to approach this issue.

Reenable DMA

DMA was disabled in bae2a2c (by @therealprof), but with no explanation as to why. Can it be reenabled? Does it need some work to get running again?

I'm happy to try getting it working again, but I have very little experience with DMA.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.