💨

RTICメモ

2022/01/11に公開

RTICメモ

Rustの組み込み向けライブラリ RTIC をつかってみたときのメモ

RTIC概要

Real-Time Interrupt-drien Concurrency の略。
Cortex-MのNVICの機能を使ってうまいことマルチタスク的なことをするライブラリ。

https://docs.rs/cortex-m-rtic/latest/rtic/

使ってみたコード

Rust向けのCMSIS-DAP実装 rust-dap

ドキュメントを読んで分からなかったところ

大体の使い方は ドキュメント に書いてあるが、実際に使うにあたってサンプルを読んだりして分かった部分についてメモしておく

#[local]#[shared] を付けた構造体のメンバーには Send が必要

RTICのマクロ内部で #[local]#[shared] のメンバーに対して Send トレイトが実装されているか確認している模様。

#[init] 関数内部で #[local]#[shared] のコンテキストは初期化されて別の割り込みコンテキスト等に共有されるので、確かに Send が必要である。

rust-dap ではUARTのRead/Writeを個別に使いたいので split を呼び出して Reader Writer に分離しているが、rp-halReader WriterSend を実装していないようで、コンパイル時にエラーが発生する。

WARN rustc_metadata::locator no metadata found: incompatible metadata version found: '/home/kenta/repos/rust-dap/boards/xiao_rp2040/target/debug/deps/libpaste-1d007800db85bb25.so'
error[E0277]: `UnsafeCell<u32>` cannot be shared between threads safely
   --> src/main.rs:24:1
    |
24  | #[rtic::app(device = rp_pico::hal::pac, peripherals = true)]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<u32>` cannot be shared between threads safely
    |
    = help: within `rp_pico::rp2040_pac::uart0::RegisterBlock`, the trait `Sync` is not implemented for `UnsafeCell<u32>`
    = note: required because it appears within the type `vcell::VolatileCell<u32>`
    = note: required because it appears within the type `Reg<UARTDR_SPEC>`
    = note: required because it appears within the type `rp_pico::rp2040_pac::uart0::RegisterBlock`
    = note: required because of the requirements on the impl of `Send` for `&'static rp_pico::rp2040_pac::uart0::RegisterBlock`
    = note: required because it appears within the type `Writer<UART0, (rp_pico::rp2040_hal::gpio::Pin<Gpio0, rp_pico::rp2040_hal::gpio::Function<rp_pico::rp2040_hal::gpio::Uart>>, rp_pico::rp2040_hal::gpio::Pin<Gpio1, rp_pico::rp2040_hal::gpio::Function<rp_pico::rp2040_hal::gpio::Uart>>)>`
note: required because it appears within the type `app::UartWriter`
   --> src/main.rs:77:16
    |
77  |     pub struct UartWriter(hal::uart::Writer<pac::UART0, UartPins>);
    |                ^^^^^^^^^^
    = note: required because it appears within the type `Option<app::UartWriter>`
note: required by a bound in `assert_send`
   --> /home/kenta/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rtic-1.0.0/src/export.rs:107:8
    |
107 |     T: Send,
    |        ^^^^ required by this bound in `assert_send`
    = note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `rust-dap-xiao-rp2040` due to previous error

rp-halReader Writer は内部でレジスタブロックへの参照をもっているが、これが Send を実装していないためエラーとなっている。

内容としてはレジスタブロックのアドレスはシステムで固定なので、実際には構築したコンテキスト以外で参照しても問題ない。

よって外側で Send を実装して Reader Writer#[shared] で使えるようにしている。 (Reader Writer Sync はどれも対象のコードがあるクレートで宣言された物ではないので、Orphan Rule対策でNew Type Patternで新しい型を作っている。)

type Uart = hal::uart::UartPeripheral<hal::uart::Enabled, pac::UART0, UartPins>;
pub struct UartReader(hal::uart::Reader<pac::UART0, UartPins>);
pub struct UartWriter(hal::uart::Writer<pac::UART0, UartPins>);
unsafe impl Sync for UartReader {}
unsafe impl Sync for UartWriter {}
unsafe impl Send for UartReader {}
unsafe impl Send for UartWriter {}

#[shared]
struct Shared {
    uart_reader: Option<UartReader>,
    uart_writer: Option<UartWriter>,
    usb_serial: SerialPort<'static, UsbBus>,
    uart_rx_consumer: heapless::spsc::Consumer<'static, u8, UART_RX_QUEUE_SIZE>,
    uart_tx_producer: heapless::spsc::Producer<'static, u8, UART_TX_QUEUE_SIZE>,
    uart_tx_consumer: heapless::spsc::Consumer<'static, u8, UART_TX_QUEUE_SIZE>,
}

複数の共有変数の同時ロック

#[shared] のメンバーはタスク関数の属性の shared で使用を宣言して、内部で c.shared.(メンバー名).lock(|m| ...) を呼び出してロックを取得して使用する。

複数の共有メンバーを同時に使いたい場合は、 (&mut c.shared.member1, &mut c.shared.member2, ...).lock(|m1, m2, ...| ) として同時にロックを取得できる。

このときタプルの中の共有メンバーに対して &mut をつけ忘れるとムーブされてしまうので注意が必要。

#[task(shared = [usb_serial, uart_tx_producer])]
fn task() {
    (&mut c.shared.usb_serial, &mut c.shared.uart_tx_producer).lock(|usb_serial, uart_tx_producer| { 
        //usb_serial, uart_tx_producer共有メンバーを使う処理
    });
}

staticなものの初期化

各タスクでは local = [...] の中で #[local] 構造体に無いそのタスク固有のstaticな変数を定義できる。

これは #[init] でも使用可能なので、初期化処理が必要なものや、 #[shared] で共有するもののうち、staticな領域が必要なものは、 #[init]local を使って宣言する。

#[shared]
struct Shared {
    uart_tx_producer: heapless::spsc::Producer<'static, u8, UART_TX_QUEUE_SIZE>,
    uart_tx_consumer: heapless::spsc::Consumer<'static, u8, UART_TX_QUEUE_SIZE>,
}
#[init(local = [
    uart_rx_queue: heapless::spsc::Queue<u8, UART_RX_QUEUE_SIZE> = heapless::spsc::Queue::new(),
    uart_tx_queue: heapless::spsc::Queue<u8, UART_TX_QUEUE_SIZE> = heapless::spsc::Queue::new(),
    USB_ALLOCATOR: Option<UsbBusAllocator<UsbBus>> = None,
    ])]
fn init(c: init::Context) -> (Shared, Local, init::Monotonics) {
    // ...
    let (uart_rx_producer, uart_rx_consumer) = c.local.uart_rx_queue.split();
    let (uart_tx_producer, uart_tx_consumer) = c.local.uart_tx_queue.split();
    // ...
    (Shared {uart_tx_producer, uart_tx_consumer}, Local { ... }, init::Monotonics())
}

Discussion