Code Monkey home page Code Monkey logo

stm32l0xx-hal's Introduction

stm32l0xx-hal

Build Status

โš ๏ธ Note: The stm32l0xx HAL team is looking for more maintainers! Do you think you could help out with reviewing contributions and improving the codebase? If yes, please let us know in issue #211!

stm32l0xx-hal is a Hardware Abstraction Layer (HAL) for the STMicro STM32L0xx family of microcontrollers.

This crate relies on Adam Greig's stm32l0 crate to provide appropriate register definitions and implements a partial set of the embedded-hal traits.

Based on the stm32l1xx-hal crate by Vitaly Domnikov and the stm32f4xx-hal crate by Daniel Egger.

Usage

Add the stm32l0xx-hal crate to your dependencies in Cargo.toml and make sure to pick the appropriate mcu-* Cargo feature to enjoy the full feature set for your MCU (see next section "Supported Configurations" for more details).

For example, when using the STM32L071KBTx MCU:

[dependencies]
stm32l0xx-hal = { version = "0.10.0", features = ["mcu-STM32L071KBTx", "rt"] }

Supported Configurations

The STM32L0 family consists of different subfamilies with different peripherals and I/O configurations. Superficially, the family can be grouped into the groups stm32l0x1, stm32l0x2 and stm32l0x3. However, some aspects like alternate function mappings for I/O pins do not follow these groups.

In order for the HAL to properly support all those MCUs, we generate some peripheral mappings and corresponding Cargo features using cube-parse.

MCU Features (mcu-*)

The easiest way for you to get started, is to use your appropriate mcu-* feature. For example, when using the STM32L071KBTx MCU, you just set the mcu-STM32L071KBTx feature in Cargo.toml:

# Cargo.toml
[dependencies]
stm32l0xx-hal = { version = "0.10.0", features = ["mcu-STM32L071KBTx", "rt"] }

If you take a look at the Cargo.toml file, you can see that mcu-STM32L071KBTx is just an alias for ["io-STM32L071", "eeprom-6144", "flash-128", "ram-20"].

I/O Features (io-*)

The io-* features are based on the GPIO peripheral version. This determines the pin function mapping of the MCU. The features seem to correspond to the product categories.

Right now, the following features are supported:

  • io-STM32L021 (Product category 1)
  • io-STM32L031 (Product category 2)
  • io-STM32L051 (Product category 3)
  • io-STM32L071 (Product category 5)

The product categories should be listed in your MCU family datasheet. The name of the io-* feature itself is derived from the internal name used in the STM32CubeMX database. It does not necessarily match the name of the MCU, for example the STM32L062K8Tx uses the GPIO peripheral version named io-STM32L051.

Memory Layout: Flash (flash-*) and RAM (ram-*)

Flash and RAM sizes are automatically configured by using the appropriate mcu-* Cargo feature, as described further up.

If you have an MCU that does not yet have a corresponding mcu-* feature, or if the default configuration is incorrect, you can override the memory.x of stm32l0xx-hal by providing your own:

In your crate root, add a file called memory.x with the correct configuration. For example:

MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 64K
  RAM : ORIGIN = 0x20000000, LENGTH = 8K
}

Add a build.rs file with the following content:

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;

fn main() {
    // Put the linker script somewhere the linker can find it
    let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
    File::create(out.join("memory.x"))
        .unwrap()
        .write_all(include_bytes!("memory.x"))
        .unwrap();
    println!("cargo:rustc-link-search={}", out.display());

    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=memory.x");
}

And finally add the disable-linker-script feature to your stm32l0xx-hal dependency:

# Cargo.toml
[dependencies]
stm32l0xx-hal = { version = "0.10.0", features = ["mcu-STM32L071K8Ux", "disable-linker-script"] }

Other Cargo Features

  • rtc: Enable the RTC API based on rtcc. Enabled by default.

Toolchain Setup

In order to use this HAL, you need the following Setup:

  1. Install Rustup

    See rustup.rs for details. You may als be able to install Rustup directly through your distro.

  2. Install the arm-none-eabi compiler toolchain

    https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads

    If you cannot install the toolchain directly through your OS / distro, we recommend installing the precompiled binaries to '/usr/local/opt'. Add the bin folders (/bin & /arm-none-eabi/bin) to your environments variable 'PATH'.

  3. Install the thumbv6m-none-eabi target for Rust

    Simply run rustup target add thumbv6m-none-eabi

  4. Install probe-run to run examples.

    cargo install probe-run

For more instructions on how to get started with ARM / Cortex-M programming using Rust, check out the Embedded Rust Book.

Build Examples

You can build examples through Cargo:

$ cargo build --release --examples --features stm32l0x1,rt

Note that not all examples are compatible with all MCUs. You might need to peek into the example source code.

Run Examples

This crate uses probe-run to run examples on target hardware.

To run the blinky example on an STM32L053 Nucleo:

cargo run --example blinky --features mcu-STM32L053R8Tx -- --chip STM32L053R8Tx

Flashing Using Helper Scripts

The following instructions outline how-to on flashing the 'serial' example code. This can be extended to any other example code.

Flashing with ST-Flash:

  1. Flash the microcontroller using the flash script
    $ ./flash.sh target/thumbv6m-none-eabi/release/examples/serial
    

Flashing with OpenOCD

  1. Flash the microcontroller using the openocd flash script
    $ ./openocd_flash.sh target/thumbv6m-none-eabi/release/examples/serial
    

Contributor Notes

  • Revert local dependencies to external cargo and uncomment configurations before committing

License

0-Clause BSD License, see LICENSE-0BSD.txt for more details.

stm32l0xx-hal's People

Contributors

abonnaudet-ledger avatar airelil avatar akashihi avatar alexismarquet avatar allexoll avatar almusil avatar apgoetz avatar arkorobotics avatar azerupi avatar berkowski avatar blakesmith avatar burrbull avatar craigjb avatar dbrgn avatar disasm avatar ejpcmac avatar electrocutie avatar hannobraun avatar jamwaffles avatar jcard0na avatar jonas-schievink avatar jordens avatar lthiery avatar lulf avatar marsik avatar michaelbeaumont avatar osannolik avatar pdgilbert avatar rnestler avatar therealprof 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

Watchers

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

stm32l0xx-hal's Issues

Flashing method

I see youโ€™ve added to the README.md a flashing method that uses st-flash. Any reason not to do this instead?

$ ./openocd_program.sh target/thumbv6m-none-eabi/debug/examples/serial

Missing ! in gpio.rs

I think there is a missing ! in gpio.rs in the gpio macro implementing is_high. see attached code for first occurence with missing ! and second occurence with ! present.

...
            impl<MODE> StatefulOutputPin for $PXx<Output<MODE>> {
                fn is_set_high(&self) -> Result<bool, Self::Error> {
                    let is_high = self.is_set_low()?;   // Missing "!"
                    Ok(is_high)
                }

                fn is_set_low(&self) -> Result<bool, Self::Error> {
                    // NOTE(unsafe) atomic read with no side effects
                    let is_low = unsafe { (*$GPIOX::ptr()).odr.read().bits() & (1 << self.i) == 0 };
                    Ok(is_low)
                }
            }

            impl<MODE> toggleable::Default for $PXx<Output<MODE>> {}

            impl<MODE> InputPin for $PXx<Output<MODE>> {
                type Error = void::Void;

                fn is_high(&self) -> Result<bool, Self::Error> {
                    let is_high = !self.is_low()?;     // Not missing
                    Ok(is_high)
                }

                fn is_low(&self) -> Result<bool, Self::Error> {
                    // NOTE(unsafe) atomic read with no side effects
                    let is_low = unsafe { (*$GPIOX::ptr()).idr.read().bits() & (1 << self.i) == 0 };
                    Ok(is_low)
                }
            }
...

Clippy CI build fails

The Clippy CI build currently fails, presumably because a new Clippy version has been pulled in. It would be nice to get this fixed (either by following what Clippy says or by silencing those warnings; haven't looked into it, so don't know what would be appropriate). Any pull request doing so would be very welcome!

LPUART1 won't work with LSE as clock source

Bottomline: baudrate not set correctly.

I've been trying to print a simple "Hello world" with serial, using LPUART1. I wanted to make sure that LSE was being used as the clock source, to make it truly low power, and I ran into a problem. I used the serial.rs example from the repo, changing the USART to LPUART, and before the clock setup I added the following code:

dp.RCC.csr.modify(|_,w| w.lseon().on()); //enable LSE
    while dp.RCC.csr.read().lserdy().is_not_ready() {    
        // wait until LSE is ready
    }

dp.RCC.ccipr.modify(|_,w| w.lpuart1sel().lse()); //set LSE as LPUART1 clock source

If I do the exact same thing, but instead of .lse() use .system() or .ahb(), it works fine. With .lse() it just won't print anything. So I started checking registers in a GDB session and found out the following:
LSEON gets set correctly, and LSERDY is set as expected. Then LPUART1SEL is correctly set to 11: LSE selected as clock.

What doesn't look right is the value of LPUART_BBR, and I think I understand, why: the function that calculates the LPUARTDIV takes fck from the system clock, and that's why it all works if .system() is used. In my code the system clock is MSI running at 1.048 MHz. But to correctly set the baudrate I need the actual LPUART clock, so in this case 32kHz.

I'm just pointing this out, I wouldn't really know how to fix it: I guess some match or if/else block based on the LSE/other choice. This is how CubeMX does it:


      case UART_CLOCKSOURCE_SYSCLK:
        lpuart_ker_ck_pres = HAL_RCC_GetSysClockFreq();
        break;
      case UART_CLOCKSOURCE_LSE:
        lpuart_ker_ck_pres = (uint32_t)LSE_VALUE;
        break;

I hope this will help someone who knows what their doing :) to improve this, meanwhile I'm trying to figure out how to go around it.

LPUART won't work below 1MHz

Not sure whether this is a problem or there is some particular way to configure it. What I'm doing is:

let mut lpserial = dp.LPUART1.usart(tx_pin, rx_pin, serial::Config::default(), &mut rcc).unwrap();

It works perfectly with MSI from Range6..Range4, but not below that. The calculation of the baudrate divisor should work OK, it adjusts for LPUART. But it won't work. Setting the baudrate explicitly to lower values (e.g. 1200 bps) doesn't change it. It just stops working below Range4.

Is there something else I have to do in my code to make it work?

System clock of 32 MHz via PLLMul::Mul4 and PLLDiv::Div2 leads to hard fault because of default voltage range

When using the following configuration for a 32 MHz system clock, which is possible since #129, a hard fault is generated:

let mut rcc = dp.RCC.freeze(Config::pll(PLLSource::HSI16, PLLMul::Mul4, PLLDiv::Div2));

I traced the problem down to the default Vcore range. As shown by the following table (STM32L0 reference manual, p. 174), VcoreRange::Range1 would be required for frequencies greater than 16 MHz. The default configured Vcore range however, is VcoreRange::Range2 (STM32L0 reference manual, p. 158).

vcore_ranges

With the current interface of stm32l0xx-hal, it seems that configuration of the Vcore range is only possible after freezing the RCC, which is too late to prevent the hard fault:

let mut rcc = dp.RCC.freeze(Config::pll(PLLSource::HSI16, PLLMul::Mul4, PLLDiv::Div2));
let mut pwr = PWR::new(dp.PWR, &mut rcc);
pwr.switch_vcore_range(VcoreRange::Range1);

I would be glad to help resolve this issue myself, but as I am new to using Rust for embedded development it would be great if someone experienced could provide me with some hints on how to pass an instance of PWR to RCC.freeze / write to PWR registers from RCC.freeze / create PWR during RCC.freeze / or whatever the best way might be.

I2C 'Wait until byte is transferred' loop

I was reading the i2c code since I want to implement it for another chip. Then I encountered the following loop in the send_byte function:

        // Wait until byte is transferred
        loop {
            let isr = self.i2c.isr.read();
            if isr.berr().bit_is_set() {
                self.i2c.icr.write(|w| w.berrcf().set_bit());
                return Err(Error::BusError);
            } else if isr.arlo().bit_is_set() {
                self.i2c.icr.write(|w| w.arlocf().set_bit());
                return Err(Error::ArbitrationLost);
            } else if isr.nackf().bit_is_set() {
                self.i2c.icr.write(|w| w.nackcf().set_bit());
                return Err(Error::Nack);
            }
            return Ok(());
        }

// Wait until byte is transferred

This loop seems wrong as it only ever runs once. Guess it might still work in practice most of the time. I guess the idea was to wait for txe bit to clear like in two lines above?

while self.i2c.isr.read().txe().bit_is_clear() {}

Doubts on I2C configuration

Hello, I'm trying to use the I2C on a stm32l011 device but the code asserts during the unwrap of presc = u8(presc).unwrap() I believe (it's hard to debug due to the optimizations).
In any case looking at the code, I found the computations of the prescaler value strange : first a ratio is computed as i2cclk / freq - 4 which can be negative in case the apb1 clock is too small and later the prescaler value is computed as ratio / constant_value but I can't find any reference to the constant value (387 for fast-mode and 514 for standard-mode).

The original STM32 library builds on values computed by the STM32CubeMx tool and I can understand why this library does not want to rely on it but are you sure about the code calculating the i2c timing values ?

Looking at the reference manual I found there should be also the possibility of enabling the analog and digital filters. Overall I admit that setting up I2C on this device requires an intensive brain session ๐Ÿ˜ญ

Supplying a customized memory map?

I want to make a bootloader and a chain-loaded firmware, so i need to specify my own memory map. However, if i try to do it via link-arg=-Tfoo.x, i'm promptly told that rust-lld: error: foo.x:3: region 'FLASH' already defined. How can i override the supplied memory map?

.i2c method not found in `stm32l0xx_hal::pac::I2C1`

I must be missing something, so please help: I'm trying to convert a working code, using SSD1306 display, from another STM32 board. I got stuck at setting up the I2C. It just won't compile, with the error as in the title. I tried the example from the crate, same problem. And when looking in the docs, I can't, in fact, find the .i2c method. After dp.I2C1. rust-analyzer shows only registers' names as possible methods. Am I missing something in the Cargo.toml that has to be set up?

The board is STM32L031, and the code:

    let dp = pac::Peripherals::take().unwrap();
  
    let mut rcc = dp.RCC.freeze(Config::hsi16());

    let gpioa = dp.GPIOA.split(&mut rcc);

    let sda = gpioa.pa10.into_open_drain_output();
    let scl = gpioa.pa9.into_open_drain_output();

    let mut i2c = dp.I2C1.i2c(sda, scl, 100.khz(), &mut rcc);

Alternative Functions as Pin Modes

In stm32l0xx-hal, we implement traits for pins in any mode and then configure the proper mode for those pins in a setup trait method. For example, for serial:

impl<MODE> TxPin<USART4> for PA0<MODE> {
    fn setup(&self) {
        self.set_alt_mode(AltMode::AF6);
    }
}

In contrast, in the stm32f0xx-hal, alternative functions are pin modes, just like Input or Output:

/// Alternate mode
pub struct Alternate<MODE> {
    _mode: PhantomData<MODE>,
}

/// Pin
pub struct PA0<MODE> {
    _mode: PhantomData<MODE>,
}

impl<MODE> PA0<MODE> {
    /// Configures the pin to operate in AF0 mode
    pub fn into_alternate_af0(self, _cs: &CriticalSection) -> PA0<Alternate<AF0>> {
        _set_alternate_mode($i, 0);
        PA0 { _mode: PhantomData }
    }
    ...

The trait impls are purely marker traits and expect the pins to be in the proper state.

img

I think this is the much nicer design for three reasons:

  • The user is aware that pins need to be put into the correct mode, it's not something that happens implicitly. Furthermore, if the pin already is in the right mode, then nothing needs to be done.
  • These marker traits can be easily implemented using a "universal pin mappings" macro, see stm32-rs/stm32f0xx-hal#29
  • Since the setup method is not implemented for every pin, the resulting binary might be smaller (unless this is optimized out by the compiler).

Thoughts?

Typo in serial.rs

I encountered what seems to be a typo in serial.rs when building with feature "io-STM32L021"

index 190c40d..7b46ea9 100755
--- a/src/serial.rs
+++ b/src/serial.rs
@@ -173,7 +173,7 @@ impl_pins!(
     PA0, AF6, LPUART1, RxPin;
     PA1, AF6, LPUART1, TxPin;
     PA2, AF4, USART2, TxPin;
-    PA2, AF6, LPUART1, TxPin,
+    PA2, AF6, LPUART1, TxPin;
     PA3, AF4, USART2, RxPin;
     PA3, AF6, LPUART1, RxPin;
     PA4, AF6, LPUART1, TxPin;

Add DMA features

I'd like to propose a few new features for DMA ops and I'd be happy to start working on a PR. Right now I'd like to use DMA to send data from a buffer I format messages in to a UART.

  • I'd like a write_some(..) equivalent to the write_all(..) methods on Tx<UART> so I can specify how many bytes to write without messing around with the Pin object.

Eventually I'd like to use DMA with SPI and ADC targets, and with the ADC I'd like to use circular writes. Neither looks like too much work to add, it just needs to be done.

But with more DMA targets and options, would it make sense to move the methods into traits?

delay limited to frequencies over 1MHz

Current implementation of delay asserts that the clock frequency is > 1_000_000 Hz, which limits its usage with MSI as clock source to Range4..Range6.

let freq = clocks.sys_clk().0;
assert!(freq > 1_000_000_u32);
let ticks_per_us = freq / 1_000_000_u32;

I understand that this is because otherwise we'd have less than 1 tick per microsecond, e.g. with Range0 at 65 kHz.

Would it make sense to add another method, e.g. LPDelay as in LowPower, with resolution expressed in ticks_per_ms and only DelayMs implemented?

Need more writers

@jonas-schievink, @lthiery, and @dbrgn:

For redundancy, I have added @hannobraun as a maintainer to this repository. I figured it's a good idea to have a backup person just in case. This action was purely made to give someone other than myself (who's also active in the embedded Rust community) access to the repository.

That said, your contributions to this repository have been critical to its growth and I would like to ask if any of you are interested in having Writer access. If you feel you'd make for a good maintainer as well, I'm open to that too.

My goal here is to avoid getting in the way of progress because I'm too busy to review PRs.

I trust your judgement and believe the peer-review method we've been exercising thus far (i.e. - asking one another to sanity check code) is good enough for now. If things gets out of hand, we can always strategize a new plan.

Please let me know your thoughts.

Regardless of your answer, I'm grateful for your contributions! Thanks!

Register definitions for factory option bytes

I'm trying to use the internal ADC reference to monitor VDDA. To do that I need the VREFINT_CAL value from the factory options. The factory option location aren't available in the SVD file (probably because they aren't peripheral) so they are not accessible.

Would it make sense to add register like definitions to the HAL or the PAC for the factory option location?

For reference a section from the STM32L071xx datasheet:

vrefint_cal_location

and from the STM32L0x1 reference manual:

vrefint_cal

cc @dbrgn

Faulty DMA Buffer Overflow in adc_trig example

Hi,

While experimenting with the ADC DMA examples, I discovered a bug in the implementation. I haven't completely root caused it, but I wanted to file an issue to keep track of it.

I assume this bug is part of the DMA implementation in #86

Currently, the DMA peripheral is set up in circular buffer mode to repeatedly transfer ADC samples into a fixed buffer. Sometimes, when the DMA peripheral reaches the end of this buffer, it causes a spurious BufferOverflow error when using the read_available() method.

This only seems to occur when the DMA transfer has reached the end of the buffer space and is about to wrap around to the start. The example adc_trig.rs uses a buffer size of 256 and a sample rate of 1Hz, so it takes a while to surface this bug. If you speed up the sample rate or decrease the buffer size, you can reproduce this issue much more quickly.

The issue seems to be caused by the function check_overrun_inner() which is responsible for checking, based on the current DMA status, if the DMA peripheral has overwritten data that has not been read yet.

It seems that if the DMA write pointer is located at position 0, but the transfer_state.complete flag is not set, this causes a spurious buffer overflow.

The DMA write pointer is calculated from the DMA_CNDTRx.NDT register field, which is supposed to indicate the number of remaining items to transfer. It seems that if this counter has restarted, and is equal to the length of the buffer, but the transfer complete flag is not set, this error condition is triggered.

I am not certain how this can happen: I assume that the transfer complete flag gets triggered by hardware pretty close to when the NDT register reloads its value, but somehow the example code is triggering this behaviour.

update PAC from 0.10.0 to 0.13.0

there has been updates to the pac.

some fields were missing (namely in the CRC peripheral, and i'm porting the F4 CRC driver to this family, but some fields aren't present in the 0.10.0 pac.

trying to change the pac version only seems to break 2 calls to unsafe functions in the DMA driver, so that seems fixable.

What is your opinion on this?

error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
   --> /Users/allexoll/Desktop/stm32l0xx-hal/src/dma.rs:344:55
    |
344 |                       handle.dma.$chfield.par.write(|w| w.pa().bits(address));
    |                                                         ^^^^^^^^^^^^^^^^^^^^ call to unsafe function

error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
   --> /Users/allexoll/Desktop/stm32l0xx-hal/src/dma.rs:351:55
    |
351 |                       handle.dma.$chfield.mar.write(|w| w.ma().bits(address));
    |                                                         ^^^^^^^^^^^^^^^^^^^^ call to unsafe function

Switching LSE on doesn't seem to work

I'm trying to use LPUART with Low Speed External oscillator (LSE). According to the STM32L0x1 reference manual, you need to set bit 8 of the control/status register (RCC_CSR LSEON), and then check if bit 9 of the same register (LSERDY) is set. In my code I do this:
dp.RCC.csr.modify(|_,w| w.lseon().on());
but the bit is not being set, checked by reading the register in a GDB session. The same does work perfectly when a basic code is set up with STM32CubeMX/CubeIDE. I looked at the C code generated by CubeIDE, but there's nothing more than setting this bit, no waiting time or anything. Has anyone else experienced this problem? What am I doing wrong here?

I2C Example - Error Reading Variable

I'm having difficulty implementing the I2C example:

  • MCU: STM32L071
  • stm32l0xx_hal version: 0.5.0
#![no_std]
#![no_main]

extern crate panic_semihosting;

use cortex_m_rt::entry;
extern crate stm32l0xx_hal;
use stm32l0xx_hal::{pac, prelude::*, rcc::Config};

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

    let mut rcc = periph.RCC.freeze(Config::hsi16());
    let gpioa = periph.GPIOA.split(&mut rcc);

    let sda = gpioa.pa10.into_open_drain_output();
    let scl = gpioa.pa9.into_open_drain_output();

    let mut i2c = periph.I2C1.i2c(sda, scl, 100.khz(), &mut rcc);

    let mut buffer = [0u8; 2];
    const ADDRESS: u8 = 0xFF;

    i2c.write(ADDRESS, &mut buffer).unwrap();

    loop {
    }
}

When debugging using GDB, I get one out of a variety of errors when I stop debugging, such as:

^C
Program received signal SIGINT, Interrupt.
0x080009d8 in stm32l0::stm32l0x1::i2c1::isr::<impl stm32l0::generic::R<u32,stm32l0::generic::Reg<u32,stm32l0::stm32l0x1::i2c1::_ISR>>>::txe (
    self=<error reading variable: Cannot access memory at address 0x20004e3c>)
    at /home/gies/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32l0-0.9.0/src/stm32l0x1/i2c1/isr.rs:801
801	        TXE_R::new((self.bits & 0x01) != 0)
^C
Program received signal SIGINT, Interrupt.
0x080009e8 in stm32l0xx_hal::i2c::I2c<I,SDA,SCL>::send_byte (
    self=<error reading variable: Cannot access memory at address 0x20004dd4>, 
    byte=<error reading variable: Cannot access memory at address 0x20004dd8>)
    at /home/gies/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32l0xx-hal-0.5.0/src/i2c.rs:183
183	        while self.i2c.isr.read().txe().bit_is_clear() {}
^C
Program received signal SIGINT, Interrupt.
core::cell::UnsafeCell<T>::get (
    self=<error reading variable: Cannot access memory at address 0x20004db0>)
    at /rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/cell.rs:1539
1539	/rustc/4560ea788cb760f0a34127156c78e2552949f734/src/libcore/cell.rs: No such file or directory.
^C
Program received signal SIGINT, Interrupt.
0x080009bc in vcell::VolatileCell<T>::get (
    self=<error reading variable: Cannot access memory at address 0x20004e38>)
    at /home/gies/.cargo/registry/src/github.com-1ecc6299db9ec823/vcell-0.1.2/src/lib.rs:32
32	        unsafe { ptr::read_volatile(self.value.get()) }

I'm relatively new to Rust. Is this an issue with the I2C example, or am I missing something else? Any assistance would be greatly appreciated.

serial doesn't seem to work at all

I'm trying to use the serial example on STM32L031K6 Nucleo board. I'm getting the following compilation errors:

error[E0432]: unresolved import `stm32l0xx_hal::serial`: no `serial` in the root

error[E0599]: no method named `usart` found for struct `stm32l0::stm32l0x1::USART2` in the current scope

.usart(tx_pin, rx_pin, serial::Config::default(), &mut rcc)
          ^^^^^ method not found in `stm32l0::stm32l0x1::USART2`

in fact I can't find these in the documentation, but they clearly do exist in the actual src code in the repo. How can I get it to work?

ADC clock configuration

It seems the if you call 'rcc.freeze(rcc::Config::msi(MSIRange::Range5));' and then enable Adc it will just freeze, because there is no clock to it (default is HSI16) and no way to change clock source. For now I just modified CFGR2 manually through unsafe code. Am I missing something?

Create script that will mimic CI run

Currently there is not an easy way to run what the commands that CI would. The goal is to create a script that would be invoked by CI and could be run locally.

Half duplex SPI

SPI would benefit from half-duplex comms (SPI control register 1: BIDIMODE).

I realize this trait is missing in embedded-hal. I could potentially try to figure this out and bodge a prototype. But that ain't gonna be easy since I'm rather new to Rust.

Any pointers appreciated.

While {} busy waits

Just a suggestion: there are quite a few places where there's a while ..some bit condition.. {} loop.

Sometimes it would be helpful if in debug builds these loops would time out and panic if the condition doesn't get hit in some sane amount of time, i.e. u16::MAX would be usually plenty.

I ran into a thing where RTC::new() was trying to call enable_lse(), which hangs forever if your devel board crystal isn't running properly for some reason. The issue is of course easy enough to find in this case, but in some other situations might save a bit of headache.

Perhaps a busywait function that takes the condition as closure ? I.e. something like

fn busywait<F>(f: F) where
    F: FnOnce() -> bool + Copy {

    let mut countdown = u16::MAX;
    while f() && (countdown != 0) {
        countdown-=1;
    };
    if countdown == 0 {
        panic!("timeout");
    }
}

which can then be called like this

busywait( || self.rb.csr.read().lserdy().bit_is_clear() );

The backtrace when it fails points right at the source of the problem.

One could of course add #[cfg(not(debug_assertions))] path here to eliminate the counter from release builds.

Does this sound like a good change ? If yes, I'll be happy to PR it

SPI pin setup

Hi there,

I've started to try to bring up a stm32l0x2 with your HAL. So far so good! I got UART working.

I need to get SPI working next, but while inspecting the spi driver, I've noticed you don't actually configure the alternate function for the pins (although you do so in the UART driver).

I was wondering if there's a reason for this? How does it work without AF configuration?

Best,
Louis

Timers can't sleep for >1 second

The hal::timer::CountDown impl of the timers uses Hertz as the Time parameter, which is a newtype around u32. This makes the longest timeout duration 1 Hz or 1 second. It would be useful to support this.

Serial `Pins` design discussion

Right now the Pins trait looks like this:

pub trait Pins<USART> {
    fn setup(&self);
}

It is then implemented for pin pairs using a macro:

macro_rules! impl_pins {
    ($($instance:ty, $tx:ident, $rx:ident, $alt:ident;)*) => {
        $(
            impl<Tx, Rx> Pins<$instance> for ($tx<Tx>, $rx<Rx>) {
                fn setup(&self) {
                    self.0.set_alt_mode(AltMode::$alt);
                    self.1.set_alt_mode(AltMode::$alt);
                }
            }
        )*
    }
}

impl_pins!(
    USART1,  PA9,  PA10, AF4;
    USART1,  PB6,  PB7,  AF0;
    USART2,  PA2,  PA3,  AF4;
    USART2,  PA14, PA15, AF4;
    USART2,  PD5,  PD6,  AF0;
    // ..
);

The problem with this is, that it assumes that a single pin pair can be used for an USART peripheral. However, pins can be mixed, so I could use USART1 with PA9 and PB7.

One approach would be to generate Pins implementations for all permutations (which can go into dozens or even hundreds of impls), but that's a workaround for a design problem in my opinion: Different pins can act as Tx and as Rx for an USART independently. (You could even use multiple Tx pins for the same USART if you wanted...)

My suggestion would be to drop the Pins<USART> trait, and to add TxPin<USART> and RxPin<USART> traits instead.

Here's a quick way to play around with this idea without having to adjust the serial trait:

pub trait TxPin<USART> {
    fn setup(&self);
}

pub trait RxPin<USART> {
    fn setup(&self);
}

impl<USART, TX, RX> Pins<USART> for (TX, RX) where TX: TxPin<USART>, RX: RxPin<USART> {
    fn setup(&self) {
        self.0.setup();
        self.1.setup();
    }
}

impl<Mode> TxPin<USART1> for PA9<Mode> { fn setup(&self) { self.set_alt_mode(AltMode::AF4); } }
impl<Mode> RxPin<USART1> for PA10<Mode> { fn setup(&self) { self.set_alt_mode(AltMode::AF4); } }
impl<Mode> TxPin<USART1> for PB6<Mode> { fn setup(&self) { self.set_alt_mode(AltMode::AF0); } }
impl<Mode> RxPin<USART1> for PB7<Mode> { fn setup(&self) { self.set_alt_mode(AltMode::AF0); } }

What do you think about this? I can create a PR with a concrete implementation if you want.

If dropping the Pins directly is not an option, we could also deprecate it and implement the Serial trait for both Pins and RxPin / TxPin.

cc @rnestler

Improper ADC-DMA Overruns

When calculating position in the buffer using values from the DMA registers the Buffer doesn't account for witnessing 0

I also managed to sometimes and very spuriously see values much larger than the transfer size. They were seeming arbitrary and had no significant bit pattern

CI not working

CI is not running for me on #150, probably because of the limitations of Travis. We should switch to GitHub Actions.

SPI Readings Incorrect on MISO LSB

I'm seeing a strange problem with the Full Duplex SPI library I can't figure out. Any help is appreciated!

The LSB on my reads usually comes back flipped even though my oscilloscope shows the slave output to be as expected. Only the MISO LSB is affected. This happens no matter what I'm reading or which slave device I connect.

I'm running code on a STM32L031 Evaluation Board. The example below is with a TI CC1200 as the slave.

Example

The master writes 0x3D two times. The slave returns 0x0F two times (As confirmed by the oscilloscope). The transfer function incorrectly returns 0x0E 0x0F

My Code:

#![no_std]
#![no_main]

//Generical Must Use Stuff
extern crate panic_semihosting;
use cortex_m_semihosting::hprintln;
use cortex_m_rt::entry;
use stm32l0xx_hal::{pac, prelude::*, rcc::Config};

// SPI Stuff
use stm32l0xx_hal::spi::{Polarity,Mode,Phase};

#[entry]
fn main() -> ! {
    
    // Configure the clock.
    let dp = pac::Peripherals::take().unwrap();
    let mut rcc = dp.RCC.freeze(Config::hsi16());
    
    // Setup SPI.
    let gpioa = dp.GPIOA.split(&mut rcc);   
    let sck = gpioa.pa5;              
    let miso = gpioa.pa6;
    let mosi = gpioa.pa7;
    let mode = Mode{polarity:Polarity::IdleLow,phase:Phase::CaptureOnFirstTransition};
    let mut ss = gpioa.pa3.into_push_pull_output();
    ss.set_high().unwrap();
    let mut spi = dp
        .SPI1
        .spi((sck, miso, mosi), mode, 100.khz(), &mut rcc);

    // Buffer to be used
    let mut buffer: [u8; 2] = [0x3D; 2];
    
    //Transfer the 2 byte buffer
    ss.set_low().unwrap();
    let _result = spi.transfer(&mut buffer).unwrap();
    ss.set_high().unwrap();
    
    //Print the Buffer
    hprintln!("Buffer After Transfer: {},{}", buffer[0],buffer[1]).unwrap();

    loop{}
}

Code Output:

Buffer After Transfer: 14,15

Oscilloscope Output:

ZoomedIn

ZoomedOut

Things I've Tried:

  • Adjusting the clock from 100kHz - 2MHz
  • Switching Modes (Mode 0 is recommended by slave, but I've tried all 4)
  • Using a BME680 as the slave (Same behavior in MISO LSB)
  • Using a different STM32L031
  • Reading from the slave 10+ times is a row. With the example above, only the first read comes back incorrectly (0x0e).

Pin types should be zero-sized

It looks like all pin types store their pin number and port, which makes them 2 Bytes in size. This isn't necessary, since their type already encodes both (PA3 is pin 3 on port A). Only partially type-erased pins need to store anything in them (PA only needs to store its number, but currently also stores both the pin number and port).

Linked Upcounting Timer

I wrote some code to link two 16 bit timers on an STM32L0 in order to form a 32 bit upcounting timer that can be used to measure relative time. The goal is to use this abstraction in order to implement the Monotonic trait for the RTIC scheduler.

Here's the current code, needs more testing & should be made more general (there are other timer pairs that allow linking), but seems to work already:

/// Two linked 16 bit timers that form a 32 bit upcounter.
pub struct LinkedTimer {
    /// Timer in master mode
    tim2: TIM2,
    /// Timer in slave mode
    tim3: TIM3,
}

impl LinkedTimer {
    /// Create a new `LinkedTimer` with TIM2 as master and TIM3 as slave.
    pub fn new(tim2: TIM2, tim3: TIM3, rcc: &mut Rcc) -> Self {
        // Enable timers
        rcc.rb.apb1enr.modify(|_, w| w.tim2en().set_bit());
        rcc.rb.apb1enr.modify(|_, w| w.tim3en().set_bit());

        // Reset timers
        rcc.rb.apb1rstr.modify(|_, w| w.tim2rst().set_bit());
        rcc.rb.apb1rstr.modify(|_, w| w.tim2rst().clear_bit());
        rcc.rb.apb1rstr.modify(|_, w| w.tim3rst().set_bit());
        rcc.rb.apb1rstr.modify(|_, w| w.tim3rst().clear_bit());

        // Enable counter
        tim2.cr1.modify(|_, w| w.cen().set_bit());
        tim3.cr1.modify(|_, w| w.cen().set_bit());

        // In the MMS (Master Mode Selection) register, set the master mode so
        // that a rising edge is output on the trigger output TRGO every time
        // an update event is generated.
        tim2.cr2.modify(|_, w| w.mms().variant(tim2::cr2::MMS_A::UPDATE));

        // In the SMCR (Slave Mode Control Register), select the internal
        // trigger 0 (ITR0) as trigger source (TS).
        //
        // See table 76 ("TIM2/TIM3 internal trigger connection") in the
        // reference manual RM0377.
        tim3.smcr.modify(|_, w| w.ts().variant(tim2::smcr::TS_A::ITR0));
        // Set the SMS (Slave Mode Selection) register to "external clock mode 1",
        // where the rising edges of the selected trigger (TRGI) clock the counter.
        tim3.smcr.modify(|_, w| w.sms().variant(tim2::smcr::SMS_A::EXT_CLOCK_MODE));

        Self { tim2, tim3 }
    }

    fn master(&self) -> &TIM2 {
        &self.tim2
    }

    fn slave(&self) -> &TIM3 {
        &self.tim3
    }

    /// Return the current 32 bit counter value.
    pub fn get_counter(&self) -> u32 {
        let lsb = self.master().cnt.read().cnt().bits() as u32;
        let msb = self.slave().cnt.read().cnt().bits() as u32;
        (msb << 16) | lsb
    }
}

When polling the value with 10 Hz:

...
Counter m=6110 s=861, combined=56432622, delta=1600025
Counter m=33271 s=885, combined=58032647, delta=1600025
Counter m=60432 s=909, combined=59632672, delta=1600025
Counter m=22057 s=934, combined=61232697, delta=1600025
Counter m=49218 s=958, combined=62832722, delta=1600025
Counter m=10843 s=983, combined=64432747, delta=1600025
Counter m=38004 s=1007, combined=66032772, delta=1600025
...

Should this go into the stm32l0-hal? If yes, how should it be called? Should there be a trait involved that's implemented for all timers that allow linking? Or a trait for upcounting timers that cannot be reset?

If yes, should there also be logic for detecting overflows in the HAL, or should that be part of user code?

Right now there's no API in embedded-hal that matches this pattern (see also this discussion). It also cannot be combined with CountDown because resetting the timer (on start) also resets the counter.

CC @astro @hannobraun @rnestler

serial module

Is there a reason that serial is not exported (pub ?) from stm32l0xx_hal? When I build the serial example in the hal build directory it works fine, but if I run it in my own package I get

use stm32l0xx_hal::{pac, prelude::*, rcc::Config, serial};
                                               ^^^^^^ no `serial` in the root

Also, looking at the doc serial does not appear as a module. (Nor does i2c but I haven't tried that yet.)

EXTI API is partially broken, could use an update

I've been planning to update the EXTI API for a while now, as I believe that it has started to outgrow its initial design. I started working on that, but it turns out I bit off more than I can chew.

I'm pausing this work until I can come up with some new ideas. In the meantime, I'm opening this issue to document the current shortcomings of the API, and the problems I had coming up with a better design.

Here's what's wrong with EXTI, as I see it:

  • The API is partially broken. Some of its methods rely on bit-banding, but everything I read says that bit-banding is only supported on Cortex-M3/M4. I couldn't find any evidence that STM32L0 is an exception.
  • The API is focused on configurable interrupt lines, more specifically GPIO interrupts. Direct interrupt lines aren't really considered. This means that a user has to pass all kinds of arguments that aren't actually needed.

In essence, there are three kind of interrupt lines:

  • Direct lines, which can only be enabled/disabled.
  • Configurable lines, which can be configured to trigger on rising and/or falling edges.
  • GPIO lines, which are configurable lines that also need special handling in SYSCFG, as all GPIO pins are multiplexed onto 16 EXTI lines.

The challenge I had was coming up with a design that:

  1. Doesn't require the user to pass arguments that are not actually used for a given interrupt line. That isn't just inconvenient but also error-prone, as it can easily lead to misunderstandings.
  2. Doesn't duplicate code all over the place.
  3. Does 1. and 2. without becoming super-complicated.

I'll think about this in the back of my mind for a while before I give this another go. If anyone has any ideas in the meantime, feel free to comment.

Order Issue for USART flushing

I believe the uart flush has the operation in the wrong order. please correct me if i'm wrong.

                fn flush(&mut self) -> nb::Result<(), Self::Error> {
                    // NOTE(unsafe) atomic read with no side effects
                    let isr = unsafe { (*$USARTX::ptr()).isr.read() };

                    // Frame complete, set the TC Clear Flag
                    unsafe {
                        (*$USARTX::ptr()).icr.write(|w| {w.tccf().set_bit()});
                    }

                    // Check TC bit on ISR
                    if isr.tc().bit_is_set() {
                        Ok(())
                    } else {
                        Err(nb::Error::WouldBlock)
                    }
                }

i believe the write to tccf should be done after checking for TC bit. the way it is done now means that we clear the TC flag before checking it, meaning that the condition will always return WouldBlock.

I'm happy to do a PR if i'm correct.

Support for the STM32L0x0 subfamily

Hi!

Support for the STM32L0x0 subfamily seems to be missing, according to both the README and docs.rs.
Presumably, this is because they are also missing from the Peripheral Access Crate, but what would be required (beyond some svd2rust calls) to make this happen?

I suspect @jglauche and I would be willing to make it happen, if you can point us in the right direction.

stm32l051k8u6 has 64kb of flash but is configured with 16kb

Hi!

Unless I'm missing something, I think that the memory descriptions loaded are not always correct? I'm using for example a stm32l051k8u6 and stm32l071k8u6. According to the Cargo.toml of this hal it should activate the following features:

mcu-STM32L051K8Ux = ["stm32l0x1", "ufqfpn32", "io-STM32L051", "eeprom-2048"]

# Or

mcu-STM32L071K8Ux = ["stm32l0x1", "ufqfpn32", "io-STM32L071", "eeprom-3072"]

So they both fall under the stm32l0x1 group and that loads memory_l0x1.x according to the build.rs file. This defines that there is 16kb of flash. However on both of those MCUs the flash should be 64kb according to the datasheet.

MEMORY
{     
    FLASH : ORIGIN = 0x08000000, LENGTH = 16K     
    RAM : ORIGIN = 0x20000000, LENGTH = 2K
}

Screenshot from 2021-06-10 10-35-23
Screenshot from 2021-06-10 10-35-06

This causes some linking issues because the firmware is "too big" to fit in 16K although it works fine when I select the stm32l0x3 feature.

  1. Can I override the memory configuration in my code without changing this hal crate as a temporary workaround?
  2. Can we have more granularity in the flash memory configurations?

PAC is reexported three times

It is reexported as pac, device and stm32. Reexporting it once is enough. I'd prefer to use the pac name for that and to remove the others.

PWM interface only exposed for TIM2

I'm looking at using this crate for a device built around the STM32L071 and found that the PWM interface is only exposed for TIM2. I'll need PWM on TIM3 as well and looked into making a PR when I noticed things were pretty hard-coded in pwm.rs.

Is there any reason things are the way they are with the current implementation? What kind of changes would be allowed to provide a more generic creation of PWM interfaces for the other timers?

I also have an idea of adding category features to enable specific peripherals following the tables in the reference manuals such as Table 2 in RM0377. Right now all peripherals are available even if your specific device doesn't have them.

adc to internal temperature

Is it possible to read the internal temperature with adc. I think it is connected to internal channel ADC_IN18 but I don't see how to specify that. (Newbie, so I may just be doing something stupid.)

Crates.io release

First, thank you so much for your work!

I'd like to use this crate, but:

  1. Adding it as a dependency with stm32l0xx-hal = { git = "https://github.com/stm32-rs/stm32l0xx-hal.git" } fails due to path = "../stm32-rs-arko/stm32l0".
  2. It's not present on crates.io

#1. should be more straightforward. Can we directly link the crates.io version? What's missing?

EDIT: I see, it's all being worked on right now, big time. Let me know how can I help.
EDIT2: I managed to use the code anyway, please bear with me, this is a kind of a first for me.

Serial errors are never returned

Currently any errors signaled by a UART are cleared and turned into WouldBlock. This is not ideal since it suggests that the only issue is that no data is available.

stm32l0xx-hal/src/serial.rs

Lines 443 to 452 in 68eeba6

// Some error occured. Clear the error flags by writing to ICR
// followed by a read from the rdr register
// NOTE(read_volatile) see `write_volatile` below
unsafe {
(*$USARTX::ptr()).icr.write(|w| {w.pecf().set_bit()});
(*$USARTX::ptr()).icr.write(|w| {w.fecf().set_bit()});
(*$USARTX::ptr()).icr.write(|w| {w.ncf().set_bit()});
(*$USARTX::ptr()).icr.write(|w| {w.orecf().set_bit()});
}
Err(nb::Error::WouldBlock)

It would be better to only clear the first error bit that is set, and return its corresponding Error variant. That way, no error bit gets lost.

Can't change PWM frequency after instanciating a Pwm struct

Hi,

I'm trying to change the frequency of a PWM pin dynamically. This is impossible with the currant API because the method to change the frequency is on the timer, but the timer contains all the pwm channels and when you 'associate' a pwm channel to a pin it is moved out of the timer struct. When you then try to configure the pwm frequency you get a 'borrow of partially moved value' error.

Example:

let mut pwm_timer = Timer::new(peripherals.TIM2, 100.hz(), &mut rcc);
let front_led_pwm_pin = pwm_timer.channel1.assign(gpioa.pa0);

// Some code

pwm_timer.set_frequency(10.hz(), &rcc);

Resulting in the following error:

error[E0382]: borrow of partially moved value: `pwm_timer`
   --> src/main.rs:104:5
    |
102 |     let front_led_pwm_pin = pwm_timer.channel1.assign(gpioa.pa0);
    |                                                ----------------- `pwm_timer.channel1` partially moved due to this method call
103 |
104 |     pwm_timer.set_frequency(10.hz(), &rcc);
    |     ^^^^^^^^^ value borrowed here after partial move
    |
note: this function takes ownership of the receiver `self`, which moves `pwm_timer.channel1`

Workaround

I discussed this on the matrix channel a bit and I currently have two workarounds requiring unsafe:

  1. Change the registers directly

    unsafe {
        (*pac::TIM2::ptr()).psc.write(|w| w.bits(psc_val));
        (*pac::TIM2::ptr()).arr.write(|w| w.bits(arr_val));
    }

    This works but you loose the niceties provided by the set_frequency function (e.g. provide a frequency in Hz).

  2. Make a raw pointer to the timer object to bypass Rust's ownership

    let pwm_timer_ptr: *mut Timer<TIM2> = &mut pwm_timer;
    // Some code
    unsafe { 
        (*pwm_timer_ptr).set_frequency(frequency.hz(), &context.rcc); 
    }

    This also compiles, however I'm not sure what the implications are. Is this "safe"? I guess it is as long as I don't try to associate the channel with another pin?

HAL crate

Would it be possible to support this use case in the HAL library to avoid the need to use unsafe?

  • Maybe this could be achieved by supporting changing the frequency from a Pwm instance? Although it would probably be very strange to have one Pwm instance change the frequency for all the other channels remotely. ๐Ÿค”
  • The other solution would involve some major changes because it would require the timer not to own the channels. I'm not sure what that involves.

What are your thoughts about this?

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.