lupyuen.org/articles/rust2.html

1516 lines
No EOL
100 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="rustdoc">
<title>Rust on Apache NuttX OS</title>
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<meta property="og:title"
content="Rust on Apache NuttX OS"
data-rh="true">
<meta property="og:description"
content="How we run Rust programs on Apache NuttX OS... And transmit a LoRa Message with Rust"
data-rh="true">
<meta property="og:image"
content="https://lupyuen.github.io/images/rust2-title.jpg">
<meta property="og:type"
content="article" data-rh="true">
<link rel="canonical" href="https://lupyuen.org/articles/rust2.html" />
<!-- End scripts/articles/*-header.html -->
<!-- Begin scripts/rustdoc-header.html: Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<link rel="alternate" type="application/rss+xml" title="RSS Feed for lupyuen" href="/rss.xml" />
<link rel="stylesheet" type="text/css" href="../normalize.css">
<link rel="stylesheet" type="text/css" href="../rustdoc.css" id="mainThemeStyle">
<link rel="stylesheet" type="text/css" href="../dark.css">
<link rel="stylesheet" type="text/css" href="../light.css" id="themeStyle">
<link rel="stylesheet" type="text/css" href="../prism.css">
<script src="../storage.js"></script><noscript>
<link rel="stylesheet" href="../noscript.css"></noscript>
<link rel="shortcut icon" href="../favicon.ico">
<style type="text/css">
#crate-search {
background-image: url("../down-arrow.svg");
}
</style>
<!-- End scripts/rustdoc-header.html -->
</head>
<body class="rustdoc">
<!--[if lte IE 8]>
<div class="warning">
This old browser is unsupported and will most likely display funky
things.
</div>
<![endif]-->
<!-- Begin scripts/rustdoc-before.html: Pre-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker -->
<div class="theme-picker" style="left: 0"><button id="theme-picker" aria-label="Pick another theme!"><img src="../brush.svg"
width="18" alt="Pick another theme!"></button>
<div id="theme-choices"></div>
</div>
<!-- Theme Picker -->
<!-- End scripts/rustdoc-before.html -->
<h1 class="title">Rust on Apache NuttX OS</h1>
<nav id="rustdoc"><ul>
<li><a href="#rust-meets-nuttx" title="Rust Meets NuttX">1 Rust Meets NuttX</a><ul></ul></li>
<li><a href="#putting-things-neatly" title="Putting Things Neatly">2 Putting Things Neatly</a><ul></ul></li>
<li><a href="#flipping-gpio" title="Flipping GPIO">3 Flipping GPIO</a><ul></ul></li>
<li><a href="#import-nuttx-functions" title="Import NuttX Functions">4 Import NuttX Functions</a><ul></ul></li>
<li><a href="#rust-embedded-hal" title="Rust Embedded HAL">5 Rust Embedded HAL</a><ul></ul></li>
<li><a href="#spi-transfer" title="SPI Transfer">6 SPI Transfer</a><ul></ul></li>
<li><a href="#rust-driver-for-lora-sx1262" title="Rust Driver for LoRa SX1262">7 Rust Driver for LoRa SX1262</a><ul></ul></li>
<li><a href="#transmit-lora-message" title="Transmit LoRa Message">8 Transmit LoRa Message</a><ul></ul></li>
<li><a href="#download-source-code" title="Download Source Code">9 Download Source Code</a><ul></ul></li>
<li><a href="#build-the-firmware" title="Build The Firmware">10 Build The Firmware</a><ul></ul></li>
<li><a href="#run-the-firmware" title="Run The Firmware">11 Run The Firmware</a><ul></ul></li>
<li><a href="#verify-lora-message" title="Verify LoRa Message">12 Verify LoRa Message</a><ul>
<li><a href="#spectrum-analyser" title="Spectrum Analyser">12.1 Spectrum Analyser</a><ul></ul></li>
<li><a href="#lora-receiver" title="LoRa Receiver">12.2 LoRa Receiver</a><ul></ul></li></ul></li>
<li><a href="#lorawan-support" title="LoRaWAN Support">13 LoRaWAN Support</a><ul></ul></li>
<li><a href="#whats-next" title="Whats Next">14 Whats Next</a><ul></ul></li>
<li><a href="#notes" title="Notes">15 Notes</a><ul></ul></li>
<li><a href="#appendix-rust-embedded-hal-for-nuttx" title="Appendix: Rust Embedded HAL for NuttX">16 Appendix: Rust Embedded HAL for NuttX</a><ul>
<li><a href="#gpio-hal" title="GPIO HAL">16.1 GPIO HAL</a><ul></ul></li>
<li><a href="#spi-hal" title="SPI HAL">16.2 SPI HAL</a><ul></ul></li>
<li><a href="#i2c-hal" title="I2C HAL">16.3 I2C HAL</a><ul></ul></li>
<li><a href="#delay-hal" title="Delay HAL">16.4 Delay HAL</a><ul></ul></li></ul></li>
<li><a href="#appendix-fix-sx1262-driver-for-nuttx" title="Appendix: Fix SX1262 Driver for NuttX">17 Appendix: Fix SX1262 Driver for NuttX</a><ul>
<li><a href="#merge-spi-requests" title="Merge SPI Requests">17.1 Merge SPI Requests</a><ul></ul></li>
<li><a href="#read-register" title="Read Register">17.2 Read Register</a><ul></ul></li>
<li><a href="#set-registers" title="Set Registers">17.3 Set Registers</a><ul></ul></li>
<li><a href="#adapt-for-risc-v" title="Adapt For RISC-V">17.4 Adapt For RISC-V</a><ul></ul></li></ul></li>
<li><a href="#appendix-rust-build-script-for-nuttx" title="Appendix: Rust Build Script for NuttX">18 Appendix: Rust Build Script for NuttX</a><ul>
<li><a href="#rust-target" title="Rust Target">18.1 Rust Target</a><ul></ul></li>
<li><a href="#rust-build-options" title="Rust Build Options">18.2 Rust Build Options</a><ul></ul></li>
<li><a href="#define-libraries" title="Define Libraries">18.3 Define Libraries</a><ul></ul></li>
<li><a href="#build-stub-library" title="Build Stub Library">18.4 Build Stub Library</a><ul></ul></li>
<li><a href="#build-rust-library" title="Build Rust Library">18.5 Build Rust Library</a><ul></ul></li>
<li><a href="#replace-stub-libary-by-rust-library" title="Replace Stub Libary by Rust Library">18.6 Replace Stub Libary by Rust Library</a><ul></ul></li>
<li><a href="#link-rust-library-into-firmware" title="Link Rust Library into Firmware">18.7 Link Rust Library into Firmware</a><ul></ul></li></ul></li>
<li><a href="#appendix-build-flash-and-run-nuttx" title="Appendix: Build, Flash and Run NuttX">19 Appendix: Build, Flash and Run NuttX</a><ul>
<li><a href="#build-nuttx" title="Build NuttX">19.1 Build NuttX</a><ul></ul></li>
<li><a href="#flash-nuttx" title="Flash NuttX">19.2 Flash NuttX</a><ul></ul></li>
<li><a href="#run-nuttx" title="Run NuttX">19.3 Run NuttX</a><ul></ul></li></ul></li></ul></nav><p>📝 <em>12 Jan 2022</em></p>
<p><img src="https://lupyuen.github.io/images/rust2-title.jpg" alt="PineDio Stack BL604 RISC-V Board" /></p>
<p><a href="https://lupyuen.github.io/articles/nuttx"><strong>Apache NuttX</strong></a> is an embedded operating system thats portable across <strong>many platforms</strong> (8-bit to 64-bit) and works like a <strong>tiny version of Linux</strong> (because its POSIX Compliant).</p>
<p><em>Can we create (safer) Embedded Apps with <strong>Rust on NuttX</strong>?</em></p>
<p><em>Can we take a Device Driver from <a href="https://github.com/rust-embedded/awesome-embedded-rust#driver-crates"><strong>Rust Embedded</strong></a>… And run it on NuttX?</em></p>
<p>Today we shall…</p>
<ol>
<li>
<p>Build and run <strong>Rust programs</strong> on NuttX</p>
</li>
<li>
<p>Access <strong>GPIO and SPI ports</strong> with Rust Embedded HAL</p>
</li>
<li>
<p>Run the <strong>Semtech SX1262 LoRa Driver</strong> from Rust Embedded</p>
</li>
<li>
<p>And transmit a <a href="https://makezine.com/2021/05/24/go-long-with-lora-radio/"><strong>LoRa Message</strong></a> over the airwaves with Rust on NuttX!</p>
</li>
</ol>
<p>We tested Rust on NuttX with <a href="https://lupyuen.github.io/articles/pinedio2"><strong>PineDio Stack BL604</strong></a> RISC-V Board (pic above) and its onboard Semtech SX1262 Transceiver.</p>
<p>But it should work on ESP32, Arm and other NuttX platforms. (With some tweaking)</p>
<p><strong>Caution:</strong> Work in Progress! Some spots are rough and rocky, Im hoping the NuttX and Rust Communities could help to fill in the potholes before release 🙏</p>
<p><img src="https://lupyuen.github.io/images/rust2-run.png" alt="Rust running on NuttX" /></p>
<h1 id="rust-meets-nuttx"><a class="doc-anchor" href="#rust-meets-nuttx">§</a>1 Rust Meets NuttX</h1>
<p>This is the <strong>simplest Rust program</strong> that will run on NuttX and print <em>“Hello World!”</em>: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L22-L56">lib.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="attr">#![no_std] </span><span class="comment">// Use the Rust Core Library instead of the Rust Standard Library, which is not compatible with embedded systems
</span><span class="attr">#[no_mangle] </span><span class="comment">// Don't mangle the function name
</span><span class="kw">extern </span><span class="string">"C" </span><span class="kw">fn </span>rust_main() { <span class="comment">// Declare `extern "C"` because it will be called by NuttX
</span><span class="kw">extern </span><span class="string">"C" </span>{ <span class="comment">// Import C Function
</span><span class="doccomment">/// Print a message to the serial console (from C stdio library)
</span><span class="kw">fn </span>puts(s: <span class="kw-2">*const </span>u8) -&gt; i32;
}
<span class="kw">unsafe </span>{ <span class="comment">// Mark as unsafe because we are calling C
// Print a message to the serial console
</span>puts(
<span class="string">b"Hello World!\0" </span><span class="comment">// Byte String terminated with null
</span>.as_ptr() <span class="comment">// Convert to pointer
</span>);
}
}</code></pre></div>
<p>Lets break it down from the top…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Use the Rust Core Library instead of the Rust Standard Library,
// which is not compatible with embedded systems
</span><span class="attr">#![no_std]</span></code></pre></div>
<p>We select the <strong>Rust Core Library</strong> (for embedded platforms), which is a subset of the Rust Standard Library (for desktops and servers).</p>
<p>Next we declare the <strong>Rust Function</strong> that will be called by NuttX…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Don't mangle the function name
</span><span class="attr">#[no_mangle]
</span><span class="comment">// Declare `extern "C"` because it will be called by NuttX
</span><span class="kw">extern </span><span class="string">"C" </span><span class="kw">fn </span>rust_main() {</code></pre></div>
<p>(Why is it named <strong>“rust_main”</strong>? Well find out in a while)</p>
<p>NuttX provides the <strong>“puts”</strong> function because its POSIX Compliant (like Linux), so we import it from C…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Import C Function
</span><span class="kw">extern </span><span class="string">"C" </span>{
<span class="doccomment">/// Print a message to the serial console (from C stdio library)
</span><span class="kw">fn </span>puts(s: <span class="kw-2">*const </span>u8) -&gt; i32;
}</code></pre></div>
<p>This declares that <strong>“puts”</strong></p>
<ul>
<li>
<p>Accepts a “<code>*const u8</code>” pointer</p>
<p>(Equivalent to “<code>const uint8_t *</code>” in C)</p>
</li>
<li>
<p>Returns an “<code>i32</code>” result</p>
<p>(Equivalent to “<code>int32_t</code>” in C)</p>
</li>
</ul>
<p>We call <strong>“puts”</strong> like so…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Mark as unsafe because we are calling C
</span><span class="kw">unsafe </span>{
<span class="comment">// Print a message to the serial console
</span>puts(
<span class="string">b"Hello World!\0" </span><span class="comment">// Byte String terminated with null
</span>.as_ptr() <span class="comment">// Convert to pointer
</span>);
}</code></pre></div>
<p>Passing a string from Rust to C looks rather cumbersome…</p>
<ul>
<li>
<p>Calls to C Functions must be marked as <strong><code>unsafe</code></strong></p>
</li>
<li>
<p>We construct a <strong>Byte String</strong> in Rust with the <code>b"..."</code> syntax</p>
</li>
<li>
<p>Rust Strings are not null-terminated! We add the <strong>Null Byte</strong> ourselves with “<code>\0</code></p>
</li>
<li>
<p>We call <strong><code>.as_ptr()</code></strong> to convert the Byte String to a pointer</p>
</li>
</ul>
<p>Though it looks messy, the Rust code above runs perfectly fine from the <strong>NuttX Shell</strong></p>
<div class="example-wrap"><pre class="language-text"><code>nsh&gt; rust_test
Hello World!</code></pre></div>
<p>Well make it neater in the next chapter.</p>
<p><em>Is there anything we missed?</em></p>
<p>We need to define a <strong>Panic Handler</strong> that will be called when a Runtime Error or Assertion Failure occurs.</p>
<p><a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L218-L243">(Our Panic Handler is defined here)</a></p>
<h1 id="putting-things-neatly"><a class="doc-anchor" href="#putting-things-neatly">§</a>2 Putting Things Neatly</h1>
<p><em>Do we really need the cumbersome syntax for <strong>“puts”</strong> when we print things?</em></p>
<p>We can do better! Lets wrap this cumbersome code…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Mark as unsafe because we are calling C
</span><span class="kw">unsafe </span>{
<span class="comment">// Print a message to the serial console
</span>puts(
<span class="string">b"Hello World!\0" </span><span class="comment">// Byte String terminated with null
</span>.as_ptr() <span class="comment">// Convert to pointer
</span>);
}</code></pre></div>
<p>…with a <strong>Rust Macro</strong>. And well get this…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Print a message to the serial console
</span><span class="macro">println!</span>(<span class="string">"Hello World!"</span>);</code></pre></div>
<p>Much neater! Well see later that <strong>“println!”</strong> supports Formatted Output too.</p>
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/macros.rs">(<strong>println!</strong> is defined here. Thanks Huang Qi! 👍)</a></p>
<p><a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L175-L216">(<strong>puts</strong> is wrapped here)</a></p>
<p><em>Why is our Rust Function named <strong>rust_main</strong> instead of <strong>main</strong>?</em></p>
<p>Our Rust code (<strong>rust_main</strong>) is compiled into a <strong>Static Library</strong> that will be linked into the NuttX Firmware.</p>
<p>Our NuttX Firmware contains a NuttX App (<strong>rust_test</strong>) that calls <strong>rust_main</strong> from C: <a href="https://github.com/lupyuen/rust_test/blob/main/rust_test_main.c#L28-L37">rust_test_main.c</a></p>
<div class="example-wrap"><pre class="language-c"><code>// Rust Function defined in rust/src/lib.rs
void rust_main(void);
// Our Main Function in C...
int main(int argc, FAR char *argv[]) {
// Calls the Rust Function
rust_main();
return 0;
}</code></pre></div>
<p>Thus its indeed possible to call Rust from C… And C from Rust!</p>
<p><a href="https://lupyuen.github.io/articles/rust2#appendix-rust-build-script-for-nuttx">(More about the Rust build script in the Appendix)</a></p>
<p><img src="https://lupyuen.github.io/images/rust2-gpio.png" alt="Rust opening GPIO Ports on NuttX" /></p>
<h1 id="flipping-gpio"><a class="doc-anchor" href="#flipping-gpio">§</a>3 Flipping GPIO</h1>
<p>Since we can call NuttX Functions from Rust, lets <strong>flip a GPIO High and Low</strong> the POSIX way: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L56-L133">lib.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Open GPIO Output
</span><span class="kw">let </span>cs = <span class="kw">unsafe </span>{
open(<span class="string">b"/dev/gpio1\0"</span>.as_ptr(), O_RDWR)
};
<span class="macro">assert!</span>(cs &gt; <span class="number">0</span>);</code></pre></div>
<p>We open the GPIO Output at <strong>“/dev/gpio1”</strong> with read-write access.</p>
<p>Then we call <strong>ioctl</strong> to set the <strong>GPIO Output to Low</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Set GPIO Output to Low
</span><span class="kw">let </span>ret = <span class="kw">unsafe </span>{
ioctl(cs, GPIOC_WRITE, <span class="number">0</span>)
};
<span class="macro">assert!</span>(ret &gt;= <span class="number">0</span>);</code></pre></div>
<p>We sleep for 1 second…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Sleep 1 second
</span><span class="kw">unsafe </span>{
sleep(<span class="number">1</span>);
}</code></pre></div>
<p>We set the <strong>GPIO Output to High</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Set GPIO Output to High
</span><span class="kw">let </span>ret = <span class="kw">unsafe </span>{
ioctl(cs, GPIOC_WRITE, <span class="number">1</span>)
};
<span class="macro">assert!</span>(ret &gt;= <span class="number">0</span>);</code></pre></div>
<p>Finally we <strong>close the GPIO Output</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Close the GPIO Output
</span><span class="kw">unsafe </span>{
close(cs);
}</code></pre></div>
<p>This code works OK for <strong>blinking an LED</strong> on a GPIO pin, but well do something more ambitious… Transfer data over SPI!</p>
<p><em>Wont this code get really messy when we do lots of GPIO and SPI?</em></p>
<p>Yep it might get terribly messy! <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L61-L136">(Like this)</a></p>
<p>In a while well mop this up with <strong>Rust Embedded HAL</strong>.</p>
<h1 id="import-nuttx-functions"><a class="doc-anchor" href="#import-nuttx-functions">§</a>4 Import NuttX Functions</h1>
<p><em>How did we import the NuttX Functions: open, ioctl, sleep, close, …?</em></p>
<p>We <strong>imported the NuttX Functions</strong> like so: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L248-L257">lib.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="kw">extern </span><span class="string">"C" </span>{ <span class="comment">// Import NuttX Functions. TODO: Import with bindgen
</span><span class="kw">pub fn </span>open(path: <span class="kw-2">*const </span>u8, oflag: i32, ...) -&gt; i32;
<span class="kw">pub fn </span>read(fd: i32, buf: <span class="kw-2">*mut </span>u8, count: u32) -&gt; i32;
<span class="kw">pub fn </span>write(fd: i32, buf: <span class="kw-2">*const </span>u8, count: u32) -&gt; i32;
<span class="kw">pub fn </span>close(fd: i32) -&gt; i32;
<span class="kw">pub fn </span>ioctl(fd: i32, request: i32, ...) -&gt; i32; <span class="comment">// On NuttX: request is i32, not u64 like Linux
</span><span class="kw">pub fn </span>sleep(secs: u32) -&gt; u32;
<span class="kw">pub fn </span>usleep(usec: u32) -&gt; u32;
<span class="kw">pub fn </span>exit(status: u32) -&gt; !; <span class="comment">// Does not return
</span>}</code></pre></div>
<p>We (very carefully) <strong>imported the NuttX Constants</strong> as well: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L259-L277">lib.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Import NuttX Constants. TODO: Import with bindgen from https://github.com/lupyuen/nuttx/blob/rust/include/nuttx/ioexpander/gpio.h
</span><span class="kw">pub const </span>GPIOC_WRITE: i32 = _GPIOBASE | <span class="number">1</span>; <span class="comment">// _GPIOC(1)
</span><span class="kw">pub const </span>GPIOC_READ: i32 = _GPIOBASE | <span class="number">2</span>; <span class="comment">// _GPIOC(2)
</span><span class="kw">pub const </span>_GPIOBASE: i32 = <span class="number">0x2300</span>; <span class="comment">// GPIO driver commands
</span><span class="kw">pub const </span>O_RDWR: i32 = O_RDOK|O_WROK; <span class="comment">// Open for both read &amp; write access</span></code></pre></div>
<p><a href="https://rust-lang.github.io/rust-bindgen/">(Someday we should auto-generate the Rust Bindings for NuttX with the <strong>bindgen</strong> tool)</a></p>
<p><img src="https://lupyuen.github.io/images/rust2-hal.png" alt="Rust Embedded HAL" /></p>
<h1 id="rust-embedded-hal"><a class="doc-anchor" href="#rust-embedded-hal">§</a>5 Rust Embedded HAL</h1>
<p><em>What is Rust Embedded HAL?</em></p>
<p><strong>Rust Embedded HAL</strong> (Hardware Abstraction Layer) defines a standard interface thats used by <strong>Rust Embedded Device Drivers</strong> to access the hardware: GPIO, SPI, I2C, …</p>
<p><a href="https://github.com/rust-embedded/awesome-embedded-rust#driver-crates">(Check out the Rust Embedded Drivers)</a></p>
<p><em>What if we implement Rust Embedded HAL for NuttX: GPIO, SPI, I2C, …?</em></p>
<p>That would be super interesting… It means that we can pick <strong>any Rust Embedded Driver</strong> and run it on NuttX! (Theoretically)</p>
<p>In a while well test the <strong>Semtech SX1262 LoRa Driver</strong> from Rust Embedded, and see if it works on NuttX!</p>
<p><em>How do we call Rust Embedded HAL from NuttX?</em></p>
<p>We have created a <strong>NuttX Embedded HAL</strong> that implements the Rust Embedded HAL on NuttX…</p>
<ul>
<li><a href="https://github.com/lupyuen/nuttx-embedded-hal"><strong>lupyuen/nuttx-embedded-hal</strong></a></li>
</ul>
<p><a href="https://lupyuen.github.io/articles/rust2#appendix-rust-embedded-hal-for-nuttx">(More details in the Appendix)</a></p>
<p>To call it, we add <strong>embedded-hal</strong> and <strong>nuttx-embedded-hal</strong> as dependencies to our <a href="https://github.com/lupyuen/rust_test/blob/main/rust/Cargo.toml#L8-L16"><strong>Cargo.toml</strong></a></p>
<div class="example-wrap"><pre class="language-text"><code>## External Rust libraries used by this module. See crates.io.
[dependencies]
## Rust Embedded HAL: https://crates.io/crates/embedded-hal
embedded-hal = &quot;0.2.7&quot;
## NuttX Embedded HAL: https://crates.io/crates/nuttx-embedded-hal
nuttx-embedded-hal = &quot;1.0.10&quot;
## SX126x LoRa Radio Driver fixed for NuttX
sx126x = { git = &quot;https://github.com/lupyuen/sx126x-rs-nuttx&quot; } </code></pre></div>
<p><a href="https://crates.io/crates/nuttx-embedded-hal">(Always use the latest version of <strong>nuttx-embedded-hal</strong>)</a></p>
<p>(Well see the <strong>sx126x</strong> driver in a while)</p>
<p>We import the <strong>Rust Embedded Traits</strong> (GPIO, SPI and Delay) that well call from our app: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L12-L18">lib.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Import Embedded Traits
</span><span class="kw">use </span>embedded_hal::{ <span class="comment">// Rust Embedded HAL
</span>digital::v2::OutputPin, <span class="comment">// GPIO Output
</span>blocking::{ <span class="comment">// Blocking I/O
</span>delay::DelayMs, <span class="comment">// Delay Interface
</span>spi::Transfer, <span class="comment">// SPI Transfer
</span>},
};</code></pre></div>
<p>To open GPIO Output <strong>“/dev/gpio1”</strong> we do this: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L133-L174">lib.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Open GPIO Output
</span><span class="kw">let </span><span class="kw-2">mut </span>cs = nuttx_embedded_hal::OutputPin
::new(<span class="string">"/dev/gpio1"</span>)
.expect(<span class="string">"open gpio failed"</span>);</code></pre></div>
<p>(This halts with an error if “/dev/gpio1” doesnt exist)</p>
<p>We declare it as <strong><code>mut</code></strong> (mutable) because we expect its Internal State to change as we flip the GPIO.</p>
<p>Next we fetch the <strong>Delay Interface</strong> that well call to sleep…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Get a Delay Interface
</span><span class="kw">let </span><span class="kw-2">mut </span>delay = nuttx_embedded_hal::Delay;</code></pre></div>
<p>Then we set the <strong>GPIO Output to Low</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Set GPIO Output to Low
</span>cs.set_low()
.expect(<span class="string">"cs failed"</span>);</code></pre></div>
<p>(“<code>expect</code>” works like an Assertion Check)</p>
<p>We sleep for 1 second…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Wait 1 second (1,000 milliseconds)
</span>delay.delay_ms(<span class="number">1000_u32</span>);</code></pre></div>
<p>(“<code>u32</code>” says that this is an unsigned 32-bit integer)</p>
<p>Finally we set the <strong>GPIO Output to High</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Set GPIO Output to High
</span>cs.set_high()
.expect(<span class="string">"cs failed"</span>);</code></pre></div>
<p>Rust Embedded HAL makes GPIO programming more fun! Lets do SPI now.</p>
<p><img src="https://lupyuen.github.io/images/spi2-pinedio1.jpg" alt="Inside PineDio Stack BL604" /></p>
<h1 id="spi-transfer"><a class="doc-anchor" href="#spi-transfer">§</a>6 SPI Transfer</h1>
<p>Lets test SPI Data Transfer to the <a href="https://www.semtech.com/products/wireless-rf/lora-core/sx1262"><strong>Semtech SX1262 LoRa Transceiver</strong></a>.</p>
<p>For PineDio Stack BL604 with its onboard SX1262 (pic above), we control <strong>SPI Chip Select</strong> ourselves via GPIO Output <strong>“/dev/gpio1”</strong></p>
<p>We begin by opening the <strong>GPIO Output</strong> for SPI Chip Select: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs#L133-L174">lib.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Test the NuttX Embedded HAL by reading SX1262 Register 8
</span><span class="kw">fn </span>test_hal() {
<span class="comment">// Open GPIO Output for SX1262 Chip Select
</span><span class="kw">let </span><span class="kw-2">mut </span>cs = nuttx_embedded_hal::OutputPin
::new(<span class="string">"/dev/gpio1"</span>)
.expect(<span class="string">"open gpio failed"</span>);</code></pre></div>
<p>Next we open the <strong>SPI Bus</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Open SPI Bus for SX1262
</span><span class="kw">let </span><span class="kw-2">mut </span>spi = nuttx_embedded_hal::Spi
::new(<span class="string">"/dev/spitest0"</span>)
.expect(<span class="string">"open spi failed"</span>);</code></pre></div>
<p><strong>“/dev/spitest0”</strong> is our <strong>SPI Test Driver</strong> that simplifies SPI programming. <a href="https://lupyuen.github.io/articles/spi2">(See this)</a></p>
<p>Before talking to SX1262, we set <strong>Chip Select to Low</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Set SX1262 Chip Select to Low
</span>cs.set_low()
.expect(<span class="string">"cs failed"</span>);</code></pre></div>
<p>We transmit <strong>5 bytes of data</strong> to SX1262 over SPI…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Define the SX1262 Command: Read Register 8
</span><span class="kw">let </span><span class="kw-2">mut </span>data: [ u8; <span class="number">5 </span>] = [ <span class="number">0x1d</span>, <span class="number">0x00</span>, <span class="number">0x08</span>, <span class="number">0x00</span>, <span class="number">0x00 </span>];
<span class="comment">// Transfer the command to SX1262 over SPI
</span>spi.transfer(<span class="kw-2">&amp;mut </span>data)
.expect(<span class="string">"spi failed"</span>);</code></pre></div>
<p>The data transmitted over SPI is the <strong>SX1262 Command</strong> that will read <strong>SX1262 Register 8</strong></p>
<div class="example-wrap"><pre class="language-text"><code> 1D 00 08 00 00</code></pre></div>
<p>We pass the data as a <strong>Mutable Reference</strong><code>&amp;mut</code>” because we expect the contents to be changed during the SPI Transfer.</p>
<p>The value of SX1262 Register 8 is returned as the <strong>last byte</strong> of the SPI Response…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="macro">println!</span>(<span class="string">"test_hal: SX1262 Register 8 is 0x{:02x}"</span>, data[<span class="number">4</span>]);</code></pre></div>
<p>We set <strong>Chip Select to High</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Set SX1262 Chip Select to High
</span>cs.set_high()
.expect(<span class="string">"cs failed"</span>);</code></pre></div>
<p>And were done! Running this Rust code on NuttX shows…</p>
<div class="example-wrap"><pre class="language-text"><code>nsh&gt; rust_test
...
test_hal: SX1262 Register 8 is 0x80</code></pre></div>
<p><a href="https://gist.github.com/lupyuen/412cc8bef51c40236767e10693c738b5">(See the Output Log)</a></p>
<p>Thats the correct value of SX1262 Register 8: <strong><code>0x80</code></strong>!</p>
<p>(Later well talk about building and flashing the NuttX Firmware)</p>
<p><img src="https://lupyuen.github.io/images/rust2-hal2.png" alt="Calling the Rust Driver for LoRa SX1262" /></p>
<h1 id="rust-driver-for-lora-sx1262"><a class="doc-anchor" href="#rust-driver-for-lora-sx1262">§</a>7 Rust Driver for LoRa SX1262</h1>
<p><em>Can we pick ANY Device Driver from <a href="https://github.com/rust-embedded/awesome-embedded-rust#driver-crates"><strong>Rust Embedded</strong></a></em></p>
<p><em>And run it on NuttX?</em></p>
<p>Now that we have a (barebones) <strong>Rust Embedded HAL</strong> for NuttX, lets find out!</p>
<p>Well test this Rust Embedded Driver for Semtech SX1262…</p>
<ul>
<li><a href="https://github.com/lupyuen/sx126x-rs-nuttx"><strong>lupyuen/sx126x-rs-nuttx</strong></a></li>
</ul>
<p>That we tweaked slightly from <strong><a href="https://github.com/tweedegolf/sx126x-rs">tweedegolf/sx126x-rs</a></strong></p>
<p><a href="https://lupyuen.github.io/articles/rust2#appendix-fix-sx1262-driver-for-nuttx">(Details in the Appendix. Thanks Tweede golf! 👍)</a></p>
<p>Lets do the same test as last chapter: <strong>Read SX1262 Register 8</strong></p>
<p>We begin by opening the <strong>GPIO Input, Output and Interrupt Pins</strong> for SX1262: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/sx1262.rs#L21-L84">sx1262.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Test the SX1262 Driver by reading a register.
/// Based on https://github.com/tweedegolf/sx126x-rs/blob/master/examples/stm32f103-ping-pong.rs
</span><span class="kw">pub fn </span>test_sx1262() {
<span class="comment">// Open GPIO Input for SX1262 Busy Pin
</span><span class="kw">let </span>lora_busy = nuttx_embedded_hal::InputPin
::new(<span class="string">"/dev/gpio0"</span>)
.expect(<span class="string">"open gpio failed"</span>);
<span class="comment">// Open GPIO Output for SX1262 Chip Select
</span><span class="kw">let </span>lora_nss = nuttx_embedded_hal::OutputPin
::new(<span class="string">"/dev/gpio1"</span>)
.expect(<span class="string">"open gpio failed"</span>);
<span class="comment">// Open GPIO Interrupt for SX1262 DIO1 Pin
</span><span class="kw">let </span>lora_dio1 = nuttx_embedded_hal::InterruptPin
::new(<span class="string">"/dev/gpio2"</span>)
.expect(<span class="string">"open gpio failed"</span>);</code></pre></div>
<p>(We wont handle interrupts today)</p>
<p>The <strong>NRESET and Antenna Pins</strong> are unused for now…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// TODO: Open GPIO Output for SX1262 NRESET Pin
</span><span class="kw">let </span>lora_nreset = nuttx_embedded_hal::UnusedPin
::new()
.expect(<span class="string">"open gpio failed"</span>);
<span class="comment">// TODO: Open GPIO Output for SX1262 Antenna Pin
</span><span class="kw">let </span>lora_ant = nuttx_embedded_hal::UnusedPin
::new()
.expect(<span class="string">"open gpio failed"</span>);
<span class="comment">// Open SPI Bus for SX1262
</span><span class="kw">let </span><span class="kw-2">mut </span>spi1 = nuttx_embedded_hal::Spi
::new(<span class="string">"/dev/spitest0"</span>)
.expect(<span class="string">"open spi failed"</span>);</code></pre></div>
<p>And we open the <strong>SPI Bus</strong> like before.</p>
<p>We <strong>define the pins</strong> for our SX1262 Driver…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Define the SX1262 Pins
</span><span class="kw">let </span>lora_pins = (
lora_nss, <span class="comment">// /dev/gpio1
</span>lora_nreset, <span class="comment">// TODO
</span>lora_busy, <span class="comment">// /dev/gpio0
</span>lora_ant, <span class="comment">// TODO
</span>lora_dio1, <span class="comment">// /dev/gpio2
</span>);
<span class="comment">// Init a busy-waiting delay
</span><span class="kw">let </span>delay = <span class="kw-2">&amp;mut </span>nuttx_hal::Delay;</code></pre></div>
<p>We <strong>initialise the SX1262 Driver</strong></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Build the SX1262 Configuration
</span><span class="kw">let </span>conf = build_config();
<span class="comment">// Construct the SX1262 Driver
</span><span class="kw">let </span><span class="kw-2">mut </span>lora = SX126x::new(lora_pins);
<span class="comment">// Init the SX1262 Driver
</span>lora.init(<span class="kw-2">&amp;mut </span>spi1, delay, conf)
.expect(<span class="string">"sx1262 init failed"</span>);</code></pre></div>
<p><a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/sx1262.rs#L117-L157">(<strong>build_config</strong> is defined here)</a></p>
<p>Lastly we <strong>read SX1262 Register 8</strong> and print the result…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Init Result Buffer as 1 byte of 0x00
</span><span class="kw">let </span><span class="kw-2">mut </span>result: [ u8; <span class="number">1 </span>] = [ <span class="number">0</span>; <span class="number">1 </span>];
<span class="comment">// Read SX1262 Register 8 into Result Buffer
</span>lora.read_register(<span class="kw-2">&amp;mut </span>spi1, delay, <span class="number">8</span>, <span class="kw-2">&amp;mut </span>result)
.expect(<span class="string">"sx1262 read register failed"</span>);
<span class="comment">// Show the register value
</span><span class="macro">println!</span>(<span class="string">"test_sx1262: SX1262 Register 8 is 0x{:02x}"</span>, result[<span class="number">0</span>]);</code></pre></div>
<p>When we run the Rust code well see…</p>
<div class="example-wrap"><pre class="language-text"><code>nsh&gt; rust_test
...
test_sx1262: SX1262 Register 8 is 0x80</code></pre></div>
<p><a href="https://gist.github.com/lupyuen/412cc8bef51c40236767e10693c738b5">(See the Output Log)</a></p>
<p>Which is the same result from the previous chapter. Yep the Rust Driver works OK with our NuttX Embedded HAL!</p>
<p>Lets test the Rust Driver to the limit… And send a LoRa Message over the airwaves!</p>
<p><img src="https://lupyuen.github.io/images/rust2-transmit2.png" alt="Transmit LoRa Message" /></p>
<h1 id="transmit-lora-message"><a class="doc-anchor" href="#transmit-lora-message">§</a>8 Transmit LoRa Message</h1>
<p>For our final test we shall transmit a <a href="https://makezine.com/2021/05/24/go-long-with-lora-radio/"><strong>LoRa Message</strong></a> with the Rust Driver for SX1262.</p>
<p>We configure the <strong>LoRa Frequency</strong> for our region like so: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/sx1262.rs#L14-L17">sx1262.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// TODO: Change this to your LoRa Frequency
</span><span class="comment">// const RF_FREQUENCY: u32 = 868_000_000; // 868 MHz (EU)
// const RF_FREQUENCY: u32 = 915_000_000; // 915 MHz (US)
</span><span class="kw">const </span>RF_FREQUENCY: u32 = <span class="number">923_000_000</span>; <span class="comment">// 923 MHz (Asia)</span></code></pre></div>
<p>We prepare for LoRa Transmission by <strong>setting some SX1262 Registers</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/sx1262.rs#L85-L115">sx1262.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Transmit a LoRa Message.
/// Based on https://github.com/tweedegolf/sx126x-rs/blob/master/examples/stm32f103-ping-pong.rs
</span><span class="kw">pub fn </span>test_sx1262() {
<span class="comment">// Omitted: Init the SX1262 Driver
</span>...
<span class="comment">// Write SX1262 Registers to prepare for transmitting LoRa message.
// Based on https://gist.github.com/lupyuen/5fdede131ad0e327478994872f190668
// and https://docs.google.com/spreadsheets/d/14Pczf2sP_Egnzi5_nikukauL2iTKA03Qgq715e50__0/edit?usp=sharing
// Write Register 0x889: 0x04 (TxModulation)
</span>lora.write_register(<span class="kw-2">&amp;mut </span>spi1, delay, Register::TxModulaton, <span class="kw-2">&amp;</span>[<span class="number">0x04</span>])
.expect(<span class="string">"write register failed"</span>);
<span class="comment">// Write Register 0x8D8: 0xFE (TxClampConfig)
</span>lora.write_register(<span class="kw-2">&amp;mut </span>spi1, delay, Register::TxClampConfig, <span class="kw-2">&amp;</span>[<span class="number">0xFE</span>])
.expect(<span class="string">"write register failed"</span>);
<span class="comment">// Write Register 0x8E7: 0x38 (Over Current Protection)
</span>lora.write_register(<span class="kw-2">&amp;mut </span>spi1, delay, Register::OcpConfiguration, <span class="kw-2">&amp;</span>[<span class="number">0x38</span>])
.expect(<span class="string">"write register failed"</span>);
<span class="comment">// Write Register 0x736: 0x0D (Inverted IQ)
</span>lora.write_register(<span class="kw-2">&amp;mut </span>spi1, delay, Register::IqPolaritySetup, <span class="kw-2">&amp;</span>[<span class="number">0x0D</span>])
.expect(<span class="string">"write register failed"</span>);</code></pre></div>
<p><a href="https://lupyuen.github.io/articles/rust2#set-registers">(More about this)</a></p>
<p>Then we <strong>transmit a LoRa Message</strong> over the airwaves…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Send a LoRa message
</span>lora.write_bytes(
<span class="kw-2">&amp;mut </span>spi1, <span class="comment">// SPI Interface
</span>delay, <span class="comment">// Delay Interface
</span><span class="string">b"Hello from Rust on NuttX!"</span>, <span class="comment">// Payload
</span><span class="number">0</span>.into(), <span class="comment">// Disable Transmit Timeout
</span><span class="number">8</span>, <span class="comment">// Preamble Length
</span>packet::lora::LoRaCrcType::CrcOn, <span class="comment">// Enable CRC
</span>).expect(<span class="string">"send failed"</span>);</code></pre></div>
<p>Containing the <strong>Message Payload</strong></p>
<div class="example-wrap"><pre class="language-text"><code>Hello from Rust on NuttX!</code></pre></div>
<p>And were done! Well see the results in a while. But first we run through the steps to build and flash our Rusty NuttX Firmware.</p>
<h1 id="download-source-code"><a class="doc-anchor" href="#download-source-code">§</a>9 Download Source Code</h1>
<p>To run Rust on NuttX, download the modified source code for <strong>NuttX OS and NuttX Apps</strong></p>
<div class="example-wrap"><pre class="language-bash"><code>mkdir nuttx
cd nuttx
git clone --recursive --branch rusti2c https://github.com/lupyuen/nuttx nuttx
git clone --recursive --branch rusti2c https://github.com/lupyuen/nuttx-apps apps</code></pre></div>
<p>Or if we prefer to <strong>add the Rust Library and App</strong> to our NuttX Project, follow these instructions…</p>
<ol>
<li>
<p><a href="https://github.com/lupyuen/rust-nuttx"><strong>“Install Rust Library”</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/rust_test"><strong>“Install Rust Test App”</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/nuttx/tree/lorawan/drivers/rf"><strong>“Install SPI Test Driver”</strong></a></p>
</li>
</ol>
<p><a href="https://lupyuen.github.io/articles/pinedio2#appendix-bundled-features">(<strong>For PineDio Stack BL604:</strong> The Rust Library and App are already preinstalled)</a></p>
<h1 id="build-the-firmware"><a class="doc-anchor" href="#build-the-firmware">§</a>10 Build The Firmware</h1>
<p>Lets build the NuttX Firmware that contains our <strong>Rust App</strong></p>
<ol>
<li>
<p>Install the build prerequisites…</p>
<p><a href="https://lupyuen.github.io/articles/nuttx#install-prerequisites"><strong>“Install Prerequisites”</strong></a></p>
</li>
<li>
<p>Assume that we have downloaded the <strong>NuttX Source Code</strong></p>
<p><a href="https://lupyuen.github.io/articles/rust2#download-source-code"><strong>“Download Source Code”</strong></a></p>
</li>
<li>
<p>Edit the <strong>Pin Definitions</strong></p>
<div class="example-wrap"><pre class="language-text"><code>## For BL602 and BL604:
nuttx/boards/risc-v/bl602/bl602evb/include/board.h
## For ESP32: Change &quot;esp32-devkitc&quot; to our ESP32 board
nuttx/boards/xtensa/esp32/esp32-devkitc/src/esp32_gpio.c</code></pre></div>
<p>Check that the <strong>Semtech SX1262 Pins</strong> are configured correctly in <a href="https://github.com/lupyuen/nuttx/blob/lorawan/boards/risc-v/bl602/bl602evb/include/board.h#L36-L95"><strong>board.h</strong></a> or <a href="https://github.com/lupyuen/nuttx/blob/lorawan/boards/xtensa/esp32/esp32-devkitc/src/esp32_gpio.c#L43-L67"><strong>esp32_gpio.c</strong></a></p>
<p><a href="https://lupyuen.github.io/articles/expander#pin-functions">(Which pins can be used? See this)</a></p>
<p><a href="https://lupyuen.github.io/articles/sx1262#connect-sx1262-transceiver"><strong>“Connect SX1262 Transceiver”</strong></a></p>
</li>
<li>
<p>Configure the build…</p>
<div class="example-wrap"><pre class="language-bash"><code>cd nuttx
## For BL602: Configure the build for BL602
./tools/configure.sh bl602evb:nsh
## For PineDio Stack BL604: Configure the build for BL604
./tools/configure.sh bl602evb:pinedio
## For ESP32: Configure the build for ESP32.
## TODO: Change &quot;esp32-devkitc&quot; to our ESP32 board.
./tools/configure.sh esp32-devkitc:nsh
## Edit the Build Config
make menuconfig </code></pre></div></li>
<li>
<p>Enable the <strong>GPIO Driver</strong> in menuconfig…</p>
<p><a href="https://lupyuen.github.io/articles/nuttx#enable-gpio-driver"><strong>“Enable GPIO Driver”</strong></a></p>
</li>
<li>
<p>Enable the <strong>SPI Peripheral</strong>, <strong>SPI Character Driver</strong> and <strong>SPI Test Driver</strong></p>
<p><a href="https://lupyuen.github.io/articles/spi2#enable-spi"><strong>“Enable SPI”</strong></a></p>
</li>
<li>
<p>Enable <strong>GPIO and SPI Logging</strong> for easier troubleshooting…</p>
<p><a href="https://lupyuen.github.io/articles/spi2#enable-logging"><strong>“Enable Logging”</strong></a></p>
</li>
<li>
<p>Enable <strong>Stack Canaries</strong> for stack checking…</p>
<p>Check the box for <strong>“Build Setup”</strong><strong>“Debug Options”</strong><strong>“Compiler Stack Canaries”</strong></p>
</li>
<li>
<p>Enable <strong>Stack Backtrace</strong> for easier troubleshooting…</p>
<p>Check the box for <strong>“RTOS Features”</strong><strong>“Stack Backtrace”</strong></p>
<p><a href="https://lupyuen.github.io/images/lorawan3-config4.png">(See this)</a></p>
</li>
<li>
<p>Enable our <strong>Rust Library</strong></p>
<p>Check the box for <strong>“Library Routines”</strong><strong>“Rust Library”</strong></p>
</li>
<li>
<p>Enable our <strong>Rust Test App</strong></p>
<p>Check the box for <strong>“Application Configuration”</strong><strong>“Examples”</strong><strong>“Rust Test App”</strong></p>
</li>
<li>
<p>Save the configuration and exit menuconfig</p>
<p><a href="https://gist.github.com/lupyuen/2857bdc21a4bcd5bb868eae78cf44826">(See the .config for BL602 and BL604)</a></p>
</li>
<li>
<p><strong>For ESP32:</strong> Edit the function <strong>esp32_bringup</strong> in this file…</p>
<div class="example-wrap"><pre class="language-text"><code>## Change &quot;esp32-devkitc&quot; to our ESP32 board
nuttx/boards/xtensa/esp32/esp32-devkitc/src/esp32_bringup.c</code></pre></div>
<p>And call <strong>spi_test_driver_register</strong> to register our SPI Test Driver.</p>
<p><a href="https://lupyuen.github.io/articles/spi2#register-device-driver">(See this)</a></p>
</li>
<li>
<p>Build, flash and run the NuttX Firmware on BL602 or ESP32…</p>
<p><a href="https://lupyuen.github.io/articles/rust2#appendix-build-flash-and-run-nuttx"><strong>“Build, Flash and Run NuttX”</strong></a></p>
</li>
</ol>
<p><img src="https://lupyuen.github.io/images/spi2-pinedio10a.jpg" alt="PineDio Stack BL604 with Antenna" /></p>
<h1 id="run-the-firmware"><a class="doc-anchor" href="#run-the-firmware">§</a>11 Run The Firmware</h1>
<p>Were ready to run the NuttX Firmware and test our <strong>Rust App</strong>!</p>
<ol>
<li>
<p>Before testing, remember to connect the <strong>LoRa Antenna</strong>, as shown in the pic above.</p>
<p>(So we dont fry the SX1262 Transceiver as we charge up the Power Amplifier)</p>
</li>
<li>
<p>In the NuttX Shell, list the <strong>NuttX Devices</strong></p>
<div class="example-wrap"><pre class="language-bash"><code>ls /dev</code></pre></div></li>
<li>
<p>We should see…</p>
<div class="example-wrap"><pre class="language-text"><code>/dev:
gpio0
gpio1
gpio2
spi0
spitest0
...</code></pre></div>
<p>Our SPI Test Driver appears as <strong>“/dev/spitest0”</strong></p>
<p>The SX1262 Pins for Busy, Chip Select and DIO1 should appear as <strong>“/dev/gpio0”</strong> (GPIO Input), <strong>“gpio1”</strong> (GPIO Output) and <strong>“gpio2”</strong> (GPIO Interrupt) respectively.</p>
</li>
<li>
<p>In the NuttX Shell, run our <strong>Rust App</strong></p>
<div class="example-wrap"><pre class="language-bash"><code>rust_test</code></pre></div></li>
<li>
<p>We should see Rust on NuttX <strong>transmitting our LoRa Message</strong></p>
<div class="example-wrap"><pre class="language-text"><code>Sending LoRa message...
Frequency: 923000000
...
Done!</code></pre></div>
<p><a href="https://gist.github.com/lupyuen/412cc8bef51c40236767e10693c738b5">(See the Output Log)</a></p>
</li>
</ol>
<p>Lets check whether Rust on NuttX has successfully transmitted our LoRa Message.</p>
<p><img src="https://lupyuen.github.io/images/sx1262-title.jpg" alt="PineDio Stack BL604 RISC-V Board with onboard Semtech SX1262 LoRa Transceiver (left)… Sniffed wirelessly with Airspy R2 Software Defined Radio (right)" /></p>
<p><em>PineDio Stack BL604 RISC-V Board with onboard Semtech SX1262 LoRa Transceiver (left)… Sniffed wirelessly with Airspy R2 Software Defined Radio (right)</em></p>
<h1 id="verify-lora-message"><a class="doc-anchor" href="#verify-lora-message">§</a>12 Verify LoRa Message</h1>
<p><em>Did Rust on NuttX transmit our LoRa Message successfully?</em></p>
<p>Lets verify the LoRa Transmission in two ways…</p>
<ol>
<li>
<p>With a <strong>Spectrum Analyser</strong></p>
</li>
<li>
<p>With a <strong>LoRa Receiver</strong></p>
</li>
</ol>
<h2 id="spectrum-analyser"><a class="doc-anchor" href="#spectrum-analyser">§</a>12.1 Spectrum Analyser</h2>
<p>We use a <strong>Spectrum Analyser</strong> (like Airspy R2, pic above) to sniff the airwaves…</p>
<p><img src="https://lupyuen.github.io/images/rust2-chirp2.jpg" alt="LoRa Chirp recorded by Cubic SDR connected to Airspy R2 SDR" /></p>
<p>This shows that our LoRa Message was transmitted…</p>
<ol>
<li>
<p>At the right <strong>Radio Frequency</strong></p>
<p>(923 MHz)</p>
</li>
<li>
<p>With <strong>sufficient power</strong></p>
<p>(Because of the red bar)</p>
</li>
</ol>
<p>LoRa Messages have a characteristic criss-cross shape known as <strong>LoRa Chirp</strong>. More about this…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/lora#visualise-lora-with-software-defined-radio"><strong>“Visualise LoRa with Software Defined Radio”</strong></a></li>
</ul>
<p><img src="https://lupyuen.github.io/images/wisblock-title.jpg" alt="RAKwireless WisBlock LPWAN Module mounted on WisBlock Base Board" /></p>
<h2 id="lora-receiver"><a class="doc-anchor" href="#lora-receiver">§</a>12.2 LoRa Receiver</h2>
<p>Next we use <strong>RAKwireless WisBlock</strong> (pic above) as a LoRa Receiver. We run this Arduino code on WisBlock…</p>
<ul>
<li><a href="https://github.com/lupyuen/wisblock-lora-receiver"><strong>wisblock-lora-receiver</strong></a></li>
</ul>
<p>Check that the <strong>LoRa Parameters</strong> are correct…</p>
<ul>
<li><a href="https://github.com/lupyuen/wisblock-lora-receiver/blob/main/src/main.cpp#L37-L56"><strong>LoRa Parameters for WisBlock Receiver</strong></a></li>
</ul>
<p>In the NuttX Shell, enter this to transmit a LoRa Message…</p>
<div class="example-wrap"><pre class="language-bash"><code>rust_test</code></pre></div>
<p>On WisBlock we should see the received <strong>LoRa Message</strong></p>
<p><img src="https://lupyuen.github.io/images/rust2-receive.png" alt="RAKwireless WisBlock receives LoRa Message from Rust on NuttX" /></p>
<p>Which is ASCII for…</p>
<div class="example-wrap"><pre class="language-text"><code>Hello from Rust on NuttX!</code></pre></div>
<p>Our SX1262 Rust Driver has successfully transmitted a LoRa Message to RAKwireless WisBlock!</p>
<p><img src="https://lupyuen.github.io/images/lorawan3-title.jpg" alt="PineDio Stack BL604 RISC-V Board (left) talking LoRaWAN to RAKwireless WisGate LoRaWAN Gateway (right)" /></p>
<p><em>PineDio Stack BL604 RISC-V Board (left) talking LoRaWAN to RAKwireless WisGate LoRaWAN Gateway (right)</em></p>
<h1 id="lorawan-support"><a class="doc-anchor" href="#lorawan-support">§</a>13 LoRaWAN Support</h1>
<p><em>What about LoRaWAN on Rust?</em></p>
<p>We need LoRaWAN if we wish to <strong>route LoRa Packets securely</strong> to a Local Area Network (ChirpStack) or to the internet (The Things Network).</p>
<p>Sadly we <strong>havent found a Complete LoRaWAN Stack</strong> for Rust yet.</p>
<p>(Probably because LoRaWAN is super complex… We need to sync up the Regional Parameters with the LoRaWAN Spec whenever LoRaWAN Regions are added or modified)</p>
<p>But we have a <strong>working LoRaWAN Stack for NuttX</strong> (in C) thats based on the official LoRaWAN Stack by Semtech…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/lorawan3"><strong>“LoRaWAN on Apache NuttX OS”</strong></a></li>
</ul>
<p>So perhaps our Rust code could <strong>call out to the LoRaWAN Stack</strong> in C and interoperate.</p>
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>14 Whats Next</h1>
<p>In the next article well talk about <strong>Rust and I2C</strong> on NuttX…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/rusti2c"><strong>“Rust talks I2C on Apache NuttX RTOS”</strong></a></li>
</ul>
<p>If youre keen to make <strong>Rust on NuttX</strong> better, please lemme know! 🙏</p>
<p>Many Thanks to my <a href="https://lupyuen.github.io/articles/sponsor"><strong>GitHub Sponsors</strong></a> for supporting my work! This article wouldnt have been possible without your support.</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/sponsor">Sponsor me a coffee</a></p>
</li>
<li>
<p><a href="https://www.reddit.com/r/rust/comments/s1qojy/rust_on_apache_nuttx_os/">Discuss this article on Reddit</a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/book">Read “The RISC-V BL602 / BL604 Book”</a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io">Check out my articles</a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/rss.xml">RSS Feed</a></p>
</li>
</ul>
<p><em>Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…</em></p>
<p><a href="https://github.com/lupyuen/lupyuen.github.io/blob/master/src/rust2.md"><code>lupyuen.github.io/src/rust2.md</code></a></p>
<h1 id="notes"><a class="doc-anchor" href="#notes">§</a>15 Notes</h1>
<ol>
<li>
<p>This article is the expanded version of <a href="https://twitter.com/MisterTechBlog/status/1478959963930169345">this Twitter Thread</a></p>
</li>
<li>
<p>This article was inspired by Huang Qis Rust Wrapper for NuttX…</p>
<p><a href="https://github.com/no1wudi/nuttx.rs"><strong>no1wudi/nuttx.rs</strong></a></p>
<p>Which has many features that will be very useful for our implementation of Rust Embedded HAL.</p>
</li>
<li>
<p>Since NuttX behaves like Linux, can we use the <a href="https://crates.io/crates/libc"><strong><code>libc</code></strong></a> crate to import the POSIX Functions?</p>
<p>Possibly, if we extend <code>libc</code> to cover NuttX.</p>
<p>Note that the Function Signatures are slightly different: <code>libc</code> declares <strong>ioctl</strong> as…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="kw">fn </span>ioctl(fd: i32, request: u64, ...) -&gt; i32</code></pre></div>
<p><a href="https://docs.rs/libc/latest/libc/fn.ioctl.html">(Source)</a></p>
<p>Whereas NuttX declares <strong>ioctl</strong> as…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="kw">fn </span>ioctl(fd: i32, request: i32, ...) -&gt; i32</code></pre></div>
<p><a href="https://github.com/apache/nuttx/blob/master/include/sys/ioctl.h#L114">(Source)</a></p>
<p>The type of the <strong>request</strong> parameter is different: <strong><code>u64</code> vs <code>i32</code></strong>.</p>
<p>So beware!</p>
</li>
<li>
<p>What about the <a href="https://crates.io/crates/nix"><strong><code>nix</code></strong></a> crate?</p>
<p><code>nix</code> doesnt support <code>no_std</code> yet, so sorry nope.</p>
<p><a href="https://github.com/nix-rust/nix/issues/281">(See this)</a></p>
</li>
<li>
<p>Instead of <code>no_std</code>, can we run the Standard Rust Library on NuttX?</p>
<p>Sony worked on porting Standard Rust Library to NuttX, but it appears to be incomplete.</p>
<p><a href="https://speakerdeck.com/sgy/cortex-m4f-and-prototyping-a-simple-web-server">(See this)</a></p>
</li>
</ol>
<p><img src="https://lupyuen.github.io/images/rust2-hal3.png" alt="GPIO HAL" /></p>
<h1 id="appendix-rust-embedded-hal-for-nuttx"><a class="doc-anchor" href="#appendix-rust-embedded-hal-for-nuttx">§</a>16 Appendix: Rust Embedded HAL for NuttX</h1>
<p>This section explains how we implemented the <strong>Rust Embedded HAL for NuttX</strong></p>
<ul>
<li>
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal"><strong>lupyuen/nuttx-embedded-hal</strong></a></p>
</li>
<li>
<p><a href="https://docs.rs/nuttx-embedded-hal"><strong>Documentation for nutt-embedded-hal</strong></a></p>
</li>
</ul>
<h2 id="gpio-hal"><a class="doc-anchor" href="#gpio-hal">§</a>16.1 GPIO HAL</h2>
<p>Lets look at the HAL for <strong>GPIO Output</strong> (OutputPin), since GPIO Input (InputPin) and GPIO Interrupt (InterruptPin) are implemented the same way.</p>
<p>Our <strong>OutputPin Struct</strong> contains a <strong>NuttX File Descriptor</strong>: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L479-L485">nuttx-embedded-hal/src/hal.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX GPIO Output Struct
</span><span class="kw">pub struct </span>OutputPin {
<span class="doccomment">/// NuttX File Descriptor
</span>fd: i32,
}</code></pre></div>
<p>We set the File Descriptor when we <strong>create the OutputPin</strong>: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L381-L395">nuttx-embedded-hal/src/hal.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of GPIO Output
</span><span class="kw">impl </span>OutputPin {
<span class="doccomment">/// Create a GPIO Output Pin from a Device Path (e.g. "/dev/gpio1")
</span><span class="kw">pub fn </span>new(path: <span class="kw-2">&amp;</span>str) -&gt; <span class="prelude-ty">Result</span>&lt;<span class="self">Self</span>, i32&gt; {
<span class="comment">// Open the NuttX Device Path (e.g. "/dev/gpio1") for read-write
</span><span class="kw">let </span>fd = open(path, O_RDWR);
<span class="kw">if </span>fd &lt; <span class="number">0 </span>{ <span class="kw">return </span><span class="prelude-val">Err</span>(fd) }
<span class="comment">// Return the pin
</span><span class="prelude-val">Ok</span>(<span class="self">Self </span>{ fd })
}
}</code></pre></div>
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L498-L522">(<strong>open</strong> is defined here)</a></p>
<p>To set the OutputPin High or Low, we call <strong>ioctl</strong> on the File Descriptor: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L201-L225">nuttx-embedded-hal/src/hal.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of GPIO Output
</span><span class="kw">impl </span>v2::OutputPin <span class="kw">for </span>OutputPin {
<span class="doccomment">/// Error Type
</span><span class="kw">type </span>Error = i32;
<span class="doccomment">/// Set the GPIO Output to High
</span><span class="kw">fn </span>set_high(<span class="kw-2">&amp;mut </span><span class="self">self</span>) -&gt; <span class="prelude-ty">Result</span>&lt;(), <span class="self">Self</span>::Error&gt; {
<span class="kw">let </span>ret = <span class="kw">unsafe </span>{
ioctl(<span class="self">self</span>.fd, GPIOC_WRITE, <span class="number">1</span>)
};
<span class="macro">assert!</span>(ret &gt;= <span class="number">0</span>);
<span class="prelude-val">Ok</span>(())
}
<span class="doccomment">/// Set the GPIO Output to low
</span><span class="kw">fn </span>set_low(<span class="kw-2">&amp;mut </span><span class="self">self</span>) -&gt; <span class="prelude-ty">Result</span>&lt;(), <span class="self">Self</span>::Error&gt; {
<span class="kw">let </span>ret = <span class="kw">unsafe </span>{
ioctl(<span class="self">self</span>.fd, GPIOC_WRITE, <span class="number">0</span>)
};
<span class="macro">assert!</span>(ret &gt;= <span class="number">0</span>);
<span class="prelude-val">Ok</span>(())
}
}</code></pre></div>
<p>When were done with OutputPin, we <strong>close the File Descriptor</strong>: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L443-L451">nuttx-embedded-hal/src/hal.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of GPIO Output
</span><span class="kw">impl </span>Drop <span class="kw">for </span>OutputPin {
<span class="doccomment">/// Close the GPIO Output
</span><span class="kw">fn </span>drop(<span class="kw-2">&amp;mut </span><span class="self">self</span>) {
<span class="kw">unsafe </span>{ close(<span class="self">self</span>.fd) };
}
}</code></pre></div>
<p>Check out the GPIO demo and docs…</p>
<ul>
<li>
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal#gpio-output"><strong>GPIO Demo</strong></a></p>
</li>
<li>
<p><a href="https://docs.rs/nuttx-embedded-hal/latest/nuttx_embedded_hal/struct.OutputPin.html"><strong>GPIO Output Docs</strong></a></p>
</li>
<li>
<p><a href="https://docs.rs/nuttx-embedded-hal/latest/nuttx_embedded_hal/struct.InputPin.html"><strong>GPIO Input Docs</strong></a></p>
</li>
<li>
<p><a href="https://docs.rs/nuttx-embedded-hal/latest/nuttx_embedded_hal/struct.InterruptPin.html"><strong>GPIO Interrupt Docs</strong></a></p>
</li>
</ul>
<p><img src="https://lupyuen.github.io/images/rust2-hal4.png" alt="SPI HAL" /></p>
<h2 id="spi-hal"><a class="doc-anchor" href="#spi-hal">§</a>16.2 SPI HAL</h2>
<p>Now we study the <strong>SPI HAL</strong> for NuttX.</p>
<p>Our <strong>Spi Struct</strong> also contains a <strong>File Descriptor</strong>: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L353-L473">nuttx-embedded-hal/src/hal.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX SPI Struct
</span><span class="kw">pub struct </span>Spi {
<span class="doccomment">/// NuttX File Descriptor
</span>fd: i32,
}
<span class="doccomment">/// NuttX Implementation of SPI Bus
</span><span class="kw">impl </span>Spi {
<span class="doccomment">/// Create an SPI Bus from a Device Path (e.g. "/dev/spitest0")
</span><span class="kw">pub fn </span>new(path: <span class="kw-2">&amp;</span>str) -&gt; <span class="prelude-ty">Result</span>&lt;<span class="self">Self</span>, i32&gt; {
<span class="comment">// Open the NuttX Device Path (e.g. "/dev/spitest0") for read-write
</span><span class="kw">let </span>fd = open(path, O_RDWR);
<span class="kw">if </span>fd &lt; <span class="number">0 </span>{ <span class="kw">return </span><span class="prelude-val">Err</span>(fd) }
<span class="comment">// Return the SPI Bus
</span><span class="prelude-val">Ok</span>(<span class="self">Self </span>{ fd })
}
}
<span class="doccomment">/// NuttX Implementation of SPI Bus
</span><span class="kw">impl </span>Drop <span class="kw">for </span>Spi {
<span class="doccomment">/// Close the SPI Bus
</span><span class="kw">fn </span>drop(<span class="kw-2">&amp;mut </span><span class="self">self</span>) {
<span class="kw">unsafe </span>{ close(<span class="self">self</span>.fd) };
}
}</code></pre></div>
<p>We <strong>open and close</strong> the File Descriptor the same way as OutputPin.</p>
<p>To do SPI Write, we <strong>write to the File Descriptor</strong>: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L185-L201">nuttx-embedded-hal/src/hal.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of SPI Write
</span><span class="kw">impl </span>spi::Write&lt;u8&gt; <span class="kw">for </span>Spi{
<span class="doccomment">/// Error Type
</span><span class="kw">type </span>Error = i32;
<span class="doccomment">/// Write SPI data
</span><span class="kw">fn </span>write(<span class="kw-2">&amp;mut </span><span class="self">self</span>, words: <span class="kw-2">&amp;</span>[u8]) -&gt; <span class="prelude-ty">Result</span>&lt;(), <span class="self">Self</span>::Error&gt; {
<span class="comment">// Transmit data
</span><span class="kw">let </span>bytes_written = <span class="kw">unsafe </span>{
write(<span class="self">self</span>.fd, words.as_ptr(), words.len() <span class="kw">as </span>u32)
};
<span class="macro">assert_eq!</span>(bytes_written, words.len() <span class="kw">as </span>i32);
<span class="prelude-val">Ok</span>(())
}
}</code></pre></div>
<p>SPI Transfer works the same way, except that we also <strong>copy the SPI Response</strong> and return it to the caller: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L161-L185">nuttx-embedded-hal/src/hal.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of SPI Transfer
</span><span class="kw">impl </span>spi::Transfer&lt;u8&gt; <span class="kw">for </span>Spi {
<span class="doccomment">/// Error Type
</span><span class="kw">type </span>Error = i32;
<span class="doccomment">/// Transfer SPI data
</span><span class="kw">fn </span>transfer&lt;<span class="lifetime">'w</span>&gt;(<span class="kw-2">&amp;mut </span><span class="self">self</span>, words: <span class="kw-2">&amp;</span><span class="lifetime">'w </span><span class="kw-2">mut </span>[u8]) -&gt; <span class="prelude-ty">Result</span>&lt;<span class="kw-2">&amp;</span><span class="lifetime">'w </span>[u8], <span class="self">Self</span>::Error&gt; {
<span class="comment">// Transmit data
</span><span class="kw">let </span>bytes_written = <span class="kw">unsafe </span>{
write(<span class="self">self</span>.fd, words.as_ptr(), words.len() <span class="kw">as </span>u32)
};
<span class="macro">assert_eq!</span>(bytes_written, words.len() <span class="kw">as </span>i32);
<span class="comment">// Read response
</span><span class="kw">let </span>bytes_read = <span class="kw">unsafe </span>{
read(<span class="self">self</span>.fd, words.as_mut_ptr(), words.len() <span class="kw">as </span>u32)
};
<span class="macro">assert_eq!</span>(bytes_read, words.len() <span class="kw">as </span>i32);
<span class="comment">// Return response
</span><span class="prelude-val">Ok</span>(words)
}
}</code></pre></div>
<p>Check out the SPI demo and docs…</p>
<ul>
<li>
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal#spi"><strong>SPI Demo</strong></a></p>
</li>
<li>
<p><a href="https://docs.rs/nuttx-embedded-hal/latest/nuttx_embedded_hal/struct.Spi.html"><strong>SPI Docs</strong></a></p>
</li>
</ul>
<h2 id="i2c-hal"><a class="doc-anchor" href="#i2c-hal">§</a>16.3 I2C HAL</h2>
<p>The implementation of I2C HAL for NuttX is described here…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/rusti2c#nuttx-embedded-hal"><strong>“NuttX Embedded HAL (I2C)”</strong></a></li>
</ul>
<p>Check out the I2C demo and docs…</p>
<ul>
<li>
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal#i2c"><strong>I2C Demo</strong></a></p>
</li>
<li>
<p><a href="https://docs.rs/nuttx-embedded-hal/latest/nuttx_embedded_hal/struct.I2c.html"><strong>I2C Docs</strong></a></p>
</li>
</ul>
<h2 id="delay-hal"><a class="doc-anchor" href="#delay-hal">§</a>16.4 Delay HAL</h2>
<p>We have also implemented the Delay HAL for NuttX…</p>
<ul>
<li>
<p><a href="https://docs.rs/nuttx-embedded-hal/latest/nuttx_embedded_hal/struct.Delay.html"><strong>Delay Docs</strong></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal#delay"><strong>Delay Demo</strong></a></p>
</li>
</ul>
<p><img src="https://lupyuen.github.io/images/rust2-driver.png" alt="Fixing SX1262 Driver for NuttX" /></p>
<h1 id="appendix-fix-sx1262-driver-for-nuttx"><a class="doc-anchor" href="#appendix-fix-sx1262-driver-for-nuttx">§</a>17 Appendix: Fix SX1262 Driver for NuttX</h1>
<p>In this article we used this Rust Embedded Driver for Semtech SX1262…</p>
<ul>
<li><a href="https://github.com/lupyuen/sx126x-rs-nuttx"><strong>lupyuen/sx126x-rs-nuttx</strong></a></li>
</ul>
<p>That we tweaked slightly from…</p>
<ul>
<li><a href="https://github.com/tweedegolf/sx126x-rs"><strong>tweedegolf/sx126x-rs</strong></a></li>
</ul>
<p>(Thanks Tweede golf! 👍)</p>
<p>Lets look at the modifications that we made.</p>
<p><img src="https://lupyuen.github.io/images/rust2-hal6.png" alt="SPI Transfers in small chunks" /></p>
<h2 id="merge-spi-requests"><a class="doc-anchor" href="#merge-spi-requests">§</a>17.1 Merge SPI Requests</h2>
<p>While testing <a href="https://github.com/tweedegolf/sx126x-rs"><strong>sx126x-rs</strong></a>, we discovered that the SPI Requests were split into <strong>1-byte or 2-byte chunks</strong>. (Pic above)</p>
<p>This fails on NuttX because the SPI Request needs to be in <strong>one contiguous block</strong> as Chip Select flips from High to Low and High.</p>
<p>To fix this, we buffer all SPI Requests in the Chip Select Guard: <a href="https://github.com/lupyuen/sx126x-rs-nuttx/blob/master/src/sx/slave_select.rs#L86-L126">sx126x-rs-nuttx/src/sx/slave_select.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="kw">impl</span>&lt;<span class="lifetime">'nss</span>, <span class="lifetime">'spi</span>, TNSS, TSPI, TSPIERR&gt; Transfer&lt;u8&gt; <span class="kw">for </span>SlaveSelectGuard&lt;<span class="lifetime">'nss</span>, <span class="lifetime">'spi</span>, TNSS, TSPI&gt;
<span class="kw">where
</span>TNSS: OutputPin,
TSPI: Write&lt;u8, Error = TSPIERR&gt; + Transfer&lt;u8, Error = TSPIERR&gt;,
{
<span class="kw">type </span>Error = SpiError&lt;TSPIERR&gt;;
<span class="kw">fn </span>transfer&lt;<span class="lifetime">'w</span>&gt;(<span class="kw-2">&amp;mut </span><span class="self">self</span>, words: <span class="kw-2">&amp;</span><span class="lifetime">'w </span><span class="kw-2">mut </span>[u8]) -&gt; <span class="prelude-ty">Result</span>&lt;<span class="kw-2">&amp;</span><span class="lifetime">'w </span>[u8], <span class="self">Self</span>::Error&gt; {
<span class="kw">unsafe </span>{
<span class="comment">// Prevent a second transfer
</span><span class="macro">debug_assert!</span>(!TRANSFERRED);
<span class="comment">// Copy the transmit data to the buffer
</span>BUF[BUFLEN..(BUFLEN + words.len())]
.clone_from_slice(words);
BUFLEN += words.len();
<span class="comment">// Transfer the data over SPI
</span><span class="kw">let </span>res = <span class="self">self</span>.spi.transfer(<span class="kw-2">&amp;mut </span>BUF[<span class="number">0</span>..BUFLEN])
.map_err(SpiError::Transfer);
<span class="comment">// Copy the result from SPI
</span>words.clone_from_slice(<span class="kw-2">&amp;</span>BUF[BUFLEN - words.len()..BUFLEN]);
<span class="comment">// Empty the buffer
</span>BUFLEN = <span class="number">0</span>;
<span class="comment">// Prevent a second write or transfer
</span>TRANSFERRED = <span class="bool-val">true</span>;
res
}
}
}
<span class="doccomment">/// Buffer for SPI Transfer. Max packet size (256) + 2 bytes for Write Buffer Command.
</span><span class="kw">static </span><span class="kw-2">mut </span>BUF: [ u8; <span class="number">258 </span>] = [ <span class="number">0</span>; <span class="number">258 </span>];
<span class="doccomment">/// Length of buffer for SPI Transfer
</span><span class="kw">static </span><span class="kw-2">mut </span>BUFLEN: usize = <span class="number">0</span>;
<span class="doccomment">/// True if we have just executed an SPI Transfer
</span><span class="kw">static </span><span class="kw-2">mut </span>TRANSFERRED: bool = <span class="bool-val">false</span>;</code></pre></div>
<p>Then we patched the driver code to ensure that all SPI Request chains consist of…</p>
<ul>
<li>
<p>0 or more SPI Writes</p>
</li>
<li>
<p>Followed by 1 optional SPI Transfer</p>
</li>
</ul>
<p>Such that we flush the buffer of SPI Requests only after the final SPI Write or final SPI Transfer.</p>
<p>So this chain of SPI Requests…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code>spi.transfer(<span class="kw-2">&amp;mut </span>[<span class="number">0x1D</span>])
.and_then(|<span class="kw">_</span>| spi.transfer(<span class="kw-2">&amp;mut </span>start_addr))
.and_then(|<span class="kw">_</span>| spi.transfer(<span class="kw-2">&amp;mut </span>[<span class="number">0x00</span>]))
.and_then(|<span class="kw">_</span>| spi.transfer(result))<span class="question-mark">?</span>;</code></pre></div>
<p>After patching becomes…</p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code>spi.write(<span class="kw-2">&amp;</span>[<span class="number">0x1D</span>]) <span class="comment">// Changed from `transfer` to `write`
</span>.and_then(|<span class="kw">_</span>| spi.write(<span class="kw-2">&amp;</span>start_addr)) <span class="comment">// Changed from `transfer` to `write`
</span>.and_then(|<span class="kw">_</span>| spi.write(<span class="kw-2">&amp;</span>[<span class="number">0x00</span>])) <span class="comment">// Changed from `transfer` to `write`
</span>.and_then(|<span class="kw">_</span>| spi.transfer(result))<span class="question-mark">?</span>; <span class="comment">// Final transfer is OK</span></code></pre></div>
<p><a href="https://github.com/lupyuen/sx126x-rs-nuttx/blob/master/src/sx/mod.rs#L241-L244">(Source)</a></p>
<p>The driver works OK on NuttX after merging the SPI Requests…</p>
<p><img src="https://lupyuen.github.io/images/rust2-driver2.png" alt="SPI Transfers after merging" /></p>
<h2 id="read-register"><a class="doc-anchor" href="#read-register">§</a>17.2 Read Register</h2>
<p>We inserted a null byte for the Read Register command, because Read Requests should have minimum 5 bytes (instead of 4): <a href="https://github.com/lupyuen/sx126x-rs-nuttx/blob/master/src/sx/mod.rs#L229-L246">sx126x-rs-nuttx/src/sx/mod.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Read data from a register
</span><span class="kw">pub fn </span>read_register&lt;<span class="lifetime">'spi</span>&gt;(
<span class="kw-2">&amp;</span><span class="lifetime">'spi </span><span class="kw-2">mut </span><span class="self">self</span>,
spi: <span class="kw-2">&amp;</span><span class="lifetime">'spi </span><span class="kw-2">mut </span>TSPI,
delay: <span class="kw-2">&amp;mut </span><span class="kw">impl </span>DelayUs&lt;u32&gt;,
start_addr: u16,
result: <span class="kw-2">&amp;mut </span>[u8],
) -&gt; <span class="prelude-ty">Result</span>&lt;(), SxError&lt;TSPIERR, TPINERR&gt;&gt; {
<span class="macro">debug_assert!</span>(result.len() &gt;= <span class="number">1</span>);
<span class="kw">let </span>start_addr = start_addr.to_be_bytes();
<span class="kw">let </span><span class="kw-2">mut </span>spi = <span class="self">self</span>.slave_select(spi, delay)<span class="question-mark">?</span>;
spi.write(<span class="kw-2">&amp;</span>[<span class="number">0x1D</span>])
.and_then(|<span class="kw">_</span>| spi.write(<span class="kw-2">&amp;</span>start_addr))
<span class="comment">// Inserted this null byte
</span>.and_then(|<span class="kw">_</span>| spi.write(<span class="kw-2">&amp;</span>[<span class="number">0x00</span>]))
.and_then(|<span class="kw">_</span>| spi.transfer(result))<span class="question-mark">?</span>;
<span class="prelude-val">Ok</span>(())
}</code></pre></div>
<h2 id="set-registers"><a class="doc-anchor" href="#set-registers">§</a>17.3 Set Registers</h2>
<p>The following registers need to be set for the LoRa Transmission to work correctly: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/sx1262.rs#L73-L91">rust_test/rust/src/sx1262.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Write SX1262 Registers to prepare for transmitting LoRa message.
// Based on https://gist.github.com/lupyuen/5fdede131ad0e327478994872f190668
// and https://docs.google.com/spreadsheets/d/14Pczf2sP_Egnzi5_nikukauL2iTKA03Qgq715e50__0/edit?usp=sharing
// Write Register 0x889: 0x04 (TxModulation)
</span>lora.write_register(<span class="kw-2">&amp;mut </span>spi1, delay, Register::TxModulaton, <span class="kw-2">&amp;</span>[<span class="number">0x04</span>])
.expect(<span class="string">"write register failed"</span>);
<span class="comment">// Write Register 0x8D8: 0xFE (TxClampConfig)
</span>lora.write_register(<span class="kw-2">&amp;mut </span>spi1, delay, Register::TxClampConfig, <span class="kw-2">&amp;</span>[<span class="number">0xFE</span>])
.expect(<span class="string">"write register failed"</span>);
<span class="comment">// Write Register 0x8E7: 0x38 (Over Current Protection)
</span>lora.write_register(<span class="kw-2">&amp;mut </span>spi1, delay, Register::OcpConfiguration, <span class="kw-2">&amp;</span>[<span class="number">0x38</span>])
.expect(<span class="string">"write register failed"</span>);
<span class="comment">// Write Register 0x736: 0x0D (Inverted IQ)
</span>lora.write_register(<span class="kw-2">&amp;mut </span>spi1, delay, Register::IqPolaritySetup, <span class="kw-2">&amp;</span>[<span class="number">0x0D</span>])
.expect(<span class="string">"write register failed"</span>);</code></pre></div>
<p>We derived the registers from the log generated by the SX1262 driver in C…</p>
<ul>
<li><a href="https://gist.github.com/lupyuen/5fdede131ad0e327478994872f190668"><strong>Log from SX1262 Driver in C</strong></a></li>
</ul>
<p>And by comparing the SPI Output of the C and Rust Drivers…</p>
<ul>
<li><a href="https://docs.google.com/spreadsheets/d/14Pczf2sP_Egnzi5_nikukauL2iTKA03Qgq715e50__0/edit?usp=sharing"><strong>Compare SPI Output of C and Rust Drivers</strong></a></li>
</ul>
<p>The C Driver for SX1262 is described here…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/sx1262"><strong>“LoRa SX1262 on Apache NuttX OS”</strong></a></li>
</ul>
<h2 id="adapt-for-risc-v"><a class="doc-anchor" href="#adapt-for-risc-v">§</a>17.4 Adapt For RISC-V</h2>
<p>The <a href="https://github.com/tweedegolf/sx126x-rs"><strong>sx126x-rs</strong></a> crate depends on the <a href="https://crates.io/crates/cortex-m"><strong>cortex-m</strong></a> crate, which works only on Arm, not RISC-V (BL602).</p>
<p>We defined the following functions to fill in for the missing functions on RISC-V: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/sx1262.rs#L146-L168">rust_test/rust/src/sx1262.rs</a></p>
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Read Priority Mask Register. Missing function called by sx126x crate (Arm only, not RISC-V).
/// See https://github.com/rust-embedded/cortex-m/blob/master/src/register/primask.rs#L29
</span><span class="attr">#[cfg(not(target_arch = <span class="string">"arm"</span>))] </span><span class="comment">// If architecture is not Arm...
</span><span class="attr">#[no_mangle]
</span><span class="kw">extern </span><span class="string">"C" </span><span class="kw">fn </span>__primask_r() -&gt; u32 { <span class="number">0 </span>}
<span class="doccomment">/// Disables all interrupts. Missing function called by sx126x crate (Arm only, not RISC-V).
/// See https://github.com/rust-embedded/cortex-m/blob/master/src/interrupt.rs#L29
</span><span class="attr">#[cfg(not(target_arch = <span class="string">"arm"</span>))] </span><span class="comment">// If architecture is not Arm...
</span><span class="attr">#[no_mangle]
</span><span class="kw">extern </span><span class="string">"C" </span><span class="kw">fn </span>__cpsid() {}
<span class="doccomment">/// Enables all interrupts. Missing function called by sx126x crate (Arm only, not RISC-V).
/// See https://github.com/rust-embedded/cortex-m/blob/master/src/interrupt.rs#L39
</span><span class="attr">#[cfg(not(target_arch = <span class="string">"arm"</span>))] </span><span class="comment">// If architecture is not Arm...
</span><span class="attr">#[no_mangle]
</span><span class="kw">extern </span><span class="string">"C" </span><span class="kw">fn </span>__cpsie() {}
<span class="doccomment">/// No operation. Missing function called by sx126x crate (Arm only, not RISC-V).
/// See https://github.com/rust-embedded/cortex-m/blob/master/src/asm.rs#L35
</span><span class="attr">#[cfg(not(target_arch = <span class="string">"arm"</span>))] </span><span class="comment">// If architecture is not Arm...
</span><span class="attr">#[no_mangle]
</span><span class="kw">extern </span><span class="string">"C" </span><span class="kw">fn </span>__nop() {}</code></pre></div>
<p>We havent tested the driver for receiving LoRa Messages, we might need more fixes for NuttX on RISC-V.</p>
<p>(But then again we might not need to receive LoRa Messages if were building a simple IoT Sensor)</p>
<p><img src="https://lupyuen.github.io/images/rust2-build.png" alt="Rust Build Script for NuttX" /></p>
<h1 id="appendix-rust-build-script-for-nuttx"><a class="doc-anchor" href="#appendix-rust-build-script-for-nuttx">§</a>18 Appendix: Rust Build Script for NuttX</h1>
<p>Lets study the Build Script for Rust on NuttX…</p>
<ul>
<li><strong>Build Script</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh">apps/examples/rust_test/run.sh</a></li>
</ul>
<p>And how it compiles the following into the NuttX Firmware…</p>
<ul>
<li>
<p><strong>Rust Project</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/Cargo.toml">apps/examples/rust_test/rust/Cargo.toml</a></p>
<p>(Rust Dependencies and Build Settings)</p>
</li>
<li>
<p><strong>Rust Source File</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/rust/src/lib.rs">apps/examples/rust_test/rust/src/lib.rs</a></p>
<p>(Defines the rust_main function)</p>
</li>
<li>
<p><strong>Rust Custom Target</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/riscv32imacf-unknown-none-elf.json">apps/examples/rust_test/riscv32imacf-unknown-none-elf.json</a></p>
<p>(Custom Rust Target for BL602 and BL604)</p>
</li>
<li>
<p><strong>Stub Library</strong>: <a href="https://github.com/lupyuen/rust-nuttx">nuttx/libs/librust</a></p>
<p>(Stub Library will be replaced by the compiled Rust Project)</p>
</li>
<li>
<p><strong>Test App</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/rust_test_main.c">apps/examples/rust_test/rust_test_main.c</a></p>
<p>(Main Function that calls rust_main)</p>
</li>
</ul>
<p>See also the Build Log for Rust on NuttX…</p>
<ul>
<li><a href="https://gist.github.com/lupyuen/9bfd71f7029bb66e327f89c8a58f450d"><strong>Build Log</strong></a></li>
</ul>
<h2 id="rust-target"><a class="doc-anchor" href="#rust-target">§</a>18.1 Rust Target</h2>
<p>Our Build Script begins by defining the <strong>Rust Target</strong> for the build: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh#L28-L39">rust_test/run.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Rust target: Custom target for BL602 and BL604
## https://docs.rust-embedded.org/embedonomicon/compiler-support.html#built-in-target
## https://docs.rust-embedded.org/embedonomicon/custom-target.html
rust_build_target=$PWD/riscv32imacf-unknown-none-elf.json
rust_build_target_folder=riscv32imacf-unknown-none-elf
## Rust target: Standard target
## rust_build_target=riscv32imac-unknown-none-elf
## rust_build_target_folder=riscv32imac-unknown-none-elf</code></pre></div>
<p><strong>For BL602 and BL604:</strong> Were using the <strong>Custom Rust Target</strong> at…</p>
<p><a href="https://github.com/lupyuen/rust_test/blob/main/riscv32imacf-unknown-none-elf.json">apps/examples/rust_test/riscv32imacf-unknown-none-elf.json</a></p>
<p>This Custom Rust Target supports <strong>floating point</strong> on 32-bit RISC-V. (The standard 32-bit RISC-V target doesnt support floating point)</p>
<p><a href="https://lupyuen.github.io/articles/rust#rust-targets">(More about Custom Rust Targets)</a></p>
<p><strong>For ESP32-C3 (RISC-V)</strong>: Set “rust_build_target” and “rust_build_target_folder” to the Standard Rust Target <strong>riscv32imc-unknown-none-elf</strong></p>
<p>Then run this command to install the Rust Target…</p>
<div class="example-wrap"><pre class="language-bash"><code>rustup target add riscv32imc-unknown-none-elf</code></pre></div>
<p><a href="https://github.com/jessebraham/esp-hal/tree/main/esp32c3-hal">(See this)</a></p>
<p><strong>For ESP32 (Xtensa)</strong>: Set “rust_build_target” and “rust_build_target_folder” to the ESP32 Rust Target <strong>xtensa-esp32-none-elf</strong></p>
<p>We need to install the Rust compiler fork with Xtensa support. <a href="https://github.com/jessebraham/esp-hal/tree/main/esp32-hal">(See this)</a></p>
<h2 id="rust-build-options"><a class="doc-anchor" href="#rust-build-options">§</a>18.2 Rust Build Options</h2>
<p>Next we define the <strong>Rust Build Options</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh#L41-L48">rust_test/run.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Rust build options: Build the Rust Core Library for our custom target
rust_build_options=&quot;--target $rust_build_target -Z build-std=core&quot;</code></pre></div>
<p><strong>For BL602 and BL604:</strong> Since were using a Custom Rust Target, we need to build the Rust Core Library for our target. Thats why we need “-Z build-std=core” for the Rust Build Options…</p>
<div class="example-wrap"><pre class="language-text"><code>--target nuttx/apps/examples/rust_test/riscv32imacf-unknown-none-elf.json \
-Z build-std=core</code></pre></div>
<p><a href="https://lupyuen.github.io/articles/rust#custom-rust-target-for-bl602">(More about building Rust Core Library)</a></p>
<p><strong>For ESP32 and ESP32-C3:</strong> Since were using a Standard Rust Target, remove “-Z build-std=core” from “rust_build_options”.</p>
<p>The Rust Build Options will look like…</p>
<div class="example-wrap"><pre class="language-text"><code>--target riscv32imc-unknown-none-elf</code></pre></div><h2 id="define-libraries"><a class="doc-anchor" href="#define-libraries">§</a>18.3 Define Libraries</h2>
<p>Next we define the <strong>libraries that will be modified</strong> during the build…</p>
<ul>
<li>
<p><strong>Stub Library:</strong> <a href="https://github.com/lupyuen/rust-nuttx">nuttx/libs/librust</a></p>
<p>This is an empty NuttX C Library that will be replaced by the Compiled Rust Library</p>
</li>
<li>
<p><strong>Rust Library:</strong> <a href="https://github.com/lupyuen/rust_test/blob/main/rust">apps/examples/rust_test/rust</a></p>
<p>This is the Rust Library (compiled as a Static Library) that will overwrite the Compiled Stub Library</p>
</li>
</ul>
<p>Thats how we <strong>inject our Rust Code</strong> into the NuttX Build: We overwrite the Compiled Stub Library by the Compiled Rust Library.</p>
<p>The Stub Library is defined like so: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh#L50-L53">rust_test/run.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Location of the Stub Library. We will replace this stub by the Rust Library
## rust_app_dest will be set to ../../../nuttx/staging/librust.a
rust_app_dir=$NUTTX_PATH/staging
rust_app_dest=$rust_app_dir/librust.a</code></pre></div>
<p>The Rust Library is defined below: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh#L55-L58">rust_test/run.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Location of the compiled Rust Library
## rust_app_build will be set to rust/target/riscv32imacf-unknown-none-elf/debug/libapp.a
rust_build_dir=$PWD/rust/target/$rust_build_target_folder/$rust_build_profile
rust_app_build=$rust_build_dir/libapp.a</code></pre></div><h2 id="build-stub-library"><a class="doc-anchor" href="#build-stub-library">§</a>18.4 Build Stub Library</h2>
<p>Our script <strong>builds NuttX twice.</strong></p>
<p>For the first build, we compile <strong>NuttX with the Stub Library</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh#L76-L83">rust_test/run.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Build the firmware with the Stub Library, ignoring references to the Rust Library
pushd $NUTTX_PATH
make || echo &quot;----- Ignore undefined references to Rust Library&quot;
popd</code></pre></div>
<p>Which fails to link because <strong>rust_main</strong> is undefined. Our script ignores the error and continues.</p>
<h2 id="build-rust-library"><a class="doc-anchor" href="#build-rust-library">§</a>18.5 Build Rust Library</h2>
<p>Now we build the Rust Library: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh#L89-L94">rust_test/run.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Build the Rust Library
pushd rust
rustup default nightly
cargo clippy $rust_build_options
cargo build $rust_build_options
popd</code></pre></div>
<p>Which expands to…</p>
<div class="example-wrap"><pre class="language-bash"><code>cargo build \
--target nuttx/apps/examples/rust_test/riscv32imacf-unknown-none-elf.json \
-Z build-std=core</code></pre></div>
<p>(For BL602 and BL604)</p>
<p>This generates a <strong>Static Library</strong> at…</p>
<div class="example-wrap"><pre class="language-text"><code>apps/examples/rust_test/rust/target/riscv32imacf-unknown-none-elf/debug/libapp.a</code></pre></div>
<p>The Rust Build looks like this…</p>
<p><img src="https://lupyuen.github.io/images/rust2-hal7.png" alt="Rust builds OK" /></p>
<h2 id="replace-stub-libary-by-rust-library"><a class="doc-anchor" href="#replace-stub-libary-by-rust-library">§</a>18.6 Replace Stub Libary by Rust Library</h2>
<p>We take the Static Library (generated by the Rust Compiler) and <strong>overwrite the Stub Library</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh#L96-L99">rust_test/run.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Replace the Stub Library by the compiled Rust Library
## Stub Library: ../../../nuttx/staging/librust.a
## Rust Library: rust/target/riscv32imacf-unknown-none-elf/debug/libapp.a
cp $rust_app_build $rust_app_dest</code></pre></div>
<p>Which is located at…</p>
<div class="example-wrap"><pre class="language-text"><code>nuttx/staging/librust.a</code></pre></div><h2 id="link-rust-library-into-firmware"><a class="doc-anchor" href="#link-rust-library-into-firmware">§</a>18.7 Link Rust Library into Firmware</h2>
<p>Finally we do the <strong>second NuttX build</strong>: <a href="https://github.com/lupyuen/rust_test/blob/main/run.sh#L105-L108">rust_test/run.sh</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Link the Rust Library to the firmware
pushd $NUTTX_PATH
make
popd</code></pre></div>
<p>Which links the Rust Static Library (and <strong>rust_main</strong>) into the NuttX Firmware.</p>
<p>Our build for Rust on NuttX is complete! <strong>nuttx.bin</strong> contains our NuttX Firmware, with Rust embedded inside.</p>
<h1 id="appendix-build-flash-and-run-nuttx"><a class="doc-anchor" href="#appendix-build-flash-and-run-nuttx">§</a>19 Appendix: Build, Flash and Run NuttX</h1>
<p><em>(For BL602 and ESP32)</em></p>
<p>Below are the steps to build, flash and run NuttX on BL602 and ESP32.</p>
<p>The instructions below will work on <strong>Linux (Ubuntu)</strong>, <strong>WSL (Ubuntu)</strong> and <strong>macOS</strong>.</p>
<p><a href="https://nuttx.apache.org/docs/latest/quickstart/install.html">(Instructions for other platforms)</a></p>
<p><a href="https://popolon.org/gblog3/?p=1977&amp;lang=en">(See this for Arch Linux)</a></p>
<h2 id="build-nuttx"><a class="doc-anchor" href="#build-nuttx">§</a>19.1 Build NuttX</h2>
<p>Follow these steps to build NuttX for BL602 or ESP32…</p>
<ol>
<li>
<p>Install the build prerequisites…</p>
<p><a href="https://lupyuen.github.io/articles/nuttx#install-prerequisites"><strong>“Install Prerequisites”</strong></a></p>
</li>
<li>
<p>Install Rust from <a href="https://rustup.rs"><strong>rustup.rs</strong></a></p>
</li>
<li>
<p>Assume that we have downloaded the <strong>NuttX Source Code</strong> and configured the build…</p>
<p><a href="https://lupyuen.github.io/articles/rust2#download-source-code"><strong>“Download Source Code”</strong></a></p>
<p><a href="https://lupyuen.github.io/articles/rust2#build-the-firmware"><strong>“Build the Firmware”</strong></a></p>
</li>
<li>
<p>Edit the file…</p>
<div class="example-wrap"><pre class="language-text"><code>apps/examples/rust_test/rust/src/sx1262.rs</code></pre></div>
<p>And set the <strong>LoRa Frequency</strong></p>
<p><a href="https://lupyuen.github.io/articles/rust2#transmit-lora-message"><strong>“Transmit LoRa Message”</strong></a></p>
</li>
<li>
<p>To build NuttX with Rust, enter this…</p>
<div class="example-wrap"><pre class="language-bash"><code>pushd apps/examples/rust_test
./run.sh
popd</code></pre></div></li>
<li>
<p>We should see…</p>
<div class="example-wrap"><pre class="language-text"><code>LD: nuttx
CP: nuttx.hex
CP: nuttx.bin</code></pre></div>
<p><a href="https://gist.github.com/lupyuen/9bfd71f7029bb66e327f89c8a58f450d">(See the complete log for BL602)</a></p>
</li>
<li>
<p>Ignore the errors at the <strong>“Flash NuttX”</strong> and <strong>“Run NuttX”</strong> steps</p>
</li>
<li>
<p><strong>For WSL:</strong> Copy the <strong>NuttX Firmware</strong> to the <strong>c:\blflash</strong> directory in the Windows File System…</p>
<div class="example-wrap"><pre class="language-bash"><code>## /mnt/c/blflash refers to c:\blflash in Windows
mkdir /mnt/c/blflash
cp nuttx.bin /mnt/c/blflash</code></pre></div>
<p>For WSL we need to run <strong>blflash</strong> under plain old Windows CMD (not WSL) because it needs to access the COM port.</p>
</li>
<li>
<p>In case of problems, refer to the <strong>NuttX Docs</strong></p>
<p><a href="https://nuttx.apache.org/docs/latest/platforms/risc-v/bl602/index.html"><strong>“BL602 NuttX”</strong></a></p>
<p><a href="https://nuttx.apache.org/docs/latest/platforms/xtensa/esp32/index.html"><strong>“ESP32 NuttX”</strong></a></p>
<p><a href="https://nuttx.apache.org/docs/latest/quickstart/install.html"><strong>“Installing NuttX”</strong></a></p>
</li>
</ol>
<blockquote>
<p><img src="https://lupyuen.github.io/images/nuttx-build2.png" alt="Building NuttX" /></p>
</blockquote>
<h2 id="flash-nuttx"><a class="doc-anchor" href="#flash-nuttx">§</a>19.2 Flash NuttX</h2>
<p><strong>For ESP32:</strong> <a href="https://nuttx.apache.org/docs/latest/platforms/xtensa/esp32/index.html#flashing"><strong>See instructions here</strong></a> <a href="https://popolon.org/gblog3/?p=1977&amp;lang=en">(Also check out this article)</a></p>
<p><strong>For BL602:</strong> Follow these steps to install <strong>blflash</strong></p>
<ol>
<li>
<p><a href="https://lupyuen.github.io/articles/flash#install-rustup"><strong>“Install rustup”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/flash#download-and-build-blflash"><strong>“Download and build blflash”</strong></a></p>
</li>
</ol>
<p>We assume that our Firmware Binary File <strong>nuttx.bin</strong> has been copied to the <strong>blflash</strong> folder.</p>
<p>Set BL602 / BL604 to <strong>Flashing Mode</strong> and restart the board…</p>
<p><strong>For PineDio Stack BL604:</strong></p>
<ol>
<li>
<p>Set the <strong>GPIO 8 Jumper</strong> to <strong>High</strong> <a href="https://lupyuen.github.io/images/pinedio-high.jpg">(Like this)</a></p>
</li>
<li>
<p>Disconnect the USB cable and reconnect</p>
<p>Or use the Improvised Reset Button <a href="https://lupyuen.github.io/articles/pinedio#appendix-improvised-reset-button-for-pinedio-stack">(Heres how)</a></p>
</li>
</ol>
<p><strong>For PineCone BL602:</strong></p>
<ol>
<li>
<p>Set the <strong>PineCone Jumper (IO 8)</strong> to the <strong><code>H</code> Position</strong> <a href="https://lupyuen.github.io/images/pinecone-jumperh.jpg">(Like this)</a></p>
</li>
<li>
<p>Press the Reset Button</p>
</li>
</ol>
<p><strong>For BL10:</strong></p>
<ol>
<li>
<p>Connect BL10 to the USB port</p>
</li>
<li>
<p>Press and hold the <strong>D8 Button (GPIO 8)</strong></p>
</li>
<li>
<p>Press and release the <strong>EN Button (Reset)</strong></p>
</li>
<li>
<p>Release the D8 Button</p>
</li>
</ol>
<p><strong>For <a href="https://docs.ai-thinker.com/en/wb2">Ai-Thinker Ai-WB2</a>, Pinenut and MagicHome BL602:</strong></p>
<ol>
<li>
<p>Disconnect the board from the USB Port</p>
</li>
<li>
<p>Connect <strong>GPIO 8</strong> to <strong>3.3V</strong></p>
</li>
<li>
<p>Reconnect the board to the USB port</p>
</li>
</ol>
<p>Enter these commands to flash <strong>nuttx.bin</strong> to BL602 / BL604 over UART…</p>
<div class="example-wrap"><pre class="language-bash"><code>## For Linux: Change &quot;/dev/ttyUSB0&quot; to the BL602 / BL604 Serial Port
blflash flash nuttx.bin \
--port /dev/ttyUSB0
## For macOS: Change &quot;/dev/tty.usbserial-1410&quot; to the BL602 / BL604 Serial Port
blflash flash nuttx.bin \
--port /dev/tty.usbserial-1410 \
--initial-baud-rate 230400 \
--baud-rate 230400
## For Windows: Change &quot;COM5&quot; to the BL602 / BL604 Serial Port
blflash flash c:\blflash\nuttx.bin --port COM5</code></pre></div>
<p><a href="https://gist.github.com/lupyuen/9c0dbd75bb6b8e810939a36ffb5c399f">(See the Output Log)</a></p>
<p>For WSL: Do this under plain old Windows CMD (not WSL) because <strong>blflash</strong> needs to access the COM port.</p>
<p><a href="https://github.com/apache/nuttx/issues/4336">(Flashing WiFi apps to BL602 / BL604? Remember to use <strong>bl_rfbin</strong>)</a></p>
<p><a href="https://lupyuen.github.io/articles/flash#flash-the-firmware">(More details on flashing firmware)</a></p>
<p><img src="https://lupyuen.github.io/images/nuttx-flash2.png" alt="Flashing NuttX" /></p>
<h2 id="run-nuttx"><a class="doc-anchor" href="#run-nuttx">§</a>19.3 Run NuttX</h2>
<p><strong>For ESP32:</strong> Use Picocom to connect to ESP32 over UART…</p>
<div class="example-wrap"><pre class="language-bash"><code>picocom -b 115200 /dev/ttyUSB0</code></pre></div>
<p><a href="https://popolon.org/gblog3/?p=1977&amp;lang=en">(More about this)</a></p>
<p><strong>For BL602:</strong> Set BL602 / BL604 to <strong>Normal Mode</strong> (Non-Flashing) and restart the board…</p>
<p><strong>For PineDio Stack BL604:</strong></p>
<ol>
<li>
<p>Set the <strong>GPIO 8 Jumper</strong> to <strong>Low</strong> <a href="https://lupyuen.github.io/images/pinedio-low.jpg">(Like this)</a></p>
</li>
<li>
<p>Disconnect the USB cable and reconnect</p>
<p>Or use the Improvised Reset Button <a href="https://lupyuen.github.io/articles/pinedio#appendix-improvised-reset-button-for-pinedio-stack">(Heres how)</a></p>
</li>
</ol>
<p><strong>For PineCone BL602:</strong></p>
<ol>
<li>
<p>Set the <strong>PineCone Jumper (IO 8)</strong> to the <strong><code>L</code> Position</strong> <a href="https://lupyuen.github.io/images/pinecone-jumperl.jpg">(Like this)</a></p>
</li>
<li>
<p>Press the Reset Button</p>
</li>
</ol>
<p><strong>For BL10:</strong></p>
<ol>
<li>Press and release the <strong>EN Button (Reset)</strong></li>
</ol>
<p><strong>For <a href="https://docs.ai-thinker.com/en/wb2">Ai-Thinker Ai-WB2</a>, Pinenut and MagicHome BL602:</strong></p>
<ol>
<li>
<p>Disconnect the board from the USB Port</p>
</li>
<li>
<p>Connect <strong>GPIO 8</strong> to <strong>GND</strong></p>
</li>
<li>
<p>Reconnect the board to the USB port</p>
</li>
</ol>
<p>After restarting, connect to BL602 / BL604s UART Port at 2 Mbps like so…</p>
<p><strong>For Linux:</strong></p>
<div class="example-wrap"><pre class="language-bash"><code>screen /dev/ttyUSB0 2000000</code></pre></div>
<p><strong>For macOS:</strong> Use CoolTerm (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p><strong>For Windows:</strong> Use <code>putty</code> (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p><strong>Alternatively:</strong> Use the Web Serial Terminal (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
<p>Press Enter to reveal the <strong>NuttX Shell</strong></p>
<div class="example-wrap"><pre class="language-text"><code>NuttShell (NSH) NuttX-10.2.0-RC0
nsh&gt;</code></pre></div>
<p>Congratulations NuttX is now running on BL602 / BL604!</p>
<p><a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">(More details on connecting to BL602 / BL604)</a></p>
<p><img src="https://lupyuen.github.io/images/nuttx-boot2.png" alt="Running NuttX" /></p>
<p><img src="https://lupyuen.github.io/images/rust2-pinedio.jpg" alt="Loads of fun with Rust, NuttX and LoRa on PineDio Stack BL604" /></p>
<p><em>Loads of fun with Rust, NuttX and LoRa on PineDio Stack BL604</em></p>
<!-- Begin scripts/rustdoc-after.html: Post-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
<!-- Begin Theme Picker and Prism Theme -->
<script src="../theme.js"></script>
<script src="../prism.js"></script>
<!-- Theme Picker and Prism Theme -->
<!-- End scripts/rustdoc-after.html -->
</body>
</html>