Rust 嵌入式 - STM32|新建工程、点亮LED

1 配置开发环境

首先介绍一下博主使用的Rust嵌入式开发环境的配置。

在这里就将Rust安装省略了,如果你对Rust语言不熟悉的话不建议入门Rust嵌入式开发。

1.1 VSCode Plugins

博主选择配置后的VSCode作为Rust嵌入式开发环境。首先创建一个新的配置文件(将各个插件之间彼此隔离),之后安装插件:rustCortex-DebugEven Better TOMLDependi

1.2 probe-rs

probe-rs 是由Rust编写的嵌入式程序烧录工具,它的功能和OpenOCD比较类似,都用来烧录.elf文件。很多Rust嵌入式的官方教程和官方模版工程都采用probe-rs作为例子,所以这里博主推荐使用probe-rs进行程序烧录。

博主使用下来,发现probe-rs在很多情况下比OpenOCD更好用。它相较于OpenOCD有以下优势:

  • 开箱即用,基本无需配置,你甚至都不需要选择烧录器类型应该是st-link还是dap-link;
  • 和Cargo有很好的兼容性,可以使用cargo run命令直接“无脑”烧录程序,开发体验统一;
  • C/C++工程也可以使用probe-rs进行烧录。

使用下面的命令安装 probe-rs(任选其一即可,有些方法可能安装速度较慢,此时考虑魔法上网或者换源):

# homebrew (for MacOS and some Linux)
brew install probe-rs/probe-rs/probe-rs

# windows
irm https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.ps1 | iex

# Cargo
cargo install cargo-binstall
cargo binstall probe-rs-tools

1.3 添加编译 target

根据使用的Cortex内核来添加编译target,如果使用STM32F103(Cortex-M3):

rustup target add thumbv7m-none-eabi

STM32H750为Cortex-M7F架构,拥有双精度浮点硬件运算单元(博主的H750芯片好像又有问题,创建工程后烧录提示找不到0x080000000x08000298的地址,非常奇怪):

rustup target add thumbv7em-none-eabihf

2 从 cargo new 创建工程

虽然可以使用 kunrling-rs 官方维护的模板库创建工程,但是博主认为这样的方法对于Rust嵌入式入门是不利的,因为很多细节对于入门的开发者是隐藏的,或者官方认为是“你应该提前知晓”的,这就导致官方的模板库对于初学者而言看起来非常臃肿。所以,一个简单而有效的方法是完全从零开始创建一个新的工程(尽量简洁而优雅),目的在于了解和学习工程中每个部件是如何运行的,以便于之后出问题了应该去哪里解决和追踪。

博主主要参考的资料来源于 stm32f1xx-hal ,在这里新建一个STM32F103点灯的工程。以供大家学习和参考:

2.1 工程创建

首先,使用cargo new led-f1命令创建一个名为led-f1的工程,并使用VSCode打开:

cargo new led-f1 
cd led
code .

2.2 配置 Cargo 和 probe-rs

在工程根目录下创建memory.x文件,并在其中填写如下内容:

MEMORY
{
    /* NOTE 1 K = 1 KiBi = 1024 bytes */
    FLASH : ORIGIN = 0x08000000, LENGTH = 64K
    RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

这个文件为probe-rs提供可执行文件的烧录位置信息。注意,这里的FLASHRAM的大小和起始地址和STM32F103C8T6一致。如果你使用其他cortex微控制器,相应的参数需要改成对应的大小。

创建.cargo/config.toml文件,并在文件中添加如下内容:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = 'probe-rs run --chip STM32F103C8'

rustflags = [
    "-C", "link-arg=-Tlink.x",
    # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
    # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
    "-C", "link-arg=--nmagic",
]

[build]
target = "thumbv7m-none-eabi"

简单解释一下上面的配置选项。首先,前面两行规定了cargo run的行为:使用probe-rs将可执行文件烧录到STM32F103C8芯片中。接下来,rustflags定义了一系列编译选项,规定了编译烧录时使用link.x脚本,当使用memory.x定义FLASHRAM的区域时这个脚本是必须的,而nmagic参数在FLASHRAM地址不在0x10000时才需要添加。最后,[build]模块指定了编译的目标(target),这里的值和最开始使用rustup add target命令添加的target是一致的。

2.3 添加 Crate

Cargo.toml的依赖中添加如下内容:

[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
panic-halt = "1.0"

stm32f1xx-hal = { version = "0.11.0", features = ["stm32f103", "rtic", "medium"] }

cortex-mcortex-m-rtpanic-halt三个crate对于大多数Cortex-M微控制器都是通用且必须的。前两个为Cortex-M控制器提供了环境支持和运行时支持,最后一个使得程序在panic时自动停止运行。

最后一个crate stm32f1xx-hal 是STM32F1的HAL库,由官方维护。其中features参数的写法也主要参考了官方的文档和示例,指定了芯片的具体型号,运行时环境等信息。

2.4 编写程序

博主尽量演示代码的逐步编写过程,以帮助读者更好地理解。修改main.rs为如下内容:

#![no_std]    // 非标准库工程,没有main函数
#![no_main]

use panic_halt as _;
use cortex_m_rt::entry;

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

#[entry]
fn main() -> ! {    // 注意这里的main函数返回一个!
    
    
    loop {
        
    }
}

接下来仅展示main函数中的内容。首先对内核外设和设备外设进行获取:

#[entry]
fn main() -> ! {
    let cp = cortex_m::Peripherals::take().unwrap();    // Core Peripherals
    let dp = pac::Peripherals::take().unwrap();     // Device Peripherals
    
    loop {
        
    }
}

初始化时钟、GPIO和Delay函数:

#[entry]
fn main() -> ! {
    let cp = cortex_m::Peripherals::take().unwrap();    // Core Peripherals
    let dp = pac::Peripherals::take().unwrap();     // Device Peripherals

    // Clock configration
    let mut rcc = dp.RCC.constrain();

    // GPIO
    let mut gpioc = dp.GPIOC.split(&mut rcc);
    let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);

    // Delay
    let mut delay = cp.SYST.delay(&rcc.clocks);
    
    loop {
        
    }
}

之后就可以愉快地在loop中点灯了:

    loop {
        led.toggle();
        
        delay.delay_ms(100u16);
    }

2.5 烧录和运行

将烧录器连接开发板后,接入电脑,直接运行cargo run命令,probe-rs会自动识别烧录器类型(dap-link或者st-link)并烧录可执行程序。可执行程序的位置在target/thumbv7m-none-eabi/debug/led-f1。博主使用MacOS,这里的led-f1文件实际上就是.elf文件,在这里没有显示后缀而已。

❯ cargo run
   ...
   Compiling ...
   ...
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.56s
     Running `probe-rs run --chip STM32F103C8 --log-format=oneline target/thumbv7m-none-eabi/debug/led-f1`
      Erasing ✔ 100% [####################]   9.00 KiB @  10.47 KiB/s (took 1s)
  Programming ✔ 100% [####################]   9.00 KiB @   6.85 KiB/s (took 1s) 
     Finished in 2.17s

烧录后命令行程序没有自动退出是正常的,因为此时程序还在死循环内运行,退出需要手动Ctrl + C。此时程序已经下载到STM32的FLASH中,重新上电就会自动执行烧录好的程序。

通过建立一个“最小化”的工程,我们了解了Rust嵌入式工程的基本结构。一般在开发时都采用模版库初始化工程,这样可以减小无效的代码编辑量。之后通过对比app-template创建的工程,可以看到很多都是类似的。模板库中还添加了其他便于嵌入式开发的功能,可以极大程度地提高开发体验。

3 从 app-template 初始化项目

3.1 安装 cargo-generateflip-link

cargo-generate一般用来从github上克隆需要的工程模板,如果你需要使用官方模板库创建工程,那么建议先安装cargo-generate:

cargo install cargo-generate

flip-link 是一个用来解决在资源受限平台(例如微控制器)上栈溢出导致静态变量内存被覆盖的问题的工具。它的Github主页(README)详细介绍了这种情况出现的原因和解决方法。如果你理解不了,其实也不用完全理解,只需要知道因为它的存在可以让嵌入式程序的内存更安全,对程序员来说基本也感知不到其存在。但是都用Rust了,那就让可以更安全的东西更安全,总是没错的。

使用下面的命令安装flip-link:

cargo install flip-link

要使用flip-link,需要在.cargo/config.toml文件中添加编译标志:

rustflags = [
  "-C", "linker=flip-link",
]

3.2 安装 defmt 日志打印工具

defmt 是对于资源受限平台(例如微控制器)友好的日志打印框架。在Rust中将其添加为依赖需要命令:

cargo add defmt

要使用defmt,同样需要在.cargo/config.toml文件中添加编译标志:

rustflags = [
  "-C", "link-arg=-Tdefmt.x",   
]
defmt有时会和embed库发生crate冲突,提示找不到defmt.x脚本。此时可以将.cargo/config.toml文件中关于defmt的编译参数禁用(参考Rust STM32F103嵌入式开发教程之问题答疑 Q&A9)。它只是个日志打印工具,不用也无可厚非,当然,如果你没有遇到这个错误,那么博主还是推荐defmt,实际使用体验还是不错的。

报错信息:

  = note: rust-lld: error: cannot find linker script defmt.x

解决方法:

rustflags = [
  # ...
  # "-C", "link-arg=-Tdefmt.x",    # 注释这一行
  # ...
]

3.3 克隆工程模板

使用下面的命令克隆工程模板:

cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart

cortex-m-quickstart 已经寄了(太老旧了),点进他的Github仓库提示推荐使用 app-template

从另一个kunrling-rs官方推荐的仓库 app-template 初始化工程(输入工程名后回车):

cargo generate --git https://github.com/knurling-rs/app-template

3.4 工程配置

其实 app-template 的README文档写的已经很清晰了,使用这个模板创建工程主要需要操作7-8步,跟着README文件操作即可。第一步【TODO 1】当然是使用cargo-generate克隆工程模板。

【TODO 2】在.cargo/config.toml修改probe-rs烧录的芯片型号(可填写的型号使用probe-rs chip list命令查看):

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
# TODO(2) replace `$CHIP` with your chip's name (see `probe-rs chip list` output)
runner = ["probe-rs", "run", "--chip", "STM32F103C8", "--log-format=oneline"]
# If you have an nRF52, you might also want to add "--allow-erase-all" to the list

【TODO 3】在.cargo/config.toml中修改编译目标:

[build]
# TODO(3) Adjust the compilation target.
# Select the correct target for your processor:
# target = "thumbv6m-none-eabi"    # Cortex-M0 and Cortex-M0+
target = "thumbv7m-none-eabi"    # Cortex-M3
# target = "thumbv7em-none-eabi"   # Cortex-M4 and Cortex-M7 (no FPU)

【TODO 4】在Cargo.toml中添加需要的HAL层crate:

# TODO(4) enter your HAL here
# some-hal = "1.2.3"
stm32f1xx-hal = { version = "0.11.0", features = ["stm32f103", "rtic", "medium"] }

【TODO 5】在src/lib.rs中引入hal库:

// TODO(5) adjust HAL import
// use some_hal as _; // memory layout
use stm32f1xx_hal as _;

【TODO 6】在项目根目录下添加memory.x文件:

MEMORY
{
    FLASH : ORIGIN = 0x08000000, LENGTH = 64K
    RAM   : ORIGIN = 0x20000000, LENGTH = 20K
}
对于添加了memory.x的cortex-m工程,.cargo/config.toml -> [rustflags]模块中的需要声明其需要link.x脚本,不能省略。
rustflags = [
  "-C", "linker=flip-link",
  "-C", "link-arg=-Tlink.x",     # 不能省略
  "-C", "link-arg=-Tdefmt.x",
  # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x
  # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95
  "-C", "link-arg=--nmagic",
]
如果你修改了memory.x文件,并且在修改之前执行了编译,那么需要清除Rust的编译文件重新编译。使用cargo clean命令清除之前的编译文件。这个问题我在app-template下提了一个issue(Force rebuild when memory.x changes),有遇到类似问题的小伙伴可以去看看。

【TODO 7】app-template已经为我们编写好了一些例子,帮助我们快速了解这个模板的功能和一些特色,例如hellopanic等,存放在src/bin目录下。我们可以直接通过cargo rb foo或者cargo rrb bar命令运行这些例子。当然,运行之前需要链接开发板。st-link或者dap-link都可以,probe-rs会自动识别你使用的是什么烧录器(很强的功能,我太喜欢了)。

❯ cargo rb hello
   Compiling led-f1-app-template v0.1.0 (/Users/debussy/Rust-STM32/led-f1-app-template)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.11s
     Running `probe-rs run --chip STM32F103C8 --log-format=oneline target/thumbv7m-none-eabi/debug/hello`
      Erasing ✔ 100% [####################]   8.00 KiB @  10.16 KiB/s (took 1s)
  Programming ✔ 100% [####################]   8.00 KiB @   6.71 KiB/s (took 1s)     
     Finished in 1.98s
Hello, world!
Firmware exited successfully

对于其他的例子,大家也可以运行试试看,例子都很简单并且易于理解,博主在这里就不赘述了。

3.5 点灯

接下来修改一下src/bin/hello.rs,完成点灯操作:

#![no_main]
#![no_std]

use led_f1_app_template as _;   // global logger + panicking-behavior + memory layout
use stm32f1xx_hal::{pac, prelude::*}; 

#[cortex_m_rt::entry]
fn main() -> ! {
    defmt::println!("Hello, world!");

    let cp = cortex_m::Peripherals::take().unwrap();        // Core Peripherals
    let dp = pac::Peripherals::take().unwrap();     // Device Peripherals

    // Constrain and Freeze clock configration
    let mut rcc = dp.RCC.constrain();

    let mut gpioc = dp.GPIOC.split(&mut rcc);
    let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
    
    let mut delay = cp.SYST.delay(&rcc.clocks);

    defmt::println!("F1 led start toggle!");

    loop {
        led.toggle();
        
        delay.delay_ms(500u32);
    }

    // led_f1_app_template::exit()
}

运行一下,看灯是否按预期闪烁:

❯ cargo rb hello
   Compiling led-f1-app-template v0.1.0 (/Users/debussy/Rust-STM32/led-f1-app-template)
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.18s
     Running `probe-rs run --chip STM32F103C8 --log-format=oneline target/thumbv7m-none-eabi/debug/hello`
      Erasing ✔ 100% [####################]  10.00 KiB @  10.83 KiB/s (took 1s)
  Programming ✔ 100% [####################]  10.00 KiB @   6.92 KiB/s (took 1s)   
     Finished in 2.37s
Hello, world!
F1 led start toggle!

和之前的现象是一致的,说明工程的创建没有问题。

转载声明:

除特殊声明外,本站所有文章均由 debussy 原创,均采用 CC BY-NC-SA 4.0 协议,转载请注明出处:Include Everything 的博客
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇