mirror of
https://github.com/lupyuen/lupyuen.github.io.git
synced 2025-01-13 10:18:33 +08:00
1880 lines
No EOL
113 KiB
HTML
1880 lines
No EOL
113 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="generator" content="rustdoc">
|
||
<title>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="What’s Next">12 What’s 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 we’re connecting BL602 to <strong>High Bandwidth</strong> peripherals… Like the ST7789 Display Controller and the SX1262 LoRa Transceiver?</p>
|
||
<p><strong>We’ll 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>We’ll 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 shouldn’t 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 we’ll 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>We’ll say <strong>“Serial Data In (SDI)”</strong> <em>(instead of “MISO”)</em></p>
|
||
</li>
|
||
<li>
|
||
<p>And we’ll say <strong>“Serial Data Out (SDO)”</strong> <em>(instead of “MOSI”)</em></p>
|
||
</li>
|
||
<li>
|
||
<p>We’ll 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 doesn’t 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>doesn’t 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, there’s 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 I’ll 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>Let’s 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>Don’t 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>We’ll 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>We’re 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 we’re 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>PineCone’s WiFi LED</strong> (Is this documented somewhere?)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>GPIO 11, 14, 17</strong> are connected to <strong>PineCone’s RGB LED</strong></p>
|
||
</li>
|
||
</ul>
|
||
<p>We won’t 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 shouldn’t 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>What’s the SPI Data that will be transferred between BL602 and BME280?</em></p>
|
||
<p>Here’s 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>Let’s 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(
|
||
&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>(There’s 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 won’t 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>(We’ll find out why later)</p>
|
||
<p>Because we’re 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, it’s <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 don’t need the received byte from the first request… And the transmitted byte from the second request)</p>
|
||
<p>Let’s 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>Let’s 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(&tx_buf1, 0, sizeof(tx_buf1));
|
||
memset(&rx_buf1, 0, sizeof(rx_buf1));
|
||
memset(&tx_buf2, 0, sizeof(tx_buf2));
|
||
memset(&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>We’ll 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 let’s 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 we’re 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 BME280’s 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(
|
||
&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 we’re 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'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>We’ll 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>What’s 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>Here’s 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 we’re 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 we’re blasting 1,000 bytes to an SPI Display Controller?</em></p>
|
||
<p>Our CPU will be <strong>interrupted only twice!</strong></p>
|
||
<p>That’s 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>Let’s 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'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 BL602’s UART Port at 2 Mbps like so…</p>
|
||
<p><strong>For Linux:</strong></p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>screen /dev/ttyUSB0 2000000</code></pre></div>
|
||
<p><strong>For macOS:</strong> Use CoolTerm (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
|
||
<p><strong>For Windows:</strong> Use <code>putty</code> (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
|
||
<p><strong>Alternatively:</strong> Use the Web Serial Terminal (<a href="https://lupyuen.github.io/articles/flash#watch-the-firmware-run">See this</a>)</p>
|
||
<p><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>Let’s 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 we’re 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 we’re 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, we’re 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>We’ll control the Chip Select Pin ourselves to produce the desired signal shape.</p>
|
||
<p>(It’s 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>BL602’s 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 we’ll 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: Don’t 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, let’s 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>We’re 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>Here’s a strange problem about the BL602 SPI Data Pins that still spooks me today…</p>
|
||
<p>Recall that we’re 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>Here’s 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, we’ll 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>Here’s 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>Here’s 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>Doesn’t this look like <strong>SPI Polarity 0 Phase 0, not Phase 1?</strong></p>
|
||
<p>Here’s another odd thing: This BL602 SPI Configuration (SPI Polarity 0, Phase 1) works perfectly splendid with BME280…</p>
|
||
<p><em>Yet BME280 doesn’t 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 doesn’t quite work the way we expect!</strong></p>
|
||
<p>(Yep I painstakingly verified… Setting BL602 to SPI Polarity 0, Phase 0 doesn’t 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 BL602’s 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>We’ll 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>BL602’s <strong>SPI Chip Select Pin</strong> doesn’t work with BME280’s SPI protocol.</p>
|
||
<p>And it doesn’t support multiple SPI Peripherals.</p>
|
||
<p>We’ll <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 that’s 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 What’s Next</h1>
|
||
<p>Now that we have SPI working on BL602, let’s 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>Here’s 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>. Here’s 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 I’ll 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>There’s 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&utm_medium=web2x&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>Here’s 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>Don’t use any pins on the I2C side (with 4 pins).</p>
|
||
</li>
|
||
<li>
|
||
<p>Connect Bus Pirate to our computer’s 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> 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)> 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)> 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)></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)></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)></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)></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)>
|
||
Clutch disengaged!!!
|
||
To finish setup, start up the power supplies with command 'W'
|
||
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> 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>(We’ll learn why in a while)</p>
|
||
</li>
|
||
<li>
|
||
<p>We should see…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>SPI> [ 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 BME280’s <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 computer’s 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 computer’s 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 BME280’s 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>Let’s walk through the BL602 SPI HAL code and understand how it performs SPI Transfers with DMA.</p>
|
||
<p>We haven’t 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 doesn’t 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'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 = &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->hwspi[port].spi_dma_event_group = xEventGroupCreate();
|
||
blog_info("port%d eventloop init = %08lx\r\n", port,
|
||
(uint32_t)g_hal_buf->hwspi[port].spi_dma_event_group);
|
||
if (NULL == g_hal_buf->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->port = port;
|
||
spi->config.mode = mode;
|
||
spi->config.freq = 0; // Will validate and set frequency in hal_spi_set_rwspeed
|
||
g_hal_buf->hwspi[port].ssp_id = port;
|
||
g_hal_buf->hwspi[port].mode = mode;
|
||
g_hal_buf->hwspi[port].polar_phase = polar_phase;
|
||
g_hal_buf->hwspi[port].freq = 0; // Will validate and set frequency in hal_spi_set_rwspeed
|
||
g_hal_buf->hwspi[port].tx_dma_ch = tx_dma_ch;
|
||
g_hal_buf->hwspi[port].rx_dma_ch = rx_dma_ch;
|
||
g_hal_buf->hwspi[port].pin_clk = pin_clk;
|
||
g_hal_buf->hwspi[port].pin_cs = pin_cs;
|
||
g_hal_buf->hwspi[port].pin_mosi = pin_mosi;
|
||
g_hal_buf->hwspi[port].pin_miso = pin_miso;
|
||
|
||
// SPI Device points to global single instance of SPI Data
|
||
spi->priv = g_hal_buf;
|
||
blog_info("[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",
|
||
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 don’t assign the SPI Frequency yet. We’ll 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("set rwspeed = %ld\r\n", speed);
|
||
#endif
|
||
if (spi_dev->config.freq == speed) {
|
||
blog_info("speed not change.\r\n");
|
||
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 < 256; i++) {
|
||
if (speed == (40000000/(i+1))) {
|
||
real_speed = speed;
|
||
real_flag = 1;
|
||
} else if (speed < (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("The max speed is 40000000 Hz, please set it smaller.");
|
||
return -1;
|
||
} else if (i == 256) {
|
||
blog_error("The min speed is 156250 Hz, please set it bigger.");
|
||
return -1;
|
||
} else {
|
||
if ( ((40000000/(i+1)) - speed) > (speed - (40000000/i)) ) {
|
||
real_speed = (40000000/(i+1));
|
||
blog_info("not support speed: %ld, change real_speed = %ld\r\n", speed, real_speed);
|
||
} else {
|
||
real_speed = (40000000/i);
|
||
blog_info("not support speed: %ld, change real_speed = %ld\r\n", 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->priv;
|
||
data->hwspi[spi_dev->port].freq = real_speed;
|
||
spi_dev->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("arg err.\r\n");
|
||
}
|
||
|
||
data = (spi_priv_data_t *)spi->priv;
|
||
if (data == NULL) {
|
||
return -1;
|
||
}</code></pre></div>
|
||
<p>For every SPI Port (there’s 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 < SPI_NUM_MAX; i++) {
|
||
hal_gpio_init(&data->hwspi[i]);
|
||
hal_spi_dma_init(&data->hwspi[i]);
|
||
}
|
||
|
||
#if (HAL_SPI_DEBUG)
|
||
blog_info("hal_spi_init.\r\n");
|
||
#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("arg err.\r\n");
|
||
return;
|
||
}
|
||
blog_info("hal_gpio_init: cs:%d, clk:%d, mosi:%d, miso: %d\r\n", arg->pin_cs, arg->pin_clk, arg->pin_mosi, arg->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->pin_cs;
|
||
gpiopins[1] = arg->pin_clk;
|
||
gpiopins[2] = arg->pin_mosi;
|
||
gpiopins[3] = arg->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 doesn’t 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->mode == 0) {
|
||
blog_info("hal_gpio_init: SPI controller mode\r\n");
|
||
GLB_Set_SPI_0_ACT_MOD_Sel(GLB_SPI_PAD_ACT_AS_MASTER);
|
||
} else {
|
||
blog_info("hal_gpio_init: SPI peripheral mode\r\n");
|
||
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->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 ---> 40 Mhz
|
||
*2 ---> 20 Mhz
|
||
*5 ---> 8 Mhz
|
||
*6 ---> 6.66 Mhz
|
||
*10 ---> 4 Mhz
|
||
* */
|
||
clk_div = (uint8_t)(40000000 / hw_arg->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, &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->polar_phase == 0) {
|
||
spicfg.clkPhaseInv = SPI_CLK_PHASE_INVERSE_0;
|
||
spicfg.clkPolarity = SPI_CLK_POLARITY_LOW;
|
||
} else if (hw_arg->polar_phase == 1) {
|
||
spicfg.clkPhaseInv = SPI_CLK_PHASE_INVERSE_1;
|
||
spicfg.clkPolarity = SPI_CLK_POLARITY_LOW;
|
||
} else if (hw_arg->polar_phase == 2) {
|
||
spicfg.clkPhaseInv = SPI_CLK_PHASE_INVERSE_0;
|
||
spicfg.clkPolarity = SPI_CLK_POLARITY_HIGH;
|
||
} else if (hw_arg->polar_phase == 3) {
|
||
spicfg.clkPhaseInv = SPI_CLK_PHASE_INVERSE_1;
|
||
spicfg.clkPolarity = SPI_CLK_POLARITY_HIGH;
|
||
} else {
|
||
blog_error("node support polar_phase \r\n");
|
||
}</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, &spicfg)
|
||
SPI_Init(0,&spicfg);
|
||
if (hw_arg->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,&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->tx_dma_ch, DMA_INT_ALL, MASK);
|
||
DMA_IntMask(hw_arg->tx_dma_ch, DMA_INT_TCOMPLETED, UNMASK);
|
||
DMA_IntMask(hw_arg->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->rx_dma_ch, DMA_INT_ALL, MASK);
|
||
DMA_IntMask(hw_arg->rx_dma_ch, DMA_INT_TCOMPLETED, UNMASK);
|
||
DMA_IntMask(hw_arg->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->tx_dma_ch, bl_spi0_dma_int_handler_tx, NULL, NULL);
|
||
bl_dma_irq_register(hw_arg->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("arg err.\r\n");
|
||
return -1;
|
||
}
|
||
|
||
priv_data = (spi_priv_data_t *)spi_dev->priv;
|
||
if (priv_data == NULL) {
|
||
blog_error("priv_data NULL.\r\n");
|
||
return -1;
|
||
}
|
||
|
||
s_xfer = (spi_ioc_transfer_t *)xfer;
|
||
|
||
#if (HAL_SPI_DEBUG)
|
||
blog_info("hal_spi_transfer = %d\r\n", 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 doesn’t work.</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#if (0 == HAL_SPI_HARDCS)
|
||
blog_info("Set CS pin %d to low\r\n", priv_data->hwspi[spi_dev->port].pin_cs);
|
||
bl_gpio_output_set(priv_data->hwspi[spi_dev->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 < size; i++) {
|
||
#if (HAL_SPI_DEBUG)
|
||
blog_info("transfer xfer[%d].len = %ld\r\n", i, s_xfer[i].len);
|
||
#endif
|
||
hal_spi_dma_trans(&priv_data->hwspi[spi_dev->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 doesn’t work.</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#if (0 == HAL_SPI_HARDCS)
|
||
bl_gpio_output_set(priv_data->hwspi[spi_dev->port].pin_cs, 1);
|
||
blog_info("Set CS pin %d to high\r\n", priv_data->hwspi[spi_dev->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>BL602’s 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> we’ll 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("malloc lli failed. \r\n");
|
||
return -1;
|
||
}
|
||
*pprxlli = pvPortMalloc(sizeof(DMA_LLI_Ctrl_Type) * count);
|
||
if (*pprxlli == NULL) {
|
||
blog_error("malloc lli failed.");
|
||
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 < 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> doesn’t 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>Let’s 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 don’t 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("Tx DMA src=0x%x, dest=0x%x, size=%d, si=%d, di=%d, i=%d\r\n", (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>Let’s 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 don’t 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("Rx DMA src=0x%x, dest=0x%x, size=%d, si=%d, di=%d, i=%d\r\n", (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)&(*pptxlli)[i];
|
||
(*pprxlli)[i-1].nextLLI = (uint32_t)&(*pprxlli)[i];
|
||
}
|
||
|
||
(*pptxlli)[i].nextLLI = 0;
|
||
(*pprxlli)[i].nextLLI = 0;
|
||
}
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p>Here’s 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("arg err.\r\n");
|
||
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->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->tx_dma_ch);
|
||
DMA_Channel_Disable(arg->rx_dma_ch);
|
||
bl_dma_int_clear(arg->tx_dma_ch);
|
||
bl_dma_int_clear(arg->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->mode == 0) {
|
||
SPI_Enable(arg->ssp_id, SPI_WORK_MODE_MASTER);
|
||
} else {
|
||
SPI_Enable(arg->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(&ptxlli, &prxlli, TxData, RxData, Len);
|
||
if (ret < 0) {
|
||
blog_error("init lli failed. \r\n");
|
||
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->tx_dma_ch, &txllicfg);
|
||
DMA_LLI_Init(arg->rx_dma_ch, &rxllicfg);
|
||
DMA_LLI_Update(arg->tx_dma_ch, (uint32_t) ptxlli);
|
||
DMA_LLI_Update(arg->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->tx_dma_ch);
|
||
DMA_Channel_Enable(arg->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
|
||
// "Wait Forever" to 100 milliseconds.
|
||
// Then check the Interrupt Counters and Error Codes.
|
||
uxBits = xEventGroupWaitBits( // Wait for...
|
||
arg->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 & EVT_GROUP_SPI_DMA_TR) == EVT_GROUP_SPI_DMA_TR) {
|
||
blog_info("recv all event group.\r\n");
|
||
}</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>, we’re waiting for the SPI DMA Transmit AND Receive Requests to complete.</p>
|
||
<p>Note that we’re <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 that’s 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 that’s 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->hwspi[0].tx_dma_ch);
|
||
|
||
if (g_hal_buf->hwspi[0].spi_dma_event_group != NULL) {
|
||
xResult = xEventGroupSetBitsFromISR(
|
||
g_hal_buf->hwspi[0].spi_dma_event_group,
|
||
EVT_GROUP_SPI_DMA_TX,
|
||
&xHigherPriorityTaskWoken);
|
||
}
|
||
|
||
if(xResult != pdFAIL) {
|
||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||
}
|
||
} else {
|
||
blog_error("bl_spi0_dma_int_handler_tx no clear isr.\r\n");
|
||
}
|
||
|
||
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 that’s 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 that’s 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->hwspi[0].rx_dma_ch);
|
||
|
||
if (g_hal_buf->hwspi[0].spi_dma_event_group != NULL) {
|
||
xResult = xEventGroupSetBitsFromISR(
|
||
g_hal_buf->hwspi[0].spi_dma_event_group,
|
||
EVT_GROUP_SPI_DMA_RX,
|
||
&xHigherPriorityTaskWoken);
|
||
}
|
||
|
||
if(xResult != pdFAIL) {
|
||
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||
}
|
||
} else {
|
||
blog_error("bl_spi0_dma_int_handler_rx no clear isr.\r\n");
|
||
}
|
||
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> |