Converting a Simple HAL-based STM32F1 blinky to STM32F4
Categories: blackpill, bluepill, embedded, microcontroller, rust, stm32
Yes, I know I could just grab the blink example from the stm32f4xx-hal repo. But this much more time-consuming way teaches more about both chips, and increses my familiarity with the all-important reference manuals.
- Clone the F1 repo, give
clone
a second parameter to change the name of the folder you clone into - Then disconnect it from the original remote, because you wouldn’t want to push the converted code to the original repo
git clone https://github.com/GregWoods/01-blink-f1-rs.git 01-blink-f4-rs
- Create the new repo in github
gh repo create 01-blink-f4-rs
- And follow the instructions on the new repo default page, to connect your new remote to your local repo
got remote remove origin
git remote add origin https://github.com/GregWoods/01-blink-f4-rs.git
git push -u origin master
Change the Setup
.cargo/config
- change
thumbv7m-none-eabi
tothumbv7em-none-eabihf
(2 instances) - change
arm-none-eabi-gdb
toarm-none-eabihf-gdb
(1 instance)
- change
cargo.toml
- change your package name from
01-blink-f1-rs
to01-blink-f4-rs
- change the hal dependency to
[dependencies.stm32f1xx-hal]
- and change the features list: remove
stm32f103
, usestm32f411
(or ```stm32f401`` for the cheaper verison of the black pill) - Add a version number to
stm32f4xx-hal
. Currently 0.7.0 (we need this the first time, becaue we don’t have a valid value in cargo.lock) Trying to build will report a list of possible versions) - remove the ```medium`` feature from the hal crate
- change your package name from
memory.x
- The FLASH and RAM start addresses are the same
- But the sizes for the stm32f411 are: FLASH 512K; RAM: 128K (as far as I can tell)
.vscode/launch.json
- replace
STM32F103VCT6
withSTM32F411CEU6
- Replace
target/stm32f1x.cfg
withtarget/stm32f4x.cfg
- Change the svd from
STM32F103.svd
toSTM32F411.svd
- Change the executable path, replace
thumbv7m-none-eabi
withthumbv7em-none-eabihf
- replace
- Go and download STM32F411.svd
main.rs
- change the use statement from
stm32f1xx_hal
tostm32f4xxx_hal
- replace
pac
from the ‘use’, withstm32
- an odd naming inconsistency between the stm32f1 and stm32f4 hal projects rustup target install thumbv7em-none-eabihf
to install the missing bits of the toolchain
- change the use statement from
Change the Code
Run cargo build
and fix the bugs one by one referring to the hal repo: github.com/stm32-rs/stm32f4xx-hal
I’m not entirely sure you’ll get the errors come back in the same order as listed here! It is likely I fixed more than one at once.
Error 1
16 | use stm32f1xx_hal::{
| ^^^^^^^^^^^^^ use of undeclared type or module `stm32f1xx_hal`
An easy one. Change stm32f1xx_hal
to stm32f4xx_hal
Error 2
18 | pac,
| ^^^ no `pac` in the root
In this hal, the peripheral access crate (pac) is called stm32. Which leads to our next fix…
Error 3
| let dp = pac::Peripherals::take().unwrap();
| ^^^ use of undeclared type or module `pac`
Change to
let dp = stm32::Peripherals::take().unwrap();
Error 4
| let mut flash = dp.FLASH.constrain();
| ^^^^^^^^^ method not found in `stm32f4::stm32f411::FLASH`
‘constrain’ is not a thing in the Reference Manual. So this is something added by the HAL, probably to keep the rust compiler happy. In the hal code, inside flash.rs is the following comment… /// Constrains the FLASH peripheral to play nicely with the other abstractions
The stm32f4xx-hal doesn’t have a flash.rs. A quick search in the github repo shows it uses stm32.FLASH from the PAC. Before I went down a rabbit hole, I noticed that the probably fix for the next will mean we don’t need to use FLASH at this time. So I remove the line. I will have to revisit this sometime, but not today!
Error 5
| let clocks = rcc.cfgr.freeze(&mut flash.acr);
| ^^^^^^ expected 0 parameters
‘freeze’ doesn’t appear to be a feature of RCC_CFGR registers as far as I can tell. This is something “Rusty”. According to ‘freeze’ in the stm32f1xx-hal, an ‘acr’ parameter exists, but is not used! It looks like the stm32f4 hal has simply removed this unused param, so we should be able to remove it here. Easy!
Error 6
| let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
| ^^^^^ expected 0 parameters
The syntax of ‘splitting’ the GPIO port remains a mystery to me for now, and passing rcc.apb2 is even more confusing. The fact that the stm32f4 doesn’t require this parameter is all I need to know for now!
Error 7
|
| let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
| ^^^^^^^^^^^^^^^^^^^^^ expected 0 parameters
Examine the Reference Manual and HAL. In the stm32f4 reference manual, there are no search results for CRL or CRH. Things are obviously done a bit differently to the stm32f1, where ```CRH`` is ‘Port Configuration Register High’
stm32f1
For port configuration, the MODE bits(2 bits per gpio line) and CNF bits (also 2 bits per ‘pin’) for each GPIO pin are interleaved, so we can only store 8 pins worth of setting within 32 bits.
The CRL register is the same, but for GPIO pins 0..7
stm32f4
The arragement is simpler for this chip. The settings for all 16 pins on a single GPIO port are stored in one 32 bit register.
Therefore the HAL simply doesn’t need to refer to CRH or CRL, it just uses the MODE register directly.
Error 8
| let mut timer = Timer::syst(cp.SYST, &clocks).start_count_down(1.hz());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 3 parameters
Looking at the stm32f4xx-hal
code, in timer.rs
, we find:
impl Timer<SYST> {
/// Configures the SYST clock as a periodic count down timer
pub fn syst<T>(mut syst: SYST, timeout: T, clocks: Clocks) -> Self
where
T: Into<Hertz>,
well, this is handy - the timeout in Hertz is set in the constructor, instead of in the chained start_count_down
.
Fixing that has shown another error
|
| let mut timer = Timer::syst(cp.SYST, 1.hz(), &clocks);
| ^^^^^^^
| |
| expected struct `stm32f4xx_hal::rcc::Clocks`, found `&stm32f4xx_hal::rcc::Clocks`
| help: consider removing the borrow: `clocks`
As is often the case, the compiler suggests the fix. I’m unsure of the consequences of giving syst ownership of clocks, but that is for another day.
Our converted line looks like:
let mut timer = Timer::syst(cp.SYST, 1.hz(), clocks);
We can fix a couple of compiler warnings about variables which don’t need to be mutable, and everything is good.
This should all now build, and the debugger can step into the code.
Comments