LoginSignup
3
3

More than 3 years have passed since last update.

Rustで組み込み Lチカ

Last updated at Posted at 2021-04-02

Rustで組み込み Lチカ

RustでRaspberry Pi4を対象に組み込みの開発環境を整えていきます。

環境構築

  • cargo-binutilsのインストール
  • toolchainの追加

cargo-binutilsのインストール

rust-lld や、rust-objcopy, rust-objdumpといった組み込みに必須のコマンドが含まれています。インストールするには

$ cargo install cargo-binutils
$ rustup component add llvm-tools-preview

を実行したらいいです。ここに詳細載ってます。

toolchainの追加

今回Raspberry Pi4で使うtoolchainはaarch64-unknown-noneです。target-tripleについてはここを参照ください。toolchainの追加は

rustup target add <target-triple>

で行えます。今回は rustup target add aarch64-unknown-none です。

環境構築は以上です。stable を使ってる方はnightlyへ変更を。。。

Lチカ

環境も整ったところで、早速Lチカコード書いていきます。

Makefileの作成

必須ではないのですが、コマンドが長く面倒なので、作成をおすすめします。cargo new blink_rustを実行した後、ディレクトリ移動して以下のMakefileを作成してください。

blink/Makefile
##project info#############################################################
PROJECT=blink_rust
TARGET=aarch64-unknown-none
ver ?= debug
QEMU_DIST=stdio

##files####################################################################
ELF=target/$(TARGET)/$(ver)/$(PROJECT)
IMG=kernel8.img
DUMP=$(PROJECT).dump
SRCS=$(wildcard src/*.rs)

###########################################################################
all: build $(DUMP) 
build : $(IMG)
dump: $(DUMP)
        less $<
check: $(SRC)
        cargo check

###########################################################################
$(ELF): $(SRCS)
        cargo rustc

$(IMG): $(ELF)
        rust-objcopy -O binary $< $@

clean:
        $(RM) src/*~ *~

distclean: clean
        cargo clean
        $(RM) *.lock $(DUMP) $(DOC_LN) $(IMG)


$(DUMP): $(ELF)
        @date > $(PROJECT).dump
        rust-objdump -d $< >> $(PROJECT).dump

カーネルロードのためのアセンブリコードとlinker script

Makefile作成を終えたらlink用のファイルとブート用のアセンブリを作成します。ここを参考にしています。アセンブリについては、Rustのコード内で、bssセクションの初期化を行っているため、それに当たる部分を削除しています。

blink/src/link.ld
/* cite from https://isometimes.github.io/rpi4-osdev/part1-bootstrapping */
SECTIONS
{
    . = 0x80000;     /* Kernel load address for AArch64 */
    .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
    .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
    PROVIDE(_data = .);
    .data : { *(.data .data.* .gnu.linkonce.d*) }
    .bss (NOLOAD) : {
        . = ALIGN(16);
        __bss_start = .;
        *(.bss .bss.*)
        *(COMMON)
        __bss_end = .;
    }
    _end = .;

   /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}
__bss_size = (__bss_end - __bss_start)>>3;
blink/src/boot.S
    .section ".text.boot"
    .global _start
_start:
    mrs x1, mpidr_el1
    and x1, x1, #3
    cbz x1, 2f
1:
    wfe
    b 1b
2:  // setting stack pointer
    ldr x1, =_start
    mov sp, x1
    // load _rust_start 
    bl _rust_start
    b 1b

Rustコード

ここまで作成したらRustのコードを作成していきます。

blink/src/main.rs
#![no_std]
#![no_main]
#![feature(asm, global_asm)]

mod boot;
mod gpio;
use gpio::*;

const MMIO_BASE: usize = 0xFE00_0000;

/// # This is the function loaded by _rust_start in boot.rs
/// see the code in boot.rs
fn main() -> ! {
    // set gpio pin
    let mut led = gpio::GpioPin::new(16);
    led.set_function(GpioFunction::OUTPUT);

    let mut dur;
    loop {
        dur = 30000;
        while dur > 5000 {
            led.set_high();
            busy_wait(dur);
            led.set_low();
            busy_wait(dur);
            dur -= 500;
        }
        led.set_low();
    }
}

/// busy wait function
fn busy_wait(time: usize) {
    for _ in 0..time {
        unsafe {
            asm!("");
        }
    }
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}
blink/src/gpio.rs
use core::convert::{From, Into};
use core::ptr::{read_volatile, write_volatile};
pub const GPBASE: usize = crate::MMIO_BASE + 0x20_0000;

const GPFSEL: usize = GPBASE + 0x0;
const GPSET: usize = GPBASE + 0x1c;
const GPCLR: usize = GPBASE + 0x28;

pub enum PullMode {
    PullNone,
    PullUp,
    PullDown,
}
impl From<PullMode> for u32 {
    fn from(val: PullMode) -> u32 {
        match val {
            PullMode::PullDown => 0b10,
            PullMode::PullNone => 0b0,
            PullMode::PullUp => 1,
        }
    }
}
impl core::clone::Clone for PullMode {
    fn clone(&self) -> Self {
        *self
    }
}
impl core::marker::Copy for PullMode {}

pub enum GpioFunction {
    INPUT,
    OUTPUT,
    ALTF0,
    ALTF1,
    ALTF2,
    ALTF3,
    ALTF4,
    ALTF5,
}
impl From<GpioFunction> for u32 {
    fn from(val: GpioFunction) -> u32 {
        match val {
            GpioFunction::INPUT => 0,
            GpioFunction::OUTPUT => 1,
            GpioFunction::ALTF0 => 0b100,
            GpioFunction::ALTF1 => 0b101,
            GpioFunction::ALTF2 => 0b110,
            GpioFunction::ALTF3 => 0b111,
            GpioFunction::ALTF4 => 0b011,
            GpioFunction::ALTF5 => 0b010,
        }
    }
}

impl core::clone::Clone for GpioFunction {
    fn clone(&self) -> Self {
        *self
    }
}

impl core::marker::Copy for GpioFunction {}

/// struct for gpio pin
pub struct GpioPin {
    pin: u32,
}

impl GpioPin {
    /// make new instance witout init
    pub fn new(pin: u32) -> Self {
        Self { pin }
    }

    pub fn set_function(&self, func: GpioFunction) {
        gpio_ctrl(self.pin, func.into(), GPFSEL, 3);
    }

    pub fn set_high(&mut self) {
        gpio_ctrl(self.pin, 1, GPSET, 1);
    }

    pub fn set_low(&mut self) {
        gpio_ctrl(self.pin, 1, GPCLR, 1);
    }
}

fn gpio_ctrl(pin_num: u32, value: u32, base: usize, width: usize) {
    let frame = 32 / width;
    let reg = (base + (pin_num as usize / frame) * 4) as *mut u32;
    let shift = ((pin_num as usize % frame) * width) as u32;
    let val = value << shift;
    let mask = ((1 << width as u32) - 1) << shift;

    unsafe {
        let tmp = read_volatile(reg); // read the previous value
        write_volatile(reg, (tmp & !mask) | val);
    }
}
blink/src/boot.rs
use crate::main;
global_asm!(include_str!("boot.S"));

#[no_mangle]
fn _rust_start() {
    init_bss();
    main();
}
extern "C" {
    static __bss_start: core::cell::UnsafeCell<u64>;
    static __bss_end: core::cell::UnsafeCell<u64>;
}

fn init_bss() {
    let mut ptr = unsafe { __bss_start.get() };
    let end = unsafe { __bss_end.get() };
    while ptr <= end {
        unsafe {
            core::ptr::write_volatile(ptr, 0);
            ptr = ptr.offset(1);
        }
    }
}

以上です!

最後に、.cargoというディレクトリを作って、

.cargo/config.toml
[build]
target="aarch64-unknown-none"
rustflags = ["-C","link-arg=-Tsrc/link.ld"]

と入れておいてください。環境変数RUST_FLAGSでエクスポートしてもいいはずです。あと、Cargo.tomlに

[profile.dev]
panic = "abort"

を付け加えてください。

ここまで終わったらmake buildを実行すると、imgファイルが作成されるので、SDカードにコピーしてください。ラズパイの電源をいれれば、Lチカするはず。。。

ちなみに、かんたんなので回路は紹介していませんが、GPIO16につないでます。

objdump 実行するとわかると思うのですが、Cに比べてめっちゃ命令数が多い。。。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3