mirror of
https://github.com/lupyuen/lupyuen.github.io.git
synced 2025-01-13 02:08:32 +08:00
2365 lines
No EOL
141 KiB
HTML
2365 lines
No EOL
141 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>Build a Linux Driver for PineDio LoRa SX1262 USB Adapter</title>
|
||
|
||
|
||
<!-- Begin scripts/articles/*-header.html: Article Header for Custom Markdown files processed by rustdoc, like chip8.md -->
|
||
<meta property="og:title"
|
||
content="Build a Linux Driver for PineDio LoRa SX1262 USB Adapter"
|
||
data-rh="true">
|
||
<meta property="og:description"
|
||
content="How we build a LoRa SX1262 Driver for PineDio USB Adapter... And test it on Pinebook Pro"
|
||
data-rh="true">
|
||
<meta property="og:image"
|
||
content="https://lupyuen.github.io/images/usb-title.jpg">
|
||
<meta property="og:type"
|
||
content="article" data-rh="true">
|
||
<link rel="canonical" href="https://lupyuen.org/articles/usb.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">Build a Linux Driver for PineDio LoRa SX1262 USB Adapter</h1>
|
||
<nav id="rustdoc"><ul>
|
||
<li><a href="#pinedio-lora-usb-adapter" title="PineDio LoRa USB Adapter">1 PineDio LoRa USB Adapter</a><ul></ul></li>
|
||
<li><a href="#lora-sx1262-driver-for-pinedio-usb" title="LoRa SX1262 Driver for PineDio USB">2 LoRa SX1262 Driver for PineDio USB</a><ul>
|
||
<li><a href="#lorawan-support" title="LoRaWAN Support">2.1 LoRaWAN Support</a><ul></ul></li>
|
||
<li><a href="#nimble-porting-layer" title="NimBLE Porting Layer">2.2 NimBLE Porting Layer</a><ul></ul></li></ul></li>
|
||
<li><a href="#read-sx1262-registers" title="Read SX1262 Registers">3 Read SX1262 Registers</a><ul>
|
||
<li><a href="#run-the-driver" title="Run the Driver">3.1 Run the Driver</a><ul></ul></li>
|
||
<li><a href="#source-files-for-linux" title="Source Files for Linux">3.2 Source Files for Linux</a><ul></ul></li></ul></li>
|
||
<li><a href="#lora-parameters" title="LoRa Parameters">4 LoRa Parameters</a><ul>
|
||
<li><a href="#initialise-lora-sx1262" title="Initialise LoRa SX1262">4.1 Initialise LoRa SX1262</a><ul></ul></li></ul></li>
|
||
<li><a href="#transmit-lora-message" title="Transmit LoRa Message">5 Transmit LoRa Message</a><ul>
|
||
<li><a href="#run-the-driver-1" title="Run the Driver">5.1 Run the Driver</a><ul></ul></li>
|
||
<li><a href="#spectrum-analysis-with-sdr" title="Spectrum Analysis with SDR">5.2 Spectrum Analysis with SDR</a><ul></ul></li></ul></li>
|
||
<li><a href="#receive-lora-message" title="Receive LoRa Message">6 Receive LoRa Message</a><ul>
|
||
<li><a href="#run-the-driver-2" title="Run the Driver">6.1 Run the Driver</a><ul></ul></li></ul></li>
|
||
<li><a href="#ch341-spi-interface" title="CH341 SPI Interface">7 CH341 SPI Interface</a><ul>
|
||
<li><a href="#initialise-spi" title="Initialise SPI">7.1 Initialise SPI</a><ul></ul></li>
|
||
<li><a href="#transfer-spi" title="Transfer SPI">7.2 Transfer SPI</a><ul></ul></li>
|
||
<li><a href="#long-messages-are-garbled" title="Long Messages are Garbled">7.3 Long Messages are Garbled</a><ul></ul></li>
|
||
<li><a href="#transmit-long-message" title="Transmit Long Message">7.4 Transmit Long Message</a><ul></ul></li>
|
||
<li><a href="#receive-long-message" title="Receive Long Message">7.5 Receive Long Message</a><ul></ul></li>
|
||
<li><a href="#spi-transfer-fails-with-32-bytes" title="SPI Transfer Fails with 32 Bytes">7.6 SPI Transfer Fails with 32 Bytes</a><ul></ul></li>
|
||
<li><a href="#fix-long-messages" title="Fix Long Messages">7.7 Fix Long Messages</a><ul></ul></li></ul></li>
|
||
<li><a href="#ch341-gpio-interface" title="CH341 GPIO Interface">8 CH341 GPIO Interface</a><ul></ul></li>
|
||
<li><a href="#multithreading-with-nimble-porting-layer" title="Multithreading with NimBLE Porting Layer">9 Multithreading with NimBLE Porting Layer</a><ul></ul></li>
|
||
<li><a href="#whats-next" title="What’s Next">10 What’s Next</a><ul></ul></li>
|
||
<li><a href="#notes" title="Notes">11 Notes</a><ul></ul></li>
|
||
<li><a href="#appendix-install-ch341-spi-driver" title="Appendix: Install CH341 SPI Driver">12 Appendix: Install CH341 SPI Driver</a><ul>
|
||
<li><a href="#when-rebooting" title="When Rebooting">12.1 When Rebooting</a><ul></ul></li></ul></li>
|
||
<li><a href="#appendix-build-pinedio-usb-driver" title="Appendix: Build PineDio USB Driver">13 Appendix: Build PineDio USB Driver</a><ul>
|
||
<li><a href="#pinedio-usb-operations" title="PineDio USB Operations">13.1 PineDio USB Operations</a><ul></ul></li></ul></li>
|
||
<li><a href="#appendix-radio-functions" title="Appendix: Radio Functions">14 Appendix: Radio Functions</a><ul>
|
||
<li><a href="#radioinit-initialise-lora-module" title="RadioInit: Initialise LoRa Module">14.1 RadioInit: Initialise LoRa Module</a><ul></ul></li>
|
||
<li><a href="#radiosetchannel-set-lora-frequency" title="RadioSetChannel: Set LoRa Frequency">14.2 RadioSetChannel: Set LoRa Frequency</a><ul></ul></li>
|
||
<li><a href="#radiosettxconfig-set-transmit-configuration" title="RadioSetTxConfig: Set Transmit Configuration">14.3 RadioSetTxConfig: Set Transmit Configuration</a><ul></ul></li>
|
||
<li><a href="#radiosetrxconfig-set-receive-configuration" title="RadioSetRxConfig: Set Receive Configuration">14.4 RadioSetRxConfig: Set Receive Configuration</a><ul></ul></li>
|
||
<li><a href="#radiosend-transmit-message" title="RadioSend: Transmit Message">14.5 RadioSend: Transmit Message</a><ul></ul></li>
|
||
<li><a href="#radiorx-receive-message" title="RadioRx: Receive Message">14.6 RadioRx: Receive Message</a><ul></ul></li>
|
||
<li><a href="#radioirqprocess-process-transmit-and-receive-interrupts" title="RadioIrqProcess: Process Transmit and Receive Interrupts">14.7 RadioIrqProcess: Process Transmit and Receive Interrupts</a><ul>
|
||
<li><a href="#transmit-done" title="Transmit Done">14.7.1 Transmit Done</a><ul></ul></li>
|
||
<li><a href="#receive-done" title="Receive Done">14.7.2 Receive Done</a><ul></ul></li>
|
||
<li><a href="#cad-done" title="CAD Done">14.7.3 CAD Done</a><ul></ul></li>
|
||
<li><a href="#transmit--receive-timeout" title="Transmit / Receive Timeout">14.7.4 Transmit / Receive Timeout</a><ul></ul></li>
|
||
<li><a href="#preamble-detected" title="Preamble Detected">14.7.5 Preamble Detected</a><ul></ul></li>
|
||
<li><a href="#sync-word-valid" title="Sync Word Valid">14.7.6 Sync Word Valid</a><ul></ul></li>
|
||
<li><a href="#header-valid" title="Header Valid">14.7.7 Header Valid</a><ul></ul></li>
|
||
<li><a href="#header-error" title="Header Error">14.7.8 Header Error</a><ul></ul></li>
|
||
<li><a href="#radioondioirq" title="RadioOnDioIrq">14.7.9 RadioOnDioIrq</a><ul></ul></li></ul></li>
|
||
<li><a href="#radiosleep-switch-to-sleep-mode" title="RadioSleep: Switch to Sleep Mode">14.8 RadioSleep: Switch to Sleep Mode</a><ul></ul></li></ul></li></ul></nav><p>📝 <em>28 Oct 2021</em></p>
|
||
<p><a href="https://codeberg.org/JF002/loramac-node"><strong>UPDATE:</strong> This PineDio USB driver is incomplete. Please use <strong>JF002/loramac-node</strong> instead</a></p>
|
||
<p><em>What if our Laptop Computer could talk to other devices…</em></p>
|
||
<p><em>Over a Long Range, Low Bandwidth wireless network like LoRa?</em></p>
|
||
<p><a href="https://lora-developers.semtech.com/documentation/tech-papers-and-guides/lora-and-lorawan/">(Up to 5 km or 3 miles in urban areas… 15 km or 10 miles in rural areas!)</a></p>
|
||
<p>Yep that’s possible today… With <a href="https://wiki.pine64.org/wiki/Pinebook_Pro"><strong>Pinebook Pro</strong></a> and the <a href="https://wiki.pine64.org/wiki/Pinedio#USB_adapter"><strong>PineDio LoRa SX1262 USB Adapter</strong></a>! (Pic below)</p>
|
||
<p>This article explains how we built the <strong>LoRa SX1262 Driver</strong> for PineDio USB Adapter and tested it on Pinebook Pro (Manjaro Linux Arm64)…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/lupyuen/lora-sx1262"><strong>github.com/lupyuen/lora-sx1262</strong></a></li>
|
||
</ul>
|
||
<p>Our LoRa SX1262 Driver is <strong>still incomplete</strong> (it’s not a Kernel Driver yet), but the driver <strong>talks OK to other LoRa Devices</strong>. (With some limitations)</p>
|
||
<p>Read on to learn more…</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-title.jpg" alt="PineDio LoRa SX1262 USB Adapter" /></p>
|
||
<h1 id="pinedio-lora-usb-adapter"><a class="doc-anchor" href="#pinedio-lora-usb-adapter">§</a>1 PineDio LoRa USB Adapter</h1>
|
||
<p>PineDio LoRa USB Adapter looks like a simple dongle…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Take a <a href="http://www.wch-ic.com/products/CH341.html"><strong>CH341 USB-to-Serial Interface Module</strong></a></p>
|
||
<p>(Top half of pic below)</p>
|
||
</li>
|
||
<li>
|
||
<p>Connect it to a <a href="https://www.semtech.com/products/wireless-rf/lora-core/sx1262"><strong>Semtech SX1262 LoRa Module</strong></a> over SPI</p>
|
||
<p>(Bottom half of pic below)</p>
|
||
</li>
|
||
</ol>
|
||
<p>And we get the PineDio LoRa USB Adapter!</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-schematic.jpg" alt="Schematic for PineDio LoRa SX1262 USB Adapter" /></p>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#USB_LoRa_adapter">(Source)</a></p>
|
||
<p><em>So CH341 exposes the SPI Interface for SX1262 over USB?</em></p>
|
||
<p>Yep Pinebook Pro shall <strong>control SX1262 over SPI</strong>, bridged by CH341.</p>
|
||
<p>Which means that we need to install a <strong>CH341 SPI Driver</strong> on Pinebook Pro.</p>
|
||
<p>(More about this in a while)</p>
|
||
<p><em>What about other pins on SX1262: DIO1, BUSY and NRESET?</em></p>
|
||
<p><strong>DIO1</strong> is used by SX1262 to signal that a LoRa Packet has been received.</p>
|
||
<p><strong>BUSY</strong> is read by our computer to check if SX1262 is busy.</p>
|
||
<p><strong>NRESET</strong> is toggled by our computer to reset the SX1262 module.</p>
|
||
<p>Pinebook Pro shall control these pins via the <strong>GPIO Interface on CH341</strong>, as we’ll see in a while.</p>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#USB_adapter">(More about PineDio USB)</a></p>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#USB_LoRa_adapter">(CH341 Datasheet)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/ttn-title.jpg" alt="PineDio Stack BL604 RISC-V Board (foreground) talking to The Things Network via RAKWireless RAK7248 LoRaWAN Gateway (background)" /></p>
|
||
<h1 id="lora-sx1262-driver-for-pinedio-usb"><a class="doc-anchor" href="#lora-sx1262-driver-for-pinedio-usb">§</a>2 LoRa SX1262 Driver for PineDio USB</h1>
|
||
<p><em>Where did the PineDio USB LoRa Driver come from?</em></p>
|
||
<p>Believe it or not… The PineDio USB LoRa Driver is the exact same driver running on <strong>PineCone BL602</strong> and <strong>PineDio Stack BL604</strong>! (Pic above)</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan"><strong>“PineCone BL602 Talks LoRaWAN”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan2"><strong>“LoRaWAN on PineDio Stack BL604 RISC-V Board”</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>But modified to talk to <strong>CH341 SPI for PineDio USB</strong>.</p>
|
||
<p>(And compiled for Arm64 instead of RISC-V 32-bit)</p>
|
||
<p>The BL602 / BL604 LoRa Driver was ported from <strong>Semtech’s Reference Implementation</strong> of SX1262 Driver…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/Lora-net/LoRaMac-node/tree/master/src/radio/sx126x"><strong>LoRaMac-node/radio/sx126x</strong></a></li>
|
||
</ul>
|
||
<p><img src="https://lupyuen.github.io/images/lorawan2-ttn3.png" alt="The Things Network in Singapore" /></p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan2#seeking-volunteers">(Source)</a></p>
|
||
<h2 id="lorawan-support"><a class="doc-anchor" href="#lorawan-support">§</a>2.1 LoRaWAN Support</h2>
|
||
<p><em>There are many LoRa Drivers out there, why did we port Semtech’s Reference Driver?</em></p>
|
||
<p>That’s because Semtech’s Reference Driver <strong>supports LoRaWAN</strong>, which adds security features to low-level LoRa.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan2#security">(Like for authentication and encryption)</a></p>
|
||
<p><em>How useful is LoRaWAN? Will we be using it?</em></p>
|
||
<p>Someday we might connect PineDio USB to a <strong>LoRaWAN Network</strong> like…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/ttn"><strong>The Things Network</strong></a>: Free-to-use public global LoRaWAN Network for IoT devices. (Pic above)</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://www.helium.com/lorawan"><strong>Helium</strong></a>: Commercial global LoRaWAN Network for IoT devices.</p>
|
||
</li>
|
||
</ul>
|
||
<p>Thus it’s good to build a LoRa Driver for PineDio USB that will support LoRaWAN in future.</p>
|
||
<p><a href="https://github.com/Lora-net/sx126x_driver">(I tried porting this new driver by Semtech… But gave up when I discovered it doesn’t support LoRaWAN)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan2#lorawan-alternatives">(Seeking security on LoRa without LoRaWAN? Check out the LoRaWAN alternatives)</a></p>
|
||
<h2 id="nimble-porting-layer"><a class="doc-anchor" href="#nimble-porting-layer">§</a>2.2 NimBLE Porting Layer</h2>
|
||
<p><em>Do we call any open source libraries in our PineDio USB Driver?</em></p>
|
||
<p>Yes we call <strong>NimBLE Porting Layer</strong>, the open source library for Multithreading Functions…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/lora2#multitask-with-nimble-porting-layer"><strong>Multitask with NimBLE Porting Layer</strong></a></li>
|
||
</ul>
|
||
<p>To transmit and receive LoRa Messages we need <strong>Timers and Background Threads</strong>. Which are provided by NimBLE Porting Layer.</p>
|
||
<p><em>Have we used NimBLE Porting Layer before?</em></p>
|
||
<p>Yep we used NimBLE Porting Layer in the <strong>LoRa SX1262 and SX1276 Drivers</strong> for BL602…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/lora2"><strong>“PineCone BL602 RISC-V Board Receives LoRa Packets”</strong></a></li>
|
||
</ul>
|
||
<p>So we’re really fortunate that NimBLE Porting Layer complies on Arm64 Linux as well.</p>
|
||
<p><a href="https://lupyuen.github.io/pinetime-rust-mynewt/articles/dfu#nimble-stack-for-bluetooth-le-on-pinetime">(It’s part of PineTime InfiniTime too!)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-pinedio3.jpg" alt="Pinebook Pro with PineDio USB Adapter" /></p>
|
||
<h1 id="read-sx1262-registers"><a class="doc-anchor" href="#read-sx1262-registers">§</a>3 Read SX1262 Registers</h1>
|
||
<p><em>What’s the simplest way to test our USB PineDio Driver?</em></p>
|
||
<p>To test whether our USB PineDio Driver is working with CH341 SPI, we can read the <strong>LoRa SX1262 Registers</strong>.</p>
|
||
<p>Here’s how: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L74-L81">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Main Function
|
||
int main(void) {
|
||
// Read SX1262 registers 0x00 to 0x0F
|
||
read_registers();
|
||
return 0;
|
||
}
|
||
|
||
/// Read SX1262 registers
|
||
static void read_registers(void) {
|
||
// Init the SPI port
|
||
SX126xIoInit();
|
||
|
||
// Read and print the first 16 registers: 0 to 15
|
||
for (uint16_t addr = 0; addr < 0x10; addr++) {
|
||
// Read the register
|
||
uint8_t val = SX126xReadRegister(addr);
|
||
|
||
// Print the register value
|
||
printf("Register 0x%02x = 0x%02x\r\n", addr, val);
|
||
}
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L65-L77">(<strong>SX126xIoInit</strong> is defined here)</a></p>
|
||
<p>In our Main Function we call <strong>read_registers</strong> and <strong>SX126xReadRegister</strong> to read a bunch of SX1262 Registers. (<code>0x00</code> to <code>0x0F</code>)</p>
|
||
<p>In our PineDio USB Driver, <strong>SX126xReadRegister</strong> calls <strong>SX126xReadRegisters</strong> and <strong>sx126x_read_register</strong> to read each register: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L268-L281">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Read an SX1262 Register at the specified address
|
||
uint8_t SX126xReadRegister(uint16_t address) {
|
||
// Read one register and return the value
|
||
uint8_t data;
|
||
SX126xReadRegisters(address, &data, 1);
|
||
return data;
|
||
}
|
||
|
||
/// Read one or more SX1262 Registers at the specified address.
|
||
/// `size` is the number of registers to read.
|
||
void SX126xReadRegisters(uint16_t address, uint8_t *buffer, uint16_t size) {
|
||
// Wake up SX1262 if sleeping
|
||
SX126xCheckDeviceReady();
|
||
|
||
// Read the SX1262 registers
|
||
int rc = sx126x_read_register(NULL, address, buffer, size);
|
||
assert(rc == 0);
|
||
|
||
// Wait for SX1262 to be ready
|
||
SX126xWaitOnBusy();
|
||
}</code></pre></div>
|
||
<p>(We’ll see <strong>SX126xCheckDeviceReady</strong> and <strong>SX126xWaitOnBusy</strong> in a while)</p>
|
||
<p><strong>sx126x_read_register</strong> reads a register by sending the Read Register Command to SX1262 over SPI: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L486-L495">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Send a Read Register Command to SX1262 over SPI
|
||
/// and return the results in `buffer`. `size` is the
|
||
/// number of registers to read.
|
||
static int sx126x_read_register(const void* context, const uint16_t address, uint8_t* buffer, const uint8_t size) {
|
||
// Reserve 4 bytes for our SX1262 Command Buffer
|
||
uint8_t buf[SX126X_SIZE_READ_REGISTER] = { 0 };
|
||
|
||
// Init the SX1262 Command Buffer
|
||
buf[0] = RADIO_READ_REGISTER; // Command ID
|
||
buf[1] = (uint8_t) (address >> 8); // MSB of Register ID
|
||
buf[2] = (uint8_t) (address >> 0); // LSB of Register ID
|
||
buf[3] = 0; // Unused
|
||
|
||
// Transmit the Command Buffer over SPI
|
||
// and receive the Result Buffer
|
||
int status = sx126x_hal_read(
|
||
context, // Context (unsued)
|
||
buf, // Command Buffer
|
||
SX126X_SIZE_READ_REGISTER, // Command Buffer Size: 4 bytes
|
||
buffer, // Result Buffer
|
||
size, // Result Buffer Size
|
||
NULL // Status not required
|
||
);
|
||
return status;
|
||
}</code></pre></div>
|
||
<p>And the values of the registers are returned by SX1262 over SPI.</p>
|
||
<p>(More about <strong>sx126x_hal_read</strong> later)</p>
|
||
<h2 id="run-the-driver"><a class="doc-anchor" href="#run-the-driver">§</a>3.1 Run the Driver</h2>
|
||
<p>Follow the instructions to <strong>install the CH341 SPI Driver</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/usb#appendix-install-ch341-spi-driver"><strong>“Install CH341 SPI Driver”</strong></a></li>
|
||
</ul>
|
||
<p>Follow the instructions to <strong>download, build and run</strong> the PineDio USB Driver…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/usb#appendix-build-pinedio-usb-driver"><strong>“Appendix: Build PineDio USB Driver”</strong></a></li>
|
||
</ul>
|
||
<p>Remember to edit <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c"><strong>src/main.c</strong></a> and uncomment…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define READ_REGISTERS</code></pre></div>
|
||
<p>Build and run the PineDio USB Driver…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>## Build PineDio USB Driver
|
||
make
|
||
|
||
## Run PineDio USB Driver
|
||
sudo ./lora-sx1262</code></pre></div>
|
||
<p>And watch for these <strong>SX1262 Register Values</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>Register 0x00 = 0x00
|
||
...
|
||
Register 0x08 = 0x80
|
||
Register 0x09 = 0x00
|
||
Register 0x0a = 0x01</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#read-registers">(See the Output Log)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#read-registers-1">(See the dmesg Log)</a></p>
|
||
<p>If we see these values… Our PineDio USB Driver is talking correctly to CH341 SPI and SX1262!</p>
|
||
<p>Note that the values above will change when we <strong>transmit and receive LoRa Messages</strong>.</p>
|
||
<p>Let’s do that next.</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-registers3.png" alt="Reading SX1262 Registers on PineDio USB" /></p>
|
||
<h2 id="source-files-for-linux"><a class="doc-anchor" href="#source-files-for-linux">§</a>3.2 Source Files for Linux</h2>
|
||
<p><em>We’re seeing layers of code, like an onion? (Or Shrek)</em></p>
|
||
<p>Yep we have <strong>layers of Source Files</strong> in our SX1262 Driver…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Source Files <strong>specific to Linux</strong></p>
|
||
<p>(For PineDio USB and Pinebook Pro)</p>
|
||
</li>
|
||
<li>
|
||
<p>Source Files <strong>specific to BL602 and BL604</strong></p>
|
||
<p>(For PineCone BL602 and PineDio Stack BL604)</p>
|
||
</li>
|
||
<li>
|
||
<p>Source Files <strong>common to all platforms</strong></p>
|
||
<p>(For Linux, BL602 and BL604)</p>
|
||
</li>
|
||
</ol>
|
||
<p>The Source Files <strong>specific to Linux</strong> are…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c"><strong>src/main.c</strong></a></p>
|
||
<p>(Main Program for Linux)</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c"><strong>src/sx126x-linux.c</strong></a></p>
|
||
<p>(Linux Interface for SX1262 Driver)</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/tree/master/npl/linux/src"><strong>npl/linux/src</strong></a></p>
|
||
<p>(NimBLE Porting Layer for Linux)</p>
|
||
</li>
|
||
</ul>
|
||
<p>All other Source Files are shared by Linux, BL602 and BL604.</p>
|
||
<p>(Except <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-board.c"><strong>sx126x-board.c</strong></a> which is the BL602 / BL604 Interface for SX1262)</p>
|
||
<h1 id="lora-parameters"><a class="doc-anchor" href="#lora-parameters">§</a>4 LoRa Parameters</h1>
|
||
<p>Before we transmit and receive LoRa Messages on PineDio USB, let’s talk about the <strong>LoRa Parameters</strong>.</p>
|
||
<p>To find out which <strong>LoRa Frequency</strong> we should use for our region…</p>
|
||
<ul>
|
||
<li><a href="https://www.thethingsnetwork.org/docs/lorawan/frequencies-by-country.html"><strong>LoRa Frequencies by Country</strong></a></li>
|
||
</ul>
|
||
<p>We set the <strong>LoRa Frequency</strong> like so: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L10-L25">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// TODO: We are using LoRa Frequency 923 MHz
|
||
/// for Singapore. Change this for your region.
|
||
#define USE_BAND_923</code></pre></div>
|
||
<p>Change <strong>USE_BAND_923</strong> to <strong>USE_BAND_433</strong>, <strong>780</strong>, <strong>868</strong> or <strong>915</strong>.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L10-L25">(See the complete list)</a></p>
|
||
<p>Below are the other <strong>LoRa Parameters</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L27-L46">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// LoRa Parameters
|
||
#define LORAPING_TX_OUTPUT_POWER 14 /* dBm */
|
||
|
||
#define LORAPING_BANDWIDTH 0 /* [0: 125 kHz, */
|
||
/* 1: 250 kHz, */
|
||
/* 2: 500 kHz, */
|
||
/* 3: Reserved] */
|
||
#define LORAPING_SPREADING_FACTOR 7 /* [SF7..SF12] */
|
||
#define LORAPING_CODINGRATE 1 /* [1: 4/5, */
|
||
/* 2: 4/6, */
|
||
/* 3: 4/7, */
|
||
/* 4: 4/8] */
|
||
#define LORAPING_PREAMBLE_LENGTH 8 /* Same for Tx and Rx */
|
||
#define LORAPING_SYMBOL_TIMEOUT 5 /* Symbols */
|
||
#define LORAPING_FIX_LENGTH_PAYLOAD_ON false
|
||
#define LORAPING_IQ_INVERSION_ON false
|
||
|
||
#define LORAPING_TX_TIMEOUT_MS 3000 /* ms */
|
||
#define LORAPING_RX_TIMEOUT_MS 10000 /* ms */
|
||
#define LORAPING_BUFFER_SIZE 64 /* LoRa message size */</code></pre></div>
|
||
<p><a href="https://www.thethingsnetwork.org/docs/lorawan/spreading-factors/">(More about LoRa Parameters)</a></p>
|
||
<p>During testing, these should <strong>match the LoRa Parameters</strong> used by the LoRa Transmitter / Receiver.</p>
|
||
<p>These are <strong>LoRa Transmitter and Receiver</strong> programs based on <a href="https://lupyuen.github.io/articles/wisblock"><strong>RAKwireless WisBlock</strong></a> (pic below) that I used for testing PineDio USB…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/wisblock-lora-transmitter/tree/pinedio"><strong>wisblock-lora-transmitter</strong></a></p>
|
||
<p><a href="https://github.com/lupyuen/wisblock-lora-transmitter/blob/pinedio/src/main.cpp#L38-L58">(LoRa Parameters for Transmitter)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/wisblock-lora-receiver"><strong>wisblock-lora-receiver</strong></a></p>
|
||
<p><a href="https://github.com/lupyuen/wisblock-lora-receiver/blob/main/src/main.cpp#L37-L56">(LoRa Parameters for Receiver)</a></p>
|
||
</li>
|
||
</ul>
|
||
<p>Thus the LoRa Parameters for PineDio USB should match the above.</p>
|
||
<p><em>Are there practical limits on the LoRa Parameters?</em></p>
|
||
<p>Yes we need to comply with the <strong>Local Regulations</strong> on the usage of <a href="https://en.wikipedia.org/wiki/ISM_radio_band"><strong>ISM Radio Bands</strong></a>: FCC, ETSI, …</p>
|
||
<ul>
|
||
<li><a href="https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/"><strong>“Regional Parameters”</strong></a></li>
|
||
</ul>
|
||
<p>(Blasting LoRa Messages non-stop is no-no!)</p>
|
||
<p>When we connect PineDio USB to <strong>The Things Network</strong>, we need to comply with their Fair Use Policy…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/ttn#fair-use-of-the-things-network"><strong>“Fair Use of The Things Network”</strong></a></li>
|
||
</ul>
|
||
<p><img src="https://lupyuen.github.io/images/wisblock-title.jpg" alt="RAKwireless WisBlock LPWAN Module mounted on WisBlock Base Board" /></p>
|
||
<h2 id="initialise-lora-sx1262"><a class="doc-anchor" href="#initialise-lora-sx1262">§</a>4.1 Initialise LoRa SX1262</h2>
|
||
<p>Our <strong>init_driver</strong> function takes the above LoRa Parameters and initialises LoRa SX1262 like so: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L149-L203">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Command to initialise the LoRa Driver.
|
||
/// Assume that create_task has been called to init the Event Queue.
|
||
static void init_driver(char *buf, int len, int argc, char **argv) {
|
||
// Set the LoRa Callback Functions
|
||
RadioEvents_t radio_events;
|
||
memset(&radio_events, 0, sizeof(radio_events)); // Must init radio_events to null, because radio_events lives on stack!
|
||
radio_events.TxDone = on_tx_done; // Packet has been transmitted
|
||
radio_events.RxDone = on_rx_done; // Packet has been received
|
||
radio_events.TxTimeout = on_tx_timeout; // Transmit Timeout
|
||
radio_events.RxTimeout = on_rx_timeout; // Receive Timeout
|
||
radio_events.RxError = on_rx_error; // Receive Error</code></pre></div>
|
||
<p>Here we set the <strong>Callback Functions</strong> that will be called when a LoRa Message has been transmitted or received, also when we encounter a transmit / receive timeout or error.</p>
|
||
<p>(We’ll see the Callback Functions in a while)</p>
|
||
<p>Next we initialise the LoRa Transceiver and set the <strong>LoRa Frequency</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Init the SPI Port and the LoRa Transceiver
|
||
Radio.Init(&radio_events);
|
||
|
||
// Set the LoRa Frequency
|
||
Radio.SetChannel(RF_FREQUENCY);</code></pre></div>
|
||
<p>We set the <strong>LoRa Transmit Parameters</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Configure the LoRa Transceiver for transmitting messages
|
||
Radio.SetTxConfig(
|
||
MODEM_LORA,
|
||
LORAPING_TX_OUTPUT_POWER,
|
||
0, // Frequency deviation: Unused with LoRa
|
||
LORAPING_BANDWIDTH,
|
||
LORAPING_SPREADING_FACTOR,
|
||
LORAPING_CODINGRATE,
|
||
LORAPING_PREAMBLE_LENGTH,
|
||
LORAPING_FIX_LENGTH_PAYLOAD_ON,
|
||
true, // CRC enabled
|
||
0, // Frequency hopping disabled
|
||
0, // Hop period: N/A
|
||
LORAPING_IQ_INVERSION_ON,
|
||
LORAPING_TX_TIMEOUT_MS
|
||
);</code></pre></div>
|
||
<p>Finally we set the <strong>LoRa Receive Parameters</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Configure the LoRa Transceiver for receiving messages
|
||
Radio.SetRxConfig(
|
||
MODEM_LORA,
|
||
LORAPING_BANDWIDTH,
|
||
LORAPING_SPREADING_FACTOR,
|
||
LORAPING_CODINGRATE,
|
||
0, // AFC bandwidth: Unused with LoRa
|
||
LORAPING_PREAMBLE_LENGTH,
|
||
LORAPING_SYMBOL_TIMEOUT,
|
||
LORAPING_FIX_LENGTH_PAYLOAD_ON,
|
||
0, // Fixed payload length: N/A
|
||
true, // CRC enabled
|
||
0, // Frequency hopping disabled
|
||
0, // Hop period: N/A
|
||
LORAPING_IQ_INVERSION_ON,
|
||
true // Continuous receive mode
|
||
);
|
||
}</code></pre></div>
|
||
<p>The <strong>Radio</strong> functions are Platform-Independent (Linux and BL602), defined in <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c">radio.c</a></p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radioinit-initialise-lora-module"><strong>RadioInit:</strong></a> Init LoRa SX1262</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radioinit-initialise-lora-module">(<strong>RadioInit</strong> is explained here)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosetchannel-set-lora-frequency"><strong>RadioSetChannel:</strong></a> Set LoRa Frequency</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosetchannel-set-lora-frequency">(<strong>RadioSetChannel</strong> is explained here)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosettxconfig-set-transmit-configuration"><strong>RadioSetTxConfig:</strong></a> Set LoRa Transmit Configuration</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosettxconfig-set-transmit-configuration">(<strong>RadioSetTxConfig</strong> is explained here)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosetrxconfig-set-receive-configuration"><strong>RadioSetRxConfig:</strong></a> Set LoRa Receive Configuration</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosetrxconfig-set-receive-configuration">(<strong>RadioSetRxConfig</strong> is explained here)</a></p>
|
||
</li>
|
||
</ul>
|
||
<p>(The <strong>Radio</strong> functions will also be called later when we implement LoRaWAN)</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-transmit2.png" alt="Transmitting a LoRa Message" /></p>
|
||
<h1 id="transmit-lora-message"><a class="doc-anchor" href="#transmit-lora-message">§</a>5 Transmit LoRa Message</h1>
|
||
<p>Now we’re ready to <strong>transmit a LoRa Message</strong>! Here’s how: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L74-L119">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Main Function
|
||
int main(void) {
|
||
// Init SX1262 driver
|
||
init_driver();
|
||
|
||
// TODO: Do we need to wait?
|
||
sleep(1);
|
||
|
||
// Send a LoRa message
|
||
send_message();
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p>We begin by calling <strong>init_driver</strong> to set the LoRa Parameters and the Callback Functions.</p>
|
||
<p>(We’ve seen <strong>init_driver</strong> in the previous section)</p>
|
||
<p>To transmit a LoRa Message, <strong>send_message</strong> calls <strong>send_once</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L205-L211">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Send a LoRa message. Assume that SX1262 driver has been initialised.
|
||
static void send_message(void) {
|
||
// Send the "PING" message
|
||
send_once(1);
|
||
}</code></pre></div>
|
||
<p><strong>send_once</strong> prepares a 64-byte LoRa Message containing the string “<code>PING</code>”: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L213-L239">demo.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// We send a "PING" message and expect a "PONG" response
|
||
const uint8_t loraping_ping_msg[] = "PING";
|
||
const uint8_t loraping_pong_msg[] = "PONG";
|
||
|
||
/// 64-byte buffer for our LoRa message
|
||
static uint8_t loraping_buffer[LORAPING_BUFFER_SIZE];
|
||
|
||
/// Send a LoRa message. If is_ping is 0, send "PONG". Otherwise send "PING".
|
||
static void send_once(int is_ping) {
|
||
// Copy the "PING" or "PONG" message
|
||
// to the transmit buffer
|
||
if (is_ping) {
|
||
memcpy(loraping_buffer, loraping_ping_msg, 4);
|
||
} else {
|
||
memcpy(loraping_buffer, loraping_pong_msg, 4);
|
||
}</code></pre></div>
|
||
<p>Then we <strong>pad the 64-byte message</strong> with values 0, 1, 2, …</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Fill up the remaining space in the
|
||
// transmit buffer (64 bytes) with values
|
||
// 0, 1, 2, ...
|
||
for (int i = 4; i < sizeof loraping_buffer; i++) {
|
||
loraping_buffer[i] = i - 4;
|
||
}</code></pre></div>
|
||
<p>And we <strong>transmit the LoRa Message</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // We compute the message length, up to max 29 bytes.
|
||
// CAUTION: Anything more will cause message corruption!
|
||
#define MAX_MESSAGE_SIZE 29
|
||
uint8_t size = sizeof loraping_buffer > MAX_MESSAGE_SIZE
|
||
? MAX_MESSAGE_SIZE
|
||
: sizeof loraping_buffer;
|
||
|
||
// We send the transmit buffer, limited to 29 bytes.
|
||
// CAUTION: Anything more will cause message corruption!
|
||
Radio.Send(loraping_buffer, size);
|
||
|
||
// TODO: Previously we send 64 bytes, which gets garbled consistently.
|
||
// Does CH341 limit SPI transfers to 31 bytes?
|
||
// (Including 2 bytes for SX1262 SPI command header)
|
||
// Radio.Send(loraping_buffer, sizeof loraping_buffer);
|
||
}</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosend-transmit-message">(<strong>RadioSend</strong> is explained here)</a></p>
|
||
<p>Our PineDio USB Driver has an issue with <strong>CH341 SPI Transfers</strong>…</p>
|
||
<p><strong>Transmitting a LoRa Message on PineDio USB longer than 29 bytes will cause message corruption!</strong></p>
|
||
<p>Thus we limit the Transmit LoRa Message Size to <strong>29 bytes</strong>.</p>
|
||
<p>(There’s a way to fix this… More about CH341 later)</p>
|
||
<p>When the LoRa Message has been transmitted, the LoRa Driver calls our Callback Function <strong>on_tx_done</strong> defined in <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L253-L266">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Callback Function that is called when our LoRa message has been transmitted
|
||
static void on_tx_done(void) {
|
||
// Log the success status
|
||
loraping_stats.tx_success++;
|
||
|
||
// Switch the LoRa Transceiver to
|
||
// low power, sleep mode
|
||
Radio.Sleep();
|
||
}</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosleep-switch-to-sleep-mode">(<strong>RadioSleep</strong> is explained here)</a></p>
|
||
<p>Here we log the number of packets transmitted, and put LoRa SX1262 into <strong>low power, sleep mode</strong>.</p>
|
||
<p>Note: <strong>on_tx_done</strong> won’t actually be called in our current driver, because we haven’t implemented Multithreading. (More about this later)</p>
|
||
<p>To handle Transmit Timeout Errors, we define the Callback Function <strong>on_tx_timeout</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L300-L312">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Callback Function that is called when our LoRa message couldn't be transmitted due to timeout
|
||
static void on_tx_timeout(void) {
|
||
// Switch the LoRa Transceiver to
|
||
// low power, sleep mode
|
||
Radio.Sleep();
|
||
|
||
// Log the timeout
|
||
loraping_stats.tx_timeout++;
|
||
}</code></pre></div><h2 id="run-the-driver-1"><a class="doc-anchor" href="#run-the-driver-1">§</a>5.1 Run the Driver</h2>
|
||
<p>Follow the instructions to <strong>install the CH341 SPI Driver</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/usb#appendix-install-ch341-spi-driver"><strong>“Install CH341 SPI Driver”</strong></a></li>
|
||
</ul>
|
||
<p>Follow the instructions to <strong>download, build and run</strong> the PineDio USB Driver…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/usb#appendix-build-pinedio-usb-driver"><strong>“Appendix: Build PineDio USB Driver”</strong></a></li>
|
||
</ul>
|
||
<p>Remember to edit <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c"><strong>src/main.c</strong></a> and uncomment…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define SEND_MESSAGE</code></pre></div>
|
||
<p>Also edit <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L10-L46"><strong>src/main.c</strong></a> and set the <strong>LoRa Parameters</strong>. (As explained earlier)</p>
|
||
<p>Build and run the PineDio USB Driver…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>## Build PineDio USB Driver
|
||
make
|
||
|
||
## Run PineDio USB Driver
|
||
sudo ./lora-sx1262</code></pre></div>
|
||
<p>We should see <strong>PineDio USB transmitting</strong> our 29-byte LoRa Message…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>send_message
|
||
RadioSend: size=29
|
||
50 49 4e 47 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 </code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L213-L239">(“<code>PING</code>” followed by 0, 1, 2, …)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#send-message">(See the Output Log)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#send-message-1">(See the dmesg Log)</a></p>
|
||
<p>On <a href="https://github.com/lupyuen/wisblock-lora-receiver"><strong>RAKwireless WisBlock</strong></a> we should see the same 29-byte LoRa Message received…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>LoRaP2P Rx Test
|
||
Starting Radio.Rx
|
||
OnRxDone: Timestamp=18, RssiValue=-28 dBm, SnrValue=13,
|
||
Data=50 49 4E 47 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 </code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#wisblock-receiver-log">(See the WisBlock Log)</a></p>
|
||
<p>PineDio USB has successfully transmitted a 29-byte LoRa Message to RAKwireless WisBlock!</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-wisblock5.jpg" alt="RAKwireless WisBlock receives 29-byte LoRa Message from RAKwireless WisBlock" /></p>
|
||
<h2 id="spectrum-analysis-with-sdr"><a class="doc-anchor" href="#spectrum-analysis-with-sdr">§</a>5.2 Spectrum Analysis with SDR</h2>
|
||
<p><em>What if nothing appears in our LoRa Receiver?</em></p>
|
||
<p>Use a <strong>Spectrum Analyser</strong> (like a <strong>Software Defined Radio</strong>) to sniff the airwaves and check whether our LoRa Message is transmitted…</p>
|
||
<ol>
|
||
<li>
|
||
<p>At the right <strong>Radio Frequency</strong></p>
|
||
<p>(923 MHz below)</p>
|
||
</li>
|
||
<li>
|
||
<p>With <strong>sufficient power</strong></p>
|
||
<p>(Red stripe below)</p>
|
||
</li>
|
||
</ol>
|
||
<p><img src="https://lupyuen.github.io/images/usb-chirp2.jpg" alt="LoRa Message captured with Software Defined Radio" /></p>
|
||
<p>LoRa Messages have a characteristic criss-cross shape: <strong>LoRa Chirp</strong>. (Like above)</p>
|
||
<p>More about LoRa Chirps and Software Defined Radio…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/lora#visualise-lora-with-software-defined-radio"><strong>“Visualise LoRa with Software Defined Radio”</strong></a></li>
|
||
</ul>
|
||
<p><img src="https://lupyuen.github.io/images/usb-receive5.png" alt="Receiving a LoRa Message with PineDio USB" /></p>
|
||
<h1 id="receive-lora-message"><a class="doc-anchor" href="#receive-lora-message">§</a>6 Receive LoRa Message</h1>
|
||
<p>Let’s <strong>receive a LoRa Message</strong> on PineDio USB!</p>
|
||
<p>This is how we do it: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L74-L119">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Main Function
|
||
int main(void) {
|
||
// TODO: Create a Background Thread
|
||
// to handle LoRa Events
|
||
create_task();</code></pre></div>
|
||
<p>We start by creating a Background Thread to handle LoRa Events.</p>
|
||
<p>(<strong>create_task</strong> doesn’t do anything because we haven’t implemented Multithreading. More about this later)</p>
|
||
<p>Next we set the <strong>LoRa Parameters</strong> and the <strong>Callback Functions</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Init SX1262 driver
|
||
init_driver();
|
||
|
||
// TODO: Do we need to wait?
|
||
sleep(1);</code></pre></div>
|
||
<p>(Yep the same <strong>init_driver</strong> we’ve seen earlier)</p>
|
||
<p>For the next <strong>10 seconds</strong> we poll and handle LoRa Events (like Message Received)…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Handle LoRa events for the next 10 seconds
|
||
for (int i = 0; i < 10; i++) {
|
||
// Prepare to receive a LoRa message
|
||
receive_message();
|
||
|
||
// Process the received LoRa message, if any
|
||
RadioOnDioIrq(NULL);
|
||
|
||
// Sleep for 1 second
|
||
usleep(1000 * 1000);
|
||
}
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p>We call <strong>receive_message</strong> to get SX1262 ready to receive a single LoRa Message.</p>
|
||
<p>Then we call <strong>RadioOnDioIrq</strong> to handle the Message Received Event. (If any)</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radioondioirq">(<strong>RadioOnDioIrq</strong> is explained here)</a></p>
|
||
<p><strong>receive_message</strong> is defined like so: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L241-L248">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Receive a LoRa message. Assume that SX1262 driver has been initialised.
|
||
/// Assume that create_task has been called to init the Event Queue.
|
||
static void receive_message(void) {
|
||
// Receive a LoRa message within the timeout period
|
||
Radio.Rx(LORAPING_RX_TIMEOUT_MS);
|
||
}</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiorx-receive-message">(<strong>RadioRx</strong> is explained here)</a></p>
|
||
<p>When the LoRa Driver receives a LoRa Message, it calls our Callback Function <strong>on_rx_done</strong> defined in <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L268-L298">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Callback Function that is called when a LoRa message has been received
|
||
static void on_rx_done(
|
||
uint8_t *payload, // Buffer containing received LoRa message
|
||
uint16_t size, // Size of the LoRa message
|
||
int16_t rssi, // Signal strength
|
||
int8_t snr) { // Signal To Noise ratio
|
||
|
||
// Switch the LoRa Transceiver to low power, sleep mode
|
||
Radio.Sleep();
|
||
|
||
// Log the signal strength, signal to noise ratio
|
||
loraping_rxinfo_rxed(rssi, snr);</code></pre></div>
|
||
<p><strong>on_rx_done</strong> switches the LoRa Transceiver to low power, sleep mode and logs the received packet.</p>
|
||
<p>Next we <strong>copy the received packet</strong> into a buffer…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Copy the received packet
|
||
if (size > sizeof loraping_buffer) {
|
||
size = sizeof loraping_buffer;
|
||
}
|
||
loraping_rx_size = size;
|
||
memcpy(loraping_buffer, payload, size);</code></pre></div>
|
||
<p>Finally we <strong>dump the buffer</strong> containing the received packet…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Dump the contents of the received packet
|
||
for (int i = 0; i < loraping_rx_size; i++) {
|
||
printf("%02x ", loraping_buffer[i]);
|
||
}
|
||
printf("\r\n");
|
||
}</code></pre></div>
|
||
<p><em>What happens when we don’t receive a packet in 10 seconds? (LORAPING_RX_TIMEOUT_MS)</em></p>
|
||
<p>The LoRa Driver calls our Callback Function <strong>on_rx_timeout</strong> defined in <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L314-L327">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Callback Function that is called when no LoRa messages could be received due to timeout
|
||
static void on_rx_timeout(void) {
|
||
// Switch the LoRa Transceiver to low power, sleep mode
|
||
Radio.Sleep();
|
||
|
||
// Log the timeout
|
||
loraping_stats.rx_timeout++;
|
||
}</code></pre></div>
|
||
<p>We switch the LoRa Transceiver into sleep mode and log the timeout.</p>
|
||
<p>Note: <strong>on_rx_timeout</strong> won’t actually be called in our current driver, because we haven’t implemented Multithreading. (More about this later)</p>
|
||
<p>To handle Receive Errors, we define the Callback Function <strong>on_rx_error</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L329-L341">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Callback Function that is called when we couldn't receive a LoRa message due to error
|
||
static void on_rx_error(void) {
|
||
// Log the error
|
||
loraping_stats.rx_error++;
|
||
|
||
// Switch the LoRa Transceiver to low power, sleep mode
|
||
Radio.Sleep();
|
||
}</code></pre></div><h2 id="run-the-driver-2"><a class="doc-anchor" href="#run-the-driver-2">§</a>6.1 Run the Driver</h2>
|
||
<p>Follow the instructions to <strong>install the CH341 SPI Driver</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/usb#appendix-install-ch341-spi-driver"><strong>“Install CH341 SPI Driver”</strong></a></li>
|
||
</ul>
|
||
<p>Follow the instructions to <strong>download, build and run</strong> the PineDio USB Driver…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/usb#appendix-build-pinedio-usb-driver"><strong>“Appendix: Build PineDio USB Driver”</strong></a></li>
|
||
</ul>
|
||
<p>Remember to edit <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c"><strong>src/main.c</strong></a> and uncomment…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define RECEIVE_MESSAGE</code></pre></div>
|
||
<p>Also edit <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L10-L46"><strong>src/main.c</strong></a> and set the <strong>LoRa Parameters</strong>. (As explained earlier)</p>
|
||
<p>Build and run the PineDio USB Driver…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>## Build PineDio USB Driver
|
||
make
|
||
|
||
## Run PineDio USB Driver
|
||
sudo ./lora-sx1262</code></pre></div>
|
||
<p>Switch over to <a href="https://github.com/lupyuen/wisblock-lora-transmitter/tree/pinedio"><strong>RAKwireless WisBlock</strong></a> and transmit a 28-byte LoRa Message…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>LoRap2p Tx Test
|
||
send: 48 65 6c 6c 6f 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16
|
||
OnTxDone</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/wisblock-lora-transmitter/blob/pinedio/src/main.cpp#L124-L148">(“<code>Hello</code>” followed by 0, 1, 2, …)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#wisblock-transmitter-log">(See the WisBlock Log)</a></p>
|
||
<p>On <strong>PineDio USB</strong> we should see the same 28-byte LoRa Message…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>IRQ_RX_DONE
|
||
03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 48 65 6c 6c 6f 00 01 02
|
||
IRQ_PREAMBLE_DETECTED
|
||
IRQ_HEADER_VALID
|
||
receive_message</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#receive-message">(See the Output Log)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#receive-message-1">(See the dmesg Log)</a></p>
|
||
<p>PineDio USB has successfully received a 28-byte LoRa Message from RAKwireless WisBlock!</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-wisblock6.jpg" alt="PineDio USB receives a 28-byte LoRa Message from RAKwireless WisBlock" /></p>
|
||
<p><em>Why 28 bytes?</em></p>
|
||
<p>Our PineDio USB Driver has an issue with <strong>CH341 SPI Transfers</strong>…</p>
|
||
<p><strong>Receiving a LoRa Message on PineDio USB longer than 28 bytes will cause message corruption!</strong></p>
|
||
<p>Thus we limit the Receive LoRa Message Size to <strong>28 bytes</strong>.</p>
|
||
<p>There’s a way to fix this… Coming up next!</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-schematic.jpg" alt="Schematic for PineDio LoRa SX1262 USB Adapter" /></p>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#USB_LoRa_adapter">(Source)</a></p>
|
||
<h1 id="ch341-spi-interface"><a class="doc-anchor" href="#ch341-spi-interface">§</a>7 CH341 SPI Interface</h1>
|
||
<p>Remember that PineDio USB Dongle contains a <a href="http://www.wch-ic.com/products/CH341.html"><strong>CH341 USB-to-Serial Interface Module</strong></a> that talks to LoRa SX1262 (over SPI)…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#USB_LoRa_adapter"><strong>CH341 Datasheet</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="http://www.wch.cn/downloads/CH341DS2_PDF.html"><strong>CH341 Interfaces (Chinese)</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>Pinebook Pro (Manjaro Linux Arm64) has a built-in driver for CH341… But it <strong>doesn’t support SPI</strong>.</p>
|
||
<p>Thus for our PineDio USB Driver we’re calling this <strong>CH341 SPI Driver</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://codeberg.org/JF002/spi-ch341-usb"><strong>JF002/spi-ch341-usb</strong></a></li>
|
||
</ul>
|
||
<p>We install the CH341 SPI Driver with these steps…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/usb#appendix-install-ch341-spi-driver"><strong>“Install CH341 SPI Driver”</strong></a></li>
|
||
</ul>
|
||
<p>Now let’s call the CH341 SPI Driver from our PineDio USB Driver.</p>
|
||
<p><a href="https://github.com/dimich-dmb/spi-ch341-usb.git">(Note: PineDio Wiki recommends <em>dimich-dmb/spi-ch341-usb</em>, but it didn’t transmit LoRa packets during my testing)</a></p>
|
||
<h2 id="initialise-spi"><a class="doc-anchor" href="#initialise-spi">§</a>7.1 Initialise SPI</h2>
|
||
<p>Here’s how our PineDio USB Driver calls CH341 SPI Driver to <strong>initialise the SPI Bus</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L651-L667">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// SPI Bus
|
||
static int spi = 0;
|
||
|
||
/// Init the SPI Bus. Return 0 on success.
|
||
static int init_spi(void) {
|
||
// Open the SPI Bus
|
||
spi = open("/dev/spidev1.0", O_RDWR);
|
||
assert(spi > 0);
|
||
|
||
// Set to SPI Mode 0
|
||
uint8_t mmode = SPI_MODE_0;
|
||
int rc = ioctl(spi, SPI_IOC_WR_MODE, &mmode);
|
||
assert(rc == 0);
|
||
|
||
// Set LSB/MSB Mode
|
||
uint8_t lsb = 0;
|
||
rc = ioctl(spi, SPI_IOC_WR_LSB_FIRST, &lsb);
|
||
assert(rc == 0);
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p><strong>init_spi</strong> is called by <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L65-L77"><strong>SX126xIoInit</strong></a>, which is called by <a href="https://lupyuen.github.io/articles/usb#radioinit-initialise-lora-module"><strong>RadioInit</strong></a> and <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L149-L203"><strong>init_driver</strong></a></p>
|
||
<p>(We’ve seen <strong>init_driver</strong> earlier)</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radioinit-initialise-lora-module">(<strong>RadioInit</strong> is explained here)</a></p>
|
||
<h2 id="transfer-spi"><a class="doc-anchor" href="#transfer-spi">§</a>7.2 Transfer SPI</h2>
|
||
<p>To <strong>transfer SPI Data</strong> between PineDio USB and CH341 / SX1262, we do this: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L669-L691">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Blocking call to transmit and receive buffers on SPI. Return 0 on success.
|
||
static int transfer_spi(const uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len) {
|
||
assert(spi > 0);
|
||
assert(len > 0);
|
||
assert(len <= 31); // CAUTION: CH341 SPI doesn't seem to support 32-byte SPI transfers
|
||
|
||
// Prepare SPI Transfer
|
||
struct spi_ioc_transfer spi_trans;
|
||
memset(&spi_trans, 0, sizeof(spi_trans));
|
||
spi_trans.tx_buf = (unsigned long) tx_buf; // Transmit Buffer
|
||
spi_trans.rx_buf = (unsigned long) rx_buf; // Receive Buffer
|
||
spi_trans.cs_change = true; // Set SPI Chip Select to Low
|
||
spi_trans.len = len; // How many bytes
|
||
printf("spi tx: "); for (int i = 0; i < len; i++) { printf("%02x ", tx_buf[i]); } printf("\n");
|
||
|
||
// Transfer and receive the SPI buffers
|
||
int rc = ioctl(spi, SPI_IOC_MESSAGE(1), &spi_trans);
|
||
assert(rc >= 0);
|
||
assert(rc == len);
|
||
|
||
printf("spi rx: "); for (int i = 0; i < len; i++) { printf("%02x ", rx_buf[i]); } printf("\n");
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p>(<strong>transfer_spi</strong> will be called by our PineDio USB Driver, as we’ll see later)</p>
|
||
<p><strong>transfer_spi</strong> has a strange assertion that stops large SPI transfers…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// CAUTION: CH341 SPI doesn't seem to
|
||
// support 32-byte SPI transfers
|
||
assert(len <= 31);</code></pre></div>
|
||
<p>We’ll learn why in a while.</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-wisblock4.jpg" alt="PineDio USB transmits a garbled 64-byte LoRa Message to RAKwireless WisBlock" /></p>
|
||
<h2 id="long-messages-are-garbled"><a class="doc-anchor" href="#long-messages-are-garbled">§</a>7.3 Long Messages are Garbled</h2>
|
||
<p><em>What happens when we transmit a LoRa Message longer than 29 bytes?</em></p>
|
||
<p>The pic above shows what happens when we <strong>transmit a long message</strong> (64 bytes) from PineDio USB to RAKwireless WisBlock…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Our <strong>64-byte message is garbled</strong> when received</p>
|
||
<p>(By RAKwireless WisBlock)</p>
|
||
</li>
|
||
<li>
|
||
<p>But the message is <strong>consistently garbled</strong></p>
|
||
<p>(RAKwireless WisBlock receives the same garbled message twice, not any random message)</p>
|
||
</li>
|
||
<li>
|
||
<p>Which means it’s <strong>not due to Radio Interference</strong></p>
|
||
<p>(Radio Interference would garble the messages randomly)</p>
|
||
</li>
|
||
</ol>
|
||
<p>By tweaking our PineDio USB Driver, we discover two shocking truths…</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Transmitting a LoRa Message on PineDio USB longer than 29 bytes will cause message corruption!</strong></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Receiving a LoRa Message on PineDio USB longer than 28 bytes will cause message corruption!</strong></p>
|
||
</li>
|
||
</ol>
|
||
<p>Let’s trace the code and solve this mystery.</p>
|
||
<h2 id="transmit-long-message"><a class="doc-anchor" href="#transmit-long-message">§</a>7.4 Transmit Long Message</h2>
|
||
<p>Our PineDio USB Driver calls this function to <strong>transmit a LoRa Message</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L497-L503">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>static int sx126x_write_buffer(const void* context, const uint8_t offset, const uint8_t* buffer, const uint8_t size) {
|
||
// Prepare the Write Buffer Command (2 bytes)
|
||
uint8_t buf[SX126X_SIZE_WRITE_BUFFER] = { 0 };
|
||
buf[0] = RADIO_WRITE_BUFFER; // Write Buffer Command
|
||
buf[1] = offset; // Write Buffer Offset
|
||
|
||
// Transfer the Write Buffer Command to SX1262 over SPI
|
||
return sx126x_hal_write(
|
||
context, // Context
|
||
buf, // Command Buffer
|
||
SX126X_SIZE_WRITE_BUFFER, // Command Buffer Size (2 bytes)
|
||
buffer, // Write Data Buffer
|
||
size // Write Data Buffer Size
|
||
);
|
||
}</code></pre></div>
|
||
<p>In this code we prepare a <strong>SX1262 Write Buffer Command</strong> (2 bytes) and pass the Command Buffer (plus Data Buffer) to <strong>sx126x_hal_write</strong>.</p>
|
||
<p>(Data Buffer contains the LoRa Message to be transmitted)</p>
|
||
<p>Note that <strong>Write Buffer Offset is always 0</strong>, because of <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L144-L147"><strong>SX126xSetPayload</strong></a> and <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L283-L289"><strong>SX126xWriteBuffer</strong></a>.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radiosend-transmit-message">(<strong>SX126xSetPayload</strong> and <strong>SX126xWriteBuffer</strong> are explained here)</a></p>
|
||
<p><strong>sx126x_hal_write</strong> transfers the Command Buffer and Data Buffer over SPI: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L532-L569">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/**
|
||
* Radio data transfer - write
|
||
*
|
||
* @remark Shall be implemented by the user
|
||
*
|
||
* @param [in] context Radio implementation parameters
|
||
* @param [in] command Pointer to the buffer to be transmitted
|
||
* @param [in] command_length Buffer size to be transmitted
|
||
* @param [in] data Pointer to the buffer to be transmitted
|
||
* @param [in] data_length Buffer size to be transmitted
|
||
*
|
||
* @returns Operation status
|
||
*/
|
||
static int sx126x_hal_write(
|
||
const void* context, const uint8_t* command, const uint16_t command_length,
|
||
const uint8_t* data, const uint16_t data_length ) {
|
||
printf("sx126x_hal_write: command_length=%d, data_length=%d\n", command_length, data_length);
|
||
|
||
// Total length is command + data length
|
||
uint16_t len = command_length + data_length;
|
||
assert(len > 0);
|
||
assert(len <= SPI_BUFFER_SIZE);
|
||
|
||
// Clear the SPI Transmit and Receive buffers
|
||
memset(&spi_tx_buf, 0, len);
|
||
memset(&spi_rx_buf, 0, len);
|
||
|
||
// Copy command bytes to SPI Transmit Buffer
|
||
memcpy(&spi_tx_buf, command, command_length);
|
||
|
||
// Copy data bytes to SPI Transmit Buffer
|
||
memcpy(&spi_tx_buf[command_length], data, data_length);
|
||
|
||
// Transmit and receive the SPI buffers
|
||
int rc = transfer_spi(spi_tx_buf, spi_rx_buf, len);
|
||
assert(rc == 0);
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p>We use an internal <strong>1024-byte buffer for SPI Transfers</strong>, so we’re hunky dory here: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L521-L528">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Max size of SPI transfers
|
||
#define SPI_BUFFER_SIZE 1024
|
||
|
||
/// SPI Transmit Buffer
|
||
static uint8_t spi_tx_buf[SPI_BUFFER_SIZE];
|
||
|
||
/// SPI Receive Buffer
|
||
static uint8_t spi_rx_buf[SPI_BUFFER_SIZE];</code></pre></div>
|
||
<p><strong>sx126x_hal_write</strong> calls <strong>transfer_spi</strong> to transfer the SPI Data.</p>
|
||
<p>(We’ve seen <strong>transfer_spi</strong> earlier)</p>
|
||
<p>Thus <strong>transfer_spi</strong> looks highly sus for transmitting Long LoRa Messages.</p>
|
||
<p>What about receiving Long LoRa Messages?</p>
|
||
<h2 id="receive-long-message"><a class="doc-anchor" href="#receive-long-message">§</a>7.5 Receive Long Message</h2>
|
||
<p>Our PineDio USB Driver calls this function to <strong>receive a LoRa Message</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L505-L513">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>static int sx126x_read_buffer(const void* context, const uint8_t offset, uint8_t* buffer, const uint8_t size) {
|
||
// Prepare the Read Buffer Command (3 bytes)
|
||
uint8_t buf[SX126X_SIZE_READ_BUFFER] = { 0 };
|
||
buf[0] = RADIO_READ_BUFFER; // Read Buffer Command
|
||
buf[1] = offset; // Read Buffer Offset
|
||
buf[2] = 0; // NOP
|
||
|
||
// Transfer the Read Buffer Command to SX1262 over SPI
|
||
int status = sx126x_hal_read(
|
||
context, // Context
|
||
buf, // Command Buffer
|
||
SX126X_SIZE_READ_BUFFER, // Command Buffer Size (3 bytes)
|
||
buffer, // Read Data Buffer
|
||
size, // Read Data Buffer Size
|
||
NULL // Ignore the status
|
||
);
|
||
return status;
|
||
}</code></pre></div>
|
||
<p>In this code we prepare a <strong>SX1262 Read Buffer Command</strong> (3 bytes) and pass the Command Buffer (plus Data Buffer) to <strong>sx126x_hal_read</strong>.</p>
|
||
<p>(Data Buffer will contain the received LoRa Message)</p>
|
||
<p>Note that <strong>Read Buffer Offset is always 0</strong>, because of <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L149-L160"><strong>SX126xGetPayload</strong></a> and <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L291-L297"><strong>SX126xReadBuffer</strong></a>.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#receive-done">(<strong>SX126xGetPayload</strong> and <strong>SX126xReadBuffer</strong> are explained here)</a></p>
|
||
<p><strong>sx126x_hal_read</strong> transfers the Command Buffer over SPI: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L571-L615">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/**
|
||
* Radio data transfer - read
|
||
*
|
||
* @remark Shall be implemented by the user
|
||
*
|
||
* @param [in] context Radio implementation parameters
|
||
* @param [in] command Pointer to the buffer to be transmitted
|
||
* @param [in] command_length Buffer size to be transmitted
|
||
* @param [in] data Pointer to the buffer to be received
|
||
* @param [in] data_length Buffer size to be received
|
||
* @param [out] status If not null, return the second SPI byte received as status
|
||
*
|
||
* @returns Operation status
|
||
*/
|
||
static int sx126x_hal_read(
|
||
const void* context, const uint8_t* command, const uint16_t command_length,
|
||
uint8_t* data, const uint16_t data_length, uint8_t *status ) {
|
||
printf("sx126x_hal_read: command_length=%d, data_length=%d\n", command_length, data_length);
|
||
|
||
// Total length is command + data length
|
||
uint16_t len = command_length + data_length;
|
||
assert(len > 0);
|
||
assert(len <= SPI_BUFFER_SIZE);
|
||
|
||
// Clear the SPI Transmit and Receive buffers
|
||
memset(&spi_tx_buf, 0, len);
|
||
memset(&spi_rx_buf, 0, len);
|
||
|
||
// Copy command bytes to SPI Transmit Buffer
|
||
memcpy(&spi_tx_buf, command, command_length);
|
||
|
||
// Transmit and receive the SPI buffers
|
||
int rc = transfer_spi(spi_tx_buf, spi_rx_buf, len);
|
||
assert(rc == 0);
|
||
|
||
// Copy SPI Receive buffer to data buffer
|
||
memcpy(data, &spi_rx_buf[command_length], data_length);
|
||
|
||
// Return the second SPI byte received as status
|
||
if (status != NULL) {
|
||
assert(len >= 2);
|
||
*status = spi_rx_buf[1];
|
||
}
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p>And returns the Data Buffer that has been read over SPI.</p>
|
||
<p><strong>sx126x_hal_read</strong> also calls <strong>transfer_spi</strong> to transfer the SPI Data.</p>
|
||
<p>Now <strong>transfer_spi</strong> is doubly sus… The same function is called to transmit AND receive Long LoRa Messages!</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-spi8.png" alt="CH341 SPI Driver fails when transferring 32 bytes" /></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L669-L691">(Source)</a></p>
|
||
<h2 id="spi-transfer-fails-with-32-bytes"><a class="doc-anchor" href="#spi-transfer-fails-with-32-bytes">§</a>7.6 SPI Transfer Fails with 32 Bytes</h2>
|
||
<p><em>Does <strong>transfer_spi</strong> impose a limit on the size of SPI Transfers?</em></p>
|
||
<p>With some tweaking, we discover that <strong>transfer_spi garbles the data when transferring 32 bytes or more</strong>!</p>
|
||
<p>This seems to be a limitation of the <strong>CH341 SPI Driver</strong>.</p>
|
||
<p><a href="https://codeberg.org/JF002/spi-ch341-usb/src/branch/remove-calls-to-spi_busnum_to_master/spi-ch341-usb.c#L68">(Due to <strong>CH341_USB_MAX_BULK_SIZE</strong> maybe?)</a></p>
|
||
<p>Hence we limit all SPI Transfers to <strong>31 bytes</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L669-L691">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Blocking call to transmit and receive buffers on SPI. Return 0 on success.
|
||
static int transfer_spi(const uint8_t *tx_buf, uint8_t *rx_buf, uint16_t len) {
|
||
// CAUTION: CH341 SPI doesn't seem to
|
||
// support 32-byte SPI transfers
|
||
assert(len <= 31);</code></pre></div>
|
||
<p><em>Why 29 bytes for the max transmit size? And 28 bytes for the max receive size?</em></p>
|
||
<p>That’s because…</p>
|
||
<ul>
|
||
<li>
|
||
<p>SX1262 Write Buffer Command (for transmit) occupies <strong>2 SPI bytes</strong></p>
|
||
</li>
|
||
<li>
|
||
<p>SX1262 Read Buffer Command (for receive) occupies <strong>3 SPI bytes</strong></p>
|
||
</li>
|
||
</ul>
|
||
<p>But wait! We might have a fix for Long LoRa Messages…</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-buffer.png" alt="SX1262 Commands for WriteBuffer and ReadBuffer" /></p>
|
||
<p><a href="https://www.semtech.com/products/wireless-rf/lora-core/sx1262">(From Semtech SX1262 Datasheet)</a></p>
|
||
<h2 id="fix-long-messages"><a class="doc-anchor" href="#fix-long-messages">§</a>7.7 Fix Long Messages</h2>
|
||
<p><em>Is there a way to fix Long LoRa Messages on PineDio USB?</em></p>
|
||
<p>Let’s look back at our code in <a href="https://lupyuen.github.io/articles/usb#transmit-long-message"><strong>sx126x_write_buffer</strong></a>.</p>
|
||
<p>To transmit a LoRa Message, we send the <strong>WriteBuffer Command</strong> to SX1262 over SPI…</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>WriteBuffer Command:</strong> <code>0x0E</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>WriteBuffer Offset:</strong> <code>0x00</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>WriteBuffer Data:</strong> Transfer 29 bytes (max)</p>
|
||
</li>
|
||
</ol>
|
||
<p>This copies the <strong>entire LoRa Message</strong> into the SX1262 Transmit Buffer as a <strong>single (huge) chunk</strong>.</p>
|
||
<p>If we try to transmit a LoRa Message that’s <strong>longer than 29 bytes</strong>, the SPI Transfer fails.</p>
|
||
<p>This appears in the <a href="https://github.com/lupyuen/lora-sx1262#send-message"><strong>Output Log</strong></a> as…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>sx126x_hal_write:
|
||
command_length=2,
|
||
data_length=29
|
||
spi tx:
|
||
0e 00
|
||
50 49 4e 47 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18
|
||
spi rx:
|
||
a2 a2
|
||
a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 a2 </code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L213-L239">(“<code>50 49 4e 47</code>” is “<code>PING</code>” followed by 0, 1, 2, …)</a></p>
|
||
<p><em>Can we transfer in smaller chunks instead?</em></p>
|
||
<p>Yes! According to the <a href="https://www.semtech.com/products/wireless-rf/lora-core/sx1262"><strong>SX1262 Datasheet</strong></a> (pic above), we can copy the LoRa Message in <strong>smaller chunks</strong> (29 bytes), by changing the <strong>WriteBuffer Offset</strong>…</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>WriteBuffer Command:</strong> <code>0x0E</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>WriteBuffer Offset:</strong> <code>0x00</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>WriteBuffer Data:</strong> Transfer first 29 bytes</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>WriteBuffer Command:</strong> <code>0x0E</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>WriteBuffer Offset:</strong> <code>0x1D</code> (29 decimal)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>WriteBuffer Data:</strong> Transfer next 29 bytes</p>
|
||
</li>
|
||
</ol>
|
||
<p>We need to mod the code in <a href="https://lupyuen.github.io/articles/usb#transmit-long-message"><strong>sx126x_write_buffer</strong></a> to copy the LoRa Message in <strong>29-byte chunks</strong>.</p>
|
||
<p><em>Awesome! Will this work for receiving Long LoRa Messages?</em></p>
|
||
<p>Yep! To receive a LoRa Message, <a href="https://lupyuen.github.io/articles/usb#receive-long-message"><strong>sx126x_read_buffer</strong></a> sends this <strong>ReadBuffer Command</strong> to SX1262 over SPI…</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>ReadBuffer Command:</strong> <code>0x1E</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer Offset:</strong> <code>0x00</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer NOP:</strong> <code>0x00</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer Data:</strong> Transfer 28 bytes (max)</p>
|
||
</li>
|
||
</ol>
|
||
<p>Which appears in the <a href="https://github.com/lupyuen/lora-sx1262#receive-message"><strong>Output Log</strong></a> as…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>sx126x_hal_read:
|
||
command_length=3,
|
||
data_length=28
|
||
spi tx:
|
||
1e 00 00
|
||
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||
spi rx:
|
||
d2 d2 d2
|
||
48 65 6c 6c 6f 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 </code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/wisblock-lora-transmitter/blob/pinedio/src/main.cpp#L124-L148">(“<code>48 65 6c 6c 6f</code>” is “<code>Hello</code>” followed by 0, 1, 2, …)</a></p>
|
||
<p>Instead of reading the <strong>entire LoRa Message</strong> (from SX1262 Receive Buffer) in a single chunk, we should read it in <strong>28-byte chunks</strong>…</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>ReadBuffer Command:</strong> <code>0x1E</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer Offset:</strong> <code>0x00</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer NOP:</strong> <code>0x00</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer Data:</strong> Transfer first 28 bytes</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer Command:</strong> <code>0x1E</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer Offset:</strong> <code>0x1C</code> (28 decimal)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer NOP:</strong> <code>0x00</code></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>ReadBuffer Data:</strong> Transfer next 28 bytes</p>
|
||
</li>
|
||
</ol>
|
||
<p>We need to fix <a href="https://lupyuen.github.io/articles/usb#receive-long-message"><strong>sx126x_read_buffer</strong></a> to read the LoRa Message in <strong>28-byte chunks</strong>.</p>
|
||
<p><em>Is this fix for Long LoRa Messages really necessary?</em></p>
|
||
<p>Maybe not!</p>
|
||
<p>Remember we need to comply with the <strong>Local Regulations</strong> on the usage of <a href="https://en.wikipedia.org/wiki/ISM_radio_band"><strong>ISM Radio Bands</strong></a>: FCC, ETSI, …</p>
|
||
<ul>
|
||
<li><a href="https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/"><strong>“Regional Parameters”</strong></a></li>
|
||
</ul>
|
||
<p>(Blasting Long LoRa Messages non-stop is no-no!)</p>
|
||
<p>When we connect PineDio USB to <strong>The Things Network</strong>, we need to comply with their Fair Use Policy…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/ttn#fair-use-of-the-things-network"><strong>“Fair Use of The Things Network”</strong></a></li>
|
||
</ul>
|
||
<p>With <strong>CBOR Encoding</strong>, we can compress simple LoRa Messages (Sensor Data) into 12 bytes roughly. <a href="https://lupyuen.github.io/articles/cbor">(See this)</a></p>
|
||
<p>Thus <strong>28 bytes might be sufficient</strong> for many LoRa Applications.</p>
|
||
<p>(Long LoRa Messages are more prone to Radio Interference and Collisions as well)</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/issues">(But lemme know if you would like me to fix this!)</a></p>
|
||
<h1 id="ch341-gpio-interface"><a class="doc-anchor" href="#ch341-gpio-interface">§</a>8 CH341 GPIO Interface</h1>
|
||
<p><em>Besides SPI, what Interfaces do we need to control LoRa SX1262?</em></p>
|
||
<p>PineDio USB needs a <strong>GPIO Interface</strong> to control these <strong>SX1262 Pins</strong>…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>DIO1</strong>: Used by SX1262 to signal that a LoRa Packet has been transmitted or received</p>
|
||
<p>(<strong>DIO1</strong> shifts from Low to High when that happens)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>BUSY</strong>: Read by our driver to check if SX1262 is busy</p>
|
||
<p>(<strong>BUSY</strong> is High when SX1262 is busy)</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>NRESET</strong>: Toggled by our driver to reset the SX1262 module</p>
|
||
</li>
|
||
</ul>
|
||
<p>We may call the GPIO Interface that’s provided by the <strong>CH341 SPI Driver</strong>…</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#Driver_development"><strong>“Driver Development (GPIO)”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://codeberg.org/JF002/spi-ch341-usb#using-gpios"><strong>“Using GPIOs (CH341)”</strong></a></p>
|
||
</li>
|
||
</ul>
|
||
<p>But the current PineDio USB Driver <strong>doesn’t use GPIO yet</strong>.</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-sleep3.png" alt="Controlling SX1262 without GPIO" /></p>
|
||
<p><em>Huh? SX1262 works without GPIO control?</em></p>
|
||
<p>We found some sneaky workarounds to <strong>control LoRa SX1262 without GPIO</strong>…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>DIO1</strong>: Because we don’t support GPIO Interrupts (yet), we <strong>poll the SX1262 Status every second</strong> to check if a LoRa Packet has been received.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L102-L112">(See this)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>BUSY</strong>: Instead of reading this pin to check if SX1262 is busy, we <strong>sleep 10 milliseconds</strong>.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L171-L182">(See this)</a></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>NRESET</strong>: To reset the SX1262 module, we <strong>manually unplug PineDio USB</strong> and plug it back in.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L149-L169">(See this)</a></p>
|
||
</li>
|
||
</ul>
|
||
<p>These sneaky hacks will need to be fixed by calling the GPIO Interface.</p>
|
||
<p><em>What needs to be fixed for GPIO?</em></p>
|
||
<p>We need to mod these functions to call the <strong>CH341 GPIO Interface</strong>…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Initialise the GPIO Pins: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L65-L77"><strong>SX126xIoInit</strong></a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan#appendix-bl602-spi-functions">(Explained here)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Register GPIO Interrupt Handler for DIO1: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L79-L91"><strong>SX126xIoIrqInit</strong></a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan#appendix-bl602-gpio-interrupts">(Explained here)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Reset SX1262 via GPIO: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L149-L169"><strong>SX126xReset</strong></a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void SX126xReset(void) {
|
||
// TODO: Set Reset pin to Low
|
||
// rc = bl_gpio_output_set(SX126X_NRESET, 1);
|
||
// assert(rc == 0);
|
||
|
||
// Wait 1 ms
|
||
DelayMs(1);
|
||
|
||
// TODO: Configure Reset pin as a GPIO Input Pin, no pullup, no pulldown
|
||
// rc = bl_gpio_enable_input(SX126X_NRESET, 0, 0);
|
||
// assert(rc == 0);
|
||
|
||
// Wait 6 ms
|
||
DelayMs(6);
|
||
}</code></pre></div></li>
|
||
<li>
|
||
<p>Check SX1262 Busy State via GPIO: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L171-L182"><strong>SX126xWaitOnBusy</strong></a></p>
|
||
<p>(<strong>SX126xWaitOnBusy</strong> is called by <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L133-L142"><strong>SX126xCheckDeviceReady</strong></a>, which wakes up SX1262 before checking if SX1262 is busy)</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void SX126xWaitOnBusy(void) {
|
||
// TODO: Fix the GPIO check for busy state.
|
||
// while( bl_gpio_input_get_value( SX126X_BUSY_PIN ) == 1 );
|
||
|
||
// Meanwhile we sleep 10 milliseconds
|
||
usleep(10 * 1000);
|
||
}</code></pre></div></li>
|
||
<li>
|
||
<p>Get DIO1 Pin State: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L337-L344"><strong>SX126xGetDio1PinState</strong></a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>uint32_t SX126xGetDio1PinState(void) {
|
||
// TODO: Read and return DIO1 Pin State
|
||
// return bl_gpio_input_get_value( SX126X_DIO1 );
|
||
|
||
// Meanwhile we always return 0
|
||
return 0;
|
||
}</code></pre></div></li>
|
||
</ol>
|
||
<p>When we have implemented <a href="https://codeberg.org/JF002/spi-ch341-usb#reacting-on-gpio-input-interrupt"><strong>GPIO Interrupts</strong></a> in our driver, we can remove the <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L102-L112"><strong>Event Polling</strong></a>. And we run a <strong>Background Thread</strong> to handle LoRa Events.</p>
|
||
<p>Here’s how we’ll do multithreading…</p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-handler.jpg" alt="Multithreading with NimBLE Porting Layer" /></p>
|
||
<h1 id="multithreading-with-nimble-porting-layer"><a class="doc-anchor" href="#multithreading-with-nimble-porting-layer">§</a>9 Multithreading with NimBLE Porting Layer</h1>
|
||
<p><em>How will we receive LoRa Messages with GPIO Interrupts?</em></p>
|
||
<p>After we have implemented <a href="https://codeberg.org/JF002/spi-ch341-usb#reacting-on-gpio-input-interrupt"><strong>GPIO Interrupts</strong></a> in our PineDio USB Driver, this is how we’ll <strong>receive LoRa Messages</strong> (see pic above)…</p>
|
||
<ol>
|
||
<li>
|
||
<p>When SX1262 receives a LoRa Message, it triggers a <strong>GPIO Interrupt</strong> on Pin DIO1</p>
|
||
</li>
|
||
<li>
|
||
<p>CH341 Driver forwards the GPIO Interrupt to our Interrupt Handler Function <a href="https://lupyuen.github.io/articles/lora2#gpio-interrupt-handler"><strong>handle_gpio_interrupt</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><strong>handle_gpio_interrupt</strong> enqueues an Event into our <strong>Event Queue</strong></p>
|
||
</li>
|
||
<li>
|
||
<p>Our <strong>Background Thread</strong> removes the Event from the Event Queue and calls <a href="https://lupyuen.github.io/articles/usb#radioondioirq"><strong>RadioOnDioIrq</strong></a> to process the received LoRa Message</p>
|
||
</li>
|
||
</ol>
|
||
<p>We handle GPIO Interrupts the same way in our <strong>LoRa SX1262 Driver for BL602</strong>…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/lorawan#appendix-bl602-gpio-interrupts"><strong>“BL602 GPIO Interrupts”</strong></a></li>
|
||
</ul>
|
||
<p><em>Why do we need a Background Thread?</em></p>
|
||
<p>This will allow our LoRa Application to <strong>run without blocking</strong> (waiting) on incoming LoRa Messages.</p>
|
||
<p>This is especially useful when we implement <strong>LoRaWAN on PineDio USB</strong>, because LoRaWAN needs to handle <strong>asynchronous messages</strong> in the background.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan#join-network-request">(Like when we join a LoRaWAN Network)</a></p>
|
||
<p><em>How will we implement the Background Thread and Event Queue?</em></p>
|
||
<p>We’ll call <strong>NimBLE Porting Layer</strong>, the open source library for Multithreading Functions…</p>
|
||
<ul>
|
||
<li><a href="https://lupyuen.github.io/articles/lora2#multitask-with-nimble-porting-layer"><strong>Multitask with NimBLE Porting Layer</strong></a></li>
|
||
</ul>
|
||
<p>Which has been compiled into our PineDio USB Driver…</p>
|
||
<ul>
|
||
<li><a href="https://github.com/lupyuen/lora-sx1262/tree/master/npl/linux/src"><strong>npl/linux/src</strong></a></li>
|
||
</ul>
|
||
<p>The code below shall be updated to <strong>start the Background Thread</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L343-L413">main.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// TODO: Create a Background Thread to handle LoRa Events
|
||
static void create_task(void) {
|
||
// Init the Event Queue
|
||
ble_npl_eventq_init(&event_queue);
|
||
|
||
// Init the Event
|
||
ble_npl_event_init(
|
||
&event, // Event
|
||
handle_event, // Event Handler Function
|
||
NULL // Argument to be passed to Event Handler
|
||
);
|
||
|
||
// TODO: Create a Background Thread to process the Event Queue
|
||
// nimble_port_freertos_init(task_callback);
|
||
}</code></pre></div>
|
||
<p>And we shall implement the GPIO Interrupt Handler Function <a href="https://lupyuen.github.io/articles/lora2#gpio-interrupt-handler"><strong>handle_gpio_interrupt</strong></a> for Linux.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c#L343-L413">(We don’t need to code the Event Queue, it has been done here)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/lorawan2-pine64.jpg" alt="PineDio LoRa Family: PineDio Gateway, PinePhone Backplate and USB Adapter" /></p>
|
||
<p><em>PineDio LoRa Family: PineDio Gateway, PinePhone Backplate and USB Adapter</em></p>
|
||
<h1 id="whats-next"><a class="doc-anchor" href="#whats-next">§</a>10 What’s Next</h1>
|
||
<p>Now that we have a <strong>Basic LoRa Driver for PineDio USB</strong>, we can explore all kinds of fun possibilities…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Merge the PineDio USB Driver back into <a href="https://github.com/lupyuen/bl_iot_sdk"><strong>BL602 IoT SDK</strong></a></p>
|
||
<p>(So we can maintain a single LoRa Driver for for PineDio USB and <a href="https://lupyuen.github.io/articles/lorawan2"><strong>PineDio Stack BL604</strong></a>)</p>
|
||
<p><a href="https://github.com/lupyuen/bl_iot_sdk/commit/caae2d5219700df297411641be7286ada07f7804?diff=unified"><strong>UPDATE</strong>: We have merged PineDio USB Driver into BL602 IoT SDK!</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Implement <strong>LoRaWAN on PineDio USB</strong></p>
|
||
<p>(So we can connect Pinebook Pro to <a href="https://lupyuen.github.io/articles/ttn"><strong>The Things Network</strong></a> and <a href="https://www.helium.com/lorawan"><strong>Helium</strong></a>)</p>
|
||
<p><a href="https://github.com/lupyuen/lorawan">(We’ll port the BL602 LoRaWAN Driver to Linux)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Explore <strong>LoRa Mesh Networks</strong> for PineDio USB…</p>
|
||
<p><a href="https://meshtastic.org/"><strong>Meshtastic</strong></a> (Data Mesh), <a href="https://hackaday.io/project/161491-qmesh-a-lora-based-voice-mesh-network"><strong>QMesh</strong></a> (Voice Mesh), <a href="https://mycelium-mesh.net/"><strong>Mycelium Mesh</strong></a> (Text Mesh)</p>
|
||
</li>
|
||
<li>
|
||
<p>Test PineDio USB with <strong>PineDio LoRa Gateway</strong>…</p>
|
||
<p><a href="https://lupyuen.github.io/articles/gateway"><strong>“PineDio LoRa Gateway: Testing The Prototype”</strong></a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#Pinephone_backplate"><strong>PinePhone LoRa Backplate</strong></a> (pic above) is an intriguing accessory…</p>
|
||
<p>The Backplate connects PinePhone to <strong>LoRa SX1262</strong> via <strong>ATtiny84</strong>… Which runs an <a href="https://github.com/zschroeder6212/tiny-i2c-spi"><strong>Arduino I2C-To-SPI Bridge</strong></a>!</p>
|
||
<p>(Our PineDio USB Driver might run on PinePhone if we talk to I2C instead of SPI)</p>
|
||
<p><a href="https://github.com/lupyuen/pinephone-lora">(Check the updates here)</a></p>
|
||
</li>
|
||
</ol>
|
||
<p>Lemme know what you would like to see!</p>
|
||
<p>Many Thanks to my <a href="https://lupyuen.github.io/articles/sponsor"><strong>GitHub Sponsors</strong></a> for supporting my work! This article wouldn’t have been possible without your support.</p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/sponsor">Sponsor me a coffee</a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://www.reddit.com/r/PINE64official/comments/qhiuer/build_a_linux_driver_for_pinedio_lora_sx1262_usb/">Discuss this article on Reddit</a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/articles/book">Read “The RISC-V BL602 / BL604 Book”</a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io">Check out my articles</a></p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://lupyuen.github.io/rss.xml">RSS Feed</a></p>
|
||
</li>
|
||
</ul>
|
||
<p><em>Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…</em></p>
|
||
<p><a href="https://github.com/lupyuen/lupyuen.github.io/blob/master/src/usb.md"><code>lupyuen.github.io/src/usb.md</code></a></p>
|
||
<h1 id="notes"><a class="doc-anchor" href="#notes">§</a>11 Notes</h1>
|
||
<ol>
|
||
<li>This article is the expanded version of <a href="https://twitter.com/MisterTechBlog/status/1451548895461326858">this Twitter Thread</a></li>
|
||
</ol>
|
||
<h1 id="appendix-install-ch341-spi-driver"><a class="doc-anchor" href="#appendix-install-ch341-spi-driver">§</a>12 Appendix: Install CH341 SPI Driver</h1>
|
||
<p>To install <strong>CH341 SPI Driver</strong> on <strong>Pinebook Pro Manjaro Arm64</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>## Install DKMS so that we may load Kernel Drivers dynamically
|
||
sudo pacman -Syu dkms base-devel --needed
|
||
|
||
## Install Kernel Headers for Manjaro: https://linuxconfig.org/manjaro-linux-kernel-headers-installation
|
||
uname -r
|
||
## Should show "5.14.12-1-MANJARO-ARM" or similar
|
||
sudo pacman -S linux-headers
|
||
pacman -Q | grep headers
|
||
## Should show "linux-headers 5.14.12-1" or similar
|
||
|
||
## Reboot to be safe
|
||
sudo reboot
|
||
|
||
## Install CH341 SPI Driver
|
||
git clone https://codeberg.org/JF002/spi-ch341-usb
|
||
pushd spi-ch341-usb
|
||
## TODO: Edit Makefile and change...
|
||
## KERNEL_DIR = /usr/src/linux-headers-$(KVERSION)/
|
||
## To...
|
||
## KERNEL_DIR = /lib/modules/$(KVERSION)/build
|
||
make
|
||
sudo make install
|
||
popd
|
||
|
||
## Unload the CH341 Non-SPI Driver if it has been automatically loaded
|
||
lsmod | grep ch341
|
||
sudo rmmod ch341
|
||
|
||
## Load the CH341 SPI Driver
|
||
sudo modprobe spi-ch341-usb</code></pre></div>
|
||
<p>Then do this…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Plug in PineDio USB</p>
|
||
</li>
|
||
<li>
|
||
<p>Check that the CH341 SPI Driver has been correctly loaded…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>dmesg</code></pre></div></li>
|
||
<li>
|
||
<p>We should see…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>ch341_gpio_probe:
|
||
registered GPIOs from 496 to 511</code></pre></div>
|
||
<p>The CH341 SPI Driver has been loaded successfully.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#connect-usb">(See the complete log)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>If we see…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>spi_ch341_usb:
|
||
loading out-of-tree module taints kernel</code></pre></div>
|
||
<p>It means the <strong>older CH341 Non-SPI Driver</strong> has been loaded.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#connect-usb-failed">(See the complete log)</a></p>
|
||
</li>
|
||
</ol>
|
||
<p>To remove the <strong>older CH341 Non-SPI Driver</strong>…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Unplug PineDio USB</p>
|
||
</li>
|
||
<li>
|
||
<p>Enter…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>## Unload the CH341 Non-SPI Driver
|
||
sudo rmmod ch341</code></pre></div></li>
|
||
<li>
|
||
<p>Plug in PineDio USB</p>
|
||
</li>
|
||
<li>
|
||
<p>Enter…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>dmesg</code></pre></div>
|
||
<p>And recheck the messages.</p>
|
||
</li>
|
||
</ol>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#USB_adapter">(More about CH341 SPI Driver)</a></p>
|
||
<h2 id="when-rebooting"><a class="doc-anchor" href="#when-rebooting">§</a>12.1 When Rebooting</h2>
|
||
<p>Whenever we reboot our computer…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Plug in PineDio USB</p>
|
||
</li>
|
||
<li>
|
||
<p>Check that the CH341 SPI Driver has been correctly loaded…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>dmesg</code></pre></div></li>
|
||
<li>
|
||
<p>We should see…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>ch341_gpio_probe:
|
||
registered GPIOs from 496 to 511</code></pre></div>
|
||
<p>The CH341 SPI Driver has been loaded successfully.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#connect-usb">(See the complete log)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>If we see…</p>
|
||
<div class="example-wrap"><pre class="language-text"><code>spi_ch341_usb:
|
||
loading out-of-tree module taints kernel</code></pre></div>
|
||
<p>It means the <strong>older CH341 Non-SPI Driver</strong> has been loaded.</p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#connect-usb-failed">(See the complete log)</a></p>
|
||
</li>
|
||
</ol>
|
||
<p>To remove the <strong>older CH341 Non-SPI Driver</strong>…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Unplug PineDio USB</p>
|
||
</li>
|
||
<li>
|
||
<p>Enter…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>## Unload the CH341 Non-SPI Driver
|
||
sudo rmmod ch341</code></pre></div></li>
|
||
<li>
|
||
<p>Plug in PineDio USB</p>
|
||
</li>
|
||
<li>
|
||
<p>Enter…</p>
|
||
<div class="example-wrap"><pre class="language-bash"><code>dmesg</code></pre></div>
|
||
<p>And recheck the messages.</p>
|
||
</li>
|
||
</ol>
|
||
<h1 id="appendix-build-pinedio-usb-driver"><a class="doc-anchor" href="#appendix-build-pinedio-usb-driver">§</a>13 Appendix: Build PineDio USB Driver</h1>
|
||
<p>To build <strong>PineDio USB Driver</strong> on <strong>Pinebook Pro Manjaro Arm64</strong>…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Follow the instructions in the previous section to install CH341 SPI Driver.</p>
|
||
</li>
|
||
<li>
|
||
<p>Check that the CH341 SPI Driver has been loaded. (<code>dmesg</code>)</p>
|
||
</li>
|
||
<li>
|
||
<p>Enter at the command prompt…</p>
|
||
</li>
|
||
</ol>
|
||
<div class="example-wrap"><pre class="language-bash"><code>## Download PineDio USB Driver
|
||
git clone --recursive https://github.com/lupyuen/lora-sx1262
|
||
cd lora-sx1262
|
||
|
||
## TODO: Edit src/main.c and uncomment
|
||
## READ_REGISTERS, SEND_MESSAGE or RECEIVE_MESSAGE.
|
||
## See "PineDio USB Operations" below.
|
||
|
||
## TODO: Edit src/main.c and update the LoRa Parameters.
|
||
## See "LoRa Parameters" above.
|
||
|
||
## Build PineDio USB Driver
|
||
make
|
||
|
||
## Run PineDio USB Driver Demo
|
||
sudo ./lora-sx1262</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#pinedio-usb-output-log">(See the Output Log)</a></p>
|
||
<p><a href="https://wiki.pine64.org/wiki/Pinedio#USB_adapter">(More about PineDio USB)</a></p>
|
||
<h2 id="pinedio-usb-operations"><a class="doc-anchor" href="#pinedio-usb-operations">§</a>13.1 PineDio USB Operations</h2>
|
||
<p>The PineDio USB Demo supports 3 operations…</p>
|
||
<ol>
|
||
<li>
|
||
<p>Read SX1262 Registers:</p>
|
||
<p>Edit <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c"><strong>src/main.c</strong></a> and uncomment…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define READ_REGISTERS</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#read-registers">(See the Read Register Log)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Send LoRa Message:</p>
|
||
<p>Edit <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c"><strong>src/main.c</strong></a> and uncomment…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define SEND_MESSAGE</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#send-message">(See the Send Message Log)</a></p>
|
||
</li>
|
||
<li>
|
||
<p>Receive LoRa Message:</p>
|
||
<p>Edit <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/main.c"><strong>src/main.c</strong></a> and uncomment…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>#define RECEIVE_MESSAGE</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262#receive-message">(See the Receive Message Log)</a></p>
|
||
</li>
|
||
</ol>
|
||
<h1 id="appendix-radio-functions"><a class="doc-anchor" href="#appendix-radio-functions">§</a>14 Appendix: Radio Functions</h1>
|
||
<p>In this section we explain the Platform-Independent (Linux and BL602) <strong>Radio Functions</strong>, which are defined in <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c">radio.c</a></p>
|
||
<ul>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L523-L559"><strong>RadioInit:</strong></a> Initialise LoRa SX1262</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L600-L604"><strong>RadioSetChannel:</strong></a> Set LoRa Frequency</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L788-L908"><strong>RadioSetTxConfig:</strong></a> Set LoRa Transmit Configuration</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L661-L786"><strong>RadioSetRxConfig:</strong></a> Set LoRa Receive Configuration</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1069-L1098"><strong>RadioSend:</strong></a> Transmit a LoRa Message</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1117-L1138"><strong>RadioRx:</strong></a> Receive one LoRa Message</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1314-L1460"><strong>RadioIrqProcess:</strong></a> Process Transmit and Receive Interrupts</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1100-L1109"><strong>RadioSleep:</strong></a> Switch SX1262 to low-power sleep mode</p>
|
||
</li>
|
||
</ul>
|
||
<h2 id="radioinit-initialise-lora-module"><a class="doc-anchor" href="#radioinit-initialise-lora-module">§</a>14.1 RadioInit: Initialise LoRa Module</h2>
|
||
<p><strong>RadioInit</strong> initialises the LoRa SX1262 Module: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L523-L559">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void RadioInit( RadioEvents_t *events ) {
|
||
// We copy the Event Callbacks from "events", because
|
||
// "events" may be stored on the stack
|
||
assert(events != NULL);
|
||
memcpy(&RadioEvents, events, sizeof(RadioEvents));
|
||
// Previously: RadioEvents = events;</code></pre></div>
|
||
<p>The function begins by copying the list of <strong>Radio Event Callbacks</strong>…</p>
|
||
<ul>
|
||
<li>
|
||
<p><strong>TxDone</strong>: Called when a LoRa Message has been transmitted</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>RxDone</strong>: Called when a LoRa Message has been received</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>TxTimeout</strong>: Called upon timeout when transmitting a LoRa Message</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>RxTimeout</strong>: Called upon timeout when receiving a LoRa Message</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>RxError</strong>: Called when a LoRa Message has been received with CRC Error</p>
|
||
</li>
|
||
</ul>
|
||
<p>This differs from the Semtech Reference Implementation, which copies the pointer to <strong>RadioEvents_t</strong> instead of the entire <strong>RadioEvents_t</strong>.</p>
|
||
<p>(Which causes problems when <strong>RadioEvents_t</strong> lives on the stack)</p>
|
||
<p>Next we <strong>init the SPI and GPIO Ports</strong>, wake up the LoRa Module, and init the TCXO Control and RF Switch Control.</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Init SPI and GPIO Ports, wake up the LoRa Module,
|
||
// init TCXO Control and RF Switch Control.
|
||
SX126xInit( RadioOnDioIrq );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L112-L131">(<strong>SX126xInit</strong> is defined here)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radioondioirq">(<strong>RadioOnDioIrq</strong> is explained here)</a></p>
|
||
<p>We set the LoRa Module to <strong>Standby Mode</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Set LoRa Module to standby mode
|
||
SX126xSetStandby( STDBY_RC );</code></pre></div>
|
||
<p>We set the <strong>Power Regulation: LDO or DC-DC</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // TODO: Declare the power regulation used to power the device
|
||
// This command allows the user to specify if DC-DC or LDO is used for power regulation.
|
||
// Using only LDO implies that the Rx or Tx current is doubled
|
||
|
||
// #warning SX126x is set to LDO power regulator mode (instead of DC-DC)
|
||
// SX126xSetRegulatorMode( USE_LDO ); // Use LDO
|
||
|
||
// #warning SX126x is set to DC-DC power regulator mode (instead of LDO)
|
||
SX126xSetRegulatorMode( USE_DCDC ); // Use DC-DC</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L390-L393">(<strong>SX126xSetRegulatorMode</strong> is defined here)</a></p>
|
||
<p>This depends on how our LoRa Module is wired for power.</p>
|
||
<p>For now we’re using <strong>DC-DC</strong> Power Regulation. (To be verified)</p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan#dc-dc-vs-ldo">(More about LDO vs DC-DC Power Regulation)</a></p>
|
||
<p>We set the <strong>Base Addresses</strong> of the Read and Write Buffers to 0…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Set the base addresses of the Read and Write Buffers to 0
|
||
SX126xSetBufferBaseAddress( 0x00, 0x00 );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L697-L704">(<strong>SX126xSetBufferBaseAddress</strong> is defined here)</a></p>
|
||
<p>The Read and Write Buffers are accessed by <a href="https://lupyuen.github.io/articles/usb#receive-long-message"><strong>sx126x_read_buffer</strong></a> and <a href="https://lupyuen.github.io/articles/usb#transmit-long-message"><strong>sx126x_write_buffer</strong></a>.</p>
|
||
<p>We set the <strong>Transmit Power</strong> and the <strong>Ramp Up Time</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // TODO: Set the correct transmit power and ramp up time
|
||
SX126xSetTxParams( 22, RADIO_RAMP_3400_US );
|
||
// TODO: Previously: SX126xSetTxParams( 0, RADIO_RAMP_200_US );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L528-L576">(<strong>SX126xSetTxParams</strong> is defined here)</a></p>
|
||
<p><strong>Ramp Up Time</strong> is the duration (in microseconds) we need to wait for SX1262’s Power Amplifier to ramp up (charge up) to the configured Transmit Power.</p>
|
||
<p>For easier testing we have set the Transmit Power to <strong>22 dBm</strong> (highest power) and Ramp Up Time to <strong>3400 microseconds</strong> (longest duration).</p>
|
||
<p>(To give sufficient time for the Power Amplifier to ramp up to the highest Transmit Power)</p>
|
||
<p>After testing we should revert to the <strong>Default Transmit Power</strong> (0) and <strong>Ramp Up Time</strong> (200 microseconds).</p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan#transmit-power">(More about the Transmit Power)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan#over-current-protection">(Over Current Protection in <strong>SX126xSetTxParams</strong>)</a></p>
|
||
<p>We configure which <strong>LoRa Events will trigger interrupts</strong> on each DIO Pin…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Set the DIO Interrupt Events:
|
||
// All LoRa Events will trigger interrupts on DIO1
|
||
SX126xSetDioIrqParams(
|
||
IRQ_RADIO_ALL, // Interrupt Mask
|
||
IRQ_RADIO_ALL, // Interrupt Events for DIO1
|
||
IRQ_RADIO_NONE, // Interrupt Events for DIO2
|
||
IRQ_RADIO_NONE // Interrupt Events for DIO3
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L457-L470">(<strong>SX126xSetDioIrqParams</strong> is defined here)</a></p>
|
||
<p>(All LoRa Events will trigger interrupts on DIO1)</p>
|
||
<p>We define the SX1262 Registers that will be restored from <strong>Retention Memory</strong> when waking up from Warm Start Mode…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Add registers to the retention list (4 is the maximum possible number)
|
||
RadioAddRegisterToRetentionList( REG_RX_GAIN );
|
||
RadioAddRegisterToRetentionList( REG_TX_MODULATION );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1167-L1195">(<strong>RadioAddRegisterToRetentionList</strong> is defined here)</a></p>
|
||
<p>Finally we init the Timeout Timers (from NimBLE Porting Layer) for <strong>Transmit Timeout</strong> and <strong>Receive Timeout</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Initialize driver timeout timers
|
||
TimerInit( &TxTimeoutTimer, RadioOnTxTimeoutIrq );
|
||
TimerInit( &RxTimeoutTimer, RadioOnRxTimeoutIrq );
|
||
|
||
// Interrupt not fired yet
|
||
IrqFired = false;
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L361-L380">(<strong>TimerInit</strong> is defined here)</a></p>
|
||
<h2 id="radiosetchannel-set-lora-frequency"><a class="doc-anchor" href="#radiosetchannel-set-lora-frequency">§</a>14.2 RadioSetChannel: Set LoRa Frequency</h2>
|
||
<p><strong>RadioSetChannel</strong> sets the LoRa Frequency: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L600-L604">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void RadioSetChannel( uint32_t freq ) {
|
||
SX126xSetRfFrequency( freq );
|
||
}</code></pre></div>
|
||
<p><strong>RadioSetChannel</strong> passes the LoRa Frequency (like <code>923000000</code> for 923 MHz) to <strong>SX126xSetRfFrequency</strong>.</p>
|
||
<p><strong>SX126xSetRfFrequency</strong> is defined as follows: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L497-L514">sx126x.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void SX126xSetRfFrequency( uint32_t frequency ) {
|
||
uint8_t buf[4];
|
||
if( ImageCalibrated == false ) {
|
||
SX126xCalibrateImage( frequency );
|
||
ImageCalibrated = true;
|
||
}
|
||
uint32_t freqInPllSteps = SX126xConvertFreqInHzToPllStep( frequency );
|
||
buf[0] = ( uint8_t )( ( freqInPllSteps >> 24 ) & 0xFF );
|
||
buf[1] = ( uint8_t )( ( freqInPllSteps >> 16 ) & 0xFF );
|
||
buf[2] = ( uint8_t )( ( freqInPllSteps >> 8 ) & 0xFF );
|
||
buf[3] = ( uint8_t )( freqInPllSteps & 0xFF );
|
||
SX126xWriteCommand( RADIO_SET_RFFREQUENCY, buf, 4 );
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L408-L438">(<strong>SX126xCalibrateImage</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L812-L826">(<strong>SX126xConvertFreqInHzToPllStep</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L204-L217">(<strong>SX126xWriteCommand</strong> is defined here)</a></p>
|
||
<h2 id="radiosettxconfig-set-transmit-configuration"><a class="doc-anchor" href="#radiosettxconfig-set-transmit-configuration">§</a>14.3 RadioSetTxConfig: Set Transmit Configuration</h2>
|
||
<p><strong>RadioSetTxConfig</strong> sets the LoRa Transmit Configuration: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L788-L908">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void RadioSetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev,
|
||
uint32_t bandwidth, uint32_t datarate,
|
||
uint8_t coderate, uint16_t preambleLen,
|
||
bool fixLen, bool crcOn, bool freqHopOn,
|
||
uint8_t hopPeriod, bool iqInverted, uint32_t timeout ) {
|
||
|
||
// LoRa Modulation or FSK Modulation?
|
||
switch( modem ) {
|
||
case MODEM_FSK:
|
||
// Omitted: FSK Modulation
|
||
...</code></pre></div>
|
||
<p>Since we’re using <strong>LoRa Modulation</strong> instead of FSK Modulation, we skip the section on FSK Modulation.</p>
|
||
<p>We begin by populating the <strong>Modulation Parameters</strong>: Spreading Factor, Bandwidth and Coding Rate…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> case MODEM_LORA:
|
||
// LoRa Modulation
|
||
SX126x.ModulationParams.PacketType =
|
||
PACKET_TYPE_LORA;
|
||
SX126x.ModulationParams.Params.LoRa.SpreadingFactor =
|
||
( RadioLoRaSpreadingFactors_t ) datarate;
|
||
SX126x.ModulationParams.Params.LoRa.Bandwidth =
|
||
Bandwidths[bandwidth];
|
||
SX126x.ModulationParams.Params.LoRa.CodingRate =
|
||
( RadioLoRaCodingRates_t )coderate;</code></pre></div>
|
||
<p>Depending on the LoRa Parameters, we optimise for <strong>Low Data Rate</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Optimise for Low Data Rate
|
||
if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
|
||
( ( bandwidth == 1 ) && ( datarate == 12 ) ) ) {
|
||
SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
|
||
} else {
|
||
SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x00;
|
||
}</code></pre></div>
|
||
<p>Next we populate the <strong>Packet Parameters</strong>: Preamble Length, Header Type, Payload Length, CRC Mode and Invert IQ…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Populate Packet Type
|
||
SX126x.PacketParams.PacketType = PACKET_TYPE_LORA;
|
||
|
||
// Populate Preamble Length
|
||
if( ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF5 ) ||
|
||
( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF6 ) ) {
|
||
if( preambleLen < 12 ) {
|
||
SX126x.PacketParams.Params.LoRa.PreambleLength = 12;
|
||
} else {
|
||
SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
|
||
}
|
||
} else {
|
||
SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
|
||
}
|
||
|
||
// Populate Header Type, Payload Length, CRC Mode and Invert IQ
|
||
SX126x.PacketParams.Params.LoRa.HeaderType =
|
||
( RadioLoRaPacketLengthsMode_t )fixLen;
|
||
SX126x.PacketParams.Params.LoRa.PayloadLength =
|
||
MaxPayloadLength;
|
||
SX126x.PacketParams.Params.LoRa.CrcMode =
|
||
( RadioLoRaCrcModes_t )crcOn;
|
||
SX126x.PacketParams.Params.LoRa.InvertIQ =
|
||
( RadioLoRaIQModes_t )iqInverted;</code></pre></div>
|
||
<p>We set the LoRa Module to <strong>Standby Mode</strong> and configure it for <strong>LoRa Modulation</strong> (or FSK Modulation)…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Set LoRa Module to Standby Mode
|
||
RadioStandby( );
|
||
|
||
// Configure LoRa Module for LoRa Modulation (or FSK Modulation)
|
||
RadioSetModem(
|
||
( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK )
|
||
? MODEM_FSK
|
||
: MODEM_LORA
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1111-L1115">(<strong>RadioStandby</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L576-L598">(<strong>RadioSetModem</strong> is defined here)</a></p>
|
||
<p>We configure the LoRa Module with the <strong>Modulation Parameters</strong> and <strong>Packet Parameters</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Configure Modulation Parameters
|
||
SX126xSetModulationParams( &SX126x.ModulationParams );
|
||
|
||
// Configure Packet Parameters
|
||
SX126xSetPacketParams( &SX126x.PacketParams );
|
||
break;
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L578-L621">(<strong>SX126xSetModulationParams</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L623-L680">(<strong>SX126xSetPacketParams</strong> is defined here)</a></p>
|
||
<p>This is a Workaround for <strong>Modulation Quality</strong> with <strong>500 kHz Bandwidth</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // WORKAROUND - Modulation Quality with 500 kHz LoRa Bandwidth, see DS_SX1261-2_V1.2 datasheet chapter 15.1
|
||
if( ( modem == MODEM_LORA ) && ( SX126x.ModulationParams.Params.LoRa.Bandwidth == LORA_BW_500 ) ) {
|
||
SX126xWriteRegister(
|
||
REG_TX_MODULATION,
|
||
SX126xReadRegister( REG_TX_MODULATION ) & ~( 1 << 2 )
|
||
);
|
||
} else {
|
||
SX126xWriteRegister(
|
||
REG_TX_MODULATION,
|
||
SX126xReadRegister( REG_TX_MODULATION ) | ( 1 << 2 )
|
||
);
|
||
}
|
||
// WORKAROUND END</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L263-L266">(<strong>SX126xWriteRegister</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L276-L281">(<strong>SX126xReadRegister</strong> is defined here)</a></p>
|
||
<p>We finish by setting the <strong>Transmit Power</strong> and <strong>Transmit Timeout</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Set Transmit Power
|
||
SX126xSetRfTxPower( power );
|
||
|
||
// Set Transmit Timeout
|
||
TxTimeout = timeout;
|
||
}</code></pre></div>
|
||
<p><strong>SX126xSetRfTxPower</strong> is defined in <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L299-L304">sx126x-linux.c</a>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void SX126xSetRfTxPower( int8_t power ) {
|
||
// TODO: Previously: SX126xSetTxParams( power, RADIO_RAMP_40_US );
|
||
SX126xSetTxParams( power, RADIO_RAMP_3400_US ); // TODO
|
||
}</code></pre></div>
|
||
<p>For easier testing we have set the Ramp Up Time to <strong>3400 microseconds</strong> (longest duration).</p>
|
||
<p>After testing we should revert to the <strong>Default Ramp Up Time</strong> (40 microseconds).</p>
|
||
<h2 id="radiosetrxconfig-set-receive-configuration"><a class="doc-anchor" href="#radiosetrxconfig-set-receive-configuration">§</a>14.4 RadioSetRxConfig: Set Receive Configuration</h2>
|
||
<p><strong>RadioSetRxConfig</strong> sets the LoRa Receive Configuration: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L661-L786">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void RadioSetRxConfig( RadioModems_t modem, uint32_t bandwidth,
|
||
uint32_t datarate, uint8_t coderate,
|
||
uint32_t bandwidthAfc, uint16_t preambleLen,
|
||
uint16_t symbTimeout, bool fixLen,
|
||
uint8_t payloadLen,
|
||
bool crcOn, bool freqHopOn, uint8_t hopPeriod,
|
||
bool iqInverted, bool rxContinuous ) {
|
||
|
||
// Set Symbol Timeout
|
||
RxContinuous = rxContinuous;
|
||
if( rxContinuous == true ) {
|
||
symbTimeout = 0;
|
||
}
|
||
|
||
// Set Max Payload Length
|
||
if( fixLen == true ) {
|
||
MaxPayloadLength = payloadLen;
|
||
}
|
||
else {
|
||
MaxPayloadLength = 0xFF;
|
||
}</code></pre></div>
|
||
<p>We begin by setting the <strong>Symbol Timeout</strong> and <strong>Max Payload Length</strong>.</p>
|
||
<p>Since we’re using <strong>LoRa Modulation</strong> instead of FSK Modulation, we skip the section on FSK Modulation…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // LoRa Modulation or FSK Modulation?
|
||
switch( modem )
|
||
{
|
||
case MODEM_FSK:
|
||
// Omitted: FSK Modulation
|
||
...</code></pre></div>
|
||
<p>We populate the <strong>Modulation Parameters</strong>: Spreading Factor, Bandwidth and Coding Rate…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> case MODEM_LORA:
|
||
// LoRa Modulation
|
||
SX126xSetStopRxTimerOnPreambleDetect( false );
|
||
SX126x.ModulationParams.PacketType =
|
||
PACKET_TYPE_LORA;
|
||
SX126x.ModulationParams.Params.LoRa.SpreadingFactor =
|
||
( RadioLoRaSpreadingFactors_t )datarate;
|
||
SX126x.ModulationParams.Params.LoRa.Bandwidth =
|
||
Bandwidths[bandwidth];
|
||
SX126x.ModulationParams.Params.LoRa.CodingRate =
|
||
( RadioLoRaCodingRates_t )coderate;</code></pre></div>
|
||
<p>Depending on the LoRa Parameters, we optimise for <strong>Low Data Rate</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Optimise for Low Data Rate
|
||
if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
|
||
( ( bandwidth == 1 ) && ( datarate == 12 ) ) ) {
|
||
SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
|
||
} else {
|
||
SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x00;
|
||
}</code></pre></div>
|
||
<p>We populate the <strong>Packet Parameters</strong>: Preamble Length, Header Type, Payload Length, CRC Mode and Invert IQ…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Populate Packet Type
|
||
SX126x.PacketParams.PacketType = PACKET_TYPE_LORA;
|
||
|
||
// Populate Preamble Length
|
||
if( ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF5 ) ||
|
||
( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF6 ) ){
|
||
if( preambleLen < 12 ) {
|
||
SX126x.PacketParams.Params.LoRa.PreambleLength = 12;
|
||
} else {
|
||
SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
|
||
}
|
||
} else {
|
||
SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
|
||
}
|
||
|
||
// Populate Header Type, Payload Length, CRC Mode and Invert IQ
|
||
SX126x.PacketParams.Params.LoRa.HeaderType =
|
||
( RadioLoRaPacketLengthsMode_t )fixLen;
|
||
SX126x.PacketParams.Params.LoRa.PayloadLength =
|
||
MaxPayloadLength;
|
||
SX126x.PacketParams.Params.LoRa.CrcMode =
|
||
( RadioLoRaCrcModes_t )crcOn;
|
||
SX126x.PacketParams.Params.LoRa.InvertIQ =
|
||
( RadioLoRaIQModes_t )iqInverted;</code></pre></div>
|
||
<p>We set the LoRa Module to <strong>Standby Mode</strong> and configure it for <strong>LoRa Modulation</strong> (or FSK Modulation)…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Set LoRa Module to Standby Mode
|
||
RadioStandby( );
|
||
|
||
// Configure LoRa Module for LoRa Modulation (or FSK Modulation)
|
||
RadioSetModem(
|
||
( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK )
|
||
? MODEM_FSK
|
||
: MODEM_LORA
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1111-L1115">(<strong>RadioStandby</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L576-L598">(<strong>RadioSetModem</strong> is defined here)</a></p>
|
||
<p>We configure the LoRa Module with the <strong>Modulation Parameters</strong>, <strong>Packet Parameters</strong> and Symbol Timeout…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Configure Modulation Parameters
|
||
SX126xSetModulationParams( &SX126x.ModulationParams );
|
||
|
||
// Configure Packet Parameters
|
||
SX126xSetPacketParams( &SX126x.PacketParams );
|
||
|
||
// Configure Symbol Timeout
|
||
SX126xSetLoRaSymbNumTimeout( symbTimeout );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L578-L621">(<strong>SX126xSetModulationParams</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L623-L680">(<strong>SX126xSetPacketParams</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L366-L388">(<strong>SX126xSetLoRaSymbNumTimeout</strong> is defined here)</a></p>
|
||
<p>This is a Workaround that <strong>optimises the Inverted IQ Operation</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // WORKAROUND - Optimizing the Inverted IQ Operation, see DS_SX1261-2_V1.2 datasheet chapter 15.4
|
||
if( SX126x.PacketParams.Params.LoRa.InvertIQ == LORA_IQ_INVERTED ) {
|
||
SX126xWriteRegister(
|
||
REG_IQ_POLARITY,
|
||
SX126xReadRegister( REG_IQ_POLARITY ) & ~( 1 << 2 )
|
||
);
|
||
} else {
|
||
SX126xWriteRegister(
|
||
REG_IQ_POLARITY,
|
||
SX126xReadRegister( REG_IQ_POLARITY ) | ( 1 << 2 )
|
||
);
|
||
}
|
||
// WORKAROUND END</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L263-L266">(<strong>SX126xWriteRegister</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L276-L281">(<strong>SX126xReadRegister</strong> is defined here)</a></p>
|
||
<p>We finish by setting the <strong>Receive Timeout</strong> to No Timeout (always receiving)…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Timeout Max, Timeout handled directly in SetRx function
|
||
RxTimeout = 0xFFFF;
|
||
break;
|
||
}
|
||
}</code></pre></div><h2 id="radiosend-transmit-message"><a class="doc-anchor" href="#radiosend-transmit-message">§</a>14.5 RadioSend: Transmit Message</h2>
|
||
<p><strong>RadioSend</strong> transmits a LoRa Message: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1069-L1098">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void RadioSend( uint8_t *buffer, uint8_t size ) {
|
||
|
||
// Set the DIO Interrupt Events:
|
||
// Transmit Done and Transmit Timeout
|
||
// will trigger interrupts on DIO1
|
||
SX126xSetDioIrqParams(
|
||
IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT, // Interrupt Mask
|
||
IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT, // Interrupt Events for DIO1
|
||
IRQ_RADIO_NONE, // Interrupt Events for DIO2
|
||
IRQ_RADIO_NONE // Interrupt Events for DIO3
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L457-L470">(<strong>SX126xSetDioIrqParams</strong> is defined here)</a></p>
|
||
<p>We begin by configuring which <strong>LoRa Events will trigger interrupts</strong> on each DIO Pin.</p>
|
||
<p>(Transmit Done and Transmit Timeout will trigger interrupts on DIO1)</p>
|
||
<p>Next we configure the <strong>Packet Parameters</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Populate the payload length
|
||
if( SX126xGetPacketType( ) == PACKET_TYPE_LORA ) {
|
||
SX126x.PacketParams.Params.LoRa.PayloadLength = size;
|
||
} else {
|
||
SX126x.PacketParams.Params.Gfsk.PayloadLength = size;
|
||
}
|
||
// Configure the packet parameters
|
||
SX126xSetPacketParams( &SX126x.PacketParams );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L523-L526">(<strong>SX126xGetPacketType</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L623-L680">(<strong>SX126xSetPacketParams</strong> is defined here)</a></p>
|
||
<p>We finish by sending the <strong>Message Payload</strong> and starting the <strong>Transmit Timer</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Send message payload
|
||
SX126xSendPayload( buffer, size, 0 );
|
||
|
||
// Start Transmit Timer
|
||
TimerStart( &TxTimeoutTimer, TxTimeout );
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L396-L421">(<strong>TimerStart</strong> is defined here)</a></p>
|
||
<p><strong>SX126xSendPayload</strong> is defined below: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L162-L166">sx126x.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Send message payload
|
||
void SX126xSendPayload( uint8_t *payload, uint8_t size, uint32_t timeout ) {
|
||
// Copy message payload to Transmit Buffer
|
||
SX126xSetPayload( payload, size );
|
||
|
||
// Transmit the buffer
|
||
SX126xSetTx( timeout );
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L289-L299">(<strong>SX126xSetTx</strong> is defined here)</a></p>
|
||
<p>This code copies the Message Payload to the SX1262 <strong>Transmit Buffer</strong> and transmits the message.</p>
|
||
<p><strong>SX126xSetPayload</strong> copies to the Transmit Buffer by calling <strong>SX126xWriteBuffer</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L144-L147">sx126x.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Copy message payload to Transmit Buffer
|
||
void SX126xSetPayload( uint8_t *payload, uint8_t size ) {
|
||
// Copy message payload to Transmit Buffer
|
||
SX126xWriteBuffer( 0x00, payload, size );
|
||
}</code></pre></div>
|
||
<p><strong>SX126xWriteBuffer</strong> wakes up the LoRa Module, writes to the Transmit Buffer and waits for the operation to be completed: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L283-L289">sx126x.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Copy message payload to Transmit Buffer
|
||
void SX126xWriteBuffer( uint8_t offset, uint8_t *buffer, uint8_t size ) {
|
||
// Wake up SX1262 if sleeping
|
||
SX126xCheckDeviceReady( );
|
||
|
||
// Copy message payload to Transmit Buffer
|
||
int rc = sx126x_write_buffer(NULL, offset, buffer, size);
|
||
assert(rc == 0);
|
||
|
||
// Wait for SX1262 to be ready
|
||
SX126xWaitOnBusy( );
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L133-L142">(<strong>SX126xCheckDeviceReady</strong> is defined here)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#transmit-long-message">(<strong>sx126x_write_buffer</strong> is explained here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L171-L182">(<strong>SX126xWaitOnBusy</strong> is defined here)</a></p>
|
||
<p>When the LoRa Message is transmitted (successfully or unsuccessfully), the LoRa Module triggers a <strong>DIO1 Interrupt</strong>.</p>
|
||
<p>Our driver calls <strong>RadioIrqProcess</strong> to process the interrupt. <a href="https://lupyuen.github.io/articles/usb#radioirqprocess-process-transmit-and-receive-interrupts">(See this)</a></p>
|
||
<h2 id="radiorx-receive-message"><a class="doc-anchor" href="#radiorx-receive-message">§</a>14.6 RadioRx: Receive Message</h2>
|
||
<p><strong>RadioRx</strong> receives a single LoRa Message: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1117-L1138">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void RadioRx( uint32_t timeout ) {
|
||
|
||
// Set the DIO Interrupt Events:
|
||
// All LoRa Events will trigger interrupts on DIO1
|
||
SX126xSetDioIrqParams(
|
||
IRQ_RADIO_ALL, // Interrupt Mask
|
||
IRQ_RADIO_ALL, // Interrupt Events for DIO1
|
||
IRQ_RADIO_NONE, // Interrupt Events for DIO2
|
||
IRQ_RADIO_NONE // Interrupt Events for DIO3
|
||
);</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L457-L470">(<strong>SX126xSetDioIrqParams</strong> is defined here)</a></p>
|
||
<p>We begin by configuring which <strong>LoRa Events will trigger interrupts</strong> on each DIO Pin.</p>
|
||
<p>(All LoRa Events will trigger interrupts on DIO1)</p>
|
||
<p>We start the <strong>Receive Timer</strong> to catch Receive Timeouts…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Start the Receive Timer
|
||
if( timeout != 0 ) {
|
||
TimerStart( &RxTimeoutTimer, timeout );
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L396-L421">(<strong>TimerStart</strong> is defined here)</a></p>
|
||
<p>Now we begin to <strong>receive a LoRa Message</strong> continuously, or until a timeout occurs…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> if( RxContinuous == true ) {
|
||
// Receive continuously
|
||
SX126xSetRx( 0xFFFFFF ); // Rx Continuous
|
||
} else {
|
||
// Receive with timeout
|
||
SX126xSetRx( RxTimeout << 6 );
|
||
}
|
||
}</code></pre></div>
|
||
<p><strong>SX126xSetRx</strong> enters Receive Mode like so: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L301-L313">sx126x.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>void SX126xSetRx( uint32_t timeout ) {
|
||
uint8_t buf[3];
|
||
|
||
// Remember we're in Receive Mode
|
||
SX126xSetOperatingMode( MODE_RX );
|
||
|
||
// Configure Receive Gain
|
||
SX126xWriteRegister( REG_RX_GAIN, 0x94 ); // default gain
|
||
|
||
// Enter Receive Mode
|
||
buf[0] = ( uint8_t )( ( timeout >> 16 ) & 0xFF );
|
||
buf[1] = ( uint8_t )( ( timeout >> 8 ) & 0xFF );
|
||
buf[2] = ( uint8_t )( timeout & 0xFF );
|
||
SX126xWriteCommand( RADIO_SET_RX, buf, 3 );
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L126-L147">(<strong>SX126xSetOperatingMode</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L263-L266">(<strong>SX126xWriteRegister</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L204-L217">(<strong>SX126xWriteCommand</strong> is defined here)</a></p>
|
||
<p>When a LoRa Message is received (successfully or unsuccessfully), the LoRa Module triggers a <strong>DIO1 Interrupt</strong>.</p>
|
||
<p>Our driver calls <strong>RadioIrqProcess</strong> to process the interrupt, which is explained next…</p>
|
||
<h2 id="radioirqprocess-process-transmit-and-receive-interrupts"><a class="doc-anchor" href="#radioirqprocess-process-transmit-and-receive-interrupts">§</a>14.7 RadioIrqProcess: Process Transmit and Receive Interrupts</h2>
|
||
<p><strong>RadioIrqProcess</strong> processes the interrupts that are triggered when a LoRa Message is transmitted and received: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1314-L1460">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Process Transmit and Receive Interrupts.
|
||
/// For BL602: Must be run in the Application
|
||
/// Task Context, not Interrupt Context because
|
||
/// we will call printf and SPI Functions here.
|
||
void RadioIrqProcess( void ) {
|
||
|
||
// Remember and clear Interrupt Flag
|
||
CRITICAL_SECTION_BEGIN( );
|
||
const bool isIrqFired = IrqFired;
|
||
IrqFired = false;
|
||
CRITICAL_SECTION_END( );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/include/sx126x-board.h#L58-L60">(Note: Critical Sections are not yet implemented)</a></p>
|
||
<p>The function begins by copying the <strong>Interrupt Flag</strong> and clearing the flag.</p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#radioondioirq">(The Interrupt Flag is set by <strong>RadioOnDioIrq</strong>)</a></p>
|
||
<p>The rest of the function will run only if the <strong>Interrupt Flag was originally set</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // IrqFired must be true to process interrupts
|
||
if( isIrqFired == true ) {
|
||
// Get the Interrupt Status
|
||
uint16_t irqRegs = SX126xGetIrqStatus( );
|
||
|
||
// Clear the Interrupt Status
|
||
SX126xClearIrqStatus( irqRegs );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L472-L478">(<strong>SX126xGetIrqStatus</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L803-L810">(<strong>SX126xClearIrqStatus</strong> is defined here)</a></p>
|
||
<p>This code fetches the <strong>Interrupt Status</strong> from the LoRa Module and clears the Interrupt Status.</p>
|
||
<p>If DIO1 is still High, we set the <strong>Interrupt Flag</strong> for future processing…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Check if DIO1 pin is High. If it is the case revert IrqFired to true
|
||
CRITICAL_SECTION_BEGIN( );
|
||
if( SX126xGetDio1PinState( ) == 1 ) {
|
||
IrqFired = true;
|
||
}
|
||
CRITICAL_SECTION_END( );</code></pre></div>
|
||
<p><strong>Interrupt Status</strong> tells us which LoRa Events have just occurred. We handle the LoRa Events accordingly…</p>
|
||
<ul>
|
||
<li>
|
||
<p>Transmit Done</p>
|
||
</li>
|
||
<li>
|
||
<p>Receive Done</p>
|
||
</li>
|
||
<li>
|
||
<p>CAD Done</p>
|
||
</li>
|
||
<li>
|
||
<p>Transmit / Receive Timeout</p>
|
||
</li>
|
||
<li>
|
||
<p>Preamble Detected</p>
|
||
</li>
|
||
<li>
|
||
<p>Sync Word Valid</p>
|
||
</li>
|
||
<li>
|
||
<p>Header Valid</p>
|
||
</li>
|
||
<li>
|
||
<p>Header Error</p>
|
||
</li>
|
||
</ul>
|
||
<h3 id="transmit-done"><a class="doc-anchor" href="#transmit-done">§</a>14.7.1 Transmit Done</h3>
|
||
<p>When the LoRa Module has transmitted a LoRa Message successfully, we stop the Transmit Timer and call the <strong>Callback Function for Transmit Done</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1339-L1349">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If a LoRa Message was transmitted successfully...
|
||
if( ( irqRegs & IRQ_TX_DONE ) == IRQ_TX_DONE ) {
|
||
|
||
// Stop the Transmit Timer
|
||
TimerStop( &TxTimeoutTimer );
|
||
|
||
//!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
|
||
SX126xSetOperatingMode( MODE_STDBY_RC );
|
||
|
||
// Call the Callback Function for Transmit Done
|
||
if( ( RadioEvents.TxDone != NULL ) ) {
|
||
RadioEvents.TxDone( );
|
||
}
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L382-L394">(<strong>TimerStop</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L126-L147">(<strong>SX126xSetOperatingMode</strong> is defined here)</a></p>
|
||
<p><strong>TxDone</strong> points to the <strong>on_tx_done</strong> Callback Function that we’ve seen earlier.</p>
|
||
<h3 id="receive-done"><a class="doc-anchor" href="#receive-done">§</a>14.7.2 Receive Done</h3>
|
||
<p>When the LoRa Module receives a LoRa Message, we stop the Receive Timer: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1351-L1389">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If a LoRa Message was received...
|
||
if( ( irqRegs & IRQ_RX_DONE ) == IRQ_RX_DONE ) {
|
||
|
||
// Stop the Receive Timer
|
||
TimerStop( &RxTimeoutTimer );</code></pre></div>
|
||
<p>In case of CRC Error, we call the <strong>Callback Function for Receive Error</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> if( ( irqRegs & IRQ_CRC_ERROR ) == IRQ_CRC_ERROR ) {
|
||
|
||
// If the received message has CRC Error...
|
||
if( RxContinuous == false ) {
|
||
//!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
|
||
SX126xSetOperatingMode( MODE_STDBY_RC );
|
||
}
|
||
|
||
// Call the Callback Function for Receive Error
|
||
if( ( RadioEvents.RxError ) ) {
|
||
RadioEvents.RxError( );
|
||
}</code></pre></div>
|
||
<p><strong>RxError</strong> points to the <strong>on_rx_error</strong> Callback Function that we’ve seen earlier.</p>
|
||
<p>If the received message has no CRC Error, we do this Workaround for <strong>Implicit Header Mode Timeout Behavior</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> } else {
|
||
// If the received message has no CRC Error...
|
||
uint8_t size;
|
||
|
||
// If we are receiving continuously...
|
||
if( RxContinuous == false ) {
|
||
//!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
|
||
SX126xSetOperatingMode( MODE_STDBY_RC );
|
||
|
||
// WORKAROUND - Implicit Header Mode Timeout Behavior, see DS_SX1261-2_V1.2 datasheet chapter 15.3
|
||
SX126xWriteRegister( REG_RTC_CTRL, 0x00 );
|
||
SX126xWriteRegister(
|
||
REG_EVT_CLR,
|
||
SX126xReadRegister( REG_EVT_CLR ) | ( 1 << 1 )
|
||
);
|
||
// WORKAROUND END
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L263-L266">(<strong>SX126xWriteRegister</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L276-L281">(<strong>SX126xReadRegister</strong> is defined here)</a></p>
|
||
<p>Then we copy the <strong>Received Message Payload</strong> and get the <strong>Packet Status</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Copy the Received Message Payload (max 255 bytes)
|
||
SX126xGetPayload( RadioRxPayload, &size , 255 );
|
||
|
||
// Get the Packet Status:
|
||
// Packet Signal Strength (RSSI), Signal-to-Noise Ratio (SNR),
|
||
// Signal RSSI, Frequency Error
|
||
SX126xGetPacketStatus( &RadioPktStatus );</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L746-L778">(<strong>SX126xGetPacketStatus</strong> is defined here)</a></p>
|
||
<p>And we call the <strong>Callback Function for Receive Done</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // Call the Callback Function for Receive Done
|
||
if( ( RadioEvents.RxDone != NULL ) ) {
|
||
RadioEvents.RxDone(
|
||
RadioRxPayload,
|
||
size,
|
||
RadioPktStatus.Params.LoRa.RssiPkt,
|
||
RadioPktStatus.Params.LoRa.SnrPkt
|
||
);
|
||
}
|
||
}
|
||
}</code></pre></div>
|
||
<p><strong>RxDone</strong> points to the <strong>on_rx_done</strong> Callback Function that we’ve seen earlier.</p>
|
||
<p><strong>SX126xGetPayload</strong> copies the received message payload from the SX1262 <strong>Receive Buffer</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L149-L160">sx126x.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Copy message payload from Receive Buffer
|
||
uint8_t SX126xGetPayload( uint8_t *buffer, uint8_t *size, uint8_t maxSize ) {
|
||
uint8_t offset = 0;
|
||
|
||
// Get the size and offset of the received message
|
||
// in the Receive Buffer
|
||
SX126xGetRxBufferStatus( size, &offset );
|
||
if( *size > maxSize ) {
|
||
return 1;
|
||
}
|
||
|
||
// Copy message payload from Receive Buffer
|
||
SX126xReadBuffer( offset, buffer, *size );
|
||
return 0;
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L727-L744">(<strong>SX126xGetRxBufferStatus</strong> is defined here)</a></p>
|
||
<p><strong>SX126xReadBuffer</strong> wakes up the LoRa Module, reads from the Receive Buffer and waits for the operation to be completed: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L291-L297">sx126x-linux.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Copy message payload from Receive Buffer
|
||
void SX126xReadBuffer( uint8_t offset, uint8_t *buffer, uint8_t size ) {
|
||
// Wake up SX1262 if sleeping
|
||
SX126xCheckDeviceReady( );
|
||
|
||
// Copy message payload from Receive Buffer
|
||
int rc = sx126x_read_buffer(NULL, offset, buffer, size);
|
||
assert(rc == 0);
|
||
|
||
// Wait for SX1262 to be ready
|
||
SX126xWaitOnBusy( );
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L133-L142">(<strong>SX126xCheckDeviceReady</strong> is defined here)</a></p>
|
||
<p><a href="https://lupyuen.github.io/articles/usb#receive-long-message">(<strong>sx126x_read_buffer</strong> is explained here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L171-L182">(<strong>SX126xWaitOnBusy</strong> is defined here)</a></p>
|
||
<h3 id="cad-done"><a class="doc-anchor" href="#cad-done">§</a>14.7.3 CAD Done</h3>
|
||
<p><strong>Channel Activity Detection</strong> lets us <strong>detect whether there’s any ongoing transmission</strong> in a LoRa Radio Channel, in a power-efficient way.</p>
|
||
<p>We won’t be doing Channel Activity Detection in our driver: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1391-L1400">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If Channel Activity Detection is complete...
|
||
if( ( irqRegs & IRQ_CAD_DONE ) == IRQ_CAD_DONE ) {
|
||
|
||
//!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
|
||
SX126xSetOperatingMode( MODE_STDBY_RC );
|
||
|
||
// Call Callback Function for CAD Done
|
||
if( ( RadioEvents.CadDone != NULL ) ) {
|
||
RadioEvents.CadDone( (
|
||
( irqRegs & IRQ_CAD_ACTIVITY_DETECTED )
|
||
== IRQ_CAD_ACTIVITY_DETECTED
|
||
) );
|
||
}
|
||
}</code></pre></div><h3 id="transmit--receive-timeout"><a class="doc-anchor" href="#transmit--receive-timeout">§</a>14.7.4 Transmit / Receive Timeout</h3>
|
||
<p>When the LoRa Module <strong>fails to transmit</strong> a LoRa Message due to Timeout, we stop the Transmit Timer and call the <strong>Callback Function for Transmit Timeout</strong>: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1402-L1425">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If a LoRa Message failed to Transmit or Receive due to Timeout...
|
||
if( ( irqRegs & IRQ_RX_TX_TIMEOUT ) == IRQ_RX_TX_TIMEOUT ) {
|
||
|
||
// If the message failed to Transmit due to Timeout...
|
||
if( SX126xGetOperatingMode( ) == MODE_TX ) {
|
||
|
||
// Stop the Transmit Timer
|
||
TimerStop( &TxTimeoutTimer );
|
||
|
||
//!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
|
||
SX126xSetOperatingMode( MODE_STDBY_RC );
|
||
|
||
// Call the Callback Function for Transmit Timeout
|
||
if( ( RadioEvents.TxTimeout != NULL ) ) {
|
||
RadioEvents.TxTimeout( );
|
||
}
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L121-L124">(<strong>SX126xGetOperatingMode</strong> is defined here)</a></p>
|
||
<p><strong>TxTimeout</strong> points to the <strong>on_tx_timeout</strong> Callback Function that we’ve seen earlier.</p>
|
||
<p>When the LoRa Module <strong>fails to receive</strong> a LoRa Message due to Timeout, we stop the Receive Timer and call the <strong>Callback Function for Receive Timeout</strong>…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If the message failed to Receive due to Timeout...
|
||
else if( SX126xGetOperatingMode( ) == MODE_RX ) {
|
||
|
||
// Stop the Receive Timer
|
||
TimerStop( &RxTimeoutTimer );
|
||
|
||
//!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
|
||
SX126xSetOperatingMode( MODE_STDBY_RC );
|
||
|
||
// Call the Callback Function for Receive Timeout
|
||
if( ( RadioEvents.RxTimeout != NULL ) ) {
|
||
RadioEvents.RxTimeout( );
|
||
}
|
||
}
|
||
}</code></pre></div>
|
||
<p><strong>RxTimeout</strong> points to the <strong>on_rx_timeout</strong> Callback Function that we’ve seen earlier.</p>
|
||
<h3 id="preamble-detected"><a class="doc-anchor" href="#preamble-detected">§</a>14.7.5 Preamble Detected</h3>
|
||
<p>Preamble is the Radio Signal that <strong>precedes the LoRa Message</strong>. When the LoRa Module detects the Preamble Signal, it knows that it’s about to receive a LoRa Message.</p>
|
||
<p>We don’t need to handle the Preamble Signal, the LoRa Module does it for us: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1427-L1431">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If LoRa Preamble was detected...
|
||
if( ( irqRegs & IRQ_PREAMBLE_DETECTED ) == IRQ_PREAMBLE_DETECTED ) {
|
||
//__NOP( );
|
||
}</code></pre></div>
|
||
<p>Our <a href="https://github.com/lupyuen/lora-sx1262#receive-message"><strong>Receive Message Log</strong></a> shows that the Preamble Signal (<strong>IRQ_PREAMBLE_DETECTED</strong>) is always detected before receiving a LoRa Message.</p>
|
||
<p>(<strong>IRQ_PREAMBLE_DETECTED</strong> appears just before the LoRa Header: <strong>IRQ_HEADER_VALID</strong>)</p>
|
||
<p><a href="https://www.link-labs.com/blog/what-is-lora">(More about LoRa Preamble)</a></p>
|
||
<h3 id="sync-word-valid"><a class="doc-anchor" href="#sync-word-valid">§</a>14.7.6 Sync Word Valid</h3>
|
||
<p><strong>Sync Words</strong> are 16-bit values that differentiate the types of LoRa Networks.</p>
|
||
<p>The LoRa Module detects the Sync Words when it receive a LoRa Message: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1433-L1437">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If a valid Sync Word was detected...
|
||
if( ( irqRegs & IRQ_SYNCWORD_VALID ) == IRQ_SYNCWORD_VALID ) {
|
||
//__NOP( );
|
||
}</code></pre></div>
|
||
<p>Note that the <strong>Sync Word differs for LoRaWAN</strong> vs Private LoRa Networks…</p>
|
||
<div class="example-wrap"><pre class="language-c"><code>// Syncword for Private LoRa networks
|
||
#define LORA_MAC_PRIVATE_SYNCWORD 0x1424
|
||
|
||
// Syncword for Public LoRa networks (LoRaWAN)
|
||
#define LORA_MAC_PUBLIC_SYNCWORD 0x3444</code></pre></div>
|
||
<p><a href="https://lupyuen.github.io/articles/lorawan#appendix-lora-sync-word">(More about Sync Words)</a></p>
|
||
<h3 id="header-valid"><a class="doc-anchor" href="#header-valid">§</a>14.7.7 Header Valid</h3>
|
||
<p>The LoRa Module checks for a <strong>valid LoRa Header</strong> when receiving a LoRa Message: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1439-L1443">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If a valid Header was received...
|
||
if( ( irqRegs & IRQ_HEADER_VALID ) == IRQ_HEADER_VALID ) {
|
||
//__NOP( );
|
||
}</code></pre></div>
|
||
<p>Our <a href="https://github.com/lupyuen/lora-sx1262#receive-message"><strong>Receive Message Log</strong></a> shows that the LoRa Header (<strong>IRQ_HEADER_VALID</strong>) is always detected before receiving a LoRa Message.</p>
|
||
<p>(<strong>IRQ_HEADER_VALID</strong> appears right after the Preamble Signal: <strong>IRQ_PREAMBLE_DETECTED</strong>)</p>
|
||
<h3 id="header-error"><a class="doc-anchor" href="#header-error">§</a>14.7.8 Header Error</h3>
|
||
<p>When the LoRa Module detects a <strong>LoRa Header with CRC Error</strong>, we stop the Receive Timer and call the Callback Function for Receive Timeout: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1445-L1458">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code> // If a Header with CRC Error was received...
|
||
if( ( irqRegs & IRQ_HEADER_ERROR ) == IRQ_HEADER_ERROR ) {
|
||
|
||
// Stop the Receive Timer
|
||
TimerStop( &RxTimeoutTimer );
|
||
|
||
if( RxContinuous == false ) {
|
||
//!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
|
||
SX126xSetOperatingMode( MODE_STDBY_RC );
|
||
}
|
||
|
||
// Call the Callback Function for Receive Timeout
|
||
if( ( RadioEvents.RxTimeout != NULL ) ) {
|
||
RadioEvents.RxTimeout( );
|
||
}
|
||
}
|
||
}
|
||
}</code></pre></div>
|
||
<p><strong>RxTimeout</strong> points to the <strong>on_rx_timeout</strong> Callback Function that we’ve seen earlier.</p>
|
||
<h3 id="radioondioirq"><a class="doc-anchor" href="#radioondioirq">§</a>14.7.9 RadioOnDioIrq</h3>
|
||
<p><strong>RadioIrqProcess</strong> (as defined above) is called by <strong>RadioOnDioIrq</strong> to handle LoRa Transmit and Receive Events: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1300-L1312">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Callback Function for Transmit and Receive Interrupts.
|
||
/// For BL602: This function runs in the context of the
|
||
/// Background Application Task. So we are safe to call
|
||
/// printf and SPI Functions now.
|
||
void RadioOnDioIrq( struct ble_npl_event *ev ) {
|
||
// Set the Interrupt Flag
|
||
IrqFired = true;
|
||
|
||
// BL602 Note: It's OK to process the interrupt here because we are in
|
||
// Application Task Context, not Interrupt Context.
|
||
// The Reference Implementation processes the interrupt in the main loop.
|
||
RadioIrqProcess();
|
||
}</code></pre></div><h2 id="radiosleep-switch-to-sleep-mode"><a class="doc-anchor" href="#radiosleep-switch-to-sleep-mode">§</a>14.8 RadioSleep: Switch to Sleep Mode</h2>
|
||
<p><strong>RadioSleep</strong> switches SX1262 to low-power sleep mode: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/radio.c#L1100-L1109">radio.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Switch to Sleep Mode
|
||
void RadioSleep( void ) {
|
||
SleepParams_t params = { 0 };
|
||
params.Fields.WarmStart = 1;
|
||
|
||
// Switch to Sleep Mode and wait 2 milliseconds
|
||
SX126xSetSleep( params );
|
||
DelayMs( 2 );
|
||
}</code></pre></div>
|
||
<p><strong>SX126xSetSleep</strong> executes the Sleep Command on the LoRa Module: <a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x.c#L253-L268">sx126x.c</a></p>
|
||
<div class="example-wrap"><pre class="language-c"><code>/// Switch to Sleep Mode
|
||
void SX126xSetSleep( SleepParams_t sleepConfig ) {
|
||
// Switch off antenna (not used)
|
||
SX126xAntSwOff( );
|
||
|
||
// Compute Sleep Parameter
|
||
uint8_t value = (
|
||
( ( uint8_t )sleepConfig.Fields.WarmStart << 2 ) |
|
||
( ( uint8_t )sleepConfig.Fields.Reset << 1 ) |
|
||
( ( uint8_t )sleepConfig.Fields.WakeUpRTC )
|
||
);
|
||
|
||
if( sleepConfig.Fields.WarmStart == 0 ) {
|
||
// Force image calibration
|
||
ImageCalibrated = false;
|
||
}
|
||
|
||
// Run Sleep Command
|
||
SX126xWriteCommand( RADIO_SET_SLEEP, &value, 1 );
|
||
SX126xSetOperatingMode( MODE_SLEEP );
|
||
}</code></pre></div>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L324-L329">(<strong>SX126xAntSwOff</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L204-L217">(<strong>SX126xWriteCommand</strong> is defined here)</a></p>
|
||
<p><a href="https://github.com/lupyuen/lora-sx1262/blob/master/src/sx126x-linux.c#L126-L147">(<strong>SX126xSetOperatingMode</strong> is defined here)</a></p>
|
||
<p><img src="https://lupyuen.github.io/images/usb-pinedio2.jpg" alt="Pinebook Pro with PineDio USB Adapter" /></p>
|
||
|
||
|
||
<!-- Begin scripts/rustdoc-after.html: Post-HTML for Custom Markdown files processed by rustdoc, like chip8.md -->
|
||
|
||
<!-- Begin Theme Picker and Prism Theme -->
|
||
<script src="../theme.js"></script>
|
||
<script src="../prism.js"></script>
|
||
<!-- Theme Picker and Prism Theme -->
|
||
|
||
<!-- End scripts/rustdoc-after.html -->
|
||
|
||
|
||
</body>
|
||
</html> |