stm32-rs / stm32f0xx-hal Goto Github PK
View Code? Open in Web Editor NEWA Rust `embedded-hal` implementation for all MCUs in the STM32 F0 family
License: BSD Zero Clause License
A Rust `embedded-hal` implementation for all MCUs in the STM32 F0 family
License: BSD Zero Clause License
I was poking clippy to see if it had any useful insights and it threw up this:
warning: digits grouped inconsistently by underscores
--> src/delay.rs:85:19
|
85 | us / (1_000_00 / self.clocks.sysclk().0)
| ^^^^^^^^ help: consider: `100_000`
|
= note: #[warn(clippy::inconsistent_digit_grouping)] on by default
= help: for further information visit https://rust-lang-nursery.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
Is this constant supposed to be 100,000 or should it but 1,000,000?
On the STM32F070RB, USB can only be used with the PLL source, not HSI48 (which doesn't exist). A method to select RCC.CFGR3.USBSW=1
should be provided.
Some implementation points to be considered (with my tentative answers in parenthesis):
freeze()
that the PLL is enabled if the PLL source is selected for USB? (Yes)enable()
that the selected clock's speed is 48 MHz? (Maybe not since we'd need to start also adding Rcc to the USB peripheral struct)Proposed usage:
let mut rcc = dp.RCC
.configure()
.usbclksrc(stm32f0xx_hal::rcc::USBClkSource::HSI48)
.sysclk(48.mhz())
.hclk(48.mhz())
.pclk(48.mhz())
.freeze(&mut dp.FLASH);
ADDENDUM: The STM32F1xx HAL has some logic related assertions about the USB clock, so my plan is to duplicate its API as much as possible.
Add a 'Getting Started' section to the README that points people new to embedded rust towards useful resources such as the embedded book or other bits.
The "blink" project in the example directory is not working. It can compile fine, but don't run on my nucleo F091RC board.
The led is supposed to blink, instead the led light up and that's all.
Tried to debug step by step, but can't go after the critical section call.
Btw, is there an example to blink a led without using a critical section ?
I'm looking to start adding support for a couple of devices (STM32F070
and STM32F091
), and I'm just looking for some clarification on something before I get started. I'm still relatively new to Rust so hopefully I'm not missing something simple ๐
Initial support for the STM32F030
, which is quite similar to the STM32F070
, was recently added to the repository, so I planned on basing my patch(es) off of the work already done. Most of the changes made so far make sense to me; at this time the major point of confusion for me lies in Cargo.toml
.
This all makes perfect sense, and is exactly how I would expect it to look:
[features]
rt = ["stm32f0/rt"]
stm32f042 = ["stm32f0/stm32f0x2"]
stm32f030 = ["stm32f0/stm32f0x0"]
The remainder, however, has me a bit confused:
stm32f030x4 = ["stm32f030x6"]
stm32f030x6 = ["stm32f030"]
stm32f030x8 = ["stm32f030", "stm32f0/stm32f0x0"]
stm32f030xc = ["stm32f030", "stm32f0/stm32f0x0"]
My first question is, why does the STM32F030
have a feature for the base model as well as the four available variants, while the STM32F042
only has the base model listed as a feature, ignoring its variants?
Second, I'm not quite understanding the dependencies listed for the STM32F030
variants; I'm not sure if an error has been made here or if I'm just failing to understand it. In my mind the stm32f030 = ["stm32f0/stm32f0x0"]
is the only line that should be required, and the remainders seems redundant, so hopefully somebody call fill in the blanks on what's going on here.
Thanks in advance, keep up the great work!
If there's a UART error (for example, it receives a byte when there's already a byte in the Receive Data Register), a sticky flag gets set. This flag causes read()
to return an error, but the flag doesn't get cleared. So every subsequent read()
also returns an error.
I propose you clear the flag by writing to the appropriate bit in icf
just before the error is returned.
When USE_FULL_ASSERT is enabled, STM32CubeMX generates this function in main.c:
void assert_failed(char *file, uint32_t line)
however the prototype in stm32f0xx_hal_conf.h is:
void assert_failed(uint8_t* file, uint32_t line);
This causes a compile error from GCC "error: conflicting types for 'assert_failed'". It looks like someone updated the prototype but forgot to update the body. The funny thing is uint8_t* is not correct anyway. I think it should be changed to const char*, or at least keep it at char*
If I compile my code with the stm32f031 feature it runs fine on an stm32f030k6t6. But if I enable the stm32f030x6 feature, the code doesn't compile, telling me the timer I'm using doesn't exist. But it does because the code compiled for f031 works on an f030?
This is the firmware and you can see the alternate line in Cargo.toml.
I'm probably doing something dumb.
Note - be sure to only look at the commit as I might change stuff.
I have seen a couple of examples that use the SysTick timer to count the rotations on a rotary encoder, like this example for the blue pill .
I was able to port that example to ESP32 (based on ESP32-Arduino code) as:
static void IRAM_ATTR pcnt_example_intr_handler(void *arg) {
uint32_t intr_status = PCNT.int_st.val;
int i = 0;
for (i = 0; i < PCNT_UNIT_MAX; i++) {
if (intr_status & (BIT(i))) {
/* Save the PCNT event type that caused an interrupt
to pass it to the main program */
int64_t status=0;
if(PCNT.status_unit[i].COUNTER_H_LIM){
status = r_enc_config.counter_h_lim;
}
if(PCNT.status_unit[i].COUNTER_L_LIM){
status = r_enc_config.counter_l_lim;
}
//pcnt_counter_clear(ptr->unit);
PCNT.int_clr.val = BIT(i); // clear the interrupt
count = status + count;
}
}
}
I saw there's a systick timer in this repo and a flash example based on it, but I can't reason around how I would use it for the rotary encoder.
Any help with this would be highly appreciated!
I'm using an stm32f042 and come from using the stm32f107. Getting started with this hal there is a different approach here in how gpio:s are acquired. Functions such as into_push_pull_output take a critical section here, whereas in other hal:s they take one or more configuration registers.
Is the current design of this hal by intention?
I was surprised to find that the example for how to blink more than one LED wraps the whole main loop in a critical section. If I am reading things correctly then the main reason for that is in how gpio:s are acquired.
I was looking a bit at restructuring this. It seems f1xx uses a different set of registers for setting up the gpio:s. But f3xx and f4xx seems more reasonably similar and could maybe be used as reference (i.e. place to shamelessly copy code from...). It seems like it would be a bit of work though, and it would cause breaking changes for users of this HAL. And maybe I'm missing an obvious/good reason for it being the way it is? So I thought it best to check here first before putting more effort into it :)
edit: Actually, after writing this and then letting go of trying to get rid of the critical section by changing the hal I came up with the following to not have the main loop inside the critical section while having more than one led:
let (mut led_yellow, mut led_blue, mut led_red) = cortex_m::interrupt::free(move |cs| {
let mut led_yellow = gpiob.pb3.into_push_pull_output(cs);
led_yellow.set_high().ok();
let mut led_blue = gpiob.pb4.into_push_pull_output(cs);
led_blue.set_high().ok();
let mut led_red = gpiob.pb5.into_push_pull_output(cs);
led_red.set_high().ok();
(led_yellow, led_blue, led_red)
});
This does not feel that bad. So maybe I should make a PR changing the multiple-led example to this instead of changing the whole hal?
I was going a bit nuts because my board was not USB-enumerating (for a seemingly simple RTIC example) and it turned out to be that I didn't uncomment those:
/* Uncomment the following lines if you have a chip in TSSOP20 (STM32F042F)
or UFQFPN28 (STM32F042G) package
This code enables clock for SYSCFG and remaps USB pins to PA9 and PA10.
*/
dp.RCC.apb2enr.modify(|_, w| w.syscfgen().set_bit());
dp.SYSCFG.cfgr1.modify(|_, w| w.pa11_pa12_rmp().remapped());
Fortunately, @mvirkkunen and other very helpful folks at rust-embedded discord channel pointed that out...
Now, would it be at all possible to detect those different variants automatically, somehow?
Usually I clock my precision devices from external 8MHz crystal and it fails to work here.
There is my setup:
// Setup clock from 8 MHz external crystal and 48 MHz PLL
let mut rcc = p.RCC
.configure()
.hse(8.mhz())
.sysclk(48.mhz())
.freeze(&mut p.FLASH);
There infinite busy-loop occurs on PLL ready flag awaiting.
To exclude hardware problems I tried to load code from other project in C with similar setup and it works as expected.
It seems PLL multiplier calculation is wrong because in case of using HSE as a clock source according to datasheet the input frequency does not divided by two alike a case when HSI is used.
I may mistake but that my debugger says:
// before enable_pll()
(gdb) svd RCC CR
Fields in RCC CR:
HSION: 1 Internal High Speed clock enable
HSIRDY: 1 Internal High Speed clock ready flag
HSITRIM: 16 Internal High Speed clock trimming
HSICAL: 93 Internal High Speed clock Calibration
HSEON: 1 External High Speed clock enable
HSERDY: 1 External High Speed clock ready flag
HSEBYP: 0 External High Speed clock Bypass
CSSON: 1 Clock Security System enable
PLLON: 0 PLL enable
PLLRDY: 0 PLL clock ready flag
(gdb) s
stm32f0xx_hal::rcc::inner::enable_pll (rcc=<optimized out>, c_src=0x20000fe0, ppre_bits=<optimized out>,
hpre_bits=<optimized out>, pllmul_bits=<optimized out>)
// after enable_pll()
(gdb) svd RCC CR
Fields in RCC CR:
HSION: 1 Internal High Speed clock enable
HSIRDY: 1 Internal High Speed clock ready flag
HSITRIM: 16 Internal High Speed clock trimming
HSICAL: 93 Internal High Speed clock Calibration
HSEON: 0 External High Speed clock enable
HSERDY: 0 External High Speed clock ready flag
HSEBYP: 0 External High Speed clock Bypass
CSSON: 0 Clock Security System enable
PLLON: 1 PLL enable
PLLRDY: 0 PLL clock ready flag
(gdb) svd RCC CFGR
Fields in RCC CFGR:
SW: 0 System clock Switch
SWS: 0 System Clock Switch Status
HPRE: 0 AHB prescaler
PPRE: 0 APB Low speed prescaler (APB1)
ADCPRE: 0 ADC prescaler
PLLSRC: 2 PLL input clock source
PLLXTPRE: 0 HSE divider for PLL entry
PLLMUL: 10 PLL Multiplication Factor
MCO: 0 Microcontroller clock output
MCOPRE: 0 Microcontroller Clock Output Prescaler
PLLNODIV: 0 PLL clock not divided for MCO
Unexpectedly HSE turned off and the PLLMUL is 10 (x12 multiplier) when 4 (x6) is expected.
A common way to lower the power consumption of the stm32f0xx (maybe all stm32's) seems to be to enable GPIOs (clock them), set them to analog mode and then disable the GPIO clocks. And seems like the clock signal for a port is enabled when splitting it (eg x.GPIOX.split()
) but couldn't find any way to later revert the operation and disable the clock for the same port.
So wondering where this kind of logic would belong, is there any other stm32-rs hal which already has this implemented (I couldn't find one)?
The STM32G0x0 USART peripherals support a 1-wire half-duplex mode. From the reference manual:
26.5.15 USART single-wire Half-duplex communication
Single-wire Half-duplex mode is selected by setting the HDSEL bit in the USART_CR3 register. In this mode, the following bits must be kept cleared:
- LINEN and CLKEN bits in the USART_CR2 register,
- SCEN and IREN bits in the USART_CR3 register.
The USART can be configured to follow a Single-wire Half-duplex protocol where the TX and RX lines are internally connected. The selection between half- and Full-duplex communication is made with a control bit HDSEL in USART_CR3.
As soon as HDSEL is written to โ1โ:- The TX and RX lines are internally connected.
- The RX pin is no longer used.
- The TX pin is always released when no data is transmitted. Thus, it acts as a standard I/O in idle or in reception. It means that the I/O must be configured so that TX is configured as alternate function open-drain with an external pull-up.
Apart from this, the communication protocol is similar to normal USART mode. Any conflict on the line must be managed by software (for instance by using a centralized arbiter). In particular, the transmission is never blocked by hardware and continues as soon as data are written in the data register while the TE bit is set.
The only other rust implementation I've been able to find is from the stm32l4xx-hal
crate, but it looks like that is using a different style of configuration, so it wouldn't be a straight port.
I'd be happy to try and contribute this, but I'll need a couple of pointers (still pretty new to embedded rust).
Timers are currently implemented with type Time = Hertz
. Besides the obvious oddity of using frequency as the unit for timeout times, the Hertz
wrapper type is also limited to integer values, which severely restricts the possible periods/frequencies that can be set for a timer.
This isn't a problem at the kHz or MHz scale, but at low audible frequencies (like the low end of the range of MIDI music notes), it's just not enough precision. At the very lowest values, the differences between frequencies are <1Hz, and above those, the frequencies are still noticeably out of tune until they reach a scale where integers provide enough precision.
When performing the first write transaction of the i2c scanner example, there is never a NACK, even if no device is attached on the bus.
1x STM32F072 Nucleo
2x appropriate (for example 4.7KOhm) pullup resistor (alternatively, use any known OK I2C breakout board with a known I2C address which already includes pullups)
1x https://github.com/stm32-rs/stm32f0xx-hal/blob/master/examples/i2c_find_address.rs (for easier pin access, you may want to change the SDA pin to be PB9 instead of PB7)
Wire up the pull up resistors from SCL/SDA to 3v3.
Run the i2c scanner example: https://github.com/stm32-rs/stm32f0xx-hal/blob/master/examples/i2c_find_address.rs
_devices
== 0_devices
== 1, ACK at address 0_devices
== 0_devices
== 1, ACK at address e.g. 6let _ = i2c.write(0x4, &[]);
and re-run the scan._devices
== 0_devices
== 0 (yay!)_devices
== 1_devices
== 2Addresses #15 (comment). We have a couple race conditions:
The $PXi types are send, but setting the alternate mode does a RMW to a shared register
I'd add a GPIO specific GpioShared
struct which is in parts and needs to be passed as a &mut
to the mode settings (af, pp, hs, od) and pull-up/down config.
The register modification to enable peripherals also suffer from race conditions.
Something like a token returned in the Rcc struct that has to be passed to the new methods, similar to the Gpio thing.
That makes the api a bit more annoying but safe. If can't think of better ways, but there are probably some
For some communication peripherals it is typical that only partial functionality is used, e.g. only RX or TX for serial or only MOSI for SPI. For these situations we should have filler types which can be used in that case. https://github.com/stm32-rs/stm32f4xx-hal/ already has appropriate implementations which we should be able to reuse for our cause.
Hello, I am using an STM32F031K6 and I am trying to modify the pwm example to output complementary pwm signals on PA7 and PA8 (since PA8 is on TIM1_CH1 and PA7 is on TIM1_CH1N).
I cannot figure out how to do this, when I try to add PA7 to the channels
declaration the pwm::tim1
function yells at me.
I have determined any pin on CH1, 2, 3, or 4 are accepted, but any on BKIN, ETR or CH1N are not.
Am I going about this the wrong way? Is there a different way to configure pins to use these special channels?
Thanks!
Hey, using the STM32F072, a periodic 1khz timer seems to actually be running at 2khz.
My RCC looks like this:
let mut rcc = rcc
.configure()
.hsi48()
.enable_crs(crs)
.sysclk(48.mhz())
.pclk(24.mhz())
.freeze(&mut flash);
My Timer setup looks like this:
let mut ms_timer = Timer::tim7(tim7, 1.khz(), &mut rcc);
ms_timer.listen(Event::TimeOut);
And my timer interrupt looks like this:
#[task(binds = TIM7, resources = [led, ms_timer])]
fn clock(cx: clock::Context) {
static mut TOGGLE: bool = false;
static mut STEPDOWN: usize = 0;
// Only way to clear the interrupt flag
cx.resources.ms_timer.wait().ok();
*STEPDOWN += 1;
if *STEPDOWN >= 1000 {
*STEPDOWN = 0;
} else {
return;
}
if *TOGGLE {
cx.resources.led.set_low().ok();
} else {
cx.resources.led.set_high().ok();
}
*TOGGLE = !*TOGGLE
}
With this, I would expect that the LED blinks at approximately 0.5Hz (it toggles at 1Hz), however it seems to be blinking at 1Hz (toggling at 2Hz). I'm not sure where the timer clock source is set (the code calculation uses pclk(), the reference manual mentions "CK_CNT", which I can't find in the HAL code. I have a suspicion that it is using the core clock (at 48MHz), but using the peripheral clock (at 24MHz) to calculate the counter value.
Stm32f030 datasheet defines pinsPA11 and PA12 alternate function 5 as "SCL" and "SDA" instead of "I2Cx_SCL" and "I2Cx_SDA". Current implementation assumes that these pins are used by the I2C1 interface. This is however seems to not be true for the STM32F030 as the I2C1 interface doesn't work on these pins.
Does anyone have any further information on this?
I may try using these pins with I2C2 in the future and then open a PR with the fix.
With my own code and also with a copy of examples/blinky_delay.rs, using git master and staging, I am getting
Compiling stm32f0xx-hal v0.17.1 (https://github.com/stm32-rs/stm32f0xx-hal#e679ab19)
error[E0425]: cannot find value `RCC_PLLSRC_PREDIV1_SUPPORT` in module `self::inner`
--> /../.cargo/git/checkouts/stm32f0xx-hal-3a41196e5b12fb2e/e679ab1/src/rcc.rs:457:66
|
457 | let pllprediv = match (&self.clock_src, self::inner::RCC_PLLSRC_PREDIV1_SUPPORT) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in `self::inner`
Am I missing something simple, or is this a bug?
I am using STM32F072RB, and its datasheet specifically mentions handling encoder signals with TIM2 and TIM3. I believe the interface implementation should be very similar to stm32f1xx-hal. Is there a reason why it was omitted from this crate?
docs.rs failed to build 0.16.0: https://docs.rs/crate/stm32f0xx-hal/0.16.0
[INFO] [stderr] WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
[INFO] [stdout] add606e904d8d00e9e83ceb3d5cf36d06acbc0cfc0cd35183e9f98ced61ac806
[INFO] running `"docker" "start" "-a" "add606e904d8d00e9e83ceb3d5cf36d06acbc0cfc0cd35183e9f98ced61ac806"`
[INFO] [stderr] Compiling semver-parser v0.7.0
[INFO] [stderr] Compiling typenum v1.11.2
[INFO] [stderr] Compiling proc-macro2 v1.0.8
[INFO] [stderr] Compiling unicode-xid v0.2.0
[INFO] [stderr] Compiling cortex-m v0.6.2
[INFO] [stderr] Checking stable_deref_trait v1.1.1
[INFO] [stderr] error[E0463]: can't find crate for `core`
[INFO] [stderr] |
[INFO] [stderr] = note: the `thumbv6m-none-eabi` target may not be installed
[INFO] [stderr]
[INFO] [stderr] error: aborting due to previous error
[INFO] [stderr]
[INFO] [stderr] For more information about this error, try `rustc --explain E0463`.
[INFO] [stderr] error: could not compile `stable_deref_trait`.
[INFO] [stderr] warning: build failed, waiting for other jobs to finish...
[INFO] [stderr] error: build failed
[INFO] running `"docker" "inspect" "add606e904d8d00e9e83ceb3d5cf36d06acbc0cfc0cd35183e9f98ced61ac806"`
[INFO] running `"docker" "rm" "-f" "add606e904d8d00e9e83ceb3d5cf36d06acbc0cfc0cd35183e9f98ced61ac806"`
[INFO] [stdout] add606e904d8d00e9e83ceb3d5cf36d06acbc0cfc0cd35183e9f98ced61ac806
Each of the stm32f0x0
, stm32f0x1
, and stm32f0x2
lines have had support added at this time. The only remaining devices belong to the stm32f0x8
line, and are as follows:
If nobody else feels like hopping on these I will likely start picking away at them shortly, but just wanted to document the progress made thus far.
Hello,
I'm trying to run the blinky-delay example on an stm32f030rc. At first I have tried copying it in a new project and adding the crate as a dependency, by removing the crate
self reference and adding version 0.14.1 to Cargo.toml
dependencies:
#![no_main]
#![no_std]
#[allow(unused)]
use panic_halt;
use stm32f0xx_hal as hal;
use hal::{delay::Delay, prelude::*, stm32};
use cortex_m::peripheral::Peripherals;
use cortex_m_rt::entry;
#[entry]
fn main() -> ! {
if let (Some(mut p), Some(cp)) = (stm32::Peripherals::take(), Peripherals::take()) {
cortex_m::interrupt::free(move |cs| {
let mut rcc = p.RCC.configure().sysclk(8.mhz()).freeze(&mut p.FLASH);
let gpioa = p.GPIOA.split(&mut rcc);
// (Re-)configure PA1 as output
let mut led = gpioa.pa1.into_push_pull_output(cs);
// Get delay provider
let mut delay = Delay::new(cp.SYST, &rcc);
loop {
led.toggle();
delay.delay_ms(1_000_u16);
}
});
}
loop {
continue;
}
}
Unfortunately this leads to the compiler complaining about unresolved imports for all modules used:
Updating crates.io index
Compiling stm32f0 v0.7.1
Compiling stm32f0xx-hal v0.14.1
Compiling app v0.1.0 (/home/maldus/Projects/app)
error[E0432]: unresolved imports `hal::delay`, `hal::prelude`, `hal::stm32`
--> src/main.rs:10:11
|
10 | use hal::{delay::Delay, prelude::*, stm32};
| ^^^^^ ^^^^^^^ ^^^^^ no `stm32` in the root
| | |
| | could not find `prelude` in `hal`
| could not find `delay` in `hal`
warning: unused import: `prelude::*`
--> src/main.rs:10:25
|
10 | use hal::{delay::Delay, prelude::*, stm32};
| ^^^^^^^^^^
|
= note: #[warn(unused_imports)] on by default
error[E0599]: no method named `mhz` found for type `{integer}` in the current scope
--> src/main.rs:19:54
|
19 | let mut rcc = p.RCC.configure().sysclk(8.mhz()).freeze(&mut p.FLASH);
| ^^^
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0432, E0599.
For more information about an error, try `rustc --explain E0432`.
error: Could not compile `app`.
I have since tried to compile examples directly from the repository itself, but I encountered similar issues. I'm working on stable, do I need a particular toolchain version to compile this?
$ rustc -V
rustc 1.36.0 (a53f9df32 2019-07-03)
An ability to set a "bypass" clock source should be added. Currently, HSE always is in the non-bypassed mode when using this HAL.
I started to code this (it's fairly simple), but don't know what the desired Rcc API would be.
My first choice would be to modify the existing fn to be pub fn hse<F>(mut self, freq: F, bypassed: bool)
. The disadvantage is that this breaks existing code.
Another implementation would be to create pub fn hse_bypass<F>(mut self, bypassed: bool)
, with a default to use a XTAL (non-bypassed) source.
This feature is needed for most of the Nucleo boards, where the clock source is an external oscillator (the STLink). I can attempt to implement either API (though I'm very new to Rust).
I'm attempting to use a periodic timer using the HAL Timer driver, on an stm32f031, and have hit a limitation.
I can instantiate it and use the driver to setup the desired overflow rate, and then turn on the interrrupt -- so that's easy enough:
let timer = hal::timers::Timer::<pac::TIM16>::tim16(cx.device.TIM16, 1.khz(), &mut rcc);
timer.listen(hal::timers::Event::TimeOut);
I can then create a handler for the TIM16 vector... but now I need to clear the UIF flag, and the only way I can come up with to do it is to steal
another copy of the TIM16 register block. One solution is that probably the driver should provide a way to clear the interrupt flags, but it seems to me that a more general solution is to make the tim
field (the reg block) public so that if I have a mutable Timer
I can manipulate it's registers as well -- this allows for more general extensions of the driver as well as simply clearing the interrupt flag.
Perhaps I am missing something about the intended use here. I'm fairly new to Rust still -- still trying to adapt from C++.
Add functionality for reading ADC values.
In the current CHANGELOG
, we see Added USB driver
under 0.15.2
, but this feature is missing from that version. As far as I can tell, no released version contains the stm32-usbd
feature.
Took me a little while to realize it wasn't actually released, because the newest release of stm32-usbd
no longer has the board-specific impl
s but I couldn't activate the stm32-usbd
feature in this one even though it's in the CHANGELOG
.
Thanks!
Thanks for all your hard work on this crate which is really appreciated. If you like, please add your credentials in your preferred form to the authors list in Cargo.toml
.
Currently, it's possible to configure the SPI1 and I2C1 peripherals like this:
let sda = gpiob.pb7.into_alternate_af1(cs);
let scl = gpiob.pb6.into_alternate_af1(cs);
let i2c = hal::i2c::I2c::i2c1(p.I2C1, (scl, sda), 100.khz(), &mut rcc);
let sck = gpioa.pa5.into_alternate_af0(cs);
let miso = gpioa.pa6.into_alternate_af0(cs);
let mosi = gpioa.pa7.into_alternate_af0(cs);
let spi = hal::spi::Spi::spi1(
p.SPI1,
(sck, miso, mosi),
ws2812_spi::MODE,
3_000.khz(),
&mut rcc,
);
However, this configuration doesn't actually work since on most STM32F0xx parts, SPI1
and I2C1
share DMA channels 2 (TX) and 3 (RX). Even if you don't use the DMA features, the attempt to share resources puts both peripherals into a bad state. On STM32F07x
parts we could use the I2C1_DMA_RMP
or SPI1_DMA_RMP
bits in SYSCFG_CFGR1
to remap one of the two peripherals to use channels 6&7, so any API for this will need to support that somehow.
This also raises a related concern, which is that there doesn't seem to be a safe way to disable peripherals. My expectation would be that the release()
methods would disable them, but looking at the implementations that doesn't seem to be the case. To support it, we'd probably have to pass the RCC
into release()
.
We should prevent the use of HSI48 on devices which don't support it.
cf.
https://github.com/stm32-rs/stm32f0xx-hal/blob/master/src/rcc.rs#L34
https://github.com/stm32-rs/stm32f0xx-hal/blob/master/src/rcc.rs#L90
https://github.com/stm32-rs/stm32f0xx-hal/blob/master/src/rcc.rs#L169
et al
CC @bentwire
When doing a SPI transfer with the blocking traits the CS line goes high before the transfer is completed.
let mut buf: [u8; 1] = [0];
w5500_cs.set_low().unwrap();
spi.write(&[0x00, 0x39, 0x00]).unwrap();
spi.transfer(&mut buf).unwrap();
w5500_cs.set_high().unwrap();
When I add some hacky polling for the BSY
bit (0x80
) in the SPI status register to that code, the transfer works as expected, with the CS line going high only after completion of the SPI bus activity.
let mut buf: [u8; 1] = [0];
w5500_cs.set_low().unwrap();
spi.write(&[0x00, 0x39, 0x00]).unwrap();
let mut spi1_sr: u32 = unsafe { core::ptr::read_volatile(0x4001_3008 as *const u32) };
while spi1_sr & 0x80 != 0x00 {
spi1_sr = unsafe { core::ptr::read_volatile(0x4001_3008 as *const u32) };
}
spi.transfer(&mut buf).unwrap();
let mut spi1_sr: u32 = unsafe { core::ptr::read_volatile(0x4001_3008 as *const u32) };
while spi1_sr & 0x80 != 0x00 {
spi1_sr = unsafe { core::ptr::read_volatile(0x4001_3008 as *const u32) };
}
w5500_cs.set_high().unwrap();
This seems to be a case of under-specified behavior in the embedded-hal (which is fixed in 1.0.0), the docs do not specify if the blocking
part is just blocking for buffer space, after which the function returns, or blocking for buffer space, and polling for completion.
I think this is a bug though, because despite the under-specification other STM32 embedded HAL traits do block for completion (such as the stm32f1 which I also tested), this appears to be the odd one out.
I am using a STM32F070CBTx.
Relevant Cargo.toml
:
[dependencies]
cortex-m = "0.6.4"
cortex-m-rt = { version = "0.6.13", features = ["device"] }
embedded-hal = "0.2.4"
stm32f0xx-hal = { version = "0.17.1", features = ["stm32f070xb"] }
rtt-target = "0.3.0"
$ cargo -V
cargo 1.50.0-nightly (a3c2627fb 2020-12-14)
Hi I'm looking for a flash driver for stm32f091, can you help me with that?
In code that uses multiple device crates I have started having cargo update
problems because of the change in the dependency bxcan = "0.4"
(see stm32-rs/bxcan#22 (comment) and stm32-rs/stm32f7xx-hal#114 (comment)). Can the dependency be more flexible, say bxcan = ">=0.4"
?
Hello,
I'm starting a new project with an stm32f0 device which I specifically chose to be able to develop Rust firmware. I made a few experiments with just registers and the cortex_m
crate following along the Embedded Rust book's teaching examples to really understand how the architecture works, then started approaching this library for ease of use.
I have a few years of experience as an embedded C developer, a few months of tutorials and experiments with Rust and overall good comprehension of modern programming language features. I'm having a few difficulties in understanding what is the proper way to do things in Rust and I could use some clarifications.
I took the blinky example and started working from that: I wanted to add a systick interrupt to keep track of milliseconds elapsed since the start:
#[exception]
fn SysTick() -> ! {
unsafe {
MILLIS += 1;
}
}
and then read the shared variable MILLIS
to manage various temporized functions in the main cycle. When I tried this approach however I realized that it didn't work because the main cycle (following the blinky example) was wrapped in a CriticalSection
context, disabling all interrupts by default.
When I tried moving the operations outside of the critical section (i.e. blinking the LED every 1000 milliseconds) the compiler meticulously pointed out that I had no way of accessing the PA0<Output<PushPull>>
reference I was using to toggle the LED. Plus, I can't even create one without a critical section token.
In fact, there is no example in the repository that has concurrent main task and interrupts: it's either just the main cycle or just the interrupt, with the main cycle stuck in an empty loop (thinking about led_hal_button_irq.rs
). So, my first question is: how would I proceed to obtain a main cycle that allows interruptions but has obtained a reference to the peripherals? The interrupt::free
context moves the values so I can't use them afterwards; I know I can populate a Mutex
or RefCell
, but those are data structures used to share and sync references between threads, and I have no need for sharing: only my main thread will access those, with the interrupts occasionally setting flags and such.
While I was looking for a solution I also dropped the idea of keeping track of time via interrupt and instead I'm using a free counter. Nonetheless I expect to hit this obstacle again soon enough.
This is the code I've written so far, just to explain what I was trying to accomplish.
#![no_main]
#![no_std]
extern crate panic_semihosting;
use stm32f0xx_hal as hal;
use hal::{prelude::*, stm32};
use cast;
use cortex_m::peripheral::Peripherals;
use cortex_m_rt::entry;
use cortex_m_rt::exception;
#[entry]
fn main() -> ! {
let mut p = stm32::Peripherals::take().unwrap();
let cp = Peripherals::take().unwrap();
cortex_m::interrupt::free(move |cs| {
p.RCC.apb1enr.modify(|_, w| w.tim6en().set_bit());
p.RCC.apb1rstr.modify(|_, w| w.tim6rst().set_bit());
p.RCC.apb1rstr.modify(|_, w| w.tim6rst().clear_bit());
let mut rcc = p.RCC.configure().sysclk(8.mhz()).freeze(&mut p.FLASH);
let gpioa = p.GPIOA.split(&mut rcc);
// (Re-)configure PA1 as output
let mut led = gpioa.pa12.into_push_pull_output(cs);
let tim = p.TIM6;
// pause
tim.cr1.modify(|_, w| w.cen().clear_bit());
// restart counter
tim.cnt.reset();
let frequency = 1000;
let ticks = rcc.clocks.pclk().0 / frequency;
let psc = cast::u16(ticks - 1).unwrap(); // / (1 << 16)).unwrap();
tim.psc.write(|w| unsafe { w.psc().bits(psc) });
// start counter
tim.cr1.modify(|_, w| w.cen().set_bit());
let mut ts: u32 = 0;
/* The main loop should be outside of the interrupt-free context */
loop {
if is_expired(ts, get_millis(&tim), 1000) {
led.toggle();
ts = get_millis(&tim);
}
check_overflow(&tim);
}
});
loop {
continue;
}
}
static mut MILLIS: u32 = 0;
pub fn is_expired(start : u32, current : u32, delay : u32) -> bool {
let finish = start.wrapping_add(delay);
(finish.wrapping_sub(current) as i32) < 0
}
pub fn check_overflow(timer: &hal::stm32::TIM6) {
cortex_m::interrupt::free(|_| {
if timer.sr.read().uif().is_update_pending() {
unsafe { MILLIS += 0xFFFF }
timer.sr.write(|w| w.uif().clear());
}
});
}
pub fn get_millis(timer: &hal::stm32::TIM6) -> u32 {
let mut result: u32 = 0;
cortex_m::interrupt::free(|_| unsafe { result = MILLIS + timer.cnt.read().bits() });
result
}
Please be patient if I'm missing a key aspect of Rust programming, I'm new to it and I promise I did my research before asking.
I'm trying to use the USART2 peripheral on my stm32f030RC. I'm not using interrupts, so from time to time an overrun interrupt is to be expected (and is acceptable); however I am unable to manage the error with the tools provided by the API.
When an overrun occurs the serial.read
method returns an error accordingly, but:
Serial
object, so I cannot access its ICF register to clear the ORE bit.Serial
constructor resets the CR3 register (https://docs.rs/stm32f0xx-hal/0.14.1/src/stm32f0xx_hal/serial.rs.html#350); I cannot change it afterwards because the USART2 reference is gone and that bit cannot be changed while the peripheral is on.How do I manage overrun errors? Either clearing the flag or disabling it altogether would be fine for me.
Edit: this problem would be resolved by PR #63
I'd really like to keep them in there. They seem like good general representations and easily give an overview about how stuff is supposed to work. I'd remove the more specific ones (all the i2c & spi ones) and replace them with generalized versions (e.g. for spi a loopback example).
They provide quick example code which (even if they exist) are only partly given by doc tests. Especially for something like interrupts. They also serve as a way to have sample programs to compare binaries.
If we use a linker file with the minimum amount of memory and keep them in the CI system, I see little potential for breakage.
It also enables people that already have some chip, but not a specific board, get started, especially if they already have some experience in embedded c.
As we currently can't use doc tests, they also server as some form of build test.
rust-lang/cargo#6453 was merged and will land soon which should allow us to clean up the cfg-gating a bit.
Thanks for all your hard work on this crate which is really appreciated. If you like, please add your credentials in your preferred form to the authors list in Cargo.toml
.
I need to configure the 4th channel to trigger timer1 3 complementary output and adc.
macro! pwm_4_channels_with_3_complementary_outputs() macro, the point I'm stuck on is that I don't know how to access the 4th channel.
I don't want to connect to the GPIO pin to which the 4th channel is connected
#![deny(unsafe_code)]
#![no_main]
#![no_std]
// Halt on panic
use panic_halt as _;
use cortex_m;
use cortex_m_rt::entry;
use stm32f0xx_hal as hal;
use hal::{delay::Delay, pac::{self, TIM1}, prelude::*, pwm, adc};
#[entry]
fn main() -> ! {
if let Some(mut dp) = pac::Peripherals::take() {
// Set up the system clock.
let mut rcc = dp.RCC.configure().sysclk(8.mhz()).freeze(&mut dp.FLASH);
let gpioa = dp.GPIOA.split(&mut rcc);
let gpiob = dp.GPIOB.split(&mut rcc);
let channels = cortex_m::interrupt::free(move |cs| {
(
gpioa.pa8.into_alternate_af2(cs), // on TIM1_CH1
gpiob.pb13.into_alternate_af2(cs), // on TIM1_CH1N
gpioa.pa9.into_alternate_af2(cs), // on TIM1_CH2
gpiob.pb14.into_alternate_af2(cs), // on TIM1_CH2N
gpioa.pa10.into_alternate_af2(cs), // on TIM1_CH3
gpiob.pb15.into_alternate_af2(cs), // on TIM1_CH3N
)
});
let pwm = pwm::tim1(dp.TIM1, channels, &mut rcc, 20u32.khz());
let (mut pwm_c_h, mut pwm_c_l, mut pwm_b_h, mut pwm_b_l, mut pwm_a_h, mut pwm_a_l) = pwm;
let max_duty = pwm_c_h.get_max_duty();
pwm_c_h.set_duty(max_duty / 2);
pwm_c_h.enable();
pwm_c_l.enable();
pwm_b_h.set_duty(max_duty / 2);
pwm_b_h.enable();
pwm_b_l.enable();
pwm_a_h.set_duty(max_duty / 2);
pwm_a_h.enable();
pwm_a_l.enable();
// simple duty sweep
if let Some(cp) = cortex_m::Peripherals::take() {
let mut delay = Delay::new(cp.SYST, &rcc);
let steps = 100;
loop {
for i in 0..steps {
pwm_c_h.set_duty(max_duty / steps * i);
pwm_b_h.set_duty(max_duty / steps * i);
pwm_a_h.set_duty(max_duty / steps * i);
delay.delay_ms(30u16);
}
for i in (1..steps).rev() {
pwm_c_h.set_duty(max_duty / steps * i);
pwm_b_h.set_duty(max_duty / steps * i);
pwm_a_h.set_duty(max_duty / steps * i);
delay.delay_ms(30u16);
}
}
}
}
// something went wrong when acquiring peripheral access
loop {
cortex_m::asm::nop();
}
}
Absolutely the same code is working fine on 0.16 and doesn't on 0.17. I believe this is due to some performance updates merged lately. There is simply nothing on SCK or MOSI pins. Initialisation code is stanard:
const MODE: Mode = Mode {
polarity: Polarity::IdleLow,
phase: Phase::CaptureOnFirstTransition,
};
let spi = Spi::spi1(p.SPI1, (mcp_sck, mcp_miso, mcp_mosi), MODE, spi_freq, &mut rcc);
Usage is also standard, write
and transfer
methods are used.
error[E0425]: cannot find value RCC_PLLSRC_PREDIV1_SUPPORT
in module self::inner
--> C:\Users\xxxxx.cargo\git\checkouts\stm32f0xx-hal-3a41196e5b12fb2e\917c181\src\rcc.rs:460:66
|
460 | let pllprediv = match (&self.clock_src, self::inner::RCC_PLLSRC_PREDIV1_SUPPORT) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in self::inner
For more information about this error, try rustc --explain E0425
.
error: could not compile stm32f0xx-hal
due to previous error
It would be great to be able to store analog pins in the array and process them sequentially.
If one needs to have multiple analog pins in the array it's not possible -- types are different and Channel trait has type-associated function.
Currently, Adc implements OneShot
trait which accepts pin.
Line 604 in 9f21c49
channel
(which is the channel id) is used instead.
Perhaps we can change the function to fn channel(&self)
but it might break some assumptions. Would be great to hear some input
I just noticed that using *const
means we can't share peripherals between interrupt/exception handlers and main anymore:
error[E0277]: `*const stm32f0::stm32f0x2::USART2` cannot be sent between threads safely
--> examples/i2c_hal_ssd1306alphabeter.rs:23:1
|
23 | / static PANIC_SERIAL: Mutex<RefCell<Option<hal::serial::Tx<USART2>>>> =
24 | | Mutex::new(RefCell::new(None));
| |___________________________________^ `*const stm32f0::stm32f0x2::USART2` cannot be sent between threads safely
I am currently working on a design involving an STM32F042 microcontroller that involves the use of both SPI controllers, one operating as a master and one operating as a slave. While this HAL does offer facilities for driving an SPI controller in master mode, I do not see any code for abstracting over a slave controller. How would I go about that, if I were to use this library to handle other aspects of the microcontroller?
I started implementing PWM for this crate in two slightly different ways:
https://github.com/stm32-rs/stm32f0xx-hal/tree/features/pwm
This uses the typical approach of initialising the PWM peripheral with the timer and pins to use for PWM. The code is loosely based on https://github.com/stm32-rs/stm32f1xx-hal/blob/master/src/pwm.rs and I can see a number of advantages and disadvantages with this.
Advantages:
Disadvantages:
https://github.com/stm32-rs/stm32f0xx-hal/tree/features/pwm-alternate
This approach only binds the timer to provide 4 PWM channels without any association to a GPIO.
Advantages:
Disadvantages:
I'm a bit on the fence which variant we should go for. Approach one is rather limiting, especially if we want to support advanced uses of PWM (and maybe other nice timer features). It is also very complex to implement for all chips and all possible combinations at once. On the other hand it would be great to have a way of telling a user that some timer/channel/GPIO combination will not work or is incorrectly configured.
Ideas?
CC @zklapow
Timer in stm32f4xx-hal crate implements the Cancel trait.
You may be interested in doing the same for this crate.
Examples:
Not sure if that's an issue or something is missing in the interrupts setup:
I tried using the blinky-irq example to do something else, e.g. display something on SSD1306, while the interrupts are updating values, toggling LEDs, etc.
As it didn't work, I tried adding single pieces one by one. The conclusion is: as soon as I put a loop {} in main () in this code https://github.com/nebelgrau77/STM32F0_blinky_IRQ
the LEDs stop blinking. It doesn't matter if it's an empty loop, or with a NOP, or with any actual stuff being done.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.