ubuntu安装 rust nightly_Rust 嵌入式开发环境搭建指南 (一):让世界闪烁吧

7fc79ec94dd3f74ae1622bbe0a44f930.png

因为这是本专栏的第一篇文章,所以我打算先在这里介绍下专栏的写作目标。

Rust 是一种系统编程语言。 它有着惊人的运行速度,能够防止段错误,并保证线程安全。

Rust 官方一直标榜着自己是系统编程语言,然而最根本的系统编程就是嵌入式系统开发。如果不能在嵌入式系统里大施拳脚,那么 Rust 就没有底气能与 C 语言叫板。经过了 3 年迭代,Rust 在嵌入式开发领域已经日渐成型,并且官方也成立了嵌入式工作组特别关注 Rust 嵌入式库与工具链的开发,同时也在不断完善The embedded rust book。这里推荐大家关注工作组的 newsletter,里面有很多工作组最新工作进展。

而本专栏将会更面向于嵌入式开发的入门教程和实践,也就是说,本专栏的文章并不假定读者拥有任何嵌入式开发的知识或经验,但是要求读者有一定 Rust 语言基础,比如说熟悉借用所有权系统,懂得使用 unsafe 手动操作内存结构等等。

专栏文章会分为几大类:

  • 单片机架构的基础知识
  • Rust 嵌入式开发的技巧
  • 各种可以跟着动手的实践项目

希望通过本专栏可以吸引 Rust 小伙伴加入嵌入式领域,<del>同时拐骗一波正在使用 C 语言开发嵌入式的水深火热的程序员。</del>

准备

为了能够自己动手实践嵌入式开发,我们需要先准备好一些材料:

  • STM32F103 最小系统开发板 (约 10 元)
  • STLINK V2 仿真器 (约 20 元)
  • 母对母杜邦线
  • USB 转 TTL 串口模块 (约 5 元)

STM32F103 是现在应用非常广泛,性能强大而且成本低廉的一款单片机,拥有着高达 72Mhz 的主频率,完全吊打 Arduino 等开发平台。

c8309bd9c0144cd6a4d44c3d417999b9.png

STM32F103 最小系统核心版

仿真器是连接 pc 与单片机的重要模块,主要用于程序烧写与调试。

2bd9e5964ea63bef4e1380e177b5b367.png

STLINK V2

串口模块用于 pc 接收单片机的串口信息用以调试,由于现代计算机普遍已经取消了串口接口,所以使用 USB 串口就是最经济可靠的选择。

d4b6053e69f4fcf3ed350835f151c044.png

USB2TTL

Rust 工具链

  1. 文章下面将会使用 nightly-msvc channel 的 Rust 编译器工具链(因为作者使用 Windows 平台开发),读者也可以使用 gnu 或者 linux 平台,步骤上如果有所出入相信使用 linux 的老手是可以自己解决的。
> rustup default nightly-msvc
info: using existing install for 'nightly-x86_64-pc-windows-msvc'
info: default toolchain set to 'nightly-x86_64-pc-windows-msvc'

  nightly-x86_64-pc-windows-msvc unchanged - rustc 1.32.0-nightly (36a50c29f 2018-11-09)

2. 除了默认的标准库外,我们还需要提前编译好的 core 核心库。在我们这里添加几个常用的编译目标指令集,rustup 就会自动把核心库下载下来。

> rustup target add thumbv6m-none-eabi thumbv7m-none-eabi thumbv7em-none-eabi thumbv7em-none-eabihf
info: downloading component 'rust-std' for 'thumbv6m-none-eabi'
info: downloading component 'rust-std' for 'thumbv7m-none-eabi'
info: downloading component 'rust-std' for 'thumbv7em-none-eabi'
info: downloading component 'rust-std' for 'thumbv7em-none-eabihf'

3. 另外我们还需要一些传统而好用的二进制工具 (binary tool) 和调试器。在 ARM官网页面 下载适合平台的最新版安装即可。这一步安装的工具包括 arm-none-eabi-nm, arm-none-eabi-gdb, arm-none-eabi-objcopy 还有 arm-none-eabi-size 等等。

4. 最后我们还差 openocd,它负责保持与与仿真器的通讯连接,我们需要使用它来进行烧写和调试指令操作。openocd 的安装途径有很多,建议向购买仿真器的商家索要,或者可以从这里下载(可能需要科学上网)。

注: 上述 3,4 步的工具需要加入 Path 环境变量。

Blinky

Blinky 是嵌入式世界的 hello world —— 让一盏 LED 闪烁。这篇文章的最终目标就是把最小系统版上唯一一颗 LED 灯闪烁起来。

我们先创建一个新的项目。

> cargo new blinky
     Created binary (application) `blinky` package

打开 Cargo.toml 添加几个依赖项。

[dependencies]
cortex-m = "0.5.8"        # cortex-m 核心指令集
cortex-m-rt = "0.6.5"     # 最小运行时,负责启动内存初始化
panic-halt = "0.2.0"      # 定义发生 panic 时采取立即停机的行为

同一架构的单片机的内存容量往往有很大差异,不同厂家的内存排布也不一定相同,所以这里我们要用 memory.x 文件里定义开发板的内存结构。在项目目录中新建文件 memory.x 并写入:

MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 128K
  RAM : ORIGIN = 0x20000000, LENGTH = 20K
}

这里定义了我们这个 MCU 拥有 128k ROM 和 20k RAM,内存起点分别在 0x08000000 和 0x20000000。

memory.x 事实上是一段链接器脚本 (Linker Script),链接器脚本用来在内存中规划如何排布代码和静态变量。很明显仅靠这小段脚本还不足以声明好运行所需的所有段 (SECTION)。幸运的是,cortex-m-rt 运行库已经为我们写好了通用的链接脚本,我们仅仅需要在编译时将名为 memory.x 的内存定义脚本放在编译目录,memory.x 就会被自动 include 到模板中。 所以这里需要一段编译时自动拷贝 memory.x 的 build script。在项目目录中新建文件 build.rs 并写入:

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());

    // Only re-run the build script when memory.x is changed,
    // instead of when any part of the source code changes.
    println!("cargo:rerun-if-changed=memory.x");
}

接着打开 src/main,写入:

#![no_std]
#![no_main]

extern crate panic_halt;

use cortex_m::asm;
use cortex_m_rt::entry;

#[entry]
fn main() -> ! {
    asm::nop();

    loop { }
}

虽然这段代码看起来毫无作用,但是对于编译来说已经足够了。

可以注意一下这里的 main 函数并不是 Rust 语言内嵌的主函数,事实上,这个主函数仅仅是用户代码的入口,真正的主函数定义在 cortex_m_rt 库中,在启动后负责静态变量和中断向量表的内存初始化,接着才将执行权交回给这里的 main 函数。

执行编译。

> cargo build --target thumbv7m-none-eabi
    Compiling blinky v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.62s

至此 Blinky 已经成功编译好了,执行文件应该会出现在 /target/thumbv7m-none-eabi/debug/blinky 。接下来我们要把这个程序烧写到芯片的 ROM 上。首先使用杜邦线连接上仿真器与开发板,对应着接口上的名字,应该很容易将四条连接线接好,四个接口分别是 SWDIO, SWCLK, 3.3VGND

接着启动 openocd

> openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg
64-bits Open On-Chip Debugger 0.10.0-dev-00289-g5eb5e34 (2016-09-03-09:40)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html

...

Polling target stm32f1x.cpu failed, trying to reexamine
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints

这一步以出现 xxxxx.cpu: hardware has x breakpoints, x watchpoints 提示为连接成功。 如果出现了其他错误,先检查是否已经安装仿真器的驱动,4条连接线有没有松动,或者更换一个 USB 口试试。

保留 openocd 终端,再打开一个新的终端启动 GDB (GNU Debugger) ,使用 GDB 进行执行程序烧写:

> arm-none-eabi-gdb
GNU gdb (GNU Tools for ARM Embedded Processors 6-2017-q1-update) 7.12.1.20170215-git

...

For help, type "help".
(gdb)

加载目标文件

(gdb) file ./target/thumbv7m-none-eabi/debug/blinky
Reading symbols from ./target/thumbv7m-none-eabi/debug/blinky...done.

连接上 openocd。(openocd 的默认端口为 3333)

(gdb) target remote :3333
Remote debugging using :3333
0x00000000 in ?? ()

重置 MCU,因为在运行状态无法进行烧写。

(gdb) monitor reset halt
stm32f1x.cpu: target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x0800016c msp: 0x20000260

开始写入

(gdb) load
Start address 0x0, load size 0
Transfer rate: 0 bits in <1 sec.

写入后 MCU 默认会暂停在初始状态,这里要手动运行。

(gdb) continue
Continuing.

好了到目前为止,如果我们的开发板毫无反应,那就对了,我们现在要给它加上最重要的 Blinky 逻辑。

打开 Cargo.toml 再加上两个依赖

stm32f103xx-hal = { git = "https://github.com/japaric/stm32f103xx-hal.git" }    # MCU 外围部件操作的统一接口
nb = "0.1"    # stm32f103xx-hal 的异步阻塞模块,用来实现时钟等待同步

修改 src/main.rs

#![no_std]
#![no_main]

extern crate panic_halt;
extern crate stm32f103xx_hal as hal;
#[macro_use]
extern crate nb;

use cortex_m_rt::entry;

use hal::prelude::*;
use hal::stm32f103xx;
use hal::timer::Timer;

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

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

  // 设置时钟总线
  let clocks = rcc.cfgr.freeze(&mut flash.acr);

  // 设置通用引脚 (GPIO)
  let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);

  // LED 对应的 PC13 引脚
  let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);

  // 淘宝上有些版本的核心板的 LED 会接在 PB12 引脚上,这样的话用下面两行替换
  // let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
  // let mut led = gpiob.pb12.into_push_pull_output(&mut gpiob.crh);
  
  let mut timer = Timer::syst(cp.SYST, 1.hz(), clocks);
  loop {
      block!(timer.wait()).unwrap();
      // 点亮 LED
      led.set_high();
      block!(timer.wait()).unwrap();
      // 关闭 LED
      led.set_low();
  }
}

重新编译 Rust。

> cargo build --target thumbv7m-none-eabi
    Compiling blinky v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 0.1s 

回到 GDB 终端,此时如果还在运行上一段代码,那按下 Ctrl + C 就可以中断执行。

Continuing.
Program received signal SIGINT, Interrupt.
0x08000240 in ?? ()
(gdb) 

直接执行 load 指令,GDB 会自动识别到可执行文件的变更并进行覆写。

(gdb) load
Start address 0x0, load size 0
Transfer rate: 0 bits in <1 sec.
(gdb) continue
Continuing. 

至此,我们的蓝色 LED 就应该会开始以一秒间隔开始闪烁了!

aa9bc11c79a41a3e12f05f314bebdec6.gif

如果你有幸看到这,那就帮忙点个赞,让更多人看到吧!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值