mirror of
https://github.com/lupyuen/lupyuen.github.io.git
synced 2025-01-13 09:08:30 +08:00
1095 lines
No EOL
69 KiB
HTML
1095 lines
No EOL
69 KiB
HTML
<!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 talks I2C on Apache NuttX RTOS</title>
|
||
|
||
|
||
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
|
||
<meta property="og:title"
|
||
content="Rust talks I2C on Apache NuttX RTOS"
|
||
data-rh="true">
|
||
<meta property="og:description"
|
||
content="Reading the Bosch BME280 I2C Sensor with Rust Embedded HAL... On BL602 RISC-V SoC and Apache NuttX RTOS"
|
||
data-rh="true">
|
||
<meta property="og:image"
|
||
content="https://lupyuen.github.io/images/rusti2c-title.jpg">
|
||
<meta property="og:type"
|
||
content="article" data-rh="true">
|
||
<link rel="canonical" href="https://lupyuen.org/articles/rusti2c.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 talks I2C on Apache NuttX RTOS</h1>
|
||
<nav id="rustdoc"><ul>
|
||
<li><a href="#read-sensor-data-from-bme280" title="Read Sensor Data from BME280">1 Read Sensor Data from BME280</a><ul></ul></li>
|
||
<li><a href="#connect-bme280" title="Connect BME280">2 Connect BME280</a><ul></ul></li>
|
||
<li><a href="#run-bme280-app" title="Run BME280 App">3 Run BME280 App</a><ul></ul></li>
|
||
<li><a href="#rust-driver-for-bme280" title="Rust Driver for BME280">4 Rust Driver for BME280</a><ul></ul></li>
|
||
<li><a href="#nuttx-embedded-hal" title="NuttX Embedded HAL">5 NuttX Embedded HAL</a><ul></ul></li>
|
||
<li><a href="#read-i2c-register" title="Read I2C Register">6 Read I2C Register</a><ul></ul></li>
|
||
<li><a href="#write-i2c-register" title="Write I2C Register">7 Write I2C Register</a><ul></ul></li>
|
||
<li><a href="#whats-next" title="What’s Next">8 What’s Next</a><ul></ul></li>
|
||
<li><a href="#notes" title="Notes">9 Notes</a><ul></ul></li>
|
||
<li><a href="#appendix-read-i2c-register-in-embedded-hal" title="Appendix: Read I2C Register in Embedded HAL">10 Appendix: Read I2C Register in Embedded HAL</a><ul>
|
||
<li><a href="#nuttx-types-and-constants" title="NuttX Types and Constants">10.1 NuttX Types and Constants</a><ul></ul></li>
|
||
<li><a href="#into-embedded-hal" title="Into Embedded HAL">10.2 Into Embedded HAL</a><ul></ul></li></ul></li>
|
||
<li><a href="#appendix-write-i2c-register-in-embedded-hal" title="Appendix: Write I2C Register in Embedded HAL">11 Appendix: Write I2C Register in Embedded HAL</a><ul></ul></li>
|
||
<li><a href="#appendix-read-i2c-register-in-c" title="Appendix: Read I2C Register in C">12 Appendix: Read I2C Register in C</a><ul>
|
||
<li><a href="#c-types-and-constants" title="C Types and Constants">12.1 C Types and Constants</a><ul></ul></li></ul></li>
|
||
<li><a href="#appendix-build-flash-and-run-nuttx" title="Appendix: Build, Flash and Run NuttX">13 Appendix: Build, Flash and Run NuttX</a><ul>
|
||
<li><a href="#download-nuttx" title="Download NuttX">13.1 Download NuttX</a><ul></ul></li>
|
||
<li><a href="#configure-nuttx" title="Configure NuttX">13.2 Configure NuttX</a><ul></ul></li>
|
||
<li><a href="#configure-rust-target" title="Configure Rust Target">13.3 Configure Rust Target</a><ul></ul></li>
|
||
<li><a href="#build-nuttx" title="Build NuttX">13.4 Build NuttX</a><ul></ul></li>
|
||
<li><a href="#flash-nuttx" title="Flash NuttX">13.5 Flash NuttX</a><ul></ul></li>
|
||
<li><a href="#run-nuttx" title="Run NuttX">13.6 Run NuttX</a><ul></ul></li></ul></li></ul></nav><p>📝 <em>22 Mar 2022</em></p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-title.jpg" alt="Bosch BME280 Sensor connected to Pine64 PineCone BL602 RISC-V Board" /></p>
|
||
<p><em><a href="https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/">Bosch BME280 Sensor</a> connected to <a href="https://lupyuen.github.io/articles/pinecone">Pine64 PineCone BL602 RISC-V Board</a></em></p>
|
||
<p><a href="https://en.wikipedia.org/wiki/I%C2%B2C"><strong>I2C</strong></a> is a great way to connect all kinds of <strong>Sensor Modules</strong> when we’re creating an <strong>IoT Gadget</strong>. Like sensors for temperature, light, motion, spectroscopy, soil moisture, GPS, LIDAR, … <a href="https://www.sparkfun.com/categories/tags/i2c"><strong>and many more!</strong></a></p>
|
||
<p><em>But where will we get the Software Drivers for the I2C Sensors?</em></p>
|
||
<p><a href="https://github.com/rust-embedded/awesome-embedded-rust#driver-crates"><strong>Embedded Rust</strong></a> has a large collection of drivers for I2C Sensors. And they will work on <a href="https://github.com/rust-embedded/awesome-embedded-rust#hal-implementation-crates"><strong>many platforms!</strong></a></p>
|
||
<p>Today we shall experiment with the Rust Driver for <a href="https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/"><strong>Bosch BME280 Sensor</strong></a> (Temperature / Humdity / Air Pressure). And learn how we made it work on the (Linux-like) <a href="https://lupyuen.github.io/articles/nuttx"><strong>Apache NuttX RTOS</strong></a>.</p>
|
||
<p>We’ll run this on the <a href="https://lupyuen.github.io/articles/pinecone"><strong>BL602 RISC-V SoC</strong></a> (pic above), though it should work fine on ESP32 and other NuttX platforms.</p>
|
||
<p>Let’s dive into our <strong>Rust I2C App for NuttX</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/lupyuen/rust-i2c-nuttx"><strong>lupyuen/rust-i2c-nuttx</strong></a></li>
|
||
</ul>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-code10a.png" alt="Read Sensor Data from BME280" /></p>
|
||
<p><a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/bme280.rs">(Source)</a></p>
|
||
<h1 id="read-sensor-data-from-bme280"><a class="doc-anchor" href="#read-sensor-data-from-bme280">§</a>1 Read Sensor Data from BME280</h1>
|
||
<p>Here’s how we read the <strong>Temperature, Humidity and Air Pressure</strong> from the BME280 Sensor: <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/bme280.rs">rust/src/bme280.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Read Temperature, Pressure and Humidity from BME280 Sensor over I2C
|
||
</span><span class="kw">pub fn </span>read_bme280() {
|
||
|
||
<span class="comment">// Open I2C Port
|
||
</span><span class="kw">let </span>i2c = nuttx_embedded_hal::I2c::new(
|
||
<span class="string">"/dev/i2c0"</span>, <span class="comment">// I2C Port
|
||
</span><span class="number">400000</span>, <span class="comment">// I2C Frequency: 400 kHz
|
||
</span>).expect(<span class="string">"open failed"</span>);</code></pre></div>
|
||
<p>We begin by opening the I2C Port “<strong>/dev/i2c0</strong>”, configured for 400 kHz.</p>
|
||
<p>(This halts with an error if the I2C Port doesn’t exist)</p>
|
||
<p><em>What’s nuttx_embedded_hal?</em></p>
|
||
<p>That’s the <strong>Hardware Abstraction Layer</strong> (HAL) for NuttX, coded in Rust. (More about this in a while)</p>
|
||
<p>Next we <strong>initialise the BME280 Driver</strong>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Init the BME280 Driver
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>bme280 = bme280::BME280::new(
|
||
i2c, <span class="comment">// I2C Port
|
||
</span><span class="number">0x77</span>, <span class="comment">// I2C Address of BME280
|
||
</span>nuttx_embedded_hal::Delay <span class="comment">// Delay Interface
|
||
</span>);</code></pre></div>
|
||
<p><strong>BME280</strong> comes from the BME280 Driver Crate. (As we’ll see soon)</p>
|
||
<p>Before reading the BME280 Sensor, we <strong>initialise the sensor</strong>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Init the BME280 Sensor
|
||
</span>bme280.init()
|
||
.expect(<span class="string">"init failed"</span>);</code></pre></div>
|
||
<p>(This halts with an error if the initialisation fails)</p>
|
||
<p>We’re ready to read the <strong>Temperature, Humidity and Air Pressure</strong> from the BME280 Sensor…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Measure Temperature, Pressure and Humidity
|
||
</span><span class="kw">let </span>measurements = bme280.measure()
|
||
.expect(<span class="string">"measure failed"</span>);</code></pre></div>
|
||
<p>Finally we <strong>print the Sensor Data</strong>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Print the measurements
|
||
</span><span class="macro">println!</span>(<span class="string">"Relative Humidity = {}%"</span>,
|
||
measurements.humidity);
|
||
<span class="macro">println!</span>(<span class="string">"Temperature = {} deg C"</span>,
|
||
measurements.temperature);
|
||
<span class="macro">println!</span>(<span class="string">"Pressure = {} pascals"</span>,
|
||
measurements.pressure);
|
||
}</code></pre></div>
|
||
<p>That’s all we need to read the Sensor Data from the BME280 Sensor!</p>
|
||
<p><em>Where is println defined?</em></p>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/macros.rs"><strong>println</strong></a> comes from our NuttX Embedded HAL. We import it at the top…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Import Libraries
|
||
</span><span class="kw">use </span>nuttx_embedded_hal::{ <span class="comment">// NuttX Embedded HAL
|
||
</span>println, <span class="comment">// Print a formatted message
|
||
</span>};</code></pre></div>
|
||
<p>Before running our Rust App, let’s connect the BME280 Sensor.</p>
|
||
<p><img src="https://lupyuen.github.io/images/bme280-connect.jpg" alt="Bosch BME280 Sensor connected to Pine64 PineCone BL602 RISC-V Board" /></p>
|
||
<h1 id="connect-bme280"><a class="doc-anchor" href="#connect-bme280">§</a>2 Connect BME280</h1>
|
||
<p>We connect BME280 to Pine64’s <a href="https://lupyuen.github.io/articles/pinecone"><strong>PineCone BL602 Board</strong></a> as follows (pic above)…</p>
|
||
<div><table><thead><tr><th style="text-align: center">BL602 Pin</th><th style="text-align: center">BME280 Pin</th><th style="text-align: left">Wire Colour</th></tr></thead><tbody>
|
||
<tr><td style="text-align: center"><strong><code>GPIO 3</code></strong></td><td style="text-align: center"><code>SDA</code></td><td style="text-align: left">Green</td></tr>
|
||
<tr><td style="text-align: center"><strong><code>GPIO 4</code></strong></td><td style="text-align: center"><code>SCL</code></td><td style="text-align: left">Blue</td></tr>
|
||
<tr><td style="text-align: center"><strong><code>3V3</code></strong></td><td style="text-align: center"><code>3.3V</code></td><td style="text-align: left">Red</td></tr>
|
||
<tr><td style="text-align: center"><strong><code>GND</code></strong></td><td style="text-align: center"><code>GND</code></td><td style="text-align: left">Black</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>The <strong>I2C Pins</strong> on BL602 are defined here: <a href="https://github.com/lupyuen/nuttx/blob/rusti2c/boards/risc-v/bl602/bl602evb/include/board.h#L85-L88">board.h</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/* I2C Configuration */
|
||
#define BOARD_I2C_SCL \
|
||
(GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_I2C | \
|
||
GPIO_PIN4)
|
||
#define BOARD_I2C_SDA \
|
||
(GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_I2C | \
|
||
GPIO_PIN3)</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/expander#pin-functions">(Which pins can be used? See this)</a></p>
|
||
<p>We disabled the <strong>UART1 Port</strong> because it uses the same pins as I2C: <a href="https://github.com/lupyuen/nuttx/blob/rusti2c/boards/risc-v/bl602/bl602evb/include/board.h#L63-L68">board.h</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#ifdef TODO /* Remember to check for duplicate pins! */
|
||
#define BOARD_UART_1_RX_PIN \
|
||
(GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_UART | \
|
||
GPIO_PIN3)
|
||
#define BOARD_UART_1_TX_PIN \
|
||
(GPIO_INPUT | GPIO_PULLUP | GPIO_FUNC_UART | \
|
||
GPIO_PIN4)
|
||
#endif /* TODO */</code></pre></div>
|
||
<p>(UART0 is used by the Serial Console)</p>
|
||
<p><em>What if we’re connecting to ESP32?</em></p>
|
||
<p><strong>For ESP32:</strong> The GPIO Pin Numbers for the I2C Port (I2C0) are defined in <a href="https://github.com/lupyuen/nuttx/blob/rusti2c/arch/xtensa/src/esp32/Kconfig#L797-L805">Kconfig</a> and menuconfig…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>config ESP32_I2C0_SCLPIN
|
||
int "I2C0 SCL Pin"
|
||
default 22
|
||
range 0 39
|
||
|
||
config ESP32_I2C0_SDAPIN
|
||
int "I2C0 SDA Pin"
|
||
default 23
|
||
range 0 39</code></pre></div>
|
||
<p><em>Do we need Pull-Up Resistors?</em></p>
|
||
<p>We’re using the <a href="https://learn.sparkfun.com/tutorials/sparkfun-bme280-breakout-hookup-guide/all"><strong>SparkFun BME280 Breakout Board</strong></a>, which has <strong>Pull-Up Resistors</strong>. (So we don’t need to add our own)</p>
|
||
<blockquote>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-run2a.png" alt="Run BME280 App" /></p>
|
||
</blockquote>
|
||
<h1 id="run-bme280-app"><a class="doc-anchor" href="#run-bme280-app">§</a>3 Run BME280 App</h1>
|
||
<p>We’re ready to run our Rust App on NuttX!</p>
|
||
<ol>
|
||
<li>
|
||
<p>Follow these steps to <strong>build, flash and run NuttX</strong>…</p>
|
||
<p><a href="https://lupyuen.github.io/articles/rusti2c#appendix-build-flash-and-run-nuttx"><strong>“Build, Flash and Run NuttX”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p>At the NuttX Shell, enter this command to 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 our <strong>I2C Port</strong> that’s connected to BME280…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>/dev:
|
||
i2c0
|
||
...</code></pre></div></li>
|
||
<li>
|
||
<p>To <strong>read the BME280 Sensor</strong>, enter this command…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>rust_i2c</code></pre></div></li>
|
||
<li>
|
||
<p>We should see the <strong>Relative Humidity, Temperature and Air Pressure</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>read_bme280
|
||
Relative Humidity = 89.284164%
|
||
Temperature = 29.942907 deg C
|
||
Pressure = 100483.04 pascals
|
||
Done!</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/rust-i2c-nuttx#test-rust-driver-for-bme280">(See the complete log)</a></p>
|
||
</li>
|
||
</ol>
|
||
<p>The Rust Driver for BME280 runs successfully on NuttX!</p>
|
||
<blockquote>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-bme280.png" alt="Rust Driver for BME280" /></p>
|
||
</blockquote>
|
||
<h1 id="rust-driver-for-bme280"><a class="doc-anchor" href="#rust-driver-for-bme280">§</a>4 Rust Driver for BME280</h1>
|
||
<p><em>We ran the Rust Driver for BME280 on NuttX… Without any code changes?</em></p>
|
||
<p>Yeah amazing right? Earlier we saw this: <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/bme280.rs">rust/src/bme280.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Init the BME280 Driver
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>bme280 = bme280::BME280
|
||
::new( ... );</code></pre></div>
|
||
<p><strong>BME280</strong> comes from the Rust Embedded Driver for BME280 (pic above)…</p>
|
||
<ul>
|
||
<li><a href="https://crates.io/crates/bme280"><strong>crates.io/bme280</strong></a></li>
|
||
</ul>
|
||
<p>That we have added to our <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/Cargo.toml"><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]
|
||
|
||
## BME280 Driver: https://crates.io/crates/bme280
|
||
bme280 = "0.2.1"
|
||
|
||
## NuttX Embedded HAL: https://crates.io/crates/nuttx-embedded-hal
|
||
nuttx-embedded-hal = "1.0.10"
|
||
|
||
## Rust Embedded HAL: https://crates.io/crates/embedded-hal
|
||
embedded-hal = "0.2.7" </code></pre></div>
|
||
<p>The Rust Driver for BME280 works on NuttX because of <strong>NuttX Embedded HAL</strong>. Let’s look inside.</p>
|
||
<p><a href="https://crates.io/crates/nuttx-embedded-hal">(BTW: Always use the latest version of NuttX Embedded HAL)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-arch2.jpg" alt="NuttX Embedded HAL" /></p>
|
||
<h1 id="nuttx-embedded-hal"><a class="doc-anchor" href="#nuttx-embedded-hal">§</a>5 NuttX Embedded HAL</h1>
|
||
<p><em>What’s NuttX Embedded HAL?</em></p>
|
||
<p><strong>NuttX Embedded HAL</strong> (Hardware Abstraction Layer) is the Rust Library that exposes a Standard Rust Interface for the <strong>Input / Output Ports on NuttX</strong>: GPIO, I2C, SPI, …</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://crates.io/crates/nuttx-embedded-hal"><strong>crates.io/nuttx-embedded-hal</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://docs.rs/nuttx-embedded-hal/"><strong>Documentation for nuttx-embedded-hal</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>Earlier we called NuttX Embedded HAL to <strong>open the I2C Port</strong>: <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/bme280.rs">bme280.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Open I2C Port
|
||
</span><span class="kw">let </span>i2c = nuttx_embedded_hal::I2c::new(
|
||
<span class="string">"/dev/i2c0"</span>, <span class="comment">// I2C Port
|
||
</span><span class="number">400000</span>, <span class="comment">// I2C Frequency: 400 kHz
|
||
</span>).expect(<span class="string">"open failed"</span>);</code></pre></div>
|
||
<p>And we passed it to the BME280 Driver…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="comment">// Init the BME280 Driver
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>bme280 = bme280::BME280::new(
|
||
i2c, <span class="comment">// I2C Port
|
||
</span><span class="number">0x77</span>, <span class="comment">// I2C Address of BME280
|
||
</span>nuttx_embedded_hal::Delay <span class="comment">// Delay Interface
|
||
</span>);</code></pre></div>
|
||
<p>(<a href="https://docs.rs/nuttx-embedded-hal/latest/nuttx_embedded_hal/struct.Delay.html"><strong>Delay</strong></a> also comes from NuttX Embedded HAL)</p>
|
||
<p><em>But BME280 Driver doesn’t know anything about NuttX?</em></p>
|
||
<p>That’s OK because the BME280 Driver and NuttX Embedded HAL both talk through the same interface: <a href="https://docs.rs/embedded-hal/latest/embedded_hal/"><strong>Rust Embedded HAL</strong></a>. (Pic above)</p>
|
||
<p>Rust Embedded HAL is the standard interface used by <a href="https://github.com/rust-embedded/awesome-embedded-rust#driver-crates"><strong>Rust Embedded Drivers</strong></a> (like the BME280 Driver) to talk to the GPIO / I2C / SPI ports.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/rust2#rust-driver-for-lora-sx1262">(Rust Driver for LoRa SX1262 works on NuttX too)</a></p>
|
||
<p><em>That’s why Rust Embedded Drivers can run on many platforms?</em></p>
|
||
<p>Yep because the Rust Embedded HAL has been implemented on <a href="https://github.com/rust-embedded/awesome-embedded-rust#hal-implementation-crates"><strong>many platforms</strong></a>: Linux, FreeBSD, nRF52, STM32 Blue Pill, …</p>
|
||
<p>And now NuttX! (As NuttX Embedded HAL)</p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-code6a.png" alt="Call NuttX Embedded HAL to read I2C register" /></p>
|
||
<p><a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/test.rs#L29-L62">(Source)</a></p>
|
||
<h1 id="read-i2c-register"><a class="doc-anchor" href="#read-i2c-register">§</a>6 Read I2C Register</h1>
|
||
<p><em>Can we call NuttX Embedded HAL in our own Rust Programs?</em></p>
|
||
<p>Yes we can! This is how we call NuttX Embedded HAL to <strong>read an I2C Register</strong> on BME280: <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/test.rs#L29-L62">test.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Read an I2C Register
|
||
</span><span class="kw">pub fn </span>test_hal_read() {
|
||
|
||
<span class="comment">// Open I2C Port
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>i2c = nuttx_embedded_hal::I2c::new(
|
||
<span class="string">"/dev/i2c0"</span>, <span class="comment">// I2C Port
|
||
</span><span class="number">400000</span>, <span class="comment">// I2C Frequency: 400 kHz
|
||
</span>).expect(<span class="string">"open failed"</span>);</code></pre></div>
|
||
<p>This opens the I2C Port “<strong>/dev/i2c0</strong>” at 400 kHz. (We’ve seen this earlier)</p>
|
||
<p>Next we prepare a one-byte <strong>Receive Buffer</strong> that will receive the Register Value…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Buffer for received Register Value (1 byte)
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>buf = [<span class="number">0 </span>; <span class="number">1</span>]; <span class="comment">// Init to 0</span></code></pre></div>
|
||
<p>Then we call NuttX Embedded HAL to <strong>read Register <code>0xD0</code></strong> from I2C Address <code>0x77</code>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Read I2C Register
|
||
</span>i2c.write_read(
|
||
<span class="number">0x77</span>, <span class="comment">// I2C Address
|
||
</span><span class="kw-2">&</span>[<span class="number">0xD0</span>], <span class="comment">// Register ID
|
||
</span><span class="kw-2">&mut </span>buf <span class="comment">// Buffer to be received (Register Value)
|
||
</span>).expect(<span class="string">"read register failed"</span>);</code></pre></div>
|
||
<p>Our Receive Buffer now contains the <strong>Register Value</strong> <code>0x60</code>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Register Value must be BME280 Device ID (0x60)
|
||
</span><span class="macro">assert_eq!</span>(buf[<span class="number">0</span>], <span class="number">0x60</span>);
|
||
}</code></pre></div>
|
||
<p>That’s how we call NuttX Embedded HAL to read an I2C Register!</p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-arch.jpg" alt="NuttX Embedded HAL" /></p>
|
||
<p><em>How did we implement I2C in the NuttX Embedded HAL?</em></p>
|
||
<p>NuttX Embedded HAL accesses the I2C Port by calling the <strong>NuttX I2C Interface</strong>: open(), ioctl() and close(). (Pic above)</p>
|
||
<p>Check out the details in the Appendix…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/rusti2c#appendix-read-i2c-register-in-embedded-hal"><strong>“Read I2C Register in Embedded HAL”</strong></a></li>
|
||
</ul>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-code7a.png" alt="Write I2C Register" /></p>
|
||
<p><a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/test.rs#L64-L116">(Source)</a></p>
|
||
<h1 id="write-i2c-register"><a class="doc-anchor" href="#write-i2c-register">§</a>7 Write I2C Register</h1>
|
||
<p><em>What about writing to I2C Registers?</em></p>
|
||
<p>This code calls NuttX Embedded HAL to <strong>write an I2C Register</strong>: <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/test.rs#L64-L116">test.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Write an I2C Register
|
||
</span><span class="kw">pub fn </span>test_hal_write() {
|
||
|
||
<span class="comment">// Open I2C Port
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>i2c = nuttx_embedded_hal::I2c::new(
|
||
<span class="string">"/dev/i2c0"</span>, <span class="comment">// I2C Port
|
||
</span><span class="number">400000</span>, <span class="comment">// I2C Frequency: 400 kHz
|
||
</span>).expect(<span class="string">"open failed"</span>);
|
||
|
||
<span class="comment">// Write 0xA0 to Register 0xF5
|
||
</span>i2c.write(
|
||
<span class="number">0x77</span>, <span class="comment">// I2C Address
|
||
</span><span class="kw-2">&</span>[<span class="number">0xF5</span>, <span class="number">0xA0</span>] <span class="comment">// Register ID and value
|
||
</span>).expect(<span class="string">"write register failed"</span>);</code></pre></div>
|
||
<p>The implementation of <strong>i2c.write</strong> in NuttX Embedded HAL is explained here…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/rusti2c#appendix-write-i2c-register-in-embedded-hal"><strong>“Write I2C Register in Embedded HAL”</strong></a></li>
|
||
</ul>
|
||
<p>When we connect a <a href="https://lupyuen.github.io/images/bme280-logic2.jpg"><strong>Logic Analyser</strong></a>, we’ll see something unusual…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>Setup Write to [0xEE] + ACK
|
||
0xF5 + ACK
|
||
0xA0 + ACK
|
||
Setup Read to [0xEF] + ACK
|
||
0xA0 + NAK</code></pre></div>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-logic3a.png" alt="Write 0xA0 to Register 0xF4" /></p>
|
||
<p>There’s an <strong>extra I2C Read</strong> at the end, right after writing the Register ID <code>0xF5</code> and Register Value <code>0xA0</code>.</p>
|
||
<p>But it’s harmless. NuttX Embedded HAL does this to work around the I2C quirks on BL602…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/bme280#appendix-quirks-in-bl602-nuttx-i2c-driver"><strong>“Quirks in BL602 NuttX I2C Driver”</strong></a></li>
|
||
</ul>
|
||
<p><em>What about GPIO and SPI on NuttX Embedded HAL?</em></p>
|
||
<p>Yep they have been implemented in NuttX Embedded HAL…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal#gpio-output"><strong>NuttX GPIO</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal#spi"><strong>NuttX SPI</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal#delay"><strong>NuttX Delay</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-run1.png" alt="Rust I2C on NuttX" /></p>
|
||
</blockquote>
|
||
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>8 What’s Next</h1>
|
||
<p>I had lots of fun running Rust on NuttX, I hope you’ll enjoy it too!</p>
|
||
<p>If you’re keen to make <strong>Rust on NuttX</strong> better, or if there’s something I should port to Rust on NuttX, 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 wouldn’t 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/tj9a2s/rust_talks_i2c_on_apache_nuttx_rtos/">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/rusti2c.md"><code>lupyuen.github.io/src/rusti2c.md</code></a></p>
|
||
<h1 id="notes"><a class="doc-anchor" href="#notes">§</a>9 Notes</h1>
|
||
<ol>
|
||
<li>This article is the expanded version of <a href="https://twitter.com/MisterTechBlog/status/1502823263121989634">this Twitter Thread</a></li>
|
||
</ol>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-arch.jpg" alt="NuttX Embedded HAL" /></p>
|
||
<h1 id="appendix-read-i2c-register-in-embedded-hal"><a class="doc-anchor" href="#appendix-read-i2c-register-in-embedded-hal">§</a>10 Appendix: Read I2C Register in Embedded HAL</h1>
|
||
<p><em>How was NuttX Embedded HAL implemented in Rust?</em></p>
|
||
<p>NuttX Embedded HAL accesses the I2C Port by calling the <strong>NuttX I2C Interface</strong>: open(), ioctl() and close(). (Pic above)</p>
|
||
<p>To understand why, let’s look at a NuttX Rust Program that <strong>reads an I2C Register</strong> on BME280: <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/test.rs#L117-L193">rust/src/test.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// Read an I2C Register
|
||
</span><span class="kw">pub fn </span>test_i2c() {
|
||
|
||
<span class="comment">// Open I2C Port
|
||
</span><span class="kw">let </span>i2c = <span class="kw">unsafe </span>{
|
||
open(<span class="string">b"/dev/i2c0\0"</span>.as_ptr(), O_RDWR)
|
||
};
|
||
<span class="macro">assert!</span>(i2c > <span class="number">0</span>);</code></pre></div>
|
||
<p>We begin by calling <strong>open()</strong> to open the I2C Port.</p>
|
||
<p>This is flagged as “<code>unsafe</code>” because we’re calling a C Function. <a href="https://lupyuen.github.io/articles/rust2#rust-meets-nuttx">(See this)</a></p>
|
||
<p>Next we prepare the buffers that will be <strong>sent and received</strong> over I2C…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Send the Register ID 0xD0 (1 byte)
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>start = [<span class="number">0xD0 </span>; <span class="number">1</span>];
|
||
|
||
<span class="comment">// Receive the Register Value (1 byte)
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>buf = [<span class="number">0 </span>; <span class="number">1</span>];</code></pre></div>
|
||
<p>We’ll read the I2C Register in 2 steps…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Send the <strong>Register ID</strong> <code>0xD0</code></p>
|
||
</li>
|
||
<li>
|
||
<p>Receive the <strong>Register Value</strong></p>
|
||
</li>
|
||
</ol>
|
||
<p>We define the First Step: Send the <strong>Register ID</strong>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Compose I2C Transfers
|
||
</span><span class="kw">let </span>msg = [
|
||
|
||
<span class="comment">// First I2C Message: Send Register ID
|
||
</span>i2c_msg_s {
|
||
frequency: <span class="number">400000</span>, <span class="comment">// I2C Frequency: 400 kHz
|
||
</span>addr: <span class="number">0x77</span>, <span class="comment">// I2C Address
|
||
</span>buffer: start.as_mut_ptr(), <span class="comment">// Buffer to be sent (Register ID)
|
||
</span>length: start.len() <span class="kw">as </span>ssize_t, <span class="comment">// Length of the buffer in bytes
|
||
|
||
// For BL602: Register ID must be passed as I2C Sub Address
|
||
</span><span class="attr">#[cfg(target_arch = <span class="string">"riscv32"</span>)] </span><span class="comment">// If architecture is RISC-V 32-bit...
|
||
</span>flags: I2C_M_NOSTOP, <span class="comment">// I2C Flags: Send I2C Sub Address
|
||
|
||
// Otherwise pass Register ID as I2C Data
|
||
</span><span class="attr">#[cfg(not(target_arch = <span class="string">"riscv32"</span>))] </span><span class="comment">// If architecture is not RISC-V 32-bit...
|
||
</span>flags: <span class="number">0</span>, <span class="comment">// I2C Flags: None
|
||
|
||
// TODO: Check for BL602 specifically, not just RISC-V 32-bit
|
||
</span>},</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/bme280#appendix-quirks-in-bl602-nuttx-i2c-driver">(<strong>I2C_M_NOSTOP</strong> is needed because of a BL602 quirk)</a></p>
|
||
<p>And here’s the Second Step: Receive the <strong>Register Value</strong>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Second I2C Message: Receive Register Value
|
||
</span>i2c_msg_s {
|
||
frequency: <span class="number">400000</span>, <span class="comment">// I2C Frequency: 400 kHz
|
||
</span>addr: <span class="number">0x77</span>, <span class="comment">// I2C Address
|
||
</span>buffer: buf.as_mut_ptr(), <span class="comment">// Buffer to be received
|
||
</span>length: buf.len() <span class="kw">as </span>ssize_t, <span class="comment">// Length of the buffer in bytes
|
||
</span>flags: I2C_M_READ, <span class="comment">// I2C Flags: Read from I2C Device
|
||
</span>},
|
||
];</code></pre></div>
|
||
<p>Finally we execute the two steps by calling <strong>ioctl()</strong>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Compose ioctl Argument
|
||
</span><span class="kw">let </span>xfer = i2c_transfer_s {
|
||
msgv: msg.as_ptr(), <span class="comment">// Array of I2C messages for the transfer
|
||
</span>msgc: msg.len() <span class="kw">as </span>size_t, <span class="comment">// Number of messages in the array
|
||
</span>};
|
||
|
||
<span class="comment">// Execute I2C Transfers
|
||
</span><span class="kw">let </span>ret = <span class="kw">unsafe </span>{
|
||
ioctl(
|
||
i2c, <span class="comment">// I2C Port
|
||
</span>I2CIOC_TRANSFER, <span class="comment">// I2C Transfer
|
||
</span><span class="kw-2">&</span>xfer <span class="comment">// I2C Messages for the transfer
|
||
</span>)
|
||
};
|
||
<span class="macro">assert!</span>(ret >= <span class="number">0</span>);</code></pre></div>
|
||
<p>The <strong>Register Value</strong> appears in our Receive Buffer…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code> <span class="comment">// Register Value must be BME280 Device ID (0x60)
|
||
</span><span class="macro">assert!</span>(buf[<span class="number">0</span>] == <span class="number">0x60</span>);
|
||
|
||
<span class="comment">// Close the I2C Port
|
||
</span><span class="kw">unsafe </span>{ close(i2c); }
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/rust-i2c-nuttx#test-i2c-port">(See the Output Log)</a></p>
|
||
<p>The above Rust code looks highly similar to the C version…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/rusti2c#appendix-read-i2c-register-in-c"><strong>“Read I2C Register in C”</strong></a></li>
|
||
</ul>
|
||
<p>Let’s look at the NuttX Types and Constants that we have ported from C to Rust.</p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-code2a.png" alt="Read I2C Register" /></p>
|
||
<p><a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/rust/src/test.rs#L117-L193">(Source)</a></p>
|
||
<h2 id="nuttx-types-and-constants"><a class="doc-anchor" href="#nuttx-types-and-constants">§</a>10.1 NuttX Types and Constants</h2>
|
||
<p><em>What are i2c_msg_s and i2c_transfer_s in the code above?</em></p>
|
||
<p>They are <strong>NuttX I2C Types</strong> that we have ported from C to Rust.</p>
|
||
<p><strong>i2c_msg_s</strong> is the <strong>I2C Message Struct</strong> that defines each message that will be sent or received over I2C: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/lib.rs#L125-L153">nuttx-embedded-hal/src/lib.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// I2C Message Struct: I2C transaction segment beginning with a START. A number of these can
|
||
/// be transferred together to form an arbitrary sequence of write/read
|
||
/// transfer to an I2C device.
|
||
/// Ported from C: https://github.com/lupyuen/nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L208-L215
|
||
</span><span class="attr">#[repr(C)]
|
||
</span><span class="kw">pub struct </span>i2c_msg_s {
|
||
<span class="doccomment">/// I2C Frequency
|
||
</span><span class="kw">pub </span>frequency: u32,
|
||
<span class="doccomment">/// I2C Address
|
||
</span><span class="kw">pub </span>addr: u16,
|
||
<span class="doccomment">/// I2C Flags (I2C_M_*)
|
||
</span><span class="kw">pub </span>flags: u16,
|
||
<span class="doccomment">/// Buffer to be transferred
|
||
</span><span class="kw">pub </span>buffer: <span class="kw-2">*mut </span>u8,
|
||
<span class="doccomment">/// Length of the buffer in bytes
|
||
</span><span class="kw">pub </span>length: ssize_t,
|
||
}</code></pre></div>
|
||
<p><strong>i2c_transfer_s</strong> contains an <strong>array of I2C Message Structs</strong> that will be sent / received when we call ioctl() to execute the I2C Transfer…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// I2C Transfer Struct: This structure is used to communicate with the I2C character driver in
|
||
/// order to perform IOCTL transfers.
|
||
/// Ported from C: https://github.com/lupyuen/nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L231-L235
|
||
</span><span class="attr">#[repr(C)]
|
||
</span><span class="kw">pub struct </span>i2c_transfer_s {
|
||
<span class="doccomment">/// Array of I2C messages for the transfer
|
||
</span><span class="kw">pub </span>msgv: <span class="kw-2">*const </span>i2c_msg_s,
|
||
<span class="doccomment">/// Number of messages in the array
|
||
</span><span class="kw">pub </span>msgc: size_t,
|
||
}</code></pre></div>
|
||
<p><em>What about I2C_M_NOSTOP, I2C_M_READ and I2CIOC_TRANSFER?</em></p>
|
||
<p><strong>I2C_M_NOSTOP</strong>, <strong>I2C_M_READ</strong> and <strong>I2CIOC_TRANSFER</strong> are <strong>NuttX I2C Constants</strong> that we have ported from C to Rust.</p>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/lib.rs#L105-L124">(See this)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-code3a.png" alt="NuttX I2C Types" /></p>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/lib.rs#L125-L153">(Source)</a></p>
|
||
<h2 id="into-embedded-hal"><a class="doc-anchor" href="#into-embedded-hal">§</a>10.2 Into Embedded HAL</h2>
|
||
<p>The code above goes into <strong>NuttX Embedded HAL</strong> like so…</p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-code5a.png" alt="Into Embedded HAL" /></p>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L97-L160">(Source)</a></p>
|
||
<p>This conforms to the I2C Interface that’s expected by <strong>Rust Embedded HAL</strong>…</p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of I2C WriteRead
|
||
</span><span class="kw">impl </span>i2c::WriteRead <span class="kw">for </span>I2c {
|
||
...
|
||
<span class="doccomment">/// Write `wbuf` to I2C Port and read `rbuf` from I2C Port.
|
||
/// We assume this is a Read I2C Register operation, with Register ID at `wbuf[0]`.
|
||
/// TODO: Handle other kinds of I2C operations
|
||
</span><span class="kw">fn </span>write_read(
|
||
<span class="kw-2">&mut </span><span class="self">self</span>, <span class="comment">// I2C Bus
|
||
</span>addr: u8, <span class="comment">// I2C Address
|
||
</span>wbuf: <span class="kw-2">&</span>[u8], <span class="comment">// Buffer to be sent (Register ID)
|
||
</span>rbuf: <span class="kw-2">&mut </span>[u8] <span class="comment">// Buffer to be received
|
||
</span>) -> <span class="prelude-ty">Result</span><(), <span class="self">Self</span>::Error> <span class="comment">// In case of error, return an error code
|
||
</span>{ ... }</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L97-L160">(Source)</a></p>
|
||
<p><em>What about the calls to open() and close()?</em></p>
|
||
<p>We moved open() into the <strong>new()</strong> constructor: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L340-L351">hal.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of I2C Bus
|
||
</span><span class="kw">impl </span>I2c {
|
||
<span class="doccomment">/// Create an I2C Bus from a Device Path (e.g. "/dev/i2c0")
|
||
</span><span class="kw">pub fn </span>new(path: <span class="kw-2">&</span>str, frequency: u32) -> <span class="prelude-ty">Result</span><<span class="self">Self</span>, i32> {
|
||
|
||
<span class="comment">// Open the NuttX Device Path (e.g. "/dev/i2c0") for read-write
|
||
</span><span class="kw">let </span>fd = open(path, O_RDWR);
|
||
<span class="kw">if </span>fd < <span class="number">0 </span>{ <span class="kw">return </span><span class="prelude-val">Err</span>(fd) }
|
||
|
||
<span class="comment">// Return the I2C Bus
|
||
</span><span class="prelude-val">Ok</span>(<span class="self">Self </span>{ fd, frequency })
|
||
}
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L492-L516">(<strong>open</strong> is defined here)</a></p>
|
||
<p>And we moved close() into the <strong>drop()</strong> destructor: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L414-L420">hal.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of I2C Bus
|
||
</span><span class="kw">impl </span>Drop <span class="kw">for </span>I2c {
|
||
|
||
<span class="doccomment">/// Close the I2C Bus
|
||
</span><span class="kw">fn </span>drop(<span class="kw-2">&mut </span><span class="self">self</span>) {
|
||
<span class="kw">unsafe </span>{ close(<span class="self">self</span>.fd) };
|
||
}
|
||
}</code></pre></div>
|
||
<p><strong>I2c</strong> Struct contains a NuttX File Descriptor and the I2C Frequency: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L454-L461">hal.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX I2C Bus
|
||
</span><span class="kw">pub struct </span>I2c {
|
||
<span class="doccomment">/// NuttX File Descriptor
|
||
</span>fd: i32,
|
||
<span class="doccomment">/// I2C Frequency in Hz
|
||
</span>frequency: u32,
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/rust-i2c-nuttx#test-i2c-hal">(See the Output Log)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-code8a.png" alt="Write I2C Register in Embedded HAL" /></p>
|
||
<p><a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L33-L96">(Source)</a></p>
|
||
<h1 id="appendix-write-i2c-register-in-embedded-hal"><a class="doc-anchor" href="#appendix-write-i2c-register-in-embedded-hal">§</a>11 Appendix: Write I2C Register in Embedded HAL</h1>
|
||
<p>BL602 has a peculiar I2C Port that uses <strong>I2C Sub Addresses</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/bme280#appendix-quirks-in-bl602-nuttx-i2c-driver"><strong>“Quirks in BL602 I2C Driver”</strong></a></li>
|
||
</ul>
|
||
<p>We tried <a href="https://lupyuen.github.io/images/rusti2c-lottery1.png"><strong>all sequences</strong></a> of I2C Read / Write / Sub Address. Only this strange sequence works for writing to I2C Registers…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Write I2C <strong>Register ID and Register Value</strong> together as I2C Sub Address</p>
|
||
</li>
|
||
<li>
|
||
<p>Followed by <strong>Read I2C Data</strong></p>
|
||
</li>
|
||
</ol>
|
||
<p>Here’s the implementation in NuttX Embedded HAL: <a href="https://github.com/lupyuen/nuttx-embedded-hal/blob/main/src/hal.rs#L33-L96">hal.rs</a></p>
|
||
|
||
<div class="example-wrap"><pre class="rust rust-example-rendered"><code><span class="doccomment">/// NuttX Implementation of I2C Write
|
||
</span><span class="kw">impl </span>i2c::Write <span class="kw">for </span>I2c {
|
||
<span class="doccomment">/// Error Type
|
||
</span><span class="kw">type </span>Error = i32;
|
||
|
||
<span class="doccomment">/// Write `buf` to I2C Port.
|
||
/// We assume this is a Write I2C Register operation, with Register ID at `buf[0]`.
|
||
/// TODO: Handle other kinds of I2C operations
|
||
</span><span class="kw">fn </span>write(<span class="kw-2">&mut </span><span class="self">self</span>, addr: u8, buf: <span class="kw-2">&</span>[u8]) -> <span class="prelude-ty">Result</span><(), <span class="self">Self</span>::Error> {
|
||
<span class="comment">// Copy to local buffer because we need a mutable reference
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>buf2 = [<span class="number">0 </span>; <span class="number">64</span>];
|
||
<span class="macro">assert!</span>(buf.len() <= buf2.len());
|
||
buf2[..buf.len()].copy_from_slice(buf);
|
||
|
||
<span class="comment">// Buffer for received I2C data
|
||
</span><span class="kw">let </span><span class="kw-2">mut </span>rbuf = [<span class="number">0 </span>; <span class="number">1</span>];
|
||
|
||
<span class="comment">// Compose I2C Transfer
|
||
</span><span class="kw">let </span>msg = [
|
||
<span class="comment">// First I2C Message: Send Register ID and I2C Data as I2C Sub Address
|
||
</span>i2c_msg_s {
|
||
frequency: <span class="self">self</span>.frequency, <span class="comment">// I2C Frequency
|
||
</span>addr: addr <span class="kw">as </span>u16, <span class="comment">// I2C Address
|
||
</span>buffer: buf2.as_mut_ptr(), <span class="comment">// Buffer to be sent
|
||
</span>length: buf.len() <span class="kw">as </span>ssize_t, <span class="comment">// Number of bytes to send
|
||
|
||
// For BL602: Register ID must be passed as I2C Sub Address
|
||
</span><span class="attr">#[cfg(target_arch = <span class="string">"riscv32"</span>)] </span><span class="comment">// If architecture is RISC-V 32-bit...
|
||
</span>flags: <span class="kw">crate</span>::I2C_M_NOSTOP, <span class="comment">// I2C Flags: Send I2C Sub Address
|
||
|
||
// Otherwise pass Register ID as I2C Data
|
||
</span><span class="attr">#[cfg(not(target_arch = <span class="string">"riscv32"</span>))] </span><span class="comment">// If architecture is not RISC-V 32-bit...
|
||
</span>flags: <span class="number">0</span>, <span class="comment">// I2C Flags: None
|
||
|
||
// TODO: Check for BL602 specifically (by target_abi?), not just RISC-V 32-bit
|
||
</span>},
|
||
<span class="comment">// Second I2C Message: Read I2C Data, because this forces BL602 to send the first message correctly
|
||
</span>i2c_msg_s {
|
||
frequency: <span class="self">self</span>.frequency, <span class="comment">// I2C Frequency
|
||
</span>addr: addr <span class="kw">as </span>u16, <span class="comment">// I2C Address
|
||
</span>buffer: rbuf.as_mut_ptr(), <span class="comment">// Buffer to be received
|
||
</span>length: rbuf.len() <span class="kw">as </span>ssize_t, <span class="comment">// Number of bytes to receive
|
||
</span>flags: I2C_M_READ, <span class="comment">// I2C Flags: Read I2C Data
|
||
</span>},
|
||
];
|
||
|
||
<span class="comment">// Compose ioctl Argument to write I2C Registers
|
||
</span><span class="kw">let </span>xfer = i2c_transfer_s {
|
||
msgv: msg.as_ptr(), <span class="comment">// Array of I2C messages for the transfer
|
||
</span>msgc: msg.len() <span class="kw">as </span>size_t, <span class="comment">// Number of messages in the array
|
||
</span>};
|
||
|
||
<span class="comment">// Execute I2C Transfer to write I2C Registers
|
||
</span><span class="kw">let </span>ret = <span class="kw">unsafe </span>{
|
||
ioctl(
|
||
<span class="self">self</span>.fd, <span class="comment">// I2C Port
|
||
</span>I2CIOC_TRANSFER, <span class="comment">// I2C Transfer
|
||
</span><span class="kw-2">&</span>xfer <span class="comment">// I2C Messages for the transfer
|
||
</span>)
|
||
};
|
||
<span class="macro">assert!</span>(ret >= <span class="number">0</span>);
|
||
<span class="prelude-val">Ok</span>(())
|
||
}
|
||
}</code></pre></div>
|
||
<p>Our Logic Analyser shows that BL602 writes correctly to the I2C Register (with a harmless I2C Read at the end)…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>Setup Write to [0xEE] + ACK
|
||
0xF5 + ACK
|
||
0xA0 + ACK
|
||
Setup Read to [0xEF] + ACK
|
||
0xA0 + NAK</code></pre></div>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-logic3a.png" alt="BL602 writes correctly to the I2C Register! With a harmless I2C Read at the end" /></p>
|
||
<p><a href="https://github.com/lupyuen/rust-i2c-nuttx#fix-i2c-write">(See the Output Log)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/rusti2c-code1.png" alt="Read I2C Register in C" /></p>
|
||
<p><a href="https://github.com/lupyuen/bme280-nuttx/blob/main/driver.c#L155-L183">(Source)</a></p>
|
||
<h1 id="appendix-read-i2c-register-in-c"><a class="doc-anchor" href="#appendix-read-i2c-register-in-c">§</a>12 Appendix: Read I2C Register in C</h1>
|
||
<p>This is how we read an I2C Register in C from a NuttX App…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>static int bme280_reg_read(const struct device *priv,
|
||
uint8_t start, uint8_t *buf, int size)
|
||
{
|
||
DEBUGASSERT(priv != NULL);
|
||
DEBUGASSERT(buf != NULL);
|
||
struct i2c_msg_s msg[2];
|
||
int ret;
|
||
|
||
msg[0].frequency = priv->freq;
|
||
msg[0].addr = priv->addr;
|
||
|
||
#ifdef CONFIG_BL602_I2C0
|
||
// For BL602: Register ID must be passed as I2C Sub Address
|
||
msg[0].flags = I2C_M_NOSTOP;
|
||
#else
|
||
// Otherwise pass Register ID as I2C Data
|
||
msg[0].flags = 0;
|
||
#endif // CONFIG_BL602_I2C0
|
||
|
||
msg[0].buffer = &start;
|
||
msg[0].length = 1;
|
||
|
||
msg[1].frequency = priv->freq;
|
||
msg[1].addr = priv->addr;
|
||
msg[1].flags = I2C_M_READ;
|
||
msg[1].buffer = buf;
|
||
msg[1].length = size;
|
||
|
||
ret = I2C_TRANSFER(priv->i2c, msg, 2);</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/bme280-nuttx/blob/main/driver.c#L155-L183">(Source)</a></p>
|
||
<p>How do we call <strong>I2C_TRANSFER</strong> from a NuttX App? Thanks to the I2C Demo App we have the answer…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>int i2ctool_get(FAR struct i2ctool_s *i2ctool, int fd, uint8_t regaddr,
|
||
FAR uint16_t *result)
|
||
{
|
||
struct i2c_msg_s msg[2];
|
||
...
|
||
int ret = i2cdev_transfer(fd, msg, 2);</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx-apps/blob/rusti2c/system/i2c/i2c_get.c#L158-L206">(Source)</a></p>
|
||
<p><strong>i2cdev_transfer</strong> is defined as…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>int i2cdev_transfer(int fd, FAR struct i2c_msg_s *msgv, int msgc)
|
||
{
|
||
struct i2c_transfer_s xfer;
|
||
|
||
/* Set up the IOCTL argument */
|
||
|
||
xfer.msgv = msgv;
|
||
xfer.msgc = msgc;
|
||
|
||
/* Perform the IOCTL */
|
||
|
||
return ioctl(fd, I2CIOC_TRANSFER, (unsigned long)((uintptr_t)&xfer));
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx-apps/blob/rusti2c/system/i2c/i2c_devif.c#L117-L129">(Source)</a></p>
|
||
<p>We ported the code above to NuttX Embedded HAL. <a href="https://lupyuen.github.io/articles/rusti2c#into-embedded-hal">(See this)</a></p>
|
||
<h2 id="c-types-and-constants"><a class="doc-anchor" href="#c-types-and-constants">§</a>12.1 C Types and Constants</h2>
|
||
<p>Earlier we’ve seen <strong>i2c_msg_s</strong> and <strong>i2c_transfer_s</strong>. They are defined as…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>struct i2c_msg_s
|
||
{
|
||
uint32_t frequency; /* I2C frequency */
|
||
uint16_t addr; /* Slave address (7- or 10-bit) */
|
||
uint16_t flags; /* See I2C_M_* definitions */
|
||
FAR uint8_t *buffer; /* Buffer to be transferred */
|
||
ssize_t length; /* Length of the buffer in bytes */
|
||
};</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L208-L215">(Source)</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>struct i2c_transfer_s
|
||
{
|
||
FAR struct i2c_msg_s *msgv; /* Array of I2C messages for the transfer */
|
||
size_t msgc; /* Number of messages in the array. */
|
||
};</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L231-L235">(Source)</a></p>
|
||
<p><strong>I2CIOC_TRANSFER</strong> is defined as…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define I2CIOC_TRANSFER _I2CIOC(0x0001)</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx/blob/rusti2c/include/nuttx/i2c/i2c_master.h#L105-L129">(Source)</a></p>
|
||
<p>_<strong>I2CIOC</strong> is defined as…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define _I2CIOC(nr) _IOC(_I2CBASE,nr)</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx/blob/rusti2c/include/nuttx/fs/ioctl.h#L467-L468">(Source)</a></p>
|
||
<p>_<strong>IOC</strong> and _<strong>I2CBASE</strong> are defined as…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define _IOC(type,nr) ((type)|(nr))</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx/blob/rusti2c/include/nuttx/fs/ioctl.h#L107">(Source)</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define _I2CBASE (0x2100) /* I2C driver commands */</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/nuttx/blob/rusti2c/include/nuttx/fs/ioctl.h#L73">(Source)</a></p>
|
||
<p>We ported these C Types and Constants to NuttX Embedded HAL. <a href="https://lupyuen.github.io/articles/rusti2c#nuttx-types-and-constants">(See this)</a></p>
|
||
<h1 id="appendix-build-flash-and-run-nuttx"><a class="doc-anchor" href="#appendix-build-flash-and-run-nuttx">§</a>13 Appendix: Build, Flash and Run NuttX</h1>
|
||
<p><em>(For BL602, BL604 and ESP32)</em></p>
|
||
<p>Below are the steps to build, flash and run NuttX on BL602, BL604 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&lang=en">(See this for Arch Linux)</a></p>
|
||
<h2 id="download-nuttx"><a class="doc-anchor" href="#download-nuttx">§</a>13.1 Download NuttX</h2>
|
||
<p>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-i2c-nuttx"><strong>“Install Rust I2C App”</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>
|
||
<h2 id="configure-nuttx"><a class="doc-anchor" href="#configure-nuttx">§</a>13.2 Configure NuttX</h2>
|
||
<p>Now we configure our NuttX project…</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>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 "esp32-devkitc" to our ESP32 board.
|
||
./tools/configure.sh esp32-devkitc:nsh
|
||
|
||
## Edit the Build Config
|
||
make menuconfig </code></pre></div></li>
|
||
<li>
|
||
<p>Enable our <strong>Rust Library</strong>…</p>
|
||
<p>Check the box for <strong>“Library Routines”</strong> → <strong>“Rust Library”</strong></p>
|
||
<p>Hit <strong>“Exit”</strong> until the Top Menu appears. (“NuttX/x64_64 Configuration”)</p>
|
||
</li>
|
||
<li>
|
||
<p>Enable our <strong>Rust I2C App</strong>…</p>
|
||
<p>Check the box for <strong>“Application Configuration”</strong> → <strong>“Examples”</strong> → <strong>“Rust I2C App”</strong></p>
|
||
<p>Hit <strong>“Exit”</strong> until the Top Menu appears. (“NuttX/x64_64 Configuration”)</p>
|
||
</li>
|
||
<li>
|
||
<p>Enable <strong>I2C0 Port</strong>…</p>
|
||
<p><strong>For BL602 / BL604:</strong> Check the box for <strong>“System Type”</strong> → <strong>“BL602 Peripheral Support”</strong> → <strong>“I2C0”</strong></p>
|
||
<p><strong>For ESP32:</strong> Check the box for <strong>“System Type”</strong> → <strong>“ESP32 Peripheral Select”</strong> → <strong>“I2C 0”</strong></p>
|
||
<p>Hit <strong>“Exit”</strong> until the Top Menu appears. (“NuttX/x64_64 Configuration”)</p>
|
||
<p><img src="https://lupyuen.github.io/images/bme280-config1.jpg" alt="Enable the I2C Port and I2C Character Driver" /></p>
|
||
</li>
|
||
<li>
|
||
<p>Enable <strong>I2C Character Driver</strong>…</p>
|
||
<p>Check the box for <strong>“Device Drivers”</strong> → <strong>“I2C Driver Support”</strong> → <strong>“I2C Character Driver”</strong></p>
|
||
<p>Hit <strong>“Exit”</strong> until the Top Menu appears. (“NuttX/x64_64 Configuration”)</p>
|
||
</li>
|
||
<li>
|
||
<p>Enable <strong>ls</strong> command…</p>
|
||
<p>Select <strong>“Application Configuration”</strong> → <strong>“NSH Library”</strong> → <strong>“Disable Individual commands”</strong></p>
|
||
<p>Uncheck <strong>“Disable ls”</strong></p>
|
||
<p>Hit <strong>“Exit”</strong> until the Top Menu appears. (“NuttX/x64_64 Configuration”)</p>
|
||
</li>
|
||
<li>
|
||
<p>Enable <strong>Logging and Assertion Checks</strong>…</p>
|
||
<p>Select <strong>“Build Setup”</strong> → <strong>“Debug Options”</strong></p>
|
||
<p>Check the boxes for the following…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>Enable Debug Features
|
||
Enable Error Output
|
||
Enable Warnings Output
|
||
Enable Informational Debug Output
|
||
Enable Debug Assertions
|
||
I2C Debug Features
|
||
I2C Error Output
|
||
I2C Warnings Output
|
||
I2C Informational Output </code></pre></div>
|
||
<p>Hit <strong>“Exit”</strong> until the Top Menu appears. (“NuttX/x64_64 Configuration”)</p>
|
||
</li>
|
||
<li>
|
||
<p>Save the configuration and exit menuconfig</p>
|
||
<p><a href="https://gist.github.com/lupyuen/85550f16517202b7978e592da976c4e7">(See the .config for BL602)</a></p>
|
||
</li>
|
||
</ol>
|
||
<h2 id="configure-rust-target"><a class="doc-anchor" href="#configure-rust-target">§</a>13.3 Configure Rust Target</h2>
|
||
<p><strong>For BL602 / BL604</strong>: Skip to the next section</p>
|
||
<p><strong>For ESP32-C3 (RISC-V)</strong>:</p>
|
||
<ol>
|
||
<li>
|
||
<p>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></li>
|
||
<li>
|
||
<p>Edit <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/run.sh"><strong>apps/examples/rust_i2c/run.sh</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p>Set “rust_build_target” and “rust_build_target_folder” to…</p>
|
||
<p><strong>riscv32imc-unknown-none-elf</strong></p>
|
||
</li>
|
||
<li>
|
||
<p>Remove “-Z build-std=core” from “rust_build_options”</p>
|
||
</li>
|
||
</ol>
|
||
<p><strong>For ESP32 (Xtensa)</strong>:</p>
|
||
<ol>
|
||
<li>
|
||
<p>Install the Rust compiler fork with Xtensa support. <a href="https://github.com/jessebraham/esp-hal/tree/main/esp32-hal">(See this)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Edit <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/run.sh"><strong>apps/examples/rust_i2c/run.sh</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p>Set “rust_build_target” and “rust_build_target_folder” to…</p>
|
||
<p><strong>xtensa-esp32-none-elf</strong></p>
|
||
</li>
|
||
<li>
|
||
<p>Remove “-Z build-std=core” from “rust_build_options”</p>
|
||
</li>
|
||
</ol>
|
||
<p><a href="https://lupyuen.github.io/articles/rust2#appendix-rust-build-script-for-nuttx">(<strong>run.sh</strong> is explained here)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/rust2#rust-target">(More about Rust Targets)</a></p>
|
||
<h2 id="build-nuttx"><a class="doc-anchor" href="#build-nuttx">§</a>13.4 Build NuttX</h2>
|
||
<p>Follow these steps to build NuttX for BL602, BL604 or ESP32…</p>
|
||
<ol>
|
||
<li>
|
||
<p>To build NuttX with Rust, run the Rust Build Script <a href="https://github.com/lupyuen/rust-i2c-nuttx/blob/main/run.sh"><strong>run.sh</strong></a>…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>pushd apps/examples/rust_i2c
|
||
./run.sh
|
||
popd</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/rust2#appendix-rust-build-script-for-nuttx">(<strong>run.sh</strong> is explained here)</a></p>
|
||
</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 / BL604)</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>13.5 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&lang=en">(Also check out this article)</a></p>
|
||
<p><strong>For BL602 / BL604:</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">(Here’s 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 "/dev/ttyUSB0" to the BL602 / BL604 Serial Port
|
||
blflash flash nuttx.bin \
|
||
--port /dev/ttyUSB0
|
||
|
||
## For macOS: Change "/dev/tty.usbserial-1410" 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 "COM5" 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>13.6 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&lang=en">(More about this)</a></p>
|
||
<p><strong>For BL602 / BL604:</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">(Here’s 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 / BL604’s 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></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/rusti2c-title2.jpg" alt="Bosch BME280 Sensor connected to Pine64 PineCone BL602 RISC-V Board" /></p>
|
||
<p><em><a href="https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/">Bosch BME280 Sensor</a> connected to <a href="https://lupyuen.github.io/articles/pinecone">Pine64 PineCone BL602 RISC-V Board</a></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> |