If you’re doing bare-metal embedded programming you’ve probably used IAR’s embedded workbench at some point. IAR is a goto solution for many European C/C++ shops and they do a decent job of supporting a huge load of architectures. However, they’re using their own toolchain. Now the question is: Can we use Rust in an IAR project? Spoiler: Yes we can, and we’ll explore how that works in this post.
Setting the stage
We’ll use the following tools in this tutorial
- IAR EWARM 9.30.1 (I pulled a demo version off IARs homepage)
- CMake 3.25
- Rust 1.65
- Corrosion.rs
- Ninja Build
Our target will bi a Nordic nrf52 DK (I had this lying around at home and it is fairly easy to use with its embedded J-Link).
For Rust, we’ll need the “thumbv7em-none-eabi” toolchain installed, as this toolchain emits code, that the Cortex M4 of the nrf52 understands:
rustup target add thumbv7m-none-eabi
To make sure the target is installed, query the installed targets with “rustup target list”. This will list all targets, with the installed one being marked as “installed”:
thumbv7em-none-eabi (installed) ... x86_64-pc-windows-gnu (installed) x86_64-pc-windows-msvc (installed)
Project Setup
Create an empty working directory for our testproject.
With the rust toolchain ready we now need a CMake Toolchain file for IAR’s ICC. Toolchainfiles describe how a compiler must be controlled to CMake. Luckily we don’t have to figure this out for ourselves, as IAR already provides a toolchain file for us here. Download the “iar-toolchain.cmake” file and drop it into the root of the working directory.
Once that is done, open the toolchain file and set the system processor to arm:
# Action: Set the `<arch>` to the compiler's target architecture # Examples: 430, 8051, arm, avr, riscv, rx, rl78, rh850, stm8 or v850 set(CMAKE_SYSTEM_PROCESSOR arm)
Adding C++ Code
We’ll want clean separation between Rust and C++, so we’ll put both in their own directories. Create a new directory in the working directory called “src”. Put a file with the following content inside and call it “main.cpp”:
#include <intrinsics.h> #include <stdio.h> #include <cstdint> extern "C" { uint32_t add_two_numbers(uint32_t left, uint32_t right); } // extern "C" void main() { add_two_numbers(10, 20); while (1) { printf("Hello world..again!\n"); __no_operation(); } }
Our Rust code will be the “extern C” function defined here. We just have to write it…
Adding Rust Code
We’ll create a very basic Rust crate, that does just export the required function. To do that, create a new folder in the working directory called “crate”. Open a shell inside and execute the following command:
cargo new rusty
This will create a basic prepopulated crate, that should have the following layout:
(crate) +--- rusty +---src | +---- lib.rs +---Cargo.toml
Open the file “Cargo.toml” and set the crate type to “staticlib”:
[package] name = "rusty" version = "0.1.0" authors = ["YourNameHere"] license = "MIT" edition = "2018" [dependencies] [lib] crate-type=["staticlib"]
Why is this necessary? By default rust libs are created with the assumption that they’ll be linked against other rust libs. This allows the rust compiler to do a lot of trickery with respect to dependencies and to avoid dependency hell. This however results in shenannigans that will no longer allow to just link the result into a C(++) program.
Open the file “lib.rs” and add the folllowing code:
#![no_std] use core::panic::PanicInfo; #[no_mangle] pub extern "C" fn add_two_numbers(left: u32, right: u32) -> u32 { left + right } #[panic_handler] pub fn panic_handler(info: &PanicInfo) -> ! { loop{}; }
What’s happening here? Well the “no_mangle” attribute coupled with the “extern C” signature causes our “add_two_numbers” function to becom a plain C-like symbol when compiled, i.e. something a linker would consider when trying to resolve the “extern “C”” section in our cpp file.
Tying things together with CMake
Until now we’ve not done all that much, frankly, we can’t even compile anything. So, let’s pull out CMake and bring this together. Create a new file in the root of the working dir called “CMakeLists.txt” and open it. Insert the following text:
# CMake requires to set its minimum required version cmake_minimum_required(VERSION 3.23) # Set the project name, enabling its required languages (e.g. ASM, C and/or CXX) project(simpleProject LANGUAGES C CXX) find_package(Corrosion REQUIRED) corrosion_import_crate(MANIFEST_PATH ./crate/rusty/Cargo.toml) # Add a executable target named "hello-world" add_executable(hello-world # Target sources ./src/main.cpp) target_link_libraries(hello-world PUBLIC rusty) # Set the target's compiler options target_compile_options(hello-world PRIVATE --cpu=Cortex-M4F --fpu=VFPv4_sp --dlib_config normal) # Set the target's linker options target_link_options(hello-world PRIVATE --semihosting --config ${TOOLKIT_DIR}/config/linker/NordicSemi/nrf52840_xxaa.icf)
This is actually pretty simple as far as CMake goes – the interesting bit is the invocation of “corrosion”. The “corrosion_import_crate” will create a linkable library target from all static libraries it finds in the passed toml file. In our case the crate was called “rusty” and thus we can link that to our executable. The compile and link options were pulled from IAR’s CMake example and mostly control the kind of target we want to build for and the target’s memory layout.
Building it…
Open a shell in the root of the working dir and execute the following command:
cmake -Boutput -G "Ninja" --toolchain ./iar-toolchain.cmake -DRust_CARGO_TARGET=thumbv7em-none-eabi
Note that the last part is required, so that the rust compiler knows which target to use.
After CMake has created the buildsystem for us we can build the actual binary by executing
cmake --build ./output
After the build completes we should find an .elf file in ./output which can be deployed to the target.
Deploying
Connect your devboard. Open EWARM and create a new project in the working directorie’s root. To debug you can
just add the .elf file compiled earlier to the project:
Setup your debug probe (in case of the nrf DK select a J-Link):
After this setup is done you should be able to deploy the .elf to the target by clicking “Download and Debug”.
Once the debugger reaches main you can even step into the rust code and get some (albiet limited) debugging done there.
Wrapping things up
This article demonstrated how to get rust code to play with IAR’s toolchain. Obviously a couple of shortcuts were taken to keep this article short. Most notably we skipped the automatic generation of a header file for the rust crate using cbindgen. Further, there are some pitfalls when trying to use multiple rust crates, so it is advised to bundle up all rust code for your project into one crate and only link that crate.
Image Credit: Nicolas Thomas/Unsplash