Rust、Eclipse、およびSTM32

互いに友だちになるためには、ヘッダーに示されているテクノロジーが必要です。





Rustで書かれたプログラムを、ARMツールチェーンを使用してリンクできるライブラリにコンパイルするという考え方です。

その結果、RustとCの混合コードを非常に快適にデバッグできます。



1. Cプロジェクトの生成



これにはSTM32CubeMXユーティリティを使用します。 デモプロジェクトには次のものが必要です。





画像



タイミング設定を確認してください。 ここで、必要に応じて、外部クオーツからのクロックとその周波数を示すことができます。



画像



プロジェクトを生成します。 「HwApi」と呼びましょう。なぜなら このコード層は鉄よりも抽象化されており、Rustでコードを記述するときに使用します。 IDEとして、SW4STM32を選択します。



画像



Workbenchがインストールされている場合、生成されたプロジェクトを開き、正常にコンパイルされることを確認できます。



画像



2. Eclipseの最新バージョン用のプロジェクトを作成します



System WorkbenchはEclipseに基づいていますが、Eclipse(Neon)の新しいメジャーバージョンで新しいプロジェクトを作成する必要があります。 RustDTは、そのバージョンのEclipseと互換性がありません。



また、GNU ARM Eclipseプラグインとともにインストールされるプロジェクトテンプレートも必要です。



画像



rustコンパイラーによって生成されたlibを正常にリンクするには、GNU ARM Embedded Toolchainのプリインストールされた最新バージョンが必要です。



画像



プロジェクトをSystem WorkbenchからEclipse CDTに移動するプロセスを開始します。 インターネットでは、このプロセスを自動化するスクリプトを見つけることができますが、手動で行います。 他のプロジェクトでHwApiLibを再利用し、Rustで記述されたコードの一部のみを変更します。

次のフォルダー/ファイルを新しいプロジェクトにコピーします。





Workbenchがインストールされている場合は、2つのプロジェクト設定ウィンドウ(古いEclipseと新しいEclipseから)を展開して、あるウィンドウから別のウィンドウに値をコピーすると便利です。 ウィンドウは少し異なるため、コピーするときは、括弧内に示されているフラグに注目します。



Workbenchがインストールされていない場合、以下に添付されているスクリーンショットから設定をコピーするだけです。



定義済みシンボルのコピー:



画像



* .hファイルを含むフォルダーパス:



画像



[最適化]タブで、サイズの最適化(-Os)を有効にできます。

次に、すべてのコンパイラ警告が必要であることを示します。



画像



リンカスクリプトへのパスを示し、リンク結果からコードで使用されていないセクションを削除するチェックボックスをマークします。



画像



次のタブでは、「newlib-nanoを使用」チェックボックスを-specs=nosys.specs



-specs=nosys.specs



を手動で指定することが重要です。



画像



コンパイル用のファイルがあるフォルダーへのパスを指定します。



画像



OKをクリックします。 次に、スタートアップファイルの拡張子を.Sヘッダーに変更して、ファイルがコンパイラーによって正常に取得されるようにします。 プロジェクトがコンパイルされていることを確認します。



画像



次に、デバッガーを構成する必要があります(実行-デバッグ構成-GDB OpenOCDデバッグ)。 プログラムを実行するアイロンの説明を含むOpenOCDのファイルを作成します(私の場合、ファイルはSTM32F103C8x_SWD.cfgと呼ばれます)。



 source [find interface/stlink-v2.cfg] set WORKAREASIZE 0x5000 transport select "hla_swd" set CHIPNAME STM32F103C8Tx source [find target/stm32f1x.cfg] # use hardware reset, connect under reset reset_config none
      
      





別のマイクロコントローラーまたは別の方法で接続する場合、WorkbenchでOpenOCDの正しいファイルを生成できます(デバッグオプション-Ac6を使用)。



構成オプションで、-fフラグと前のステップで作成されたファイルへのパスを指定します。



画像



デバッグをクリックします。 デバッガーがコードをマイクロコントローラーに正常にアップロードし、デバッグが開始されたことを確認します。



画像



Rustプロジェクトを作成します。 なぜなら 安定版ではサポートされていないコンパイラー命令が必要になります。cmdで次のコマンドを実行して、コンパイラーの夜間バージョンを切り替える必要があります。



 rustup update rustup default nightly
      
      





次に、コンパイラの現在のバージョンを取得する必要があります。



 rustc -v --version
      
      





画像



次に、rustソースコードのクローンを作成し、このコンパイラのビルドに使用されたコミット(commit-hashで指定)に切り替えます。



 git clone git@github.com:rust-lang/rust.git cd rust git checkout cab4bff3de1a61472f3c2e7752ef54b87344d1c9
      
      





次のステップは、ARMに必要なライブラリをコンパイルすることです。



 mkdir libs-arm rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g src/libcore/lib.rs --out-dir libs-arm --emit obj,link rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g src/liballoc/lib.rs --out-dir libs-arm -L libs-arm --emit obj,link rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g src/libstd_unicode/lib.rs --out-dir libs-arm -L libs-arm --emit obj,link rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g src/libcollections/lib.rs --out-dir libs-arm -L libs-arm --emit obj,link
      
      





将来、コンパイラが更新されるたびに(rustup更新)、ソースの現在のバージョンに切り替えて、ARM用のライブラリを再コンパイルする必要があります。そうしないと、rustでコードをデバッグする機能が失われます。



最後に、EclipseでRustプロジェクトの作成を開始できます。



画像



画像



Eclipseは、rust-codeを操作するためのコンパイラ、ソース、およびユーティリティへのパスを指定するように求めます。



画像



通常、これらのコンポーネントはC:\ Users \%username%\。Cargoにあります。 Rust src-以前にダウンロードしたソース内のsrcフォルダーへのパス。



次に、メインコード:



lib.rs



 #![feature(macro_reexport)] #![feature(unboxed_closures)] #![feature(lang_items, asm)] #![no_std] #![feature(alloc, collections)] #![allow(dead_code)] #![allow(non_snake_case)] extern crate alloc; pub mod runtime_support; pub mod api; #[macro_reexport(vec, format)] pub extern crate collections; use api::*; #[no_mangle] pub extern fn demo_main_loop() -> ! { let usart2 = Stm32Usart::new(Stm32UsartDevice::Usart2); loop { let u2_byte = usart2.try_read_byte(); match u2_byte { Some(v) => { let c = v as char; match c { 'r' => { toggle_led(Stm32Led::Red); } 'g' => { toggle_led(Stm32Led::Green); } 'b' => { toggle_led(Stm32Led::Blue); } _ => { usart2.print("cmd not found"); } } } _ => {} } delay(1); } }
      
      





api.rs-RustとCコードを相互に統合するためのレイヤー



 use collections::Vec; extern { fn stm32_delay(millis: u32); fn usart2_send_string(str: *const u8, len: u16); fn usart2_send_byte(byte: u8); fn usart2_try_get_byte() -> i16; fn stm32_toggle_led(led: u8); fn stm32_enable_led(led: u8); fn stm32_disable_led(led: u8); } pub fn delay(millis: u32) { unsafe { stm32_delay(millis); } } #[derive(Copy, Clone)] pub enum Stm32UsartDevice { Usart2 } #[derive(Copy, Clone)] pub struct Stm32Usart { device: Stm32UsartDevice } impl Stm32Usart { pub fn new(device: Stm32UsartDevice) -> Stm32Usart { Stm32Usart { device: device } } pub fn print(&self, str: &str) { let bytes = str.bytes().collect::<Vec<u8>>(); self.print_bytes(bytes.as_slice()); } pub fn print_bytes(&self, bytes: &[u8]) { unsafe { match self.device { Stm32UsartDevice::Usart2 => usart2_send_string(bytes.as_ptr(), bytes.len() as u16) } } } pub fn println(&self, str: &str) { self.print(str); self.print("\r\n"); } pub fn send_byte(&self, byte: u8) { unsafe { match self.device { Stm32UsartDevice::Usart2 => usart2_send_byte(byte) } } } pub fn try_read_byte(&self) -> Option<u8> { unsafe { let r = usart2_try_get_byte(); if r == -1 { return None; } return Some(r as u8); } } } pub enum Stm32Led { Red, Green, Blue, Orange } impl Stm32Led { fn to_api(&self) -> u8 { match *self { Stm32Led::Green => 2, Stm32Led::Blue => 3, Stm32Led::Red => 1, Stm32Led::Orange => 0 } } } pub fn toggle_led(led: Stm32Led) { unsafe { stm32_toggle_led(led.to_api()); } } pub fn enable_led(led: Stm32Led) { unsafe { stm32_enable_led(led.to_api()); } } pub fn disable_led(led: Stm32Led) { unsafe { stm32_disable_led(led.to_api()); } }
      
      





runtime_support.rs-低レベルのRust関数をサポートするため



 extern crate core; /// Call the debugger and halts execution. #[no_mangle] pub extern "C" fn abort() -> ! { loop {} } #[cfg(not(test))] #[inline(always)] /// NOP instruction pub fn nop() { unsafe { asm!("nop" :::: "volatile"); } } #[cfg(test)] /// NOP instruction (mock) pub fn nop() {} #[cfg(not(test))] #[inline(always)] /// WFI instruction pub fn wfi() { unsafe { asm!("wfi" :::: "volatile"); } } #[cfg(test)] /// WFI instruction (mock) pub fn wfi() {} #[lang = "panic_fmt"] fn panic_fmt(_: core::fmt::Arguments, _: &(&'static str, usize)) -> ! { loop {} } #[lang = "eh_personality"] extern "C" fn eh_personality() {} // Memory allocator support, via C's stdlib #[repr(u8)] #[allow(non_camel_case_types)] pub enum c_void { __variant1, __variant2, } extern "C" { pub fn malloc(size: u32) -> *mut c_void; pub fn realloc(p: *mut c_void, size: u32) -> *mut c_void; pub fn free(p: *mut c_void); } #[no_mangle] #[allow(unused_variables)] pub unsafe extern "C" fn __rust_allocate(size: usize, align: usize) -> *mut u8 { malloc(size as u32) as *mut u8 } #[no_mangle] #[allow(unused_variables)] pub unsafe extern "C" fn __rust_deallocate(ptr: *mut u8, old_size: usize, align: usize) { free(ptr as *mut c_void); } #[no_mangle] #[allow(unused_variables)] pub unsafe extern "C" fn __rust_reallocate(ptr: *mut u8, old_size: usize, size: usize, align: usize) -> *mut u8 { realloc(ptr as *mut c_void, size as u32) as *mut u8 }
      
      





また、プロジェクトのルートで、ターゲットプラットフォームの構成ファイルを作成する必要があります

thumbv7m-none-eabi.json grosswsは、現在このファイルがコンパイラに含まれており、作成できないことを示唆しています。



 { "arch": "arm", "cpu": "cortex-m3", "data-layout": "em:ep:32:32-i1:8:32-i8:8:32-i16:16:32-i64:64-v128:64:128-a:0:32-n32-S64", "disable-redzone": true, "executables": true, "llvm-target": "thumbv7m-none-eabi", "morestack": false, "os": "none", "relocation-model": "static", "target-endian": "little", "target-pointer-width": "32" }
      
      





ARMで動作するようにコンパイルされたコンポーネントを含むlibs-armフォルダーを、Rust標準ライブラリからプロジェクトのRustフォルダーにコピーします。



Debugターゲットを変更して、必要なパラメーターでコンパイルを開始するようにします



 rustc -C opt-level=2 -Z no-landing-pads --target thumbv7m-none-eabi -g --crate-type lib -L libs-arm src/lib.rs --emit obj,link
      
      





画像



画像



Rustプロジェクトをコンパイルします。 その結果、プロジェクトフォルダーにlib.oファイルが表示されます。



Cプロジェクトで、api.h / api.cファイルを作成し、api.rsで使用される関数を宣言して実装します。



api.h



 #ifndef SERIAL_DEMO_API_H_ #define SERIAL_DEMO_API_H_ #include "stm32f1xx_hal.h" void stm32_delay(uint32_t milli); void usart2_send_string(uint8_t* str, uint16_t len); void usart2_send_byte(uint8_t byte); int16_t usart2_try_get_byte(void); void stm32_toggle_led(uint8_t led); void stm32_enable_led(uint8_t led); void stm32_disable_led(uint8_t led); #endif
      
      





api.c



 #include "api.h" #include "stm32f1xx_hal.h" #include "stm32f1xx_hal_uart.h" #include "main.h" void stm32_delay(uint32_t milli) { HAL_Delay(milli); } extern UART_HandleTypeDef huart2; void usart2_send_string(uint8_t* str, uint16_t len) { HAL_UART_Transmit(&huart2, str, len, 1000); } void usart2_send_byte(uint8_t byte) { while (!(USART2->SR & UART_FLAG_TXE)); USART2->DR = (byte & 0xFF); } int16_t usart2_try_get_byte(void) { volatile unsigned int vsr; vsr = USART2->SR; if (vsr & UART_FLAG_RXNE) { USART2->SR &= ~(UART_FLAG_RXNE); return (USART2->DR & 0x1FF); } return -1; } uint16_t stm32_led_to_pin(uint8_t led); void stm32_toggle_led(uint8_t led) { HAL_GPIO_TogglePin(LED_R_GPIO_Port, stm32_led_to_pin(led)); } void stm32_enable_led(uint8_t led) { HAL_GPIO_WritePin(LED_R_GPIO_Port, stm32_led_to_pin(led), GPIO_PIN_SET); } void stm32_disable_led(uint8_t led) { HAL_GPIO_WritePin(LED_R_GPIO_Port, stm32_led_to_pin(led), GPIO_PIN_RESET); } uint16_t stm32_led_to_pin(uint8_t led) { switch (led) { case 1: return LED_R_Pin; case 2: return LED_G_Pin; case 3: return LED_B_Pin; default: return LED_B_Pin; } }
      
      





main関数内にdemo_main_loop()の呼び出しを追加します。



main.c



 ... /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ demo_main_loop(); } /* USER CODE END 3 */ ...
      
      





すべてをリンクするために残ります。 これを行うには、Cでプロジェクトプロパティを開き、不足しているobjファイルを取得する場所をリンカーに示します。



画像



コンパイルします。 バイナリはかなりの重量を増しましたが、それでもSTM32F103C8に収まります。



画像



デバッグを開始すると、EclipseがCコードからRustにシームレスに切り替わることがわかります。



画像



記事の最後で、次の投稿の著者に感謝したいと思います。彼らがいなければ、このプロセスをマスターできなかったでしょう。



www.hashmismatch.net/pragmatic-bare-metal-rust

spin.atomicobject.com/2015/02/20/rust-language-c-embedded

github.com/japaric/rust-cross



私は、これがマイクロコントローラのプログラミングにRustを使用する開発者コミュニティの出現における追加のステップとして役立つことを期待して記事を書きました。 非常に便利で現代的な言語ですが、かなり高いエントリーしきい値を持っています。



All Articles