blob: c8df0815debc18a0bb48b67971c8a6b3f7903a36 [file] [log] [blame]
..
#
# Copyright 2020 Casper Meijn <casper@meijn.net>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
Rust Blinky application
===========================================
This tutorial shows you how to convert the Blinky application to Rust. This includes
integrating Cargo into newt builder.
.. contents::
:local:
:depth: 2
Prerequisites
~~~~~~~~~~~~~
Ensure that you meet the following prerequisites before continuing with
this tutorial:
- You have basic knowledge about `Rust <https://doc.rust-lang.org/stable/book/>`__.
- You have basic knowledge about `Rust Embedded <https://rust-embedded.github.io/book/>`__.
- You have rust installed using `rustup <https://doc.rust-lang.org/stable/book/ch01-01-installation.html>`__.
- Follow :doc:`Blinky tutorial <../blinky/blinky>` to create a
project with a basic application. You will extend that application in this
tutorial.
Initialize Rust
~~~~~~~~~~~~~~~
The first step is to initialize a crate for developing your application in. You
can do this in the existing `blinky` directory.
.. code-block:: console
$ cargo init --lib apps/blinky
Created library package
$ tree apps/blinky
apps/blinky
├── Cargo.toml
├── pkg.yml
└── src
├── lib.rs
└── main.c
1 directory, 4 files
This creates a Cargo.toml configuration file and src/lib.rs. We will use these
files to place our application in. You may notice that the Rust application is
not configured as an app, but as an lib. This is needed later to allow linking
to the rest of mynewt.
Setup the basics
~~~~~~~~~~~~~~~~
Now we want to actually convert the application code to Rust. Let's open `src/lib.rs`
and remove the contents. Then start with some basic setup:
.. code-block:: rust
#![no_std]
extern crate panic_halt;
#[no_mangle]
pub extern "C" fn main() {
loop {
}
}
The first line states that this program doesn't use the standard library. This
means that only the core library is linked to the program. See
`rust-embedded book <https://rust-embedded.github.io/book/intro/no-std.html>`__
for more information.
The next line specifies the panic handler. Panicking is an important feature of
Rust. To ease this tutorial we choose to halt the processor on panic. See
`rust-embedded book <https://docs.rust-embedded.org/book/start/panicking.html>`__
for alternatives.
Then we have the ``main`` function. It contains a endless loop as it should never
return, but doesn't do anything useful yet. It is marked ``no_mangle`` and ``extern "C"``
to make sure it can be called from the mynewt C code.
Converting sysinit
~~~~~~~~~~~~~~~~~~
The next step is to do the sysinit. This is implemented as a C macro, which is
incompatible with Rust. This could be solved by using a C library, but for this
tutorial we will simply execute the macro manually.
.. code-block:: rust
:emphasize-lines: 3-7, 13-16
#![no_std]
extern "C" {
fn sysinit_start();
fn sysinit_app();
fn sysinit_end();
}
extern crate panic_halt;
#[no_mangle]
pub extern "C" fn main() {
/* Initialize all packages. */
unsafe { sysinit_start(); }
unsafe { sysinit_app(); }
unsafe { sysinit_end(); }
loop {
}
}
First we manually define the three sysinit functions. This is similar to the
C header file. Then we execute the sysinit as the macro would do.
We need the ``unsafe`` indication because the C code doesn't have the same memory
guarantees as Rust. Normally we need to build a safe Rust wrapper, but that is
out of scope for this tutorial.
Doing GPIO and delays
~~~~~~~~~~~~~~~~~~~~~
Now it is time to do some GPIO and add a delay. Again we define the functions
and then use them in and around the loop. We need some constants that are
normally defined by the BSP or MCU. These constants need to move to a better
place later.
.. code-block:: rust
:emphasize-lines: 7-9, 14, 16, 25, 28-29, 31-32
#![no_std]
extern "C" {
fn sysinit_start();
fn sysinit_app();
fn sysinit_end();
fn hal_gpio_init_out(pin: i32, val: i32) -> i32;
fn hal_gpio_toggle(pin: i32);
fn os_time_delay(osticks: u32);
}
extern crate panic_halt;
const OS_TICKS_PER_SEC: u32 = 128;
const LED_BLINK_PIN: i32 = 23;
#[no_mangle]
pub extern "C" fn main() {
/* Initialize all packages. */
unsafe { sysinit_start(); }
unsafe { sysinit_app(); }
unsafe { sysinit_end(); }
unsafe { hal_gpio_init_out(LED_BLINK_PIN, 1); }
loop {
/* Wait one second */
unsafe { os_time_delay(OS_TICKS_PER_SEC); }
/* Toggle the LED */
unsafe { hal_gpio_toggle(LED_BLINK_PIN); }
}
}
Cargo build
~~~~~~~~~~~
Now that the application is converted we need to build it and link it the rest of mynewt. We start with Cargo.toml:
.. code-block:: toml
:emphasize-lines: 7-8, 10-11
[package]
name = "rust-klok"
version = "0.1.0"
authors = ["Casper Meijn <casper@meijn.net>"]
edition = "2018"
[dependencies]
panic-halt = "0.2.0"
[lib]
crate-type = ["staticlib"]
This adds the ``panic-halt`` dependency, which is needed for the panic handler as
mentioned earlier. It also configures the crate as staticlib, which causes the
application to be build as .a-library. This will be needed in a later step.
Next we need a script for running ``cargo`` and moving the library to the correct
place. Create a new file named ``apps/blinky/cargo_build.sh`` with the following contents:
.. code-block:: bash
:emphasize-lines: 1-21
#!/bin/bash
set -eu
if [[ ${MYNEWT_VAL_ARCH_NAME} == '"cortex_m0"' ]]; then
TARGET="thumbv6m-none-eabi"
elif [[ ${MYNEWT_VAL_ARCH_NAME} == '"cortex_m3"' ]]; then
TARGET="thumbv7m-none-eabi"
elif [[ ${MYNEWT_VAL_ARCH_NAME} == '"cortex_m4"' || ${MYNEWT_VAL_ARCH_NAME} == '"cortex_m7"' ]]; then
if [[ $MYNEWT_VAL_HARDFLOAT -eq 1 ]]; then
TARGET="thumbv7em-none-eabihf"
else
TARGET="thumbv7em-none-eabi"
fi
else
echo "The ARCH_NAME ${MYNEWT_VAL_ARCH_NAME} is not supported"
exit 1
fi
cargo build --target="${TARGET}" --target-dir="${MYNEWT_PKG_BIN_DIR}"
cp "${MYNEWT_PKG_BIN_DIR}"/${TARGET}/debug/*.a "${MYNEWT_PKG_BIN_ARCHIVE}"
The script first sets the name of the target as the Rust compiler knows it. Sadly
this is not the same as the names mynewt uses. You need to choose the same type of
compiler as mynewt uses. The following targets are available depending on the
MCU type:
* ``thumbv6m-none-eabi`` - use this for Cortex-M0 and Cortex-M0+.
* ``thumbv7m-none-eabi`` - use this for Cortex-M3.
* ``thumbv7em-none-eabi`` - use this for Cortex-M4 and Cortex-M7.
* ``thumbv7em-none-eabihf`` - use this for Cortex-M4 and Cortex-M7 with the
``HARDFLOAT`` syscfg enabled.
Then it runs ``cargo build`` with
the target directory set to a path that ``newt`` provides. Lastly it copies the
generated library to the correct path.
Don't forget to mark the script as executable:
.. code-block:: console
$ chmod +x apps/blinky/cargo_build.sh
Newt integration
~~~~~~~~~~~~~~~~
To automatically run ``cargo`` we need to add the following to pkg.yml:
.. code-block:: yaml
:emphasize-lines: 3-4, 5-7
...
pkg.pre_build_cmds:
'./cargo_build.sh': 1
pkg.lflags:
- '-Wl,--allow-multiple-definition'
The first section tells the mynewt build system to run ``cargo_build.sh`` as part
of ``newt build``. The second section tells the linker to ignore double function
definitions. This is needed as the Rust compiler adds some functions that are
also in baselibc.
Now we are ready to build a firmware! Remove ``main.c`` and start the build:
.. code-block:: console
$ rm apps/blinky/src/main.c
$ newt build nrf52_blinky
If this command complains about a target may not be installed, then you need to
install it. You need the same toolchain as configured earlier for the ``TARGET``
variable:
.. code-block:: console
$ rustup target add <your-target>
Conclusion
~~~~~~~~~~
You now have a firmware where the application is written in Rust. It is nicely
integrated into newt builder. However it still needs some work: it misses safe
Rust wrappers for the mynewt libraries and there are some magic constants that
need to be moved to a better location.