lupyuen.org/articles/spi.html

1880 lines
No EOL
113 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 SPI too!</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 SPI too!"
data-rh="true">
<meta property="og:description"
content="PineCone BL602 RISC-V Board talks to BME280 Sensor over SPI... Let's find out how"
data-rh="true">
<meta property="og:image"
content="https://lupyuen.github.io/images/spi-title.jpg">
<meta property="og:type"
content="article" data-rh="true">
<link rel="canonical" href="https://lupyuen.org/articles/spi.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 SPI too!</h1>
<nav id="rustdoc"><ul>
<li><a href="#times-are-a-changin" title="Times Are a-Changin">1 Times Are a-Changin</a><ul></ul></li>
<li><a href="#bl602-hardware-abstraction-layer-for-spi" title="BL602 Hardware Abstraction Layer for SPI">2 BL602 Hardware Abstraction Layer for SPI</a><ul></ul></li>
<li><a href="#connect-bl602-to-bme280-spi-sensor" title="Connect BL602 to BME280 SPI Sensor">3 Connect BL602 to BME280 SPI Sensor</a><ul>
<li><a href="#selecting-spi-pins" title="Selecting SPI Pins">3.1 Selecting SPI Pins</a><ul></ul></li>
<li><a href="#spi-protocol-for-bme280" title="SPI Protocol for BME280">3.2 SPI Protocol for BME280</a><ul></ul></li></ul></li>
<li><a href="#initialise-spi-port" title="Initialise SPI Port">4 Initialise SPI Port</a><ul></ul></li>
<li><a href="#transfer-spi-data" title="Transfer SPI Data">5 Transfer SPI Data</a><ul>
<li><a href="#transmit-and-receive-buffers" title="Transmit and Receive Buffers">5.1 Transmit and Receive Buffers</a><ul></ul></li>
<li><a href="#initialise-spi-buffers-and-transfers" title="Initialise SPI Buffers and Transfers">5.2 Initialise SPI Buffers and Transfers</a><ul></ul></li>
<li><a href="#first-spi-transfer" title="First SPI Transfer">5.3 First SPI Transfer</a><ul></ul></li>
<li><a href="#second-spi-transfer" title="Second SPI Transfer">5.4 Second SPI Transfer</a><ul></ul></li>
<li><a href="#execute-the-spi-transfers" title="Execute the SPI Transfers">5.5 Execute the SPI Transfers</a><ul></ul></li>
<li><a href="#spi-with-direct-memory-access" title="SPI with Direct Memory Access">5.6 SPI with Direct Memory Access</a><ul></ul></li></ul></li>
<li><a href="#build-and-run-the-firmware" title="Build and Run the Firmware">6 Build and Run the Firmware</a><ul>
<li><a href="#flash-the-firmware" title="Flash the firmware">6.1 Flash the firmware</a><ul></ul></li>
<li><a href="#run-the-firmware" title="Run the firmware">6.2 Run the firmware</a><ul></ul></li>
<li><a href="#enter-spi-commands" title="Enter SPI commands">6.3 Enter SPI commands</a><ul></ul></li></ul></li>
<li><a href="#control-our-own-chip-select-pin" title="Control our own Chip Select Pin">7 Control our own Chip Select Pin</a><ul>
<li><a href="#configure-chip-select-pin-as-gpio-output-pin" title="Configure Chip Select Pin as GPIO Output Pin">7.1 Configure Chip Select Pin as GPIO Output Pin</a><ul></ul></li>
<li><a href="#set-chip-select-to-low" title="Set Chip Select to Low">7.2 Set Chip Select to Low</a><ul></ul></li>
<li><a href="#set-chip-select-to-high" title="Set Chip Select to High">7.3 Set Chip Select to High</a><ul></ul></li></ul></li>
<li><a href="#spi-data-pins-are-flipped" title="SPI Data Pins are flipped">8 SPI Data Pins are flipped</a><ul></ul></li>
<li><a href="#spi-phase-looks-sus" title="SPI Phase looks sus">9 SPI Phase looks sus</a><ul></ul></li>
<li><a href="#unsolved-mysteries" title="Unsolved Mysteries">10 Unsolved Mysteries</a><ul></ul></li>
<li><a href="#port-bl602-spi-hal-to-other-operating-systems" title="Port BL602 SPI HAL to other Operating Systems">11 Port BL602 SPI HAL to other Operating Systems</a><ul></ul></li>
<li><a href="#whats-next" title="Whats Next">12 Whats Next</a><ul></ul></li>
<li><a href="#notes" title="Notes">13 Notes</a><ul></ul></li>
<li><a href="#appendix-test-bme280-spi-interface-with-bus-pirate" title="Appendix: Test BME280 SPI Interface with Bus Pirate">14 Appendix: Test BME280 SPI Interface with Bus Pirate</a><ul></ul></li>
<li><a href="#appendix-troubleshoot-bl602-spi-with-logic-analyser" title="Appendix: Troubleshoot BL602 SPI with Logic Analyser">15 Appendix: Troubleshoot BL602 SPI with Logic Analyser</a><ul></ul></li>
<li><a href="#appendix-inside-bl602-spi-hal" title="Appendix: Inside BL602 SPI HAL">16 Appendix: Inside BL602 SPI HAL</a><ul>
<li><a href="#definitions" title="Definitions">16.1 Definitions</a><ul></ul></li>
<li><a href="#spi_init-init-spi-port" title="spi_init: Init SPI Port">16.2 spi_init: Init SPI Port</a><ul></ul></li>
<li><a href="#hal_spi_set_rwspeed-set-spi-frequency" title="hal_spi_set_rwspeed: Set SPI Frequency">16.3 hal_spi_set_rwspeed: Set SPI Frequency</a><ul></ul></li>
<li><a href="#hal_spi_init-init-spi-pins-and-dma" title="hal_spi_init: Init SPI Pins and DMA">16.4 hal_spi_init: Init SPI Pins and DMA</a><ul></ul></li>
<li><a href="#hal_gpio_init-assign-spi-pins" title="hal_gpio_init: Assign SPI Pins">16.5 hal_gpio_init: Assign SPI Pins</a><ul></ul></li>
<li><a href="#hal_spi_dma_init-init-spi-dma" title="hal_spi_dma_init: Init SPI DMA">16.6 hal_spi_dma_init: Init SPI DMA</a><ul></ul></li>
<li><a href="#hal_spi_transfer-execute-spi-transfer" title="hal_spi_transfer: Execute SPI Transfer">16.7 hal_spi_transfer: Execute SPI Transfer</a><ul></ul></li>
<li><a href="#lli_list_init-create-dma-linked-list" title="lli_list_init: Create DMA Linked List">16.8 lli_list_init: Create DMA Linked List</a><ul></ul></li>
<li><a href="#hal_spi_dma_trans-execute-spi-transfer-with-dma" title="hal_spi_dma_trans: Execute SPI Transfer with DMA">16.9 hal_spi_dma_trans: Execute SPI Transfer with DMA</a><ul></ul></li>
<li><a href="#bl_spi0_dma_int_handler_tx-transmit-dma-interrupt-handler" title="bl_spi0_dma_int_handler_tx: Transmit DMA Interrupt Handler">16.10 bl_spi0_dma_int_handler_tx: Transmit DMA Interrupt Handler</a><ul></ul></li>
<li><a href="#bl_spi0_dma_int_handler_rx-receive-dma-interrupt-handler" title="bl_spi0_dma_int_handler_rx: Receive DMA Interrupt Handler">16.11 bl_spi0_dma_int_handler_rx: Receive DMA Interrupt Handler</a><ul></ul></li>
<li><a href="#dma-interrupt-counters" title="DMA Interrupt Counters">16.12 DMA Interrupt Counters</a><ul></ul></li></ul></li></ul></nav><p>📝 <em>7 Feb 2021</em></p>
<p><a href="https://lupyuen.github.io/articles/pinecone"><strong>PineCone</strong></a> and <a href="https://wiki.pine64.org/wiki/Nutcracker#Pinenut-01S_Module_information_and_schematics"><strong>Pinenut BL602</strong></a> work great with I2C Sensors. <a href="https://lupyuen.github.io/articles/i2c">(See this)</a></p>
<p>But what if were connecting BL602 to <strong>High Bandwidth</strong> peripherals… Like the ST7789 Display Controller and the SX1262 LoRa Transceiver?</p>
<p><strong>Well need SPI on BL602!</strong></p>
<p>Today we shall connect the BL602 RISC-V SoC to a simple SPI Sensor: <strong>BME280</strong></p>
<p>Well learn about the SPI quirks on BL602 and how we fixed them…</p>
<ol>
<li>
<p><strong>Serial Data In</strong> and <strong>Serial Data Out</strong> seem to be flipped</p>
</li>
<li>
<p><strong>SPI Phase 1</strong> behaves like Phase 0</p>
</li>
<li>
<p>Why we shouldnt use <strong>Pin 0 for SPI</strong></p>
</li>
<li>
<p>Why we should control <strong>SPI Chip Select</strong> ourselves</p>
</li>
</ol>
<p>Also well learn to <strong>troubleshoot BL602 SPI with a Logic Analyser</strong>.</p>
<p><img src="https://lupyuen.github.io/images/spi-title.jpg" alt="PineCone BL602 RISC-V Board connected to BME280 SPI Sensor" /></p>
<p><em>PineCone BL602 RISC-V Board connected to BME280 SPI Sensor</em></p>
<h1 id="times-are-a-changin"><a class="doc-anchor" href="#times-are-a-changin">§</a>1 Times Are a-Changin</h1>
<p>Humans evolve… So do the terms that we use!</p>
<p>This article will become obsolete quickly unless we adopt the <a href="https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names"><strong>new names for SPI Pins</strong></a></p>
<ul>
<li>
<p>Well say <strong>“Serial Data In (SDI)”</strong> <em>(instead of “MISO”)</em></p>
</li>
<li>
<p>And well say <strong>“Serial Data Out (SDO)”</strong> <em>(instead of “MOSI”)</em></p>
</li>
<li>
<p>Well refer to BL602 as the <strong>“SPI Controller”</strong></p>
</li>
<li>
<p>And BME280 as the <strong>“SPI Peripheral”</strong></p>
</li>
</ul>
<p>Note that Serial Data In and Serial Data Out are flipped across the SPI Controller and the SPI Peripheral…</p>
<ul>
<li>
<p><strong>Serial Data In on BL602</strong> connects to <strong>Serial Data Out on BME280</strong></p>
</li>
<li>
<p>And <strong>Serial Data Out on BL602</strong> connects to <strong>Serial Data In on BME280</strong></p>
</li>
</ul>
<p>(Yep it works like the Transmit / Receive pins for a UART port)</p>
<h1 id="bl602-hardware-abstraction-layer-for-spi"><a class="doc-anchor" href="#bl602-hardware-abstraction-layer-for-spi">§</a>2 BL602 Hardware Abstraction Layer for SPI</h1>
<p>The BL602 IoT SDK contains an <strong>SPI Hardware Abstraction Layer (HAL)</strong> that we may call in our C programs to transfer data over SPI…</p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c"><strong>BL602 SPI HAL: <code>bl602_hal/hal_spi.c</code></strong></a></li>
</ul>
<p>However there are a couple of concerns over the BL602 SPI HAL…</p>
<ol>
<li>
<p><strong>BL602 SPI HAL doesnt support all BL602 SPI features</strong>.</p>
<p>It supports SPI Transfers via <strong>Direct Memory Access (DMA)</strong>. Which is good for blasting pixels to Display Controllers (like ST7789).</p>
<p>But it <strong>doesnt support byte-by-byte SPI Transfer</strong>, like the <a href="https://github.com/pine64/ArduinoCore-bouffalo/blob/main/libraries/SPI/src/SPI.cpp"><strong>Arduino SPI HAL for BL602</strong></a>.</p>
</li>
<li>
<p><strong>BL602 SPI HAL was designed to work with <a href="https://github.com/alibaba/AliOS-Things">AliOS Things</a></strong> operating system and its Virtual File System.</p>
<p>It uses the AliOS Device Tree for configuring the SPI Port. Which might be overkill for some embedded programs.</p>
<p>I have added an SPI HAL function <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L838-L886"><strong><code>spi_init</code></strong></a> that lets us <strong>call the SPI HAL without AliOS Things</strong> and its Device Tree.</p>
<p><a href="https://lupyuen.github.io/articles/flash#device-tree">More about BL602 Device Tree</a></p>
</li>
<li>
<p><strong>BL602 SPI HAL works only with FreeRTOS</strong>.</p>
<p>Unlike the BL602 HALs for GPIO, PWM and I2C, theres no Low Level HAL that works on all operating systems.</p>
<p>But we may port the SPI HAL to other operating systems by emulating a few FreeRTOS functions for Event Groups. (More about this later)</p>
</li>
</ol>
<p>Hence we can still <strong>write SPI programs for BL602 without AliOS</strong>. And Ill highlight the SPI features that have special limitations.</p>
<p>We shall test BL602 SPI with this BL602 Command-Line Firmware that I have created: <a href="https://github.com/lupyuen/bl_iot_sdk/tree/master/customer_app/sdk_app_spi_demo"><code>sdk_app_spi_demo</code></a></p>
<p>The firmware will work on <strong>all BL602 boards,</strong> including PineCone and Pinenut.</p>
<p><img src="https://lupyuen.github.io/images/spi-connect.jpg" alt="PineCone BL602 connected to SparkFun BME280 Sensor over SPI" /></p>
<p><em>PineCone BL602 connected to <a href="https://www.sparkfun.com/products/13676">SparkFun BME280 Sensor</a> over SPI</em></p>
<h1 id="connect-bl602-to-bme280-spi-sensor"><a class="doc-anchor" href="#connect-bl602-to-bme280-spi-sensor">§</a>3 Connect BL602 to BME280 SPI Sensor</h1>
<p>Lets connect BL602 to the <a href="https://learn.sparkfun.com/tutorials/sparkfun-bme280-breakout-hookup-guide"><strong>Bosch BME280 Sensor for Temperature, Humidity and Air Pressure</strong></a></p>
<p>(The steps in this article will work for BMP280 too)</p>
<p>BME280 supports two interfaces: SPI (6 pins) and I2C (4 pins). We shall connect to the <strong>SPI side of BME280</strong>.</p>
<p><em>Dont use any pins on the I2C side! (Because the <code>3V3</code> pin selects SPI or I2C)</em></p>
<p>Connect BL602 to BME280 (the SPI side with 6 pins) according to the pic above…</p>
<div><table><thead><tr><th style="text-align: left">BL602 Pin</th><th style="text-align: left">BME280 SPI</th><th style="text-align: left">Wire Colour</th></tr></thead><tbody>
<tr><td style="text-align: left"><strong><code>GPIO 1</code></strong></td><td style="text-align: left"><code>SDO</code> <em>(MISO)</em></td><td style="text-align: left">Green</td></tr>
<tr><td style="text-align: left"><strong><code>GPIO 2</code></strong></td><td style="text-align: left">Do Not <br> Connect</td><td style="text-align: left">Do Not <br> Connect</td></tr>
<tr><td style="text-align: left"><strong><code>GPIO 3</code></strong></td><td style="text-align: left"><code>SCK</code></td><td style="text-align: left">Yellow</td></tr>
<tr><td style="text-align: left"><strong><code>GPIO 4</code></strong></td><td style="text-align: left"><code>SDI</code> <em>(MOSI)</em></td><td style="text-align: left">Blue</td></tr>
<tr><td style="text-align: left"><strong><code>GPIO 14</code></strong></td><td style="text-align: left"><code>CS</code></td><td style="text-align: left">Orange</td></tr>
<tr><td style="text-align: left"><strong><code>3V3</code></strong></td><td style="text-align: left"><code>3.3V</code></td><td style="text-align: left">Red</td></tr>
<tr><td style="text-align: left"><strong><code>GND</code></strong></td><td style="text-align: left"><code>GND</code></td><td style="text-align: left">Black</td></tr>
</tbody></table>
</div>
<p>Well talk about GPIO 2 in a while.</p>
<p><img src="https://lupyuen.github.io/images/spi-pins.jpg" alt="BL602 SPI Pins 1 (SDI), 3 (Clock), 4 (SDO) and 14 (Chip Select)" /></p>
<p><em>BL602 SPI Pins 1 (SDI), 3 (Clock), 4 (SDO) and 14 (Chip Select)</em></p>
<h2 id="selecting-spi-pins"><a class="doc-anchor" href="#selecting-spi-pins">§</a>3.1 Selecting SPI Pins</h2>
<p>Were NOT using the <a href="https://wiki.pine64.org/wiki/Nutcracker#Pinenut-12S_Module_information">Recommended SPI Pins for PineCone and Pinenut</a>: GPIO 0, 11, 14, 17.</p>
<p>And were NOT using the <a href="https://github.com/bouffalolab/BLOpenFlasher/blob/main/bl602/device_tree/bl_factory_params_IoTKitA_40M.dts#L237-L259">Default SPI Pins for BL602 Device Tree</a>: GPIO 0, 1, 2, 3.</p>
<p><em>Why did we choose these pins for SPI?</em></p>
<ul>
<li>
<p><strong>GPIO 0</strong> is connected to the <strong>PineCones WiFi LED</strong> (Is this documented somewhere?)</p>
</li>
<li>
<p><strong>GPIO 11, 14, 17</strong> are connected to <strong>PineCones RGB LED</strong></p>
</li>
</ul>
<p>We wont use these PineCone LED Pins for SPI because…</p>
<ol>
<li>
<p>Somebody else will probably use the LED Pins to control the LEDs. Contention ensues!</p>
</li>
<li>
<p>Lights switching on for no reason is just plain… Spooky</p>
</li>
</ol>
<p>(Sorry my mistake… I shouldnt be using Pin 14 for Chip Select. Beware of contention!)</p>
<h2 id="spi-protocol-for-bme280"><a class="doc-anchor" href="#spi-protocol-for-bme280">§</a>3.2 SPI Protocol for BME280</h2>
<p><em>What shall we accomplish with BL602 and BME280?</em></p>
<ol>
<li>
<p>BME280 has a <strong>Chip ID Register, at Register ID <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>
<p><em>Whats the SPI Data that will be transferred between BL602 and BME280?</em></p>
<p>Heres how BL602 and BME280 will talk over SPI…</p>
<ol>
<li>
<p>BL602 transmits byte <strong><code>0xD0</code></strong> to BME280 on <strong>Serial Data Out</strong> <em>(formerly MOSI)</em></p>
</li>
<li>
<p>BME280 returns byte <strong><code>0x60</code></strong> to BL602 on <strong>Serial Data In</strong> <em>(formerly MISO)</em></p>
</li>
</ol>
<p>The <strong>SPI Chip Select Pin (CS)</strong> and <strong>SPI Clock Pin (SCK)</strong> will frame and synchronise the data transfer…</p>
<p><img src="https://lupyuen.github.io/images/spi-analyse9a.png" alt="BL602 talks to BME280 over SPI, visualised by a Logic Analyser" /></p>
<p><em>BL602 talks to BME280 over SPI, visualised by a Logic Analyser</em></p>
<h1 id="initialise-spi-port"><a class="doc-anchor" href="#initialise-spi-port">§</a>4 Initialise SPI Port</h1>
<p>Lets dive into the code for our SPI Demo Firmware!</p>
<p>Before we initialise the SPI Port, we define these constants and variables in <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L45-L100"><code>sdk_app_spi_demo/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>/// Use SPI Port Number 0
#define SPI_PORT 0
/// Use GPIO 14 as SPI Chip Select Pin
#define SPI_CS_PIN 14
/// SPI Port
static spi_dev_t spi;</code></pre></div>
<ul>
<li>
<p><code>SPI_Port</code> is the SPI Port Number. We use the one and only port on BL602: <strong>SPI Port 0</strong></p>
</li>
<li>
<p><code>SPI_CS_PIN</code> is the Pin Number for the SPI Chip Select Pin. We select <strong>Pin 14</strong></p>
</li>
<li>
<p><code>spi</code> is the device instance of the SPI Port</p>
</li>
</ul>
<p>Our demo firmware initialises the SPI Port in the function <code>test_spi_init</code> from <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L45-L100"><code>sdk_app_spi_demo/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>/// Init the SPI Port
static void test_spi_init(char *buf, int len, int argc, char **argv) {
// Configure the SPI Port
int rc = spi_init(
&amp;spi, // SPI Device
SPI_PORT, // SPI Port
0, // SPI Mode: 0 for Controller
1, // SPI Polarity and Phase: 1 for (CPOL=0, CPHA=1)
200 * 1000, // SPI Frequency (200 kHz)
2, // Transmit DMA Channel
3, // Receive DMA Channel
3, // SPI Clock Pin
2, // Unused SPI Chip Select Pin
1, // SPI Serial Data In Pin (formerly MISO)
4 // SPI Serial Data Out Pin (formerly MOSI)
);
assert(rc == 0);</code></pre></div>
<p>This function initialises <code>spi</code> by calling the (custom) BL602 SPI HAL Function <code>spi_init</code>.</p>
<p>(<code>spi_init</code> may only be called once in our firmware, because it maintains a single global instance of SPI device)</p>
<p>Here are the parameters for <code>spi_init</code></p>
<ul>
<li>
<p><strong>SPI Device:</strong> SPI device instance to be initialised, <strong><code>spi</code></strong></p>
</li>
<li>
<p><strong>SPI Port:</strong> SPI Port Number <strong>0</strong></p>
</li>
<li>
<p><strong>SPI Mode:</strong> We choose <strong>0</strong> to configure BL602 as <strong>SPI Controller</strong>. Valid values are…</p>
<ul>
<li>0 to configure BL602 as SPI Controller</li>
<li>1 to configure BL602 as SPI Peripheral.</li>
</ul>
</li>
<li>
<p><strong>SPI Polarity and Phase:</strong> We choose <strong>1</strong> for <strong>Polarity 0 (CPOL), Phase 1 (CPHA)</strong>. Valid values are…</p>
<ul>
<li>0 for CPOL=0, CPHA=0</li>
<li>1 for CPOL=0, CPHA=1</li>
<li>2 for CPOL=1, CPHA=0</li>
<li>3 for CPOL=1, CPHA=1</li>
</ul>
<p>(Theres a bug with SPI Polarity and Phase, more about this later)</p>
</li>
<li>
<p><strong>SPI Frequency:</strong> We set the SPI Frequency (Hz) to 200,000, which means <strong>200 kHz</strong>.</p>
<p>(Slow but reliable, and easier to troubleshoot)</p>
<p>SPI Frequency ranges from 200 kHz to 40 MHz.</p>
</li>
<li>
<p><strong>Transmit DMA Channel:</strong> We select <strong>DMA Channel 2</strong> for transmitting SPI Data</p>
</li>
<li>
<p><strong>Receive DMA Channel:</strong> We select <strong>DMA Channel 3</strong> for receiving SPI Data</p>
</li>
<li>
<p><strong>SPI Clock Pin:</strong> We select <strong>Pin 3</strong></p>
</li>
<li>
<p><strong>Unused SPI Chip Select Pin:</strong> We select <strong>Pin 2</strong>.</p>
<p>We wont connect this pin to BME280, but it must NOT be the same as the Actual Chip Select Pin (14)</p>
<p>(More about Chip Select later)</p>
</li>
<li>
<p><strong>SPI Serial Data In Pin:</strong> We select <strong>Pin 1</strong> <em>(Formerly MISO)</em></p>
</li>
<li>
<p><strong>SPI Serial Data Out Pin:</strong> We select <strong>Pin 4</strong> <em>(Formerly MOSI)</em></p>
</li>
</ul>
<p>Next we configure the Actual Chip Select Pin (14) as a GPIO Pin…</p>
<div class="example-wrap"><pre class="language-c"><code> // Configure Chip Select pin as a GPIO Pin
GLB_GPIO_Type pins[1];
pins[0] = SPI_CS_PIN;
BL_Err_Type rc2 = GLB_GPIO_Func_Init(
GPIO_FUN_SWGPIO, // Configure as GPIO
pins, // Pins to be configured (Pin 14)
sizeof(pins) / sizeof(pins[0]) // Number of pins (1)
);
assert(rc2 == SUCCESS);</code></pre></div>
<p>(Well find out why later)</p>
<p>Because were not ready to talk to BME280 yet, we set the Chip Select Pin to High to deactivate BME280…</p>
<div class="example-wrap"><pre class="language-c"><code> // Configure Chip Select pin as a GPIO Output Pin (instead of GPIO Input)
rc = bl_gpio_enable_output(SPI_CS_PIN, 0, 0);
assert(rc == 0);
// Set Chip Select pin to High, to deactivate BME280
rc = bl_gpio_output_set(SPI_CS_PIN, 1);
assert(rc == 0);
}</code></pre></div>
<p>(More about Chip Select in a while)</p>
<p>Our SPI Port is initialised, all set for transferring data!</p>
<p><a href="https://lupyuen.github.io/articles/spi#spi_init-init-spi-port">(BL602 SPI HAL Function <code>spi_init</code> shall be explained in the Appendix)</a></p>
<h1 id="transfer-spi-data"><a class="doc-anchor" href="#transfer-spi-data">§</a>5 Transfer SPI Data</h1>
<p>SPI Controllers (like BL602) and Peripherals (like BME280) can transmit and receive SPI Data simultaneously… Because SPI allows <strong>Full Duplex</strong> communication.</p>
<p>When the BL602 SPI HAL executes an <strong>SPI Transfer</strong> request, its <strong>transmitting and receiving data simultaneously</strong>.</p>
<p>Remember how BL602 and BME280 will talk over SPI?</p>
<ol>
<li>
<p>BL602 transmits byte <strong><code>0xD0</code></strong> to BME280</p>
</li>
<li>
<p>BL602 receives byte <strong><code>0x60</code></strong> from BME280</p>
</li>
</ol>
<p>BL602 SPI HAL handles this as <strong>two SPI Transfer</strong> requests of one byte each…</p>
<ol>
<li>
<p><strong>First SPI Transfer</strong>: BL602 transmits byte <strong><code>0xD0</code></strong></p>
</li>
<li>
<p><strong>Second SPI Transfer</strong>: BL602 receives byte <strong><code>0x60</code></strong></p>
</li>
</ol>
<p>(Yep there will be “wasted data”… We dont need the received byte from the first request… And the transmitted byte from the second request)</p>
<p>Lets construct the two SPI Transfer requests.</p>
<p><img src="https://lupyuen.github.io/images/spi-analyse9b.png" alt="First and Second SPI Transfers" /></p>
<p><em>First and Second SPI Transfers</em></p>
<h2 id="transmit-and-receive-buffers"><a class="doc-anchor" href="#transmit-and-receive-buffers">§</a>5.1 Transmit and Receive Buffers</h2>
<p>First we define the <strong>Transmit and Receive Buffers</strong> (one byte each) for the two SPI Transfers: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L102-L108"><code>sdk_app_spi_demo/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>/// SPI Transmit and Receive Buffers for First SPI Transfer
static uint8_t tx_buf1[1]; // We shall transmit Register ID (0xD0)
static uint8_t rx_buf1[1]; // Unused. We expect to receive the result from BME280 in the second SPI Transfer.
/// SPI Transmit and Receive Buffers for Second SPI Transfer
static uint8_t tx_buf2[1]; // Unused. For safety, we shall transmit 0xFF which is a read command (not write).
static uint8_t rx_buf2[1]; // We expect to receive Chip ID (0x60) from BME280</code></pre></div><h2 id="initialise-spi-buffers-and-transfers"><a class="doc-anchor" href="#initialise-spi-buffers-and-transfers">§</a>5.2 Initialise SPI Buffers and Transfers</h2>
<p>Lets look at the function in our demo firmware that creates the two SPI Transfers and executes them: <code>test_spi_transfer</code> from <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L110-L156"><code>sdk_app_spi_demo/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>/// Start the SPI data transfer
static void test_spi_transfer(char *buf, int len, int argc, char **argv) {
// Clear the buffers
memset(&amp;tx_buf1, 0, sizeof(tx_buf1));
memset(&amp;rx_buf1, 0, sizeof(rx_buf1));
memset(&amp;tx_buf2, 0, sizeof(tx_buf2));
memset(&amp;rx_buf2, 0, sizeof(rx_buf2));
// Prepare 2 SPI Transfers
static spi_ioc_transfer_t transfers[2];
memset(transfers, 0, sizeof(transfers)); </code></pre></div>
<p>Here we erase the Transmit and Receive Buffers, and prepare two SPI Transfers.</p>
<h2 id="first-spi-transfer"><a class="doc-anchor" href="#first-spi-transfer">§</a>5.3 First SPI Transfer</h2>
<p>Next we define the First SPI Transfer…</p>
<div class="example-wrap"><pre class="language-c"><code> // First SPI Transfer: Transmit Register ID (0xD0) to BME280
tx_buf1[0] = 0xd0; // Read BME280 Chip ID Register (0xD0). Read/Write Bit (High Bit) is 1 for Read.
transfers[0].tx_buf = (uint32_t) tx_buf1; // Transmit Buffer (Register ID)
transfers[0].rx_buf = (uint32_t) rx_buf1; // Receive Buffer
transfers[0].len = sizeof(tx_buf1); // How many bytes</code></pre></div>
<p>Well be transmitting one byte <strong><code>0xD0</code></strong> to BME280. This goes into the <strong>Transmit Buffer <code>tx_buf1</code></strong></p>
<p>We set the <strong>Transmit and Receive Buffers</strong> for the First SPI Transfer in <strong><code>transfers[0]</code></strong></p>
<p>Also we set the <strong>data length</strong> of the First SPI Transfer (one byte) in <strong><code>transfers[0]</code></strong></p>
<h2 id="second-spi-transfer"><a class="doc-anchor" href="#second-spi-transfer">§</a>5.4 Second SPI Transfer</h2>
<p>Then we define the Second SPI Transfer…</p>
<div class="example-wrap"><pre class="language-c"><code> // Second SPI Transfer: Receive Chip ID (0x60) from BME280
tx_buf2[0] = 0xff; // Unused. Read/Write Bit (High Bit) is 1 for Read.
transfers[1].tx_buf = (uint32_t) tx_buf2; // Transmit Buffer
transfers[1].rx_buf = (uint32_t) rx_buf2; // Receive Buffer (Chip ID)
transfers[1].len = sizeof(tx_buf2); // How many bytes</code></pre></div>
<p>BME280 will ignore the byte transmitted by BL602 in the Second SPI Transfer. But lets send <strong><code>0xFF</code></strong> for safety. This goes into the <strong>Transmit Buffer <code>tx_buf2</code></strong></p>
<p>We set the <strong>Transmit and Receive Buffers</strong> for the Second SPI Transfer in <strong><code>transfers[1]</code></strong></p>
<p>Also we set the <strong>data length</strong> of the Second SPI Transfer (one byte) in <strong><code>transfers[1]</code></strong></p>
<h2 id="execute-the-spi-transfers"><a class="doc-anchor" href="#execute-the-spi-transfers">§</a>5.5 Execute the SPI Transfers</h2>
<p>Now were ready to execute the two SPI Transfers!</p>
<p>By SPI Convention, we set the <strong>Chip Select Pin to Low</strong> to activate BME280 (and get it ready for talking)…</p>
<div class="example-wrap"><pre class="language-c"><code> // Set Chip Select pin to Low, to activate BME280
int rc = bl_gpio_output_set(SPI_CS_PIN, 0);
assert(rc == 0);</code></pre></div>
<p>Now that we have BME280s attention, we execute the two SPI Transfers by calling the BL602 SPI HAL Function <code>hal_spi_transfer</code></p>
<div class="example-wrap"><pre class="language-c"><code> // Execute the two SPI Transfers with the DMA Controller
rc = hal_spi_transfer(
&amp;spi, // SPI Device
transfers, // SPI Transfers
sizeof(transfers) / sizeof(transfers[0]) // How many transfers (Number of requests, not bytes)
);
assert(rc == 0);
// DMA Controller will transmit and receive the SPI data in the background.
// hal_spi_transfer will wait for the two SPI Transfers to complete before returning.</code></pre></div>
<p><code>hal_spi_transfer</code> will wait for the two SPI Transfers to complete before returning.</p>
<p>When were done with the two SPI Transfers, we set the <strong>Chip Select Pin to High</strong> to deactivate BME280 (and put it to sleep)…</p>
<div class="example-wrap"><pre class="language-c"><code> // Now that we&#39;re done with the two SPI Transfers...
// Set Chip Select pin to High, to deactivate BME280
rc = bl_gpio_output_set(SPI_CS_PIN, 1);
assert(rc == 0);
}</code></pre></div>
<p><strong>Mission Accomplished!</strong> The Receive Buffer for the Second SPI Transfer <strong><code>rx_buf2</code></strong> will contain the data received from BME280: <strong><code>0x60</code></strong>.</p>
<p>Well witness this shortly.</p>
<p><a href="https://lupyuen.github.io/articles/spi#hal_spi_transfer-execute-spi-transfer">(BL602 SPI HAL Function <code>hal_spi_transfer</code> shall be explained in the Appendix)</a></p>
<h2 id="spi-with-direct-memory-access"><a class="doc-anchor" href="#spi-with-direct-memory-access">§</a>5.6 SPI with Direct Memory Access</h2>
<p><em>Whats Direct Memory Access? How does it help SPI?</em></p>
<p><strong>Direct Memory Access (DMA)</strong> is a BL602 hardware feature that will automagically copy data from RAM to the SPI Port (and back)… Without any intervention from our firmware code!</p>
<p>Heres how SPI works with and without DMA…</p>
<p><strong>SPI Without DMA:</strong></p>
<ol>
<li>
<p>Firmware code transmits and receives <strong>4 bytes of data</strong></p>
<p>(Because BL602 has an SPI buffer size of 4 bytes)</p>
</li>
<li>
<p>Waits for <strong>Transfer Complete Interrupt</strong></p>
</li>
<li>
<p>Firmware code transmits and receives another <strong>4 bytes of data</strong></p>
</li>
<li>
<p>Waits for <strong>Transfer Complete Interrupt</strong></p>
</li>
<li>
<p>Repeat until <strong>all bytes</strong> have been transmitted and received</p>
</li>
<li>
<p><em>What if were blasting 1,000 bytes to an SPI Display Controller?</em></p>
<p>Our CPU will be <strong>interrupted 250 times</strong> to transmit data.</p>
<p>Not so efficient!</p>
</li>
</ol>
<p><strong>SPI With DMA:</strong></p>
<ol>
<li>
<p>Firmware code tells the <strong>DMA Controller</strong> the addresses of the <strong>Transmit and Receive Buffers</strong>, and how many bytes to transmit and receive</p>
<p>(BL602 DMA Controller uses a <strong>DMA Linked List</strong> with two entries: Transmit Buffer and Receive Buffer)</p>
</li>
<li>
<p>DMA Controller transmits and receives <strong>the entire buffer</strong> automatically</p>
<p>(Even when the firmware code is running!)</p>
</li>
<li>
<p>DMA Controller triggers two <strong>DMA Complete Interrupts</strong></p>
<p>(One interrupt for Transmit Complete, another interrupt for Receive Complete)</p>
</li>
<li>
<p><em>What if were blasting 1,000 bytes to an SPI Display Controller?</em></p>
<p>Our CPU will be <strong>interrupted only twice!</strong></p>
<p>Thats super efficient!</p>
</li>
</ol>
<p>All SPI Transfers done with the BL602 SPI HAL will use super-efficient DMA.</p>
<p>To learn more about SPI with DMA…</p>
<ul>
<li>
<p><a href="https://lupyuen.github.io/articles/spi#lli_list_init-create-dma-linked-list">How the SPI HAL creates a DMA Linked List</a></p>
</li>
<li>
<p><a href="https://lupyuen.github.io/articles/spi#hal_spi_dma_trans-execute-spi-transfer-with-dma">How the SPI HAL executes the DMA Linked List</a></p>
</li>
</ul>
<p><img src="https://lupyuen.github.io/images/spi-firmware3.jpg" alt="SPI Demo Firmware for BL602" /></p>
<p><em>SPI Demo Firmware for BL602</em></p>
<h1 id="build-and-run-the-firmware"><a class="doc-anchor" href="#build-and-run-the-firmware">§</a>6 Build and Run the Firmware</h1>
<p>Lets run the SPI Demo Firmware for BL602.</p>
<p>Download the Firmware Binary File <strong><code>sdk_app_spi_demo.bin</code></strong> from…</p>
<ul>
<li><a href="https://github.com/lupyuen/bl_iot_sdk/releases/tag/v3.0.0"><strong>Binary Release of <code>sdk_app_spi_demo</code></strong></a></li>
</ul>
<p>Alternatively, we may build the Firmware Binary File <code>sdk_app_spi_demo.bin</code> from the <a href="https://github.com/lupyuen/bl_iot_sdk/tree/master/customer_app/sdk_app_spi_demo">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_spi_demo
## 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_spi_demo.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>6.1 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_spi_demo.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_spi_demo.bin</code> to BL602 over UART…</p>
<div class="example-wrap"><pre class="language-bash"><code>## For Linux:
blflash flash build_out/sdk_app_spi_demo.bin \
--port /dev/ttyUSB0
## For macOS:
blflash flash build_out/sdk_app_spi_demo.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_spi_demo.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>6.2 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-spi-commands"><a class="doc-anchor" href="#enter-spi-commands">§</a>6.3 Enter SPI commands</h2>
<p>Lets enter some SPI 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====
spi_init : Init SPI port
spi_transfer : Transfer SPI data
spi_result : Show SPI data received</code></pre></div></li>
<li>
<p>First we <strong>initialise our SPI Port</strong>.</p>
<p>Enter this command…</p>
<div class="example-wrap"><pre class="language-text"><code>spi_init</code></pre></div>
<p><code>spi_init</code> calls the function <code>test_spi_init</code>, which we have seen earlier.</p>
</li>
<li>
<p>We should see this…</p>
<div class="example-wrap"><pre class="language-text"><code>port0 eventloop init = 42010b48
[HAL] [SPI] Init :
port=0, mode=0, polar_phase = 1, freq=200000, tx_dma_ch=2, rx_dma_ch=3, pin_clk=3, pin_cs=2, pin_mosi=1, pin_miso=4
set rwspeed = 200000
hal_gpio_init: cs:2, clk:3, mosi:1, miso: 4
hal_gpio_init: SPI controller mode
hal_spi_init.
Set CS pin 14 to high</code></pre></div></li>
<li>
<p>Now we <strong>start the two SPI Transfers</strong></p>
<div class="example-wrap"><pre class="language-text"><code>spi_transfer</code></pre></div>
<p><code>spi_transfer</code> calls the function <code>test_spi_transfer</code>, which we have seen earlier.</p>
</li>
<li>
<p>We should see this…</p>
<div class="example-wrap"><pre class="language-text"><code>Set CS pin 14 to low
hal_spi_transfr = 2
transfer xfer[0].len = 1
Tx DMA src=0x4200d1b8, dest=0x4000a288, size=1, si=1, di=0, i=1
Rx DMA src=0x4000a28c, dest=0x4200d1b0, size=1, si=0, di=1, i=1
recv all event group.
transfer xfer[1].len = 1
Tx DMA src=0x4200d1bc, dest=0x4000a288, size=1, si=1, di=0, i=1
Rx DMA src=0x4000a28c, dest=0x4200d1b4, size=1, si=0, di=1, i=1
recv all event group.
Set CS pin 14 to high</code></pre></div></li>
<li>
<p>Finally we display the <strong>SPI Data received</strong> from BME280…</p>
<div class="example-wrap"><pre class="language-text"><code>spi_result</code></pre></div>
<p>(<code>spi_result</code> is defined here in <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L158-L182"><code>sdk_app_spi_demo/demo.c</code></a>)</p>
</li>
<li>
<p>We should see this…</p>
<div class="example-wrap"><pre class="language-text"><code>SPI Transfer #1: Received Data 0x0x4200d1b0:
ff
SPI Transfer #2: Received Data 0x0x4200d1b4:
60
Tx Interrupts: 2
Tx Status: 0x0
Tx Term Count: 0x0
Tx Error: 0x0
Rx Interrupts: 2
Rx Status: 0x0
Rx Term Count: 0x0
Rx Error: 0x0</code></pre></div>
<p>This shows that we have encountered <strong>two Transmit Interrupts</strong> and <strong>two Receive Interrupts</strong>, no errors. Which is expected for two SPI Transfers.</p>
<p>Remember that were reading the Chip ID from BME280. We should see this Chip ID under <strong><code>SPI Transfer #2</code></strong></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 SPI!</p>
<h1 id="control-our-own-chip-select-pin"><a class="doc-anchor" href="#control-our-own-chip-select-pin">§</a>7 Control our own Chip Select Pin</h1>
<p>Earlier we said that were not using Pin 2, the designated Chip Select Pin from the BL602 SPI Port…</p>
<div class="example-wrap"><pre class="language-c"><code>// Configure the SPI Port
int rc = spi_init(
...
2, // Unused SPI Chip Select Pin</code></pre></div>
<p>But instead, were using Pin 14 as our own Chip Select Pin…</p>
<div class="example-wrap"><pre class="language-c"><code>/// Use GPIO 14 as SPI Chip Select Pin
#define SPI_CS_PIN 14</code></pre></div>
<p><em>Why are we controlling the Chip Select Pin ourselves?</em></p>
<p><img src="https://lupyuen.github.io/images/spi-analyse4a.png" alt="Chip Select Pin from SPI Port connected to BME280" /></p>
<ol>
<li>
<p><strong>We get to shape the Chip Select Signal ourselves</strong></p>
<p>When we use the Chip Select Pin from the SPI Port, the <strong>Chip Select Pin goes High</strong> between the two SPI Transfers. (See pic above)</p>
<p>This is not good… We expect the <strong>Chip Select Pin to stay Low</strong> between the two SPI Transfers! <a href="https://lupyuen.github.io/images/spi-analyse9a.png">(See this)</a></p>
<p>Well control the Chip Select Pin ourselves to produce the desired signal shape.</p>
<p>(Its like shaping our own eyebrows… We have complete control!)</p>
<p>(How do we know that the Chip Select Pin should stay Low? From the Bus Pirate data captured by the Logic Analyser. <a href="https://lupyuen.github.io/images/spi-analyse1a.png">See this</a>)</p>
</li>
<li>
<p><strong>We want to control multiple SPI Peripherals with BL602</strong></p>
<p>We may connect multiple SPI Peripherals to BL602 at the same time: BME280 Sensor, ST7789 Display Controller, SX1262 LoRa Transceiver, …</p>
<p>BL602s Serial Data In, Serial Data Out and Clock Pins may be shared by the SPI Peripherals… <strong>But each SPI Periperhal needs its own Chip Select Pin</strong>.</p>
<p>Hence well control the Chip Select Pin ourselves to support multiple SPI Peripherals.</p>
<p><a href="https://learn.sparkfun.com/tutorials/serial-peripheral-interface-spi/all#chip-select-cs">More about connecting multiple SPI Peripherals</a></p>
</li>
</ol>
<p>Read on to learn how we use BL602 GPIO to control our Chip Select Pin.</p>
<p>(Remember: Dont use Pin 2 for any other purpose… Because the SPI Port is still controlling it!)</p>
<h2 id="configure-chip-select-pin-as-gpio-output-pin"><a class="doc-anchor" href="#configure-chip-select-pin-as-gpio-output-pin">§</a>7.1 Configure Chip Select Pin as GPIO Output Pin</h2>
<p>BL602 is extremely versatile for Pin Functions… We may assign <strong>any BL602 Pin as GPIO, SPI, I2C, UART, PWM or JTAG!</strong></p>
<p>(See Table 3.1 “Pin Description”, Page 27 in the <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en">BL602 Reference Manual</a>)</p>
<p>We configure Pin 14 for GPIO like so: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L86-L99"><code>sdk_app_spi_demo/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>/// Use GPIO 14 as SPI Chip Select Pin
#define SPI_CS_PIN 14
// Configure Chip Select pin as a GPIO Pin
GLB_GPIO_Type pins[1];
pins[0] = SPI_CS_PIN;
BL_Err_Type rc2 = GLB_GPIO_Func_Init(
GPIO_FUN_SWGPIO, // Configure as GPIO
pins, // Pins to be configured (Pin 14)
sizeof(pins) / sizeof(pins[0]) // Number of pins (1)
);
assert(rc2 == SUCCESS);</code></pre></div>
<p>We call <strong><code>GLB_GPIO_Func_Init</code></strong> to configure Pin 14 as a plain GPIO Pin: <code>GPIO_FUN_SWGPIO</code>.</p>
<p>(<code>GLB_GPIO_Func_Init</code> comes from 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_glb.c"><code>bl602_glb.c</code></a>)</p>
<p>Now that Pin 14 is configured as a GPIO Pin, lets configure it for <strong>GPIO Output</strong> (instead of GPIO Input)…</p>
<div class="example-wrap"><pre class="language-c"><code>// Configure Chip Select pin as a GPIO Output Pin (instead of GPIO Input)
rc = bl_gpio_enable_output(SPI_CS_PIN, 0, 0);
assert(rc == 0);</code></pre></div>
<p>Were ready to toggle Pin 14 as a GPIO Output Pin!</p>
<p>(<code>bl_gpio_enable_output</code> comes from the <strong>BL602 GPIO Low Level HAL</strong>. <a href="https://lupyuen.github.io/articles/led#enable-gpio">See this</a>)</p>
<h2 id="set-chip-select-to-low"><a class="doc-anchor" href="#set-chip-select-to-low">§</a>7.2 Set Chip Select to Low</h2>
<p>To set our Chip Select Pin to Low (which activates BME280), we do this: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L135-L155"><code>sdk_app_spi_demo/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>// Set Chip Select pin to Low, to activate BME280
int rc = bl_gpio_output_set(SPI_CS_PIN, 0);
assert(rc == 0);</code></pre></div>
<p>We set Chip Select to Low just before executing the two SPI Transfers.</p>
<p>(<code>bl_gpio_output_set</code> comes from the <strong>BL602 GPIO Low Level HAL</strong>. <a href="https://lupyuen.github.io/articles/led#read-and-write-gpio">See this</a>)</p>
<h2 id="set-chip-select-to-high"><a class="doc-anchor" href="#set-chip-select-to-high">§</a>7.3 Set Chip Select to High</h2>
<p>To set our Chip Select Pin to High (which deactivates BME280), we do this…</p>
<div class="example-wrap"><pre class="language-c"><code>// Set Chip Select pin to High, to deactivate BME280
rc = bl_gpio_output_set(SPI_CS_PIN, 1);
assert(rc == 0);</code></pre></div>
<p>We set Chip Select to High in two places…</p>
<ol>
<li>
<p>When we initialise the SPI Port</p>
</li>
<li>
<p>After completing the two SPI Transfers</p>
</li>
</ol>
<h1 id="spi-data-pins-are-flipped"><a class="doc-anchor" href="#spi-data-pins-are-flipped">§</a>8 SPI Data Pins are flipped</h1>
<p>Heres a strange problem about the BL602 SPI Data Pins that still spooks me today…</p>
<p>Recall that were using <strong>Pins 1 and 4 as the SPI Data Pins</strong>.</p>
<p>Yep BL602 will let us assign Pins 1 and 4 to the SPI Port… But within that SPI Port, <strong>each pin serves a fixed SPI Function</strong> (like Serial Data In and Serial Data Out).</p>
<p>Heres what the <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en">BL602 Reference Manual</a> says (Table 3.1 “Pin Description”, Page 27)…</p>
<p><img src="https://lupyuen.github.io/images/spi-gpio.png" alt="SPI Functions for Pins 1 and 4" /></p>
<ul>
<li>
<p><strong>GPIO 1</strong> should be <strong>BL602 Serial Data Out</strong> <em>(formerly MOSI)</em></p>
<p>Which connects to <strong>BME280 Serial Data In</strong></p>
</li>
<li>
<p><strong>GPIO 4</strong> should be <strong>BL602 Serial Data In</strong> <em>(formerly MISO)</em></p>
<p>Which connects to <strong>BME280 Serial Data Out</strong></p>
</li>
</ul>
<p>(Remember that Data In/Out are flipped across BL602 and BME280)</p>
<p>Yet when we connect BL602 to BME280 in the above manner, well see this…</p>
<p><img src="https://lupyuen.github.io/images/spi-analyse2a.png" alt="BL602 SPI Data Pins are flipped" /></p>
<p><strong>The two BL602 SPI Data Pins are flipped!</strong> (According to the Logic Analyser)</p>
<p>This seems to be a bug in the BL602 hardware or documentation. We fix this by <strong>flipping the two data pins</strong></p>
<div><table><thead><tr><th style="text-align: center">BL602 Pin</th><th style="text-align: center">BME280 SPI</th><th style="text-align: left">Wire Colour</th></tr></thead><tbody>
<tr><td style="text-align: center"><strong><code>GPIO 1</code></strong></td><td style="text-align: center"><code>SDO</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>SDI</code></td><td style="text-align: left">Blue</td></tr>
</tbody></table>
</div>
<p>This works perfectly fine, though it contradicts the BL602 Reference Manual.</p>
<p>Because of this bug, we shall refer to <strong>Pin 1 as Serial Data In</strong> <em>(formerly MISO)</em>, and <strong>Pin 4 as Serial Data Out</strong> <em>(formerly MOSI)</em>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L45-L100"><code>sdk_app_spi_demo/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>// Configure the SPI Port
int rc = spi_init(
...
1, // SPI Serial Data In Pin (formerly MISO)
4 // SPI Serial Data Out Pin (formerly MOSI)</code></pre></div>
<p>(Could we have mistakenly configured BL602 as SPI Peripheral… Instead of SPI Controller? 🤔)</p>
<p><a href="https://lupyuen.github.io/articles/pinedio#spi-pins-are-swapped"><strong>UPDATE:</strong> We may call GLB_Swap_SPI_0_MOSI_With_MISO to swap the two SPI Data Pins. (See this)</a></p>
<h1 id="spi-phase-looks-sus"><a class="doc-anchor" href="#spi-phase-looks-sus">§</a>9 SPI Phase looks sus</h1>
<p>Heres another spooky problem: <strong>BL602 SPI Phase seems incorrect</strong>.</p>
<p>Earlier we have configured BL602 for <strong>SPI Polarity 0 (CPOL), Phase 1 (CPHA)</strong>: <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L45-L100"><code>sdk_app_spi_demo/demo.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>// Configure the SPI Port
int rc = spi_init(
...
1, // SPI Polarity and Phase: 1 for (CPOL=0, CPHA=1)</code></pre></div>
<p>Heres how it looks with a Logic Analyser…</p>
<p><img src="https://lupyuen.github.io/images/spi-analyse10a.png" alt="BL602 SPI Polarity 0, Phase 1" /></p>
<p>Note that the <strong>Serial Data Out Pin</strong> <em>(formerly MOSI)</em> <strong>goes High</strong><strong>before the Clock Pin goes High</strong>.</p>
<p>Now compare this with the SPI Polarity and Phase diagrams here…</p>
<ul>
<li><a href="https://www.analog.com/en/analog-dialogue/articles/introduction-to-spi-interface.html#"><strong>Introduction to SPI Interface</strong></a></li>
</ul>
<p>Doesnt this look like <strong>SPI Polarity 0 Phase 0, not Phase 1?</strong></p>
<p>Heres another odd thing: This BL602 SPI Configuration (SPI Polarity 0, Phase 1) works perfectly splendid with BME280…</p>
<p><em>Yet BME280 doesnt support SPI Polarity 0, Phase 1!</em></p>
<p>BME280 only supports…</p>
<ul>
<li>
<p>SPI Polarity 0, Phase 0</p>
</li>
<li>
<p>SPI Polarity 1, Phase 1</p>
</li>
</ul>
<p>Again this seems to be a bug in the BL602 hardware or documentation.</p>
<p><strong>Be careful when configuring the BL602 SPI Phase… It doesnt quite work the way we expect!</strong></p>
<p>(Yep I painstakingly verified… Setting BL602 to SPI Polarity 0, Phase 0 doesnt work with BME280)</p>
<h1 id="unsolved-mysteries"><a class="doc-anchor" href="#unsolved-mysteries">§</a>10 Unsolved Mysteries</h1>
<p>To summarise the spooky mysteries we have observed on BL602 SPI…</p>
<ol>
<li>
<p>SPI Pins for <strong>Serial Data In</strong> and <strong>Serial Data Out</strong> seem to be flipped, when observed with a Logic Analyser.</p>
<p>This contradicts the BL602 Reference Manual.</p>
<p>To fix this, we <strong>flip the Serial Data In and Serial Data Out Pins</strong>.</p>
</li>
<li>
<p>To talk to BME280, we must configure BL602 for <strong>SPI Polarity 0 Phase 1</strong>.</p>
<p>Though the Logic Analyser shows that BL602 behaves as SPI Polarity 0 Phase 0 (not Phase 1).</p>
<p><strong>Be careful when configuring BL602s SPI Phase.</strong></p>
</li>
<li>
<p>Using <strong>Pin 0 for SPI</strong> will switch on the WiFi LED on PineCone.</p>
<p>(This is undocumented behaviour… What else does Pin 0 do?)</p>
<p>Well switch to <strong>Pin 4 for SPI Serial Data Out.</strong></p>
</li>
<li>
<p>(This is neither spooky nor mysterious… But we fixed it anyway)</p>
<p>BL602s <strong>SPI Chip Select Pin</strong> doesnt work with BME280s SPI protocol.</p>
<p>And it doesnt support multiple SPI Peripherals.</p>
<p>Well <strong>control the SPI Chip Select Pin</strong> ourselves with GPIO.</p>
</li>
</ol>
<p>We have implemented workarounds for these issues.</p>
<p><strong>BL602 SPI is Good To Go!</strong></p>
<h1 id="port-bl602-spi-hal-to-other-operating-systems"><a class="doc-anchor" href="#port-bl602-spi-hal-to-other-operating-systems">§</a>11 Port BL602 SPI HAL to other Operating Systems</h1>
<p><em>BL602 SPI HAL runs on FreeRTOS today. Will the SPI HAL run on other Embedded Operating Systems? Like Mynewt, RIOT, Zephyr, …</em></p>
<p>We may port the BL602 SPI HAL to other operating systems by <strong>emulating these FreeRTOS functions for Event Groups</strong></p>
<ol>
<li>
<p><code>xEventGroupCreate</code>: Create an Event Group</p>
<p>(Used by the DMA Interrupt Handlers to notify the Foreground Task)</p>
</li>
<li>
<p><code>xEventGroupClearBits</code>: Clear the Event Group</p>
<p>(Called by the Foreground Task)</p>
</li>
<li>
<p><code>xEventGroupWaitBits</code>: Wait for Event Group</p>
<p>(Called by the Foreground Task)</p>
</li>
<li>
<p><code>xEventGroupSetBitsFromISR</code>: Notify the Event Group</p>
<p>(Called by the DMA Interrupt Handlers)</p>
</li>
<li>
<p><code>portYIELD_FROM_ISR</code>: Wakes up the Foreground Task thats waiting for the Event Group.</p>
<p>(Called by the DMA Interrupt Handlers)</p>
</li>
</ol>
<p>We also need to <strong>emulate these FreeRTOS heap memory functions</strong>, which are similar to <code>malloc</code> and <code>free</code></p>
<ol>
<li>
<p><code>pvPortMalloc</code>: Allocate heap memory</p>
<p>(For creating the DMA Linked List)</p>
</li>
<li>
<p><code>vPortFree</code>: Free the allocated heap memory</p>
</li>
</ol>
<p><a href="https://lupyuen.github.io/articles/spi#appendix-inside-bl602-spi-hal">(The usage of these functions is explained in the Appendix)</a></p>
<p><img src="https://lupyuen.github.io/images/spi-st7789.jpg" alt="ST7789 Display Controller with SPI Interface" /></p>
<p><em>ST7789 Display Controller with SPI Interface</em></p>
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>12 Whats Next</h1>
<p>Now that we have SPI working on BL602, lets test it with the <a href="https://www.rhydolabz.com/documents/33/ST7789.pdf"><strong>ST7789 Display Controller</strong></a>… And maybe with the <a href="https://docs.lvgl.io/latest/en/html/intro/index.html"><strong>LVGL Graphics Library</strong></a> too!</p>
<p>Heres the article…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/display"><strong>“PineCone BL602 Blasting Pixels to ST7789 Display with LVGL Library”</strong></a></li>
</ul>
<p>BL602 SPI HAL is also used by the <strong>Semtech SX1276 / Hope RF96 LoRa Transceiver</strong>. Heres how…</p>
<ul>
<li><a href="https://lupyuen.github.io/articles/lora"><strong>“Connect PineCone BL602 to LoRa Transceiver”</strong></a></li>
</ul>
<p>Eventually Ill be porting BL602 SPI HAL to <a href="https://lupyuen.github.io/articles/gpio"><strong>Apache Mynewt operating system</strong></a>… So that we can build BL602 SPI applications in Rust!</p>
<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/RISCV/comments/lehhrv/pinecone_bl602_talks_spi_too/?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/spi.md"><code>lupyuen.github.io/src/spi.md</code></a></p>
<h1 id="notes"><a class="doc-anchor" href="#notes">§</a>13 Notes</h1>
<ol>
<li>This article is the expanded version of <a href="https://twitter.com/MisterTechBlog/status/1354972803179507715?s=19">this meandering Twitter Thread</a></li>
</ol>
<p><img src="https://lupyuen.github.io/images/spi-buspirate.jpg" alt="Bus Pirate connected to BME280 Sensor over SPI" /></p>
<p><em>Bus Pirate connected to BME280 Sensor over SPI</em></p>
<h1 id="appendix-test-bme280-spi-interface-with-bus-pirate"><a class="doc-anchor" href="#appendix-test-bme280-spi-interface-with-bus-pirate">§</a>14 Appendix: Test BME280 SPI Interface 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 SPI signals that should be sent down the wire to BME280.</p>
<p>Heres how we test BME280 (or BMP280) with Bus Pirate…</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 SPI Pin</th></tr></thead><tbody>
<tr><td style="text-align: center"><strong><code>MOSI</code></strong></td><td style="text-align: center"><code>SDI</code></td></tr>
<tr><td style="text-align: center"><strong><code>MISO</code></strong></td><td style="text-align: center"><code>SDO</code></td></tr>
<tr><td style="text-align: center"><strong><code>CLK</code></strong></td><td style="text-align: center"><code>SCK</code></td></tr>
<tr><td style="text-align: center"><strong><code>CS</code></strong></td><td style="text-align: center"><code>CS</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>
<p>Connect to the <strong>SPI side of BME280</strong> (with 6 pins).</p>
<p>Dont use any pins on the I2C side (with 4 pins).</p>
</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> to show the menu</p>
<p>Select <strong><code>SPI</code></strong></p>
<div class="example-wrap"><pre class="language-text"><code>HiZ&gt; m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. KEYB
9. LCD
10. PIC
11. DIO
x. exit(without change)
(1)&gt; 5</code></pre></div></li>
<li>
<p>Select <strong><code>250 kHz</code></strong></p>
<div class="example-wrap"><pre class="language-text"><code>Set speed:
1. 30KHz
2. 125KHz
3. 250KHz
4. 1MHz
(1)&gt; 3</code></pre></div></li>
<li>
<p>Select <strong><code>Idle Low</code></strong></p>
<div class="example-wrap"><pre class="language-text"><code>Clock polarity:
1. Idle low *default
2. Idle high
(1)&gt;</code></pre></div></li>
<li>
<p>Select <strong><code>Active To Idle</code></strong></p>
<div class="example-wrap"><pre class="language-text"><code>Output clock edge:
1. Idle to active
2. Active to idle *default
(2)&gt;</code></pre></div></li>
<li>
<p>Select <strong><code>Middle</code></strong></p>
<div class="example-wrap"><pre class="language-text"><code>Input sample phase:
1. Middle *default
2. End
(1)&gt;</code></pre></div></li>
<li>
<p>Select <strong><code>/CS</code></strong></p>
<div class="example-wrap"><pre class="language-text"><code>CS:
1. CS
2. /CS *default
(2)&gt;</code></pre></div></li>
<li>
<p>Select <strong><code>Open Drain</code></strong></p>
<div class="example-wrap"><pre class="language-text"><code>Select output type:
1. Open drain (H=Hi-Z, L=GND)
2. Normal (H=3.3V, L=GND)
(1)&gt;
Clutch disengaged!!!
To finish setup, start up the power supplies with command &#39;W&#39;
Ready</code></pre></div></li>
<li>
<p>Enter <strong><code>W</code></strong> to power on BME280</p>
<div class="example-wrap"><pre class="language-text"><code>SPI&gt; W
POWER SUPPLIES ON
Clutch engaged!!!</code></pre></div></li>
<li>
<p>Enter this command…</p>
<div class="example-wrap"><pre class="language-text"><code>[ 0xD0 r ]</code></pre></div>
<p>(Well learn why in a while)</p>
</li>
<li>
<p>We should see…</p>
<div class="example-wrap"><pre class="language-text"><code>SPI&gt; [ 0xD0 r ]
/CS ENABLED
WRITE: 0xD0
READ: 0x60
/CS DISABLED</code></pre></div>
<p>This means that the result is <code>0x60</code>, which is the Chip ID for BME280.</p>
<p>(For BMP280 the Chip ID is <code>0x58</code>)</p>
</li>
</ol>
<p>This Bus Pirate SPI command…</p>
<div class="example-wrap"><pre class="language-text"><code>[ 0xD0 r ]</code></pre></div>
<p>Will do this…</p>
<ol>
<li>
<p>Set <strong>Chip Select Pin to Low</strong> (to activate BME280)</p>
</li>
<li>
<p><strong>Transmit byte <code>0xD0</code></strong> to BME280</p>
</li>
<li>
<p><strong>Receive one byte</strong> from BME280</p>
</li>
<li>
<p>Set <strong>Chip Select Pin to High</strong> (to deactivate BME280)</p>
</li>
</ol>
<p>This will read BME280s <strong>Chip ID Register <code>0xD0</code></strong>.</p>
<p>We expect the byte received to be <strong><code>0x60</code></strong>, which is the Chip ID for BME280.</p>
<p>(For BMP280 the Chip ID is <code>0x58</code>)</p>
<p><a href="http://dangerousprototypes.com/docs/SPI">Check out the SPI Guide for Bus Pirate</a></p>
<p><img src="https://lupyuen.github.io/images/spi-analyse1a.png" alt="Bus Pirate talks to BME280 over SPI, visualised by LA2016 Logic Analyser" /></p>
<p><em>Bus Pirate talks to BME280 over SPI, visualised by LA2016 Logic Analyser</em></p>
<p>To see the actual SPI Signals on each Bus Pirate Pin (shown above), we will use a <strong>Logic Analyser</strong>.</p>
<p>This is useful for comparing and checking whether BL602 is sending the right SPI Signals to BME280.</p>
<p>The next section explains how.</p>
<p><img src="https://lupyuen.github.io/images/spi-analyser.jpg" alt="PineCone BL602 RISC-V Board connected to LA2016 Logic Analyser and BME280 SPI Sensor" /></p>
<p><em>PineCone BL602 RISC-V Board connected to LA2016 Logic Analyser and BME280 SPI Sensor</em></p>
<h1 id="appendix-troubleshoot-bl602-spi-with-logic-analyser"><a class="doc-anchor" href="#appendix-troubleshoot-bl602-spi-with-logic-analyser">§</a>15 Appendix: Troubleshoot BL602 SPI with Logic Analyser</h1>
<p>We shall use the <a href="https://www.seeedstudio.com/LA2016-Logic-Analyzer-p-2218.html"><strong>LA2016 Logic Analyser</strong></a> to capture and decode the SPI Signals between BL602 and BME280. (Also between Bus Pirate and BME280)</p>
<p>(LA2016 User Guide is in the link above, under “Learn and Documents”)</p>
<p>Connect the Logic Analyser to BL602 according to the pic above…</p>
<div><table><thead><tr><th style="text-align: center">Logic Analyser</th><th style="text-align: left">BL602 Pin</th></tr></thead><tbody>
<tr><td style="text-align: center"><strong><code>CH0</code></strong></td><td style="text-align: left"><code>GPIO 4 (SDO / MOSI)</code></td></tr>
<tr><td style="text-align: center"><strong><code>CH1</code></strong></td><td style="text-align: left"><code>GPIO 1 (SDI / MISO)</code></td></tr>
<tr><td style="text-align: center"><strong><code>CH2</code></strong></td><td style="text-align: left"><code>GPIO 3 (SCK)</code></td></tr>
<tr><td style="text-align: center"><strong><code>CH3</code></strong></td><td style="text-align: left"><code>GPIO 14 (CS)</code></td></tr>
<tr><td style="text-align: center"><strong><code>GND</code></strong></td><td style="text-align: left"><code>GND</code></td></tr>
</tbody></table>
</div>
<p>But be very careful!</p>
<p><strong>Rule of Single Power Source</strong>: Be sure that BL602 and Logic Analyser are powered by the same source… Both must be connected to the same computer!</p>
<p>Also the LA2016 User Guide says that the measured voltage is <strong>highly sensitive to grounding</strong>. We shall connect BL602 to the Logic Analyser in this sequence…</p>
<ol>
<li>
<p><strong>Disconnect BL602 and Logic Analyser</strong> from our computers USB ports</p>
</li>
<li>
<p><strong>Connect <code>GND</code></strong> from BL602 to Logic Analyser</p>
</li>
<li>
<p><strong>Connect BL602 and Logic Analyser</strong> to our computers USB ports</p>
</li>
<li>
<p><strong>Connect the remaining pins</strong> from BL602 to Logic Analyser</p>
</li>
</ol>
<p>Then do this…</p>
<ol>
<li>
<p><strong>Launch the Logic Analyser Software</strong> on our computer</p>
</li>
<li>
<p><strong>Start capturing</strong> the SPI Signals with the Logic Analyser Software</p>
</li>
<li>
<p><strong>Open a Serial Terminal</strong> to BL602.</p>
<p><strong>Enter the SPI commands</strong> to read BME280s Chip ID Register.</p>
</li>
<li>
<p>Switch back to the Logic Analyser Software.</p>
<p><strong>Select the SPI Analyser</strong> to decode the SPI Signals.</p>
</li>
</ol>
<p>We should see the decoded SPI Signals between BL602 and BME280 on all four SPI pins…</p>
<p><img src="https://lupyuen.github.io/images/spi-analyse9a.png" alt="BL602 talks to BME280 over SPI, visualised by LA2016 Logic Analyser" /></p>
<p><em>BL602 talks to BME280 over SPI, visualised by LA2016 Logic Analyser</em></p>
<p>This should look similar to the decoded SPI Signals between Bus Pirate and BME280. <a href="https://lupyuen.github.io/images/spi-analyse1a.png">(See this)</a></p>
<h1 id="appendix-inside-bl602-spi-hal"><a class="doc-anchor" href="#appendix-inside-bl602-spi-hal">§</a>16 Appendix: Inside BL602 SPI HAL</h1>
<p>Lets walk through the BL602 SPI HAL code and understand how it performs SPI Transfers with DMA.</p>
<p>We havent modified any logic in the SPI HAL. (Though we have added some debug code to understand how the HAL works)</p>
<p>We added the function <code>spi_init</code> to initialise the SPI Port without using the AliOS Device Tree.</p>
<h2 id="definitions"><a class="doc-anchor" href="#definitions">§</a>16.1 Definitions</h2>
<p><code>HAL_SPI_DEBUG</code> is presently set to <code>1</code> to enable debug messages.</p>
<p>For production we should change this to <code>0</code>.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L57-L58"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>// TODO: Change to 0 for production to disable logging
#define HAL_SPI_DEBUG (1) </code></pre></div>
<p><code>HAL_SPI_HARDCS</code> is supposed to control the Chip Select Pin via GPIO… But it doesnt work according to our testing.</p>
<div class="example-wrap"><pre class="language-c"><code>// TODO: When set to 0, this is supposed to control
// Chip Select Pin as GPIO (instead of SPI).
// But this doesn&#39;t work, because the pin has been
// configured for SPI Port, which overrides GPIO.
#define HAL_SPI_HARDCS (1)</code></pre></div><h2 id="spi_init-init-spi-port"><a class="doc-anchor" href="#spi_init-init-spi-port">§</a>16.2 spi_init: Init SPI Port</h2>
<p>We added the function <code>spi_init</code> to initialise the SPI Port without using the AliOS Device Tree. This function is called by our demo firmware.</p>
<p><code>spi_init</code> was derived from these SPI HAL Functions (which use the AliOS Device Tree)…</p>
<ul>
<li>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L597-L760"><code>spi_arg_set_fdt2</code></a></p>
</li>
<li>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L524-L591"><code>vfs_spi_init_fullname</code></a></p>
</li>
</ul>
<p>Note that there is only a <strong>single global instance of SPI Data.</strong></p>
<p><strong><code>spi_init</code> shall only be called once by our firmware.</strong></p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L838-L886"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>// Global single instance of SPI Data.
// We support only one instance of SPI Device.
static spi_priv_data_t g_spi_data;
// Init the SPI Device for DMA without calling AOS and
// Device Tree. Return non-zero in case of error.
// Supports only one instance of SPI Device.
// Based on spi_arg_set_fdt2 and vfs_spi_init_fullname.
int spi_init(spi_dev_t *spi, uint8_t port,
uint8_t mode, uint8_t polar_phase, uint32_t freq, uint8_t tx_dma_ch, uint8_t rx_dma_ch,
uint8_t pin_clk, uint8_t pin_cs, uint8_t pin_mosi, uint8_t pin_miso)
{
assert(spi != NULL);
// Use the global single instance of SPI Data
g_hal_buf = &amp;g_spi_data;
memset(g_hal_buf, 0, sizeof(spi_priv_data_t));</code></pre></div>
<p><a href="https://lupyuen.github.io/articles/spi#initialise-spi-port">(The parameters for <code>spi_init</code> are listed here)</a></p>
<p>We call <code>xEventGroupCreate</code> to create a FreeRTOS Event Group.</p>
<p>This Event Group will be signalled by the DMA Interrupt Handlers to notify the Foreground Task that the DMA Transmit and Receive Requests have been completed.</p>
<div class="example-wrap"><pre class="language-c"><code> // Create the Event Group for DMA Interrupt Handler to notify Foreground Task
g_hal_buf-&gt;hwspi[port].spi_dma_event_group = xEventGroupCreate();
blog_info(&quot;port%d eventloop init = %08lx\r\n&quot;, port,
(uint32_t)g_hal_buf-&gt;hwspi[port].spi_dma_event_group);
if (NULL == g_hal_buf-&gt;hwspi[port].spi_dma_event_group) {
return -ENOMEM;
}</code></pre></div>
<p>We set the internal fields of the SPI device…</p>
<div class="example-wrap"><pre class="language-c"><code> // Init the SPI Device
memset(spi, 0, sizeof(spi_dev_t));
spi-&gt;port = port;
spi-&gt;config.mode = mode;
spi-&gt;config.freq = 0; // Will validate and set frequency in hal_spi_set_rwspeed
g_hal_buf-&gt;hwspi[port].ssp_id = port;
g_hal_buf-&gt;hwspi[port].mode = mode;
g_hal_buf-&gt;hwspi[port].polar_phase = polar_phase;
g_hal_buf-&gt;hwspi[port].freq = 0; // Will validate and set frequency in hal_spi_set_rwspeed
g_hal_buf-&gt;hwspi[port].tx_dma_ch = tx_dma_ch;
g_hal_buf-&gt;hwspi[port].rx_dma_ch = rx_dma_ch;
g_hal_buf-&gt;hwspi[port].pin_clk = pin_clk;
g_hal_buf-&gt;hwspi[port].pin_cs = pin_cs;
g_hal_buf-&gt;hwspi[port].pin_mosi = pin_mosi;
g_hal_buf-&gt;hwspi[port].pin_miso = pin_miso;
// SPI Device points to global single instance of SPI Data
spi-&gt;priv = g_hal_buf;
blog_info(&quot;[HAL] [SPI] Init :\r\nport=%d, mode=%d, polar_phase = %d, freq=%ld, tx_dma_ch=%d, rx_dma_ch=%d, pin_clk=%d, pin_cs=%d, pin_mosi=%d, pin_miso=%d\r\n&quot;,
port, mode, polar_phase, freq, tx_dma_ch, rx_dma_ch, pin_clk, pin_cs, pin_mosi, pin_miso);</code></pre></div>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L563-L580">(This code is derived from <code>vfs_spi_init_fullname</code>)</a></p>
<p>Note that we dont assign the SPI Frequency yet. Well assign later in <code>hal_spi_set_rwspeed</code> because the function validates the SPI Frequency.</p>
<p>Finally we call <a href="https://lupyuen.github.io/articles/spi#hal_spi_set_rwspeed-set-spi-frequency"><code>hal_spi_set_rwspeed</code></a> to set the SPI Frequency, assign the SPI Pins and initialise the DMA Controller…</p>
<div class="example-wrap"><pre class="language-c"><code> // Init the SPI speed, pins and DMA
int rc = hal_spi_set_rwspeed(spi, freq);
assert(rc == 0);
return rc;
}</code></pre></div><h2 id="hal_spi_set_rwspeed-set-spi-frequency"><a class="doc-anchor" href="#hal_spi_set_rwspeed-set-spi-frequency">§</a>16.3 hal_spi_set_rwspeed: Set SPI Frequency</h2>
<p><code>hal_spi_set_rwspeed</code> is called by <a href="https://lupyuen.github.io/articles/spi#spi_init-init-spi-port"><code>spi_init</code></a> to set the SPI Frequency, assign the SPI Pins and initialise the DMA Controller.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L430-L480"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int hal_spi_set_rwspeed(spi_dev_t *spi_dev, uint32_t speed)
{
spi_priv_data_t *data;
int i;
uint8_t real_flag = 0;
uint32_t real_speed = 0;
#if (HAL_SPI_DEBUG)
blog_info(&quot;set rwspeed = %ld\r\n&quot;, speed);
#endif
if (spi_dev-&gt;config.freq == speed) {
blog_info(&quot;speed not change.\r\n&quot;);
return 0;
}</code></pre></div>
<p>Parameter <code>speed</code> is the SPI Frequency (in Hz) from 200,000 (200 kHz) to 40,000,000 (40 MHz).</p>
<p>The Actual SPI Frequency needs to be divisible by 40,000,000.</p>
<p>Here we compute the divisor for the SPI Frequency and derive the Actual SPI Frequency <code>real_speed</code></p>
<div class="example-wrap"><pre class="language-c"><code> for (i = 0; i &lt; 256; i++) {
if (speed == (40000000/(i+1))) {
real_speed = speed;
real_flag = 1;
} else if (speed &lt; (40000000/(i+1))) {
continue;
} else {
break;
}
}</code></pre></div>
<p>We validate that the Actual SPI Frequency is in the range 200 kHz to 40 MHz…</p>
<div class="example-wrap"><pre class="language-c"><code> if (real_flag != 1) {
if (i == 0) {
blog_error(&quot;The max speed is 40000000 Hz, please set it smaller.&quot;);
return -1;
} else if (i == 256) {
blog_error(&quot;The min speed is 156250 Hz, please set it bigger.&quot;);
return -1;
} else {
if ( ((40000000/(i+1)) - speed) &gt; (speed - (40000000/i)) ) {
real_speed = (40000000/(i+1));
blog_info(&quot;not support speed: %ld, change real_speed = %ld\r\n&quot;, speed, real_speed);
} else {
real_speed = (40000000/i);
blog_info(&quot;not support speed: %ld, change real_speed = %ld\r\n&quot;, speed, real_speed);
}
}
}</code></pre></div>
<p>We set the Actual SPI Frequency…</p>
<div class="example-wrap"><pre class="language-c"><code> data = (spi_priv_data_t *)spi_dev-&gt;priv;
data-&gt;hwspi[spi_dev-&gt;port].freq = real_speed;
spi_dev-&gt;config.freq = real_speed;</code></pre></div>
<p>Finally we call <a href="https://lupyuen.github.io/articles/spi#hal_spi_init-init-spi-pins-and-dma"><code>hal_spi_init</code></a> to assign the SPI Pins and initialise the DMA Controller.</p>
<div class="example-wrap"><pre class="language-c"><code> hal_spi_init(spi_dev);
return 0;
}</code></pre></div><h2 id="hal_spi_init-init-spi-pins-and-dma"><a class="doc-anchor" href="#hal_spi_init-init-spi-pins-and-dma">§</a>16.4 hal_spi_init: Init SPI Pins and DMA</h2>
<p><code>hal_spi_init</code> is called by <a href="https://lupyuen.github.io/articles/spi#hal_spi_set_rwspeed-set-spi-frequency"><code>hal_spi_set_rwspeed</code></a> to assign the SPI Pins and initialise the DMA Controller.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L360-L384"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int32_t hal_spi_init(spi_dev_t *spi)
{
int i;
spi_priv_data_t *data;
if (!spi) {
blog_error(&quot;arg err.\r\n&quot;);
}
data = (spi_priv_data_t *)spi-&gt;priv;
if (data == NULL) {
return -1;
}</code></pre></div>
<p>For every SPI Port (theres only one: SPI Port 0)…</p>
<ol>
<li>
<p>We call <a href="https://lupyuen.github.io/articles/spi#hal_gpio_init-assign-spi-pins"><code>hal_gpio_init</code></a> to assign the SPI Pins and configure the SPI Port as SPI Controller or Peripheral</p>
</li>
<li>
<p>We call <a href="https://lupyuen.github.io/articles/spi#hal_spi_dma_init-init-spi-dma"><code>hal_spi_dma_init</code></a> to initialise the DMA Controller</p>
</li>
</ol>
<div class="example-wrap"><pre class="language-c"><code> for (i = 0; i &lt; SPI_NUM_MAX; i++) {
hal_gpio_init(&amp;data-&gt;hwspi[i]);
hal_spi_dma_init(&amp;data-&gt;hwspi[i]);
}
#if (HAL_SPI_DEBUG)
blog_info(&quot;hal_spi_init.\r\n&quot;);
#endif
return 0;
}</code></pre></div><h2 id="hal_gpio_init-assign-spi-pins"><a class="doc-anchor" href="#hal_gpio_init-assign-spi-pins">§</a>16.5 hal_gpio_init: Assign SPI Pins</h2>
<p><code>hal_gpio_init</code> is called by <a href="https://lupyuen.github.io/articles/spi#hal_spi_init-init-spi-pins-and-dma"><code>hal_spi_init</code></a> to assign the SPI Pins and configure the SPI Port as SPI Controller or Peripheral.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L98-L124"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>static void hal_gpio_init(spi_hw_t *arg)
{
GLB_GPIO_Type gpiopins[4];
if (!arg) {
blog_error(&quot;arg err.\r\n&quot;);
return;
}
blog_info(&quot;hal_gpio_init: cs:%d, clk:%d, mosi:%d, miso: %d\r\n&quot;, arg-&gt;pin_cs, arg-&gt;pin_clk, arg-&gt;pin_mosi, arg-&gt;pin_miso);</code></pre></div>
<p>We call <code>GLB_GPIO_Func_Init</code> to assign the four SPI Pins to the SPI Port…</p>
<div class="example-wrap"><pre class="language-c"><code> gpiopins[0] = arg-&gt;pin_cs;
gpiopins[1] = arg-&gt;pin_clk;
gpiopins[2] = arg-&gt;pin_mosi;
gpiopins[3] = arg-&gt;pin_miso;
GLB_GPIO_Func_Init(GPIO_FUN_SPI,gpiopins,sizeof(gpiopins)/sizeof(gpiopins[0]));</code></pre></div>
<p>The sequence of the SPI Pins doesnt matter, because each pin has a fixed SPI Function (like Serial Data In, Serial Data Out) within the SPI Port.</p>
<p>(<code>GLB_GPIO_Func_Init</code> comes from 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_glb.c"><code>bl602_glb.c</code></a>)</p>
<p>Finally we configure the SPI Port as SPI Controller or Peripheral…</p>
<div class="example-wrap"><pre class="language-c"><code> if (arg-&gt;mode == 0) {
blog_info(&quot;hal_gpio_init: SPI controller mode\r\n&quot;);
GLB_Set_SPI_0_ACT_MOD_Sel(GLB_SPI_PAD_ACT_AS_MASTER);
} else {
blog_info(&quot;hal_gpio_init: SPI peripheral mode\r\n&quot;);
GLB_Set_SPI_0_ACT_MOD_Sel(GLB_SPI_PAD_ACT_AS_SLAVE);
}
return;
}</code></pre></div><h2 id="hal_spi_dma_init-init-spi-dma"><a class="doc-anchor" href="#hal_spi_dma_init-init-spi-dma">§</a>16.6 hal_spi_dma_init: Init SPI DMA</h2>
<p><code>hal_spi_dma_init</code> is called by <a href="https://lupyuen.github.io/articles/spi#hal_spi_init-init-spi-pins-and-dma"><code>hal_spi_init</code></a> to initialise the DMA Controller.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L207-L288"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>static void hal_spi_dma_init(spi_hw_t *arg)
{
spi_hw_t *hw_arg = arg;
SPI_CFG_Type spicfg;
SPI_ClockCfg_Type clockcfg;
SPI_FifoCfg_Type fifocfg;
SPI_ID_Type spi_id;
uint8_t clk_div;
spi_id = hw_arg-&gt;ssp_id;</code></pre></div>
<p>We configure the <strong>Timing Intervals for the SPI Clock</strong> (according to the Actual SPI Frequency)…</p>
<ol>
<li>
<p>Length of the Start Condition</p>
</li>
<li>
<p>Length of the Stop Condition</p>
</li>
<li>
<p>Length of Phase 0</p>
</li>
<li>
<p>Length of Phase 1</p>
</li>
<li>
<p>Interval between each frame of data</p>
</li>
</ol>
<div class="example-wrap"><pre class="language-c"><code> /* clock */
/*1 ---&gt; 40 Mhz
*2 ---&gt; 20 Mhz
*5 ---&gt; 8 Mhz
*6 ---&gt; 6.66 Mhz
*10 ---&gt; 4 Mhz
* */
clk_div = (uint8_t)(40000000 / hw_arg-&gt;freq);
GLB_Set_SPI_CLK(ENABLE,0);
clockcfg.startLen = clk_div;
clockcfg.stopLen = clk_div;
clockcfg.dataPhase0Len = clk_div;
clockcfg.dataPhase1Len = clk_div;
clockcfg.intervalLen = clk_div;
SPI_ClockConfig(spi_id, &amp;clockcfg);</code></pre></div>
<p>(This is currently a Square Wave… But we may shape the SPI Clock if necessary)</p>
<p>We set the SPI Configuration: Deglitching, Continuous Chip Enable, Byte Sequence, Bit Sequence, Frame Size…</p>
<div class="example-wrap"><pre class="language-c"><code> /* spi config */
spicfg.deglitchEnable = DISABLE;
spicfg.continuousEnable = ENABLE;
spicfg.byteSequence = SPI_BYTE_INVERSE_BYTE0_FIRST,
spicfg.bitSequence = SPI_BIT_INVERSE_MSB_FIRST,
spicfg.frameSize = SPI_FRAME_SIZE_8;</code></pre></div>
<p>We set the SPI Polarity and Phase…</p>
<div class="example-wrap"><pre class="language-c"><code> if (hw_arg-&gt;polar_phase == 0) {
spicfg.clkPhaseInv = SPI_CLK_PHASE_INVERSE_0;
spicfg.clkPolarity = SPI_CLK_POLARITY_LOW;
} else if (hw_arg-&gt;polar_phase == 1) {
spicfg.clkPhaseInv = SPI_CLK_PHASE_INVERSE_1;
spicfg.clkPolarity = SPI_CLK_POLARITY_LOW;
} else if (hw_arg-&gt;polar_phase == 2) {
spicfg.clkPhaseInv = SPI_CLK_PHASE_INVERSE_0;
spicfg.clkPolarity = SPI_CLK_POLARITY_HIGH;
} else if (hw_arg-&gt;polar_phase == 3) {
spicfg.clkPhaseInv = SPI_CLK_PHASE_INVERSE_1;
spicfg.clkPolarity = SPI_CLK_POLARITY_HIGH;
} else {
blog_error(&quot;node support polar_phase \r\n&quot;);
}</code></pre></div>
<p>We initialise the SPI Port and disable it. We disable all interrupts.</p>
<div class="example-wrap"><pre class="language-c"><code> // TODO: In future when there are multiple
// SPI ports, this should be
// SPI_Init(spi_id, &amp;spicfg)
SPI_Init(0,&amp;spicfg);
if (hw_arg-&gt;mode == 0)
{
SPI_Disable(spi_id, SPI_WORK_MODE_MASTER);
} else {
SPI_Disable(spi_id, SPI_WORK_MODE_SLAVE);
}
SPI_IntMask(spi_id,SPI_INT_ALL,MASK);</code></pre></div>
<p>We configure the SPI FIFO (First-In First-Out Queue) Threshold, and use DMA for FIFO…</p>
<div class="example-wrap"><pre class="language-c"><code> /* fifo */
fifocfg.txFifoThreshold = 1;
fifocfg.rxFifoThreshold = 1;
fifocfg.txFifoDmaEnable = ENABLE;
fifocfg.rxFifoDmaEnable = ENABLE;
SPI_FifoConfig(spi_id,&amp;fifocfg);</code></pre></div>
<p><strong>SPI FIFO</strong> is the <strong>Transmit / Receive Queue for SPI</strong>. The SPI FIFO Transmit and Receive Queues can buffer up to <strong>4 bytes</strong> each…</p>
<ul>
<li>
<p><strong>SPI Transmit FIFO:</strong> <code>spi_fifo_wdata</code> at <code>0x4000a288</code></p>
</li>
<li>
<p><strong>SPI Receive FIFO:</strong> <code>spi_fifo_rdata</code> at <code>0x4000a28c</code></p>
</li>
</ul>
<p>When we set the SPI FIFO Thresholds to 1 (<code>txFifoThreshold</code> and <code>rxFifoThreshold</code>), the SPI Port will notify the DMA Controller whenever one byte has been transmitted or received, so that the DMA Controller will copy the next transmit / receive byte.</p>
<p>Next we configure the Transmit DMA Interrupts…</p>
<div class="example-wrap"><pre class="language-c"><code> DMA_Disable();
DMA_IntMask(hw_arg-&gt;tx_dma_ch, DMA_INT_ALL, MASK);
DMA_IntMask(hw_arg-&gt;tx_dma_ch, DMA_INT_TCOMPLETED, UNMASK);
DMA_IntMask(hw_arg-&gt;tx_dma_ch, DMA_INT_ERR, UNMASK);</code></pre></div>
<p>Then we configure the Receive DMA Interrupts and enable the DMA interrupts…</p>
<div class="example-wrap"><pre class="language-c"><code> DMA_IntMask(hw_arg-&gt;rx_dma_ch, DMA_INT_ALL, MASK);
DMA_IntMask(hw_arg-&gt;rx_dma_ch, DMA_INT_TCOMPLETED, UNMASK);
DMA_IntMask(hw_arg-&gt;rx_dma_ch, DMA_INT_ERR, UNMASK);
bl_irq_enable(DMA_ALL_IRQn);</code></pre></div>
<p>Finally we register the DMA Interrupt Handlers…</p>
<ol>
<li>
<p><strong>Transmit DMA Interrupt Handler:</strong> <a href="https://lupyuen.github.io/articles/spi#bl_spi0_dma_int_handler_tx-transmit-dma-interrupt-handler"><code>bl_spi0_dma_int_handler_tx</code></a></p>
<p>This will be triggered when an SPI DMA Transmit Request completes (successfully or unsuccessfully)</p>
</li>
<li>
<p><strong>Receive DMA Interrupt Handler:</strong> <a href="https://lupyuen.github.io/articles/spi#bl_spi0_dma_int_handler_rx-receive-dma-interrupt-handler"><code>bl_spi0_dma_int_handler_rx</code></a></p>
<p>This will be triggered when an SPI DMA Receive Request completes (successfully or unsuccessfully)</p>
</li>
</ol>
<div class="example-wrap"><pre class="language-c"><code> bl_dma_irq_register(hw_arg-&gt;tx_dma_ch, bl_spi0_dma_int_handler_tx, NULL, NULL);
bl_dma_irq_register(hw_arg-&gt;rx_dma_ch, bl_spi0_dma_int_handler_rx, NULL, NULL);
return;
}</code></pre></div><h2 id="hal_spi_transfer-execute-spi-transfer"><a class="doc-anchor" href="#hal_spi_transfer-execute-spi-transfer">§</a>16.7 hal_spi_transfer: Execute SPI Transfer</h2>
<p><code>hal_spi_transfer</code> is called by our demo firmware to execute multiple SPI Transfers with DMA.</p>
<p><code>hal_spi_transfer</code> is a Blocking Function… It waits for the SPI Transfers to complete before returning.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L482-L522"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>int hal_spi_transfer(spi_dev_t *spi_dev, void *xfer, uint8_t size)
{
uint16_t i;
spi_ioc_transfer_t * s_xfer;
spi_priv_data_t *priv_data;
if ((!spi_dev) || (!xfer)) {
blog_error(&quot;arg err.\r\n&quot;);
return -1;
}
priv_data = (spi_priv_data_t *)spi_dev-&gt;priv;
if (priv_data == NULL) {
blog_error(&quot;priv_data NULL.\r\n&quot;);
return -1;
}
s_xfer = (spi_ioc_transfer_t *)xfer;
#if (HAL_SPI_DEBUG)
blog_info(&quot;hal_spi_transfer = %d\r\n&quot;, size);
#endif</code></pre></div>
<p>Parameter <code>xfer</code> contains an array of SPI Transfers (<code>spi_ioc_transfer_t</code>).</p>
<p>Parameter <code>size</code> specifies the number of SPI Transfers in <code>xfer</code>.</p>
<p>If <code>HAL_SPI_HARDCS</code> is 0, this code is supposed to set the Chip Select Pin to Low via GPIO (which activates the SPI Peripheral). But it doesnt work.</p>
<div class="example-wrap"><pre class="language-c"><code>#if (0 == HAL_SPI_HARDCS)
blog_info(&quot;Set CS pin %d to low\r\n&quot;, priv_data-&gt;hwspi[spi_dev-&gt;port].pin_cs);
bl_gpio_output_set(priv_data-&gt;hwspi[spi_dev-&gt;port].pin_cs, 0);
#endif</code></pre></div>
<p><strong>For every SPI Transfer:</strong> We call <a href="https://lupyuen.github.io/articles/spi#hal_spi_dma_trans-execute-spi-transfer-with-dma"><code>hal_spi_dma_trans</code></a> to execute the SPI Transfer, and wait for the SPI Transfer to complete.</p>
<div class="example-wrap"><pre class="language-c"><code> for (i = 0; i &lt; size; i++) {
#if (HAL_SPI_DEBUG)
blog_info(&quot;transfer xfer[%d].len = %ld\r\n&quot;, i, s_xfer[i].len);
#endif
hal_spi_dma_trans(&amp;priv_data-&gt;hwspi[spi_dev-&gt;port],
(uint8_t *)s_xfer[i].tx_buf, (uint8_t *)s_xfer[i].rx_buf, s_xfer[i].len);
}</code></pre></div>
<p>If <code>HAL_SPI_HARDCS</code> is 0, this code is supposed to set the Chip Select Pin to High via GPIO (which deactivates the SPI Peripheral). But it doesnt work.</p>
<div class="example-wrap"><pre class="language-c"><code>#if (0 == HAL_SPI_HARDCS)
bl_gpio_output_set(priv_data-&gt;hwspi[spi_dev-&gt;port].pin_cs, 1);
blog_info(&quot;Set CS pin %d to high\r\n&quot;, priv_data-&gt;hwspi[spi_dev-&gt;port].pin_cs);
#endif
return 0;
}</code></pre></div><h2 id="lli_list_init-create-dma-linked-list"><a class="doc-anchor" href="#lli_list_init-create-dma-linked-list">§</a>16.8 lli_list_init: Create DMA Linked List</h2>
<p>BL602s DMA Controller accepts a <strong>DMA Linked List</strong> of DMA Requests that will be executed automatically.</p>
<p>In <code>lli_list_init</code> well create two DMA Linked Lists of DMA Requests…</p>
<ol>
<li>
<p>One DMA Linked List for <strong>SPI Transmit:</strong> Copy data from RAM to SPI Port for transmission</p>
</li>
<li>
<p>Another DMA Linked List for <strong>SPI Receive:</strong> Copy received data from SPI Port to RAM</p>
</li>
</ol>
<p><code>lli_list_init</code> is called by <a href="https://lupyuen.github.io/articles/spi#hal_spi_dma_trans-execute-spi-transfer-with-dma"><code>hal_spi_dma_trans</code></a>.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L126-L205"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>static int lli_list_init(DMA_LLI_Ctrl_Type **pptxlli, DMA_LLI_Ctrl_Type **pprxlli, uint8_t *ptx_data, uint8_t *prx_data, uint32_t length)
{
uint32_t i = 0;
uint32_t count;
uint32_t remainder;
struct DMA_Control_Reg dmactrl;</code></pre></div>
<p>Each DMA Transfer Request is limited to <strong>2048 bytes (<code>LLI_BUFF_SIZE</code>).</strong></p>
<p>To execute larger requests, we break them into <strong>smaller requests of 2048 bytes each</strong>, and add them to the DMA Linked List…</p>
<div class="example-wrap"><pre class="language-c"><code> count = length / LLI_BUFF_SIZE;
remainder = length % LLI_BUFF_SIZE;
if (remainder != 0) {
count = count + 1;
}</code></pre></div>
<p>We define the parameters for the DMA Request…</p>
<div class="example-wrap"><pre class="language-c"><code> dmactrl.SBSize = DMA_BURST_SIZE_1;
dmactrl.DBSize = DMA_BURST_SIZE_1;
dmactrl.SWidth = DMA_TRNS_WIDTH_8BITS;
dmactrl.DWidth = DMA_TRNS_WIDTH_8BITS;
dmactrl.Prot = 0;
dmactrl.SLargerD = 0;</code></pre></div>
<p>We call <code>pvPortMalloc</code> (from FreeRTOS) to allocate heap memory for storing the DMA Linked List…</p>
<div class="example-wrap"><pre class="language-c"><code> *pptxlli = pvPortMalloc(sizeof(DMA_LLI_Ctrl_Type) * count);
if (*pptxlli == NULL) {
blog_error(&quot;malloc lli failed. \r\n&quot;);
return -1;
}
*pprxlli = pvPortMalloc(sizeof(DMA_LLI_Ctrl_Type) * count);
if (*pprxlli == NULL) {
blog_error(&quot;malloc lli failed.&quot;);
vPortFree(*pptxlli);
return -1;
}</code></pre></div>
<p><strong>For every chunk of SPI Transfer (max 2048 bytes):</strong></p>
<p>We create the DMA Requests for each chunk of SPI Transmit and SPI Receive (max 2048 bytes)…</p>
<div class="example-wrap"><pre class="language-c"><code> for (i = 0; i &lt; count; i++) {</code></pre></div>
<p>We compute the DMA Transmit / Receive Size…</p>
<div class="example-wrap"><pre class="language-c"><code> if (remainder == 0) {
dmactrl.TransferSize = LLI_BUFF_SIZE;
} else {
if (i == count - 1) {
dmactrl.TransferSize = remainder;
} else {
dmactrl.TransferSize = LLI_BUFF_SIZE;
}
}</code></pre></div>
<p><strong>For SPI Transmit:</strong> We configure the <strong>DMA Automatic Address Accumulation</strong> for Source (SI) and Destination (DI)…</p>
<div class="example-wrap"><pre class="language-c"><code> dmactrl.SI = DMA_MINC_ENABLE;
dmactrl.DI = DMA_MINC_DISABLE;</code></pre></div>
<p>The <a href="https://github.com/bouffalolab/bl_docs/tree/main/BL602_RM/en">BL602 Reference Manual</a> doesnt explain Automatic Address Accumulation. (See Section 6.3.2 “DMA Channel Configuration”, Page 73)</p>
<p><em>(In the original Chinese docs, “Automatic Address Accumulation” actually means “Automatic Address Increment”. <a href="https://twitter.com/MisterTechBlog/status/1358502589105508352?s=19">See this</a>)</em></p>
<p>Lets assume that the above configuration will auto-increment the Source RAM Address (SI) when the DMA Controller copies data from RAM to the SPI Port.</p>
<p>We dont auto-increment the Destination Address (DI) because the SPI Port uses a single address for transmitting data: <code>spi_fifo_wdata</code> at <code>0x4000a288</code></p>
<p><em>(<code>DMA_MINC_ENABLE</code> means “Enable Memory Increment Mode”, <code>DMA_MINC_DISABLE</code> means “Disable Memory Increment Mode”. <a href="https://www.st.com/content/ccc/resource/technical/document/user_manual/2f/71/ba/b8/75/54/47/cf/DM00105879.pdf/files/DM00105879.pdf/jcr:content/translations/en.DM00105879.pdf">See this</a>)</em></p>
<p>We set I to 1 if this is the last entry in the DMA Linked List…</p>
<div class="example-wrap"><pre class="language-c"><code> if (i == count - 1) {
dmactrl.I = 1;
} else {
dmactrl.I = 0;
}</code></pre></div>
<p><strong>For SPI Transmit:</strong> We create a DMA Request that copies data from RAM to the SPI Port for transmission…</p>
<div class="example-wrap"><pre class="language-c"><code> (*pptxlli)[i].srcDmaAddr = (uint32_t)(ptx_data + i * LLI_BUFF_SIZE);
(*pptxlli)[i].destDmaAddr = (uint32_t)(SPI_BASE+SPI_FIFO_WDATA_OFFSET);
(*pptxlli)[i].dmaCtrl = dmactrl;
blog_info(&quot;Tx DMA src=0x%x, dest=0x%x, size=%d, si=%d, di=%d, i=%d\r\n&quot;, (unsigned) (*pptxlli)[i].srcDmaAddr, (unsigned) (*pptxlli)[i].destDmaAddr, dmactrl.TransferSize, dmactrl.SI, dmactrl.DI, dmactrl.I);</code></pre></div>
<p><strong>For SPI Receive:</strong> We configure the <strong>DMA Automatic Address Accumulation (Increment)</strong> for Source (SI) and Destination (DI)…</p>
<div class="example-wrap"><pre class="language-c"><code> dmactrl.SI = DMA_MINC_DISABLE;
dmactrl.DI = DMA_MINC_ENABLE;</code></pre></div>
<p>Lets assume that this will auto-increment the Destination RAM Address (DI) when the DMA Controller copies the received data from the SPI Port to RAM.</p>
<p>We dont auto-increment the Source Address (SI) because the SPI Port uses a single address for receiving data: <code>spi_fifo_rdata</code> at <code>0x4000a28c</code></p>
<p><em>(<code>DMA_MINC_ENABLE</code> means “Enable Memory Increment Mode”, <code>DMA_MINC_DISABLE</code> means “Disable Memory Increment Mode”. <a href="https://www.st.com/content/ccc/resource/technical/document/user_manual/2f/71/ba/b8/75/54/47/cf/DM00105879.pdf/files/DM00105879.pdf/jcr:content/translations/en.DM00105879.pdf">See this</a>)</em></p>
<p><strong>For SPI Receive:</strong> We create a DMA Request that copies the received data from the SPI Port to RAM…</p>
<div class="example-wrap"><pre class="language-c"><code> (*pprxlli)[i].srcDmaAddr = (uint32_t)(SPI_BASE+SPI_FIFO_RDATA_OFFSET);
(*pprxlli)[i].destDmaAddr = (uint32_t)(prx_data + i * LLI_BUFF_SIZE);
(*pprxlli)[i].dmaCtrl = dmactrl;
blog_info(&quot;Rx DMA src=0x%x, dest=0x%x, size=%d, si=%d, di=%d, i=%d\r\n&quot;, (unsigned) (*pprxlli)[i].srcDmaAddr, (unsigned) (*pprxlli)[i].destDmaAddr, dmactrl.TransferSize, dmactrl.SI, dmactrl.DI, dmactrl.I);</code></pre></div>
<p>Finally we append both DMA Requests to the DMA Linked Lists (Transmit and Receive)…</p>
<div class="example-wrap"><pre class="language-c"><code> if (i != 0) {
(*pptxlli)[i-1].nextLLI = (uint32_t)&amp;(*pptxlli)[i];
(*pprxlli)[i-1].nextLLI = (uint32_t)&amp;(*pprxlli)[i];
}
(*pptxlli)[i].nextLLI = 0;
(*pprxlli)[i].nextLLI = 0;
}
return 0;
}</code></pre></div>
<p>Heres the debug output from <code>lli_list_init</code> when it creates the DMA Linked Lists (Transmit and Receive) for our two SPI Transfers…</p>
<ol>
<li>
<p><strong>DMA Linked Lists (Transmit and Receive) for First SPI Transfer:</strong></p>
<div class="example-wrap"><pre class="language-text"><code>Tx DMA src=0x4200d1b8, dest=0x4000a288, size=1, si=1, di=0, i=1
Rx DMA src=0x4000a28c, dest=0x4200d1b0, size=1, si=0, di=1, i=1</code></pre></div>
<p>The Transmit DMA Linked List <strong><code>Tx DMA</code></strong> copies the data from our first Transmit Buffer <strong><code>tx_buf1</code></strong> to the SPI Port…</p>
<ul>
<li>
<p><strong><code>Tx DMA src</code></strong> is the address of our first Transmit Buffer <code>tx_buf1</code></p>
</li>
<li>
<p><strong><code>Tx DMA dest</code></strong> is the address of the SPI Transmit FIFO: <code>spi_fifo_wdata</code> at <code>0x4000a288</code></p>
</li>
</ul>
<p>The Receive DMA Linked List <strong><code>Rx DMA</code></strong> copies the received data from the SPI Port to our first Receive Buffer <strong><code>rx_buf1</code></strong></p>
<ul>
<li>
<p><strong><code>Rx DMA src</code></strong> is the address of the SPI Receive FIFO: <code>spi_fifo_rdata</code> at <code>0x4000a28c</code></p>
</li>
<li>
<p><strong><code>Rx DMA dest</code></strong> is the address of our first Receive Buffer <code>rx_buf1</code></p>
</li>
</ul>
</li>
<li>
<p><strong>DMA Linked Lists (Transmit and Receive) for Second SPI Transfer:</strong></p>
<div class="example-wrap"><pre class="language-text"><code>Tx DMA src=0x4200d1bc, dest=0x4000a288, size=1, si=1, di=0, i=1
Rx DMA src=0x4000a28c, dest=0x4200d1b4, size=1, si=0, di=1, i=1</code></pre></div>
<p>The Transmit DMA Linked List <strong><code>Tx DMA</code></strong> copies the data from our second Transmit Buffer <strong><code>tx_buf2</code></strong> to the SPI Port…</p>
<ul>
<li>
<p><strong><code>Tx DMA src</code></strong> is the address of our second Transmit Buffer <code>tx_buf2</code></p>
</li>
<li>
<p><strong><code>Tx DMA dest</code></strong> is the address of the SPI Transmit FIFO: <code>spi_fifo_wdata</code> at <code>0x4000a288</code></p>
</li>
</ul>
<p>The Receive DMA Linked List <strong><code>Rx DMA</code></strong> copies the received data from the SPI Port to our second Receive Buffer <strong><code>rx_buf2</code></strong></p>
<ul>
<li>
<p><strong><code>Rx DMA src</code></strong> is the address of the SPI Receive FIFO: <code>spi_fifo_rdata</code> at <code>0x4000a28c</code></p>
</li>
<li>
<p><strong><code>Rx DMA dest</code></strong> is the address of our second Receive Buffer <code>rx_buf2</code></p>
</li>
</ul>
</li>
</ol>
<h2 id="hal_spi_dma_trans-execute-spi-transfer-with-dma"><a class="doc-anchor" href="#hal_spi_dma_trans-execute-spi-transfer-with-dma">§</a>16.9 hal_spi_dma_trans: Execute SPI Transfer with DMA</h2>
<p><code>hal_spi_dma_trans</code> is called by <a href="https://lupyuen.github.io/articles/spi#hal_spi_transfer-execute-spi-transfer"><code>hal_spi_transfer</code></a> to execute an SPI Transfer with DMA, and wait for the SPI Transfer to complete.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L290-L358"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>static void hal_spi_dma_trans(spi_hw_t *arg, uint8_t *TxData, uint8_t *RxData, uint32_t Len)
{
EventBits_t uxBits;
DMA_LLI_Cfg_Type txllicfg;
DMA_LLI_Cfg_Type rxllicfg;
DMA_LLI_Ctrl_Type *ptxlli;
DMA_LLI_Ctrl_Type *prxlli;
int ret;
if (!arg) {
blog_error(&quot;arg err.\r\n&quot;);
return;
}</code></pre></div>
<p>We define the DMA Request for SPI Transmit…</p>
<div class="example-wrap"><pre class="language-c"><code> txllicfg.dir = DMA_TRNS_M2P;
txllicfg.srcPeriph = DMA_REQ_NONE;
txllicfg.dstPeriph = DMA_REQ_SPI_TX;</code></pre></div>
<ul>
<li>
<p><code>DMA_TRNS_M2P</code> means Memory to Peripheral Transfer</p>
</li>
<li>
<p><code>DMA_REQ_SPI_TX</code> says that the destination is the SPI Transmit Port</p>
</li>
</ul>
<p>We define the DMA Request for SPI Receive…</p>
<div class="example-wrap"><pre class="language-c"><code> rxllicfg.dir = DMA_TRNS_P2M;
rxllicfg.srcPeriph = DMA_REQ_SPI_RX;
rxllicfg.dstPeriph = DMA_REQ_NONE;</code></pre></div>
<ul>
<li>
<p><code>DMA_TRNS_P2M</code> means Peripheral to Memory Transfer</p>
</li>
<li>
<p><code>DMA_REQ_SPI_RX</code> says that the source is the SPI Receive Port</p>
</li>
</ul>
<p>We clear the Event Group (from FreeRTOS) so that we can be signalled by the DMA Interrupt Handlers (when the DMA Requests are done)…</p>
<div class="example-wrap"><pre class="language-c"><code> xEventGroupClearBits(arg-&gt;spi_dma_event_group, EVT_GROUP_SPI_DMA_TR);</code></pre></div>
<p>We disable the DMA Channels and DMA Interrupts…</p>
<div class="example-wrap"><pre class="language-c"><code> DMA_Channel_Disable(arg-&gt;tx_dma_ch);
DMA_Channel_Disable(arg-&gt;rx_dma_ch);
bl_dma_int_clear(arg-&gt;tx_dma_ch);
bl_dma_int_clear(arg-&gt;rx_dma_ch);</code></pre></div>
<p>We enable the DMA Controller and enable SPI for Controller or Peripheral mode…</p>
<div class="example-wrap"><pre class="language-c"><code> DMA_Enable();
if (arg-&gt;mode == 0) {
SPI_Enable(arg-&gt;ssp_id, SPI_WORK_MODE_MASTER);
} else {
SPI_Enable(arg-&gt;ssp_id, SPI_WORK_MODE_SLAVE);
}</code></pre></div>
<p>We call <code>lli_list_init</code> to create the DMA Linked Lists (Transmit and Receive) that will contain every SPI Transfer…</p>
<div class="example-wrap"><pre class="language-c"><code> ret = lli_list_init(&amp;ptxlli, &amp;prxlli, TxData, RxData, Len);
if (ret &lt; 0) {
blog_error(&quot;init lli failed. \r\n&quot;);
return;
}</code></pre></div>
<p>We assign the DMA Linked Lists (Transmit and Receive) to the DMA Controller…</p>
<div class="example-wrap"><pre class="language-c"><code> DMA_LLI_Init(arg-&gt;tx_dma_ch, &amp;txllicfg);
DMA_LLI_Init(arg-&gt;rx_dma_ch, &amp;rxllicfg);
DMA_LLI_Update(arg-&gt;tx_dma_ch, (uint32_t) ptxlli);
DMA_LLI_Update(arg-&gt;rx_dma_ch, (uint32_t) prxlli);</code></pre></div>
<p>We enable the DMA Channels. The DMA Controller will transfer data according to the DMA Linked Lists…</p>
<div class="example-wrap"><pre class="language-c"><code> DMA_Channel_Enable(arg-&gt;tx_dma_ch);
DMA_Channel_Enable(arg-&gt;rx_dma_ch);</code></pre></div>
<p>We call <code>xEventGroupWaitBits</code> (from FreeRTOS) to wait until the Event Group is signalled by both DMA Interrupt Handlers: Transmit Complete and Receive Complete…</p>
<div class="example-wrap"><pre class="language-c"><code> // TODO: To troubleshoot SPI Transfers that hang
// (like ST7789 above 4 MHz), change...
// portMAX_DELAY
// To...
// 100 / portTICK_PERIOD_MS
// Which will change the SPI Timeout from
// &quot;Wait Forever&quot; to 100 milliseconds.
// Then check the Interrupt Counters and Error Codes.
uxBits = xEventGroupWaitBits( // Wait for...
arg-&gt;spi_dma_event_group, // Event Group
EVT_GROUP_SPI_DMA_TR, // For BOTH Transmit and Receive to complete
pdTRUE, // Clear bits on exit
pdTRUE, // Both Transmit and Receive bits must be set
portMAX_DELAY // Wait forever for both bits to be set
);
if ((uxBits &amp; EVT_GROUP_SPI_DMA_TR) == EVT_GROUP_SPI_DMA_TR) {
blog_info(&quot;recv all event group.\r\n&quot;);
}</code></pre></div>
<p><strong><code>EVT_GROUP_SPI_DMA_TR</code></strong> is a combination of two Events…</p>
<ol>
<li>
<p><strong><code>EVT_GROUP_SPI_DMA_TX</code>:</strong> The Transmit DMA Interrupt Handler triggers this Event when an SPI DMA Transmit Request completes (successfully or unsuccessfully)</p>
</li>
<li>
<p><strong><code>EVT_GROUP_SPI_DMA_RX</code>:</strong> The Receive DMA Interrupt Handler triggers this Event when an SPI DMA Receive Request completes (successfully or unsuccessfully)</p>
</li>
</ol>
<p>Thus when we wait for <code>EVT_GROUP_SPI_DMA_TR</code>, were waiting for the SPI DMA Transmit AND Receive Requests to complete.</p>
<p>Note that were <strong>waiting forever</strong> until both requests complete. For easier troubleshooting, follow the instructions in the comments above to change the SPI Timeout from <strong><code>portMAX_DELAY</code></strong> to <strong><code>100 / portTICK_PERIOD_MS</code></strong></p>
<p><a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L70-L72">(<code>EVT_GROUP_SPI_DMA_TR</code> is defined here)</a></p>
<p>Finally we free the heap memory for the DMA Linked Lists (Transmit and Receive)…</p>
<div class="example-wrap"><pre class="language-c"><code> vPortFree(ptxlli);
vPortFree(prxlli);
}</code></pre></div><h2 id="bl_spi0_dma_int_handler_tx-transmit-dma-interrupt-handler"><a class="doc-anchor" href="#bl_spi0_dma_int_handler_tx-transmit-dma-interrupt-handler">§</a>16.10 bl_spi0_dma_int_handler_tx: Transmit DMA Interrupt Handler</h2>
<p><code>bl_spi0_dma_int_handler_tx</code> is the DMA Interrupt Handler thats triggered when an SPI DMA Transmit Request completes (successfully or unsuccessfully).</p>
<p>The DMA Interrupt Handler is registered by <a href="https://lupyuen.github.io/articles/spi#hal_spi_dma_init-init-spi-dma"><code>hal_spi_dma_init</code></a>.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L769-L808"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>void bl_spi0_dma_int_handler_tx(void)
{
g_tx_counter++; // Increment the Transmit Interrupt Counter
g_tx_status = *(uint32_t *) 0x4000c000; // Set the Transmit Status
g_tx_tc = *(uint32_t *) 0x4000c004; // Set the Transmit Terminal Count
if (g_tx_error == 0) { g_tx_error = *(uint32_t *) 0x4000c00c; } // Set the Transmit Error Code</code></pre></div>
<p><a href="https://lupyuen.github.io/articles/spi#dma-interrupt-counters">(<code>g_tx_counter</code>, <code>g_tx_status</code> and <code>g_tx_tc</code> are explained here)</a></p>
<p>We call <code>xEventGroupSetBitsFromISR</code> to notify the Event Group, by triggering the <code>EVT_GROUP_SPI_DMA_TX</code> Event.</p>
<p>Then we call <code>portYIELD_FROM_ISR</code> to wake up the Foreground Task thats waiting for the SPI DMA Transmit Request to complete (<code>hal_spi_dma_trans</code>).</p>
<div class="example-wrap"><pre class="language-c"><code> BaseType_t xResult = pdFAIL;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (NULL != g_hal_buf) {
bl_dma_int_clear(g_hal_buf-&gt;hwspi[0].tx_dma_ch);
if (g_hal_buf-&gt;hwspi[0].spi_dma_event_group != NULL) {
xResult = xEventGroupSetBitsFromISR(
g_hal_buf-&gt;hwspi[0].spi_dma_event_group,
EVT_GROUP_SPI_DMA_TX,
&amp;xHigherPriorityTaskWoken);
}
if(xResult != pdFAIL) {
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
} else {
blog_error(&quot;bl_spi0_dma_int_handler_tx no clear isr.\r\n&quot;);
}
return;
}</code></pre></div><h2 id="bl_spi0_dma_int_handler_rx-receive-dma-interrupt-handler"><a class="doc-anchor" href="#bl_spi0_dma_int_handler_rx-receive-dma-interrupt-handler">§</a>16.11 bl_spi0_dma_int_handler_rx: Receive DMA Interrupt Handler</h2>
<p><code>bl_spi0_dma_int_handler_rx</code> is the DMA Interrupt Handler thats triggered when an SPI DMA Receive Request completes (successfully or unsuccessfully).</p>
<p>The DMA Interrupt Handler is registered by <a href="https://lupyuen.github.io/articles/spi#hal_spi_dma_init-init-spi-dma"><code>hal_spi_dma_init</code></a>.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L810-L836"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>void bl_spi0_dma_int_handler_rx(void)
{
g_rx_counter++; // Increment the Receive Interrupt Counter
g_rx_status = *(uint32_t *) 0x4000c000; // Set the Receive Status
g_rx_tc = *(uint32_t *) 0x4000c004; // Set the Receive Terminal Count
if (g_rx_error == 0) { g_rx_error = *(uint32_t *) 0x4000c00c; } // Set the Receive Error Code</code></pre></div>
<p><a href="https://lupyuen.github.io/articles/spi#dma-interrupt-counters">(<code>g_rx_counter</code>, <code>g_rx_status</code> and <code>g_rx_tc</code> are explained here)</a></p>
<p>We call <code>xEventGroupSetBitsFromISR</code> to notify the Event Group, by triggering the <code>EVT_GROUP_SPI_DMA_RX</code> Event.</p>
<p>Then we call <code>portYIELD_FROM_ISR</code> to wake up the Foreground Task thats waiting for the SPI DMA Receive Request to complete (<code>hal_spi_dma_trans</code>).</p>
<div class="example-wrap"><pre class="language-c"><code> BaseType_t xResult = pdFAIL;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if (NULL != g_hal_buf) {
bl_dma_int_clear(g_hal_buf-&gt;hwspi[0].rx_dma_ch);
if (g_hal_buf-&gt;hwspi[0].spi_dma_event_group != NULL) {
xResult = xEventGroupSetBitsFromISR(
g_hal_buf-&gt;hwspi[0].spi_dma_event_group,
EVT_GROUP_SPI_DMA_RX,
&amp;xHigherPriorityTaskWoken);
}
if(xResult != pdFAIL) {
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
} else {
blog_error(&quot;bl_spi0_dma_int_handler_rx no clear isr.\r\n&quot;);
}
return;
}</code></pre></div><h2 id="dma-interrupt-counters"><a class="doc-anchor" href="#dma-interrupt-counters">§</a>16.12 DMA Interrupt Counters</h2>
<p>To check whether the DMA Interrupts are working correctly, we added Interrupt Counters and captured the Status and Error Codes.</p>
<p>From <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/components/hal_drv/bl602_hal/hal_spi.c#L769-L808"><code>bl602_hal/hal_spi.c</code></a></p>
<div class="example-wrap"><pre class="language-c"><code>// Interrupt Counters for Transmit and Receive
int g_tx_counter;
int g_rx_counter;
// Status, Terminal Counts and Error Codes for Transmit and Receive
uint32_t g_tx_status; // Transmit Status (from 0x4000c000)
uint32_t g_tx_tc; // Transmit Terminal Count (from 0x4000c004)
uint32_t g_tx_error; // Transmit Error Code (from 0x4000c00c)
uint32_t g_rx_status; // Receive Status (from 0x4000c000)
uint32_t g_rx_tc; // Receive Terminal Count (from 0x4000c004)
uint32_t g_rx_error; // Receive Error Code (from 0x4000c00c)</code></pre></div>
<p>These values are displayed when we enter the SPI Command <a href="https://github.com/lupyuen/bl_iot_sdk/blob/master/customer_app/sdk_app_spi_demo/sdk_app_spi_demo/demo.c#L158-L182"><code>spi_result</code></a>.</p>
<p>When we complete two SPI Transfers successfully, we should see these values…</p>
<div class="example-wrap"><pre class="language-text"><code>Tx Interrupts: 2
Tx Status: 0x0
Tx Term Count: 0x0
Tx Error: 0x0
Rx Interrupts: 2
Rx Status: 0x0
Rx Term Count: 0x0
Rx Error: 0x0</code></pre></div>
<p>The Interrupt Counters, Status and Error Codes are set by the DMA Interrupt Handlers…</p>
<ol>
<li>
<p><strong>Transmit DMA Interrupt Handler:</strong> <a href="https://lupyuen.github.io/articles/spi#bl_spi0_dma_int_handler_tx-transmit-dma-interrupt-handler"><code>bl_spi0_dma_int_handler_tx</code></a></p>
<p>This will be triggered when an SPI DMA Transmit Request completes (successfully or unsuccessfully)</p>
</li>
<li>
<p><strong>Receive DMA Interrupt Handler:</strong> <a href="https://lupyuen.github.io/articles/spi#bl_spi0_dma_int_handler_rx-receive-dma-interrupt-handler"><code>bl_spi0_dma_int_handler_rx</code></a></p>
<p>This will be triggered when an SPI DMA Receive Request completes (successfully or unsuccessfully)</p>
</li>
</ol>
<!-- 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>