lupyuen.org/articles/i2c.html

1018 lines
No EOL
71 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>PineCone BL602 talks to I2C Sensors</title>
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
<meta property="og:title"
content="PineCone BL602 talks to I2C Sensors"
data-rh="true">
<meta property="og:description"
content="How we call the BL602 RISC-V Hardware Abstraction Layer to access the BME280 I2C Sensor"
data-rh="true">
<meta property="og:image"
content="https://lupyuen.github.io/images/i2c-title.jpg">
<meta property="og:type"
content="article" data-rh="true">
<link rel="canonical" href="https://lupyuen.org/articles/i2c.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">PineCone BL602 talks to I2C Sensors</h1>
<nav id="rustdoc"><ul>
<li><a href="#bl602-i2c-hardware-abstraction-layer-high-level-vs-low-level" title="BL602 I2C Hardware Abstraction Layer: High Level vs Low Level">1 BL602 I2C Hardware Abstraction Layer: High Level vs Low Level</a><ul></ul></li>
<li><a href="#connect-bl602-to-bme280-i2c-sensor" title="Connect BL602 to BME280 I2C Sensor">2 Connect BL602 to BME280 I2C Sensor</a><ul>
<li><a href="#i2c-protocol-for-bme280" title="I2C Protocol for BME280">2.1 I2C Protocol for BME280</a><ul></ul></li></ul></li>
<li><a href="#initialise-i2c-port" title="Initialise I2C Port">3 Initialise I2C Port</a><ul>
<li><a href="#select-i2c-port" title="Select I2C Port">3.1 Select I2C Port</a><ul></ul></li>
<li><a href="#assign-i2c-pins-and-set-i2c-frequency" title="Assign I2C Pins and set I2C Frequency">3.2 Assign I2C Pins and set I2C Frequency</a><ul></ul></li>
<li><a href="#enable-i2c-interrupts" title="Enable I2C Interrupts">3.3 Enable I2C Interrupts</a><ul></ul></li>
<li><a href="#register-i2c-interrupt-handler" title="Register I2C Interrupt Handler">3.4 Register I2C Interrupt Handler</a><ul></ul></li>
<li><a href="#hal-functions" title="HAL Functions">3.5 HAL Functions</a><ul></ul></li></ul></li>
<li><a href="#i2c-message" title="I2C Message">4 I2C Message</a><ul>
<li><a href="#define-i2c-message-and-buffer" title="Define I2C Message and Buffer">4.1 Define I2C Message and Buffer</a><ul></ul></li>
<li><a href="#set-i2c-operation-and-buffer" title="Set I2C Operation and Buffer">4.2 Set I2C Operation and Buffer</a><ul></ul></li>
<li><a href="#set-i2c-device-address-and-register-address" title="Set I2C Device Address and Register Address">4.3 Set I2C Device Address and Register Address</a><ul></ul></li>
<li><a href="#i2c-terms" title="I2C Terms">4.4 I2C Terms</a><ul></ul></li></ul></li>
<li><a href="#start-i2c-read" title="Start I2C Read">5 Start I2C Read</a><ul>
<li><a href="#create-i2c-message" title="Create I2C Message">5.1 Create I2C Message</a><ul></ul></li>
<li><a href="#start-i2c-transfer" title="Start I2C Transfer">5.2 Start I2C Transfer</a><ul></ul></li></ul></li>
<li><a href="#handle-i2c-interrupts" title="Handle I2C Interrupts">6 Handle I2C Interrupts</a><ul>
<li><a href="#get-i2c-message-and-interrupt-reason" title="Get I2C Message and Interrupt Reason">6.1 Get I2C Message and Interrupt Reason</a><ul></ul></li>
<li><a href="#i2c-data-received" title="I2C Data Received">6.2 I2C Data Received</a><ul></ul></li>
<li><a href="#i2c-transfer-end" title="I2C Transfer End">6.3 I2C Transfer End</a><ul></ul></li>
<li><a href="#i2c-no-acknowledge" title="I2C No Acknowledge">6.4 I2C No Acknowledge</a><ul></ul></li>
<li><a href="#i2c-data-transmitted" title="I2C Data Transmitted">6.5 I2C Data Transmitted</a><ul></ul></li>
<li><a href="#i2c-errors" title="I2C Errors">6.6 I2C Errors</a><ul></ul></li>
<li><a href="#transfer-data" title="Transfer Data">6.7 Transfer Data</a><ul></ul></li></ul></li>
<li><a href="#transmit-and-receive-i2c-data" title="Transmit and Receive I2C Data">7 Transmit and Receive I2C Data</a><ul>
<li><a href="#i2c-write-operation" title="I2C Write Operation">7.1 I2C Write Operation</a><ul></ul></li>
<li><a href="#i2c-read-operation" title="I2C Read Operation">7.2 I2C Read Operation</a><ul></ul></li></ul></li>
<li><a href="#stop-i2c-read" title="Stop I2C Read">8 Stop I2C Read</a><ul></ul></li>
<li><a href="#build-and-run-the-firmware" title="Build and Run the Firmware">9 Build and Run the Firmware</a><ul>
<li><a href="#build-the-firmware" title="Build the firmware">9.1 Build the firmware</a><ul></ul></li>
<li><a href="#flash-the-firmware" title="Flash the firmware">9.2 Flash the firmware</a><ul></ul></li>
<li><a href="#run-the-firmware" title="Run the firmware">9.3 Run the firmware</a><ul></ul></li>
<li><a href="#enter-i2c-commands" title="Enter I2C commands">9.4 Enter I2C commands</a><ul></ul></li></ul></li>
<li><a href="#why-we-need-an-embedded-os-for-i2c" title="Why we need an Embedded OS for I2C">10 Why we need an Embedded OS for I2C</a><ul>
<li><a href="#high-level-hal-unmasked" title="High Level HAL unmasked">10.1 High Level HAL unmasked</a><ul></ul></li>
<li><a href="#high-level-hal-without-freertos" title="High Level HAL without FreeRTOS">10.2 High Level HAL without FreeRTOS</a><ul></ul></li></ul></li>
<li><a href="#whats-next" title="Whats Next">11 Whats Next</a><ul></ul></li>
<li><a href="#notes" title="Notes">12 Notes</a><ul></ul></li>
<li><a href="#appendix-how-to-troubleshoot-risc-v-exceptions" title="Appendix: How to Troubleshoot RISC-V Exceptions">13 Appendix: How to Troubleshoot RISC-V Exceptions</a><ul></ul></li>
<li><a href="#appendix-test-bme280-with-bus-pirate" title="Appendix: Test BME280 with Bus Pirate">14 Appendix: Test BME280 with Bus Pirate</a><ul></ul></li></ul></nav><p>📝 <em>29 Jan 2021</em></p>
<p><strong><a href="https://lupyuen.github.io/articles/pinecone">PineCone BL602</a> (<a href="https://wiki.pine64.org/wiki/Nutcracker#Pinenut-01S_Module_information_and_schematics">and Pinenut</a>)</strong> is an awesome RISC-V Microcontroller Board with WiFi and Bluetooth LE Networking.</p>
<p>But to turn PineCone BL602 into an <strong>IoT Gadget</strong> we need one more thing…</p>
<p><strong>An I2C Sensor!</strong></p>
<p>Today we shall connect PineCone / Pinenut / Any BL602 Board to an I2C Sensor and read some data.</p>
<p>We shall also discover a feature thats unique to BL602: <strong>I2C Register Addresses</strong></p>
<p>Remember to check out the <strong>Appendix</strong> for Special Topics…</p>
<ol>
<li>
<p>How to troubleshoot <strong>RISC-V Exceptions</strong></p>
</li>
<li>
<p>How to test <strong>I2C Sensors</strong> with Bus Pirate</p>
</li>
</ol>
<p><img src="https://lupyuen.github.io/images/i2c-title.jpg" alt="PineCone BL602 RISC-V Evaluation Board connected to BME280 I2C Sensor" /></p>
<p><em>PineCone BL602 RISC-V Evaluation Board connected to BME280 I2C Sensor</em></p>
<h1 id="bl602-i2c-hardware-abstraction-layer-high-level-vs-low-level"><a class="doc-anchor" href="#bl602-i2c-hardware-abstraction-layer-high-level-vs-low-level">§</a>1 BL602 I2C Hardware Abstraction Layer: High Level vs Low Level</h1>
<p>BL602s IoT SDK contains an <strong>I2C Hardware Abstraction Layer (HAL)</strong> that we may call in our C programs to access I2C Sensors.</p>
<p>BL602s I2C HAL is packaged as two levels…</p>
<ol>
<li>
<p><strong>Low Level HAL <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a></strong>: This runs on BL602 Bare Metal.</p>
<p>The Low Level HAL manipulates the BL602 I2C Registers directly to perform I2C functions.</p>
</li>
<li>
<p><strong>High Level HAL <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c"><code>hal_i2c.c</code></a></strong>: This calls the Low Level HAL, and uses the Device Tree and FreeRTOS.</p>
<p>The High Level HAL is called by the <a href="https://github.com/alibaba/AliOS-Things">AliOS Firmware</a> created by the BL602 IoT SDK.</p>
<p>(AliOS functions are easy to identify… Their function names begin with “<code>aos_</code>”)</p>
<p>(Why does the High Level HAL use FreeRTOS? Well learn in a while)</p>
</li>
</ol>
<p>Today we shall use the <strong>Low Level I2C HAL <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a></strong> because…</p>
<ul>
<li>
<p>The Low Level I2C HAL is <strong>simpler to understand</strong>.</p>
<p>Well learn all about the BL602 I2C Hardware by calling the Low Level HAL Functions.</p>
</li>
<li>
<p>The Low Level I2C HAL <strong>works on all Embedded Operating Systems</strong>. (Not just FreeRTOS)</p>
<p>In the next article well port the Low Level I2C HAL to Mynewt. And hopefully the PineCone BL602 Community will port it to Arduino, RIOT, Zephyr, …</p>
<p><a href="https://github.com/nandojve/zephyr/blob/bouffalo/boards/riscv/dt_bl10_devkit/doc/index.rst"><strong>UPDATE: Check out Zephyr for BL602</strong></a></p>
<p><a href="https://github.com/bouffalolab/bl_mcu_sdk/pull/18"><strong>UPDATE: Zephyr is being ported to BL602 MCU SDK</strong></a></p>
</li>
<li>
<p>But the Low Level I2C HAL is <strong>not functionally complete</strong>.</p>
<p>(Yes we said that BL602 will <em>talk to I2C Sensors today</em>… Though we wont be able to <em>use the sensor data meaningfully yet</em>)</p>
<p>Well see in a while that the Low Level HAL requires an Embedded Operating System to function properly. (Which is beyond the scope of this article)</p>
</li>
</ul>
<p>We shall test BL602 I2C with this BL602 Command-Line Firmware (modded from BL602 IoT SDK): <a href="https://github.com/lupyuen/bl_iot_sdk/tree/master/customer_app/sdk_app_i2c"><code>sdk_app_i2c</code></a></p>
<p><img src="https://lupyuen.github.io/images/i2c-fail.jpg" alt="BL602 Command-Line Firmware sdk_app_i2c" /></p>
<p>(Dont worry, well make it hunky dory by the end of the article!)</p>
<p>The firmware will work on all BL602 boards, including PineCone and Pinenut.</p>
<p><img src="https://lupyuen.github.io/images/i2c-bme280.jpg" alt="PineCone BL602 connected to SparkFun BME280 I2C Sensor" /></p>
<p><em>PineCone BL602 connected to <a href="https://www.sparkfun.com/products/13676">SparkFun BME280 I2C Sensor</a></em></p>
<h1 id="connect-bl602-to-bme280-i2c-sensor"><a class="doc-anchor" href="#connect-bl602-to-bme280-i2c-sensor">§</a>2 Connect BL602 to BME280 I2C Sensor</h1>
<p>Lets connect BL602 to the <a href="https://learn.sparkfun.com/tutorials/sparkfun-bme280-breakout-hookup-guide"><strong>Bosch BME280 I2C Sensor for Temperature, Humidity and Air Pressure</strong></a></p>
<p>(Air Pressure is very useful for sensing which level of a building were on!)</p>
<p>Connect BL602 to BME280 according to the 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 steps in this article will work for BMP280 too)</p>
<p>The Low Level I2C HAL assigns GPIO 3 and 4 to the I2C Port on BL602. (See <strong>“Section 3.2.8: GPIO Function”</strong> in the <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en"><strong>BL602 Reference Manual</strong></a>)</p>
<p>(If were using the High Level I2C HAL, the I2C Pins are defined in the Device Tree)</p>
<p><em>What shall we accomplish with BL602 and BME280?</em></p>
<ol>
<li>
<p>Well access BME280 at <strong>I2C Device ID <code>0x77</code></strong></p>
<p>(BME280 may be configured as Device ID <code>0x76</code> or <code>0x77</code>. <a href="https://www.sparkfun.com/products/13676">SparkFun BME280</a> in the pic above uses <code>0x77</code>)</p>
</li>
<li>
<p>BME280 has an I2C Register, <strong>Chip ID, at Register <code>0xD0</code></strong></p>
</li>
<li>
<p>Reading the Chip ID Register will give us the <strong>Chip ID value <code>0x60</code></strong></p>
<p>(<code>0x60</code> identifies the chip as BME280. For BMP280 the Chip ID is <code>0x58</code>)</p>
</li>
</ol>
<h2 id="i2c-protocol-for-bme280"><a class="doc-anchor" href="#i2c-protocol-for-bme280">§</a>2.1 I2C Protocol for BME280</h2>
<p><em>What are the data bytes that will be sent by BL602?</em></p>
<p>Heres the I2C Data that will be sent by BL602 to BME280…</p>
<div class="example-wrap"><pre class="language-text"><code> [Start] 0xEE 0xD0 [Stop]
[Start] 0xEF [Read] [Stop]</code></pre></div>
<p>BL602 will initiate two I2C Transactions, indicated by <code>[Start] ... [Stop]</code></p>
<ol>
<li>
<p>In the First I2C Transaction, BL602 specifies the I2C Register to be read: <code>0xD0</code> (Chip ID)</p>
</li>
<li>
<p>In the Second I2C Transaction, BME280 returns the value of the Chip ID Register, indicated by <code>[Read]</code></p>
</li>
</ol>
<p><em>What are 0xEE and 0xEF?</em></p>
<p>They are the Read / Write aliases of the I2C Device ID <code>0x77</code></p>
<ul>
<li>
<p><code>0xEE</code> = (<code>0x77</code> * 2) + 0, for Writing Data</p>
</li>
<li>
<p><code>0xEF</code> = (<code>0x77</code> * 2) + 1, for Reading Data</p>
</li>
</ul>
<p>I2C uses this even / odd convention to indicate whether were writing or reading data.</p>
<p>To sum up: We need to reproduce on BL602 the two <code>[Start] ... [Stop]</code> transactions. Which includes sending 3 bytes (<code>0xEE</code>, <code>0xD0</code>, <code>0xEF</code>) and receiving 1 byte (<code>0x60</code>).</p>
<p><a href="https://lupyuen.github.io/articles/building-a-rust-driver-for-pinetimes-touch-controller">More about I2C</a></p>
<p><img src="https://lupyuen.github.io/images/i2c-cartoon4.png" alt="Initialise I2C Port" /></p>
<h1 id="initialise-i2c-port"><a class="doc-anchor" href="#initialise-i2c-port">§</a>3 Initialise I2C Port</h1>
<p>Remember our Command-Line Firmware <a href="https://github.com/lupyuen/bl_iot_sdk/tree/master/customer_app/sdk_app_i2c"><code>sdk_app_i2c</code></a> for testing I2C on BL602?</p>
<p>Heres the command for initialising the I2C Port…</p>
<div class="example-wrap"><pre class="language-text"><code> i2c_init</code></pre></div>
<p>Lets discover how this command calls the Low Level I2C HAL to initialise the I2C Port: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L343-L369"><code>sdk_app_i2c/demo.c</code></a></p>
<h2 id="select-i2c-port"><a class="doc-anchor" href="#select-i2c-port">§</a>3.1 Select I2C Port</h2><div class="example-wrap"><pre class="language-c"><code>/// Init I2C Port. Based on hal_i2c_init in hal_i2c.c
static void test_i2c_init(char *buf, int len, int argc, char **argv) {
// Use I2C Port 0
const int i2cx = 0;</code></pre></div>
<p>Well use <strong>I2C Port 0</strong>, the one and only I2C Port on BL602.</p>
<h2 id="assign-i2c-pins-and-set-i2c-frequency"><a class="doc-anchor" href="#assign-i2c-pins-and-set-i2c-frequency">§</a>3.2 Assign I2C Pins and set I2C Frequency</h2><div class="example-wrap"><pre class="language-c"><code> // Init I2C Port 0 to GPIO 3 and 4
i2c_gpio_init(i2cx);
// Set I2C Port 0 to 500 kbps
i2c_set_freq(500, i2cx);</code></pre></div>
<p>We call <code>i2c_gpio_init</code> to assign <strong>GPIO 3 and 4 as the SDA and SCL pins</strong> for I2C Port 0.</p>
<p>Then we call <code>i2c_set_freq</code> to set the <strong>I2C Frequency</strong> to 500 kbps.</p>
<h2 id="enable-i2c-interrupts"><a class="doc-anchor" href="#enable-i2c-interrupts">§</a>3.3 Enable I2C Interrupts</h2>
<p>The I2C Port triggers <strong>I2C Interrupts</strong> after sending and receiving queued data, also when an error occurs. So we need to enable I2C Interrupts…</p>
<div class="example-wrap"><pre class="language-c"><code> // Disable I2C Port 0
I2C_Disable(i2cx);
// Enable I2C interrupts
bl_irq_enable(I2C_IRQn);
I2C_IntMask(i2cx, I2C_INT_ALL, MASK);</code></pre></div>
<p>We disable the I2C Port, then enable I2C Interrupts on the I2C Port.</p>
<h2 id="register-i2c-interrupt-handler"><a class="doc-anchor" href="#register-i2c-interrupt-handler">§</a>3.4 Register I2C Interrupt Handler</h2>
<p>To handle I2C Interrupts we <strong>register an Interrupt Handler Function</strong></p>
<div class="example-wrap"><pre class="language-c"><code> // Register the I2C Interrupt Handler
bl_irq_register_with_ctx(
I2C_IRQn, // For I2C Interrupt:
test_i2c_interrupt_entry, // Interrupt Handler
&amp;gpstmsg // Pointer to current I2C Message
);
}</code></pre></div>
<p>Here we register the function <code>test_i2c_interrupt_entry</code> as the Interrupt Handler Function for I2C Interrupts. (More about this function in a while)</p>
<p><code>gpstmsg</code> is the <strong>Interrupt Context</strong> that will be passed to the Interrupt Handler Function…</p>
<div class="example-wrap"><pre class="language-c"><code>/// Global pointer to current I2C Message
static i2c_msg_t *gpstmsg;</code></pre></div>
<p><code>gpstmsg</code> points to the <strong>current I2C Message</strong> being sent or received, so that the Interrupt Handler knows which Message Buffer to use for sending and receiving data.</p>
<h2 id="hal-functions"><a class="doc-anchor" href="#hal-functions">§</a>3.5 HAL Functions</h2>
<p>Lets list down the HAL Functions called above and where they are defined…</p>
<p>The following functions are defined in the <strong>Low Level I2C HAL</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a></p>
<div class="example-wrap"><pre class="language-text"><code>i2c_gpio_init, i2c_set_freq</code></pre></div>
<p>These functions are defined in the <strong>BL602 Interrupt HAL</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_irq.c"><code>bl_irq.c</code></a></p>
<div class="example-wrap"><pre class="language-text"><code>bl_irq_enable, bl_irq_register_with_ctx</code></pre></div>
<p>And these functions are defined in the <strong>BL602 Standard Driver</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/bl602/bl602_std/bl602_std/StdDriver/Src/bl602_i2c.c"><code>bl602_i2c.c</code></a></p>
<div class="example-wrap"><pre class="language-text"><code>I2C_Disable, I2C_IntMask</code></pre></div>
<p>(The BL602 Standard Driver contains low-level functions to manipulate the BL602 Hardware Registers)</p>
<p><img src="https://lupyuen.github.io/images/i2c-cartoon6.png" alt="I2C Message" /></p>
<h1 id="i2c-message"><a class="doc-anchor" href="#i2c-message">§</a>4 I2C Message</h1>
<p>Our objective is to <strong>read Register <code>0xD0</code></strong> from our BME280 Sensor with <strong>Device ID <code>0x77</code></strong></p>
<p>We specify these details in an <strong>I2C Message Struct <code>i2c_msg_t</code></strong> thats defined in the Low Level I2C HAL.</p>
<p>Heres how we create an I2C Message: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L424-L442"><code>sdk_app_i2c/demo.c</code></a></p>
<h2 id="define-i2c-message-and-buffer"><a class="doc-anchor" href="#define-i2c-message-and-buffer">§</a>4.1 Define I2C Message and Buffer</h2><div class="example-wrap"><pre class="language-c"><code>// Define I2C message and buffer
static i2c_msg_t read_msg; // Message for reading I2C Data
static uint8_t read_buf[32]; // Buffer for reading I2C Data
int data_len = 1; // Bytes to be read</code></pre></div>
<p>First we define <code>read_msg</code> as a static I2C Message.</p>
<p>The data returned by our BME280 Sensor shall be stored in the static buffer <code>read_buf</code>.</p>
<h2 id="set-i2c-operation-and-buffer"><a class="doc-anchor" href="#set-i2c-operation-and-buffer">§</a>4.2 Set I2C Operation and Buffer</h2><div class="example-wrap"><pre class="language-c"><code>// Set the I2C operation
read_msg.i2cx = 0; // I2C Port
read_msg.direct = I2C_M_READ; // Read I2C data
read_msg.block = I2C_M_BLOCK; // Wait until data has been read</code></pre></div>
<p>Next we set the <strong>I2C Port</strong> (0) and the <strong>I2C Operation</strong> (Blocking Read).</p>
<div class="example-wrap"><pre class="language-c"><code>// Set the I2C buffer
read_msg.buf = read_buf; // Read buffer
read_msg.len = data_len; // Number of bytes to be read
read_msg.idex = 0; // Index of next byte to be read into buf</code></pre></div>
<p>Then we assign the data buffer <code>read_buf</code> to <code>read_msg</code> and set the number of bytes to be read (1).</p>
<p><code>idex</code> is the index into the buffer <code>read_buf</code>. Our I2C Interrupt Handler will increment this index as it populates the buffer upon receiving data.</p>
<h2 id="set-i2c-device-address-and-register-address"><a class="doc-anchor" href="#set-i2c-device-address-and-register-address">§</a>4.3 Set I2C Device Address and Register Address</h2>
<p>Well be reading data from BME280, which has Device ID <code>0x77</code>.</p>
<p>We specify the <strong>Device Address</strong> like so…</p>
<div class="example-wrap"><pre class="language-c"><code>// Set device address
read_msg.addr = 0x77; // BME280 I2C Secondary Address (Primary Address is 0x76)</code></pre></div>
<p><em>Now heres the really really interesting thing about BL602…</em></p>
<p>Remember that we will be reading Register <code>0xD0</code> (Chip ID) on BME280?</p>
<p>We specify the <strong>Register Address</strong> in this incredibly easy peasy way…</p>
<div class="example-wrap"><pre class="language-c"><code>// Set register address
read_msg.subflag = 1; // Enable Register Address
read_msg.subaddr = 0xd0; // Register Address (BME280 Chip ID)
read_msg.sublen = 1; // Length of Register Address (bytes)</code></pre></div>
<p><strong>This I2C Register Address feature is unique to BL602!</strong></p>
<p>The I2C Register Address feature is <strong>not available</strong> on STM32 Blue Pill, Nordic nRF52, GigaDevice GD32 VF103 (RISC-V), ESP32, … Not even on Raspberry Pi Pico!</p>
<p>(Though it seems to be supported on <a href="https://mcuxpresso.nxp.com/api_doc/dev/116/group__i2c.html">NXP Microcontrollers</a> as “I2C Subaddress”)</p>
<p>Thus <strong>BL602 I2C works a little differently</strong> from other microcontrollers.</p>
<p>This may complicate the support for I2C in Embedded Operating Systems like Mynewt, RIOT and Zephyr. (More about this in a while)</p>
<h2 id="i2c-terms"><a class="doc-anchor" href="#i2c-terms">§</a>4.4 I2C Terms</h2>
<p>The I2C Documentation in the <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en">BL602 Reference Manual</a> appears somewhat confusing because of the I2C Register Address feature. <a href="https://lupyuen.github.io/images/i2c-confuse.png">See this</a></p>
<p>In this article we shall standardise on these I2C Terms…</p>
<ol>
<li>
<p>We say <strong>“Device Address”</strong></p>
<p>(Instead of “Slave Address”, “Slave Device”)</p>
</li>
<li>
<p>We say <strong>“Register Address”</strong></p>
<p>(Instead of “Subaddress”, “Slave Device Address”, “Slave Device Register Address”)</p>
</li>
</ol>
<p><img src="https://lupyuen.github.io/images/i2c-cartoon2.png" alt="Start I2C Read" /></p>
<h1 id="start-i2c-read"><a class="doc-anchor" href="#start-i2c-read">§</a>5 Start I2C Read</h1>
<p>Now that we have created our I2C Message, lets watch it in action!</p>
<p>To begin reading data from our BME280 Sensor, we enter this command…</p>
<div class="example-wrap"><pre class="language-text"><code> i2c_start_read</code></pre></div>
<p>Lets find out what happens inside that command: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L420-L448"><code>sdk_app_i2c/demo.c</code></a></p>
<h2 id="create-i2c-message"><a class="doc-anchor" href="#create-i2c-message">§</a>5.1 Create I2C Message</h2>
<p>We start by creating the I2C Message. We have seen this code earlier for creating the message…</p>
<div class="example-wrap"><pre class="language-c"><code>// Define I2C message and buffer
static i2c_msg_t read_msg; // Message for reading I2C Data
static uint8_t read_buf[32]; // Buffer for reading I2C Data
static void test_i2c_start_read(char *buf, int len, int argc, char **argv) {
// Start reading data from I2C device
// Expect result 0x60 for BME280, 0x58 for BMP280
int data_len = 1; // Bytes to be read
memset(read_buf, 0, sizeof(read_buf));
// Set the I2C operation
read_msg.i2cx = 0; // I2C Port
read_msg.direct = I2C_M_READ; // Read I2C data
read_msg.block = I2C_M_BLOCK; // Wait until data has been read
// Set the I2C buffer
read_msg.buf = read_buf; // Read buffer
read_msg.len = data_len; // Number of bytes to be read
read_msg.idex = 0; // Index of next byte to be read into buf
// Set device address and register address
read_msg.addr = 0x77; // BME280 I2C Secondary Address (Primary Address is 0x76)
read_msg.subflag = 1; // Enable Register Address
read_msg.subaddr = 0xd0; // Register Address (BME280 Chip ID)
read_msg.sublen = 1; // Length of Register Address (bytes)</code></pre></div>
<p>(For I2C Write Operation <code>I2C_M_WRITE</code>: The Message buffer field <code>buf</code> should point to a byte array that contains the I2C Data that will be written to the I2C Register)</p>
<h2 id="start-i2c-transfer"><a class="doc-anchor" href="#start-i2c-transfer">§</a>5.2 Start I2C Transfer</h2>
<p>Now we start the I2C data transfer…</p>
<div class="example-wrap"><pre class="language-c"><code> // Start the I2C transfer and enable I2C interrupts
gpstmsg = &amp;read_msg;
i2c_transfer_start(&amp;read_msg);
// do_read_data will be called to read data
// in the I2C Interrupt Handler (test_i2c_transferbytes)
}</code></pre></div>
<p>We point <code>gpstmsg</code> to our I2C Message. (Will be used for saving data into our buffer)</p>
<p>Then we call <code>i2c_transfer_start</code> to start the I2C data transfer and enable the I2C Interrupts.</p>
<p><code>i2c_transfer_start</code> is defined in the <strong>Low Level I2C HAL</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a></p>
<p><em>How does BL602 receive the I2C data from our BME280 Sensor?</em></p>
<p>The I2C data transfer happens in the background, thanks to our <strong>I2C Interrupt Handler</strong>.</p>
<p>Our I2C Interrupt Handler receives the I2C data from the BME280 Sensor and populates our read buffer <code>read_buf</code></p>
<p>Lets go deep into our I2C Interrupt Handler…</p>
<p><img src="https://lupyuen.github.io/images/i2c-cartoon3.png" alt="Handle I2C Interrupts" /></p>
<h1 id="handle-i2c-interrupts"><a class="doc-anchor" href="#handle-i2c-interrupts">§</a>6 Handle I2C Interrupts</h1>
<p>Earlier we registered <code>test_i2c_interrupt_entry</code> as our Interrupt Handler for I2C Interrupts…</p>
<div class="example-wrap"><pre class="language-c"><code>// Register the I2C Interrupt Handler
bl_irq_register_with_ctx(
I2C_IRQn, // For I2C Interrupt:
test_i2c_interrupt_entry, // Interrupt Handler
&amp;gpstmsg // Pointer to current I2C Message
);</code></pre></div>
<p>And the current I2C Message <code>gpstmsg</code> will be passed as our Interrupt Context.</p>
<p>Lets find out how our Interrupt Handler handles I2C Interrupts: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L273-L328"><code>sdk_app_i2c/demo.c</code></a></p>
<h2 id="get-i2c-message-and-interrupt-reason"><a class="doc-anchor" href="#get-i2c-message-and-interrupt-reason">§</a>6.1 Get I2C Message and Interrupt Reason</h2>
<p>When an I2C Interrupt is triggered, we fetch the Interrupt Reason and the I2C Message (from the Interrupt Context)…</p>
<div class="example-wrap"><pre class="language-c"><code>/// I2C Interrupt Handler. Based on i2c_interrupt_entry in hal_i2c.c
static void test_i2c_interrupt_entry(void *ctx) {
// Fetch the current I2C Message from the Interrupt Context
i2c_msg_t *msg = *((i2c_msg_t **)ctx);
// Get the reason for the interrupt
uint32_t reason = BL_RD_REG(I2C_BASE, I2C_INT_STS);
// Handle each reason and increment the Interrupt Counters
count_int++; // Overall interrupts</code></pre></div>
<p>According to the <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en"><strong>BL602 Reference Manual</strong></a> there are 6 kinds of I2C Interrupts…</p>
<p><img src="https://lupyuen.github.io/images/i2c-interrupt.png" alt="BL602 I2C Interrupts" /></p>
<p>Some good… Some not so good. Lets handle each type of interrupt…</p>
<h2 id="i2c-data-received"><a class="doc-anchor" href="#i2c-data-received">§</a>6.2 I2C Data Received</h2>
<p>(For I2C Read Operation)</p>
<p>When we receive data from our I2C Sensor… Its good news!</p>
<div class="example-wrap"><pre class="language-c"><code> if (BL_IS_REG_BIT_SET(reason, I2C_RXF_INT)) {
// Receive FIFO Ready
count_rfx++;
msg-&gt;event = EV_I2C_RXF_INT;
// Should not return</code></pre></div>
<p>This condition flows through to the end of our Interrupt Handler, and calls <code>test_i2c_transferbytes</code> to copy the received data into our Message Buffer and receive more data.</p>
<h2 id="i2c-transfer-end"><a class="doc-anchor" href="#i2c-transfer-end">§</a>6.3 I2C Transfer End</h2>
<p>If the I2C data transfer is ending, we call <code>test_i2c_stop</code> to disable the I2C Port.</p>
<div class="example-wrap"><pre class="language-c"><code> } else if (BL_IS_REG_BIT_SET(reason, I2C_END_INT)) {
// Transfer End
count_end++;
msg-&gt;event = EV_I2C_END_INT;
test_i2c_stop(msg);
return; // Stop now</code></pre></div>
<p>This condition quits our Interrupt Handler right away.</p>
<h2 id="i2c-no-acknowledge"><a class="doc-anchor" href="#i2c-no-acknowledge">§</a>6.4 I2C No Acknowledge</h2>
<p>This is bad… We encounter I2C No Acknowledge usually when the I2C Device Address is misconfigured (say <code>0x76</code> instead of <code>0x77</code>).</p>
<div class="example-wrap"><pre class="language-c"><code> } else if (BL_IS_REG_BIT_SET(reason, I2C_NAK_INT)) {
// No Acknowledge
count_nak++;
msg-&gt;event = EV_I2C_NAK_INT;
test_i2c_stop(msg);
return; // Stop now</code></pre></div>
<p>We disable the I2C Port and quit the Interrupt Handler right away.</p>
<h2 id="i2c-data-transmitted"><a class="doc-anchor" href="#i2c-data-transmitted">§</a>6.5 I2C Data Transmitted</h2>
<p>(For I2C Write Operation)</p>
<p>This is good, it means that the queued data has been transmitted…</p>
<div class="example-wrap"><pre class="language-c"><code> } else if (BL_IS_REG_BIT_SET(reason, I2C_TXF_INT)) {
// Transmit FIFO Ready
count_txf++;
msg-&gt;event = EV_I2C_TXF_INT;
// Should not return</code></pre></div>
<p>This condition flows through to the end of our Interrupt Handler, and calls <code>test_i2c_transferbytes</code> to transmit the next 4 bytes of data from our Message Buffer.</p>
<h2 id="i2c-errors"><a class="doc-anchor" href="#i2c-errors">§</a>6.6 I2C Errors</h2>
<p>Lastly we handle the remaining errors: <strong>Arbitration Lost, FIFO Error, Unknown Error</strong></p>
<div class="example-wrap"><pre class="language-c"><code> } else if (BL_IS_REG_BIT_SET(reason, I2C_ARB_INT)) {
// Arbitration Lost
count_arb++;
msg-&gt;event = EV_I2C_ARB_INT;
test_i2c_stop(msg);
return; // Stop now
} else if (BL_IS_REG_BIT_SET(reason,I2C_FER_INT)) {
// FIFO Error
count_fer++;
msg-&gt;event = EV_I2C_FER_INT;
test_i2c_stop(msg);
return; // Stop now
} else {
// Unknown Error
count_unk++;
msg-&gt;event = EV_I2C_UNKNOW_INT;
test_i2c_stop(msg);
// Should not return
}</code></pre></div>
<p>We disable the I2C Port and quit the Interrupt Handler right away. (Except for Unknown Error)</p>
<h2 id="transfer-data"><a class="doc-anchor" href="#transfer-data">§</a>6.7 Transfer Data</h2>
<p>For I2C Data Received and I2C Data Transmitted, our Interrupt Handler flows through to this code…</p>
<div class="example-wrap"><pre class="language-c"><code> // For Receive FIFO Ready and Transmit FIFO Ready, transfer 4 bytes of data
test_i2c_transferbytes(msg);
}</code></pre></div>
<p><code>test_i2c_transferbytes</code> does the following…</p>
<ul>
<li>
<p><strong>For I2C Read Operation:</strong> Copy the received data into our Message Buffer (4 bytes at a time) and receive more data.</p>
</li>
<li>
<p><strong>For I2C Write Operation:</strong> Transmit the next 4 bytes of data from our Message Buffer.</p>
</li>
</ul>
<p>More about this in the next section…</p>
<p><img src="https://lupyuen.github.io/images/i2c-cartoon1.png" alt="Transmit and Receive I2C Data" /></p>
<h1 id="transmit-and-receive-i2c-data"><a class="doc-anchor" href="#transmit-and-receive-i2c-data">§</a>7 Transmit and Receive I2C Data</h1>
<p>BL602 I2C has a <strong>FIFO Queue (First In First Out) of 4 bytes</strong> for transmitting and receiving I2C data.</p>
<p>Our I2C Interrupt Handler calls <code>test_i2c_transferbytes</code> to transmit and receive data in 4-byte chunks.</p>
<p>Heres how it works for I2C Write and I2C Read Operations: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L249-L271"><code>sdk_app_i2c/demo.c</code></a></p>
<h2 id="i2c-write-operation"><a class="doc-anchor" href="#i2c-write-operation">§</a>7.1 I2C Write Operation</h2>
<p>In an I2C Write Operation, we handle the I2C Data Transmitted Interrupt by <strong>transmitting the next 4 bytes from the Message Buffer</strong></p>
<div class="example-wrap"><pre class="language-c"><code>/// For Rx FIFO Ready and Tx FIFO Ready, transfer 4 bytes of data.
/// Called by I2C Interrupt Handler. Based on i2c_transferbytes in hal_i2c.c
static void test_i2c_transferbytes(i2c_msg_t *msg) {
// For I2C Write Operation and I2C Data Transmitted Interrupt...
if (msg-&gt;direct == I2C_M_WRITE &amp;&amp; msg-&gt;event == EV_I2C_TXF_INT) {
if (msg-&gt;idex &lt; msg-&gt;len) {
// If there is buffer data to be transmitted, transmit 4 bytes from buffer
do_write_data(msg);
} else if (msg-&gt;idex == msg-&gt;len) {
// Otherwise suppress the Data Transmitted Interrupts
I2C_IntMask(msg-&gt;i2cx, I2C_TX_FIFO_READY_INT, MASK);
} </code></pre></div>
<p>If there is no more data to be transmitted, we suppress the I2C Data Transmitted Interrupts.</p>
<p><code>do_write_data</code> is defined in the <strong>Low Level I2C HAL</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a></p>
<h2 id="i2c-read-operation"><a class="doc-anchor" href="#i2c-read-operation">§</a>7.2 I2C Read Operation</h2>
<p>In an I2C Read Operation, we handle the I2C Data Received Interrupt by <strong>copying the received bytes into the Message Buffer, 4 bytes at a time</strong></p>
<div class="example-wrap"><pre class="language-c"><code> // For I2C Read Operation and I2C Data Received Interrupt...
} else if (msg-&gt;direct == I2C_M_READ &amp;&amp; msg-&gt;event == EV_I2C_RXF_INT) {
if (msg-&gt;idex &lt; msg-&gt;len) {
// If there is data to be received, copy 4 bytes into buffer
do_read_data(msg);
} else {
// Otherwise suppress the Data Received Interrupts
I2C_IntMask(msg-&gt;i2cx, I2C_RX_FIFO_READY_INT, MASK);
}
}
}</code></pre></div>
<p>If there is no more data to be received, we suppress the I2C Data Received Interrupts.</p>
<p><code>do_read_data</code> is defined in the <strong>Low Level I2C HAL</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a></p>
<p>(FYI: <code>test_i2c_transferbytes</code> is the fixed version of <code>i2c_transferbytes</code> from the High Level I2C HAL <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c"><code>hal_i2c.c</code></a>. <a href="https://lupyuen.github.io/images/i2c-transferbytes.png">Heres the fix</a>)</p>
<p><img src="https://lupyuen.github.io/images/i2c-cartoon5.png" alt="Stop I2C Read" /></p>
<h1 id="stop-i2c-read"><a class="doc-anchor" href="#stop-i2c-read">§</a>8 Stop I2C Read</h1>
<p>Heres the final command that well enter into the BL602 Firmware… It terminates the I2C transfer.</p>
<div class="example-wrap"><pre class="language-text"><code> i2c_stop_read</code></pre></div>
<p>This command calls <code>test_i2c_stop</code> to close the I2C Port: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L450-L460"><code>sdk_app_i2c/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>/// Stop reading data from I2C device
static void test_i2c_stop_read(char *buf, int len, int argc, char **argv) {
// Stop the I2C transfer on I2C Port 0
test_i2c_stop(&amp;read_msg);
// Dump the data received
for (int i = 0; i &lt; read_msg.len; i++) {
printf(&quot;%02x\n&quot;, read_buf[i]);
}
}</code></pre></div>
<p>The command also dumps the data received in the I2C Message Buffer.</p>
<p><code>test_i2c_stop</code> closes the I2C Port like so: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L236-L247"><code>sdk_app_i2c/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>/// Stop the I2C Transfer. Called by I2C Interrupt Handler.
/// Based on i2c_callback in hal_i2c.c
static void test_i2c_stop(i2c_msg_t *msg) {
// Disable I2C Port
I2C_Disable(msg-&gt;i2cx);
// Suppress all I2C Interrupts
I2C_IntMask(msg-&gt;i2cx, I2C_INT_ALL, MASK);
// Clear any error status
i2c_clear_status(msg-&gt;i2cx);
}</code></pre></div>
<p><code>i2c_clear_status</code> is defined in the <strong>Low Level I2C HAL</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a></p>
<p><img src="https://lupyuen.github.io/images/i2c-success.png" alt="Reading BME280 with sdk_app_i2c firmware" /></p>
<p><em>Reading BME280 with sdk_app_i2c firmware</em></p>
<h1 id="build-and-run-the-firmware"><a class="doc-anchor" href="#build-and-run-the-firmware">§</a>9 Build and Run the Firmware</h1>
<p>Weve read the I2C code… Lets download, flash and run the modded <a href="https://github.com/lupyuen/bl_iot_sdk/tree/master/customer_app/sdk_app_i2c"><code>sdk_app_i2c</code></a> firmware!</p>
<h2 id="build-the-firmware"><a class="doc-anchor" href="#build-the-firmware">§</a>9.1 Build the firmware</h2>
<p>Download the Firmware Binary File <strong><code>sdk_app_i2c.bin</code></strong> from…</p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/releases/tag/v2.0.0"><strong>Binary Release of <code>sdk_app_i2c</code></strong></a></li>
</ul>
<p>Alternatively, we may build the Firmware Binary File <code>sdk_app_i2c.bin</code> from the <a href="https://github.com/lupyuen/bl_iot_sdk/tree/master/customer_app/sdk_app_i2c">source code</a></p>
<div class="example-wrap"><pre class="language-bash"><code>## Download the master branch of lupyuen&#39;s bl_iot_sdk
git clone --recursive --branch master https://github.com/lupyuen/bl_iot_sdk
cd bl_iot_sdk/customer_app/sdk_app_i2c
## TODO: Change this to the full path of bl_iot_sdk
export BL60X_SDK_PATH=$HOME/bl_iot_sdk
export CONFIG_CHIP_NAME=BL602
make
## For WSL: Copy the firmware to /mnt/c/blflash, which refers to c:\blflash in Windows
mkdir /mnt/c/blflash
cp build_out/sdk_app_i2c.bin /mnt/c/blflash</code></pre></div>
<p><a href="https://lupyuen.github.io/articles/pinecone#building-firmware">More details on building bl_iot_sdk</a></p>
<h2 id="flash-the-firmware"><a class="doc-anchor" href="#flash-the-firmware">§</a>9.2 Flash the firmware</h2>
<p>Follow these steps to install <code>blflash</code></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 <code>sdk_app_i2c.bin</code> has been copied to the <code>blflash</code> folder.</p>
<p>Set BL602 to <strong>Flashing Mode</strong> and restart the board.</p>
<p><strong>For PineCone:</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 <code>sdk_app_i2c.bin</code> to BL602 over UART…</p>
<div class="example-wrap"><pre class="language-bash"><code>## For Linux:
blflash flash build_out/sdk_app_i2c.bin \
--port /dev/ttyUSB0
## For macOS:
blflash flash build_out/sdk_app_i2c.bin \
--port /dev/tty.usbserial-1420 \
--initial-baud-rate 230400 \
--baud-rate 230400
## For Windows: Change COM5 to the BL602 Serial Port
blflash flash c:\blflash\sdk_app_i2c.bin --port COM5</code></pre></div>
<p>(For WSL: Do this under plain old Windows CMD, not WSL, because blflash needs to access the COM port)</p>
<p><a href="https://lupyuen.github.io/articles/flash#flash-the-firmware">More details on flashing firmware</a></p>
<h2 id="run-the-firmware"><a class="doc-anchor" href="#run-the-firmware">§</a>9.3 Run the firmware</h2>
<p>Set BL602 to <strong>Normal Mode</strong> (Non-Flashing) and restart the board…</p>
<p><strong>For PineCone:</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 BL602s 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><a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">More details on connecting to BL602</a></p>
<h2 id="enter-i2c-commands"><a class="doc-anchor" href="#enter-i2c-commands">§</a>9.4 Enter I2C commands</h2>
<p>Lets enter some I2C commands to read our BME280 Sensor!</p>
<ol>
<li>
<p>Press Enter to reveal the command prompt.</p>
</li>
<li>
<p>Enter <code>help</code> to see the available commands…</p>
<div class="example-wrap"><pre class="language-text"><code>help
====User Commands====
i2c_status : I2C status
i2c_init : Init I2C port
i2c_start_read : Start reading I2C data
i2c_stop_read : Stop reading I2C data</code></pre></div></li>
<li>
<p>First we <strong>initialise our I2C Port</strong>.</p>
<p>Enter this command…</p>
<div class="example-wrap"><pre class="language-text"><code>i2c_init</code></pre></div>
<p>(Earlier weve seen the code for this command)</p>
</li>
<li>
<p>Before doing any I2C business, lets <strong>dump the Interrupt Counters</strong> to see which I2C Interrupts get triggered…</p>
<div class="example-wrap"><pre class="language-text"><code>i2c_status</code></pre></div>
<p>We should see…</p>
<div class="example-wrap"><pre class="language-text"><code>Interrupts: 0 NACK: 0
Trans End: 0 Arb Lost: 0
Tx Ready: 0 FIFO Error: 0
Rx Ready: 0 Unknown: 0</code></pre></div>
<p>Which means that no I2C Interrupts have been triggered yet.</p>
</li>
<li>
<p>Now we <strong>start the I2C Read Operation</strong></p>
<div class="example-wrap"><pre class="language-text"><code>i2c_start_read</code></pre></div>
<p>(Weve seen the code for this command as well)</p>
</li>
<li>
<p>Again we dump the Interrupt Counters…</p>
<div class="example-wrap"><pre class="language-text"><code>i2c_status</code></pre></div>
<p>Aha Something Different! We have encountered <strong>one interrupt for Data Received</strong> (Rx Ready), because BME280 has returned some I2C data to BL602…</p>
<div class="example-wrap"><pre class="language-text"><code>Interrupts: 2 NACK: 0
Trans End: 1 Arb Lost: 0
Tx Ready: 0 FIFO Error: 0
Rx Ready: 1 Unknown: 0 </code></pre></div>
<p>After receiving the data (one byte) from BME280 (and saving it), our Interrupt Handler terminates the I2C connection.</p>
<p>Hence we see <strong>one interrupt for Transaction End</strong>. Were done!</p>
</li>
<li>
<p>To <strong>check the data received</strong>, enter this command…</p>
<div class="example-wrap"><pre class="language-text"><code>i2c_stop_read</code></pre></div>
<p>Remember that were reading the Chip ID from BME280. We should see this Chip ID…</p>
<div class="example-wrap"><pre class="language-text"><code>60</code></pre></div>
<p>(For BMP280 the Chip ID is <code>0x58</code>)</p>
</li>
</ol>
<p>Congratulations! We have successfully read the BME280 Sensor from BL602 over I2C!</p>
<h1 id="why-we-need-an-embedded-os-for-i2c"><a class="doc-anchor" href="#why-we-need-an-embedded-os-for-i2c">§</a>10 Why we need an Embedded OS for I2C</h1>
<p>We have 2 problems when calling the Low Level I2C HAL…</p>
<ol>
<li>
<p>Our program <strong>doesnt wait for I2C Read/Write Operations to complete.</strong></p>
<p>If we enter the command <code>i2c_stop_read</code> really quickly, it might <strong>terminate the I2C Read Operation before its done!</strong></p>
<p>(Assuming we can type at superhuman speed)</p>
<p>The I2C data transfer happens in the background, executed by the Interrupt Handler. The Foreground Task isnt notified when the data transfer is complete.</p>
<p><strong>Solution:</strong> Our Interrupt Handler should use a <strong>Semaphore or a Message Queue</strong> to notify the Foreground Task when the data transfer is done.</p>
</li>
<li>
<p>Our program uses <strong>shared variables for I2C Read/Write Operations.</strong></p>
<p>Remember these?</p>
<div class="example-wrap"><pre class="language-c"><code>static i2c_msg_t *gpstmsg; // Global pointer to current I2C Message
static i2c_msg_t read_msg; // Message for reading I2C Data
static uint8_t read_buf[32]; // Buffer for reading I2C Data</code></pre></div>
<p>These global variables will get really confused when we talk to multiple I2C Sensors.</p>
<p>In fact, the entire <strong>I2C Port is a shared resource</strong>! It needs to be protected from overlapping I2C Operations.</p>
<p><strong>Solution:</strong> Our program should use a <strong>Semaphore or a Mutex Lock</strong> to prevent concurrent updates to the shared variables.</p>
<p>We could use a <strong>Message Queue to enqueue I2C Requests</strong> and execute the I2C Requests one at a time.</p>
</li>
</ol>
<h2 id="high-level-hal-unmasked"><a class="doc-anchor" href="#high-level-hal-unmasked">§</a>10.1 High Level HAL unmasked</h2>
<p><em>What happens when we implement the two Solutions in FreeRTOS?</em></p>
<p>When we implement these two Solutions in FreeRTOS… Well get the <strong>High Level I2C HAL!</strong> (See <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c"><code>hal_i2c.c</code></a>)</p>
<p>Hence the High Level I2C HAL (which calls FreeRTOS) is <strong>fully functional today</strong> for processing I2C Sensor Data.</p>
<p><em>But the High Level I2C HAL lacks documentation… How do we use it?</em></p>
<p>The code explained in this article looks highly similar to the High Level I2C HAL.</p>
<p>Heres the list of functions weve seen in this article, and their equivalent functions in the High Level I2C HAL…</p>
<div><table><thead><tr><th style="text-align: left">Function In <br> This Article</th><th style="text-align: left">Function In <br> High Level HAL</th></tr></thead><tbody>
<tr><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L343-L369"><code>test_i2c</code><br><code>_init</code></a></td><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c#L272-L298"><code>hal_i2c</code><br><code>_init</code></a></td></tr>
<tr><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L420-L448"><code>test_i2c</code><br><code>_start_read</code></a></td><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c#L300-L324"><code>hal_i2c</code><br><code>_read_block</code></a></td></tr>
<tr><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L273-L328"><code>test_i2c</code><br><code>_interrupt</code><br><code>_entry</code></a></td><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c#L97-L133"><code>i2c</code><br><code>_interrupt</code><br><code>_entry</code></a></td></tr>
<tr><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L249-L271"><code>test_i2c</code><br><code>_transferbytes</code></a></td><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c#L74-L95"><code>i2c</code><br><code>_transferbytes</code></a></td></tr>
<tr><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L236-L247"><code>test_i2c</code><br><code>_stop</code></a></td><td style="text-align: left"><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c#L52-L72"><code>i2c</code><br><code>_callback</code></a></td></tr>
</tbody></table>
</div>
<ul>
<li>
<p><a href="https://help.aliyun.com/document_detail/161064.html?spm=a2c4g.11186623.6.577.492c4564jOCOjU">See the Chinese docs for High Level I2C HAL</a></p>
</li>
<li>
<p><a href="https://github.com/bouffalolab/bl_iot_sdk/tree/master/customer_app/sdk_app_i2c">See the original (unmodified) High Level I2C HAL Demo</a></p>
</li>
</ul>
<h2 id="high-level-hal-without-freertos"><a class="doc-anchor" href="#high-level-hal-without-freertos">§</a>10.2 High Level HAL without FreeRTOS</h2>
<p><em>Instead of FreeRTOS… Can we implement the two Solutions with Mynewt, RIOT or Zephyr?</em></p>
<p>Yes! We may implement the two Solutions with any Embedded Operating System that supports <strong>Task Synchronisation</strong> features (Semaphore, Mutex, Message Queue).</p>
<p>Thus to do meaningful work with I2C (like reading I2C Sensor Data periodically and processing the data), we need to use the <strong>Low Level I2C HAL together with an Embedded Operating System</strong>.</p>
<p>The High Level I2C HAL is a great reference that guides us on the proper implementation of the two Solutions on any operating system.</p>
<p><img src="https://lupyuen.github.io/images/i2c-hack.jpg" alt="Hacking BL602 and BME280 on a Saturday night" /></p>
<p><em>Hacking BL602 and BME280 on a Saturday Night</em></p>
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>11 Whats Next</h1>
<p>Now that we understand the inner workings of I2C on BL602…</p>
<ol>
<li>
<p>Lets <strong>port BL602 I2C to Mynewt</strong> and complete the I2C implementation…</p>
<p><a href="https://lupyuen.github.io/articles/gpio">(Like we did for BL602 GPIO)</a></p>
</li>
<li>
<p>Also <strong>work on BL602 SPI</strong>! Check out the article…</p>
<p><a href="https://lupyuen.github.io/articles/spi"><strong>“PineCone BL602 talks SPI too”</strong></a></p>
<p><a href="https://twitter.com/MisterTechBlog/status/1354776244018057218?s=20">(I have received ST7789 SPI displays for testing… Many thanks to my Generous Sponsor! 😀)</a></p>
</li>
</ol>
<p>Theres plenty more code in the <a href="https://github.com/bouffalolab/bl_iot_sdk"><strong>BL602 IoT SDK</strong></a> to be deciphered and documented: <strong>ADC, DAC, WiFi, Bluetooth LE,</strong></p>
<p><a href="https://wiki.pine64.org/wiki/Nutcracker"><strong>Come Join Us… Make BL602 Better!</strong></a></p>
<p>🙏 👍 😀</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/embedded_oc/comments/l7d469/pinecone_bl602_talks_to_i2c_sensors/?utm_source=share&amp;utm_medium=web2x&amp;context=3">Discuss this article on Reddit</a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/book">Read “The RISC-V BL602 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/i2c.md"><code>lupyuen.github.io/src/i2c.md</code></a></p>
<h1 id="notes"><a class="doc-anchor" href="#notes">§</a>12 Notes</h1>
<ol>
<li>
<p>Check out the <strong><a href="https://github.com/pine64/ArduinoCore-bouffalo/blob/main/libraries/Wire/src/Wire.cpp">BL602 I2C HAL for Arduino</a></strong></p>
</li>
<li>
<p>Why is BL602s <strong>I2C Register Address</strong> feature incompatible with Mynewt (and other embedded operating systems)?</p>
<p>Because Mynewt exposes an I2C API that <strong>controls the I2C Stop Bit explicitly</strong>. <a href="https://mynewt.apache.org/latest/os/modules/hal/hal_i2c/hal_i2c.html#c.hal_i2c_master_write">(See this <code>last_op</code> parameter)</a></p>
<p>When porting BL602 I2C to Mynewt, we need to reconcile the two styles of I2C coding: <strong>Register Address vs Stop Bit.</strong></p>
</li>
<li>
<p>We talked about reading I2C Registers… What about <strong>writing to I2C Registers</strong>?</p>
<p>The code should be similar. The demo program contains code for writing to I2C Registers, but it hasnt been tested. And it needs cleaning up. <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/demo.c#L376-L418">See this</a></p>
</li>
<li>
<p>Why arent we using <strong>DMA for I2C</strong>?</p>
<p>DMA for I2C (and SPI) sounds overkill for an IoT Gadget. We should keep the firmware simple and easy to maintain. (Until we have more maintainers)</p>
<p>Well come back later to implement DMA for I2C (and SPI) if we need to do any high-speed bulk data transfer.</p>
</li>
<li>
<p><strong>BL602 SPI</strong> doesnt have a Low Level HAL… It only comes as a High Level HAL with FreeRTOS. Which will be a challenging exploration. <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c">See this</a></p>
</li>
<li>
<p>This article is the expanded version of <a href="https://twitter.com/MisterTechBlog/status/1352937390776545281?s=19"><strong>this Twitter Thread</strong></a></p>
</li>
<li>
<p><strong>Quiz for the Reader:</strong> What could go wrong with this code?</p>
<p><img src="https://lupyuen.github.io/images/i2c-init.png" alt="i2c_gpio_init: What happens when i2cx is NOT I2C0" /></p>
<p><a href="https://twitter.com/MisterTechBlog/status/1351441955637534720?s=20"><strong>Heres The Answer</strong></a></p>
<p>(From Low Level I2C HAL <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a>)</p>
</li>
<li>
<p><strong>Another Quiz for the Reader:</strong> Why does this code look dubious?</p>
<p><img src="https://lupyuen.github.io/images/i2c-transferbytes.png" alt="i2c_transferbytes: Assignment inside Condition" /></p>
<p><a href="https://github.com/bouffalolab/bl_iot_sdk/issues/33"><strong>Heres The Answer</strong></a></p>
<p>(From High Level I2C HAL <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c"><code>hal_i2c.c</code></a>)</p>
</li>
</ol>
<p><img src="https://lupyuen.github.io/images/i2c-cartoon8.png" alt="Bug" /></p>
<h1 id="appendix-how-to-troubleshoot-risc-v-exceptions"><a class="doc-anchor" href="#appendix-how-to-troubleshoot-risc-v-exceptions">§</a>13 Appendix: How to Troubleshoot RISC-V Exceptions</h1>
<p>Heres how I tracked down my first RISC-V Exception and fixed it…</p>
<p><img src="https://lupyuen.github.io/images/i2c-exception.png" alt="RISC-V Exception in sdk_app_i2c" /></p>
<p>When our program <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/"><code>sdk_app_i2c</code></a> is sending I2C data, the program crashes with the RISC-V Exception shown above…</p>
<div class="example-wrap"><pre class="language-text"><code>start_write_data
Exception Entry---&gt;&gt;&gt;
mcause 30000007, mepc 23008fe2, mtval 00000014
Exception code: 7
msg: Store/AMO access fault</code></pre></div>
<p>What does this mean?</p>
<ul>
<li>
<p><strong><code>mcause</code> (Machine Cause Register)</strong>: Tells us the reason for the exception. <a href="http://www.five-embeddev.com/riscv-isa-manual/latest/machine.html#sec:mcause">More details</a></p>
<p>The Exception Code is 7 (Store/AMO Access Fault), which means that we have accessed an invalid memory address.</p>
<p>(Probably a bad pointer)</p>
<p><a href="http://www.five-embeddev.com/riscv-isa-manual/latest/machine.html#sec:mcause">List of RISC-V Exception Codes</a></p>
</li>
<li>
<p><strong><code>mepc</code> (Machine Exception Program Counter)</strong>: The address of the code that caused the exception. <a href="http://www.five-embeddev.com/riscv-isa-manual/latest/machine.html#machine-exception-program-counter-mepc">More details</a></p>
<p>Well look up the code address <code>0x2300 8fe2</code> in a while.</p>
</li>
<li>
<p><strong><code>mtval</code> (Machine Trap Value Register)</strong>: The invalid address that was accessed. <a href="http://www.five-embeddev.com/riscv-isa-manual/latest/machine.html#machine-trap-value-register-mtval">More details</a></p>
<p>Our program attempted to access the invalid address <code>0x000 00014</code> and crashed.</p>
<p>Looks like a null pointer problem!</p>
</li>
</ul>
<p>Lets track down code address <code>0x2300 8fe2</code> and find out why it caused the exception…</p>
<p><img src="https://lupyuen.github.io/images/i2c-disassembly.png" alt="RISC-V Disassembly" /></p>
<ol>
<li>
<p>According to the RISC-V Disassembly <a href="https://github.com/lupyuen/bl_iot_sdk/releases/download/v1.0.1/sdk_app_i2c.S"><code>sdk_app_i2c.S</code></a>, the code address <code>0x2300 8fe2</code> is located in the I2C Interrupt Handler of the BL602 I2C HAL (See pic)</p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c#L97-L133"><code>i2c_interrupt_entry</code> in <code>hal_i2c.c</code></a></li>
</ul>
</li>
<li>
<p>Why did it crash? Because the Interrupt Context <code>ctx</code> is null!</p>
<p>In fact, the I2C Interrupt Handler <code>i2c_interrupt_entry</code> shouldnt have been called.</p>
<p>It comes from the High Level HAL <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_i2c.c"><code>hal_i2c.c</code></a>, but were actually using the Low Level HAL <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/bl_i2c.c"><code>bl_i2c.c</code></a>.</p>
</li>
<li>
<p>Why was <code>i2c_interrupt_entry</code> set as the I2C Interrupt Handler?</p>
<p>Because <code>hal_i2c_init</code> was called here…</p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/sdk_app_i2c/main.c#L159-L199"><code>aos_loop_proc</code> in <code>main.c</code></a></li>
</ul>
</li>
</ol>
<p><img src="https://lupyuen.github.io/images/i2c-inithal.png" alt="I2C Init HAL" /></p>
<p>After commenting out <code>hal_i2c_init</code>, the program no longer uses <code>i2c_interrupt_entry</code> as the I2C Interrupt Handler.</p>
<p>And no more crashing!</p>
<p>For more RISC-V Exception troubleshooting tips, check out <strong>BL602 Stack Trace and BL602 Stack Dump</strong></p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/lora2#bl602-stack-trace"><strong>“BL602 Stack Trace”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/lora2#bl602-stack-dump"><strong>“BL602 Stack Dump”</strong></a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/lora2#bl602-assertion-failures"><strong>“BL602 Assertion Failures”</strong></a></p>
</li>
</ul>
<p><em>How did we get the RISC-V Disassembly?</em></p>
<p>We generate RISC-V Disassembly <code>sdk_app_i2c.S</code> from ELF Executable <code>sdk_app_i2c.elf</code> with this command…</p>
<div class="example-wrap"><pre class="language-bash"><code>riscv-none-embed-objdump \
-t -S --demangle --line-numbers --wide \
sdk_app_i2c.elf \
&gt;sdk_app_i2c.S \
2&gt;&amp;1</code></pre></div>
<p><em>Is it safe to comment out <code>hal_i2c_init</code>?</em></p>
<p>Not quite. When we comment out <code>hal_i2c_init</code>, we disable the High Level I2C HAL functions in our demo firmware <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/"><code>sdk_app_i2c</code></a></p>
<p>Thats the reason why we havent merged the <code>i2c</code> branch to the <code>master</code> branch…</p>
<ul>
<li>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/"><strong><code>i2c</code> Branch</strong></a> is used for testing Low Level I2C HAL</p>
</li>
<li>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_i2c/"><strong><code>master</code> Branch</strong></a> is used for testing High Level I2C HAL</p>
</li>
</ul>
<p>(The proper fix is to create a new command that calls <code>hal_i2c_init</code>)</p>
<p><em>What are the <code>aos</code> functions in the code above?</em></p>
<p>The <code>aos</code> functions are defined in <a href="https://github.com/alibaba/AliOS-Things">AliOS</a>. Remember that the High Level I2C HAL is called by AliOS Firmware.</p>
<p><img src="https://lupyuen.github.io/images/i2c-cartoon7.png" alt="Bus Pirate" /></p>
<h1 id="appendix-test-bme280-with-bus-pirate"><a class="doc-anchor" href="#appendix-test-bme280-with-bus-pirate">§</a>14 Appendix: Test BME280 with Bus Pirate</h1>
<p><a href="http://dangerousprototypes.com/docs/Bus_Pirate"><strong>Bus Pirate</strong></a> is a useful gadget for verifying whether our BME280 Sensor works OK. And for checking the I2C bytes that should be sent down the wire to BME280.</p>
<p><a href="http://dangerousprototypes.com/docs/I2C">(Bus Pirate also works as a simple Protocol Analyser for sniffing I2C data)</a></p>
<p>Heres how we test BME280 (or BMP280) with Bus Pirate…</p>
<p><img src="https://lupyuen.github.io/images/i2c-buspirate.jpg" alt="Bus Pirate connected to BME280" /></p>
<ol>
<li>
<p>Connect Bus Pirate to BME280 (or BMP280) according to the pic above…</p>
<div><table><thead><tr><th style="text-align: center">Bus Pirate Pin</th><th style="text-align: center">BME280 Pin</th></tr></thead><tbody>
<tr><td style="text-align: center"><strong><code>MOSI</code></strong></td><td style="text-align: center"><code>SDA</code></td></tr>
<tr><td style="text-align: center"><strong><code>CLK</code></strong></td><td style="text-align: center"><code>SCL</code></td></tr>
<tr><td style="text-align: center"><strong><code>3.3V</code></strong></td><td style="text-align: center"><code>3.3V</code></td></tr>
<tr><td style="text-align: center"><strong><code>GND</code></strong></td><td style="text-align: center"><code>GND</code></td></tr>
</tbody></table>
</div></li>
<li>
<p>Connect Bus Pirate to our computers USB port.</p>
<p>Open a Serial Terminal for Bus Pirate.</p>
</li>
<li>
<p>Enter <strong><code>m</code></strong> for the menu</p>
<p>Select <strong><code>I2C</code></strong></p>
<p><img src="https://lupyuen.github.io/images/i2c-buspirate2.png" alt="Bus Pirate Menu" /></p>
</li>
<li>
<p>Select <strong><code>Hardware</code></strong></p>
<p>Select <strong><code>400 kbps</code></strong></p>
<p><img src="https://lupyuen.github.io/images/i2c-buspirate3.png" alt="I2C Speed" /></p>
</li>
<li>
<p>Enter <strong><code>W</code></strong> to power up BME280</p>
<p><img src="https://lupyuen.github.io/images/i2c-buspirate4.png" alt="Power up BME280" /></p>
</li>
<li>
<p>Enter <strong><code>(1)</code></strong> to scan the I2C Bus</p>
<p><img src="https://lupyuen.github.io/images/i2c-buspirate5.png" alt="Scan I2C bus" /></p>
</li>
<li>
<p>Here we see that BME280 has been detected at I2C Address <code>0x77</code></p>
<p>I2C uses the even / odd address convention to indicate whether were writing or reading data. So our BME280 at address <code>0x77</code> appears as two Read / Write aliases…</p>
<ul>
<li>
<p><strong><code>0xEE</code></strong> = (<code>0x77</code> * 2) + 0, for Writing Data</p>
</li>
<li>
<p><strong><code>0xEF</code></strong> = (<code>0x77</code> * 2) + 1, for Reading Data</p>
</li>
</ul>
</li>
<li>
<p>To read Register <code>0xD0</code> (Chip ID) from BME280, enter this command…</p>
<div class="example-wrap"><pre class="language-text"><code>[0xee 0xd0] [0xef r]</code></pre></div>
<p>(More about this later)</p>
</li>
<li>
<p>We should see the result <code>0x60</code>, which is the Chip ID for BME280</p>
<p><img src="https://lupyuen.github.io/images/i2c-buspirate6.png" alt="Read register 0xD0" /></p>
<p>(For BMP280 the Chip ID is <code>0x58</code>)</p>
</li>
</ol>
<p>We tested BME280 with this Bus Pirate I2C command…</p>
<div class="example-wrap"><pre class="language-text"><code> [0xee 0xd0] [0xef r]</code></pre></div>
<p>This means that Bus Pirate will initiate two I2C Transactions, indicated by <strong><code>[ ... ]</code></strong></p>
<ol>
<li>
<p><strong>In the First I2C Transaction:</strong> Bus Pirate sends <strong><code>0xEE</code></strong> to indicate a Write Transaction (for address <code>0x77</code>).</p>
<p>Then it sends the I2C Register to be read: <strong><code>0xD0</code></strong> (Chip ID)</p>
</li>
<li>
<p><strong>In the Second I2C Transaction:</strong> Bus Pirate sends <strong><code>0xEF</code></strong> to indicate a Read Transaction (for address <code>0x77</code>).</p>
<p>BME280 returns the value of the Chip ID Register, indicated by <strong><code>r</code></strong></p>
</li>
</ol>
<p>To sum up: Bus Pirate initiates two <code>[ ... ]</code> transactions. The transactions will send 3 bytes (<code>0xEE</code>, <code>0xD0</code>, <code>0xEF</code>) and receive 1 byte (<code>0x60</code>).</p>
<p>This is identical to the I2C data transmitted by BL602 to BME280 that have seen earlier in the article…</p>
<div class="example-wrap"><pre class="language-text"><code> [Start] 0xEE 0xD0 [Stop]
[Start] 0xEF [Read] [Stop]</code></pre></div>
<p>For help on other Bus Pirate commands, enter <strong><code>?</code></strong></p>
<p><img src="https://lupyuen.github.io/images/i2c-buspirate1.png" alt="Bus Pirate Help" /></p>
<p><a href="http://dangerousprototypes.com/docs/I2C">Check out the I2C Guide for Bus Pirate</a></p>
<p><img src="https://lupyuen.github.io/images/i2c-sketch.jpg" alt="Sketching I2C cartoons" /></p>
<p><em>Sketching I2C cartoons. <a href="https://github.com/lupyuen/lupyuen.github.io/releases/tag/v1.0.0">Download the Photoshop images</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>